Skip to content

Commit fceacfe

Browse files
authored
Migrate custom role providers to licensed feature (#79127)
This changes the license checking for custom role providers to use the new `LicensedFeature` model with feature tracking. To support this, and reduce code complexity the logic for managing role providers has been moved out of the `CompositeRolesStore` into a new `RoleProviders` test.
1 parent eea8b25 commit fceacfe

File tree

8 files changed

+635
-193
lines changed

8 files changed

+635
-193
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public class XPackLicenseState {
4242
*/
4343
public enum Feature {
4444
SECURITY_AUDITING(OperationMode.GOLD, false),
45-
SECURITY_CUSTOM_ROLE_PROVIDERS(OperationMode.PLATINUM, true),
4645
SECURITY_TOKEN_SERVICE(OperationMode.STANDARD, false),
4746
SECURITY_AUTHORIZATION_REALM(OperationMode.PLATINUM, true),
4847
SECURITY_AUTHORIZATION_ENGINE(OperationMode.PLATINUM, true),

x-pack/plugin/core/src/test/java/org/elasticsearch/license/MockLicenseState.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77

88
package org.elasticsearch.license;
99

10+
import org.mockito.Mockito;
11+
12+
import java.util.function.Consumer;
1013
import java.util.function.LongSupplier;
1114

15+
import static org.mockito.Mockito.doAnswer;
16+
1217
/** A license state that may be mocked by testing because the internal methods are made public */
1318
public class MockLicenseState extends XPackLicenseState {
1419

@@ -30,4 +35,18 @@ public void enableUsageTracking(LicensedFeature feature, String contextName) {
3035
public void disableUsageTracking(LicensedFeature feature, String contextName) {
3136
super.disableUsageTracking(feature, contextName);
3237
}
38+
39+
public static MockLicenseState createMock() {
40+
MockLicenseState mock = Mockito.mock(MockLicenseState.class);
41+
Mockito.when(mock.copyCurrentLicenseState()).thenReturn(mock);
42+
return mock;
43+
}
44+
45+
public static void acceptListeners(MockLicenseState licenseState, Consumer<LicenseStateListener> addListener) {
46+
doAnswer(inv -> {
47+
final LicenseStateListener listener = (LicenseStateListener) inv.getArguments()[0];
48+
addListener.accept(listener);
49+
return null;
50+
}).when(licenseState).addListener(Mockito.any());
51+
}
3352
}

x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,13 @@ public static OperationMode randomBasicStandardOrGold() {
8989
public void testSecurityDefaults() {
9090
XPackLicenseState licenseState = new XPackLicenseState(() -> 0);
9191
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
92-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true));
9392
}
9493

9594
public void testSecurityStandard() {
9695
XPackLicenseState licenseState = new XPackLicenseState(() -> 0);
9796
licenseState.update(STANDARD, true, null);
9897

9998
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
100-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
10199
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
102100
}
103101

@@ -106,7 +104,6 @@ public void testSecurityStandardExpired() {
106104
licenseState.update(STANDARD, false, null);
107105

108106
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
109-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
110107
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
111108
}
112109

@@ -115,7 +112,6 @@ public void testSecurityBasic() {
115112
licenseState.update(BASIC, true, null);
116113

117114
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
118-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
119115
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(false));
120116
}
121117

@@ -124,7 +120,6 @@ public void testSecurityGold() {
124120
licenseState.update(GOLD, true, null);
125121

126122
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
127-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
128123
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
129124
}
130125

@@ -133,7 +128,6 @@ public void testSecurityGoldExpired() {
133128
licenseState.update(GOLD, false, null);
134129

135130
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
136-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
137131
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
138132
}
139133

@@ -142,7 +136,6 @@ public void testSecurityPlatinum() {
142136
licenseState.update(PLATINUM, true, null);
143137

144138
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
145-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true));
146139
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
147140
}
148141

@@ -151,7 +144,6 @@ public void testSecurityPlatinumExpired() {
151144
licenseState.update(PLATINUM, false, null);
152145

153146
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
154-
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
155147
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
156148
}
157149

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@
245245
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
246246
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
247247
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
248+
import org.elasticsearch.xpack.security.authz.store.RoleProviders;
248249
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
249250
import org.elasticsearch.xpack.security.operator.FileOperatorUsersStore;
250251
import org.elasticsearch.xpack.security.operator.OperatorOnlyRegistry;
@@ -318,6 +319,7 @@
318319
import java.util.Collections;
319320
import java.util.HashMap;
320321
import java.util.HashSet;
322+
import java.util.LinkedHashMap;
321323
import java.util.List;
322324
import java.util.Locale;
323325
import java.util.Map;
@@ -376,6 +378,10 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
376378
public static final LicensedFeature.Persistent CUSTOM_REALMS_FEATURE =
377379
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "custom", License.OperationMode.PLATINUM);
378380

381+
// Custom role providers are Platinum+
382+
public static final LicensedFeature.Persistent CUSTOM_ROLE_PROVIDERS_FEATURE =
383+
LicensedFeature.persistent(null, "security-roles-provider", License.OperationMode.PLATINUM);
384+
379385
private static final Logger logger = LogManager.getLogger(Security.class);
380386

381387
public static final SystemIndexDescriptor SECURITY_MAIN_INDEX_DESCRIPTOR = getSecurityMainIndexDescriptor();
@@ -423,7 +429,6 @@ public Security(Settings settings, final Path configPath) {
423429
this.bootstrapChecks.set(Collections.emptyList());
424430
}
425431
this.securityExtensions.addAll(extensions);
426-
427432
}
428433

429434
private static void runStartupChecks(Settings settings) {
@@ -548,9 +553,15 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
548553
xContentRegistry);
549554
final NativeRolesStore nativeRolesStore = new NativeRolesStore(settings, client, getLicenseState(), securityIndex.get());
550555
final ReservedRolesStore reservedRolesStore = new ReservedRolesStore();
551-
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> rolesProviders = new ArrayList<>();
556+
557+
final Map<String, List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>>> customRoleProviders = new LinkedHashMap<>();
552558
for (SecurityExtension extension : securityExtensions) {
553-
rolesProviders.addAll(extension.getRolesProviders(extensionComponents));
559+
final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> providers = extension.getRolesProviders(
560+
extensionComponents
561+
);
562+
if (providers != null && providers.isEmpty() == false) {
563+
customRoleProviders.put(extension.toString(), providers);
564+
}
554565
}
555566

556567
final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex.get(),
@@ -572,8 +583,15 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
572583
);
573584
components.add(serviceAccountService);
574585

575-
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
576-
privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
586+
final RoleProviders roleProviders = new RoleProviders(
587+
reservedRolesStore,
588+
fileRolesStore,
589+
nativeRolesStore,
590+
customRoleProviders,
591+
getLicenseState()
592+
);
593+
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, roleProviders,
594+
privilegeStore, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
577595
serviceAccountService, dlsBitsetCache.get(), expressionResolver,
578596
new DeprecationRoleDescriptorConsumer(clusterService, threadPool));
579597
securityIndex.get().addStateListener(allRolesStore::onSecurityIndexStateChange);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.elasticsearch.common.util.concurrent.ThreadContext;
3131
import org.elasticsearch.common.util.set.Sets;
3232
import org.elasticsearch.license.XPackLicenseState;
33-
import org.elasticsearch.license.XPackLicenseState.Feature;
3433
import org.elasticsearch.xpack.core.common.IteratingActionListener;
3534
import org.elasticsearch.xpack.core.security.authc.Authentication;
3635
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
@@ -84,8 +83,8 @@
8483
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed;
8584

