|
@@ -15,6 +15,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
|
|
|
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
|
|
import org.elasticsearch.cluster.metadata.MetaData;
|
|
|
+import org.elasticsearch.common.Nullable;
|
|
|
import org.elasticsearch.common.bytes.BytesReference;
|
|
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
@@ -53,6 +54,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
|
|
|
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
|
|
|
import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
|
|
|
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
|
|
+import org.elasticsearch.xpack.core.security.support.MetadataUtils;
|
|
|
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
|
|
|
import org.elasticsearch.xpack.core.security.user.SystemUser;
|
|
|
import org.elasticsearch.xpack.core.security.user.User;
|
|
@@ -64,10 +66,14 @@ import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.time.Instant;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.Collection;
|
|
|
import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.HashSet;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
|
import java.util.concurrent.ExecutionException;
|
|
@@ -84,6 +90,7 @@ import static org.hamcrest.Matchers.anyOf;
|
|
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
|
import static org.hamcrest.Matchers.hasItem;
|
|
|
+import static org.hamcrest.Matchers.hasSize;
|
|
|
import static org.hamcrest.Matchers.is;
|
|
|
import static org.hamcrest.Matchers.nullValue;
|
|
|
import static org.mockito.Matchers.any;
|
|
@@ -143,21 +150,14 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
}, null);
|
|
|
FileRolesStore fileRolesStore = mock(FileRolesStore.class);
|
|
|
doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
- ReservedRolesStore reservedRolesStore = mock(ReservedRolesStore.class);
|
|
|
- doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
- NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
|
|
|
- doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
|
|
|
final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
|
|
|
- final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
- CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore,
|
|
|
- reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(),
|
|
|
- new ThreadContext(Settings.EMPTY), licenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
|
|
|
- rds -> effectiveRoleDescriptors.set(rds));
|
|
|
+ CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(Settings.EMPTY, fileRolesStore, null,
|
|
|
+ null, null, licenseState, null, null, rds -> effectiveRoleDescriptors.set(rds));
|
|
|
|
|
|
PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
|
|
|
compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
|
|
@@ -220,20 +220,13 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
}, null);
|
|
|
FileRolesStore fileRolesStore = mock(FileRolesStore.class);
|
|
|
doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
- ReservedRolesStore reservedRolesStore = mock(ReservedRolesStore.class);
|
|
|
- doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
- NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
|
|
|
- doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
|
|
|
when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
|
|
|
final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
|
|
|
- final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
- CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore,
|
|
|
- reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(),
|
|
|
- new ThreadContext(Settings.EMPTY), licenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
|
|
|
- rds -> effectiveRoleDescriptors.set(rds));
|
|
|
+ CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(Settings.EMPTY, fileRolesStore, null,
|
|
|
+ null, null, licenseState, null, null, rds -> effectiveRoleDescriptors.set(rds));
|
|
|
|
|
|
PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
|
|
|
compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
|
|
@@ -266,6 +259,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
|
|
|
doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
|
|
|
+
|
|
|
doAnswer((invocationOnMock) -> {
|
|
|
ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
|
|
|
callback.onResponse(RoleRetrievalResult.success(Collections.emptySet()));
|
|
@@ -281,12 +275,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
}).when(nativePrivilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
|
|
|
|
|
|
final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
|
|
|
- final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
- final CompositeRolesStore compositeRolesStore =
|
|
|
- new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
|
|
- nativePrivilegeStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
|
|
|
- new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class),
|
|
|
- documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
|
|
|
+ final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS,
|
|
|
+ fileRolesStore, nativeRolesStore, reservedRolesStore, nativePrivilegeStore, null, null, null,
|
|
|
+ rds -> effectiveRoleDescriptors.set(rds));
|
|
|
verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
|
|
|
|
|
|
final String roleName = randomAlphaOfLengthBetween(1, 10);
|
|
@@ -322,7 +313,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
if (getSuperuserRole && numberOfTimesToCall > 0) {
|
|
|
// the superuser role was requested so we get the role descriptors again
|
|
|
verify(reservedRolesStore, times(2)).accept(anySetOf(String.class), any(ActionListener.class));
|
|
|
- verify(nativePrivilegeStore).getPrivileges(isA(Set.class),isA(Set.class), any(ActionListener.class));
|
|
|
+ verify(nativePrivilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
|
|
|
}
|
|
|
verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore, nativePrivilegeStore);
|
|
|
}
|
|
@@ -422,9 +413,6 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore);
|
|
|
}
|
|
|
|
|
|
- private DocumentSubsetBitsetCache buildBitsetCache() {
|
|
|
- return new DocumentSubsetBitsetCache(Settings.EMPTY, mock(ThreadPool.class));
|
|
|
- }
|
|
|
|
|
|
public void testCustomRolesProviders() {
|
|
|
final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
|
|
@@ -899,12 +887,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
}).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
|
|
|
final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
|
|
|
|
|
|
- final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
- final CompositeRolesStore compositeRolesStore =
|
|
|
- new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
|
|
- mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
|
|
|
- new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
|
|
|
- rds -> {});
|
|
|
+ final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore,
|
|
|
+ nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), null, mock(ApiKeyService.class),
|
|
|
+ null, null);
|
|
|
verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
|
|
|
|
|
|
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
|
@@ -940,11 +925,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
}).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
|
|
|
final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
|
|
|
|
|
|
- final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
- final CompositeRolesStore compositeRolesStore =
|
|
|
- new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
|
|
- mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(settings),
|
|
|
- new XPackLicenseState(settings), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {});
|
|
|
+ final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(settings, fileRolesStore, nativeRolesStore,
|
|
|
+ reservedRolesStore, mock(NativePrivilegeStore.class), null, mock(ApiKeyService.class), null, null);
|
|
|
verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
|
|
|
|
|
|
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
|
@@ -1124,11 +1106,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
|
|
|
final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
|
|
|
|
|
|
- final CompositeRolesStore compositeRolesStore =
|
|
|
- new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
|
|
- mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
|
|
|
- new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {
|
|
|
- });
|
|
|
+ final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(
|
|
|
+ SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, null, null, mock(ApiKeyService.class),
|
|
|
+ documentSubsetBitsetCache, null);
|
|
|
|
|
|
PlainActionFuture<Map<String, Object>> usageStatsListener = new PlainActionFuture<>();
|
|
|
compositeRolesStore.usageStats(usageStatsListener);
|
|
@@ -1138,6 +1118,111 @@ public class CompositeRolesStoreTests extends ESTestCase {
|
|
|
assertThat(usageStats.get("dls"), is(Map.of("bit_set_cache", documentSubsetBitsetCache.usageStats())));
|
|
|
}
|
|
|
|
|
|
+ public void testLoggingOfDeprecatedRoles() {
|
|
|
+ List<RoleDescriptor> descriptors = new ArrayList<>();
|
|
|
+ Function<Map<String, Object>, RoleDescriptor> newRole = metadata -> new RoleDescriptor(
|
|
|
+ randomAlphaOfLengthBetween(4, 9), generateRandomStringArray(5, 5, false, true),
|
|
|
+ null, null, null, null, metadata, null);
|
|
|
+
|
|
|
+ RoleDescriptor deprecated1 = newRole.apply(MetadataUtils.getDeprecatedReservedMetadata("some reason"));
|
|
|
+ RoleDescriptor deprecated2 = newRole.apply(MetadataUtils.getDeprecatedReservedMetadata("a different reason"));
|
|
|
+
|
|
|
+ // Can't use getDeprecatedReservedMetadata because `Map.of` doesn't accept null values,
|
|
|
+ // so we clone metadata with a real value and then remove that key
|
|
|
+ final Map<String, Object> nullReasonMetadata = new HashMap<>(deprecated2.getMetadata());
|
|
|
+ nullReasonMetadata.remove(MetadataUtils.DEPRECATED_REASON_METADATA_KEY);
|
|
|
+ assertThat(nullReasonMetadata.keySet(), hasSize(deprecated2.getMetadata().size() -1));
|
|
|
+ RoleDescriptor deprecated3 = newRole.apply(nullReasonMetadata);
|
|
|
+
|
|
|
+ descriptors.add(deprecated1);
|
|
|
+ descriptors.add(deprecated2);
|
|
|
+ descriptors.add(deprecated3);
|
|
|
+
|
|
|
+ for (int i = randomIntBetween(2, 10); i > 0; i--) {
|
|
|
+ // the non-deprecated metadata is randomly one of:
|
|
|
+ // {}, {_deprecated:null}, {_deprecated:false},
|
|
|
+ // {_reserved:true}, {_reserved:true,_deprecated:null}, {_reserved:true,_deprecated:false}
|
|
|
+ Map<String, Object> metadata = randomBoolean() ? Map.of() : MetadataUtils.DEFAULT_RESERVED_METADATA;
|
|
|
+ if (randomBoolean()) {
|
|
|
+ metadata = new HashMap<>(metadata);
|
|
|
+ metadata.put(MetadataUtils.DEPRECATED_METADATA_KEY, randomBoolean() ? null : false);
|
|
|
+ }
|
|
|
+ descriptors.add(newRole.apply(metadata));
|
|
|
+ }
|
|
|
+ Collections.shuffle(descriptors, random());
|
|
|
+
|
|
|
+ final CompositeRolesStore compositeRolesStore =
|
|
|
+ buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, null, null, null, null, null, null, null, null);
|
|
|
+
|
|
|
+ // Use a LHS so that the random-shufle-order of the list is preserved
|
|
|
+ compositeRolesStore.logDeprecatedRoles(new LinkedHashSet<>(descriptors));
|
|
|
+
|
|
|
+ assertWarnings(
|
|
|
+ "The role [" + deprecated1.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
|
|
|
+ " some reason",
|
|
|
+ "The role [" + deprecated2.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
|
|
|
+ " a different reason",
|
|
|
+ "The role [" + deprecated3.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
|
|
|
+ " Please check the documentation"
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private CompositeRolesStore buildCompositeRolesStore(Settings settings,
|
|
|
+ @Nullable FileRolesStore fileRolesStore,
|
|
|
+ @Nullable NativeRolesStore nativeRolesStore,
|
|
|
+ @Nullable ReservedRolesStore reservedRolesStore,
|
|
|
+ @Nullable NativePrivilegeStore privilegeStore,
|
|
|
+ @Nullable XPackLicenseState licenseState,
|
|
|
+ @Nullable ApiKeyService apiKeyService,
|
|
|
+ @Nullable DocumentSubsetBitsetCache documentSubsetBitsetCache,
|
|
|
+ @Nullable Consumer<Collection<RoleDescriptor>> roleConsumer) {
|
|
|
+ if (fileRolesStore == null) {
|
|
|
+ fileRolesStore = mock(FileRolesStore.class);
|
|
|
+ doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
+ when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
|
|
|
+ }
|
|
|
+ if (nativeRolesStore == null) {
|
|
|
+ nativeRolesStore = mock(NativeRolesStore.class);
|
|
|
+ doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
+ doAnswer((invocationOnMock) -> {
|
|
|
+ ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
|
|
|
+ callback.onResponse(RoleRetrievalResult.failure(new RuntimeException("intentionally failed!")));
|
|
|
+ return null;
|
|
|
+ }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
|
|
|
+ }
|
|
|
+ if (reservedRolesStore == null) {
|
|
|
+ reservedRolesStore = mock(ReservedRolesStore.class);
|
|
|
+ doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
|
|
|
+ }
|
|
|
+ if (privilegeStore == null) {
|
|
|
+ privilegeStore = mock(NativePrivilegeStore.class);
|
|
|
+ doAnswer((invocationOnMock) -> {
|
|
|
+ ActionListener<Collection<ApplicationPrivilegeDescriptor>> callback = null;
|
|
|
+ callback = (ActionListener<Collection<ApplicationPrivilegeDescriptor>>) invocationOnMock.getArguments()[2];
|
|
|
+ callback.onResponse(Collections.emptyList());
|
|
|
+ return null;
|
|
|
+ }).when(privilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
|
|
|
+ }
|
|
|
+ if (licenseState == null) {
|
|
|
+ licenseState = new XPackLicenseState(settings);
|
|
|
+ }
|
|
|
+ if (apiKeyService == null) {
|
|
|
+ apiKeyService = mock(ApiKeyService.class);
|
|
|
+ }
|
|
|
+ if (documentSubsetBitsetCache == null) {
|
|
|
+ documentSubsetBitsetCache = buildBitsetCache();
|
|
|
+ }
|
|
|
+ if (roleConsumer == null) {
|
|
|
+ roleConsumer = rds -> { };
|
|
|
+ }
|
|
|
+ return new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, privilegeStore,
|
|
|
+ Collections.emptyList(), new ThreadContext(settings), licenseState, cache, apiKeyService, documentSubsetBitsetCache,
|
|
|
+ roleConsumer);
|
|
|
+ }
|
|
|
+
|
|
|
+ private DocumentSubsetBitsetCache buildBitsetCache() {
|
|
|
+ return new DocumentSubsetBitsetCache(Settings.EMPTY, mock(ThreadPool.class));
|
|
|
+ }
|
|
|
private static class InMemoryRolesProvider implements BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>> {
|
|
|
private final Function<Set<String>, RoleRetrievalResult> roleDescriptorsFunc;
|
|
|
|