config.go 15 KB

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