Skip to content
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

Better management of the CSP header #24577

Merged
merged 1 commit into from
Apr 8, 2024
Merged

Better management of the CSP header #24577

merged 1 commit into from
Apr 8, 2024

Conversation

rmartinc
Copy link
Contributor

@rmartinc rmartinc commented Nov 6, 2023

Closes #24568

With this PR the CSP modifications are performed independently to the frame-src (front-channel logout handler) and frame-ancestors (getting the iframe from resources) directives. In the frame-src case, the different hostnames are added to the configured directive (for example if the realm defines 'self' keycloak.org the final directive sent after adding the clients values is 'self' keycloak.org client1 client2). In the case of frame-ancestors the directive is removed for iframe inclusion only if the default 'self' value is configured (if the admin changed it to any other value, the directive is not modified). Added a test in forms to use firefox and chrome (the test needs a real browser to check the CSP header).

Parsing just manages spaces as separator to read the current CSP defined in the realm. The spec allows other things as separator (like tabs, new lines,...) but I didn't want to complicate it more. If you think it's necessary just let me know.

@mposolda @stianst Take a look when you have time.

@rmartinc rmartinc requested review from a team as code owners November 6, 2023 13:58
@rmartinc rmartinc requested a review from a team November 6, 2023 13:58
@rmartinc rmartinc requested a review from a team as a code owner November 6, 2023 13:58

if (options.isAllowAnyFrameAncestor()) {
headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS.getHeaderName());

csp.frameAncestors(null);
if (csp.isDefaultFrameAncestors()) {
// only remove frame ancestors if defined to default 'self'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmartinc Can you elaborate on why this is the case? Isn't the intention of options.isAllowAnyFrameAncestor() so that we can allow some resources to be loaded under any third-party domains? If that is the case, then we should remove frame ancestors regardless whether it is defined to be default or not, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention of this part is to just force the frame-ancestors if it's not modified by the admins. If admins are setting some default ancestors for example self https://*.example.com I suppose the admins know what they are doing and limiting to those ancestors on purpose (because they know the clients are always from those URLs or similar). But not writing in stone, if you have a better idea just let me know. This part is only used to get the IFrame for login.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that currently there are three resources that are using the options.isAllowAnyFrameAncestor() feature to remove frame ancestors, so that they can be loaded in any iFrames:

It seems to me that it is safe to allow these resources to be hosted in any iFrames all the time. That is why I am thinking we should continue to treat these resources differently from the other pages (such as the actual login pages), even when the admins specifically are limiting the frame ancestors on purpose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, all those resources are used by the keycloak-js client when the checkLoginIframe is default true. So, just used for the keycloak-js client login. I'm OK to always remove ancestors but then we should take in mind that this cannot be overridden.

@mposolda mposolda self-assigned this Apr 4, 2024
Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmartinc I've added only minor comments. Hopefully moving the test to different class (and maybe constants) and we should be fine. Is it ok to you?

@@ -1029,4 +1033,35 @@ public void testExecuteActionIfSessionExists() {
}

}

@Test
public void testFrontChannelLogoutCustomCSP() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use rather RPInitiatedLogoutTest as it contains other tests related to OIDC frontchannel-logout? (I am actually thinking about moving frontchannel-logout tests to separate class, but I guess that is beyond the scope of this PR, but if you want to do it, it is welcome :-) )

Copy link
Contributor Author

@rmartinc rmartinc Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not possible. For this test I need the chrome/firefox driver to correctly follow the CSP directives. If the test uses default browser it is always OK because the CSP directives are not taken into account. So I need the forms or the wenauthn CI run (the ones that uses -Dbrowser=chrome). I remember I initially added the test in another class to realize that it was not tested in CI. If you want I can add it in a complete new class but in forms folder (something like CSPLoginTest for example).

Rebased too just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a RPInitiatedFrontChannelLogoutTest inside forms folder with the frontend tests of RPInitiatedLogoutTest and the new CSP method. The LoginTest is not modified now.


public static ContentSecurityPolicyBuilder create() {
return new ContentSecurityPolicyBuilder();
return new ContentSecurityPolicyBuilder()
.add("frame-src", "'self'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Java constants for those fields (directive-names as well as stuff like 'self') can be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Closes keycloak#24568

Signed-off-by: rmartinc <rmartinc@redhat.com>
Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmartinc Thanks!

@mposolda mposolda merged commit 2b769e5 into keycloak:main Apr 8, 2024
65 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

iframe for frontend logout gets blocked if a custom CSP header is used
4 participants