Skip to content

Add support for OpenID Connect implicit authentication flow.#42069

Merged
azasypkin merged 7 commits intoelastic:masterfrom
azasypkin:issue-xxx-oidc-implicit-flow
Aug 8, 2019
Merged

Add support for OpenID Connect implicit authentication flow.#42069
azasypkin merged 7 commits intoelastic:masterfrom
azasypkin:issue-xxx-oidc-implicit-flow

Conversation

@azasypkin
Copy link
Contributor

@azasypkin azasypkin commented Jul 26, 2019

Kibana OIDC authentication provider currently supports Authentication using the Authorization Code Flow only. The main reason is that it is a recommended authentication flow and should be used by default whenever it's possible.

The authorization code flow implies that Elasticsearch has access to the Token endpoint of the OpenID Connect Provider (OP), but it's neither always possible nor desired. So in the scenarios like this having support for another, so called Implicit Flow would be beneficial.

This PR adds support for the OpenID Connect Authentication using the Implicit Flow. The tricky part here is that OP passes arguments required for the authentication using the implicit flow via URL fragment. That means we can't directly receive these arguments on the server side and have to provide a client-side proxy page that UA will be forwarded to and where we'll be extracting URL fragment and only then sending them to the Kibana Server OIDC endpoint to perform actual authentication.

Since this PR modifies OIDC authentication provider we should consider enabling OIDC integration tests first, that's blocked by #36959 (tests were re-enabled).

Notes to myself:


How to test

  1. Run Elasticsearch with (ping me to get values for client_id and client_secret):
$ yarn es snapshot --license trial \
    -E xpack.security.authc.token.enabled=true \
    -E xpack.security.authc.realms.native.native1.order=0 \
    -E xpack.security.authc.realms.oidc.oidc1.order=1 \
    -E xpack.security.authc.realms.oidc.oidc1.rp.client_id=xxxx \
    -E xpack.security.authc.realms.oidc.oidc1.rp.client_secret=xxxx \
    -E xpack.security.authc.realms.oidc.oidc1.rp.response_type="id_token token" \
    -E xpack.security.authc.realms.oidc.oidc1.rp.requested_scopes=profile \
    -E xpack.security.authc.realms.oidc.oidc1.rp.redirect_uri=https://localhost:5601/api/security/v1/oidc/implicit \
    -E xpack.security.authc.realms.oidc.oidc1.op.authorization_endpoint=https://elastic-dev.auth0.com/authorize \
    -E xpack.security.authc.realms.oidc.oidc1.op.token_endpoint=https://elastic-dev.auth0.com/oauth/token \
    -E xpack.security.authc.realms.oidc.oidc1.op.userinfo_endpoint=https://elastic-dev.auth0.com/userinfo \
    -E xpack.security.authc.realms.oidc.oidc1.op.issuer=https://elastic-dev.auth0.com/ \
    -E xpack.security.authc.realms.oidc.oidc1.op.jwkset_path=https://elastic-dev.auth0.com/.well-known/jwks.json \
    -E xpack.security.authc.realms.oidc.oidc1.claims.principal=sub \
    -E xpack.security.authc.realms.oidc.oidc1.claims.groups=https://myapp.example.com/kbn_groups \
    -E xpack.security.authc.realms.oidc.oidc1.claims.name=name
  1. Add role mapping for users authenticated with OIDC realm, e.g.:
POST http://localhost:9200/_security/role_mapping/oidc1
Content-Type: application/json

{
  "roles": [ "superuser" ],
  "enabled": true,
  "rules": { "field" : { "realm.name" : "oidc1" } }
}
  1. Run Kibana with xpack.security.authc.providers: [oidc, basic]

Blocked by: #36959
Fixes: #41984

"Release Note: Adding support for the OpenID Connect Authentication using the Implicit Flow"

/cc @jkakavas

@azasypkin azasypkin added blocked Team:Security Platform Security: Auth, Users, Roles, Spaces, Audit Logging, etc t// Feature:Security/Authentication Platform Security - Authentication Feature:New Feature v7.5.0 labels Jul 26, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-security

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: that the main thing I need to get rid of (hopefully) before this PR is ready for review

