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  }