config.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. *b = false
  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. WaitTimeout int
  109. WriteTimeout int
  110. DownloadTimeout int
  111. Concurrency int
  112. MaxClients int
  113. TTL int
  114. MaxSrcDimension int
  115. MaxSrcResolution int
  116. MaxSrcFileSize int
  117. MaxGifFrames int
  118. JpegProgressive bool
  119. PngInterlaced bool
  120. Quality int
  121. GZipCompression int
  122. EnableWebpDetection bool
  123. EnforceWebp bool
  124. EnableClientHints bool
  125. Keys []securityKey
  126. Salts []securityKey
  127. AllowInsecure bool
  128. SignatureSize int
  129. Secret string
  130. AllowOrigin string
  131. UserAgent string
  132. IgnoreSslVerification bool
  133. LocalFileSystemRoot string
  134. S3Enabled bool
  135. S3Region string
  136. S3Endpoint string
  137. GCSKey string
  138. ETagEnabled bool
  139. BaseURL string
  140. Presets presets
  141. WatermarkData string
  142. WatermarkPath string
  143. WatermarkURL string
  144. WatermarkOpacity float64
  145. NewRelicAppName string
  146. NewRelicKey string
  147. PrometheusBind string
  148. BugsnagKey string
  149. BugsnagStage string
  150. HoneybadgerKey string
  151. HoneybadgerEnv string
  152. SentryDSN string
  153. SentryEnvironment string
  154. SentryRelease string
  155. FreeMemoryInterval int
  156. DownloadBufferSize int
  157. GZipBufferSize int
  158. }
  159. var conf = config{
  160. Bind: ":8080",
  161. ReadTimeout: 10,
  162. WriteTimeout: 10,
  163. DownloadTimeout: 5,
  164. Concurrency: runtime.NumCPU() * 2,
  165. TTL: 3600,
  166. IgnoreSslVerification: false,
  167. MaxSrcResolution: 16800000,
  168. MaxGifFrames: 1,
  169. AllowInsecure: false,
  170. SignatureSize: 32,
  171. Quality: 80,
  172. GZipCompression: 5,
  173. UserAgent: fmt.Sprintf("imgproxy/%s", version),
  174. ETagEnabled: false,
  175. S3Enabled: false,
  176. WatermarkOpacity: 1,
  177. BugsnagStage: "production",
  178. HoneybadgerEnv: "production",
  179. SentryEnvironment: "production",
  180. SentryRelease: fmt.Sprintf("imgproxy/%s", version),
  181. FreeMemoryInterval: 10,
  182. }
  183. func init() {
  184. initSyslog()
  185. keyPath := flag.String("keypath", "", "path of the file with hex-encoded key")
  186. saltPath := flag.String("saltpath", "", "path of the file with hex-encoded salt")
  187. presetsPath := flag.String("presets", "", "path of the file with presets")
  188. showVersion := flag.Bool("v", false, "show version")
  189. flag.Parse()
  190. if *showVersion {
  191. fmt.Println(version)
  192. os.Exit(0)
  193. }
  194. if port := os.Getenv("PORT"); len(port) > 0 {
  195. conf.Bind = fmt.Sprintf(":%s", port)
  196. }
  197. strEnvConfig(&conf.Bind, "IMGPROXY_BIND")
  198. intEnvConfig(&conf.ReadTimeout, "IMGPROXY_READ_TIMEOUT")
  199. intEnvConfig(&conf.WriteTimeout, "IMGPROXY_WRITE_TIMEOUT")
  200. intEnvConfig(&conf.DownloadTimeout, "IMGPROXY_DOWNLOAD_TIMEOUT")
  201. intEnvConfig(&conf.Concurrency, "IMGPROXY_CONCURRENCY")
  202. intEnvConfig(&conf.MaxClients, "IMGPROXY_MAX_CLIENTS")
  203. intEnvConfig(&conf.TTL, "IMGPROXY_TTL")
  204. intEnvConfig(&conf.MaxSrcDimension, "IMGPROXY_MAX_SRC_DIMENSION")
  205. megaIntEnvConfig(&conf.MaxSrcResolution, "IMGPROXY_MAX_SRC_RESOLUTION")
  206. intEnvConfig(&conf.MaxSrcFileSize, "IMGPROXY_MAX_SRC_FILE_SIZE")
  207. intEnvConfig(&conf.MaxGifFrames, "IMGPROXY_MAX_GIF_FRAMES")
  208. boolEnvConfig(&conf.JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
  209. boolEnvConfig(&conf.PngInterlaced, "IMGPROXY_PNG_INTERLACED")
  210. intEnvConfig(&conf.Quality, "IMGPROXY_QUALITY")
  211. intEnvConfig(&conf.GZipCompression, "IMGPROXY_GZIP_COMPRESSION")
  212. boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
  213. boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
  214. boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")
  215. hexEnvConfig(&conf.Keys, "IMGPROXY_KEY")
  216. hexEnvConfig(&conf.Salts, "IMGPROXY_SALT")
  217. intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE")
  218. hexFileConfig(&conf.Keys, *keyPath)
  219. hexFileConfig(&conf.Salts, *saltPath)
  220. strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
  221. strEnvConfig(&conf.AllowOrigin, "IMGPROXY_ALLOW_ORIGIN")
  222. strEnvConfig(&conf.UserAgent, "IMGPROXY_USER_AGENT")
  223. boolEnvConfig(&conf.IgnoreSslVerification, "IMGPROXY_IGNORE_SSL_VERIFICATION")
  224. strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT")
  225. boolEnvConfig(&conf.S3Enabled, "IMGPROXY_USE_S3")
  226. strEnvConfig(&conf.S3Region, "IMGPROXY_S3_REGION")
  227. strEnvConfig(&conf.S3Endpoint, "IMGPROXY_S3_ENDPOINT")
  228. strEnvConfig(&conf.GCSKey, "IMGPROXY_GCS_KEY")
  229. boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG")
  230. strEnvConfig(&conf.BaseURL, "IMGPROXY_BASE_URL")
  231. conf.Presets = make(presets)
  232. presetEnvConfig(conf.Presets, "IMGPROXY_PRESETS")
  233. presetFileConfig(conf.Presets, *presetsPath)
  234. strEnvConfig(&conf.WatermarkData, "IMGPROXY_WATERMARK_DATA")
  235. strEnvConfig(&conf.WatermarkPath, "IMGPROXY_WATERMARK_PATH")
  236. strEnvConfig(&conf.WatermarkURL, "IMGPROXY_WATERMARK_URL")
  237. floatEnvConfig(&conf.WatermarkOpacity, "IMGPROXY_WATERMARK_OPACITY")
  238. strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
  239. strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
  240. strEnvConfig(&conf.PrometheusBind, "IMGPROXY_PROMETHEUS_BIND")
  241. strEnvConfig(&conf.BugsnagKey, "IMGPROXY_BUGSNAG_KEY")
  242. strEnvConfig(&conf.BugsnagStage, "IMGPROXY_BUGSNAG_STAGE")
  243. strEnvConfig(&conf.HoneybadgerKey, "IMGPROXY_HONEYBADGER_KEY")
  244. strEnvConfig(&conf.HoneybadgerEnv, "IMGPROXY_HONEYBADGER_ENV")
  245. strEnvConfig(&conf.SentryDSN, "IMGPROXY_SENTRY_DSN")
  246. strEnvConfig(&conf.SentryEnvironment, "IMGPROXY_SENTRY_ENVIRONMENT")
  247. strEnvConfig(&conf.SentryRelease, "IMGPROXY_SENTRY_RELEASE")
  248. intEnvConfig(&conf.FreeMemoryInterval, "IMGPROXY_FREE_MEMORY_INTERVAL")
  249. intEnvConfig(&conf.DownloadBufferSize, "IMGPROXY_DOWNLOAD_BUFFER_SIZE")
  250. intEnvConfig(&conf.GZipBufferSize, "IMGPROXY_GZIP_BUFFER_SIZE")
  251. if len(conf.Keys) != len(conf.Salts) {
  252. logFatal("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts))
  253. }
  254. if len(conf.Keys) == 0 {
  255. logWarning("No keys defined, so signature checking is disabled")
  256. conf.AllowInsecure = true
  257. }
  258. if len(conf.Salts) == 0 {
  259. logWarning("No salts defined, so signature checking is disabled")
  260. conf.AllowInsecure = true
  261. }
  262. if conf.SignatureSize < 1 || conf.SignatureSize > 32 {
  263. logFatal("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize)
  264. }
  265. if len(conf.Bind) == 0 {
  266. logFatal("Bind address is not defined")
  267. }
  268. if conf.ReadTimeout <= 0 {
  269. logFatal("Read timeout should be greater than 0, now - %d\n", conf.ReadTimeout)
  270. }
  271. if conf.WriteTimeout <= 0 {
  272. logFatal("Write timeout should be greater than 0, now - %d\n", conf.WriteTimeout)
  273. }
  274. if conf.DownloadTimeout <= 0 {
  275. logFatal("Download timeout should be greater than 0, now - %d\n", conf.DownloadTimeout)
  276. }
  277. if conf.Concurrency <= 0 {
  278. logFatal("Concurrency should be greater than 0, now - %d\n", conf.Concurrency)
  279. }
  280. if conf.MaxClients <= 0 {
  281. conf.MaxClients = conf.Concurrency * 10
  282. }
  283. if conf.TTL <= 0 {
  284. logFatal("TTL should be greater than 0, now - %d\n", conf.TTL)
  285. }
  286. if conf.MaxSrcDimension < 0 {
  287. logFatal("Max src dimension should be greater than or equal to 0, now - %d\n", conf.MaxSrcDimension)
  288. } else if conf.MaxSrcDimension > 0 {
  289. logWarning("IMGPROXY_MAX_SRC_DIMENSION is deprecated and can be removed in future versions. Use IMGPROXY_MAX_SRC_RESOLUTION")
  290. }
  291. if conf.MaxSrcResolution <= 0 {
  292. logFatal("Max src resolution should be greater than 0, now - %d\n", conf.MaxSrcResolution)
  293. }
  294. if conf.MaxSrcFileSize < 0 {
  295. logFatal("Max src file size should be greater than or equal to 0, now - %d\n", conf.MaxSrcFileSize)
  296. }
  297. if conf.MaxGifFrames <= 0 {
  298. logFatal("Max GIF frames should be greater than 0, now - %d\n", conf.MaxGifFrames)
  299. }
  300. if conf.Quality <= 0 {
  301. logFatal("Quality should be greater than 0, now - %d\n", conf.Quality)
  302. } else if conf.Quality > 100 {
  303. logFatal("Quality can't be greater than 100, now - %d\n", conf.Quality)
  304. }
  305. if conf.GZipCompression < 0 {
  306. logFatal("GZip compression should be greater than or quual to 0, now - %d\n", conf.GZipCompression)
  307. } else if conf.GZipCompression > 9 {
  308. logFatal("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression)
  309. }
  310. if conf.IgnoreSslVerification {
  311. logWarning("Ignoring SSL verification is very unsafe")
  312. }
  313. if conf.LocalFileSystemRoot != "" {
  314. stat, err := os.Stat(conf.LocalFileSystemRoot)
  315. if err != nil {
  316. logFatal("Cannot use local directory: %s", err)
  317. } else {
  318. if !stat.IsDir() {
  319. logFatal("Cannot use local directory: not a directory")
  320. }
  321. }
  322. if conf.LocalFileSystemRoot == "/" {
  323. logNotice("Exposing root via IMGPROXY_LOCAL_FILESYSTEM_ROOT is unsafe")
  324. }
  325. }
  326. if err := checkPresets(conf.Presets); err != nil {
  327. logFatal(err.Error())
  328. }
  329. if conf.WatermarkOpacity <= 0 {
  330. logFatal("Watermark opacity should be greater than 0")
  331. } else if conf.WatermarkOpacity > 1 {
  332. logFatal("Watermark opacity should be less than or equal to 1")
  333. }
  334. if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind {
  335. logFatal("Can't use the same binding for the main server and Prometheus")
  336. }
  337. if conf.FreeMemoryInterval <= 0 {
  338. logFatal("Free memory interval should be greater than zero")
  339. }
  340. if conf.DownloadBufferSize < 0 {
  341. logFatal("Download buffer size should be greater than or quual to 0")
  342. } else if conf.DownloadBufferSize > int(^uint32(0)) {
  343. logFatal("Download buffer size can't be creater than %d", ^uint32(0))
  344. }
  345. if conf.GZipBufferSize < 0 {
  346. logFatal("GZip buffer size should be greater than or quual to 0")
  347. } else if conf.GZipBufferSize > int(^uint32(0)) {
  348. logFatal("GZip buffer size can't be creater than %d", ^uint32(0))
  349. }
  350. initDownloading()
  351. initNewrelic()
  352. initPrometheus()
  353. initErrorsReporting()
  354. initVips()
  355. }