diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index c746def..f28ba8b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -6,6 +6,10 @@ "./..." ], "Deps": [ + { + "ImportPath": "github.com/gin-contrib/secure", + "Rev": "312887ea6e1f6d9b6dcb5fdd548f8c8dfb62eaca" + }, { "ImportPath": "github.com/gin-contrib/sse", "Comment": "v0.1.0", diff --git a/main.go b/main.go index ffeba6b..a9d4299 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "time" "github.com/gin-gonic/contrib/static" + "github.com/gin-contrib/secure" "github.com/gin-gonic/gin" _ "github.com/heroku/x/hmetrics/onload" ) @@ -206,6 +207,17 @@ func main() { port := ":" + os.Getenv("PORT") router := gin.Default() + router.Use(secure.New(secure.Config{ + SSLRedirect: true, + SSLHost: "np.pogge.rs", + STSSeconds: 315360000, + STSIncludeSubdomains: false, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, + })) + // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./public", true))) diff --git a/vendor/github.com/gin-contrib/secure/.gitignore b/vendor/github.com/gin-contrib/secure/.gitignore new file mode 100644 index 0000000..2d83068 --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/.gitignore @@ -0,0 +1 @@ +coverage.out diff --git a/vendor/github.com/gin-contrib/secure/.travis.yml b/vendor/github.com/gin-contrib/secure/.travis.yml new file mode 100644 index 0000000..e367b24 --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/.travis.yml @@ -0,0 +1,37 @@ +language: go +sudo: false + +go: + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - master + +matrix: + fast_finish: true + include: + - go: 1.11.x + env: GO111MODULE=on + - go: 1.12.x + env: GO111MODULE=on + +install: + - go get -d -t -v + - go get github.com/campoy/embedmd + +script: + - embedmd -d *.md + - go test -v -covermode=atomic -coverprofile=coverage.out . + +after_success: + - bash <(curl -s https://codecov.io/bash) + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/acc2c57482e94b44f557 + on_success: change + on_failure: always + on_start: false diff --git a/vendor/github.com/gin-contrib/secure/LICENSE b/vendor/github.com/gin-contrib/secure/LICENSE new file mode 100644 index 0000000..2ef350e --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 gin-contrib + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/gin-contrib/secure/README.md b/vendor/github.com/gin-contrib/secure/README.md new file mode 100644 index 0000000..ec62c4a --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/README.md @@ -0,0 +1,68 @@ +# Secure + +[![Build Status](https://travis-ci.org/gin-contrib/secure.svg)](https://travis-ci.org/gin-contrib/secure) +[![codecov](https://codecov.io/gh/gin-contrib/secure/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/secure) +[![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/secure)](https://goreportcard.com/report/github.com/gin-contrib/secure) +[![GoDoc](https://godoc.org/github.com/gin-contrib/secure?status.svg)](https://godoc.org/github.com/gin-contrib/secure) + +Secure meddleware for [Gin](https://github.com/gin-gonic/gin/) framework. + +## Example + +See the [example1](example/code1/example.go), [example2](example/code2/example.go). + +DefaultConfig returns a Configuration with strict security settings + +[embedmd]:# (secure.go go /func DefaultConfig/ /^}$/) +```go +func DefaultConfig() Config { + return Config{ + SSLRedirect: true, + IsDevelopment: false, + STSSeconds: 315360000, + STSIncludeSubdomains: true, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + ContentSecurityPolicy: "default-src 'self'", + IENoOpen: true, + SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, + } +} +``` + +[embedmd]:# (example/code1/example.go go) +```go +package main + +import ( + "github.com/gin-contrib/secure" + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + + router.Use(secure.New(secure.Config{ + AllowedHosts: []string{"example.com", "ssl.example.com"}, + SSLRedirect: true, + SSLHost: "ssl.example.com", + STSSeconds: 315360000, + STSIncludeSubdomains: true, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + ContentSecurityPolicy: "default-src 'self'", + IENoOpen: true, + ReferrerPolicy: "strict-origin-when-cross-origin", + SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, + })) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + // Listen and Server in 0.0.0.0:8080 + router.Run() +} +``` diff --git a/vendor/github.com/gin-contrib/secure/go.mod b/vendor/github.com/gin-contrib/secure/go.mod new file mode 100644 index 0000000..b3762c0 --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/go.mod @@ -0,0 +1,7 @@ +module github.com/gin-contrib/secure + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-gonic/gin v1.4.0 + github.com/stretchr/testify v1.3.0 +) diff --git a/vendor/github.com/gin-contrib/secure/go.sum b/vendor/github.com/gin-contrib/secure/go.sum new file mode 100644 index 0000000..a48bdd0 --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/go.sum @@ -0,0 +1,39 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/gin-contrib/secure/policy.go b/vendor/github.com/gin-contrib/secure/policy.go new file mode 100644 index 0000000..570ce87 --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/policy.go @@ -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 +} diff --git a/vendor/github.com/gin-contrib/secure/secure.go b/vendor/github.com/gin-contrib/secure/secure.go new file mode 100644 index 0000000..b5d5b0e --- /dev/null +++ b/vendor/github.com/gin-contrib/secure/secure.go @@ -0,0 +1,91 @@ +package secure + +import "github.com/gin-gonic/gin" + +// Config is a struct for specifying configuration options for the secure. +type Config struct { + // AllowedHosts is a list of fully qualified domain names that are allowed. + //Default is empty list, which allows any and all host names. + AllowedHosts []string + // If SSLRedirect is set to true, then only allow https requests. + // Default is false. + SSLRedirect bool + // If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. + // Default is false (301). + SSLTemporaryRedirect bool + // SSLHost is the host name that is used to redirect http requests to https. + // Default is "", which indicates to use the same host. + SSLHost string + // STSSeconds is the max-age of the Strict-Transport-Security header. + // Default is 0, which would NOT include the header. + STSSeconds int64 + // If STSIncludeSubdomains is set to true, the `includeSubdomains` will + // be appended to the Strict-Transport-Security header. Default is false. + STSIncludeSubdomains bool + // If FrameDeny is set to true, adds the X-Frame-Options header with + // the value of `DENY`. Default is false. + FrameDeny bool + // CustomFrameOptionsValue allows the X-Frame-Options header value + // to be set with a custom value. This overrides the FrameDeny option. + CustomFrameOptionsValue string + // If ContentTypeNosniff is true, adds the X-Content-Type-Options header + // with the value `nosniff`. Default is false. + ContentTypeNosniff bool + // If BrowserXssFilter is true, adds the X-XSS-Protection header with + // the value `1; mode=block`. Default is false. + BrowserXssFilter bool + // ContentSecurityPolicy allows the Content-Security-Policy header value + // to be set with a custom value. Default is "". + ContentSecurityPolicy string + // HTTP header "Referrer-Policy" governs which referrer information, sent in the Referrer header, should be included with requests made. + ReferrerPolicy string + // When true, the whole secury policy applied by the middleware is disable + // completely. + IsDevelopment bool + // Handlers for when an error occurs (ie bad host). + BadHostHandler gin.HandlerFunc + // Prevent Internet Explorer from executing downloads in your site’s context + IENoOpen bool + + // If the request is insecure, treat it as secure if any of the headers in this dict are set to their corresponding value + // This is useful when your app is running behind a secure proxy that forwards requests to your app over http (such as on Heroku). + SSLProxyHeaders map[string]string +} + +// DefaultConfig returns a Configuration with strict security settings. +// ``` +// SSLRedirect: true +// IsDevelopment: false +// STSSeconds: 315360000 +// STSIncludeSubdomains: true +// FrameDeny: true +// ContentTypeNosniff: true +// BrowserXssFilter: true +// ContentSecurityPolicy: "default-src 'self'" +// SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, +// ``` +func DefaultConfig() Config { + return Config{ + SSLRedirect: true, + IsDevelopment: false, + STSSeconds: 315360000, + STSIncludeSubdomains: true, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + ContentSecurityPolicy: "default-src 'self'", + IENoOpen: true, + SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, + } +} + +// New creates an instance of the secure middleware using the specified configuration. +// router.Use(secure.N) +func New(config Config) gin.HandlerFunc { + policy := newPolicy(config) + return func(c *gin.Context) { + if !policy.applyToContext(c) { + return + } + } +}