-
Notifications
You must be signed in to change notification settings - Fork 24.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
PKI Authentication Delegation in new endpoint #43796
Conversation
Pinging @elastic/es-security |
@elasticmachine run elasticsearch-ci/default-distro |
Since I didn't yet managed to raise the promised bug fix PR, I will detail it here to cut the suspense. The 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 |
Although line count wise this isn't huge, the weight of it feels big to me. 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, |
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 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; |
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.
null and empty check
import java.security.cert.X509Certificate; | ||
import java.util.Arrays; | ||
|
||
public class DelegatePkiRequest extends ActionRequest { |
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.
make it final?
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 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; |
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 guess the expectation is that this will be ordered chain, shall we check that here? or try to order it before setting it?
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.
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); |
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.
null or empty check
|
||
public class DelegatePkiResponse extends ActionResponse implements ToXContentObject { | ||
|
||
private String tokenString; |
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.
make it final?
public class DelegatePkiResponse extends ActionResponse implements ToXContentObject { | ||
|
||
private String tokenString; | ||
private TimeValue expiresIn; |
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.
make it final?
import java.io.IOException; | ||
import java.util.Objects; | ||
|
||
public class AuthenticationDelegateeInfo implements ToXContentObject { |
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 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.
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 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( |
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.
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?
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 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()); |
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.
logger.debug("failed certificate validation for Subbject DN [{}]", token.dn()); | |
logger.debug("failed certificate validation for Subject DN [{}]", token.dn()); |
Thanks @bizybot for the very fast review. I have acknowledged the feedback and I will address it in the ensuing followups as suggested. |
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. |
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