config.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/hex"
  5. "flag"
  6. "fmt"
  7. "log"
  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. *b = false
  35. if env, err := strconv.ParseBool(os.Getenv(name)); err == nil {
  36. *b = env
  37. }
  38. }
  39. func hexEnvConfig(b *[]securityKey, name string) {
  40. var err error
  41. if env := os.Getenv(name); len(env) > 0 {
  42. parts := strings.Split(env, ",")
  43. keys := make([]securityKey, len(parts))
  44. for i, part := range parts {
  45. if keys[i], err = hex.DecodeString(part); err != nil {
  46. log.Fatalf("%s expected to be hex-encoded strings. Invalid: %s\n", name, part)
  47. }
  48. }
  49. *b = keys
  50. }
  51. }
  52. func hexFileConfig(b *[]securityKey, filepath string) {
  53. if len(filepath) == 0 {
  54. return
  55. }
  56. f, err := os.Open(filepath)
  57. if err != nil {
  58. log.Fatalf("Can't open file %s\n", filepath)
  59. }
  60. keys := []securityKey{}
  61. scanner := bufio.NewScanner(f)
  62. for scanner.Scan() {
  63. part := scanner.Text()
  64. if len(part) == 0 {
  65. continue
  66. }
  67. if key, err := hex.DecodeString(part); err == nil {
  68. keys = append(keys, key)
  69. } else {
  70. log.Fatalf("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part)
  71. }
  72. }
  73. if err := scanner.Err(); err != nil {
  74. log.Fatalf("Failed to read file %s: %s", filepath, err)
  75. }
  76. *b = keys
  77. }
  78. func presetEnvConfig(p presets, name string) {
  79. if env := os.Getenv(name); len(env) > 0 {
  80. presetStrings := strings.Split(env, ",")
  81. for _, presetStr := range presetStrings {
  82. if err := parsePreset(p, presetStr); err != nil {
  83. log.Fatalln(err)
  84. }
  85. }
  86. }
  87. }
  88. func presetFileConfig(p presets, filepath string) {
  89. if len(filepath) == 0 {
  90. return
  91. }
  92. f, err := os.Open(filepath)
  93. if err != nil {
  94. log.Fatalf("Can't open file %s\n", filepath)
  95. }
  96. scanner := bufio.NewScanner(f)
  97. for scanner.Scan() {
  98. if err := parsePreset(p, scanner.Text()); err != nil {
  99. log.Fatalln(err)
  100. }
  101. }
  102. if err := scanner.Err(); err != nil {
  103. log.Fatalf("Failed to read presets file: %s", err)
  104. }
  105. }
  106. type config struct {
  107. Bind string
  108. ReadTimeout int
  109. WaitTimeout int
  110. WriteTimeout int
  111. DownloadTimeout int
  112. Concurrency int
  113. MaxClients int
  114. TTL int
  115. MaxSrcDimension int
  116. MaxSrcResolution int
  117. JpegProgressive bool
  118. PngInterlaced bool
  119. Quality int
  120. GZipCompression int
  121. EnableWebpDetection bool
  122. EnforceWebp bool
  123. EnableClientHints bool
  124. Keys []securityKey
  125. Salts []securityKey
  126. AllowInsecure bool
  127. SignatureSize int
  128. Secret string
  129. AllowOrigin string
  130. UserAgent string
  131. IgnoreSslVerification bool
  132. LocalFileSystemRoot string
  133. S3Enabled bool
  134. S3Region string
  135. S3Endpoint string
  136. GCSKey string
  137. ETagEnabled bool
  138. BaseURL string
  139. Presets presets
  140. WatermarkData string
  141. WatermarkPath string
  142. WatermarkURL string
  143. WatermarkOpacity float64
  144. NewRelicAppName string
  145. NewRelicKey string
  146. PrometheusBind string
  147. BugsnagKey string
  148. BugsnagStage string
  149. HoneybadgerKey string
  150. HoneybadgerEnv string
  151. }
  152. var conf = config{
  153. Bind: ":8080",
  154. ReadTimeout: 10,
  155. WriteTimeout: 10,
  156. DownloadTimeout: 5,
  157. Concurrency: runtime.NumCPU() * 2,
  158. TTL: 3600,
  159. IgnoreSslVerification: false,
  160. MaxSrcDimension: 8192,
  161. MaxSrcResolution: 16800000,
  162. AllowInsecure: false,
  163. SignatureSize: 32,
  164. Quality: 80,
  165. GZipCompression: 5,
  166. UserAgent: fmt.Sprintf("imgproxy/%s", version),
  167. ETagEnabled: false,
  168. S3Enabled: false,
  169. WatermarkOpacity: 1,
  170. BugsnagStage: "production",
  171. HoneybadgerEnv: "production",
  172. }
  173. func init() {
  174. keyPath := flag.String("keypath", "", "path of the file with hex-encoded key")
  175. saltPath := flag.String("saltpath", "", "path of the file with hex-encoded salt")
  176. presetsPath := flag.String("presets", "", "path of the file with presets")
  177. showVersion := flag.Bool("v", false, "show version")
  178. flag.Parse()
  179. if *showVersion {
  180. fmt.Println(version)
  181. os.Exit(0)
  182. }
  183. if port := os.Getenv("PORT"); len(port) > 0 {
  184. conf.Bind = fmt.Sprintf(":%s", port)
  185. }
  186. strEnvConfig(&conf.Bind, "IMGPROXY_BIND")
  187. intEnvConfig(&conf.ReadTimeout, "IMGPROXY_READ_TIMEOUT")
  188. intEnvConfig(&conf.WriteTimeout, "IMGPROXY_WRITE_TIMEOUT")
  189. intEnvConfig(&conf.DownloadTimeout, "IMGPROXY_DOWNLOAD_TIMEOUT")
  190. intEnvConfig(&conf.Concurrency, "IMGPROXY_CONCURRENCY")
  191. intEnvConfig(&conf.MaxClients, "IMGPROXY_MAX_CLIENTS")
  192. intEnvConfig(&conf.TTL, "IMGPROXY_TTL")
  193. intEnvConfig(&conf.MaxSrcDimension, "IMGPROXY_MAX_SRC_DIMENSION")
  194. megaIntEnvConfig(&conf.MaxSrcResolution, "IMGPROXY_MAX_SRC_RESOLUTION")
  195. boolEnvConfig(&conf.JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
  196. boolEnvConfig(&conf.PngInterlaced, "IMGPROXY_PNG_INTERLACED")
  197. intEnvConfig(&conf.Quality, "IMGPROXY_QUALITY")
  198. intEnvConfig(&conf.GZipCompression, "IMGPROXY_GZIP_COMPRESSION")
  199. boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
  200. boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
  201. boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")
  202. hexEnvConfig(&conf.Keys, "IMGPROXY_KEY")
  203. hexEnvConfig(&conf.Salts, "IMGPROXY_SALT")
  204. intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE")
  205. hexFileConfig(&conf.Keys, *keyPath)
  206. hexFileConfig(&conf.Salts, *saltPath)
  207. strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
  208. strEnvConfig(&conf.AllowOrigin, "IMGPROXY_ALLOW_ORIGIN")
  209. strEnvConfig(&conf.UserAgent, "IMGPROXY_USER_AGENT")
  210. boolEnvConfig(&conf.IgnoreSslVerification, "IMGPROXY_IGNORE_SSL_VERIFICATION")
  211. strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT")
  212. boolEnvConfig(&conf.S3Enabled, "IMGPROXY_USE_S3")
  213. strEnvConfig(&conf.S3Region, "IMGPROXY_S3_REGION")
  214. strEnvConfig(&conf.S3Endpoint, "IMGPROXY_S3_ENDPOINT")
  215. strEnvConfig(&conf.GCSKey, "IMGPROXY_GCS_KEY")
  216. boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG")
  217. strEnvConfig(&conf.BaseURL, "IMGPROXY_BASE_URL")
  218. conf.Presets = make(presets)
  219. presetEnvConfig(conf.Presets, "IMGPROXY_PRESETS")
  220. presetFileConfig(conf.Presets, *presetsPath)
  221. strEnvConfig(&conf.WatermarkData, "IMGPROXY_WATERMARK_DATA")
  222. strEnvConfig(&conf.WatermarkPath, "IMGPROXY_WATERMARK_PATH")
  223. strEnvConfig(&conf.WatermarkURL, "IMGPROXY_WATERMARK_URL")
  224. floatEnvConfig(&conf.WatermarkOpacity, "IMGPROXY_WATERMARK_OPACITY")
  225. strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
  226. strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
  227. strEnvConfig(&conf.PrometheusBind, "IMGPROXY_PROMETHEUS_BIND")
  228. strEnvConfig(&conf.BugsnagKey, "IMGPROXY_BUGSNAG_KEY")
  229. strEnvConfig(&conf.BugsnagStage, "IMGPROXY_BUGSNAG_STAGE")
  230. strEnvConfig(&conf.HoneybadgerKey, "IMGPROXY_HONEYBADGER_KEY")
  231. strEnvConfig(&conf.HoneybadgerEnv, "IMGPROXY_HONEYBADGER_ENV")
  232. if len(conf.Keys) != len(conf.Salts) {
  233. log.Fatalf("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts))
  234. }
  235. if len(conf.Keys) == 0 {
  236. warning("No keys defined, so signature checking is disabled")
  237. conf.AllowInsecure = true
  238. }
  239. if len(conf.Salts) == 0 {
  240. warning("No salts defined, so signature checking is disabled")
  241. conf.AllowInsecure = true
  242. }
  243. if conf.SignatureSize < 1 || conf.SignatureSize > 32 {
  244. log.Fatalf("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize)
  245. }
  246. if len(conf.Bind) == 0 {
  247. log.Fatalln("Bind address is not defined")
  248. }
  249. if conf.ReadTimeout <= 0 {
  250. log.Fatalf("Read timeout should be greater than 0, now - %d\n", conf.ReadTimeout)
  251. }
  252. if conf.WriteTimeout <= 0 {
  253. log.Fatalf("Write timeout should be greater than 0, now - %d\n", conf.WriteTimeout)
  254. }
  255. if conf.DownloadTimeout <= 0 {
  256. log.Fatalf("Download timeout should be greater than 0, now - %d\n", conf.DownloadTimeout)
  257. }
  258. if conf.Concurrency <= 0 {
  259. log.Fatalf("Concurrency should be greater than 0, now - %d\n", conf.Concurrency)
  260. }
  261. if conf.MaxClients <= 0 {
  262. conf.MaxClients = conf.Concurrency * 10
  263. }
  264. if conf.TTL <= 0 {
  265. log.Fatalf("TTL should be greater than 0, now - %d\n", conf.TTL)
  266. }
  267. if conf.MaxSrcDimension <= 0 {
  268. log.Fatalf("Max src dimension should be greater than 0, now - %d\n", conf.MaxSrcDimension)
  269. }
  270. if conf.MaxSrcResolution <= 0 {
  271. log.Fatalf("Max src resolution should be greater than 0, now - %d\n", conf.MaxSrcResolution)
  272. }
  273. if conf.Quality <= 0 {
  274. log.Fatalf("Quality should be greater than 0, now - %d\n", conf.Quality)
  275. } else if conf.Quality > 100 {
  276. log.Fatalf("Quality can't be greater than 100, now - %d\n", conf.Quality)
  277. }
  278. if conf.GZipCompression < 0 {
  279. log.Fatalf("GZip compression should be greater than or quual to 0, now - %d\n", conf.GZipCompression)
  280. } else if conf.GZipCompression > 9 {
  281. log.Fatalf("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression)
  282. }
  283. if conf.IgnoreSslVerification {
  284. warning("Ignoring SSL verification is very unsafe")
  285. }
  286. if conf.LocalFileSystemRoot != "" {
  287. stat, err := os.Stat(conf.LocalFileSystemRoot)
  288. if err != nil {
  289. log.Fatalf("Cannot use local directory: %s", err)
  290. } else {
  291. if !stat.IsDir() {
  292. log.Fatalf("Cannot use local directory: not a directory")
  293. }
  294. }
  295. if conf.LocalFileSystemRoot == "/" {
  296. log.Print("Exposing root via IMGPROXY_LOCAL_FILESYSTEM_ROOT is unsafe")
  297. }
  298. }
  299. if err := checkPresets(conf.Presets); err != nil {
  300. log.Fatalln(err)
  301. }
  302. if conf.WatermarkOpacity <= 0 {
  303. log.Fatalln("Watermark opacity should be greater than 0")
  304. } else if conf.WatermarkOpacity > 1 {
  305. log.Fatalln("Watermark opacity should be less than or equal to 1")
  306. }
  307. if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind {
  308. log.Fatalln("Can't use the same binding for the main server and Prometheus")
  309. }
  310. initDownloading()
  311. initNewrelic()
  312. initPrometheus()
  313. initErrorsReporting()
  314. initVips()
  315. }