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

Oidc additional client auth types #58708

Merged
merged 10 commits into from
Sep 14, 2020

Conversation

jkakavas
Copy link
Member

@jkakavas jkakavas commented Jun 29, 2020

The OpenID Connect specification defines a number of ways for a
client (RP) to authenticate itself to the OP when accessing the
Token Endpoint. We currently only support client_secret_basic.

This change introduces support for 2 additional authentication
methods, namely client_secret_post (where the client credentials
are passed in the body of the POST request to the OP) and
client_secret_jwt where the client constructs a JWT and signs
it using the the client secret as a key.

Support for the above, and especially client_secret_jwt in our
integration tests meant that the OP we use ( Connect2id server )
should be able to validate the JWT that we send it from the RP
(which contains the Token Endpoint as the aud claim ).
Since we run the OP in docker and it listens on an ephemeral port
we would have no way of knowing the port so that we can configure
the ES running via the testcluster to know the "correct" Token
Endpoint, and even if we did, this would not be the Token Endpoint
URL that the OP would think it listens on. To alleviate this, we
run an ES single node cluster in docker, alongside the OP so that
we can configured it with the correct hostname and port within
the docker network.

The OpenID Connect specification defines a number of ways for a
client (RP) to authenticate itself to the OP when accessing the
Token Endpoint. We currently only support `client_secret_basic`.

This change introduces support for 2 additional authentication
methods, namely `client_secret_post` (where the client credentials
are passed in the body of the POST request to the OP) and
`client_secret_jwt` where the client constructs a JWT and signs
it using the the client secret as a key.

Support for the above, and especially `client_secret_jwt` in our
integration tests meant that the OP we use ( Connect2id server )
should be able to validate the JWT that we send it from the RP.
Since we run the OP in docker and it listens on an ephemeral port
we would have no way of knowing the port so that we can configure
the ES running via the testcluster to know the "correct" Token
Endpoint, and even if we did, this would not be the Token Endpoint
URL that the OP would think it listens on. To alleviate this, we
run an ES single node cluster in docker, alongside the OP so that
we can configured it with the correct hostname and port within
the docker network.
@jkakavas jkakavas added >enhancement :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) labels Jun 29, 2020
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security (:Security/Authentication)

@elasticmachine elasticmachine added the Team:Security Meta label for security team label Jun 29, 2020
@jkakavas
Copy link
Member Author

@mark-vieira added you for a +1 for the docker-compose changes since we chatted about this

Copy link
Member

@ywangd ywangd left a comment

Choose a reason for hiding this comment

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

I still need go through the tests more carefully (they are new to me). Here are a few comments/questions to start. Thanks!

} else {
tokensListener.onFailure(new ElasticsearchSecurityException("Failed to exchange code for Id Token using Token Endpoint." +
"Expected client authentication method to be one of " + OpenIdConnectRealmSettings.CLIENT_AUTH_METHODS + " but was " +
rpConfig.getClientAuthenticationMethod()));
Copy link
Member

Choose a reason for hiding this comment

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

Nit: The name or value is usually placed inside a pair of brackets in error messages, .e.g ... but was [xxx].

Comment on lines +486 to +489
for (Map.Entry<String, List<String>> entry : clientSecretJWT.toParameters().entrySet()) {
// Both client_assertion and client_assertion_type are singleton lists
params.add(new BasicNameValuePair(entry.getKey(), entry.getValue().get(0)));
}
Copy link
Member

Choose a reason for hiding this comment

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

Maybe I am missing something obvious. But client_id is not added as one of the request parameters? The map returned from ClientSecretJWT.toParameters() only contains client_assertion and client_assertion_type if I read the code correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

But client_id is not added as one of the request parameters?

It's not, it doesn't need to be. It is added as the iss claim of the JWT and validated from there.

only contains client_assertion and client_assertion_type

yes, these are the two parameters needed, see https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication

Copy link
Member

Choose a reason for hiding this comment

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

You are right. For a moment, I forgot how JWT is organized. It's too late for me ...

Copy link
Member Author

Choose a reason for hiding this comment

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

I know that feeling !!! :) No need to go through this today, it can definitely wait. I raised the PR because I had it ready since last week

