...

Source file src/github.com/gin-contrib/secure/policy.go

Documentation: github.com/gin-contrib/secure

     1  package secure
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/gin-gonic/gin"
    10  )
    11  
    12  type (
    13  	// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
    14  	// provided to configure which features should be enabled, and the ability to override a few of the default values.
    15  	policy struct {
    16  		// Customize Secure with an Options struct.
    17  		config       Config
    18  		fixedHeaders []header
    19  	}
    20  
    21  	header struct {
    22  		key   string
    23  		value []string
    24  	}
    25  )
    26  
    27  // Constructs a new Policy instance with supplied options.
    28  func newPolicy(config Config) *policy {
    29  	policy := &policy{}
    30  	policy.loadConfig(config)
    31  	return policy
    32  }
    33  
    34  func (p *policy) loadConfig(config Config) {
    35  	p.config = config
    36  	p.fixedHeaders = make([]header, 0, 5)
    37  
    38  	// Frame Options header.
    39  	if len(config.CustomFrameOptionsValue) > 0 {
    40  		p.addHeader("X-Frame-Options", config.CustomFrameOptionsValue)
    41  	} else if config.FrameDeny {
    42  		p.addHeader("X-Frame-Options", "DENY")
    43  	}
    44  
    45  	// Content Type Options header.
    46  	if config.ContentTypeNosniff {
    47  		p.addHeader("X-Content-Type-Options", "nosniff")
    48  	}
    49  
    50  	// XSS Protection header.
    51  	if config.BrowserXssFilter {
    52  		p.addHeader("X-Xss-Protection", "1; mode=block")
    53  	}
    54  
    55  	// Content Security Policy header.
    56  	if len(config.ContentSecurityPolicy) > 0 {
    57  		p.addHeader("Content-Security-Policy", config.ContentSecurityPolicy)
    58  	}
    59  
    60  	if len(config.ReferrerPolicy) > 0 {
    61  		p.addHeader("Referrer-Policy", config.ReferrerPolicy)
    62  	}
    63  
    64  	// Strict Transport Security header.
    65  	if config.STSSeconds != 0 {
    66  		stsSub := ""
    67  		if config.STSIncludeSubdomains {
    68  			stsSub = "; includeSubdomains"
    69  		}
    70  
    71  		// TODO
    72  		// "max-age=%d%s" refactor
    73  		p.addHeader(
    74  			"Strict-Transport-Security",
    75  			fmt.Sprintf("max-age=%d%s", config.STSSeconds, stsSub))
    76  	}
    77  
    78  	// X-Download-Options header.
    79  	if config.IENoOpen {
    80  		p.addHeader("X-Download-Options", "noopen")
    81  	}
    82  
    83  	// FeaturePolicy header.
    84  	if len(config.FeaturePolicy) > 0 {
    85  		p.addHeader("Feature-Policy", config.FeaturePolicy)
    86  	}
    87  }
    88  
    89  func (p *policy) addHeader(key string, value string) {
    90  	p.fixedHeaders = append(p.fixedHeaders, header{
    91  		key:   key,
    92  		value: []string{value},
    93  	})
    94  }
    95  
    96  func (p *policy) applyToContext(c *gin.Context) bool {
    97  	if !p.config.IsDevelopment {
    98  		p.writeSecureHeaders(c)
    99  
   100  		if !p.checkAllowHosts(c) {
   101  			return false
   102  		}
   103  		if !p.checkSSL(c) {
   104  			return false
   105  		}
   106  	}
   107  	return true
   108  }
   109  
   110  func (p *policy) writeSecureHeaders(c *gin.Context) {
   111  	header := c.Writer.Header()
   112  	for _, pair := range p.fixedHeaders {
   113  		header[pair.key] = pair.value
   114  	}
   115  }
   116  
   117  func (p *policy) checkAllowHosts(c *gin.Context) bool {
   118  	if len(p.config.AllowedHosts) == 0 {
   119  		return true
   120  	}
   121  
   122  	host := c.Request.Host
   123  	if len(host) == 0 {
   124  		host = c.Request.URL.Host
   125  	}
   126  
   127  	for _, allowedHost := range p.config.AllowedHosts {
   128  		if strings.EqualFold(allowedHost, host) {
   129  			return true
   130  		}
   131  	}
   132  
   133  	if p.config.BadHostHandler != nil {
   134  		p.config.BadHostHandler(c)
   135  	} else {
   136  		c.AbortWithStatus(403)
   137  	}
   138  
   139  	return false
   140  }
   141  
   142  // checks if a host (possibly with trailing port) is an IPV4 address
   143  func isIPV4(host string) bool {
   144  	if index := strings.IndexByte(host, ':'); index != -1 {
   145  		host = host[:index]
   146  	}
   147  	return net.ParseIP(host) != nil
   148  }
   149  
   150  func (p *policy) isSSLRequest(req *http.Request) bool {
   151  	if strings.EqualFold(req.URL.Scheme, "https") || req.TLS != nil {
   152  		return true
   153  	}
   154  
   155  	for h, v := range p.config.SSLProxyHeaders {
   156  		hv, ok := req.Header[h]
   157  
   158  		if !ok {
   159  			continue
   160  		}
   161  
   162  		if strings.EqualFold(hv[0], v) {
   163  			return true
   164  		}
   165  	}
   166  
   167  	if p.config.DontRedirectIPV4Hostnames && isIPV4(req.Host) {
   168  		return true
   169  	}
   170  
   171  	return false
   172  }
   173  
   174  func (p *policy) checkSSL(c *gin.Context) bool {
   175  	if !p.config.SSLRedirect {
   176  		return true
   177  	}
   178  
   179  	req := c.Request
   180  	isSSLRequest := p.isSSLRequest(req)
   181  	if isSSLRequest {
   182  		return true
   183  	}
   184  
   185  	// TODO
   186  	// req.Host vs req.URL.Host
   187  	url := req.URL
   188  	url.Scheme = "https"
   189  	url.Host = req.Host
   190  
   191  	if len(p.config.SSLHost) > 0 {
   192  		url.Host = p.config.SSLHost
   193  	}
   194  
   195  	status := http.StatusMovedPermanently
   196  	if p.config.SSLTemporaryRedirect {
   197  		status = http.StatusTemporaryRedirect
   198  	}
   199  	c.Redirect(status, url.String())
   200  	c.Abort()
   201  	return false
   202  }
   203  

View as plain text