config.go 14 KB

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