• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..19-Feb-2022-

.gitignoreH A D19-Feb-2022282 2821

LICENSEH A D19-Feb-20221.1 KiB2116

README.mdH A D19-Feb-202214.5 KiB396319

csp.goH A D19-Feb-20221.1 KiB4530

doc.goH A D19-Feb-2022638 261

go.modH A D19-Feb-202285 63

go.sumH A D19-Feb-2022169 32

secure.goH A D19-Feb-202218 KiB488318

README.md

1# Secure [![GoDoc](https://godoc.org/github.com/unrolled/secure?status.svg)](http://godoc.org/github.com/unrolled/secure) [![Test](https://github.com/unrolled/secure/workflows/Test/badge.svg?branch=v1)](https://github.com/unrolled/secure/actions)
2
3Secure is an HTTP middleware for Go that facilitates some quick security wins. It's a standard net/http [Handler](http://golang.org/pkg/net/http/#Handler), and can be used with many [frameworks](#integration-examples) or directly with Go's net/http package.
4
5## Usage
6
7~~~ go
8// main.go
9package main
10
11import (
12    "net/http"
13
14    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
15)
16
17var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18    w.Write([]byte("hello world"))
19})
20
21func main() {
22    secureMiddleware := secure.New(secure.Options{
23        AllowedHosts:          []string{"example\\.com", ".*\\.example\\.com"},
24        AllowedHostsAreRegex:  true,
25        HostsProxyHeaders:     []string{"X-Forwarded-Host"},
26        SSLRedirect:           true,
27        SSLHost:               "ssl.example.com",
28        SSLProxyHeaders:       map[string]string{"X-Forwarded-Proto": "https"},
29        STSSeconds:            31536000,
30        STSIncludeSubdomains:  true,
31        STSPreload:            true,
32        FrameDeny:             true,
33        ContentTypeNosniff:    true,
34        BrowserXssFilter:      true,
35        ContentSecurityPolicy: "script-src $NONCE",
36    })
37
38    app := secureMiddleware.Handler(myHandler)
39    http.ListenAndServe("127.0.0.1:3000", app)
40}
41~~~
42
43Be sure to include the Secure middleware as close to the top (beginning) as possible (but after logging and recovery). It's best to do the allowed hosts and SSL check before anything else.
44
45The above example will only allow requests with a host name of 'example.com', or 'ssl.example.com'. Also if the request is not HTTPS, it will be redirected to HTTPS with the host name of 'ssl.example.com'.
46Once those requirements are satisfied, it will add the following headers:
47~~~ go
48Strict-Transport-Security: 31536000; includeSubdomains; preload
49X-Frame-Options: DENY
50X-Content-Type-Options: nosniff
51X-XSS-Protection: 1; mode=block
52Content-Security-Policy: script-src 'nonce-a2ZobGFoZg=='
53~~~
54
55### Set the `IsDevelopment` option to `true` when developing!
56When `IsDevelopment` is true, the AllowedHosts, SSLRedirect, STS header, and HPKP header will not be in effect. This allows you to work in development/test mode and not have any annoying redirects to HTTPS (ie. development can happen on HTTP), or block `localhost` has a bad host.
57
58### Available options
59Secure comes with a variety of configuration options (Note: these are not the default option values. See the defaults below.):
60
61~~~ go
62// ...
63s := secure.New(secure.Options{
64    AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
65    AllowedHostsAreRegex: false,  // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false.
66    HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
67    SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false.
68    SSLTemporaryRedirect: false, // If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
69    SSLHost: "ssl.example.com", // SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
70    SSLHostFunc: nil, // SSLHostFunc is a function pointer, the return value of the function is the host name that has same functionality as `SSHost`. Default is nil. If SSLHostFunc is nil, the `SSLHost` option will be used.
71    SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, // SSLProxyHeaders is set of header keys with associated values that would indicate a valid HTTPS request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
72    STSSeconds: 31536000, // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
73    STSIncludeSubdomains: true, // If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
74    STSPreload: true, // If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
75    ForceSTSHeader: false, // STS header is only included when the connection is HTTPS. If you want to force it to always be added, set to true. `IsDevelopment` still overrides this. Default is false.
76    FrameDeny: true, // If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
77    CustomFrameOptionsValue: "SAMEORIGIN", // CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option. Default is "".
78    ContentTypeNosniff: true, // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
79    BrowserXssFilter: true, // If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
80    CustomBrowserXssValue: "1; report=https://example.com/xss-report", // CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "".
81    ContentSecurityPolicy: "default-src 'self'", // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "". Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be later retrieved using the Nonce function.
82    PublicKey: `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`, // Deprecated: This feature is no longer recommended. PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "".
83    ReferrerPolicy: "same-origin", // ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "".
84    FeaturePolicy: "vibrate 'none';", // Deprecated: this header has been renamed to PermissionsPolicy. FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
85    PermissionsPolicy: "fullscreen=(), geolocation=()", // PermissionsPolicy allows the Permissions-Policy header with the value to be set with a custom value. Default is "".
86    ExpectCTHeader: `enforce, max-age=30, report-uri="https://www.example.com/ct-report"`,
87
88    IsDevelopment: true, // This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false.
89})
90// ...
91~~~
92
93### Default options
94These are the preset options for Secure:
95
96~~~ go
97s := secure.New()
98
99// Is the same as the default configuration options:
100
101l := secure.New(secure.Options{
102    AllowedHosts: []string,
103    AllowedHostsAreRegex: false,
104    HostsProxyHeaders: []string,
105    SSLRedirect: false,
106    SSLTemporaryRedirect: false,
107    SSLHost: "",
108    SSLProxyHeaders: map[string]string{},
109    STSSeconds: 0,
110    STSIncludeSubdomains: false,
111    STSPreload: false,
112    ForceSTSHeader: false,
113    FrameDeny: false,
114    CustomFrameOptionsValue: "",
115    ContentTypeNosniff: false,
116    BrowserXssFilter: false,
117    ContentSecurityPolicy: "",
118    PublicKey: "",
119    ReferrerPolicy: "",
120    FeaturePolicy: "",
121    PermissionsPolicy: "",
122    ExpectCTHeader: "",
123    IsDevelopment: false,
124})
125~~~
126Also note the default bad host handler returns an error:
127~~~ go
128http.Error(w, "Bad Host", http.StatusInternalServerError)
129~~~
130Call `secure.SetBadHostHandler` to change the bad host handler.
131
132### Redirecting HTTP to HTTPS
133If you want to redirect all HTTP requests to HTTPS, you can use the following example.
134
135~~~ go
136// main.go
137package main
138
139import (
140    "log"
141    "net/http"
142
143    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
144)
145
146var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
147    w.Write([]byte("hello world"))
148})
149
150func main() {
151    secureMiddleware := secure.New(secure.Options{
152        SSLRedirect: true,
153        SSLHost:     "localhost:8443", // This is optional in production. The default behavior is to just redirect the request to the HTTPS protocol. Example: http://github.com/some_page would be redirected to https://github.com/some_page.
154    })
155
156    app := secureMiddleware.Handler(myHandler)
157
158    // HTTP
159    go func() {
160        log.Fatal(http.ListenAndServe(":8080", app))
161    }()
162
163    // HTTPS
164    // To generate a development cert and key, run the following from your *nix terminal:
165    // go run $GOROOT/src/crypto/tls/generate_cert.go --host="localhost"
166    log.Fatal(http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", app))
167}
168~~~
169
170### Strict Transport Security
171The STS header will only be sent on verified HTTPS connections (and when `IsDevelopment` is false). Be sure to set the `SSLProxyHeaders` option if your application is behind a proxy to ensure the proper behavior. If you need the STS header for all HTTP and HTTPS requests (which you [shouldn't](http://tools.ietf.org/html/rfc6797#section-7.2)), you can use the `ForceSTSHeader` option. Note that if `IsDevelopment` is true, it will still disable this header even when `ForceSTSHeader` is set to true.
172
173* The `preload` flag is required for domain inclusion in Chrome's [preload](https://hstspreload.appspot.com/) list.
174
175### Content Security Policy
176If you need dynamic support for CSP while using Websockets, check out this other middleware [awakenetworks/csp](https://github.com/awakenetworks/csp).
177
178## Integration examples
179
180### [chi](https://github.com/pressly/chi)
181~~~ go
182// main.go
183package main
184
185import (
186    "net/http"
187
188    "github.com/pressly/chi"
189    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
190)
191
192func main() {
193    secureMiddleware := secure.New(secure.Options{
194        FrameDeny: true,
195    })
196
197    r := chi.NewRouter()
198    r.Use(secureMiddleware.Handler)
199
200    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
201        w.Write([]byte("X-Frame-Options header is now `DENY`."))
202    })
203
204    http.ListenAndServe("127.0.0.1:3000", r)
205}
206~~~
207
208### [Echo](https://github.com/labstack/echo)
209~~~ go
210// main.go
211package main
212
213import (
214    "net/http"
215
216    "github.com/labstack/echo"
217    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
218)
219
220func main() {
221    secureMiddleware := secure.New(secure.Options{
222        FrameDeny: true,
223    })
224
225    e := echo.New()
226    e.GET("/", func(c echo.Context) error {
227        return c.String(http.StatusOK, "X-Frame-Options header is now `DENY`.")
228    })
229
230    e.Use(echo.WrapMiddleware(secureMiddleware.Handler))
231    e.Logger.Fatal(e.Start("127.0.0.1:3000"))
232}
233~~~
234
235### [Gin](https://github.com/gin-gonic/gin)
236~~~ go
237// main.go
238package main
239
240import (
241    "github.com/gin-gonic/gin"
242    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
243)
244
245func main() {
246    secureMiddleware := secure.New(secure.Options{
247        FrameDeny: true,
248    })
249    secureFunc := func() gin.HandlerFunc {
250        return func(c *gin.Context) {
251            err := secureMiddleware.Process(c.Writer, c.Request)
252
253            // If there was an error, do not continue.
254            if err != nil {
255                c.Abort()
256                return
257            }
258
259            // Avoid header rewrite if response is a redirection.
260            if status := c.Writer.Status(); status > 300 && status < 399 {
261                c.Abort()
262            }
263        }
264    }()
265
266    router := gin.Default()
267    router.Use(secureFunc)
268
269    router.GET("/", func(c *gin.Context) {
270        c.String(200, "X-Frame-Options header is now `DENY`.")
271    })
272
273    router.Run("127.0.0.1:3000")
274}
275~~~
276
277### [Goji](https://github.com/zenazn/goji)
278~~~ go
279// main.go
280package main
281
282import (
283    "net/http"
284
285    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
286    "github.com/zenazn/goji"
287    "github.com/zenazn/goji/web"
288)
289
290func main() {
291    secureMiddleware := secure.New(secure.Options{
292        FrameDeny: true,
293    })
294
295    goji.Get("/", func(c web.C, w http.ResponseWriter, req *http.Request) {
296        w.Write([]byte("X-Frame-Options header is now `DENY`."))
297    })
298    goji.Use(secureMiddleware.Handler)
299    goji.Serve() // Defaults to ":8000".
300}
301~~~
302
303### [Iris](https://github.com/kataras/iris)
304~~~ go
305//main.go
306package main
307
308import (
309    "github.com/kataras/iris/v12"
310    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
311)
312
313func main() {
314    app := iris.New()
315
316    secureMiddleware := secure.New(secure.Options{
317        FrameDeny: true,
318    })
319
320    app.Use(iris.FromStd(secureMiddleware.HandlerFuncWithNext))
321    // Identical to:
322    // app.Use(func(ctx iris.Context) {
323    //     err := secureMiddleware.Process(ctx.ResponseWriter(), ctx.Request())
324    //
325    //     // If there was an error, do not continue.
326    //     if err != nil {
327    //         return
328    //     }
329    //
330    //     ctx.Next()
331    // })
332
333    app.Get("/home", func(ctx iris.Context) {
334        ctx.Writef("X-Frame-Options header is now `%s`.", "DENY")
335    })
336
337    app.Listen(":8080")
338}
339~~~
340
341### [Mux](https://github.com/gorilla/mux)
342~~~ go
343//main.go
344package main
345
346import (
347    "log"
348    "net/http"
349
350    "github.com/gorilla/mux"
351    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
352)
353
354func main() {
355    secureMiddleware := secure.New(secure.Options{
356        FrameDeny: true,
357    })
358
359    r := mux.NewRouter()
360    r.Use(secureMiddleware.Handler)
361    http.Handle("/", r)
362    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil))
363}
364~~~
365
366### [Negroni](https://github.com/urfave/negroni)
367Note this implementation has a special helper function called `HandlerFuncWithNext`.
368~~~ go
369// main.go
370package main
371
372import (
373    "net/http"
374
375    "github.com/urfave/negroni"
376    "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
377)
378
379func main() {
380    mux := http.NewServeMux()
381    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
382        w.Write([]byte("X-Frame-Options header is now `DENY`."))
383    })
384
385    secureMiddleware := secure.New(secure.Options{
386        FrameDeny: true,
387    })
388
389    n := negroni.Classic()
390    n.Use(negroni.HandlerFunc(secureMiddleware.HandlerFuncWithNext))
391    n.UseHandler(mux)
392
393    n.Run("127.0.0.1:3000")
394}
395~~~
396