-
Notifications
You must be signed in to change notification settings - Fork 25.1k
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
Enable run_as for all authentication schemes #79809
Conversation
Pinging @elastic/es-security (Team:Security) |
...plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticatorChain.java
Outdated
Show resolved
Hide resolved
...security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java
Outdated
Show resolved
Hide resolved
} else if (ApiKeyService.isApiKeyAuthentication(authentication)) { | ||
getRolesForApiKey(authentication, roleActionListener); | ||
if (authentication.getUser().isRunAs() && user == authentication.getUser().authenticatedUser()) { | ||
if (ServiceAccountSettings.REALM_TYPE.equals(authentication.getAuthenticatedBy().getType())) { |
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 think this series of if
/else
would be clearer as a switch
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 still used if/else
in the updated version since the logic is now simpler. But please let me know if you still think switch
is better.
if (ServiceAccountSettings.REALM_TYPE.equals(authentication.getAuthenticatedBy().getType())) { | ||
getRolesForServiceAccount(authentication.getUser().authenticatedUser().principal(), roleActionListener); | ||
} else if ("_es_api_key".equals(authentication.getAuthenticatedBy().getType())) { | ||
getRolesForApiKey(authentication, roleActionListener); |
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 think we need a comment here to explain where we're using the authentication
not the authenticatedUser()
.
It's not obvious that this is reliant of the API Key id being in the authentication metadata.
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 added a comment
...rity/src/main/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountService.java
Outdated
Show resolved
Hide resolved
This PR removes the restriction for run_as so the authenticated user does not have to be authenticated by a realm. It can now also be either an API key, a token or a service account. Note we don't currently have a service account that has run_as privilege. This PR also include a change to the existing internal behaviour: the final authentication object created for run-as now keeps information of the authenticated user instead of dropping them. For example, metadata associated with the original authentication is preserved. If the original authentication is an API key, the final authentication will have authentication type as "api_key" as well.
fe946be
to
2dc1796
Compare
@tvernum The PR is now updated according to our last chat. Sorry to waste some of your time on the initial version. It is now ready for another look. Thanks! |
if (authentication.isServiceAccount()) { | ||
if (authentication.isServiceAccount() && false == authentication.getUser().isRunAs()) { | ||
listener.onFailure(new ElasticsearchException("OAuth2 token creation is not supported for service accounts")); |
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 means a service account by itself cannot create oauth tokens. But it can run-as a regular user and create oauth tokens.
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 think this comes into my point above - the fact that you need an extra check here for "oh, except if it's run as", implies we have an issue.
I wonder whether we need something more like:
authentication.isServiceAccount(user)
and the answer depends on whether user
is the authenticated user or the lookup user.
but then the API is kind of weird.
Maybe
enum UserType {
AUTHENTICATING, // the user who provided credentials
LOOKUP, // the user who was looked up, if any
EFFECTIVE // the lookup user if there was one, otherwise the authenticating user
};
boolean isServiceAccount(UserType type);
or
interface UserInfo {
boolean isServiceAccount();
boolean isApiKey();
// etc;
}
public UserInfo getAuthenticatingUserInfo();
@Nullable;
public UserInfo getLookupUserInfo();
public UserInfo getEffectiveUserInfo();
String userText = "user [" + authUser.principal() + "]"; | ||
String userText = (authentication.isServiceAccount() ? "service account" : "user") + " [" + authUser.principal() + "]"; |
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 is visible to end user. So I am not sure if it should be categorized as breaking change. I think no since service account are not meant to be directly user facing. It however creates a RestCompat test failure with 7.16. I think if we agree to make the change here. I'll raise a small PR just to change this wording for 7.16 as a bug fix.
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.
We haven't ever treated error messages as being part of the server's contract, & would not consider this to be a breaking change.
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 left some comments - we might want to discuss it directly next week.
I like where this is going, but I think there's a concept missing somewhere in our set of Authentication classes, and that makes it more complex than it needs to be.
String userText = "user [" + authUser.principal() + "]"; | ||
String userText = (authentication.isServiceAccount() ? "service account" : "user") + " [" + authUser.principal() + "]"; |
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.
We haven't ever treated error messages as being part of the server's contract, & would not consider this to be a breaking change.
@@ -114,7 +114,7 @@ public AuthenticationType getAuthenticationType() { | |||
} | |||
|
|||
public boolean isServiceAccount() { | |||
return ServiceAccountSettings.REALM_TYPE.equals(getAuthenticatedBy().getType()) && null == getLookedUpBy(); | |||
return ServiceAccountSettings.REALM_TYPE.equals(getAuthenticatedBy().getType()); |
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 suspect we're going to end up with a bug at some point because this method is a now little ambiguous about what it does.
isServiceAccount
is true, if the user authenticated using a service account token (or, in the future another credential), but the effective (run-as) user might not be a service account.
Anything that relies on isServiceAccount
to make decisions has the potential to do the wrong thing if they don't take that into account.
I wonder if we can rename the method to make that more clear somehow.
isServiceAccountAuthentication()
?isAuthenticatedWithServiceAccountCredential()
?authenticatedAsServiceAccount()
I don't love any of those, but I think we need to do something to mitigate the risk.
if (authentication.isServiceAccount()) { | ||
if (authentication.isServiceAccount() && false == authentication.getUser().isRunAs()) { | ||
listener.onFailure(new ElasticsearchException("OAuth2 token creation is not supported for service accounts")); |
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 think this comes into my point above - the fact that you need an extra check here for "oh, except if it's run as", implies we have an issue.
I wonder whether we need something more like:
authentication.isServiceAccount(user)
and the answer depends on whether user
is the authenticated user or the lookup user.
but then the API is kind of weird.
Maybe
enum UserType {
AUTHENTICATING, // the user who provided credentials
LOOKUP, // the user who was looked up, if any
EFFECTIVE // the lookup user if there was one, otherwise the authenticating user
};
boolean isServiceAccount(UserType type);
or
interface UserInfo {
boolean isServiceAccount();
boolean isApiKey();
// etc;
}
public UserInfo getAuthenticatingUserInfo();
@Nullable;
public UserInfo getLookupUserInfo();
public UserInfo getEffectiveUserInfo();
if (authentication.getUser().isRunAs() | ||
|| (false == authentication.isServiceAccount() && AuthenticationType.API_KEY != authentication.getAuthenticationType())) { |
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 is more complex than we'd like - I suspect it's a case of missing the getEffectiveUserInfo
concept.
If we could have something like
if (authentication.getUser().isRunAs() | |
|| (false == authentication.isServiceAccount() && AuthenticationType.API_KEY != authentication.getAuthenticationType())) { | |
if (authentication.getEffectiveUserInfo().hasRoles()) { |
I don't love "UserInfo" though, but I think you get the idea.
Maybe AccountType
or UserType
or PrincipalType
would work
@tvernum and I discussed in a separate channel and agreed that there are work needs to be done for the |
API Key can run-as since elastic#79809. There are places in the code where we assume API key cannot run-as. Most of them are corrected in elastic#81564. But there are still a few things got missed. This PR fixes the methods for checking owner user realm for API key. Note the resource sharing check (canAccessResourcesOf) also needs to be fixed, this will be handled by elastic#84277
This PR removes run-as support for authentication types other than realm and API key. The change essentially makes the behaviour closer to the existing one (in released versions) except for API keys. This is not to say that the existing behaviour is the best. But we need more time to agree on the new behaviour. Relates: elastic#79809
This PR removes run-as support for authentication types other than realm and API key. The change essentially makes the behaviour closer to the existing one (in released versions) except for API keys. This is not to say that the existing behaviour is the best. But we need more time to agree on the new behaviour. Relates: #79809
API Key can run-as since #79809. There are places in the code where we assume API key cannot run-as. Most of them are corrected in #81564. But there are still a few things got missed. This PR fixes the methods for checking owner user realm for API key. This means, when API Keys "running-as" (impersonating other users), we do not expose the authenticating key ID and name to the end-user such as the Authenticate API and the SetSecurityUseringest processor. Only the effective user is revealed, just like in the regular case of a realm user run as. For audit logging, the key's ID and name are not exposed either. But this is mainly because there are no existing fields suitable for these information. We do intend to add them later (#84394) because auditing logging is to consumed by system admin instead of end-users. Note the resource sharing check (canAccessResourcesOf) also needs to be fixed, this will be handled by #84277
) This PR removes run-as support for authentication types other than realm and API key. The change essentially makes the behaviour closer to the existing one (in released versions) except for API keys. This is not to say that the existing behaviour is the best. But we need more time to agree on the new behaviour. Relates: elastic#79809
…84399) This PR removes run-as support for authentication types other than realm and API key. The change essentially makes the behaviour closer to the existing one (in released versions) except for API keys. This is not to say that the existing behaviour is the best. But we need more time to agree on the new behaviour. Relates: #79809
API Key can run-as since elastic#79809. There are places in the code where we assume API key cannot run-as. Most of them are corrected in elastic#81564. But there are still a few things got missed. This PR fixes the methods for checking owner user realm for API key. This means, when API Keys "running-as" (impersonating other users), we do not expose the authenticating key ID and name to the end-user such as the Authenticate API and the SetSecurityUseringest processor. Only the effective user is revealed, just like in the regular case of a realm user run as. For audit logging, the key's ID and name are not exposed either. But this is mainly because there are no existing fields suitable for these information. We do intend to add them later (elastic#84394) because auditing logging is to consumed by system admin instead of end-users. Note the resource sharing check (canAccessResourcesOf) also needs to be fixed, this will be handled by elastic#84277
* Fix owner user realm check for API key authentication (#84325) API Key can run-as since #79809. There are places in the code where we assume API key cannot run-as. Most of them are corrected in #81564. But there are still a few things got missed. This PR fixes the methods for checking owner user realm for API key. This means, when API Keys "running-as" (impersonating other users), we do not expose the authenticating key ID and name to the end-user such as the Authenticate API and the SetSecurityUseringest processor. Only the effective user is revealed, just like in the regular case of a realm user run as. For audit logging, the key's ID and name are not exposed either. But this is mainly because there are no existing fields suitable for these information. We do intend to add them later (#84394) because auditing logging is to consumed by system admin instead of end-users. Note the resource sharing check (canAccessResourcesOf) also needs to be fixed, this will be handled by #84277 * fix test
This PR removes the restriction for run_as so the authenticated user
does not have to be authenticated by a realm. It can now also be either
an API key, a token or a service account. Note we don't currently have
a service account that has run_as privilege.
This PR also include a change to the existing internal behaviour: the
final authentication object created for run-as now keeps information of
the authenticated user instead of dropping them. For example, metadata
associated with the original authentication is preserved. If the
original authentication is an API key, the final authentication will
have authentication type as "api_key" as well.