github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safehttp/plugins/hsts/hsts.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package hsts provides HTTP Strict Transport Security. 16 // 17 // HTTP Strict Transport Security informs browsers that a website 18 // should only be accessed using HTTPS and not HTTP. This plugin enforces HSTS by 19 // redirecting all HTTP traffic to HTTPS and by setting the 20 // Strict-Transport-Security header on all HTTPS responses. Please note that this 21 // only applies if the framework is not run in dev mode. 22 // 23 // More info: 24 // - MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security 25 // - Wikipedia: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security 26 // - RFC 6797: https://tools.ietf.org/html/rfc6797 27 // 28 // # Usage 29 // 30 // To construct the plugin with safe default settings, use Default. Otherwise, 31 // create the Interceptor yourself. 32 // Install it using safehttp.ServerMux.Install. 33 package hsts 34 35 import ( 36 "net/url" 37 "strconv" 38 "strings" 39 "time" 40 41 "github.com/google/go-safeweb/safehttp" 42 ) 43 44 // Interceptor implements automatic HSTS functionality. 45 // See https://tools.ietf.org/html/rfc6797 for more info. 46 type Interceptor struct { 47 // MaxAge is the duration that the browser should remember 48 // that a site is only to be accessed using HTTPS. MaxAge 49 // must be positive. It will be rounded to seconds before use. 50 MaxAge time.Duration 51 52 // DisableIncludeSubDomains disables the includeSubDomains directive. 53 // When DisableIncludeSubDomains is false, all subdomains 54 // of the domain where this service is hosted will also be added 55 // to the browsers HSTS list. 56 DisableIncludeSubDomains bool 57 58 // Preload enables the preload directive. 59 // This should only be enabled if this site should be 60 // added to the browser HSTS preload list, which is supported 61 // by all major browsers. See https://hstspreload.org/ for 62 // more info. 63 Preload bool 64 65 // BehindProxy controls how the plugin should behave with regards 66 // to HTTPS. If this server is behind a proxy that terminates HTTPS 67 // traffic then this should be enabled. If this is enabled 68 // then the plugin will always send the Strict-Transport-Security 69 // header and will not redirect HTTP traffic to HTTPS traffic. 70 BehindProxy bool 71 } 72 73 var _ safehttp.Interceptor = Interceptor{} 74 75 // Default creates a new HSTS interceptor with safe defaults. 76 // These safe defaults are: 77 // - max-age set to 2 years, 78 // - includeSubDomains is enabled, 79 // - preload is disabled. 80 func Default() Interceptor { 81 return Interceptor{MaxAge: 63072000 * time.Second} // two years in seconds 82 } 83 84 // Before should be executed before the request is sent to the handler. 85 // The function redirects HTTP requests to HTTPS. When HTTPS traffic 86 // is received the Strict-Transport-Security header is applied to the 87 // response. 88 func (it Interceptor) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result { 89 if safehttp.IsLocalDev() { 90 return safehttp.NotWritten() 91 } 92 93 if it.MaxAge < 0 { 94 return w.WriteError(safehttp.StatusInternalServerError) 95 } 96 97 if !it.BehindProxy && r.TLS == nil { 98 u, err := url.Parse(r.URL().String()) 99 if err != nil { 100 return w.WriteError(safehttp.StatusInternalServerError) 101 } 102 u.Scheme = "https" 103 return safehttp.Redirect(w, r, u.String(), safehttp.StatusMovedPermanently) 104 } 105 106 var value strings.Builder 107 value.WriteString("max-age=") 108 value.WriteString(strconv.FormatInt(int64(it.MaxAge.Seconds()), 10)) 109 if !it.DisableIncludeSubDomains { 110 value.WriteString("; includeSubDomains") 111 } 112 if it.Preload { 113 value.WriteString("; preload") 114 } 115 set := w.Header().Claim("Strict-Transport-Security") 116 set([]string{value.String()}) 117 return safehttp.NotWritten() 118 } 119 120 // Commit is a no-op, required to satisfy the safehttp.Interceptor interface. 121 func (Interceptor) Commit(w safehttp.ResponseHeadersWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) { 122 } 123 124 // Match returns false since there are no supported configurations. 125 func (Interceptor) Match(safehttp.InterceptorConfig) bool { 126 return false 127 }