// Package secureheader adds some HTTP header fields widely // considered to improve safety of HTTP requests. These fields // are documented as follows: // // Strict Transport Security: https://tools.ietf.org/html/rfc6797 // Frame Options: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-00 // Cross Site Scripting: https://msdn.microsoft.com/en-us/library/dd565647%28v=vs.85%29.aspx // Content Type Options: https://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx // Content Security Policy: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html // // The easiest way to use this package: // // http.ListenAndServe(addr, secureheader.DefaultConfig) // // DefaultConfig is initialized with conservative (safer and more // restrictive) behavior. If you want to change that, set its // fields to different values before calling ListenAndServe. See // the example code below. // // This package was inspired by Twitter's secureheaders Ruby // library. See https://github.com/twitter/secureheaders. package secureheader import ( "net" "net/http" "strconv" "time" ) // DefaultConfig is initialized with conservative (safer and more // restrictive) behavior. var DefaultConfig = &Config{ HTTPSRedirect: true, HTTPSUseForwardedProto: ShouldUseForwardedProto(), PermitClearLoopback: false, ContentTypeOptions: true, CSP: false, CSPBody: "default-src 'self'", CSPReportURI: "", CSPReportOnly: false, CSPReportOnlyBody: "default-src 'self'", CSPReportOnlyReportURI: "", HSTS: true, HSTSMaxAge: 300 * 24 * time.Hour, HSTSIncludeSubdomains: true, HSTSPreload: false, FrameOptions: true, FrameOptionsPolicy: Deny, XSSProtection: true, XSSProtectionBlock: true, } // Handler returns a new HTTP handler // using the configuration in DefaultConfig, // serving requests using h. // If h is nil, it uses http.DefaultServeMux. func Handler(h http.Handler) *Config { c := new(Config) *c = *DefaultConfig c.Next = h return c } type Config struct { // If true, redirects any request with scheme http to the // equivalent https URL. HTTPSRedirect bool HTTPSUseForwardedProto bool // Allow cleartext (non-HTTPS) HTTP connections to a loopback // address, even if HTTPSRedirect is true. PermitClearLoopback bool // If true, sets X-Content-Type-Options to "nosniff". ContentTypeOptions bool // If true, send a Content-Security-Policy header. For more // information on deploying CSP, see for example // https://medium.com/sourceclear/content-security-policy-with-sentry-efb04f336f59 // Dsiabled by default. If you set CSP = true, // the default policy is "default-src 'self'" and reporting is disabled. // To enable reporting, set CSPReportURI to your reporting endpoint. CSP bool CSPBody string CSPReportURI string // If true, the browser will report CSP violations, but won't enforce them // It *is* meaningful to set both headers // Content-Security-Policy *AND* Content-Security-Policy-Report-Only // and give them different bodys & report-uri's. The browser will // enforce the former, but only generate warnings on the latter. // Like CSPBody, the default is "default-src 'self'", and // Set CSPReportOnlyReportURI to your reporting endpoint. CSPReportOnly bool CSPReportOnlyBody string CSPReportOnlyReportURI string // If true, sets the HTTP Strict Transport Security header // field, which instructs browsers to send future requests // over HTTPS, even if the URL uses the unencrypted http // scheme. HSTS bool HSTSMaxAge time.Duration HSTSIncludeSubdomains bool HSTSPreload bool // If true, sets X-Frame-Options, to control when the request // should be displayed inside an HTML frame. FrameOptions bool FrameOptionsPolicy FramePolicy // If true, sets X-XSS-Protection to "1", optionally with // "mode=block". See the official documentation, linked above, // for the meaning of these values. XSSProtection bool XSSProtectionBlock bool // Used by ServeHTTP, after setting any extra headers, to // reply to the request. Next is typically nil, in which case // http.DefaultServeMux is used instead. Next http.Handler } // ServeHTTP sets header fields on w according to the options in // c, then either replies directly or runs c.Next to reply. // Typically c.Next is nil, in which case http.DefaultServeMux is // used instead. func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) { if c.HTTPSRedirect && !c.isHTTPS(r) && !c.okloopback(r) { url := *r.URL url.Scheme = "https" url.Host = r.Host http.Redirect(w, r, url.String(), http.StatusMovedPermanently) return } if c.ContentTypeOptions { w.Header().Set("X-Content-Type-Options", "nosniff") } if c.CSP { v := c.CSPBody if c.CSPReportURI != "" { v += "; report-uri " + c.CSPReportURI } w.Header().Set("Content-Security-Policy", v) } if c.CSPReportOnly { v := c.CSPReportOnlyBody if c.CSPReportOnlyReportURI != "" { v += "; report-uri " + c.CSPReportOnlyReportURI } w.Header().Set("Content-Security-Policy-Report-Only", v) } if c.HSTS && c.isHTTPS(r) { v := "max-age=" + strconv.FormatInt(int64(c.HSTSMaxAge/time.Second), 10) if c.HSTSIncludeSubdomains { v += "; includeSubDomains" } if c.HSTSPreload { v += "; preload" } w.Header().Set("Strict-Transport-Security", v) } if c.FrameOptions { w.Header().Set("X-Frame-Options", string(c.FrameOptionsPolicy)) } if c.XSSProtection { v := "1" if c.XSSProtectionBlock { v += "; mode=block" } w.Header().Set("X-XSS-Protection", v) } next := c.Next if next == nil { next = http.DefaultServeMux } next.ServeHTTP(w, r) } // Given that r is cleartext (not HTTPS), okloopback returns // whether r is on a permitted loopback connection. func (c *Config) okloopback(r *http.Request) bool { return c.PermitClearLoopback && isLoopback(r) } func (c *Config) isHTTPS(r *http.Request) bool { if c.HTTPSUseForwardedProto { return r.Header.Get("X-Forwarded-Proto") == "https" } return r.TLS != nil } // FramePolicy tells the browser under what circumstances to allow // the response to be displayed inside an HTML frame. There are // three options: // // Deny do not permit display in a frame // SameOrigin permit display in a frame from the same origin // AllowFrom(url) permit display in a frame from the given url type FramePolicy string const ( Deny FramePolicy = "DENY" SameOrigin FramePolicy = "SAMEORIGIN" ) // AllowFrom returns a FramePolicy specifying that the requested // resource should be included in a frame from only the given url. func AllowFrom(url string) FramePolicy { return FramePolicy("ALLOW-FROM: " + url) } // ShouldUseForwardedProto returns whether to trust the // X-Forwarded-Proto header field. // DefaultConfig.HTTPSUseForwardedProto is initialized to this // value. // // This value depends on the particular environment where the // package is built. It is currently true iff build constraint // "heroku" is satisfied. func ShouldUseForwardedProto() bool { return defaultUseForwardedProto } func isLoopback(r *http.Request) bool { a, err := net.ResolveTCPAddr("tcp", r.RemoteAddr) return err == nil && a.IP.IsLoopback() }