config.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/hex"
  5. "flag"
  6. "fmt"
  7. "math"
  8. "os"
  9. "runtime"
  10. "strconv"
  11. "strings"
  12. )
  13. func intEnvConfig(i *int, name string) {
  14. if env, err := strconv.Atoi(os.Getenv(name)); err == nil {
  15. *i = env
  16. }
  17. }
  18. func floatEnvConfig(i *float64, name string) {
  19. if env, err := strconv.ParseFloat(os.Getenv(name), 64); err == nil {
  20. *i = env
  21. }
  22. }
  23. func megaIntEnvConfig(f *int, name string) {
  24. if env, err := strconv.ParseFloat(os.Getenv(name), 64); err == nil {
  25. *f = int(env * 1000000)
  26. }
  27. }
  28. func strEnvConfig(s *string, name string) {
  29. if env := os.Getenv(name); len(env) > 0 {
  30. *s = env
  31. }
  32. }
  33. func strSliceEnvConfig(s *[]string, name string) {
  34. if env := os.Getenv(name); len(env) > 0 {
  35. parts := strings.Split(env, ",")
  36. for i, p := range parts {
  37. parts[i] = strings.TrimSpace(p)
  38. }
  39. *s = parts
  40. return
  41. }
  42. *s = []string{}
  43. }
  44. func boolEnvConfig(b *bool, name string) {
  45. if env, err := strconv.ParseBool(os.Getenv(name)); err == nil {
  46. *b = env
  47. }
  48. }
  49. func hexEnvConfig(b *[]securityKey, name string) error {
  50. var err error
  51. if env := os.Getenv(name); len(env) > 0 {
  52. parts := strings.Split(env, ",")
  53. keys := make([]securityKey, len(parts))
  54. for i, part := range parts {
  55. if keys[i], err = hex.DecodeString(part); err != nil {
  56. return fmt.Errorf("%s expected to be hex-encoded strings. Invalid: %s\n", name, part)
  57. }
  58. }
  59. *b = keys
  60. }
  61. return nil
  62. }
  63. func hexFileConfig(b *[]securityKey, filepath string) error {
  64. if len(filepath) == 0 {
  65. return nil
  66. }
  67. f, err := os.Open(filepath)
  68. if err != nil {
  69. return fmt.Errorf("Can't open file %s\n", filepath)
  70. }
  71. keys := []securityKey{}
  72. scanner := bufio.NewScanner(f)
  73. for scanner.Scan() {
  74. part := scanner.Text()
  75. if len(part) == 0 {
  76. continue
  77. }
  78. if key, err := hex.DecodeString(part); err == nil {
  79. keys = append(keys, key)
  80. } else {
  81. return fmt.Errorf("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part)
  82. }
  83. }
  84. if err := scanner.Err(); err != nil {
  85. return fmt.Errorf("Failed to read file %s: %s", filepath, err)
  86. }
  87. *b = keys
  88. return nil
  89. }
  90. func presetEnvConfig(p presets, name string) error {
  91. if env := os.Getenv(name); len(env) > 0 {
  92. presetStrings := strings.Split(env, ",")
  93. for _, presetStr := range presetStrings {
  94. if err := parsePreset(p, presetStr); err != nil {
  95. return fmt.Errorf(err.Error())
  96. }
  97. }
  98. }
  99. return nil
  100. }
  101. func presetFileConfig(p presets, filepath string) error {
  102. if len(filepath) == 0 {
  103. return nil
  104. }
  105. f, err := os.Open(filepath)
  106. if err != nil {
  107. return fmt.Errorf("Can't open file %s\n", filepath)
  108. }
  109. scanner := bufio.NewScanner(f)
  110. for scanner.Scan() {
  111. if err := parsePreset(p, scanner.Text()); err != nil {
  112. return fmt.Errorf(err.Error())
  113. }
  114. }
  115. if err := scanner.Err(); err != nil {
  116. return fmt.Errorf("Failed to read presets file: %s", err)
  117. }
  118. return nil
  119. }
  120. type config struct {
  121. Network string
  122. Bind string
  123. ReadTimeout int
  124. WriteTimeout int
  125. KeepAliveTimeout int
  126. DownloadTimeout int
  127. Concurrency int
  128. MaxClients int
  129. TTL int
  130. CacheControlPassthrough bool
  131. SoReuseport bool
  132. PathPrefix string
  133. MaxSrcDimension int
  134. MaxSrcResolution int
  135. MaxSrcFileSize int
  136. MaxAnimationFrames int
  137. MaxSvgCheckBytes int
  138. JpegProgressive bool
  139. PngInterlaced bool
  140. PngQuantize bool
  141. PngQuantizationColors int
  142. Quality int
  143. GZipCompression int
  144. StripMetadata bool
  145. EnableWebpDetection bool
  146. EnforceWebp bool
  147. EnableClientHints bool
  148. UseLinearColorspace bool
  149. DisableShrinkOnLoad bool
  150. Keys []securityKey
  151. Salts []securityKey
  152. AllowInsecure bool
  153. SignatureSize int
  154. Secret string
  155. AllowOrigin string
  156. UserAgent string
  157. IgnoreSslVerification bool
  158. DevelopmentErrorsMode bool
  159. AllowedSources []string
  160. LocalFileSystemRoot string
  161. S3Enabled bool
  162. S3Region string
  163. S3Endpoint string
  164. GCSEnabled bool
  165. GCSKey string
  166. ETagEnabled bool
  167. BaseURL string
  168. Presets presets
  169. OnlyPresets bool
  170. WatermarkData string
  171. WatermarkPath string
  172. WatermarkURL string
  173. WatermarkOpacity float64
  174. FallbackImageData string
  175. FallbackImagePath string
  176. FallbackImageURL string
  177. NewRelicAppName string
  178. NewRelicKey string
  179. PrometheusBind string
  180. PrometheusNamespace string
  181. BugsnagKey string
  182. BugsnagStage string
  183. HoneybadgerKey string
  184. HoneybadgerEnv string
  185. SentryDSN string
  186. SentryEnvironment string
  187. SentryRelease string
  188. ReportDownloadingErrors bool
  189. FreeMemoryInterval int
  190. DownloadBufferSize int
  191. GZipBufferSize int
  192. BufferPoolCalibrationThreshold int
  193. }
  194. var conf = config{
  195. Network: "tcp",
  196. Bind: ":8080",
  197. ReadTimeout: 10,
  198. WriteTimeout: 10,
  199. KeepAliveTimeout: 10,
  200. DownloadTimeout: 5,
  201. Concurrency: runtime.NumCPU() * 2,
  202. TTL: 3600,
  203. MaxSrcResolution: 16800000,
  204. MaxAnimationFrames: 1,
  205. MaxSvgCheckBytes: 32 * 1024,
  206. SignatureSize: 32,
  207. PngQuantizationColors: 256,
  208. Quality: 80,
  209. StripMetadata: true,
  210. UserAgent: fmt.Sprintf("imgproxy/%s", version),
  211. Presets: make(presets),
  212. WatermarkOpacity: 1,
  213. BugsnagStage: "production",
  214. HoneybadgerEnv: "production",
  215. SentryEnvironment: "production",
  216. SentryRelease: fmt.Sprintf("imgproxy/%s", version),
  217. ReportDownloadingErrors: true,
  218. FreeMemoryInterval: 10,
  219. BufferPoolCalibrationThreshold: 1024,
  220. }
  221. func configure() error {
  222. keyPath := flag.String("keypath", "", "path of the file with hex-encoded key")
  223. saltPath := flag.String("saltpath", "", "path of the file with hex-encoded salt")
  224. presetsPath := flag.String("presets", "", "path of the file with presets")
  225. flag.Parse()
  226. if port := os.Getenv("PORT"); len(port) > 0 {
  227. conf.Bind = fmt.Sprintf(":%s", port)
  228. }
  229. strEnvConfig(&conf.Network, "IMGPROXY_NETWORK")
  230. strEnvConfig(&conf.Bind, "IMGPROXY_BIND")
  231. intEnvConfig(&conf.ReadTimeout, "IMGPROXY_READ_TIMEOUT")
  232. intEnvConfig(&conf.WriteTimeout, "IMGPROXY_WRITE_TIMEOUT")
  233. intEnvConfig(&conf.KeepAliveTimeout, "IMGPROXY_KEEP_ALIVE_TIMEOUT")
  234. intEnvConfig(&conf.DownloadTimeout, "IMGPROXY_DOWNLOAD_TIMEOUT")
  235. intEnvConfig(&conf.Concurrency, "IMGPROXY_CONCURRENCY")
  236. intEnvConfig(&conf.MaxClients, "IMGPROXY_MAX_CLIENTS")
  237. intEnvConfig(&conf.TTL, "IMGPROXY_TTL")
  238. boolEnvConfig(&conf.CacheControlPassthrough, "IMGPROXY_CACHE_CONTROL_PASSTHROUGH")
  239. boolEnvConfig(&conf.SoReuseport, "IMGPROXY_SO_REUSEPORT")
  240. strEnvConfig(&conf.PathPrefix, "IMGPROXY_PATH_PREFIX")
  241. intEnvConfig(&conf.MaxSrcDimension, "IMGPROXY_MAX_SRC_DIMENSION")
  242. megaIntEnvConfig(&conf.MaxSrcResolution, "IMGPROXY_MAX_SRC_RESOLUTION")
  243. intEnvConfig(&conf.MaxSrcFileSize, "IMGPROXY_MAX_SRC_FILE_SIZE")
  244. intEnvConfig(&conf.MaxSvgCheckBytes, "IMGPROXY_MAX_SVG_CHECK_BYTES")
  245. if _, ok := os.LookupEnv("IMGPROXY_MAX_GIF_FRAMES"); ok {
  246. logWarning("`IMGPROXY_MAX_GIF_FRAMES` is deprecated and will be removed in future versions. Use `IMGPROXY_MAX_ANIMATION_FRAMES` instead")
  247. intEnvConfig(&conf.MaxAnimationFrames, "IMGPROXY_MAX_GIF_FRAMES")
  248. }
  249. intEnvConfig(&conf.MaxAnimationFrames, "IMGPROXY_MAX_ANIMATION_FRAMES")
  250. strSliceEnvConfig(&conf.AllowedSources, "IMGPROXY_ALLOWED_SOURCES")
  251. boolEnvConfig(&conf.JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
  252. boolEnvConfig(&conf.PngInterlaced, "IMGPROXY_PNG_INTERLACED")
  253. boolEnvConfig(&conf.PngQuantize, "IMGPROXY_PNG_QUANTIZE")
  254. intEnvConfig(&conf.PngQuantizationColors, "IMGPROXY_PNG_QUANTIZATION_COLORS")
  255. intEnvConfig(&conf.Quality, "IMGPROXY_QUALITY")
  256. intEnvConfig(&conf.GZipCompression, "IMGPROXY_GZIP_COMPRESSION")
  257. boolEnvConfig(&conf.StripMetadata, "IMGPROXY_STRIP_METADATA")
  258. boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
  259. boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
  260. boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")
  261. boolEnvConfig(&conf.UseLinearColorspace, "IMGPROXY_USE_LINEAR_COLORSPACE")
  262. boolEnvConfig(&conf.DisableShrinkOnLoad, "IMGPROXY_DISABLE_SHRINK_ON_LOAD")
  263. if err := hexEnvConfig(&conf.Keys, "IMGPROXY_KEY"); err != nil {
  264. return err
  265. }
  266. if err := hexEnvConfig(&conf.Salts, "IMGPROXY_SALT"); err != nil {
  267. return err
  268. }
  269. intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE")
  270. if err := hexFileConfig(&conf.Keys, *keyPath); err != nil {
  271. return err
  272. }
  273. if err := hexFileConfig(&conf.Salts, *saltPath); err != nil {
  274. return err
  275. }
  276. strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
  277. strEnvConfig(&conf.AllowOrigin, "IMGPROXY_ALLOW_ORIGIN")
  278. strEnvConfig(&conf.UserAgent, "IMGPROXY_USER_AGENT")
  279. boolEnvConfig(&conf.IgnoreSslVerification, "IMGPROXY_IGNORE_SSL_VERIFICATION")
  280. boolEnvConfig(&conf.DevelopmentErrorsMode, "IMGPROXY_DEVELOPMENT_ERRORS_MODE")
  281. strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT")
  282. boolEnvConfig(&conf.S3Enabled, "IMGPROXY_USE_S3")
  283. strEnvConfig(&conf.S3Region, "IMGPROXY_S3_REGION")
  284. strEnvConfig(&conf.S3Endpoint, "IMGPROXY_S3_ENDPOINT")
  285. boolEnvConfig(&conf.GCSEnabled, "IMGPROXY_USE_GCS")
  286. strEnvConfig(&conf.GCSKey, "IMGPROXY_GCS_KEY")
  287. boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG")
  288. strEnvConfig(&conf.BaseURL, "IMGPROXY_BASE_URL")
  289. if err := presetEnvConfig(conf.Presets, "IMGPROXY_PRESETS"); err != nil {
  290. return err
  291. }
  292. if err := presetFileConfig(conf.Presets, *presetsPath); err != nil {
  293. return err
  294. }
  295. boolEnvConfig(&conf.OnlyPresets, "IMGPROXY_ONLY_PRESETS")
  296. strEnvConfig(&conf.WatermarkData, "IMGPROXY_WATERMARK_DATA")
  297. strEnvConfig(&conf.WatermarkPath, "IMGPROXY_WATERMARK_PATH")
  298. strEnvConfig(&conf.WatermarkURL, "IMGPROXY_WATERMARK_URL")
  299. floatEnvConfig(&conf.WatermarkOpacity, "IMGPROXY_WATERMARK_OPACITY")
  300. strEnvConfig(&conf.FallbackImageData, "IMGPROXY_FALLBACK_IMAGE_DATA")
  301. strEnvConfig(&conf.FallbackImagePath, "IMGPROXY_FALLBACK_IMAGE_PATH")
  302. strEnvConfig(&conf.FallbackImageURL, "IMGPROXY_FALLBACK_IMAGE_URL")
  303. strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
  304. strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
  305. strEnvConfig(&conf.PrometheusBind, "IMGPROXY_PROMETHEUS_BIND")
  306. strEnvConfig(&conf.PrometheusNamespace, "IMGPROXY_PROMETHEUS_NAMESPACE")
  307. strEnvConfig(&conf.BugsnagKey, "IMGPROXY_BUGSNAG_KEY")
  308. strEnvConfig(&conf.BugsnagStage, "IMGPROXY_BUGSNAG_STAGE")
  309. strEnvConfig(&conf.HoneybadgerKey, "IMGPROXY_HONEYBADGER_KEY")
  310. strEnvConfig(&conf.HoneybadgerEnv, "IMGPROXY_HONEYBADGER_ENV")
  311. strEnvConfig(&conf.SentryDSN, "IMGPROXY_SENTRY_DSN")
  312. strEnvConfig(&conf.SentryEnvironment, "IMGPROXY_SENTRY_ENVIRONMENT")
  313. strEnvConfig(&conf.SentryRelease, "IMGPROXY_SENTRY_RELEASE")
  314. boolEnvConfig(&conf.ReportDownloadingErrors, "IMGPROXY_REPORT_DOWNLOADING_ERRORS")
  315. intEnvConfig(&conf.FreeMemoryInterval, "IMGPROXY_FREE_MEMORY_INTERVAL")
  316. intEnvConfig(&conf.DownloadBufferSize, "IMGPROXY_DOWNLOAD_BUFFER_SIZE")
  317. intEnvConfig(&conf.GZipBufferSize, "IMGPROXY_GZIP_BUFFER_SIZE")
  318. intEnvConfig(&conf.BufferPoolCalibrationThreshold, "IMGPROXY_BUFFER_POOL_CALIBRATION_THRESHOLD")
  319. if len(conf.Keys) != len(conf.Salts) {
  320. return fmt.Errorf("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts))
  321. }
  322. if len(conf.Keys) == 0 {
  323. logWarning("No keys defined, so signature checking is disabled")
  324. conf.AllowInsecure = true
  325. }
  326. if len(conf.Salts) == 0 {
  327. logWarning("No salts defined, so signature checking is disabled")
  328. conf.AllowInsecure = true
  329. }
  330. if conf.SignatureSize < 1 || conf.SignatureSize > 32 {
  331. return fmt.Errorf("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize)
  332. }
  333. if len(conf.Bind) == 0 {
  334. return fmt.Errorf("Bind address is not defined")
  335. }
  336. if conf.ReadTimeout <= 0 {
  337. return fmt.Errorf("Read timeout should be greater than 0, now - %d\n", conf.ReadTimeout)
  338. }
  339. if conf.WriteTimeout <= 0 {
  340. return fmt.Errorf("Write timeout should be greater than 0, now - %d\n", conf.WriteTimeout)
  341. }
  342. if conf.KeepAliveTimeout < 0 {
  343. return fmt.Errorf("KeepAlive timeout should be greater than or equal to 0, now - %d\n", conf.KeepAliveTimeout)
  344. }
  345. if conf.DownloadTimeout <= 0 {
  346. return fmt.Errorf("Download timeout should be greater than 0, now - %d\n", conf.DownloadTimeout)
  347. }
  348. if conf.Concurrency <= 0 {
  349. return fmt.Errorf("Concurrency should be greater than 0, now - %d\n", conf.Concurrency)
  350. }
  351. if conf.MaxClients <= 0 {
  352. conf.MaxClients = conf.Concurrency * 10
  353. }
  354. if conf.TTL <= 0 {
  355. return fmt.Errorf("TTL should be greater than 0, now - %d\n", conf.TTL)
  356. }
  357. if conf.MaxSrcDimension < 0 {
  358. return fmt.Errorf("Max src dimension should be greater than or equal to 0, now - %d\n", conf.MaxSrcDimension)
  359. } else if conf.MaxSrcDimension > 0 {
  360. logWarning("IMGPROXY_MAX_SRC_DIMENSION is deprecated and can be removed in future versions. Use IMGPROXY_MAX_SRC_RESOLUTION")
  361. }
  362. if conf.MaxSrcResolution <= 0 {
  363. return fmt.Errorf("Max src resolution should be greater than 0, now - %d\n", conf.MaxSrcResolution)
  364. }
  365. if conf.MaxSrcFileSize < 0 {
  366. return fmt.Errorf("Max src file size should be greater than or equal to 0, now - %d\n", conf.MaxSrcFileSize)
  367. }
  368. if conf.MaxAnimationFrames <= 0 {
  369. return fmt.Errorf("Max animation frames should be greater than 0, now - %d\n", conf.MaxAnimationFrames)
  370. }
  371. if conf.PngQuantizationColors < 2 {
  372. return fmt.Errorf("Png quantization colors should be greater than 1, now - %d\n", conf.PngQuantizationColors)
  373. } else if conf.PngQuantizationColors > 256 {
  374. return fmt.Errorf("Png quantization colors can't be greater than 256, now - %d\n", conf.PngQuantizationColors)
  375. }
  376. if conf.Quality <= 0 {
  377. return fmt.Errorf("Quality should be greater than 0, now - %d\n", conf.Quality)
  378. } else if conf.Quality > 100 {
  379. return fmt.Errorf("Quality can't be greater than 100, now - %d\n", conf.Quality)
  380. }
  381. if conf.GZipCompression < 0 {
  382. return fmt.Errorf("GZip compression should be greater than or equal to 0, now - %d\n", conf.GZipCompression)
  383. } else if conf.GZipCompression > 9 {
  384. return fmt.Errorf("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression)
  385. }
  386. if conf.GZipCompression > 0 {
  387. logWarning("GZip compression is deprecated and can be removed in future versions")
  388. }
  389. if conf.IgnoreSslVerification {
  390. logWarning("Ignoring SSL verification is very unsafe")
  391. }
  392. if conf.LocalFileSystemRoot != "" {
  393. stat, err := os.Stat(conf.LocalFileSystemRoot)
  394. if err != nil {
  395. return fmt.Errorf("Cannot use local directory: %s", err)
  396. }
  397. if !stat.IsDir() {
  398. return fmt.Errorf("Cannot use local directory: not a directory")
  399. }
  400. if conf.LocalFileSystemRoot == "/" {
  401. logWarning("Exposing root via IMGPROXY_LOCAL_FILESYSTEM_ROOT is unsafe")
  402. }
  403. }
  404. if _, ok := os.LookupEnv("IMGPROXY_USE_GCS"); !ok && len(conf.GCSKey) > 0 {
  405. logWarning("Set IMGPROXY_USE_GCS to true since it may be required by future versions to enable GCS support")
  406. conf.GCSEnabled = true
  407. }
  408. if conf.WatermarkOpacity <= 0 {
  409. return fmt.Errorf("Watermark opacity should be greater than 0")
  410. } else if conf.WatermarkOpacity > 1 {
  411. return fmt.Errorf("Watermark opacity should be less than or equal to 1")
  412. }
  413. if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind {
  414. return fmt.Errorf("Can't use the same binding for the main server and Prometheus")
  415. }
  416. if conf.FreeMemoryInterval <= 0 {
  417. return fmt.Errorf("Free memory interval should be greater than zero")
  418. }
  419. if conf.DownloadBufferSize < 0 {
  420. return fmt.Errorf("Download buffer size should be greater than or equal to 0")
  421. } else if conf.DownloadBufferSize > math.MaxInt32 {
  422. return fmt.Errorf("Download buffer size can't be greater than %d", math.MaxInt32)
  423. }
  424. if conf.GZipBufferSize < 0 {
  425. return fmt.Errorf("GZip buffer size should be greater than or equal to 0")
  426. } else if conf.GZipBufferSize > math.MaxInt32 {
  427. return fmt.Errorf("GZip buffer size can't be greater than %d", math.MaxInt32)
  428. }
  429. if conf.BufferPoolCalibrationThreshold < 64 {
  430. return fmt.Errorf("Buffer pool calibration threshold should be greater than or equal to 64")
  431. }
  432. return nil
  433. }