Copy link
Contributor Author

@azasypkin azasypkin Jul 29, 2019

Choose a reason for hiding this comment

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

Alternatively we could just render tiny HTML page outside of Kibana context with inline "nonced" onliner script.... That way we won't have rison that garbles URL fragment right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: I was thinking about using POST here, but couldn't convince myself enough since all these args are already came through URL fragment 🤷‍♂️ Would be glad to hear any thoughts on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: actually, it probably makes sense to have a dedicated endpoint for implicit flow, haven't thought thoroughly about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll defer to your opinion on this one. I don't think the current implementation is un-readable, but if you think it makes it easier to have a separate end-point, that seems reasonable also.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think the current implementation is un-readable

Great, I don't have strong opinion on that. Let's keep it as is for now then and split whenever we feel we really need to split.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: we should find a better way of solving the underlying problem, but not in this PR likely.

@azasypkin azasypkin force-pushed the issue-xxx-oidc-implicit-flow branch from 653d4e8 to f82980a Compare July 30, 2019 13:33
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: using CSP here feels like an overkill here, but it doesn't hurt to have it I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to do anything about server.customResponseHeaders explicitly, or does the global hapi configuration apply automatically?

I've been trying to think of potential issues that we get from rendering all of the HTML ourselves, but I haven't been able to think of any.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need to do anything about server.customResponseHeaders explicitly, or does the global hapi configuration apply automatically?

Hapi's onPreResponse hooks do everything for us "automagically". I actually was surprised that CSP isn't done via onPreResponse too... But I admit I don't know much about all the variables we had to account for when we were implementing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: we can just pass entire window.location.href instead, it will also work and make this code a tad easier. Now when I look at this I can't think of any reason to not use entire window.location.href 🙈 What do you think?

Copy link
Contributor

@kobelb kobelb Jul 30, 2019

Choose a reason for hiding this comment

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

That seems easy enough to me, and potentially less fragile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, will switch to window.location.href.

@azasypkin azasypkin force-pushed the issue-xxx-oidc-implicit-flow branch from f82980a to a47f882 Compare July 30, 2019 13:50
@azasypkin
Copy link
Contributor Author

@kobelb would you mind giving a preliminary feedback on the approach itself? Feel free to ping me to get user credentials and client secret for the test environment.

Please, skip changes in @kbn/es, @kbn/test and x-pack/test - these are from #42239 that is the blocker for the current PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: It seems it'd be better to include <!DOCTYPE html> at least, will do.

Copy link
Contributor

Choose a reason for hiding this comment

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

The w3c validator makes it seem like we should include <title>foo</title> as well: https://validator.w3.org/nu/#textarea

Borrowing from https://mathiasbynens.be/notes/minimal-html who found whatwg/html@5c7cf94, the title can be omitted when dealing with e-mails, but it sounds like we should be using it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh, would never thought we may need title :) Let me read through this, thanks for sharing!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the spec agrees with you: https://html.spec.whatwg.org/multipage/semantics.html#the-title-element: Neither tag is omissible. 🙂

@kobelb kobelb self-requested a review July 30, 2019 18:46
Copy link
Contributor

@kobelb kobelb left a comment

Choose a reason for hiding this comment

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

I think the approach is fine. Perhaps long-term we can add the ability to render custom HTML content without all of the other "cruft" in the core application, but what we have now seems reasonable.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll defer to your opinion on this one. I don't think the current implementation is un-readable, but if you think it makes it easier to have a separate end-point, that seems reasonable also.

@azasypkin
Copy link
Contributor Author

Perhaps long-term we can add the ability to render custom HTML content without all of the other "cruft" in the core application

++, that's what we'll need to have for sure - custom HTML with only the absolutely necessary stuff (CSP, xsrf, HSTS, CORS etc. etc.). Not a blocker for now, but at some point managing this manually and outside of core's control in every such case will hurt us.

@azasypkin azasypkin force-pushed the issue-xxx-oidc-implicit-flow branch from a47f882 to dcfabee Compare August 1, 2019 15:25
@azasypkin azasypkin marked this pull request as ready for review August 1, 2019 15:25
@azasypkin azasypkin requested a review from a team as a code owner August 1, 2019 15:25
@elasticmachine

