server.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package server // import "miniflux.app/v2/internal/http/server"
  4. import (
  5. "crypto/tls"
  6. "fmt"
  7. "log/slog"
  8. "net"
  9. "net/http"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "miniflux.app/v2/internal/config"
  14. "miniflux.app/v2/internal/storage"
  15. "miniflux.app/v2/internal/worker"
  16. "golang.org/x/crypto/acme"
  17. "golang.org/x/crypto/acme/autocert"
  18. )
  19. func StartWebServer(store *storage.Storage, pool *worker.Pool) ([]*http.Server, func()) {
  20. var servers []*http.Server
  21. autocertTLSConfig, challengeServer := setupAutocert(store)
  22. if challengeServer != nil {
  23. servers = append(servers, challengeServer)
  24. }
  25. certFile := config.Opts.CertFile()
  26. keyFile := config.Opts.CertKeyFile()
  27. certDomain := config.Opts.CertDomain()
  28. targets := determineListenTargets(config.Opts.ListenAddr(), certDomain, certFile, keyFile)
  29. if autocertTLSConfig != nil || anyTLS(targets) {
  30. config.Opts.SetHTTPSValue(true)
  31. }
  32. // Create a single certificate loader shared by all TLS servers
  33. // that use the same cert/key pair.
  34. var certLoader *certificateLoader
  35. if certFile != "" && keyFile != "" {
  36. hasTLSTarget := false
  37. for _, t := range targets {
  38. if t.mode == modeTLS || t.mode == modeUnixSocketTLS {
  39. hasTLSTarget = true
  40. break
  41. }
  42. }
  43. if hasTLSTarget {
  44. var err error
  45. certLoader, err = newCertificateLoader(certFile, keyFile)
  46. if err != nil {
  47. printErrorAndExit("Unable to load TLS certificate from %s / %s: %v", certFile, keyFile, err)
  48. }
  49. }
  50. }
  51. for _, t := range targets {
  52. srv := &http.Server{
  53. Addr: t.address,
  54. ReadTimeout: config.Opts.HTTPServerTimeout(),
  55. WriteTimeout: config.Opts.HTTPServerTimeout(),
  56. IdleTimeout: config.Opts.HTTPServerTimeout(),
  57. ReadHeaderTimeout: config.Opts.HTTPServerTimeout(),
  58. Handler: newRouter(store, pool),
  59. }
  60. switch t.mode {
  61. case modeSystemd:
  62. startSystemdSocketServer(srv)
  63. case modeUnixSocket:
  64. startUnixSocketServer(srv, t.address)
  65. case modeUnixSocketTLS:
  66. startUnixSocketTLSServer(srv, t.address, certLoader)
  67. case modeAutocertTLS:
  68. startAutoCertTLSServer(srv, autocertTLSConfig)
  69. case modeTLS:
  70. startTLSServer(srv, certLoader)
  71. default:
  72. startHTTPServer(srv)
  73. }
  74. servers = append(servers, srv)
  75. }
  76. certReloadFn := func() {}
  77. if certLoader != nil {
  78. certReloadFn = certLoader.Reload
  79. }
  80. return servers, certReloadFn
  81. }
  82. type listenerMode int
  83. const (
  84. modeHTTP listenerMode = iota
  85. modeTLS
  86. modeAutocertTLS
  87. modeUnixSocket
  88. modeUnixSocketTLS
  89. modeSystemd
  90. )
  91. type listenTarget struct {
  92. address string
  93. mode listenerMode
  94. }
  95. func determineListenTargets(addresses []string, certDomain, certFile, keyFile string) []listenTarget {
  96. isSystemd := os.Getenv("LISTEN_PID") == strconv.Itoa(os.Getpid())
  97. hasCertFiles := certFile != "" && keyFile != ""
  98. hasAutocert := certDomain != ""
  99. var targets []listenTarget
  100. for i, addr := range addresses {
  101. if isSystemd {
  102. if i == 0 {
  103. targets = append(targets, listenTarget{address: addr, mode: modeSystemd})
  104. } else {
  105. slog.Warn("Systemd socket activation: only the first listen address is used, others are ignored",
  106. slog.String("skipped_address", addr),
  107. )
  108. }
  109. continue
  110. }
  111. isUnix := strings.HasPrefix(addr, "/")
  112. switch {
  113. case isUnix && hasCertFiles:
  114. targets = append(targets, listenTarget{address: addr, mode: modeUnixSocketTLS})
  115. case isUnix:
  116. targets = append(targets, listenTarget{address: addr, mode: modeUnixSocket})
  117. case hasAutocert && (addr == ":https" || (i == 0 && strings.Contains(addr, ":"))):
  118. targets = append(targets, listenTarget{address: addr, mode: modeAutocertTLS})
  119. case hasCertFiles:
  120. targets = append(targets, listenTarget{address: addr, mode: modeTLS})
  121. default:
  122. targets = append(targets, listenTarget{address: addr, mode: modeHTTP})
  123. }
  124. }
  125. return targets
  126. }
  127. func anyTLS(targets []listenTarget) bool {
  128. for _, t := range targets {
  129. switch t.mode {
  130. case modeTLS, modeAutocertTLS, modeUnixSocketTLS:
  131. return true
  132. }
  133. }
  134. return false
  135. }
  136. func setupAutocert(store *storage.Storage) (*tls.Config, *http.Server) {
  137. certDomain := config.Opts.CertDomain()
  138. if certDomain == "" {
  139. return nil, nil
  140. }
  141. slog.Debug("Configuring autocert manager", slog.String("domain", certDomain))
  142. certManager := autocert.Manager{
  143. Cache: storage.NewCertificateCache(store),
  144. Prompt: autocert.AcceptTOS,
  145. HostPolicy: autocert.HostWhitelist(certDomain),
  146. }
  147. tlsConfig := &tls.Config{
  148. NextProtos: []string{"h2", "http/1.1", acme.ALPNProto},
  149. }
  150. tlsConfig.GetCertificate = certManager.GetCertificate
  151. challengeServer := &http.Server{
  152. Handler: certManager.HTTPHandler(nil),
  153. Addr: ":http",
  154. }
  155. slog.Info("Starting ACME HTTP challenge server", slog.String("address", challengeServer.Addr))
  156. go func() {
  157. if err := challengeServer.ListenAndServe(); err != http.ErrServerClosed {
  158. slog.Error("ACME HTTP challenge server failed", slog.Any("error", err))
  159. }
  160. }()
  161. return tlsConfig, challengeServer
  162. }
  163. func startSystemdSocketServer(server *http.Server) {
  164. go func() {
  165. f := os.NewFile(3, "systemd socket")
  166. listener, err := net.FileListener(f)
  167. if err != nil {
  168. printErrorAndExit(`Unable to create listener from systemd socket: %v`, err)
  169. }
  170. slog.Info(`Starting server using systemd socket`)
  171. if err := server.Serve(listener); err != http.ErrServerClosed {
  172. printErrorAndExit(`Systemd socket server failed to start: %v`, err)
  173. }
  174. }()
  175. }
  176. func startUnixSocketServer(server *http.Server, socketFile string) {
  177. listener := createUnixSocketListener(socketFile)
  178. go func() {
  179. slog.Info("Starting server using a Unix socket", slog.String("socket", socketFile))
  180. if err := server.Serve(listener); err != http.ErrServerClosed {
  181. printErrorAndExit("Unix socket server failed to start on %s: %v", socketFile, err)
  182. }
  183. }()
  184. }
  185. func startUnixSocketTLSServer(server *http.Server, socketFile string, certLoader *certificateLoader) {
  186. server.TLSConfig = &tls.Config{
  187. GetCertificate: certLoader.getCertificate,
  188. // NextProtos intentionally nil — ServeTLS auto-configures it
  189. // based on the http2server GODEBUG setting.
  190. }
  191. listener := createUnixSocketListener(socketFile)
  192. go func() {
  193. slog.Info("Starting TLS server using a Unix socket",
  194. slog.String("socket", socketFile),
  195. )
  196. if err := server.ServeTLS(listener, "", ""); err != http.ErrServerClosed {
  197. printErrorAndExit("TLS Unix socket server failed to start on %s: %v", socketFile, err)
  198. }
  199. }()
  200. }
  201. func createUnixSocketListener(socketFile string) net.Listener {
  202. if err := os.Remove(socketFile); err != nil && !os.IsNotExist(err) {
  203. printErrorAndExit("Unable to remove existing Unix socket %s: %v", socketFile, err)
  204. }
  205. listener, err := net.Listen("unix", socketFile)
  206. if err != nil {
  207. printErrorAndExit(`Server failed to listen on Unix socket %s: %v`, socketFile, err)
  208. }
  209. if err := os.Chmod(socketFile, 0660); err != nil {
  210. printErrorAndExit(`Unable to change socket permission for %s: %v`, socketFile, err)
  211. }
  212. return listener
  213. }
  214. func startAutoCertTLSServer(server *http.Server, autoTLSConfig *tls.Config) {
  215. if server.TLSConfig == nil {
  216. server.TLSConfig = &tls.Config{}
  217. }
  218. server.TLSConfig.GetCertificate = autoTLSConfig.GetCertificate
  219. server.TLSConfig.NextProtos = autoTLSConfig.NextProtos
  220. go func() {
  221. slog.Info("Starting TLS server using automatic certificate management",
  222. slog.String("listen_address", server.Addr),
  223. )
  224. if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
  225. printErrorAndExit("Autocert server failed to start on %s: %v", server.Addr, err)
  226. }
  227. }()
  228. }
  229. func startTLSServer(server *http.Server, certLoader *certificateLoader) {
  230. server.TLSConfig = &tls.Config{
  231. GetCertificate: certLoader.getCertificate,
  232. // NextProtos intentionally nil — ServeTLS auto-configures it
  233. // based on the http2server GODEBUG setting.
  234. }
  235. listener, err := net.Listen("tcp", server.Addr)
  236. if err != nil {
  237. printErrorAndExit("TLS server failed to listen on %s: %v", server.Addr, err)
  238. }
  239. go func() {
  240. slog.Info("Starting TLS server using a certificate",
  241. slog.String("listen_address", server.Addr),
  242. )
  243. if err := server.ServeTLS(listener, "", ""); err != http.ErrServerClosed {
  244. printErrorAndExit("TLS server failed to start on %s: %v", server.Addr, err)
  245. }
  246. }()
  247. }
  248. func startHTTPServer(server *http.Server) {
  249. go func() {
  250. slog.Info("Starting HTTP server",
  251. slog.String("listen_address", server.Addr),
  252. )
  253. if err := server.ListenAndServe(); err != http.ErrServerClosed {
  254. printErrorAndExit("HTTP server failed to start on %s: %v", server.Addr, err)
  255. }
  256. }()
  257. }
  258. func printErrorAndExit(format string, a ...any) {
  259. message := fmt.Sprintf(format, a...)
  260. slog.Error(message)
  261. fmt.Fprintf(os.Stderr, "%v\n", message)
  262. os.Exit(1)
  263. }