@@ -841,6 +842,8 @@ private RelyingPartyConfiguration getDefaultRpConfig() throws URISyntaxException
new ResponseType("id_token", "token"),
new Scope("openid"),
JWSAlgorithm.RS384,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
JWSAlgorithm.HS384,
new URI("https://rp.elastic.co/successfull_logout"));
}
private RelyingPartyConfiguration getRpConfig(String alg) throws URISyntaxException {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: need a blank line before this method.

.put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.PRINCIPAL_CLAIM.getClaim()), "sub")
.put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_REDIRECT_URI), "https://rp.my.com")
.put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_ID), "rp-my")
.put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_METHOD), "none")
Copy link
Member

Choose a reason for hiding this comment

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

"none", "None", "noNe", "nOnE", "NonE" ... 👍 that we will not have this issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be testing the OP though, not our implementation, as it is the OP that should not validate a client JWT with none algorithm. We do not disallow none specifically, we use an allowlist that only allows "HS256", "HS384", "HS512"

I like the suggestion though, we can use it in OpenIdConnectAuthenticatorTests.testImplicitFlowFailsWithNoneAlgorithm which tests our implementation

+ "/c2id/clients";
private static final String LOGIN_API = "http://127.0.0.1:" + getEphemeralTcpPortFromProperty("oidc-provider", "8080")
+ "/c2id-login/api/";
private static final String ES_PORT = getEphemeralTcpPortFromProperty("elasticsearch-node", "9200");
Copy link
Member

Choose a reason for hiding this comment

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

I don't see test.fixtures.elasticsearch-node.9200 get set anywhere. How does this work?

Copy link
Member Author

@jkakavas jkakavas Jun 30, 2020

Choose a reason for hiding this comment

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

elasticsearch-node is the name of the elasticsearch service in https:/elastic/elasticsearch/pull/58708/files#diff-2be63811028f90650408e594c29099b9R3 and 9200 is defined there in the ports section. I assume it is the gradle docker compose plugin that handles this magic or something in our buildSrc , Mark can probably share more details

Copy link
Member

Choose a reason for hiding this comment

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

Ah awesome! buildSrc magic. TIL

@ywangd
Copy link
Member

ywangd commented Jun 30, 2020

I assume this PR will close #54582 ? Or do you plan to add more auth methods after this one? Anyway, I am tagging it here for cross-reference.

@jkakavas
Copy link
Member Author

I still need go through the tests more carefully (they are new to me). Here are a few comments/questions to start. Thanks!

we can wait until I return, this is not urgent, no worries.

Copy link
Contributor

@mark-vieira mark-vieira left a comment

Choose a reason for hiding this comment

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

One comment, otherwise LGTM.

@@ -1,4 +1,36 @@
import org.elasticsearch.gradle.VersionProperties
import org.elasticsearch.gradle.Architecture

apply plugin: 'elasticsearch.build'
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can remove elasticsearch.build plugin. This project contains no code or tests, it's simply a test fixture.

Copy link
Member

@ywangd ywangd left a comment

Choose a reason for hiding this comment

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

LGTM. I left a few questions about the c2id-server setup for my learning purpose. Thanks!

@@ -40,11 +153,13 @@ services:
- ./idp/shib-jetty-base/start.d/ssl.ini:/opt/shib-jetty-base/start.d/ssl.ini

oidc-provider:
image: "c2id/c2id-server:7.8"
image: "c2id/c2id-server:9.5"
Copy link
Member

Choose a reason for hiding this comment

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

Is this an image that we maintain? If so, where can I find the Dockerfile that builds the image?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, we use the official one that the folks from connect2id ( who implement the OP ) publish in Dockerhub. I dont think they publish the Dockerfile, some details are in https://connect2id.com/products/server/docs/guides/docker

Copy link
Member

Choose a reason for hiding this comment

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

Thanks! I checked the image on Dockerhub and it has zero documentation. I found it hard to believe it was the official image ... apparently I am wrong.

httpPost3.setEntity(new StringEntity(postClient, ContentType.APPLICATION_JSON));
httpPost3.setHeader("Accept", "application/json");
httpPost3.setHeader("Content-type", "application/json");
httpPost3.setHeader("Authorization", "Bearer 811fa888f3e0fdc9e01d4201bfeee46a");
Copy link
Member

Choose a reason for hiding this comment

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

Not sure where this Bearer token is picked from. I guess it has something to do with the docker image?

Copy link
Member Author

Choose a reason for hiding this comment

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

We define this in override.properties which we bind mount to the container, see the docker compose file and it is used as a static credential to talk to the OPs API , I'll add a comment here when I get to merge this

Comment on lines +1 to +2
op.issuer=http://oidc-provider:8080/c2id
op.authz.endpoint=http://oidc-provider:8080/c2id-login/
Copy link
Member

Choose a reason for hiding this comment

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

Same here. Would be great to learn how these configurations work.

Copy link
Member Author

Choose a reason for hiding this comment

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

See https://connect2id.com/products/server/docs/guides/docker for some details. Issuer is the OPs issuer string that it will use to populate the iss claim in the ID Token. The OP also uses this to determine the token endpoint and userinfo endpoint it expects to be listening on by adding the proper suffix.

The authz.endpoint is an API endpoint that we use to programmatically authenticate users with the OP.

We use the docker compose service name oidc-provider so that es which also runs in docker can access it via it (docker networking magic :) )

Copy link
Member

Choose a reason for hiding this comment

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

Sorry for not being clear here. I wasn't asking about oidc-provider, which I understand it's docker's doing. Was more about property names, e.g. op.authz.endpoint and what do they mean to the OP. The guide you link should answer all my questions. Thanks!

@jkakavas
Copy link
Member Author

@elasticmachine run elasticsearch-ci/2

@jkakavas
Copy link
Member Author

:x-pack:test:idp-fixture:composeUp fails for some reason, @breskeby is kindly assisting me pinpointing the cause

@breskeby
Copy link
Contributor

Hey looking at the composeUp issue I noticed two things:

  1. As far as I understand the composeUp task should depend on preProcessFixture but it seems this is not wired up correctly.
  2. triggering preProcessFixture might not fix the problem as it seems the up-to-date checking of this and its depending tasks returns false positives.

I need some more time to narrow down the proper fix.

@jkakavas As a workaround, for now running ./gradlew preProcessFixture --rerun-tasks triggers the preProcessFixture task and its dependencies with ignoring the up-to-date check and should fix the problem for you locally.

@jkakavas
Copy link
Member Author

@breskeby any updates on this ? Did you find sometime to look into the composeUp task? Anything I could do to help with this? Thanks!

@breskeby
Copy link
Contributor

@jkakavas I pushed a fix on task dependencies setup for test clusters earlier today. Can you rebase against master and see if that fixes your issue?

@jkakavas
Copy link
Member Author

jkakavas commented Sep 1, 2020

@elasticmachine update branch

@jkakavas
Copy link
Member Author

jkakavas commented Sep 1, 2020

@elasticmachine run elasticsearch-ci/packaging-sample-windows

@jkakavas
Copy link
Member Author

jkakavas commented Sep 1, 2020

Still:

http-proxy uses an image, skipping
oidc-provider uses an image, skipping
Creating network "89c62680e863be49b17213e549106bdb_idp-fixture__default" with the default driver
Pulling elasticsearch-node (elasticsearch:test)...
manifest for elasticsearch:test not found
Removing network 89c62680e863be49b17213e549106bdb_idp-fixture__default

@breskeby

@breskeby
Copy link
Contributor

breskeby commented Sep 1, 2020

@mark-vieira I looked briefly into this but couldn't find a root cause for this. do you have an idea what's going wrong here?

@ywangd
Copy link
Member

ywangd commented Sep 7, 2020

Would be very much appreciated, if ElasticSearch 7.9.x could support client_secret_jwt instead of only plain secret-id.

The PR targets v7.10.0. Also patch releases, e.g. v7.9.2, normally only get bug fixes. Since this PR is new features, it is unlikely to be part of v7.9.2. But I'll double confirm with the team and get back to you.

@ywangd ywangd merged commit 5d341dc into elastic:master Sep 14, 2020
ywangd pushed a commit to ywangd/elasticsearch that referenced this pull request Sep 14, 2020
The OpenID Connect specification defines a number of ways for a
client (RP) to authenticate itself to the OP when accessing the
Token Endpoint. We currently only support `client_secret_basic`.

This change introduces support for 2 additional authentication
methods, namely `client_secret_post` (where the client credentials
are passed in the body of the POST request to the OP) and
`client_secret_jwt` where the client constructs a JWT and signs
it using the the client secret as a key.

Support for the above, and especially `client_secret_jwt` in our
integration tests meant that the OP we use ( Connect2id server )
should be able to validate the JWT that we send it from the RP.
Since we run the OP in docker and it listens on an ephemeral port
we would have no way of knowing the port so that we can configure
the ES running via the testcluster to know the "correct" Token
Endpoint, and even if we did, this would not be the Token Endpoint
URL that the OP would think it listens on. To alleviate this, we
run an ES single node cluster in docker, alongside the OP so that
we can configured it with the correct hostname and port within
the docker network.
@ywangd
Copy link
Member

ywangd commented Sep 14, 2020

Backport #62289
The change should be part of the upcoming v7.10.0 release.

ywangd added a commit that referenced this pull request Sep 16, 2020
The OpenID Connect specification defines a number of ways for a
client (RP) to authenticate itself to the OP when accessing the
Token Endpoint. We currently only support `client_secret_basic`.

This change introduces support for 2 additional authentication
methods, namely `client_secret_post` (where the client credentials
are passed in the body of the POST request to the OP) and
`client_secret_jwt` where the client constructs a JWT and signs
it using the the client secret as a key.

Support for the above, and especially `client_secret_jwt` in our
integration tests meant that the OP we use ( Connect2id server )
should be able to validate the JWT that we send it from the RP.
Since we run the OP in docker and it listens on an ephemeral port
we would have no way of knowing the port so that we can configure
the ES running via the testcluster to know the "correct" Token
Endpoint, and even if we did, this would not be the Token Endpoint
URL that the OP would think it listens on. To alleviate this, we
run an ES single node cluster in docker, alongside the OP so that
we can configured it with the correct hostname and port within
the docker network.

Co-authored-by: Ioannis Kakavas <[email protected]>
@tvernum
Copy link
Contributor

tvernum commented Oct 7, 2020

@jkakavas @ywangd Can we check the labels here?
I think we need to add v8.0.0 and remove backport pending

@ywangd
Copy link
Member

ywangd commented Oct 7, 2020

@tvernum You are right. They are corrected now.

jkakavas added a commit to jkakavas/elasticsearch that referenced this pull request Mar 9, 2021
Support for additional Client authentication methods was added in
the OIDC realm in elastic#58708. This change adds the `rp.client_auth_method`
in the realm settings reference doc.
jkakavas added a commit that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in #58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit to jkakavas/elasticsearch that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in elastic#58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit to jkakavas/elasticsearch that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in elastic#58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit to jkakavas/elasticsearch that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in elastic#58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit to jkakavas/elasticsearch that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in elastic#58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in #58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in #58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in #58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
jkakavas added a commit that referenced this pull request Mar 10, 2021
Support for additional Client authentication methods was added in
the OIDC realm in #58708. This change adds the `rp.client_auth_method`
and `rp.client_auth_signature_algorithm` settings in the realm settings 
reference doc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>enhancement :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) Team:Security Meta label for security team v7.10.0 v8.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants