From 407cc3a9d7271ba5b3ec6227729a922d650fdf06 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Thu, 16 Mar 2017 13:47:35 -0400 Subject: [PATCH] Only allow platinum licenses to use custom role providers (elastic/x-pack-elasticsearch#748) This commit restricts custom role providers to only apply to those instances of x-pack with a platinum level license. All other license types will not be allowed to use custom role providers. Any custom role providers implemented via the XPackExtension will not take effect unless the license is platinum. relates elastic/x-pack-elasticsearch#720 Original commit: elastic/x-pack-elasticsearch@4fc35494ee2ad4a56202764d373a70bf0e145ca5 --- .../license/XPackLicenseState.java | 8 +++ .../authz/store/CompositeRolesStore.java | 2 +- .../org/elasticsearch/license/TestUtils.java | 11 +++ .../license/XPackLicenseStateTests.java | 9 +++ .../authz/store/CompositeRolesStoreTests.java | 67 +++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) 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;