config.go 13 KB

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