8685
/**
87-
* A composite roles store that combines built in roles, file-based roles, and index-based roles. Checks the built in roles first, then the
88-
* file roles, and finally the index roles.
86+
* A composite roles store that can retrieve roles from multiple sources.
87+
* @see RoleProviders
8988
*/
9089
public class CompositeRolesStore {
9190

@@ -98,8 +97,7 @@ public class CompositeRolesStore {
9897

9998
private final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(CompositeRolesStore.class);
10099

101-
private final FileRolesStore fileRolesStore;
102-
private final NativeRolesStore nativeRolesStore;
100+
private final RoleProviders roleProviders;
103101
private final NativePrivilegeStore privilegeStore;
104102
private final XPackLicenseState licenseState;
105103
private final Consumer<Collection<RoleDescriptor>> effectiveRoleDescriptorsConsumer;
@@ -114,25 +112,31 @@ public class CompositeRolesStore {
114112
private final ApiKeyService apiKeyService;
115113
private final ServiceAccountService serviceAccountService;
116114
private final boolean isAnonymousEnabled;
117-
private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> builtInRoleProviders;
118-
private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> allRoleProviders;
119115
private final Role superuserRole;
120116
private final Role xpackUserRole;
121117
private final Role asyncSearchUserRole;
122118
private final Automaton restrictedIndicesAutomaton;
123119

124-
public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore,
125-
ReservedRolesStore reservedRolesStore, NativePrivilegeStore privilegeStore,
126-
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> rolesProviders,
120+
public CompositeRolesStore(Settings settings, RoleProviders roleProviders, NativePrivilegeStore privilegeStore,
127121
ThreadContext threadContext, XPackLicenseState licenseState, FieldPermissionsCache fieldPermissionsCache,
128122
ApiKeyService apiKeyService, ServiceAccountService serviceAccountService,
129123
DocumentSubsetBitsetCache dlsBitsetCache, IndexNameExpressionResolver resolver,
130124
Consumer<Collection<RoleDescriptor>> effectiveRoleDescriptorsConsumer) {
131-
this.fileRolesStore = Objects.requireNonNull(fileRolesStore);
132-
this.dlsBitsetCache = Objects.requireNonNull(dlsBitsetCache);
133-
fileRolesStore.addListener(this::invalidate);
134-
this.nativeRolesStore = Objects.requireNonNull(nativeRolesStore);
125+
this.roleProviders = roleProviders;
126+
roleProviders.addChangeListener(new RoleProviders.ChangeListener() {
127+
@Override
128+
public void rolesChanged(Set<String> roles) {
129+
CompositeRolesStore.this.invalidate(roles);
130+
}
131+
132+
@Override
133+
public void providersChanged() {
134+
CompositeRolesStore.this.invalidateAll();
135+
}
136+
});
137+
135138
this.privilegeStore = Objects.requireNonNull(privilegeStore);
139+
this.dlsBitsetCache = Objects.requireNonNull(dlsBitsetCache);
136140
this.licenseState = Objects.requireNonNull(licenseState);
137141
this.fieldPermissionsCache = Objects.requireNonNull(fieldPermissionsCache);
138142
this.apiKeyService = Objects.requireNonNull(apiKeyService);
@@ -152,16 +156,6 @@ public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, Nat
152156
nlcBuilder.setMaximumWeight(nlcCacheSize);
153157
}
154158
this.negativeLookupCache = nlcBuilder.build();
155-
this.builtInRoleProviders = List.of(reservedRolesStore, fileRolesStore, nativeRolesStore);
156-
if (rolesProviders.isEmpty()) {
157-
this.allRoleProviders = this.builtInRoleProviders;
158-
} else {
159-
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> allList =
160-
new ArrayList<>(builtInRoleProviders.size() + rolesProviders.size());
161-
allList.addAll(builtInRoleProviders);
162-
allList.addAll(rolesProviders);
163-
this.allRoleProviders = Collections.unmodifiableList(allList);
164-
}
165159
this.anonymousUser = new AnonymousUser(settings);
166160
this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
167161
this.restrictedIndicesAutomaton = resolver.getSystemNameAutomaton();
@@ -411,9 +405,7 @@ private void roleDescriptors(Set<String> roleNames, ActionListener<RolesRetrieva
411405

412406
private void loadRoleDescriptorsAsync(Set<String> roleNames, ActionListener<RolesRetrievalResult> listener) {
413407
final RolesRetrievalResult rolesResult = new RolesRetrievalResult();
414-
final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> asyncRoleProviders =
415-
licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS) ? allRoleProviders : builtInRoleProviders;
416-
408+
final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> asyncRoleProviders = roleProviders.getProviders();
417409
final ActionListener<RoleRetrievalResult> descriptorsListener =
418410
ContextPreservingActionListener.wrapPreservingContext(ActionListener.wrap(ignore -> {
419411
rolesResult.setMissingRoles(roleNames);
@@ -553,13 +545,12 @@ public void invalidate(Set<String> roles) {
553545
}
554546

555547
public void usageStats(ActionListener<Map<String, Object>> listener) {
556-
final Map<String, Object> usage = new HashMap<>(2);
557-
usage.put("file", fileRolesStore.usageStats());
548+
final Map<String, Object> usage = new HashMap<>();
558549
usage.put("dls", Map.of("bit_set_cache", dlsBitsetCache.usageStats()));
559-
nativeRolesStore.usageStats(ActionListener.wrap(map -> {
560-
usage.put("native", map);
561-
listener.onResponse(usage);
562-
}, listener::onFailure));
550+
roleProviders.usageStats(listener.map(roleUsage -> {
551+
usage.putAll(roleUsage);
552+
return usage;
553+
}));
563554
}
564555

565556
public void onSecurityIndexStateChange(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) {

0 commit comments

Comments
 (0)