Skip to content

Commit 37d9ea4

Browse files
committed
update to allow default (set if empty/blank from upstream) and force (always write) headers
1 parent 7ce8de4 commit 37d9ea4

File tree

3 files changed

+66
-62
lines changed

3 files changed

+66
-62
lines changed

.traefik.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ import: github.com/cdwiegand/standard-security-headers-plugin
44
basePkg: standard_security_headers
55
summary: 'Adds standard security headers by default'
66
testData:
7-
addCspHeader: true
7+
sanitizeExposingHeaders: true

README.md

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,21 @@
22

33
This plugin will append some standard security headers based on the response mime-type.
44

5-
## Configuration
5+
## Configuration example:
66

7-
1. Enable the plugin in your Traefik configuration:
8-
9-
```yaml
10-
experimental:
11-
plugins:
12-
standard_security_headers:
13-
moduleName: github.com/cdwiegand/standard-security-headers-plugin
14-
version: v0.2.1
157
```
16-
17-
1. Define the middleware. Note that this plugin does not need any configuration, however, values must be passed in for it to be accepted within Traefik:
18-
19-
```yaml
208
http:
21-
# ...
22-
middlewares:
23-
# this name must match the middleware that you attach to routers later
24-
security-headers:
25-
plugin:
26-
standard_security_headers:
27-
removeExposingHeaders: true
9+
middlewares:
10+
standard-security-headers:
11+
plugin:
12+
standard-security-headers:
13+
sanitizeExposingHeaders: "true"
14+
defaultHeaders:
15+
xframeOptions: "SAMEORIGIN"
16+
forceHeaders:
17+
contentTypeOptions: "nosniff"
2818
```
2919

30-
Please note that traefik requires at least one configuration variable set, to keep the defaults you can set `addCspHeader: true` to accomodate this. *This is not a requirement of this plugin, but a traefik requirement.*
31-
32-
1. Then add it to your given routers, such as this:
33-
34-
```yaml
35-
http:
36-
# ...
37-
routers:
38-
example-router:
39-
rule: host(`demo.localhost`)
40-
service: service-foo
41-
entryPoints:
42-
- web
43-
# add these 2 lines, use the same name you defined directly under "middlewares":
44-
middlewares:
45-
- security-headers
46-
# end add those 2 lines
47-
```
48-
49-
1. You are done!
50-
5120
## Testing Methods
5221

5322
Testing by using local plugin functionality, assuming the code is checked out to `C:\devel\standard-security-headers-plugin`:

security-headers.go

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,46 @@ import (
1010
const (
1111
Header_XFrameOptions = "X-Frame-Options"
1212
Default_XFrameOptions = "SAMEORIGIN"
13+
Force_XFrameOptions = ""
1314
Header_ContentTypeOptions = "X-Content-Type-Options"
1415
Default_ContentTypeOptions = "nosniff"
16+
Force_ContentTypeOptions = "nosniff"
1517
Header_XssProtection = "X-XSS-Protection"
1618
Default_XssProtection = "1; mode=block"
19+
Force_XssProtection = "1; mode=block"
1720
Header_ReferrerPolicy = "Referrer-Policy"
1821
Default_ReferrerPolicy = "strict-origin-when-cross-origin"
22+
Force_ReferrerPolicy = ""
1923
Header_StrictTransportSecurity = "Strict-Transport-Security"
2024
Default_StrictTransportSecurity = "max-age=63072000; includeSubDomains; preload"
25+
Force_StrictTransportSecurity = ""
2126
Header_ContentSecurityPolicy = "Content-Security-Policy"
2227
Default_ContentSecurityPolicy = ""
28+
Force_ContentSecurityPolicy = ""
2329
Header_ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
2430
Default_ContentSecurityPolicyReportOnly = ""
31+
Force_ContentSecurityPolicyReportOnly = ""
2532
Header_CrossOriginOpenerPolicy = "Cross-Origin-Opener-Policy"
2633
Default_CrossOriginOpenerPolicy = ""
34+
Force_CrossOriginOpenerPolicy = ""
2735
Header_CrossOriginEmbedderPolicy = "Cross-Origin-Embedder-Policy"
2836
Default_CrossOriginEmbedderPolicy = ""
37+
Force_CrossOriginEmbedderPolicy = ""
2938
Header_CrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
3039
Default_CrossOriginResourcePolicy = ""
40+
Force_CrossOriginResourcePolicy = ""
3141
Header_PermissionsPolicy = "Permissions-Policy"
3242
Default_PermissionsPolicy = ""
43+
Force_PermissionsPolicy = ""
3344
)
3445

3546
// Config the plugin configuration.
3647
type Config struct {
3748
SanitizeExposingHeaders bool `json:"sanitizeExposingHeaders"`
49+
DefaultHeaders ConfigHeaders `json:"defaultHeaders"`
50+
ForceHeaders ConfigHeaders `json:"forceHeaders"`
51+
}
52+
type ConfigHeaders struct {
3853
XFrameOptions string `json:"xframeOptions"`
3954
ContentTypeOptions string `json:"contentTypeOptions"`
4055
XssProtection string `json:"xssProtection"`
@@ -50,8 +65,7 @@ type Config struct {
5065

5166
// CreateConfig creates the DEFAULT plugin configuration - no access to config yet!
5267
func CreateConfig() *Config {
53-
return &Config{
54-
SanitizeExposingHeaders: true,
68+
defaultHeaders := ConfigHeaders {
5569
XFrameOptions: Default_XFrameOptions,
5670
ContentTypeOptions: Default_ContentTypeOptions,
5771
XssProtection: Default_XssProtection,
@@ -63,6 +77,26 @@ func CreateConfig() *Config {
6377
CrossOriginEmbedderPolicy: Default_CrossOriginEmbedderPolicy,
6478
CrossOriginResourcePolicy: Default_CrossOriginResourcePolicy,
6579
PermissionsPolicy: Default_PermissionsPolicy,
80+
}
81+
82+
forceHeaders := ConfigHeaders {
83+
XFrameOptions: Force_XFrameOptions,
84+
ContentTypeOptions: Force_ContentTypeOptions,
85+
XssProtection: Force_XssProtection,
86+
ReferrerPolicy: Force_ReferrerPolicy,
87+
StrictTransportSecurity: Force_StrictTransportSecurity,
88+
ContentSecurityPolicy: Force_ContentSecurityPolicy,
89+
ContentSecurityPolicyReportOnly: Force_ContentSecurityPolicyReportOnly,
90+
CrossOriginOpenerPolicy: Force_CrossOriginOpenerPolicy,
91+
CrossOriginEmbedderPolicy: Force_CrossOriginEmbedderPolicy,
92+
CrossOriginResourcePolicy: Force_CrossOriginResourcePolicy,
93+
PermissionsPolicy: Force_PermissionsPolicy,
94+
}
95+
96+
return &Config{
97+
SanitizeExposingHeaders: true,
98+
DefaultHeaders: defaultHeaders,
99+
ForceHeaders: forceHeaders,
66100
}
67101
}
68102

@@ -102,8 +136,8 @@ func (t *StandardSecurityPlugin) ServeHTTP(rw http.ResponseWriter, req *http.Req
102136

103137
if contentTypeIsOrStartsWith(contentType, "text/html") {
104138
// text/html only
105-
handleHeader(headers, Header_XFrameOptions, t.Config.XFrameOptions)
106-
handleHeader(headers, Header_XssProtection, t.Config.XssProtection)
139+
handleHeader(headers, Header_XFrameOptions, t.Config.DefaultHeaders.XFrameOptions, t.Config.ForceHeaders.XFrameOptions)
140+
handleHeader(headers, Header_XssProtection, t.Config.DefaultHeaders.XssProtection, t.Config.ForceHeaders.XssProtection)
107141
} else {
108142
headers.Del(Header_XFrameOptions)
109143
headers.Del(Header_XssProtection)
@@ -115,20 +149,20 @@ func (t *StandardSecurityPlugin) ServeHTTP(rw http.ResponseWriter, req *http.Req
115149
contentTypeIsOrStartsWith(contentType, "text/javascript") ||
116150
contentTypeIsOrStartsWith(contentType, "application/pdf") ||
117151
contentTypeIsOrStartsWith(contentType, "image/svg+xml") {
118-
handleHeader(headers, Header_ContentSecurityPolicy, t.Config.ContentSecurityPolicy)
119-
handleHeader(headers, Header_ContentSecurityPolicyReportOnly, t.Config.ContentSecurityPolicyReportOnly)
152+
handleHeader(headers, Header_ContentSecurityPolicy, t.Config.DefaultHeaders.ContentSecurityPolicy, t.Config.ForceHeaders.ContentSecurityPolicy)
153+
handleHeader(headers, Header_ContentSecurityPolicyReportOnly, t.Config.DefaultHeaders.ContentSecurityPolicyReportOnly, t.Config.ForceHeaders.ContentSecurityPolicyReportOnly)
120154
} else {
121155
headers.Del(Header_ContentSecurityPolicy)
122156
headers.Del(Header_ContentSecurityPolicyReportOnly)
123157
}
124158

125-
handleHeader(headers, Header_ContentTypeOptions, t.Config.ContentTypeOptions)
126-
handleHeader(headers, Header_ReferrerPolicy, t.Config.ReferrerPolicy)
127-
handleHeader(headers, Header_StrictTransportSecurity, t.Config.StrictTransportSecurity)
128-
handleHeader(headers, Header_CrossOriginOpenerPolicy, t.Config.CrossOriginOpenerPolicy)
129-
handleHeader(headers, Header_CrossOriginEmbedderPolicy, t.Config.CrossOriginEmbedderPolicy)
130-
handleHeader(headers, Header_CrossOriginResourcePolicy, t.Config.CrossOriginResourcePolicy)
131-
handleHeader(headers, Header_PermissionsPolicy, t.Config.PermissionsPolicy)
159+
handleHeader(headers, Header_ContentTypeOptions, t.Config.DefaultHeaders.ContentTypeOptions, t.Config.ForceHeaders.ContentTypeOptions)
160+
handleHeader(headers, Header_ReferrerPolicy, t.Config.DefaultHeaders.ReferrerPolicy, t.Config.ForceHeaders.ReferrerPolicy)
161+
handleHeader(headers, Header_StrictTransportSecurity, t.Config.DefaultHeaders.StrictTransportSecurity, t.Config.ForceHeaders.StrictTransportSecurity)
162+
handleHeader(headers, Header_CrossOriginOpenerPolicy, t.Config.DefaultHeaders.CrossOriginOpenerPolicy, t.Config.ForceHeaders.CrossOriginOpenerPolicy)
163+
handleHeader(headers, Header_CrossOriginEmbedderPolicy, t.Config.DefaultHeaders.CrossOriginEmbedderPolicy, t.Config.ForceHeaders.CrossOriginEmbedderPolicy)
164+
handleHeader(headers, Header_CrossOriginResourcePolicy, t.Config.DefaultHeaders.CrossOriginResourcePolicy, t.Config.ForceHeaders.CrossOriginResourcePolicy)
165+
handleHeader(headers, Header_PermissionsPolicy, t.Config.DefaultHeaders.PermissionsPolicy, t.Config.ForceHeaders.PermissionsPolicy)
132166

133167
t.next.ServeHTTP(rw, req)
134168
}
@@ -137,13 +171,14 @@ func contentTypeIsOrStartsWith(haystack string, match string) bool {
137171
return haystack == match || strings.HasPrefix(haystack, match+";")
138172
}
139173

140-
func handleHeader(headers http.Header, headerName string, newValue string) {
141-
if newValue == "" {
142-
return
143-
} else if newValue == "-" { // - means remove value
144-
headers.Del(headerName)
145-
} else {
146-
headers.Set(headerName, newValue)
174+
func handleHeader(headers http.Header, headerName string, defaultValue string, forceValue string) {
175+
if forceValue != "" {
176+
headers[headerName] = []string{forceValue}
177+
return
178+
}
179+
180+
if defaultValue != "" && headers.Get(headerName) == "" {
181+
headers[headerName]= []string{defaultValue}
147182
}
148183
}
149184

0 commit comments

Comments
 (0)