-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add config properties for HTTP security headers #97158
Add config properties for HTTP security headers #97158
Conversation
a9feda4
to
0d90a4f
Compare
This was recently renamed to `server.maxPayload`, I just updated the docs and other references accordingly. Unrelated to the other work in this PR besides the fact that I am mucking around in the `src/core/server/http/http_config.ts` file.
0d90a4f
to
62864b1
Compare
62864b1
to
5745057
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Author's notes for reviewers
const additionalHeaders = { | ||
...customHeaders, | ||
...securityResponseHeaders, | ||
...customResponseHeaders, | ||
[KIBANA_NAME_HEADER]: serverName, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We explicitly allow customResponseHeaders
to override any values in securityResponseHeaders
. This is so the new config properties do not break existing configurations that already set the same headers using customResponseHeaders
.
We could detect this situation when the config is set and throw an error, but we'd have to make that change in the 8..0 release, I think.
const strictTransportSecuritySchema = schema.object({ | ||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security | ||
maxAge: schema.duration({ defaultValue: '1Y' }), | ||
includeSubDomains: schema.boolean({ defaultValue: false }), | ||
preload: schema.boolean({ defaultValue: false }), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, Strict-Transport-Security
is enabled with a max age of 1 year. This is recommended by MDN and most other authoritative sources. We could decrease this but I'm not sure we should.
I omitted includeSubDomains
by default because of #52809 (comment).
Note: if Kibana is served over HTTP, this header is still sent, but browsers ignore it. It only affects browsers once they see this header in an HTTPS response.
xContentTypeOptions: schema.oneOf([schema.literal('nosniff'), schema.literal(null)], { | ||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options | ||
defaultValue: 'nosniff', | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, X-Content-Type-Options
is enabled. It only has one valid value. This shouldn't have any negative impact on users.
referrerPolicy: schema.oneOf( | ||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy | ||
[ | ||
schema.literal('no-referrer'), | ||
schema.literal('no-referrer-when-downgrade'), | ||
schema.literal('origin'), | ||
schema.literal('origin-when-cross-origin'), | ||
schema.literal('same-origin'), | ||
schema.literal('strict-origin'), | ||
schema.literal('strict-origin-when-cross-origin'), | ||
schema.literal('unsafe-url'), | ||
schema.literal(null), | ||
], | ||
{ defaultValue: 'no-referrer-when-downgrade' } | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, Referrer-Policy
is enabled with a value of "no-referrer-when-downgrade". This is not very strict, it does the bare minimum to prevent the Referer header from being sent to unsafe (HTTP) destinations when Kibana is served over HTTPS. See also #52809 (comment)
permissionsPolicy: schema.oneOf([schema.string(), schema.literal(null)], { | ||
defaultValue: 'camera=(), microphone=()', // disables camera and microphone access in Kibana and any embedded pages | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, Permissions-Policy
is enabled with a value of "camera=(), microphone=()". There is not a lot of authoritative info on this header yet (the header was previously Feature-Policy
but has been slightly reworked and renamed). I figured we can set it with some safe feature policies as a starting point.
Note that Permissions-Policy
is subtractive, so if we do not specify a directive, that is not affected. Kibana shouldn't ever need access to a user's camera or microphone, so what we are saying here is that Kibana (and any embedded pages) are not allowed to access these features.
Edit: I believe another, more clear way of writing this permissions policy is: 'camera=(none), microphone=(none)'
. While the W3C draft mentions the none
keyword, the explainer document above does not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy#example leads me to believe that we should be using Permissions-Policy: camera 'none'; microphone 'none'
. It also looks like this isn't very widely supported per https://caniuse.com/permissions-policy. Perhaps we should just hold off on this one until it matures? Does the lack of this header get flagged by scanners right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The page is a bit misleading / outdated. That's the format for the old Feature-Policy
header, it hasn't been updated to the Permissions-Policy
format.
At any rate based on #97158 (comment) below, I think we will leave this header disabled by default and mark the config prop as experimental, if that sounds good to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me! Thanks.
permissionsPolicy: schema.oneOf([schema.string(), schema.literal(null)], { | ||
defaultValue: 'camera=(), microphone=()', // disables camera and microphone access in Kibana and any embedded pages | ||
}), | ||
disableEmbedding: schema.boolean({ defaultValue: false }), // is used to control X-Frame-Options and CSP headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When enabled, this changes the default Content-Security-Policy
header (by adding the frame-ancestors
directive) and adds the X-Frame-Options
header.
Even though it appears that all of Kibana's target browsers support frame-ancestors
, we still include X-Frame-Options
for a couple of reasons:
- If the user has customized the
Content-Security-Policy
for some reason, we won't add theframe-ancestors
directive -- so includingX-Frame-Options
as a backup is a good idea - Naive security scanning tools can see a missing
X-Frame-Options
as a problem, even if there is aContent-Security-Policy
present with aframe-ancestors
directive
In the future (8.0 release?) I would love to change this to be enabled by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came looking for answers to these questions - thanks for taking the time to explain!
@@ -116,12 +116,20 @@ kibana_vars=( | |||
server.compression.referrerWhitelist | |||
server.cors | |||
server.cors.origin | |||
server.customResponseHeaders |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a duplicate value so I removed it (it still exists below on line 127)
server.maxPayloadBytes | ||
server.maxPayload |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added server.maxPayload
but left the deprecated server.maxPayloadBytes
for now (see 8cc60bf)
if (!rawCspConfig.rules?.length && source.disableEmbedding) { | ||
this.rules.push(FRAME_ANCESTORS_RULE); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a good idea to log a warning if disableEmbedding
is enabled but custom CSP rules are used that do not include the frame-ancestors
directive. But I think we can leave that for a future enhancement, as we don't have the ability to log when config is ingested at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kibana-docker LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a handful of nits, but otherwise, LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Tested locally and can confirm that Kibana responds with correct security headers.
Couple minor comments below.
docs/setup/settings.asciidoc
Outdated
a| | ||
---- | ||
server.securityResponseHeaders: | ||
strictTransportSecurity.preload: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elastic/kibana-docs What's the best way of printing long settings like this? The table is too narrow to fit both the name and description. However, breaking it up like this makes it a bit confusing for users too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thomheymann the table capabilities in Asciidoc are limited, and I agree that breaking up the setting names is confusing. These would be best displayed in a definition list instead of a table. I propose that we merge these changes, then I'll fix the formatting before 7.13 releases.
Previously, this property contained three sub-properties that each controlled individual directives in the header. This is really more complexity than is necessary, we can simply allow users to enter in free text for this header.
permissionsPolicy: schema.oneOf([schema.string(), schema.literal(null)], { | ||
defaultValue: 'camera=(), microphone=()', // disables camera and microphone access in Kibana and any embedded pages | ||
}), | ||
disableEmbedding: schema.boolean({ defaultValue: false }), // is used to control X-Frame-Options and CSP headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came looking for answers to these questions - thanks for taking the time to explain!
], | ||
{ defaultValue: 'no-referrer-when-downgrade' } | ||
), | ||
permissionsPolicy: schema.oneOf([schema.string(), schema.literal(null)], { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wary of supporting production deployments of an experimental web technology, with very mixed browser support:
I'd be more comfortable if we omitted this option for the time being. Users who want to control this have a way to do so via custom response headers, and they can do so at their own risk.
If we have a compelling reason to keep this (aside from the score card), then I'd like to see this setting marked as experimental so that we aren't committing to support of this specific header.
I briefly looked around to see if there were high profile sites using this header, and I haven't found any usages of it yet. I know they exist somewhere, I just haven't found them 😉 . I was trying to get an idea of how comfortable other companies were using this experimental header in production.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I was on the fence about this as well. I just wanted to do as much as possible to get ahead of scanning tools, but I think the only one that checks for this atm is securityheaders.com, and I think if you don't use this header it just caps your score at "A" (instead of "A+").
Perhaps we can leave the config property in, but change it to null/disabled by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still like to mark the property as experimental -- we don't know what the future holds for deprecating/removing features after the 8.0 release, so this would give us some more flexibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One followup thought -- we should update core telemetry to indicate if these settings have been altered. It'd be great to track this over time in case we need data about future changes we want to make.
export const securityResponseHeadersSchema = schema.object({ | ||
strictTransportSecurity: schema.oneOf([schema.string(), schema.literal(null)], { | ||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security | ||
defaultValue: 'max-age=31536000', // 1 year in seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does Strict-Transport-Security: max-age=31536000
affect Kibana when it's hosted on a base-path? For example, if Kibana is hosted at https://foo.com/kibana/
will it apply to websites at https://foo.com/bar
? If it's in effect for the entire domain, at a minimum, we won't want to set this in 7.13.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make an issue to change the defaults for strictTransportSecurity
and disableEmbedding
in the 8.0 release.
Edit: added in #97348
One API integration test is failing with the following message:
I set the type for this and some other security headers in the usage data config as I can think of two obvious solutions:
@elastic/kibana-telemetry what do you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, LGTM
I spoke to @Bamieh and @afharo offline and this point was raised:
So we agreed that option (2) above is the best path forward. I updated this accordingly in a501b8c. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one note around the descriptions :)
"strictTransportSecurity": { | ||
"type": "keyword", | ||
"_meta": { | ||
"description": "The strictTransportSecurity response header, if it is configured." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it is configured
I don't think this is true. We are always reporting it. Either the configured value or "NULL"
. Maybe I misread the code though. Can you confirm @jportner?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes sorry about that, I forgot to update the descriptions when I added the null
coalescing. I'll update those now.
Edit: done in fd78745.
Edit 2: s/coalescing/coercing/
💚 Build Succeeded
Metrics [docs]Unknown metric groupsAPI count
API count missing comments
History
To update your PR or re-run it, just comment with: |
💔 Backport failed
To backport manually run: |
Pinging @elastic/kibana-security (Team:Security) |
Resolves #52809 and #97153.
Overview
This PR adds the following config properties (each prefixed by
server.securityResponseHeaders
):.strictTransportSecurity
null
(Header is not enabled)Can be set to
null
to disable this header..xContentTypeOptions
'nosniff'
(Header is enabled)Can be set to
null
to disable this header..referrerPolicy
'no-referrer-when-downgrade'
(Header is enabled)Can be set to
null
to disable this header..permissionsPolicy
null
(Header is not enabled)Can be set to
null
to disable this header..disableEmbedding
false
(Header is not enabled, embedding is allowed)New scan result from securityheaders.com with default settings ("C" rating):
Docs
Docs preview: link (diff)
Note concerning the docs: the asciidoc hacks that I needed to make for the settings table in bb8e2aa are kind of awful, but it seems to be the only solution that will work. The key names for the new config options are too long, and they stretch the left column of the table out to the maximum size as a result. Regular columns in AsciiDoc will not preserve leading whitespace characters, so I had to utilize an AsciiDoc block element column style with the
a
operator.Screenshots before and after bb8e2aa
Before:
After: