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

PKI Authentication Delegation in new endpoint #43796

Closed

Conversation

albertzaharovits
Copy link
Contributor

This implementation creates new REST and transport actions (one of each) that do the validation of a certificate chain against installed PKI realms. It is expected that the client calling the endpoint performed TLS termination and captured the clients certificate. Upon successful validation an access token is released (but no refresh token). The access token (Bearer) impersonates the end-user as if the end-user, owner of the private key associated with the certificate chain, authenticated directly to ES.

It is draft because it does not contain tests.

This would be a rather prosaic implementation, if not for a bug fix, that I would describe in a separate PR.

Relates #34396

@albertzaharovits albertzaharovits added >feature :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) v8.0.0 v7.3.0 labels Jun 30, 2019
@albertzaharovits albertzaharovits self-assigned this Jun 30, 2019
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-security

@albertzaharovits
Copy link
Contributor Author

@elasticmachine run elasticsearch-ci/default-distro

@albertzaharovits
Copy link
Contributor Author

Since I didn't yet managed to raise the promised bug fix PR, I will detail it here to cut the suspense.

The X509AuthenticationToken has a principal field which is set during the token construction. The field is set by parsing the subject DN of the client cert. Given the current inner workings, of first iterating the realms to build the token and afterwards iterating them again to authenticate the token, it was possible to parse the DN using the pattern of some PKI realm but actually authenticating the token by another PKI realm (given the truststore configurations).

The proposed fix (in this PR, but I will raise a separate PR just for the fix) is to simply consider the un-parsed DN as the principal of the X509AuthenticationToken. The principal after authentication is still the one after the parsing.

@tvernum
Copy link
Contributor

tvernum commented Jul 2, 2019

Although line count wise this isn't huge, the weight of it feels big to me.
That is, because it attempts to solve the whole space in 1 PR (and merge direct to master/7.x), I know that I need to review it in terms of "is this a complete feature?" as well as "does every part of this make sense?".
To feel like I can do justice to the review, I think would need to do several passes, each one considering it from a different angle.

It would be my preference (despite the relatively small line count) to split this into smaller PRs if possible so we can review the various separate aspects independently - e.g. "Here is the REST endpoint, Request & Response objects, the transport action is stubbed out & doesn't do anything."

Copy link
Contributor

@bizybot bizybot left a comment

Choose a reason for hiding this comment

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

I agree with Tim to split this into smaller changes, this is based on my observation from my earlier PRs and would help everyone to focus one thing at a time. Thank you.
As I had started to review this earlier, I have posted the comments here in case we move to split this PR.

private X509Certificate[] certificates;

public DelegatePkiRequest(X509Certificate[] certificates) {
this.certificates = certificates;
Copy link
Contributor

Choose a reason for hiding this comment

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

null and empty check

import java.security.cert.X509Certificate;
import java.util.Arrays;

public class DelegatePkiRequest extends ActionRequest {
Copy link
Contributor

Choose a reason for hiding this comment

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

make it final?

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 will make it final. But, in general, I think we should make final only the classes with algorithms inside, POJOs like requests and responses would generally benefit from inheritance.

private X509Certificate[] certificates;

public DelegatePkiRequest(X509Certificate[] certificates) {
this.certificates = certificates;
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the expectation is that this will be ordered chain, shall we check that here? or try to order it before setting 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.

They are already in the correct order (as the client provided them), and the order is checked by trustManager.checkClientTrusted . The client should dump the cert chain as captured from the TLS connection.

DelegatePkiResponse() { }

public DelegatePkiResponse(String tokenString, TimeValue expiresIn) {
this.tokenString = Objects.requireNonNull(tokenString);
Copy link
Contributor

Choose a reason for hiding this comment

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

null or empty check


public class DelegatePkiResponse extends ActionResponse implements ToXContentObject {

private String tokenString;
Copy link
Contributor

Choose a reason for hiding this comment

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

make it final?

public class DelegatePkiResponse extends ActionResponse implements ToXContentObject {

private String tokenString;
private TimeValue expiresIn;
Copy link
Contributor

Choose a reason for hiding this comment

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

make it final?

import java.io.IOException;
import java.util.Objects;

public class AuthenticationDelegateeInfo implements ToXContentObject {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am trying to understand the purpose of this class. It feels like a wrapper around the original Authentication and we add it to the user metadata. Do we intend to use this information somewhere after authentication? Could you please elaborate, also if you could add java docs. Thank you.

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 class should encapsulate all the details based on which the realm allows the delegation or not. At its simplest it could be a boolean flag saying that this token is delegated (as opposed to locally crafted), but here it encapsulates the authentication of the client. In this case the realm could restrict delegation to only certain users. I will leave it as a boolean, before we decide on the means to restrict delegation.

@@ -101,7 +101,8 @@ public void testRestAuthenticationFailure() throws Exception {
try (CloseableHttpResponse response = SocketAccess.doPrivileged(() -> client.execute(put))) {
assertThat(response.getStatusLine().getStatusCode(), is(401));
String body = EntityUtils.toString(response.getEntity());
assertThat(body, containsString("unable to authenticate user [Elasticsearch Test Client]"));
assertThat(body, containsString(
Copy link
Contributor

Choose a reason for hiding this comment

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

As we do not extract DN upfront anymore the REST response on failure has changed. Do we think of it as a breaking change from the REST endpoint or it is okay?

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 message, although part of the body, is the reason field in the ElasticsearchException . I believe we've been pretty lax about changes to it, but I can't find a recent PR that changed them. But generally I see we put messages from other internal exceptions in there so changes can happen unknowingly. So, I think this is not a breaking change, but might benefit from @tvernum opinion as well.

} else if (logger.isDebugEnabled()) {
logger.debug("failed certificate validation for principal [{}]", token.principal());
logger.debug("failed certificate validation for Subbject DN [{}]", token.dn());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
logger.debug("failed certificate validation for Subbject DN [{}]", token.dn());
logger.debug("failed certificate validation for Subject DN [{}]", token.dn());

@albertzaharovits
Copy link
Contributor Author

Thanks @bizybot for the very fast review. I have acknowledged the feedback and I will address it in the ensuing followups as suggested.

@albertzaharovits
Copy link
Contributor Author

After #44561 has been raised there is no more code here that hasn't been merged or is on a PR or feature branch (https://github.com/elastic/elasticsearch/tree/proxied-pki), so closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>feature :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) v7.4.0 v8.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants