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

Enable run_as for all authentication schemes #79809

Merged
merged 10 commits into from
Nov 3, 2021

Conversation

ywangd
Copy link
Member

@ywangd ywangd commented Oct 26, 2021

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.

@ywangd ywangd added >enhancement :Security/Authentication Logging in, Usernames/passwords, Realms (Native/LDAP/AD/SAML/PKI/etc) v8.0.0 labels Oct 26, 2021
@ywangd ywangd requested a review from tvernum October 26, 2021 13:43
@elasticmachine elasticmachine added the Team:Security Meta label for security team label Oct 26, 2021
@elasticmachine
Copy link
Collaborator

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

} else if (ApiKeyService.isApiKeyAuthentication(authentication)) {
getRolesForApiKey(authentication, roleActionListener);
if (authentication.getUser().isRunAs() && user == authentication.getUser().authenticatedUser()) {
if (ServiceAccountSettings.REALM_TYPE.equals(authentication.getAuthenticatedBy().getType())) {
Copy link
Contributor

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

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 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);
Copy link
Contributor

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.

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 added a comment

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.
@ywangd ywangd force-pushed the more-general-run-as branch from fe946be to 2dc1796 Compare October 29, 2021 02:39
@ywangd
Copy link
Member Author

ywangd commented Oct 29, 2021

@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!

@ywangd ywangd requested a review from tvernum October 29, 2021 02:47
Comment on lines 78 to 79
if (authentication.isServiceAccount()) {
if (authentication.isServiceAccount() && false == authentication.getUser().isRunAs()) {
listener.onFailure(new ElasticsearchException("OAuth2 token creation is not supported for service accounts"));
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 means a service account by itself cannot create oauth tokens. But it can run-as a regular user and create oauth tokens.

Copy link
Contributor

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() + "]";
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 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.

Copy link
Contributor

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.

@ywangd ywangd added v8.1.0 and removed v8.0.0 labels Oct 29, 2021
Copy link
Contributor

@tvernum tvernum left a 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() + "]";
Copy link
Contributor

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());
Copy link
Contributor

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.

Comment on lines 78 to 79
if (authentication.isServiceAccount()) {
if (authentication.isServiceAccount() && false == authentication.getUser().isRunAs()) {
listener.onFailure(new ElasticsearchException("OAuth2 token creation is not supported for service accounts"));
Copy link
Contributor

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();

Comment on lines 899 to 900
if (authentication.getUser().isRunAs()
|| (false == authentication.isServiceAccount() && AuthenticationType.API_KEY != authentication.getAuthenticationType())) {
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 more complex than we'd like - I suspect it's a case of missing the getEffectiveUserInfo concept.

If we could have something like

Suggested change
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

@ywangd
Copy link
Member Author

ywangd commented Nov 1, 2021

@tvernum and I discussed in a separate channel and agreed that there are work needs to be done for the Authentication class so it's easier to reason about and use. We also agreed that it should be separate PRs and keep this one mostly as is. I raised #80117 and we agree to prioritize it once this PR is merged.

@ywangd ywangd requested a review from tvernum November 1, 2021 05:46
@ywangd ywangd merged commit 67f6855 into elastic:master Nov 3, 2021
@legrego
Copy link
Member

legrego commented Nov 15, 2021

@ywangd does this resolve #51910?

@ywangd
Copy link
Member Author

ywangd commented Nov 15, 2021

@ywangd does this resolve #51910?

Yes it does. Forgot that we have this issue. I will close it. Thanks for the reminder.

ywangd added a commit that referenced this pull request Jan 18, 2022
This is essentially a follow-up PR for #79809 which enables run-as for API keys.
The code still has a few places where it assumes API keys cannot perform run-as. 
This PR fixes the incorrect assumptions and adds relevant tests.

Resolves: #81425
@pugnascotia pugnascotia changed the title Enable run as for all authentication schemes Enable run_as for all authentication schemes Jan 31, 2022
ywangd added a commit to ywangd/elasticsearch that referenced this pull request Feb 24, 2022
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
ywangd added a commit to ywangd/elasticsearch that referenced this pull request Feb 24, 2022
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
ywangd added a commit that referenced this pull request Feb 28, 2022
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
ywangd added a commit that referenced this pull request Feb 28, 2022
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
ywangd added a commit to ywangd/elasticsearch that referenced this pull request Feb 28, 2022
)

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
ywangd added a commit that referenced this pull request Feb 28, 2022
…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
ywangd added a commit to ywangd/elasticsearch that referenced this pull request Feb 28, 2022
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
elasticsearchmachine pushed a commit that referenced this pull request Feb 28, 2022
* 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
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 v8.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants