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

Remove the deprecated Authentication#getSourceRealm method #92222

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@
* Authentication is serialized and travels across the cluster nodes as the sub-requests are handled,
* and can also be cached by long-running jobs that continue to act on behalf of the user, beyond
* the lifetime of the original request.
*
* The authentication consists of two {@link Subject}s
* <ul>
* <li>{@link #authenticatingSubject} performs the authentication, i.e. it provides a credential.</li>
* <li>{@link #effectiveSubject} The subject that {@link #authenticatingSubject} impersonates ({@link #isRunAs()})</li>
* </ul>
* If {@link #isRunAs()} is {@code false}, the two {@link Subject}s will be the same object.
*
* Authentication also has a {@link #type} that indicates which mechanism the {@link #authenticatingSubject}
* uses to perform the authentication.
*
* The Authentication's version is its {@link Subject}'s version, i.e. {@code getEffectiveSubject().getVersion()}.
* It is guaranteed that the versions are identical for the two Subjects. Hence {@code getAuthenticatingSubject().getVersion()}
* will give out the same result. But using {@code getEffectiveSubject()} is more idiomatic since most callers
* of this class should just need to know about the {@link #effectiveSubject}. That is, often times, the caller
* begins with {@code authentication.getEffectiveSubject()} for interrogating an Authentication object.
*/
public final class Authentication implements ToXContentObject {

Expand Down Expand Up @@ -167,22 +183,8 @@ public boolean isRunAs() {
return authenticatingSubject != effectiveSubject;
}

/**
* Get the realm where the effective user comes from.
* The effective user is the es-security-runas-user if present or the authenticated user.
*
* Use {@code getEffectiveSubject().getRealm()} instead.
*/
@Deprecated
public RealmRef getSourceRealm() {
// TODO: This code retains the existing behaviour which is slightly wrong because
// when run-as lookup fails, the effectiveSubject will have a null realm. In this
// case, the code returns the authenticatingSubject's realm. This is wrong in theory
// because it is not the intention of this method. In practice, it does not matter
// because failed lookup will be rejected at authZ time. But fixing it causes test
// failures. So leave it for now.
final RealmRef sourceRealm = effectiveSubject.getRealm();
return sourceRealm == null ? authenticatingSubject.getRealm() : sourceRealm;
public boolean isFailedRunAs() {
return isRunAs() && effectiveSubject.getRealm() == null;
}

/**
Expand Down Expand Up @@ -228,9 +230,6 @@ public Authentication maybeRewriteForOlderVersion(Version olderVersion) {
);

}
if (isAssignedToDomain() && false == newAuthentication.isAssignedToDomain()) {
logger.info("Rewriting authentication [" + this + "] without domain");
}
Comment on lines -231 to -233
Copy link
Member Author

Choose a reason for hiding this comment

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

Relocated this logging inside the RealmRef itself which I think is a better location and also get rid of the usages of isAssignedToDomain

return newAuthentication;
}

Expand Down Expand Up @@ -262,7 +261,6 @@ public Authentication runAs(User runAs, @Nullable RealmRef lookupRealmRef) {
public Authentication token() {
assert false == isServiceAccount();
final Authentication newTokenAuthentication = new Authentication(effectiveSubject, authenticatingSubject, AuthenticationType.TOKEN);
assert Objects.equals(getDomain(), newTokenAuthentication.getDomain());
return newTokenAuthentication;
}

Expand Down Expand Up @@ -325,23 +323,28 @@ public Authentication maybeAddAnonymousRoles(@Nullable AnonymousUser anonymousUs
}
}

// Package private for tests
/**
* Returns {@code true} if the effective user belongs to a realm under a domain.
* See also {@link #getDomain()} and {@link #getSourceRealm()}.
*/
public boolean isAssignedToDomain() {
boolean isAssignedToDomain() {
return getDomain() != null;
}

// Package private for tests
/**
* Returns the {@link RealmDomain} that the effective user belongs to.
* A user belongs to a realm which in turn belongs to a domain.
*
* The same username can be authenticated by different realms (e.g. with different credential types),
* but resources created across realms cannot be accessed unless the realms are also part of the same domain.
*/
public @Nullable RealmDomain getDomain() {
return getSourceRealm().getDomain();
@Nullable
RealmDomain getDomain() {
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 changed this method (also isAssignedToDomain) to package private because:

  1. It is not really used in production code
  2. I am not sure whether we want them. Since we removed getRealm method from Authentication, having a getDomain feels going backwards.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed that it doesn't make sense to expose either of the domain methods beyond tests.

if (isFailedRunAs()) {
return null;
}
return getEffectiveSubject().getRealm().getDomain();
}

public boolean isAuthenticatedWithServiceAccount() {
Expand Down Expand Up @@ -861,6 +864,7 @@ public static Authentication newApiKeyAuthentication(AuthenticationResult<User>

private static RealmRef maybeRewriteRealmRef(Version streamVersion, RealmRef realmRef) {
if (realmRef != null && realmRef.getDomain() != null && streamVersion.before(VERSION_REALM_DOMAINS)) {
logger.info("Rewriting realm [" + realmRef + "] without domain");
// security domain erasure
new RealmRef(realmRef.getName(), realmRef.getType(), realmRef.getNodeName(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ private static boolean checkIfUserIsOwnerOfApiKeys(
if (false == username.equals(authentication.getEffectiveSubject().getUser().principal())) {
return false;
}
RealmDomain domain = authentication.getSourceRealm().getDomain();
RealmDomain domain = authentication.getEffectiveSubject().getRealm().getDomain();
if (domain != null) {
return domain.realms().stream().anyMatch(realmIdentifier -> realmName.equals(realmIdentifier.getName()));
} else {
return realmName.equals(authentication.getSourceRealm().getName());
return realmName.equals(authentication.getEffectiveSubject().getRealm().getName());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,12 @@ public Authentication build() {
return build(ESTestCase.randomBoolean());
}

public Authentication build(boolean runAsIfNotAlready) {
/**
* @param maybeRunAsIfNotAlready If the authentication is *not* run-as and the subject is a realm user, it will be transformed
* into a run-as authentication by moving the realm user to be the run-as user. The authenticating
* subject can be either a realm user or an API key (in general any subject type that can run-as).
*/
public Authentication build(boolean maybeRunAsIfNotAlready) {
if (authenticatingAuthentication != null) {
if (user == null) {
user = randomUser();
Expand All @@ -402,7 +407,7 @@ public Authentication build(boolean runAsIfNotAlready) {
realmRef = randomRealmRef(isRealmUnderDomain == null ? ESTestCase.randomBoolean() : isRealmUnderDomain);
}
assert false == SYNTHETIC_REALM_TYPES.contains(realmRef.getType()) : "use dedicate methods for synthetic realms";
if (runAsIfNotAlready) {
if (maybeRunAsIfNotAlready) {
authentication = builder().runAs().user(user).realmRef(realmRef).build();
} else {
authentication = Authentication.newRealmAuthentication(user, realmRef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,14 @@

public class AuthenticationTests extends ESTestCase {

public void testWillGetLookedUpByWhenItExists() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: would cover isFailedRunAs here

Copy link
Member Author

@ywangd ywangd Dec 8, 2022

Choose a reason for hiding this comment

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

Good point! I adapted the tests for isFailedRunAs. Also added new assertions in ApiKeyServiceTests to ensure the behaviours of getCreatorRealmName and getCreatorRealmType do not change.

final RealmRef authenticatedBy = new RealmRef("auth_by", "auth_by_type", "node");
final RealmRef lookedUpBy = new RealmRef("lookup_by", "lookup_by_type", "node");
final Authentication authentication = AuthenticationTestHelper.builder()
.user(new User("not-user"))
.realmRef(authenticatedBy)
.runAs()
.user(new User("user"))
.realmRef(lookedUpBy)
.build();

assertEquals(lookedUpBy, authentication.getSourceRealm());
}

public void testWillGetAuthenticateByWhenLookupIsNull() {
final RealmRef authenticatedBy = new RealmRef("auth_by", "auth_by_type", "node");
final Authentication authentication = AuthenticationTestHelper.builder()
.user(new User("user"))
.realmRef(authenticatedBy)
.build(false);

assertEquals(authenticatedBy, authentication.getSourceRealm());
public void testIsFailedRunAs() {
final Authentication failedAuthentication = randomRealmAuthentication(randomBoolean()).runAs(randomUser(), null);
assertTrue(failedAuthentication.isRunAs());
assertTrue(failedAuthentication.isFailedRunAs());

final Authentication authentication = AuthenticationTestHelper.builder().realm().runAs().build();
assertTrue(authentication.isRunAs());
assertFalse(authentication.isFailedRunAs());
}

public void testCanAccessResourcesOf() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,11 @@ public static String getCreatorRealmName(final Authentication authentication) {
} else {
// TODO we should use the effective subject realm here but need to handle the failed lookup scenario, in which the realm may be
// `null`. Since this method is used in audit logging, this requires some care.
return authentication.getSourceRealm().getName();
if (authentication.isFailedRunAs()) {
return authentication.getAuthenticatingSubject().getRealm().getName();
} else {
return authentication.getEffectiveSubject().getRealm().getName();
}
}
}

Expand Down Expand Up @@ -1791,7 +1795,11 @@ public static String getCreatorRealmType(final Authentication authentication) {
} else {
// TODO we should use the effective subject realm here but need to handle the failed lookup scenario, in which the realm may be
// `null`. Since this method is used in audit logging, this requires some care.
return authentication.getSourceRealm().getType();
if (authentication.isFailedRunAs()) {
return authentication.getAuthenticatingSubject().getRealm().getType();
} else {
return authentication.getEffectiveSubject().getRealm().getType();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1805,12 +1805,13 @@ static BytesReference createTokenDocument(
}
builder.endObject().endObject();
}
final Authentication.RealmRef userTokenEffectiveRealm = userToken.getAuthentication().getEffectiveSubject().getRealm();
builder.startObject("access_token")
.field("invalidated", false)
.field("user_token", userToken)
.field("realm", userToken.getAuthentication().getSourceRealm().getName());
if (userToken.getAuthentication().getSourceRealm().getDomain() != null) {
builder.field("realm_domain", userToken.getAuthentication().getSourceRealm().getDomain());
.field("realm", userTokenEffectiveRealm.getName());
if (userTokenEffectiveRealm.getDomain() != null) {
builder.field("realm_domain", userTokenEffectiveRealm.getDomain());
}
builder.endObject().endObject();
return BytesReference.bytes(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.elasticsearch.xpack.core.security.action.service.TokenInfo;
import org.elasticsearch.xpack.core.security.action.service.TokenInfo.TokenSource;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.service.ServiceAccount.ServiceAccountId;
import org.elasticsearch.xpack.security.authc.service.ServiceAccountToken.ServiceAccountTokenId;
Expand Down Expand Up @@ -269,15 +270,16 @@ private XContentBuilder newDocument(Authentication authentication, ServiceAccoun
.field("creation_time", clock.instant().toEpochMilli())
.field("enabled", true);
{
final Subject effectiveSubject = authentication.getEffectiveSubject();
builder.startObject("creator")
.field("principal", authentication.getEffectiveSubject().getUser().principal())
.field("full_name", authentication.getEffectiveSubject().getUser().fullName())
.field("email", authentication.getEffectiveSubject().getUser().email())
.field("metadata", authentication.getEffectiveSubject().getUser().metadata())
.field("realm", authentication.getSourceRealm().getName())
.field("realm_type", authentication.getSourceRealm().getType());
if (authentication.getSourceRealm().getDomain() != null) {
builder.field("realm_domain", authentication.getSourceRealm().getDomain());
.field("principal", effectiveSubject.getUser().principal())
.field("full_name", effectiveSubject.getUser().fullName())
.field("email", effectiveSubject.getUser().email())
.field("metadata", effectiveSubject.getUser().metadata())
.field("realm", effectiveSubject.getRealm().getName())
.field("realm_type", effectiveSubject.getRealm().getType());
if (effectiveSubject.getRealm().getDomain() != null) {
builder.field("realm_domain", effectiveSubject.getRealm().getDomain());
}
builder.endObject();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ public boolean isOperatorUser(Authentication authentication) {
// If not null, it will be compared exactly as well.
// The special handling for realm name is because there can only be one file or native realm and it does
// not matter what the name is.
final Authentication.RealmRef realm = authentication.getEffectiveSubject().getRealm();
if (realm == null) {
return false;
}
return operatorUsersDescriptor.groups.stream().anyMatch(group -> {
final Authentication.RealmRef realm = authentication.getSourceRealm();
final boolean match = group.usernames.contains(authentication.getEffectiveSubject().getUser().principal())
&& group.authenticationType == authentication.getAuthenticationType()
&& realm.getType().equals(group.realmType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2019,7 +2019,7 @@ public void testAccessGrantedInternalSystemAction() throws Exception {
.put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "access_granted")
.put(LoggingAuditTrail.AUTHENTICATION_TYPE_FIELD_NAME, authentication.getAuthenticationType().toString())
.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, systemUser.principal())
.put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, authentication.getSourceRealm().getName())
.put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, authentication.getEffectiveSubject().getRealm().getName())
.put(LoggingAuditTrail.ACTION_FIELD_NAME, "internal:_action")
.put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, request.getClass().getSimpleName())
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1512,8 +1512,8 @@ public void testGetCreatorRealm() {

// Realm
final Authentication authentication3 = AuthenticationTests.randomRealmAuthentication(randomBoolean());
assertThat(ApiKeyService.getCreatorRealmName(authentication3), equalTo(authentication3.getSourceRealm().getName()));
assertThat(ApiKeyService.getCreatorRealmType(authentication3), equalTo(authentication3.getSourceRealm().getType()));
assertThat(ApiKeyService.getCreatorRealmName(authentication3), equalTo(authentication3.getEffectiveSubject().getRealm().getName()));
assertThat(ApiKeyService.getCreatorRealmType(authentication3), equalTo(authentication3.getEffectiveSubject().getRealm().getType()));

// Realm run-as
final Authentication authentication4 = authentication3.runAs(AuthenticationTests.randomUser(), lookupRealmRef);
Expand All @@ -1526,8 +1526,19 @@ public void testGetCreatorRealm() {
AuthenticationTests.randomAnonymousAuthentication(),
AuthenticationTests.randomInternalAuthentication()
);
assertThat(ApiKeyService.getCreatorRealmName(authentication5), equalTo(authentication5.getSourceRealm().getName()));
assertThat(ApiKeyService.getCreatorRealmType(authentication5), equalTo(authentication5.getSourceRealm().getType()));
assertThat(ApiKeyService.getCreatorRealmName(authentication5), equalTo(authentication5.getEffectiveSubject().getRealm().getName()));
assertThat(ApiKeyService.getCreatorRealmType(authentication5), equalTo(authentication5.getEffectiveSubject().getRealm().getType()));

// Failed run-as returns authenticating subject's realm
final Authentication authentication6 = authentication3.runAs(AuthenticationTests.randomUser(), null);
assertThat(
ApiKeyService.getCreatorRealmName(authentication6),
equalTo(authentication6.getAuthenticatingSubject().getRealm().getName())
);
assertThat(
ApiKeyService.getCreatorRealmType(authentication6),
equalTo(authentication6.getAuthenticatingSubject().getRealm().getType())
);
}

public void testGetOwnersRealmNames() {
Expand Down
Loading