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

Expose lookup of realm domain config by realm id #106424

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.core.security.action.profile.Profile;
import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo.RoleDescriptorsBytes;
import org.elasticsearch.xpack.core.security.authc.RealmConfig.RealmIdentifier;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
Expand Down Expand Up @@ -1008,6 +1010,11 @@ public String toString() {
return builder.toString();
}

/**
* {@link RealmRef} expresses the grouping of realms, identified with {@link RealmIdentifier}s, under {@link RealmDomain}s.
* A domain groups different realms, such that any username, authenticated by different realms from the <b>same domain</b>,
* is to be associated to a single {@link Profile}.
*/
public static class RealmRef implements Writeable, ToXContentObject {

private final String nodeName;
Expand Down Expand Up @@ -1082,6 +1089,13 @@ public String getType() {
return domain;
}

/**
* The {@code RealmIdentifier} is the fully qualified way to refer to a realm.
*/
public RealmIdentifier getIdentifier() {
return new RealmIdentifier(type, name);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.XPackField;
import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
import org.elasticsearch.xpack.core.security.authc.RealmConfig.RealmIdentifier;
import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
* An authentication mechanism to which the default authentication org.elasticsearch.xpack.security.authc.AuthenticationService
Expand Down Expand Up @@ -145,8 +143,11 @@ public void usageStats(ActionListener<Map<String, Object>> listener) {
listener.onResponse(stats);
}

public void initRealmRef(Map<RealmIdentifier, RealmRef> realmRefs) {
final RealmRef realmRef = Objects.requireNonNull(realmRefs.get(new RealmIdentifier(type(), name())), "realmRef must not be null");
/**
* Must be called only once by the realms initialization logic, soon after this {@code Realm} is constructed,
* in order to link in the realm domain details, which may refer to any of the other realms.
*/
public void setRealmRef(RealmRef realmRef) {
this.realmRef.set(realmRef);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authc.esnative.ClientReservedRealm;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.Realms;
Expand Down Expand Up @@ -63,12 +62,7 @@ public TransportGetUsersAction(
this.settings = settings;
this.usersStore = usersStore;
this.reservedRealm = reservedRealm;
this.nativeRealmRef = realms.getRealmRefs()
.values()
.stream()
.filter(realmRef -> NativeRealmSettings.TYPE.equals(realmRef.getType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("native realm realm ref not found"));
this.nativeRealmRef = realms.getNativeRealmRef();
this.profileService = profileService;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.elasticsearch.xpack.core.security.action.user.QueryUserResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.profile.ProfileService;
Expand Down Expand Up @@ -57,12 +56,7 @@ public TransportQueryUserAction(
super(ActionTypes.QUERY_USER_ACTION.name(), actionFilters, transportService.getTaskManager());
this.usersStore = usersStore;
this.profileService = profileService;
this.nativeRealmRef = realms.getRealmRefs()
.values()
.stream()
.filter(realmRef -> NativeRealmSettings.TYPE.equals(realmRef.getType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("native realm realm ref not found"));
this.nativeRealmRef = realms.getNativeRealmRef();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
Expand Down Expand Up @@ -110,7 +111,13 @@ public Realms(
// initRealms will add default file and native realm config if they are not explicitly configured
final List<Realm> initialRealms = initRealms(realmConfigs);
realmRefs = calculateRealmRefs(realmConfigs, realmToDomainConfig);
initialRealms.forEach(realm -> realm.initRealmRef(realmRefs));
for (Realm realm : initialRealms) {
Authentication.RealmRef realmRef = Objects.requireNonNull(
realmRefs.get(new RealmConfig.RealmIdentifier(realm.type(), realm.name())),
"realmRef can not be null"
);
realm.setRealmRef(realmRef);
}

this.allConfiguredRealms = initialRealms;
this.allConfiguredRealms.forEach(r -> r.initialize(this.allConfiguredRealms, licenseState));
Expand Down Expand Up @@ -155,6 +162,12 @@ private Map<RealmConfig.RealmIdentifier, Authentication.RealmRef> calculateRealm
new Authentication.RealmRef(realmIdentifier.getName(), realmIdentifier.getType(), nodeName, realmDomain)
);
}
assert realmRefs.values().stream().filter(realmRef -> ReservedRealm.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one reserved realm configured";
assert realmRefs.values().stream().filter(realmRef -> NativeRealmSettings.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one native realm configured";
assert realmRefs.values().stream().filter(realmRef -> FileRealmSettings.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one file realm configured";
return Map.copyOf(realmRefs);
}

Expand Down Expand Up @@ -368,8 +381,52 @@ public Map<String, Object> domainUsageStats() {
}
}

public Map<RealmConfig.RealmIdentifier, Authentication.RealmRef> getRealmRefs() {
return realmRefs;
/**
* Retrieves the {@link Authentication.RealmRef}, which contains the {@link DomainConfig}, if configured,
* for the passed in {@link RealmConfig.RealmIdentifier}.
* If the realm is not currently configured, {@code null} is returned.
*/
public @Nullable Authentication.RealmRef getRealmRef(RealmConfig.RealmIdentifier realmIdentifier) {
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 is the goal of this PR.
It exposes the domain configuration (the set of realm ids) for a given realm id.
The plan is to use the realm id of the API Key owner to retrieve the domain using this method. Then retrieve the profiles associated to any of the realms in the domain.

// "file", "native", and "reserved" realms may be renamed, but they refer to the same corpus of users
if (FileRealmSettings.TYPE.equals(realmIdentifier.getType())) {
return getFileRealmRef();
} else if (NativeRealmSettings.TYPE.equals(realmIdentifier.getType())) {
return getNativeRealmRef();
Comment on lines +391 to +394
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 kink here is that the name of the native or file realms is irrelevant, when considering the domain configuration, see also

. Moreover there is always exactly one RealmRef for both file and native realms (which may contain the domain conf). The rationale is that the file and native realms always refer to the same corpus of internal users.

} else if (ReservedRealm.TYPE.equals(realmIdentifier.getType())) {
return getReservedRealmRef();
Comment on lines +395 to +396
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 reserved realm is less relevant in the domain context, because it cannot be assigned to any.

} else {
// but for other realms, it is assumed that a different realm name or realm type signifies a different corpus of users
return realmRefs.get(realmIdentifier);
}
}

public Authentication.RealmRef getNativeRealmRef() {
return realmRefs.values()
.stream()
.filter(realmRef -> NativeRealmSettings.TYPE.equals(realmRef.getType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("native realm realm ref not found"));
}

public Authentication.RealmRef getFileRealmRef() {
return realmRefs.values()
.stream()
.filter(realmRef -> FileRealmSettings.TYPE.equals(realmRef.getType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("file realm realm ref not found"));
}

public Authentication.RealmRef getReservedRealmRef() {
return realmRefs.values()
.stream()
.filter(realmRef -> ReservedRealm.TYPE.equals(realmRef.getType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("reserved realm realm ref not found"));
}

// should only be useful for testing
int getRealmRefsCount() {
return realmRefs.size();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
Expand Down Expand Up @@ -114,12 +113,7 @@ public void testAnonymousUser() {
when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true);
AnonymousUser anonymousUser = new AnonymousUser(settings);
ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser, threadPool);
reservedRealm.initRealmRef(
Map.of(
new RealmConfig.RealmIdentifier(ReservedRealm.TYPE, ReservedRealm.NAME),
new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node")
)
);
reservedRealm.setRealmRef(new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node"));
TransportService transportService = new TransportService(
Settings.EMPTY,
mock(Transport.class),
Expand Down Expand Up @@ -195,12 +189,7 @@ public void testReservedUsersOnly() {
new AnonymousUser(settings),
threadPool
);
reservedRealm.initRealmRef(
Map.of(
new RealmConfig.RealmIdentifier(ReservedRealm.TYPE, ReservedRealm.NAME),
new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node")
)
);
reservedRealm.setRealmRef(new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node"));
PlainActionFuture<Collection<User>> userFuture = new PlainActionFuture<>();
reservedRealm.users(userFuture);
final Collection<User> allReservedUsers = userFuture.actionGet();
Expand Down Expand Up @@ -284,12 +273,7 @@ public void testGetAllUsers() {
new AnonymousUser(settings),
threadPool
);
reservedRealm.initRealmRef(
Map.of(
new RealmConfig.RealmIdentifier(ReservedRealm.TYPE, ReservedRealm.NAME),
new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node")
)
);
reservedRealm.setRealmRef(new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node"));
TransportService transportService = new TransportService(
Settings.EMPTY,
mock(Transport.class),
Expand Down Expand Up @@ -390,12 +374,7 @@ public void testGetUsersWithProfileUidException() {
new AnonymousUser(settings),
threadPool
);
reservedRealm.initRealmRef(
Map.of(
new RealmConfig.RealmIdentifier(ReservedRealm.TYPE, ReservedRealm.NAME),
new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node")
)
);
reservedRealm.setRealmRef(new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node"));
TransportService transportService = new TransportService(
Settings.EMPTY,
mock(Transport.class),
Expand Down Expand Up @@ -445,12 +424,7 @@ private void testGetStoreOnlyUsers(List<User> storeUsers) {
NativeUsersStore usersStore = mock(NativeUsersStore.class);
AnonymousUser anonymousUser = new AnonymousUser(settings);
ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser, threadPool);
reservedRealm.initRealmRef(
Map.of(
new RealmConfig.RealmIdentifier(ReservedRealm.TYPE, ReservedRealm.NAME),
new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node")
)
);
reservedRealm.setRealmRef(new Authentication.RealmRef(ReservedRealm.NAME, ReservedRealm.TYPE, "node"));
TransportService transportService = new TransportService(
Settings.EMPTY,
mock(Transport.class),
Expand Down Expand Up @@ -596,16 +570,8 @@ private List<User> randomUsersWithInternalUsernames() {

private Realms mockRealms() {
final Realms realms = mock(Realms.class);
when(realms.getRealmRefs()).thenReturn(
Map.of(
new RealmConfig.RealmIdentifier(NativeRealmSettings.TYPE, NativeRealmSettings.DEFAULT_NAME),
new Authentication.RealmRef(
NativeRealmSettings.DEFAULT_NAME,
NativeRealmSettings.TYPE,
randomAlphaOfLengthBetween(3, 8),
null
)
)
when(realms.getNativeRealmRef()).thenReturn(
new Authentication.RealmRef(NativeRealmSettings.DEFAULT_NAME, NativeRealmSettings.TYPE, randomAlphaOfLengthBetween(3, 8), null)
);
return realms;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.elasticsearch.xpack.core.security.action.user.QueryUserRequest;
import org.elasticsearch.xpack.core.security.action.user.QueryUserResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.Subject;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.core.security.user.User;
Expand Down Expand Up @@ -305,16 +304,8 @@ private ProfileService mockProfileService(boolean throwException, boolean profil

private Realms mockRealms() {
final Realms realms = mock(Realms.class);
when(realms.getRealmRefs()).thenReturn(
Map.of(
new RealmConfig.RealmIdentifier(NativeRealmSettings.TYPE, NativeRealmSettings.DEFAULT_NAME),
new Authentication.RealmRef(
NativeRealmSettings.DEFAULT_NAME,
NativeRealmSettings.TYPE,
randomAlphaOfLengthBetween(3, 8),
null
)
)
when(realms.getNativeRealmRef()).thenReturn(
new Authentication.RealmRef(NativeRealmSettings.DEFAULT_NAME, NativeRealmSettings.TYPE, randomAlphaOfLengthBetween(3, 8), null)
);
return realms;
}
Expand Down
Loading