http.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package safeweb provides a wrapper around an http.Server that applies
  4. // basic web application security defenses by default. The wrapper can be
  5. // used in place of an http.Server. A safeweb.Server adds mitigations for
  6. // Cross-Site Request Forgery (CSRF) attacks, and annotates requests with
  7. // appropriate Cross-Origin Resource Sharing (CORS), Content-Security-Policy,
  8. // X-Content-Type-Options, and Referer-Policy headers.
  9. //
  10. // To use safeweb, the application must separate its "browser" routes from "API"
  11. // routes, with each on its own http.ServeMux. When serving requests, the
  12. // server will first check the browser mux, and if no matching route is found it
  13. // will defer to the API mux.
  14. //
  15. // # Browser Routes
  16. //
  17. // All routes in the browser mux enforce CSRF protection using the gorilla/csrf
  18. // package. The application must template the CSRF token into its forms using
  19. // the [TemplateField] and [TemplateTag] APIs. Applications that are served in a
  20. // secure context (over HTTPS) should also set the SecureContext field to true
  21. // to ensure that the the CSRF cookies are marked as Secure.
  22. //
  23. // In addition, browser routes will also have the following applied:
  24. // - Content-Security-Policy header that disallows inline scripts, framing, and third party resources.
  25. // - X-Content-Type-Options header on responses set to "nosniff" to prevent MIME type sniffing attacks.
  26. // - Referer-Policy header set to "same-origin" to prevent leaking referrer information to third parties.
  27. //
  28. // By default the Content-Security-Policy header will disallow inline styles.
  29. // This can be overridden by setting the CSPAllowInlineStyles field to true in
  30. // the safeweb.Config struct.
  31. //
  32. // # API routes
  33. //
  34. // safeweb inspects the Content-Type header of incoming requests to the API mux
  35. // and prohibits the use of `application/x-www-form-urlencoded` values. If the
  36. // application provides a list of allowed origins and methods in its
  37. // configuration safeweb will set the appropriate CORS headers on pre-flight
  38. // OPTIONS requests served by the API mux.
  39. //
  40. // # HTTP Redirects
  41. //
  42. // The [RedirectHTTP] method returns a handler that redirects all incoming HTTP
  43. // requests to HTTPS at the same path on the provided fully qualified domain
  44. // name (FQDN).
  45. //
  46. // # Example usage
  47. //
  48. // h := http.NewServeMux()
  49. // h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  50. // fmt.Fprint(w, "Hello, world!")
  51. // })
  52. // s, err := safeweb.NewServer(safeweb.Config{
  53. // BrowserMux: h,
  54. // })
  55. // if err != nil {
  56. // log.Fatalf("failed to create server: %v", err)
  57. // }
  58. // ln, err := net.Listen("tcp", ":8080")
  59. // if err != nil {
  60. // log.Fatalf("failed to listen: %v", err)
  61. // }
  62. // defer ln.Close()
  63. // if err := s.Serve(ln); err != nil && err != http.ErrServerClosed {
  64. // log.Fatalf("failed to serve: %v", err)
  65. // }
  66. //
  67. // [TemplateField]: https://pkg.go.dev/github.com/gorilla/csrf#TemplateField
  68. // [TemplateTag]: https://pkg.go.dev/github.com/gorilla/csrf#TemplateTag
  69. package safeweb
  70. import (
  71. "cmp"
  72. "context"
  73. crand "crypto/rand"
  74. "fmt"
  75. "log"
  76. "maps"
  77. "net"
  78. "net/http"
  79. "net/url"
  80. "path"
  81. "slices"
  82. "strings"
  83. "github.com/gorilla/csrf"
  84. )
  85. // CSP is the value of a Content-Security-Policy header. Keys are CSP
  86. // directives (like "default-src") and values are source expressions (like
  87. // "'self'" or "https://tailscale.com"). A nil slice value is allowed for some
  88. // directives like "upgrade-insecure-requests" that don't expect a list of
  89. // source definitions.
  90. type CSP map[string][]string
  91. // DefaultCSP is the recommended CSP to use when not loading resources from
  92. // other domains and not embedding the current website. If you need to tweak
  93. // the CSP, it is recommended to extend DefaultCSP instead of writing your own
  94. // from scratch.
  95. func DefaultCSP() CSP {
  96. return CSP{
  97. "default-src": {"self"}, // origin is the only valid source for all content types
  98. "frame-ancestors": {"none"}, // disallow framing of the page
  99. "form-action": {"self"}, // disallow form submissions to other origins
  100. "base-uri": {"self"}, // disallow base URIs from other origins
  101. // TODO(awly): consider upgrade-insecure-requests in SecureContext
  102. // instead, as this is deprecated.
  103. "block-all-mixed-content": nil, // disallow mixed content when serving over HTTPS
  104. }
  105. }
  106. // Set sets the values for a given directive. Empty values are allowed, if the
  107. // directive doesn't expect any (like "upgrade-insecure-requests").
  108. func (csp CSP) Set(directive string, values ...string) {
  109. csp[directive] = values
  110. }
  111. // Add adds a source expression to an existing directive.
  112. func (csp CSP) Add(directive, value string) {
  113. csp[directive] = append(csp[directive], value)
  114. }
  115. // Del deletes a directive and all its values.
  116. func (csp CSP) Del(directive string) {
  117. delete(csp, directive)
  118. }
  119. func (csp CSP) String() string {
  120. keys := slices.Collect(maps.Keys(csp))
  121. slices.Sort(keys)
  122. var s strings.Builder
  123. for _, k := range keys {
  124. s.WriteString(k)
  125. for _, v := range csp[k] {
  126. // Special values like 'self', 'none', 'unsafe-inline', etc., must
  127. // be quoted. Do it implicitly as a convenience here.
  128. if !strings.Contains(v, ".") && len(v) > 1 && v[0] != '\'' && v[len(v)-1] != '\'' {
  129. v = "'" + v + "'"
  130. }
  131. s.WriteString(" " + v)
  132. }
  133. s.WriteString("; ")
  134. }
  135. return strings.TrimSpace(s.String())
  136. }
  137. // The default Strict-Transport-Security header. This header tells the browser
  138. // to exclusively use HTTPS for all requests to the origin for the next year.
  139. var DefaultStrictTransportSecurityOptions = "max-age=31536000"
  140. // Config contains the configuration for a safeweb server.
  141. type Config struct {
  142. // SecureContext specifies whether the Server is running in a secure (HTTPS) context.
  143. // Setting this to true will cause the Server to set the Secure flag on CSRF cookies.
  144. SecureContext bool
  145. // BrowserMux is the HTTP handler for any routes in your application that
  146. // should only be served to browsers in a primary origin context. These
  147. // requests will be subject to CSRF protection and will have
  148. // browser-specific headers in their responses.
  149. BrowserMux *http.ServeMux
  150. // APIMux is the HTTP handler for any routes in your application that
  151. // should only be served to non-browser clients or to browsers in a
  152. // cross-origin resource sharing context.
  153. APIMux *http.ServeMux
  154. // AccessControlAllowOrigin specifies the Access-Control-Allow-Origin header sent in response to pre-flight OPTIONS requests.
  155. // Provide a list of origins, e.g. ["https://foobar.com", "https://foobar.net"] or the wildcard value ["*"].
  156. // No headers will be sent if no origins are provided.
  157. AccessControlAllowOrigin []string
  158. // AccessControlAllowMethods specifies the Access-Control-Allow-Methods header sent in response to pre-flight OPTIONS requests.
  159. // Provide a list of methods, e.g. ["GET", "POST", "PUT", "DELETE"].
  160. // No headers will be sent if no methods are provided.
  161. AccessControlAllowMethods []string
  162. // CSRFSecret is the secret used to sign CSRF tokens. It must be 32 bytes long.
  163. // This should be considered a sensitive value and should be kept secret.
  164. // If this is not provided, the Server will generate a random CSRF secret on
  165. // startup.
  166. CSRFSecret []byte
  167. // CSP is the Content-Security-Policy header to return with BrowserMux
  168. // responses.
  169. CSP CSP
  170. // CSPAllowInlineStyles specifies whether to include `style-src:
  171. // unsafe-inline` in the Content-Security-Policy header to permit the use of
  172. // inline CSS.
  173. CSPAllowInlineStyles bool
  174. // CookiesSameSiteLax specifies whether to use SameSite=Lax in cookies. The
  175. // default is to set SameSite=Strict.
  176. CookiesSameSiteLax bool
  177. // StrictTransportSecurityOptions specifies optional directives for the
  178. // Strict-Transport-Security header sent in response to requests made to the
  179. // BrowserMux when SecureContext is true.
  180. // If empty, it defaults to max-age of 1 year.
  181. StrictTransportSecurityOptions string
  182. // HTTPServer, if specified, is the underlying http.Server that safeweb will
  183. // use to serve requests. If nil, a new http.Server will be created.
  184. // Do not use the Handler field of http.Server, as it will be ignored.
  185. // Instead, set your handlers using APIMux and BrowserMux.
  186. HTTPServer *http.Server
  187. }
  188. func (c *Config) setDefaults() error {
  189. if c.BrowserMux == nil {
  190. c.BrowserMux = &http.ServeMux{}
  191. }
  192. if c.APIMux == nil {
  193. c.APIMux = &http.ServeMux{}
  194. }
  195. if c.CSRFSecret == nil || len(c.CSRFSecret) == 0 {
  196. c.CSRFSecret = make([]byte, 32)
  197. if _, err := crand.Read(c.CSRFSecret); err != nil {
  198. return fmt.Errorf("failed to generate CSRF secret: %w", err)
  199. }
  200. }
  201. if c.CSP == nil {
  202. c.CSP = DefaultCSP()
  203. }
  204. return nil
  205. }
  206. // Server is a safeweb server.
  207. type Server struct {
  208. Config
  209. h *http.Server
  210. csp string
  211. csrfProtect func(http.Handler) http.Handler
  212. }
  213. // NewServer creates a safeweb server with the provided configuration. It will
  214. // validate the configuration to ensure that it is complete and return an error
  215. // if not.
  216. func NewServer(config Config) (*Server, error) {
  217. // ensure that CORS configuration is complete
  218. corsMethods := len(config.AccessControlAllowMethods) > 0
  219. corsHosts := len(config.AccessControlAllowOrigin) > 0
  220. if corsMethods != corsHosts {
  221. return nil, fmt.Errorf("must provide both AccessControlAllowOrigin and AccessControlAllowMethods or neither")
  222. }
  223. // fill in any missing fields
  224. if err := config.setDefaults(); err != nil {
  225. return nil, fmt.Errorf("failed to set defaults: %w", err)
  226. }
  227. sameSite := csrf.SameSiteStrictMode
  228. if config.CookiesSameSiteLax {
  229. sameSite = csrf.SameSiteLaxMode
  230. }
  231. if config.CSPAllowInlineStyles {
  232. if _, ok := config.CSP["style-src"]; ok {
  233. config.CSP.Add("style-src", "unsafe-inline")
  234. } else {
  235. config.CSP.Set("style-src", "self", "unsafe-inline")
  236. }
  237. }
  238. s := &Server{
  239. Config: config,
  240. csp: config.CSP.String(),
  241. // only set Secure flag on CSRF cookies if we are in a secure context
  242. // as otherwise the browser will reject the cookie
  243. csrfProtect: csrf.Protect(config.CSRFSecret, csrf.Secure(config.SecureContext), csrf.SameSite(sameSite)),
  244. }
  245. s.h = cmp.Or(config.HTTPServer, &http.Server{})
  246. if s.h.Handler != nil {
  247. return nil, fmt.Errorf("use safeweb.Config.APIMux and safeweb.Config.BrowserMux instead of http.Server.Handler")
  248. }
  249. s.h.Handler = s
  250. return s, nil
  251. }
  252. type handlerType int
  253. const (
  254. unknownHandler handlerType = iota
  255. apiHandler
  256. browserHandler
  257. )
  258. func (h handlerType) String() string {
  259. switch h {
  260. case browserHandler:
  261. return "browser"
  262. case apiHandler:
  263. return "api"
  264. default:
  265. return "unknown"
  266. }
  267. }
  268. // checkHandlerType returns either apiHandler or browserHandler, depending on
  269. // whether apiPattern or browserPattern is more specific (i.e. which pattern
  270. // contains more pathname components). If they are equally specific, it returns
  271. // unknownHandler.
  272. func checkHandlerType(apiPattern, browserPattern string) handlerType {
  273. apiPattern, browserPattern = path.Clean(apiPattern), path.Clean(browserPattern)
  274. c := cmp.Compare(strings.Count(apiPattern, "/"), strings.Count(browserPattern, "/"))
  275. if apiPattern == "/" || browserPattern == "/" {
  276. c = cmp.Compare(len(apiPattern), len(browserPattern))
  277. }
  278. switch {
  279. case c > 0:
  280. return apiHandler
  281. case c < 0:
  282. return browserHandler
  283. default:
  284. return unknownHandler
  285. }
  286. }
  287. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  288. // if we are not in a secure context, signal to the CSRF middleware that
  289. // TLS-only header checks should be skipped
  290. if !s.Config.SecureContext {
  291. r = csrf.PlaintextHTTPRequest(r)
  292. }
  293. _, bp := s.BrowserMux.Handler(r)
  294. _, ap := s.APIMux.Handler(r)
  295. switch {
  296. case bp == "" && ap != "": // APIMux match
  297. s.serveAPI(w, r)
  298. case bp != "" && ap == "": // BrowserMux match
  299. s.serveBrowser(w, r)
  300. case bp == "" && ap == "": // neither match
  301. http.NotFound(w, r)
  302. case bp != "" && ap != "":
  303. // Both muxes match the path. Route to the more-specific handler (as
  304. // determined by the number of components in the path). If it somehow
  305. // happens that both patterns are equally specific, something strange
  306. // has happened; say so.
  307. //
  308. // NOTE: checkHandlerType does not know about what the serve* handlers
  309. // will do — including, possibly, redirecting to more specific patterns.
  310. // If you have a less-specific pattern that redirects to something more
  311. // specific, this logic will not do what you wanted.
  312. handler := checkHandlerType(ap, bp)
  313. switch handler {
  314. case apiHandler:
  315. s.serveAPI(w, r)
  316. case browserHandler:
  317. s.serveBrowser(w, r)
  318. default:
  319. s := http.StatusInternalServerError
  320. log.Printf("conflicting mux paths in safeweb: request %q matches browser mux pattern %q and API mux pattern %q; returning %d", r.URL.Path, bp, ap, s)
  321. http.Error(w, "multiple handlers match this request", s)
  322. }
  323. }
  324. }
  325. func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) {
  326. // disallow x-www-form-urlencoded requests to the API
  327. if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" {
  328. http.Error(w, "invalid content type", http.StatusBadRequest)
  329. return
  330. }
  331. // set CORS headers for pre-flight OPTIONS requests if any were configured
  332. if r.Method == "OPTIONS" && len(s.AccessControlAllowOrigin) > 0 {
  333. w.Header().Set("Access-Control-Allow-Origin", strings.Join(s.AccessControlAllowOrigin, ", "))
  334. w.Header().Set("Access-Control-Allow-Methods", strings.Join(s.AccessControlAllowMethods, ", "))
  335. }
  336. s.APIMux.ServeHTTP(w, r)
  337. }
  338. func (s *Server) serveBrowser(w http.ResponseWriter, r *http.Request) {
  339. w.Header().Set("Content-Security-Policy", s.csp)
  340. w.Header().Set("X-Content-Type-Options", "nosniff")
  341. w.Header().Set("Referer-Policy", "same-origin")
  342. w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
  343. if s.SecureContext {
  344. w.Header().Set("Strict-Transport-Security", cmp.Or(s.StrictTransportSecurityOptions, DefaultStrictTransportSecurityOptions))
  345. }
  346. s.csrfProtect(s.BrowserMux).ServeHTTP(w, r)
  347. }
  348. // ServeRedirectHTTP serves a single HTTP handler on the provided listener that
  349. // redirects all incoming HTTP requests to the HTTPS address of the provided
  350. // fully qualified domain name (FQDN). Callers are responsible for closing the
  351. // listener.
  352. func (s *Server) ServeRedirectHTTP(ln net.Listener, fqdn string) error {
  353. return http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  354. new := url.URL{
  355. Scheme: "https",
  356. Host: fqdn,
  357. Path: r.URL.Path,
  358. RawQuery: r.URL.RawQuery,
  359. }
  360. http.Redirect(w, r, new.String(), http.StatusMovedPermanently)
  361. }))
  362. }
  363. // Serve starts the server and listens on the provided listener. It will block
  364. // until the server is closed. The caller is responsible for closing the
  365. // listener.
  366. func (s *Server) Serve(ln net.Listener) error {
  367. return s.h.Serve(ln)
  368. }
  369. // ListenAndServe listens on the TCP network address addr and then calls Serve
  370. // to handle requests on incoming connections. If addr == "", ":http" is used.
  371. func (s *Server) ListenAndServe(addr string) error {
  372. if addr == "" {
  373. addr = ":http"
  374. }
  375. lst, err := net.Listen("tcp", addr)
  376. if err != nil {
  377. return err
  378. }
  379. return s.Serve(lst)
  380. }
  381. // Close closes all client connections and stops accepting new ones.
  382. func (s *Server) Close() error {
  383. return s.h.Close()
  384. }
  385. // Shutdown gracefully shuts down the server without interrupting any active
  386. // connections. It has the same semantics as[http.Server.Shutdown].
  387. func (s *Server) Shutdown(ctx context.Context) error { return s.h.Shutdown(ctx) }