Skip to content

Commit

Permalink
Migrate custom role providers to licensed feature (#79127)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tvernum authored Oct 18, 2021
1 parent eea8b25 commit fceacfe
Show file tree
Hide file tree
Showing 8 changed files with 635 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public class XPackLicenseState {
*/
public enum Feature {
SECURITY_AUDITING(OperationMode.GOLD, false),
SECURITY_CUSTOM_ROLE_PROVIDERS(OperationMode.PLATINUM, true),
SECURITY_TOKEN_SERVICE(OperationMode.STANDARD, false),
SECURITY_AUTHORIZATION_REALM(OperationMode.PLATINUM, true),
SECURITY_AUTHORIZATION_ENGINE(OperationMode.PLATINUM, true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@

package org.elasticsearch.license;

import org.mockito.Mockito;

import java.util.function.Consumer;
import java.util.function.LongSupplier;

import static org.mockito.Mockito.doAnswer;

/** A license state that may be mocked by testing because the internal methods are made public */
public class MockLicenseState extends XPackLicenseState {

Expand All @@ -30,4 +35,18 @@ public void enableUsageTracking(LicensedFeature feature, String contextName) {
public void disableUsageTracking(LicensedFeature feature, String contextName) {
super.disableUsageTracking(feature, contextName);
}

public static MockLicenseState createMock() {
MockLicenseState mock = Mockito.mock(MockLicenseState.class);
Mockito.when(mock.copyCurrentLicenseState()).thenReturn(mock);
return mock;
}

public static void acceptListeners(MockLicenseState licenseState, Consumer<LicenseStateListener> addListener) {
doAnswer(inv -> {
final LicenseStateListener listener = (LicenseStateListener) inv.getArguments()[0];
addListener.accept(listener);
return null;
}).when(licenseState).addListener(Mockito.any());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,13 @@ public static OperationMode randomBasicStandardOrGold() {
public void testSecurityDefaults() {
XPackLicenseState licenseState = new XPackLicenseState(() -> 0);
assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(false));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

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

assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true));
assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false));
assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.authz.store.RoleProviders;
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
import org.elasticsearch.xpack.security.operator.FileOperatorUsersStore;
import org.elasticsearch.xpack.security.operator.OperatorOnlyRegistry;
Expand Down Expand Up @@ -318,6 +319,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -376,6 +378,10 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
public static final LicensedFeature.Persistent CUSTOM_REALMS_FEATURE =
LicensedFeature.persistentLenient(REALMS_FEATURE_FAMILY, "custom", License.OperationMode.PLATINUM);

// Custom role providers are Platinum+
public static final LicensedFeature.Persistent CUSTOM_ROLE_PROVIDERS_FEATURE =
LicensedFeature.persistent(null, "security-roles-provider", License.OperationMode.PLATINUM);

private static final Logger logger = LogManager.getLogger(Security.class);

public static final SystemIndexDescriptor SECURITY_MAIN_INDEX_DESCRIPTOR = getSecurityMainIndexDescriptor();
Expand Down Expand Up @@ -423,7 +429,6 @@ public Security(Settings settings, final Path configPath) {
this.bootstrapChecks.set(Collections.emptyList());
}
this.securityExtensions.addAll(extensions);

}

private static void runStartupChecks(Settings settings) {
Expand Down Expand Up @@ -548,9 +553,15 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
xContentRegistry);
final NativeRolesStore nativeRolesStore = new NativeRolesStore(settings, client, getLicenseState(), securityIndex.get());
final ReservedRolesStore reservedRolesStore = new ReservedRolesStore();
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> rolesProviders = new ArrayList<>();

final Map<String, List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>>> customRoleProviders = new LinkedHashMap<>();
for (SecurityExtension extension : securityExtensions) {
rolesProviders.addAll(extension.getRolesProviders(extensionComponents));
final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> providers = extension.getRolesProviders(
extensionComponents
);
if (providers != null && providers.isEmpty() == false) {
customRoleProviders.put(extension.toString(), providers);
}
}

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

final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
final RoleProviders roleProviders = new RoleProviders(
reservedRolesStore,
fileRolesStore,
nativeRolesStore,
customRoleProviders,
getLicenseState()
);
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, roleProviders,
privilegeStore, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
serviceAccountService, dlsBitsetCache.get(), expressionResolver,
new DeprecationRoleDescriptorConsumer(clusterService, threadPool));
securityIndex.get().addStateListener(allRolesStore::onSecurityIndexStateChange);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.license.XPackLicenseState.Feature;
import org.elasticsearch.xpack.core.common.IteratingActionListener;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
Expand Down Expand Up @@ -84,8 +83,8 @@
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed;

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

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

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

private final FileRolesStore fileRolesStore;
private final NativeRolesStore nativeRolesStore;
private final RoleProviders roleProviders;
private final NativePrivilegeStore privilegeStore;
private final XPackLicenseState licenseState;
private final Consumer<Collection<RoleDescriptor>> effectiveRoleDescriptorsConsumer;
Expand All @@ -114,25 +112,31 @@ public class CompositeRolesStore {
private final ApiKeyService apiKeyService;
private final ServiceAccountService serviceAccountService;
private final boolean isAnonymousEnabled;
private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> builtInRoleProviders;
private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> allRoleProviders;
private final Role superuserRole;
private final Role xpackUserRole;
private final Role asyncSearchUserRole;
private final Automaton restrictedIndicesAutomaton;

public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore,
ReservedRolesStore reservedRolesStore, NativePrivilegeStore privilegeStore,
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> rolesProviders,
public CompositeRolesStore(Settings settings, RoleProviders roleProviders, NativePrivilegeStore privilegeStore,
ThreadContext threadContext, XPackLicenseState licenseState, FieldPermissionsCache fieldPermissionsCache,
ApiKeyService apiKeyService, ServiceAccountService serviceAccountService,
DocumentSubsetBitsetCache dlsBitsetCache, IndexNameExpressionResolver resolver,
Consumer<Collection<RoleDescriptor>> effectiveRoleDescriptorsConsumer) {
this.fileRolesStore = Objects.requireNonNull(fileRolesStore);
this.dlsBitsetCache = Objects.requireNonNull(dlsBitsetCache);
fileRolesStore.addListener(this::invalidate);
this.nativeRolesStore = Objects.requireNonNull(nativeRolesStore);
this.roleProviders = roleProviders;
roleProviders.addChangeListener(new RoleProviders.ChangeListener() {
@Override
public void rolesChanged(Set<String> roles) {
CompositeRolesStore.this.invalidate(roles);
}

@Override
public void providersChanged() {
CompositeRolesStore.this.invalidateAll();
}
});

this.privilegeStore = Objects.requireNonNull(privilegeStore);
this.dlsBitsetCache = Objects.requireNonNull(dlsBitsetCache);
this.licenseState = Objects.requireNonNull(licenseState);
this.fieldPermissionsCache = Objects.requireNonNull(fieldPermissionsCache);
this.apiKeyService = Objects.requireNonNull(apiKeyService);
Expand All @@ -152,16 +156,6 @@ public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, Nat
nlcBuilder.setMaximumWeight(nlcCacheSize);
}
this.negativeLookupCache = nlcBuilder.build();
this.builtInRoleProviders = List.of(reservedRolesStore, fileRolesStore, nativeRolesStore);
if (rolesProviders.isEmpty()) {
this.allRoleProviders = this.builtInRoleProviders;
} else {
List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> allList =
new ArrayList<>(builtInRoleProviders.size() + rolesProviders.size());
allList.addAll(builtInRoleProviders);
allList.addAll(rolesProviders);
this.allRoleProviders = Collections.unmodifiableList(allList);
}
this.anonymousUser = new AnonymousUser(settings);
this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
this.restrictedIndicesAutomaton = resolver.getSystemNameAutomaton();
Expand Down Expand Up @@ -411,9 +405,7 @@ private void roleDescriptors(Set<String> roleNames, ActionListener<RolesRetrieva

private void loadRoleDescriptorsAsync(Set<String> roleNames, ActionListener<RolesRetrievalResult> listener) {
final RolesRetrievalResult rolesResult = new RolesRetrievalResult();
final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> asyncRoleProviders =
licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS) ? allRoleProviders : builtInRoleProviders;

final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> asyncRoleProviders = roleProviders.getProviders();
final ActionListener<RoleRetrievalResult> descriptorsListener =
ContextPreservingActionListener.wrapPreservingContext(ActionListener.wrap(ignore -> {
rolesResult.setMissingRoles(roleNames);
Expand Down Expand Up @@ -553,13 +545,12 @@ public void invalidate(Set<String> roles) {
}

public void usageStats(ActionListener<Map<String, Object>> listener) {
final Map<String, Object> usage = new HashMap<>(2);
usage.put("file", fileRolesStore.usageStats());
final Map<String, Object> usage = new HashMap<>();
usage.put("dls", Map.of("bit_set_cache", dlsBitsetCache.usageStats()));
nativeRolesStore.usageStats(ActionListener.wrap(map -> {
usage.put("native", map);
listener.onResponse(usage);
}, listener::onFailure));
roleProviders.usageStats(listener.map(roleUsage -> {
usage.putAll(roleUsage);
return usage;
}));
}

public void onSecurityIndexStateChange(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) {
Expand Down
Loading

0 comments on commit fceacfe

Please sign in to comment.