Skip to content

Commit a298645

Browse files
authored
Fix unique realm name check to cover default realms (#87999)
All enabled realms must have unique names. This PR tightened the logic for ensuring realm name uniqueness. Previously the unique name check does not cover realms that are automatically enabled. Relates: #69096
1 parent 46884f0 commit a298645

File tree

3 files changed

+187
-41
lines changed

3 files changed

+187
-41
lines changed

docs/changelog/87999.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 87999
2+
summary: Fix unique realm name check to cover default realms
3+
area: Authentication
4+
type: bug
5+
issues: []

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,11 @@ protected List<Realm> initRealms(List<RealmConfig> realmConfigs) throws Exceptio
272272

273273
checkUniqueOrders(orderToRealmName);
274274
Collections.sort(realms);
275+
ensureUniqueExplicitlyConfiguredRealmNames(nameToRealmIdentifier);
275276

276277
maybeAddBasicRealms(realms, realmConfigs);
277278
// always add built in first!
278-
realms.add(0, reservedRealm);
279-
String duplicateRealms = nameToRealmIdentifier.entrySet()
280-
.stream()
281-
.filter(entry -> entry.getValue().size() > 1)
282-
.map(entry -> entry.getKey() + ": " + entry.getValue())
283-
.collect(Collectors.joining("; "));
284-
if (Strings.hasText(duplicateRealms)) {
285-
throw new IllegalArgumentException("Found multiple realms configured with the same name: " + duplicateRealms + "");
286-
}
279+
addReservedRealm(realms);
287280
logDeprecationForReservedPrefixedRealmNames(reservedPrefixedRealmIdentifiers);
288281
return Collections.unmodifiableList(realms);
289282
}
@@ -372,8 +365,9 @@ protected void doClose() throws IOException {
372365
private void maybeAddBasicRealms(List<Realm> realms, List<RealmConfig> realmConfigs) throws Exception {
373366
final Set<String> disabledBasicRealmTypes = findDisabledBasicRealmTypes(realmConfigs);
374367
final Set<String> realmTypes = realms.stream().map(Realm::type).collect(Collectors.toUnmodifiableSet());
375-
// Add native realm first so that file realm will be in the beginning
368+
// Add native realm first so that file realm will be added before it
376369
if (false == disabledBasicRealmTypes.contains(NativeRealmSettings.TYPE) && false == realmTypes.contains(NativeRealmSettings.TYPE)) {
370+
ensureRealmNameIsAvailable(realms, NativeRealmSettings.DEFAULT_NAME);
377371
var nativeRealmId = new RealmConfig.RealmIdentifier(NativeRealmSettings.TYPE, NativeRealmSettings.DEFAULT_NAME);
378372
var realmConfig = new RealmConfig(
379373
nativeRealmId,
@@ -385,6 +379,7 @@ private void maybeAddBasicRealms(List<Realm> realms, List<RealmConfig> realmConf
385379
realms.add(0, factories.get(NativeRealmSettings.TYPE).create(realmConfig));
386380
}
387381
if (false == disabledBasicRealmTypes.contains(FileRealmSettings.TYPE) && false == realmTypes.contains(FileRealmSettings.TYPE)) {
382+
ensureRealmNameIsAvailable(realms, FileRealmSettings.DEFAULT_NAME);
388383
var fileRealmId = new RealmConfig.RealmIdentifier(FileRealmSettings.TYPE, FileRealmSettings.DEFAULT_NAME);
389384
var realmConfig = new RealmConfig(
390385
fileRealmId,
@@ -397,6 +392,29 @@ private void maybeAddBasicRealms(List<Realm> realms, List<RealmConfig> realmConf
397392
}
398393
}
399394

395+
private void addReservedRealm(List<Realm> realms) {
396+
ensureRealmNameIsAvailable(realms, ReservedRealm.NAME);
397+
realms.add(0, reservedRealm);
398+
}
399+
400+
/**
401+
* Check that the given realmName is not yet used by the given list of realms.
402+
*/
403+
private void ensureRealmNameIsAvailable(List<Realm> realms, String realmName) {
404+
assert realms.size() == realms.stream().map(Realm::name).collect(Collectors.toUnmodifiableSet()).size()
405+
: "existing realm names must be unique";
406+
final Realm misNamedRealm = realms.stream().filter(realm -> realmName.equals(realm.name())).findFirst().orElse(null);
407+
if (misNamedRealm != null) {
408+
throw new IllegalArgumentException(
409+
"Found realm configured with name clashing with the ["
410+
+ realmName
411+
+ "] realm: ["
412+
+ (RealmSettings.realmSettingPrefix(misNamedRealm.type()) + misNamedRealm.name())
413+
+ "]"
414+
);
415+
}
416+
}
417+
400418
private static Settings ensureOrderSetting(Settings settings, RealmConfig.RealmIdentifier realmIdentifier, int order) {
401419
String orderSettingKey = RealmSettings.realmSettingPrefix(realmIdentifier) + "order";
402420
return Settings.builder().put(settings).put(orderSettingKey, order).build();
@@ -413,6 +431,17 @@ private static void checkUniqueOrders(Map<Integer, Set<String>> orderToRealmName
413431
}
414432
}
415433

434+
private void ensureUniqueExplicitlyConfiguredRealmNames(Map<String, Set<String>> nameToRealmIdentifier) {
435+
String duplicateRealms = nameToRealmIdentifier.entrySet()
436+
.stream()
437+
.filter(entry -> entry.getValue().size() > 1)
438+
.map(entry -> entry.getKey() + ": " + entry.getValue())
439+
.collect(Collectors.joining("; "));
440+
if (Strings.hasText(duplicateRealms)) {
441+
throw new IllegalArgumentException("Found multiple realms configured with the same name: " + duplicateRealms + "");
442+
}
443+
}
444+
416445
private List<RealmConfig> buildRealmConfigs() {
417446
final Map<RealmConfig.RealmIdentifier, Settings> realmsSettings = RealmSettings.getRealmSettings(settings);
418447
final Set<String> internalTypes = new HashSet<>();

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java

Lines changed: 143 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.elasticsearch.common.util.CollectionUtils;
1818
import org.elasticsearch.common.util.concurrent.ThreadContext;
1919
import org.elasticsearch.common.util.set.Sets;
20+
import org.elasticsearch.core.Tuple;
2021
import org.elasticsearch.env.Environment;
2122
import org.elasticsearch.env.TestEnvironment;
2223
import org.elasticsearch.license.License;
@@ -125,16 +126,7 @@ public void init() throws Exception {
125126

126127
threadContext = new ThreadContext(Settings.EMPTY);
127128

128-
ThreadPool threadPool = mock(ThreadPool.class);
129-
when(threadPool.getThreadContext()).thenReturn(threadContext);
130-
131-
reservedRealm = new ReservedRealm(
132-
mock(Environment.class),
133-
Settings.EMPTY,
134-
mock(NativeUsersStore.class),
135-
new AnonymousUser(Settings.EMPTY),
136-
threadPool
137-
);
129+
reservedRealm = buildReservedRealm();
138130
allowAllRealms();
139131
}
140132

@@ -408,27 +400,6 @@ public void testRealmAssignedToMultipleDomainsNotPermitted() {
408400
);
409401
}
410402

411-
public void testReservedRealmIsAlwaysDomainless() throws Exception {
412-
int realmId = randomIntBetween(0, randomRealmTypesCount - 1);
413-
Settings settings = Settings.builder()
414-
.put("xpack.security.authc.realms.type_" + realmId + ".reserved.order", 2)
415-
.put("xpack.security.authc.domains.domain_reserved.realms", "reserved")
416-
.put("xpack.security.authc.realms." + NativeRealmSettings.TYPE + ".disabled_native.enabled", false)
417-
.put("xpack.security.authc.realms." + FileRealmSettings.TYPE + ".disabled_file.enabled", false)
418-
.put("path.home", createTempDir())
419-
.build();
420-
Environment env = TestEnvironment.newEnvironment(settings);
421-
Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm);
422-
Iterator<Realm> iterator = realms.iterator();
423-
assertThat(iterator.hasNext(), is(true));
424-
Realm realm = iterator.next();
425-
assertThat(realm, is(reservedRealm));
426-
realm = iterator.next();
427-
assertThat(realm.type(), is("type_" + realmId));
428-
assertThat(realm.name(), is("reserved"));
429-
assertThat(realm.realmRef().getDomain().name(), is("domain_reserved"));
430-
}
431-
432403
public void testDomainWithUndefinedRealms() {
433404
Settings settings = Settings.builder()
434405
.put("xpack.security.authc.domains.test_domain.realms", "reserved, default_file")
@@ -1097,6 +1068,147 @@ public void testWarningsForReservedPrefixedRealmNames() throws Exception {
10971068
);
10981069
}
10991070

1071+
public void testExplicitlyConfiguredRealmNamesMustNotClashWithDefaultRealmNames() throws Exception {
1072+
final Tuple<String, String> defaultRealm = randomFrom(
1073+
new Tuple<>("reserved", "reserved"),
1074+
new Tuple<>("file", "default_file"),
1075+
new Tuple<>("native", "default_native")
1076+
);
1077+
1078+
final String configuredRealmType = randomValueOtherThan(defaultRealm.v1(), () -> randomAlphaOfLengthBetween(3, 18));
1079+
factories.put(configuredRealmType, DummyRealm::new);
1080+
final String realmSettingKey = "xpack.security.authc.realms." + configuredRealmType + "." + defaultRealm.v2();
1081+
1082+
// The name of an explicitly configured realm must not be either default_native, default_file or "reserved"
1083+
final Settings settings1 = Settings.builder()
1084+
.put("path.home", createTempDir())
1085+
.put(realmSettingKey + ".order", randomInt())
1086+
.build();
1087+
final IllegalArgumentException e1 = expectThrows(
1088+
IllegalArgumentException.class,
1089+
() -> new Realms(
1090+
settings1,
1091+
TestEnvironment.newEnvironment(settings1),
1092+
factories,
1093+
licenseState,
1094+
threadContext,
1095+
buildReservedRealm()
1096+
)
1097+
);
1098+
assertThat(
1099+
e1.getMessage(),
1100+
containsString(
1101+
"Found realm configured with name clashing with the [" + defaultRealm.v2() + "] realm: [" + realmSettingKey + "]"
1102+
)
1103+
);
1104+
1105+
// It is OK if the explicitly configured realm is disabled
1106+
final Settings settings2 = Settings.builder()
1107+
.put("path.home", createTempDir())
1108+
.put(realmSettingKey + ".order", randomInt())
1109+
.put(realmSettingKey + ".enabled", false)
1110+
.build();
1111+
final Realms realms2 = new Realms(
1112+
settings2,
1113+
TestEnvironment.newEnvironment(settings2),
1114+
factories,
1115+
licenseState,
1116+
threadContext,
1117+
buildReservedRealm()
1118+
);
1119+
assertThat(realms2.getActiveRealms().stream().map(Realm::type).toList(), containsInAnyOrder("reserved", "file", "native"));
1120+
1121+
// This block of tests do not apply to reserved realm since it is not configurable
1122+
if ("file".equals(defaultRealm.v1()) || "native".equals(defaultRealm.v1())) {
1123+
final String autoEnabledRealmType = "file".equals(defaultRealm.v1()) ? "native" : "file";
1124+
1125+
// It is OK if the default file or native realm is disabled
1126+
final Settings settings3 = Settings.builder()
1127+
.put("path.home", createTempDir())
1128+
.put(realmSettingKey + ".order", randomInt())
1129+
.put("xpack.security.authc.realms." + defaultRealm.v1() + "." + randomAlphaOfLengthBetween(3, 18) + ".enabled", false)
1130+
.build();
1131+
final Realms realms3 = new Realms(
1132+
settings3,
1133+
TestEnvironment.newEnvironment(settings3),
1134+
factories,
1135+
licenseState,
1136+
threadContext,
1137+
buildReservedRealm()
1138+
);
1139+
assertThat(
1140+
realms3.getActiveRealms().stream().map(Realm::type).toList(),
1141+
containsInAnyOrder("reserved", configuredRealmType, autoEnabledRealmType)
1142+
);
1143+
1144+
// It is OK if file or native realm is named something else
1145+
final Settings settings4 = Settings.builder()
1146+
.put("path.home", createTempDir())
1147+
.put(realmSettingKey + ".order", randomInt())
1148+
.put("xpack.security.authc.realms." + defaultRealm.v1() + "." + randomAlphaOfLengthBetween(3, 18) + ".order", randomInt())
1149+
.build();
1150+
final Realms realms4 = new Realms(
1151+
settings4,
1152+
TestEnvironment.newEnvironment(settings4),
1153+
factories,
1154+
licenseState,
1155+
threadContext,
1156+
buildReservedRealm()
1157+
);
1158+
assertThat(
1159+
realms4.getActiveRealms().stream().map(Realm::type).toList(),
1160+
containsInAnyOrder("reserved", configuredRealmType, "file", "native")
1161+
);
1162+
1163+
// All explicitly configured realm names still must be unique
1164+
final Settings settings5 = Settings.builder()
1165+
.put("path.home", createTempDir())
1166+
.put(realmSettingKey + ".order", randomInt())
1167+
.put("xpack.security.authc.realms." + defaultRealm.v1() + "." + defaultRealm.v2() + ".order", randomInt())
1168+
.build();
1169+
final IllegalArgumentException e5 = expectThrows(
1170+
IllegalArgumentException.class,
1171+
() -> new Realms(
1172+
settings5,
1173+
TestEnvironment.newEnvironment(settings5),
1174+
factories,
1175+
licenseState,
1176+
threadContext,
1177+
buildReservedRealm()
1178+
)
1179+
);
1180+
assertThat(e5.getMessage(), containsString("Found multiple realms configured with the same name"));
1181+
}
1182+
1183+
// It is OK to explicitly configure file to be default_file and native to be default_native
1184+
final Settings settings6 = Settings.builder()
1185+
.put("path.home", createTempDir())
1186+
.put("xpack.security.authc.realms.file.default_file.order", randomInt())
1187+
.put("xpack.security.authc.realms.native.default_native.order", randomInt())
1188+
.build();
1189+
final Realms realms6 = new Realms(
1190+
settings6,
1191+
TestEnvironment.newEnvironment(settings6),
1192+
factories,
1193+
licenseState,
1194+
threadContext,
1195+
buildReservedRealm()
1196+
);
1197+
assertThat(realms6.getActiveRealms().stream().map(Realm::type).toList(), containsInAnyOrder("reserved", "file", "native"));
1198+
}
1199+
1200+
private ReservedRealm buildReservedRealm() {
1201+
final ThreadPool threadPool = mock(ThreadPool.class);
1202+
when(threadPool.getThreadContext()).thenReturn(threadContext);
1203+
return new ReservedRealm(
1204+
mock(Environment.class),
1205+
Settings.EMPTY,
1206+
mock(NativeUsersStore.class),
1207+
new AnonymousUser(Settings.EMPTY),
1208+
threadPool
1209+
);
1210+
}
1211+
11001212
private boolean randomDisableRealm(Settings.Builder builder, String type) {
11011213
final boolean disabled = randomBoolean();
11021214
if (disabled) {

0 commit comments

Comments
 (0)