Headers middleware: support Content-Security-Policy-Report-Only

This commit is contained in:
Roman Donchenko 2024-06-07 10:24:04 +03:00 committed by GitHub
parent 67f0700377
commit b37aaea36d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 116 additions and 66 deletions

View file

@ -394,6 +394,10 @@ This overrides the `BrowserXssFilter` option.
The `contentSecurityPolicy` option allows the `Content-Security-Policy` header value to be set with a custom value. The `contentSecurityPolicy` option allows the `Content-Security-Policy` header value to be set with a custom value.
### `contentSecurityPolicyReportOnly`
The `contentSecurityPolicyReportOnly` option allows the `Content-Security-Policy-Report-Only` header value to be set with a custom value.
### `publicKey` ### `publicKey`
The `publicKey` implements HPKP to prevent MITM attacks with forged certificates. The `publicKey` implements HPKP to prevent MITM attacks with forged certificates.

View file

@ -55,6 +55,7 @@
- "traefik.http.middlewares.middleware12.headers.allowedhosts=foobar, foobar" - "traefik.http.middlewares.middleware12.headers.allowedhosts=foobar, foobar"
- "traefik.http.middlewares.middleware12.headers.browserxssfilter=true" - "traefik.http.middlewares.middleware12.headers.browserxssfilter=true"
- "traefik.http.middlewares.middleware12.headers.contentsecuritypolicy=foobar" - "traefik.http.middlewares.middleware12.headers.contentsecuritypolicy=foobar"
- "traefik.http.middlewares.middleware12.headers.contentsecuritypolicyreportonly=foobar"
- "traefik.http.middlewares.middleware12.headers.contenttypenosniff=true" - "traefik.http.middlewares.middleware12.headers.contenttypenosniff=true"
- "traefik.http.middlewares.middleware12.headers.custombrowserxssvalue=foobar" - "traefik.http.middlewares.middleware12.headers.custombrowserxssvalue=foobar"
- "traefik.http.middlewares.middleware12.headers.customframeoptionsvalue=foobar" - "traefik.http.middlewares.middleware12.headers.customframeoptionsvalue=foobar"

View file

@ -198,6 +198,7 @@
browserXssFilter = true browserXssFilter = true
customBrowserXSSValue = "foobar" customBrowserXSSValue = "foobar"
contentSecurityPolicy = "foobar" contentSecurityPolicy = "foobar"
contentSecurityPolicyReportOnly = "foobar"
publicKey = "foobar" publicKey = "foobar"
referrerPolicy = "foobar" referrerPolicy = "foobar"
permissionsPolicy = "foobar" permissionsPolicy = "foobar"

View file

@ -242,6 +242,7 @@ http:
browserXssFilter: true browserXssFilter: true
customBrowserXSSValue: foobar customBrowserXSSValue: foobar
contentSecurityPolicy: foobar contentSecurityPolicy: foobar
contentSecurityPolicyReportOnly: foobar
publicKey: foobar publicKey: foobar
referrerPolicy: foobar referrerPolicy: foobar
permissionsPolicy: foobar permissionsPolicy: foobar

View file

@ -1309,6 +1309,10 @@ spec:
description: ContentSecurityPolicy defines the Content-Security-Policy description: ContentSecurityPolicy defines the Content-Security-Policy
header value. header value.
type: string type: string
contentSecurityPolicyReportOnly:
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
header value.
type: string
contentTypeNosniff: contentTypeNosniff:
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
header with the nosniff value. header with the nosniff value.

View file

@ -71,6 +71,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` |
| `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` | | `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` |
| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicyReportOnly` | `foobar` |
| `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` | | `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` |
| `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` |
| `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` | | `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` |

View file

@ -585,6 +585,10 @@ spec:
description: ContentSecurityPolicy defines the Content-Security-Policy description: ContentSecurityPolicy defines the Content-Security-Policy
header value. header value.
type: string type: string
contentSecurityPolicyReportOnly:
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
header value.
type: string
contentTypeNosniff: contentTypeNosniff:
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
header with the nosniff value. header with the nosniff value.

View file

@ -1309,6 +1309,10 @@ spec:
description: ContentSecurityPolicy defines the Content-Security-Policy description: ContentSecurityPolicy defines the Content-Security-Policy
header value. header value.
type: string type: string
contentSecurityPolicyReportOnly:
description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
header value.
type: string
contentTypeNosniff: contentTypeNosniff:
description: ContentTypeNosniff defines whether to add the X-Content-Type-Options description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
header with the nosniff value. header with the nosniff value.

View file

@ -330,6 +330,7 @@
browserXssFilter = true browserXssFilter = true
customBrowserXSSValue = "foobar" customBrowserXSSValue = "foobar"
contentSecurityPolicy = "foobar" contentSecurityPolicy = "foobar"
contentSecurityPolicyReportOnly = "foobar"
publicKey = "foobar" publicKey = "foobar"
referrerPolicy = "foobar" referrerPolicy = "foobar"
isDevelopment = true isDevelopment = true

View file

@ -313,6 +313,8 @@ type Headers struct {
CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty" toml:"customBrowserXSSValue,omitempty" yaml:"customBrowserXSSValue,omitempty"` CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty" toml:"customBrowserXSSValue,omitempty" yaml:"customBrowserXSSValue,omitempty"`
// ContentSecurityPolicy defines the Content-Security-Policy header value. // ContentSecurityPolicy defines the Content-Security-Policy header value.
ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty" toml:"contentSecurityPolicy,omitempty" yaml:"contentSecurityPolicy,omitempty"` ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty" toml:"contentSecurityPolicy,omitempty" yaml:"contentSecurityPolicy,omitempty"`
// ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only header value.
ContentSecurityPolicyReportOnly string `json:"contentSecurityPolicyReportOnly,omitempty" toml:"contentSecurityPolicyReportOnly,omitempty" yaml:"contentSecurityPolicyReportOnly,omitempty"`
// PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates. // PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates.
PublicKey string `json:"publicKey,omitempty" toml:"publicKey,omitempty" yaml:"publicKey,omitempty"` PublicKey string `json:"publicKey,omitempty" toml:"publicKey,omitempty" yaml:"publicKey,omitempty"`
// ReferrerPolicy defines the Referrer-Policy header value. // ReferrerPolicy defines the Referrer-Policy header value.
@ -376,6 +378,7 @@ func (h *Headers) HasSecureHeadersDefined() bool {
h.BrowserXSSFilter || h.BrowserXSSFilter ||
h.CustomBrowserXSSValue != "" || h.CustomBrowserXSSValue != "" ||
h.ContentSecurityPolicy != "" || h.ContentSecurityPolicy != "" ||
h.ContentSecurityPolicyReportOnly != "" ||
h.PublicKey != "" || h.PublicKey != "" ||
h.ReferrerPolicy != "" || h.ReferrerPolicy != "" ||
(h.FeaturePolicy != nil && *h.FeaturePolicy != "") || (h.FeaturePolicy != nil && *h.FeaturePolicy != "") ||

View file

@ -63,6 +63,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware8.headers.addvaryheader": "true", "traefik.http.middlewares.Middleware8.headers.addvaryheader": "true",
"traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true", "traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true",
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar", "traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicyreportonly": "foobar",
"traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true", "traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true",
"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar", "traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar",
"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar", "traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar",
@ -611,22 +612,23 @@ func TestDecodeConfiguration(t *testing.T) {
"name0": "foobar", "name0": "foobar",
"name1": "foobar", "name1": "foobar",
}, },
SSLForceHost: Bool(true), SSLForceHost: Bool(true),
STSSeconds: 42, STSSeconds: 42,
STSIncludeSubdomains: true, STSIncludeSubdomains: true,
STSPreload: true, STSPreload: true,
ForceSTSHeader: true, ForceSTSHeader: true,
FrameDeny: true, FrameDeny: true,
CustomFrameOptionsValue: "foobar", CustomFrameOptionsValue: "foobar",
ContentTypeNosniff: true, ContentTypeNosniff: true,
BrowserXSSFilter: true, BrowserXSSFilter: true,
CustomBrowserXSSValue: "foobar", CustomBrowserXSSValue: "foobar",
ContentSecurityPolicy: "foobar", ContentSecurityPolicy: "foobar",
PublicKey: "foobar", ContentSecurityPolicyReportOnly: "foobar",
ReferrerPolicy: "foobar", PublicKey: "foobar",
FeaturePolicy: String("foobar"), ReferrerPolicy: "foobar",
PermissionsPolicy: "foobar", FeaturePolicy: String("foobar"),
IsDevelopment: true, PermissionsPolicy: "foobar",
IsDevelopment: true,
}, },
}, },
"Middleware9": { "Middleware9": {
@ -1134,22 +1136,23 @@ func TestEncodeConfiguration(t *testing.T) {
"name0": "foobar", "name0": "foobar",
"name1": "foobar", "name1": "foobar",
}, },
SSLForceHost: Bool(true), SSLForceHost: Bool(true),
STSSeconds: 42, STSSeconds: 42,
STSIncludeSubdomains: true, STSIncludeSubdomains: true,
STSPreload: true, STSPreload: true,
ForceSTSHeader: true, ForceSTSHeader: true,
FrameDeny: true, FrameDeny: true,
CustomFrameOptionsValue: "foobar", CustomFrameOptionsValue: "foobar",
ContentTypeNosniff: true, ContentTypeNosniff: true,
BrowserXSSFilter: true, BrowserXSSFilter: true,
CustomBrowserXSSValue: "foobar", CustomBrowserXSSValue: "foobar",
ContentSecurityPolicy: "foobar", ContentSecurityPolicy: "foobar",
PublicKey: "foobar", ContentSecurityPolicyReportOnly: "foobar",
ReferrerPolicy: "foobar", PublicKey: "foobar",
FeaturePolicy: String("foobar"), ReferrerPolicy: "foobar",
PermissionsPolicy: "foobar", FeaturePolicy: String("foobar"),
IsDevelopment: true, PermissionsPolicy: "foobar",
IsDevelopment: true,
}, },
}, },
"Middleware9": { "Middleware9": {
@ -1299,6 +1302,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicyReportOnly": "foobar",
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true",
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar",
"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar", "traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar",

View file

@ -17,24 +17,25 @@ type secureHeader struct {
// newSecure constructs a new secure instance with supplied options. // newSecure constructs a new secure instance with supplied options.
func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader { func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
opt := secure.Options{ opt := secure.Options{
BrowserXssFilter: cfg.BrowserXSSFilter, BrowserXssFilter: cfg.BrowserXSSFilter,
ContentTypeNosniff: cfg.ContentTypeNosniff, ContentTypeNosniff: cfg.ContentTypeNosniff,
ForceSTSHeader: cfg.ForceSTSHeader, ForceSTSHeader: cfg.ForceSTSHeader,
FrameDeny: cfg.FrameDeny, FrameDeny: cfg.FrameDeny,
IsDevelopment: cfg.IsDevelopment, IsDevelopment: cfg.IsDevelopment,
STSIncludeSubdomains: cfg.STSIncludeSubdomains, STSIncludeSubdomains: cfg.STSIncludeSubdomains,
STSPreload: cfg.STSPreload, STSPreload: cfg.STSPreload,
ContentSecurityPolicy: cfg.ContentSecurityPolicy, ContentSecurityPolicy: cfg.ContentSecurityPolicy,
CustomBrowserXssValue: cfg.CustomBrowserXSSValue, ContentSecurityPolicyReportOnly: cfg.ContentSecurityPolicyReportOnly,
CustomFrameOptionsValue: cfg.CustomFrameOptionsValue, CustomBrowserXssValue: cfg.CustomBrowserXSSValue,
PublicKey: cfg.PublicKey, CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
ReferrerPolicy: cfg.ReferrerPolicy, PublicKey: cfg.PublicKey,
AllowedHosts: cfg.AllowedHosts, ReferrerPolicy: cfg.ReferrerPolicy,
HostsProxyHeaders: cfg.HostsProxyHeaders, AllowedHosts: cfg.AllowedHosts,
SSLProxyHeaders: cfg.SSLProxyHeaders, HostsProxyHeaders: cfg.HostsProxyHeaders,
STSSeconds: cfg.STSSeconds, SSLProxyHeaders: cfg.SSLProxyHeaders,
PermissionsPolicy: cfg.PermissionsPolicy, STSSeconds: cfg.STSSeconds,
SecureContextKey: contextKey, PermissionsPolicy: cfg.PermissionsPolicy,
SecureContextKey: contextKey,
} }
return &secureHeader{ return &secureHeader{

View file

@ -139,6 +139,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar", "traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0": "foobar",
"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1": "foobar", "traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy": "foobar", "traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy": "foobar",
"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicyReportOnly": "foobar",
"traefik/http/middlewares/Middleware09/headers/publicKey": "foobar", "traefik/http/middlewares/Middleware09/headers/publicKey": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0": "foobar", "traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0": "foobar",
"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1": "foobar", "traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1": "foobar",
@ -601,22 +602,23 @@ func Test_buildConfiguration(t *testing.T) {
"name1": "foobar", "name1": "foobar",
"name0": "foobar", "name0": "foobar",
}, },
SSLForceHost: Bool(true), SSLForceHost: Bool(true),
STSSeconds: 42, STSSeconds: 42,
STSIncludeSubdomains: true, STSIncludeSubdomains: true,
STSPreload: true, STSPreload: true,
ForceSTSHeader: true, ForceSTSHeader: true,
FrameDeny: true, FrameDeny: true,
CustomFrameOptionsValue: "foobar", CustomFrameOptionsValue: "foobar",
ContentTypeNosniff: true, ContentTypeNosniff: true,
BrowserXSSFilter: true, BrowserXSSFilter: true,
CustomBrowserXSSValue: "foobar", CustomBrowserXSSValue: "foobar",
ContentSecurityPolicy: "foobar", ContentSecurityPolicy: "foobar",
PublicKey: "foobar", ContentSecurityPolicyReportOnly: "foobar",
ReferrerPolicy: "foobar", PublicKey: "foobar",
FeaturePolicy: String("foobar"), ReferrerPolicy: "foobar",
PermissionsPolicy: "foobar", FeaturePolicy: String("foobar"),
IsDevelopment: true, PermissionsPolicy: "foobar",
IsDevelopment: true,
}, },
}, },
"Middleware17": { "Middleware17": {

View file

@ -214,6 +214,7 @@ func init() {
BrowserXSSFilter: true, BrowserXSSFilter: true,
CustomBrowserXSSValue: "foo", CustomBrowserXSSValue: "foo",
ContentSecurityPolicy: "foo", ContentSecurityPolicy: "foo",
ContentSecurityPolicyReportOnly: "foo",
PublicKey: "foo", PublicKey: "foo",
ReferrerPolicy: "foo", ReferrerPolicy: "foo",
PermissionsPolicy: "foo", PermissionsPolicy: "foo",

View file

@ -170,6 +170,7 @@
"browserXssFilter": true, "browserXssFilter": true,
"customBrowserXSSValue": "xxxx", "customBrowserXSSValue": "xxxx",
"contentSecurityPolicy": "xxxx", "contentSecurityPolicy": "xxxx",
"contentSecurityPolicyReportOnly": "xxxx",
"publicKey": "xxxx", "publicKey": "xxxx",
"referrerPolicy": "foo", "referrerPolicy": "foo",
"permissionsPolicy": "foo", "permissionsPolicy": "foo",

View file

@ -173,6 +173,7 @@
"browserXssFilter": true, "browserXssFilter": true,
"customBrowserXSSValue": "foo", "customBrowserXSSValue": "foo",
"contentSecurityPolicy": "foo", "contentSecurityPolicy": "foo",
"contentSecurityPolicyReportOnly": "foo",
"publicKey": "foo", "publicKey": "foo",
"referrerPolicy": "foo", "referrerPolicy": "foo",
"permissionsPolicy": "foo", "permissionsPolicy": "foo",

View file

@ -817,6 +817,22 @@
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
<!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - contentSecurityPolicyReportOnly -->
<q-card-section v-if="middleware.headers">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">
Content Security Policy (Report Only)
</div>
<q-chip
dense
class="app-chip app-chip-green"
>
{{ exData(middleware).contentSecurityPolicyReportOnly }}
</q-chip>
</div>
</div>
</q-card-section>
<!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - publicKey --> <!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - publicKey -->
<q-card-section v-if="middleware.headers"> <q-card-section v-if="middleware.headers">
<div class="row items-start no-wrap"> <div class="row items-start no-wrap">