This comment has been minimized.

@azasypkin azasypkin force-pushed the issue-xxx-oidc-implicit-flow branch from dcfabee to 776cda2 Compare August 1, 2019 15:44
@azasypkin azasypkin requested a review from a team as a code owner August 1, 2019 15:44
@azasypkin azasypkin added v7.4.0 and removed v7.5.0 labels Aug 1, 2019
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@azasypkin azasypkin requested a review from kobelb August 2, 2019 12:02
@azasypkin
Copy link
Contributor Author

@kobelb this PR should be ready for review whenever you have time 🙂 Thanks!

Copy link
Contributor

@jbudz jbudz left a comment

Choose a reason for hiding this comment

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

renovate portion LGTM

async handler(request, h) {
try {
const query = request.query || {};
const payload = request.payload || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

It predates this PR, but should we considering add payload validation to this route?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know to be honest, I was even planning to eventually get rid of query validation as well since we (Kibana) neither parse nor try to interpret them and just blindly pass to Elasticsearch anyway (and we allow unknown parameters as well). If it happens so that these args are malformed, imo, it's better to allow ES to parse them and log more detailed error in their log than having this validation in Kibana.

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think?

We likely get little benefit from doing it ourselves because we end up passing it directly to Elasticsearch. It could potentially help us if we received the wrong "data-type" for a specific parameter. So if we received a payload.iss which was an Object as opposed to a string, we could potentially have more informative error messages instead of something breaking in a somewhat obscure manner downstream.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with it either way, I defer to your opinion :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've just tried to add a payload validation and was hit by this limitation 😢 That means we can't have payload validation if route defines multiple methods and some of them don't support payload. The only alternative is to split this route into two very similar ones. Since you're fine with the current implementation I'll keep it simple and leave it as is for now. Let's get back to it when we migrate this route to NP (I leave a code comment).

I'll add a validation for authenticationResponseURI query parameter as you suggested below though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good!!


const nonce = await generateCSPNonce();
const cspRulesHeader = createCSPRuleString(legacyConfig.get('csp.rules'), nonce);
return h.response(`
Copy link
Contributor

Choose a reason for hiding this comment

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

This is likely a naive question. When reading through the OIDC spec, section 3.2.2.5 states

When using the Implicit Flow, all response parameters are added to the fragment component of the Redirection URI, as specified in OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses], unless a different Response Mode was specified.

And section 2.1 of the OAuth2.0 Response Type Encoding Practices makes it appear that we could potentially use the query response mode https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#rfc.section.2.1 to get around needing to use this page to parse the fragment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is likely a naive question.

And what is the question? 🙂 So, I assume you're asking why we can't just say OP and RP (ES) to use response_mode=query for the implicit flow. Per OAuth 2.0 Multiple Response Type Encoding Practices it seems that query is forbidden for id_token and token response types:

The default Response Mode for this Response Type is the fragment encoding and the query encoding MUST NOT be used.

cc @jkakavas

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, thanks for the clarification. I stopped reading the specs too soon.

Copy link

Choose a reason for hiding this comment

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

fragment is used on purpose so that the access token and the id token are not sent as part of any requests to a back-end server ( The assumption was that the implicit flow would be best used by javascript clients running in the browser ) and thus don't get leaked in logs or be available more than needed on the wire. We (and many other clients) have the validation on the server side so we end up making a request with it to the kibana server eitherway so there goes that.. In any case as @azasypkin mentioned we're not allowed to use anything else for response_type

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks @jkakavas

// fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details
// at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth
let loginAttempt;
if (query.authenticationResponseURI) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add authenticationResponseURI to the route's query validation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replied above, but let me know if you think it makes sense to treat our own authenticationResponseURI differently.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@azasypkin
Copy link
Contributor Author

7.x/7.4.0: 4c396f8

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backported Feature:Security/Authentication Platform Security - Authentication release_note:enhancement Team:Security Platform Security: Auth, Users, Roles, Spaces, Audit Logging, etc t// v7.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants