mirror of
https://github.com/jakejarvis/npqueue.git
synced 2026-07-26 14:16:02 -04:00
gin secure middleware
This commit is contained in:
+184
@@ -0,0 +1,184 @@
|
||||
package secure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type (
|
||||
// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
|
||||
// provided to configure which features should be enabled, and the ability to override a few of the default values.
|
||||
policy struct {
|
||||
// Customize Secure with an Options struct.
|
||||
config Config
|
||||
fixedHeaders []header
|
||||
}
|
||||
|
||||
header struct {
|
||||
key string
|
||||
value []string
|
||||
}
|
||||
)
|
||||
|
||||
// Constructs a new Policy instance with supplied options.
|
||||
func newPolicy(config Config) *policy {
|
||||
policy := &policy{}
|
||||
policy.loadConfig(config)
|
||||
return policy
|
||||
}
|
||||
|
||||
func (p *policy) loadConfig(config Config) {
|
||||
p.config = config
|
||||
p.fixedHeaders = make([]header, 0, 5)
|
||||
|
||||
// Frame Options header.
|
||||
if len(config.CustomFrameOptionsValue) > 0 {
|
||||
p.addHeader("X-Frame-Options", config.CustomFrameOptionsValue)
|
||||
} else if config.FrameDeny {
|
||||
p.addHeader("X-Frame-Options", "DENY")
|
||||
}
|
||||
|
||||
// Content Type Options header.
|
||||
if config.ContentTypeNosniff {
|
||||
p.addHeader("X-Content-Type-Options", "nosniff")
|
||||
}
|
||||
|
||||
// XSS Protection header.
|
||||
if config.BrowserXssFilter {
|
||||
p.addHeader("X-Xss-Protection", "1; mode=block")
|
||||
}
|
||||
|
||||
// Content Security Policy header.
|
||||
if len(config.ContentSecurityPolicy) > 0 {
|
||||
p.addHeader("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
}
|
||||
|
||||
if len(config.ReferrerPolicy) > 0 {
|
||||
p.addHeader("Referrer-Policy", config.ReferrerPolicy)
|
||||
}
|
||||
|
||||
// Strict Transport Security header.
|
||||
if config.STSSeconds != 0 {
|
||||
stsSub := ""
|
||||
if config.STSIncludeSubdomains {
|
||||
stsSub = "; includeSubdomains"
|
||||
}
|
||||
|
||||
// TODO
|
||||
// "max-age=%d%s" refactor
|
||||
p.addHeader(
|
||||
"Strict-Transport-Security",
|
||||
fmt.Sprintf("max-age=%d%s", config.STSSeconds, stsSub))
|
||||
}
|
||||
|
||||
// X-Download-Options header.
|
||||
if config.IENoOpen {
|
||||
p.addHeader("X-Download-Options", "noopen")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *policy) addHeader(key string, value string) {
|
||||
p.fixedHeaders = append(p.fixedHeaders, header{
|
||||
key: key,
|
||||
value: []string{value},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *policy) applyToContext(c *gin.Context) bool {
|
||||
if !p.config.IsDevelopment {
|
||||
p.writeSecureHeaders(c)
|
||||
|
||||
if !p.checkAllowHosts(c) {
|
||||
return false
|
||||
}
|
||||
if !p.checkSSL(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *policy) writeSecureHeaders(c *gin.Context) {
|
||||
header := c.Writer.Header()
|
||||
for _, pair := range p.fixedHeaders {
|
||||
header[pair.key] = pair.value
|
||||
}
|
||||
}
|
||||
|
||||
func (p *policy) checkAllowHosts(c *gin.Context) bool {
|
||||
if len(p.config.AllowedHosts) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
host := c.Request.Host
|
||||
if len(host) == 0 {
|
||||
host = c.Request.URL.Host
|
||||
}
|
||||
|
||||
for _, allowedHost := range p.config.AllowedHosts {
|
||||
if strings.EqualFold(allowedHost, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.BadHostHandler != nil {
|
||||
p.config.BadHostHandler(c)
|
||||
} else {
|
||||
c.AbortWithStatus(403)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *policy) isSSLRequest(req *http.Request) bool {
|
||||
if strings.EqualFold(req.URL.Scheme, "https") || req.TLS != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for h, v := range p.config.SSLProxyHeaders {
|
||||
hv, ok := req.Header[h]
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold(hv[0], v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *policy) checkSSL(c *gin.Context) bool {
|
||||
if !p.config.SSLRedirect {
|
||||
return true
|
||||
}
|
||||
|
||||
req := c.Request
|
||||
isSSLRequest := p.isSSLRequest(req)
|
||||
if isSSLRequest {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO
|
||||
// req.Host vs req.URL.Host
|
||||
url := req.URL
|
||||
url.Scheme = "https"
|
||||
url.Host = req.Host
|
||||
|
||||
if len(p.config.SSLHost) > 0 {
|
||||
url.Host = p.config.SSLHost
|
||||
}
|
||||
|
||||
status := http.StatusMovedPermanently
|
||||
if p.config.SSLTemporaryRedirect {
|
||||
status = http.StatusTemporaryRedirect
|
||||
}
|
||||
c.Redirect(status, url.String())
|
||||
c.Abort()
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user