diff --git a/plugin/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/plugin/src/main/java/org/elasticsearch/license/XPackLicenseState.java index 7826235d175..099dc4f7aaa 100644 --- a/plugin/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/plugin/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -283,6 +283,14 @@ public class XPackLicenseState { } } + /** + * @return whether custom role providers are allowed based on the license {@link OperationMode} + */ + public boolean isCustomRoleProvidersAllowed() { + final Status localStatus = status; + return (localStatus.mode == OperationMode.PLATINUM || localStatus.mode == OperationMode.TRIAL) && localStatus.active; + } + /** * Determine if Watcher is available based on the current license. *

diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index 161c42152b9..0cb17f47079 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -152,7 +152,7 @@ public class CompositeRolesStore extends AbstractComponent { if (builtInRoleDescriptors.size() != filteredRoleNames.size()) { final Set missing = difference(filteredRoleNames, builtInRoleDescriptors); assert missing.isEmpty() == false : "the missing set should not be empty if the sizes didn't match"; - if (!customRolesProviders.isEmpty()) { + if (licenseState.isCustomRoleProvidersAllowed() && !customRolesProviders.isEmpty()) { new IteratingActionListener<>(roleDescriptorActionListener, (rolesProvider, listener) -> { // resolve descriptors with role provider rolesProvider.accept(missing, ActionListener.wrap((resolvedDescriptors) -> { diff --git a/plugin/src/test/java/org/elasticsearch/license/TestUtils.java b/plugin/src/test/java/org/elasticsearch/license/TestUtils.java index 4fda068be73..cb4a7b80c50 100644 --- a/plugin/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/plugin/src/test/java/org/elasticsearch/license/TestUtils.java @@ -334,4 +334,15 @@ public class TestUtils { activeUpdates.add(active); } } + + /** + * A license state that makes the {@link #update(License.OperationMode, boolean)} + * method public for use in tests. + */ + public static class UpdatableLicenseState extends XPackLicenseState { + @Override + public void update(License.OperationMode mode, boolean active) { + super.update(mode, active); + } + } } diff --git a/plugin/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java b/plugin/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java index 4208244bf9d..1fd56876859 100644 --- a/plugin/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java @@ -68,6 +68,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(true)); } public void testSecurityBasic() { @@ -80,6 +81,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NONE)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityBasicExpired() { @@ -92,6 +94,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NONE)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityStandard() { @@ -104,6 +107,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NATIVE)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityStandardExpired() { @@ -116,6 +120,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NATIVE)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityGold() { @@ -128,6 +133,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.DEFAULT)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityGoldExpired() { @@ -140,6 +146,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.DEFAULT)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityPlatinum() { @@ -152,6 +159,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(true)); } public void testSecurityPlatinumExpired() { @@ -164,6 +172,7 @@ public class XPackLicenseStateTests extends ESTestCase { assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false)); } public void testSecurityAckBasicToNotGoldOrStandard() { diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 6baade5266d..68de9010958 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -11,6 +11,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.license.License.OperationMode; +import org.elasticsearch.license.TestUtils.UpdatableLicenseState; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; @@ -319,6 +321,71 @@ public class CompositeRolesStoreTests extends ESTestCase { } } + public void testCustomRolesProvidersLicensing() { + final FileRolesStore fileRolesStore = mock(FileRolesStore.class); + when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet()); + final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class); + doAnswer((invocationOnMock) -> { + ActionListener> callback = (ActionListener>) invocationOnMock.getArguments()[1]; + callback.onResponse(Collections.emptySet()); + return null; + }).when(nativeRolesStore).getRoleDescriptors(isA(String[].class), any(ActionListener.class)); + final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(); + + final InMemoryRolesProvider inMemoryProvider = new InMemoryRolesProvider((roles) -> { + Set descriptors = new HashSet<>(); + if (roles.contains("roleA")) { + descriptors.add(new RoleDescriptor("roleA", null, + new IndicesPrivileges[] { + IndicesPrivileges.builder().privileges("READ").indices("foo").grantedFields("*").build() + }, null)); + } + return descriptors; + }); + + UpdatableLicenseState xPackLicenseState = new UpdatableLicenseState(); + // these licenses don't allow custom role providers + xPackLicenseState.update(randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.STANDARD), true); + CompositeRolesStore compositeRolesStore = new CompositeRolesStore( + Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + + Set roleNames = Sets.newHashSet("roleA"); + PlainActionFuture future = new PlainActionFuture<>(); + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + Role role = future.actionGet(); + + // no roles should've been populated, as the license doesn't permit custom role providers + assertEquals(0, role.indices().groups().length); + + compositeRolesStore = new CompositeRolesStore( + Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + // these licenses allow custom role providers + xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), true); + roleNames = Sets.newHashSet("roleA"); + future = new PlainActionFuture<>(); + fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + role = future.actionGet(); + + // roleA should've been populated by the custom role provider, because the license allows it + assertEquals(1, role.indices().groups().length); + + // license expired, don't allow custom role providers + compositeRolesStore = new CompositeRolesStore( + Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), false); + roleNames = Sets.newHashSet("roleA"); + future = new PlainActionFuture<>(); + fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + role = future.actionGet(); + assertEquals(0, role.indices().groups().length); + } + private static class InMemoryRolesProvider implements BiConsumer, ActionListener>> { private final Function, Set> roleDescriptorsFunc;