-
Notifications
You must be signed in to change notification settings - Fork 14
/
secureheader.go
236 lines (209 loc) · 7.25 KB
/
secureheader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Package secureheader adds some HTTP header fields widely
// considered to improve safety of HTTP requests. These fields
// are documented as follows:
//
// Strict Transport Security: https://tools.ietf.org/html/rfc6797
// Frame Options: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-00
// Cross Site Scripting: https://msdn.microsoft.com/en-us/library/dd565647%28v=vs.85%29.aspx
// Content Type Options: https://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx
// Content Security Policy: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html
//
// The easiest way to use this package:
//
// http.ListenAndServe(addr, secureheader.DefaultConfig)
//
// DefaultConfig is initialized with conservative (safer and more
// restrictive) behavior. If you want to change that, set its
// fields to different values before calling ListenAndServe. See
// the example code below.
//
// This package was inspired by Twitter's secureheaders Ruby
// library. See https://github.com/twitter/secureheaders.
package secureheader
import (
"net"
"net/http"
"strconv"
"time"
)
// DefaultConfig is initialized with conservative (safer and more
// restrictive) behavior.
var DefaultConfig = &Config{
HTTPSRedirect: true,
HTTPSUseForwardedProto: ShouldUseForwardedProto(),
PermitClearLoopback: false,
ContentTypeOptions: true,
CSP: false,
CSPBody: "default-src 'self'",
CSPReportURI: "",
CSPReportOnly: false,
CSPReportOnlyBody: "default-src 'self'",
CSPReportOnlyReportURI: "",
HSTS: true,
HSTSMaxAge: 300 * 24 * time.Hour,
HSTSIncludeSubdomains: true,
HSTSPreload: false,
FrameOptions: true,
FrameOptionsPolicy: Deny,
XSSProtection: true,
XSSProtectionBlock: true,
}
// Handler returns a new HTTP handler
// using the configuration in DefaultConfig,
// serving requests using h.
// If h is nil, it uses http.DefaultServeMux.
func Handler(h http.Handler) *Config {
c := new(Config)
*c = *DefaultConfig
c.Next = h
return c
}
type Config struct {
// If true, redirects any request with scheme http to the
// equivalent https URL.
HTTPSRedirect bool
HTTPSUseForwardedProto bool
// Allow cleartext (non-HTTPS) HTTP connections to a loopback
// address, even if HTTPSRedirect is true.
PermitClearLoopback bool
// If true, sets X-Content-Type-Options to "nosniff".
ContentTypeOptions bool
// If true, send a Content-Security-Policy header. For more
// information on deploying CSP, see for example
// https://medium.com/sourceclear/content-security-policy-with-sentry-efb04f336f59
// Dsiabled by default. If you set CSP = true,
// the default policy is "default-src 'self'" and reporting is disabled.
// To enable reporting, set CSPReportURI to your reporting endpoint.
CSP bool
CSPBody string
CSPReportURI string
// If true, the browser will report CSP violations, but won't enforce them
// It *is* meaningful to set both headers
// Content-Security-Policy *AND* Content-Security-Policy-Report-Only
// and give them different bodys & report-uri's. The browser will
// enforce the former, but only generate warnings on the latter.
// Like CSPBody, the default is "default-src 'self'", and
// Set CSPReportOnlyReportURI to your reporting endpoint.
CSPReportOnly bool
CSPReportOnlyBody string
CSPReportOnlyReportURI string
// If true, sets the HTTP Strict Transport Security header
// field, which instructs browsers to send future requests
// over HTTPS, even if the URL uses the unencrypted http
// scheme.
HSTS bool
HSTSMaxAge time.Duration
HSTSIncludeSubdomains bool
HSTSPreload bool
// If true, sets X-Frame-Options, to control when the request
// should be displayed inside an HTML frame.
FrameOptions bool
FrameOptionsPolicy FramePolicy
// If true, sets X-XSS-Protection to "1", optionally with
// "mode=block". See the official documentation, linked above,
// for the meaning of these values.
XSSProtection bool
XSSProtectionBlock bool
// Used by ServeHTTP, after setting any extra headers, to
// reply to the request. Next is typically nil, in which case
// http.DefaultServeMux is used instead.
Next http.Handler
}
// ServeHTTP sets header fields on w according to the options in
// c, then either replies directly or runs c.Next to reply.
// Typically c.Next is nil, in which case http.DefaultServeMux is
// used instead.
func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if c.HTTPSRedirect && !c.isHTTPS(r) && !c.okloopback(r) {
url := *r.URL
url.Scheme = "https"
url.Host = r.Host
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
return
}
if c.ContentTypeOptions {
w.Header().Set("X-Content-Type-Options", "nosniff")
}
if c.CSP {
v := c.CSPBody
if c.CSPReportURI != "" {
v += "; report-uri " + c.CSPReportURI
}
w.Header().Set("Content-Security-Policy", v)
}
if c.CSPReportOnly {
v := c.CSPReportOnlyBody
if c.CSPReportOnlyReportURI != "" {
v += "; report-uri " + c.CSPReportOnlyReportURI
}
w.Header().Set("Content-Security-Policy-Report-Only", v)
}
if c.HSTS && c.isHTTPS(r) {
v := "max-age=" + strconv.FormatInt(int64(c.HSTSMaxAge/time.Second), 10)
if c.HSTSIncludeSubdomains {
v += "; includeSubDomains"
}
if c.HSTSPreload {
v += "; preload"
}
w.Header().Set("Strict-Transport-Security", v)
}
if c.FrameOptions {
w.Header().Set("X-Frame-Options", string(c.FrameOptionsPolicy))
}
if c.XSSProtection {
v := "1"
if c.XSSProtectionBlock {
v += "; mode=block"
}
w.Header().Set("X-XSS-Protection", v)
}
next := c.Next
if next == nil {
next = http.DefaultServeMux
}
next.ServeHTTP(w, r)
}
// Given that r is cleartext (not HTTPS), okloopback returns
// whether r is on a permitted loopback connection.
func (c *Config) okloopback(r *http.Request) bool {
return c.PermitClearLoopback && isLoopback(r)
}
func (c *Config) isHTTPS(r *http.Request) bool {
if c.HTTPSUseForwardedProto {
return r.Header.Get("X-Forwarded-Proto") == "https"
}
return r.TLS != nil
}
// FramePolicy tells the browser under what circumstances to allow
// the response to be displayed inside an HTML frame. There are
// three options:
//
// Deny do not permit display in a frame
// SameOrigin permit display in a frame from the same origin
// AllowFrom(url) permit display in a frame from the given url
type FramePolicy string
const (
Deny FramePolicy = "DENY"
SameOrigin FramePolicy = "SAMEORIGIN"
)
// AllowFrom returns a FramePolicy specifying that the requested
// resource should be included in a frame from only the given url.
func AllowFrom(url string) FramePolicy {
return FramePolicy("ALLOW-FROM: " + url)
}
// ShouldUseForwardedProto returns whether to trust the
// X-Forwarded-Proto header field.
// DefaultConfig.HTTPSUseForwardedProto is initialized to this
// value.
//
// This value depends on the particular environment where the
// package is built. It is currently true iff build constraint
// "heroku" is satisfied.
func ShouldUseForwardedProto() bool {
return defaultUseForwardedProto
}
func isLoopback(r *http.Request) bool {
a, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
return err == nil && a.IP.IsLoopback()
}