diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityFeatureSetUsage.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityFeatureSetUsage.java index b549cffc0cc..f615fbd0b53 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityFeatureSetUsage.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityFeatureSetUsage.java @@ -92,4 +92,8 @@ public class SecurityFeatureSetUsage extends XPackFeatureSet.Usage { builder.field(ANONYMOUS_XFIELD, anonymousUsage); } } + + public Map getRealmsUsage() { + return Collections.unmodifiableMap(realmsUsage); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Realm.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Realm.java index 234141c77c9..3e92be2ef90 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Realm.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Realm.java @@ -119,11 +119,11 @@ public abstract class Realm implements Comparable { */ public abstract void lookupUser(String username, ActionListener listener); - public Map usageStats() { + public void usageStats(ActionListener> listener) { Map stats = new HashMap<>(); stats.put("name", name()); stats.put("order", order()); - return stats; + listener.onResponse(stats); } @Override diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java index 1be3b4cd679..ab70b8513de 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java @@ -86,7 +86,6 @@ public class SecurityFeatureSet implements XPackFeatureSet { @Override public void usage(ActionListener listener) { - Map realmsUsage = buildRealmsUsage(realms); Map sslUsage = sslUsage(settings); Map auditUsage = auditUsage(settings); Map ipFilterUsage = ipFilterUsage(ipFilter); @@ -94,10 +93,11 @@ public class SecurityFeatureSet implements XPackFeatureSet { final AtomicReference> rolesUsageRef = new AtomicReference<>(); final AtomicReference> roleMappingUsageRef = new AtomicReference<>(); - final CountDown countDown = new CountDown(2); + final AtomicReference> realmsUsageRef = new AtomicReference<>(); + final CountDown countDown = new CountDown(3); final Runnable doCountDown = () -> { if (countDown.countDown()) { - listener.onResponse(new SecurityFeatureSetUsage(available(), enabled(), realmsUsage, + listener.onResponse(new SecurityFeatureSetUsage(available(), enabled(), realmsUsageRef.get(), rolesUsageRef.get(), roleMappingUsageRef.get(), sslUsage, auditUsage, ipFilterUsage, anonymousUsage)); } @@ -116,6 +116,12 @@ public class SecurityFeatureSet implements XPackFeatureSet { doCountDown.run(); }, listener::onFailure); + final ActionListener> realmsUsageListener = + ActionListener.wrap(realmsUsage -> { + realmsUsageRef.set(realmsUsage); + doCountDown.run(); + }, listener::onFailure); + if (rolesStore == null) { rolesStoreUsageListener.onResponse(Collections.emptyMap()); } else { @@ -126,13 +132,11 @@ public class SecurityFeatureSet implements XPackFeatureSet { } else { roleMappingStore.usageStats(roleMappingStoreUsageListener); } - } - - static Map buildRealmsUsage(Realms realms) { if (realms == null) { - return Collections.emptyMap(); + realmsUsageListener.onResponse(Collections.emptyMap()); + } else { + realms.usageStats(realmsUsageListener); } - return realms.usageStats(); } static Map sslUsage(Settings settings) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java index 38319597523..0284ae9a05f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java @@ -15,12 +15,16 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.license.XPackLicenseState; @@ -188,46 +192,67 @@ public class Realms extends AbstractComponent implements Iterable { return realms; } - public Map usageStats() { + public void usageStats(ActionListener> listener) { Map realmMap = new HashMap<>(); - for (Realm realm : this) { - if (ReservedRealm.TYPE.equals(realm.type())) { - continue; + final AtomicBoolean failed = new AtomicBoolean(false); + final List realmList = asList().stream() + .filter(r -> ReservedRealm.TYPE.equals(r.type()) == false) + .collect(Collectors.toList()); + final CountDown countDown = new CountDown(realmList.size()); + final Runnable doCountDown = () -> { + if ((realmList.isEmpty() || countDown.countDown()) && failed.get() == false) { + final AllowedRealmType allowedRealmType = licenseState.allowedRealmType(); + // iterate over the factories so we can add enabled & available info + for (String type : factories.keySet()) { + assert ReservedRealm.TYPE.equals(type) == false; + realmMap.compute(type, (key, value) -> { + if (value == null) { + return MapBuilder.newMapBuilder() + .put("enabled", false) + .put("available", isRealmTypeAvailable(allowedRealmType, type)) + .map(); + } + + assert value instanceof Map; + Map realmTypeUsage = (Map) value; + realmTypeUsage.put("enabled", true); + // the realms iterator returned this type so it must be enabled + assert isRealmTypeAvailable(allowedRealmType, type); + realmTypeUsage.put("available", true); + return value; + }); + } + listener.onResponse(realmMap); + } + }; + + if (realmList.isEmpty()) { + doCountDown.run(); + } else { + for (Realm realm : realmList) { + realm.usageStats(ActionListener.wrap(stats -> { + if (failed.get() == false) { + synchronized (realmMap) { + realmMap.compute(realm.type(), (key, value) -> { + if (value == null) { + Object realmTypeUsage = convertToMapOfLists(stats); + return realmTypeUsage; + } + assert value instanceof Map; + combineMaps((Map) value, stats); + return value; + }); + } + doCountDown.run(); + } + }, + e -> { + if (failed.compareAndSet(false, true)) { + listener.onFailure(e); + } + })); } - realmMap.compute(realm.type(), (key, value) -> { - if (value == null) { - Object realmTypeUsage = convertToMapOfLists(realm.usageStats()); - return realmTypeUsage; - } - assert value instanceof Map; - combineMaps((Map) value, realm.usageStats()); - return value; - }); } - - final AllowedRealmType allowedRealmType = licenseState.allowedRealmType(); - // iterate over the factories so we can add enabled & available info - for (String type : factories.keySet()) { - assert ReservedRealm.TYPE.equals(type) == false; - realmMap.compute(type, (key, value) -> { - if (value == null) { - return MapBuilder.newMapBuilder() - .put("enabled", false) - .put("available", isRealmTypeAvailable(allowedRealmType, type)) - .map(); - } - - assert value instanceof Map; - Map realmTypeUsage = (Map) value; - realmTypeUsage.put("enabled", true); - // the realms iterator returned this type so it must be enabled - assert isRealmTypeAvailable(allowedRealmType, type); - realmTypeUsage.put("available", true); - return value; - }); - } - - return realmMap; } private void addNativeRealms(List realms) throws Exception { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java index af2bfcf0d6c..a84b76beab8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java @@ -15,6 +15,8 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import java.util.Map; + import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isIndexDeleted; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed; @@ -46,6 +48,16 @@ public class NativeRealm extends CachingUsernamePasswordRealm { } } + @Override + public void usageStats(ActionListener> listener) { + super.usageStats(ActionListener.wrap(stats -> + userStore.getUserCount(ActionListener.wrap(size -> { + stats.put("size", size); + listener.onResponse(stats); + }, listener::onFailure)) + , listener::onFailure)); + } + // method is used for testing to verify cache expiration since expireAll is final void clearCache() { expireAll(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 1477c6dc880..72a65b8213f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -150,6 +150,30 @@ public class NativeUsersStore extends AbstractComponent { } } + void getUserCount(final ActionListener listener) { + if (securityIndex.indexExists() == false) { + listener.onResponse(0L); + } else { + securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareSearch(SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), USER_DOC_TYPE)) + .setSize(0) + .request(), + new ActionListener() { + @Override + public void onResponse(SearchResponse response) { + listener.onResponse(response.getHits().getTotalHits()); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }, client::search)); + } + } + /** * Async method to retrieve a user and their password */ diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java index 88656b9e01e..e2586ea836d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java @@ -55,11 +55,11 @@ public class FileRealm extends CachingUsernamePasswordRealm { } @Override - public Map usageStats() { - Map stats = super.usageStats(); - // here we can determine the size based on the in mem user store - stats.put("size", userPasswdStore.usersCount()); - return stats; + public void usageStats(ActionListener> listener) { + super.usageStats(ActionListener.wrap(stats -> { + stats.put("size", userPasswdStore.usersCount()); + listener.onResponse(stats); + }, listener::onFailure)); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index a7c6efdda31..87749850141 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -160,12 +160,14 @@ public final class LdapRealm extends CachingUsernamePasswordRealm { } @Override - public Map usageStats() { - Map usage = super.usageStats(); - usage.put("load_balance_type", LdapLoadBalancing.resolve(config.settings()).toString()); - usage.put("ssl", sessionFactory.isSslUsed()); - usage.put("user_search", LdapUserSearchSessionFactory.hasUserSearchSettings(config)); - return usage; + public void usageStats(ActionListener> listener) { + super.usageStats(ActionListener.wrap(usage -> { + usage.put("size", getCacheSize()); + usage.put("load_balance_type", LdapLoadBalancing.resolve(config.settings()).toString()); + usage.put("ssl", sessionFactory.isSslUsed()); + usage.put("user_search", LdapUserSearchSessionFactory.hasUserSearchSettings(config)); + listener.onResponse(usage); + }, listener::onFailure)); } private static void buildUser(LdapSession session, String username, ActionListener listener, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java index 8dae5275eda..e9c107abcce 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java @@ -22,6 +22,7 @@ import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.user.User; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; @@ -177,10 +178,15 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm } @Override - public Map usageStats() { - Map stats = super.usageStats(); - stats.put("size", cache.count()); - return stats; + public void usageStats(ActionListener> listener) { + super.usageStats(ActionListener.wrap(stats -> { + stats.put("cache", Collections.singletonMap("size", getCacheSize())); + listener.onResponse(stats); + }, listener::onFailure)); + } + + protected int getCacheSize() { + return cache.count(); } protected abstract void doAuthenticate(UsernamePasswordToken token, ActionListener listener); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java index f2d78806da0..59bc8042fba 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java @@ -87,7 +87,7 @@ public class FileRolesStore extends AbstractComponent { } public Map usageStats() { - Map usageStats = new HashMap<>(); + Map usageStats = new HashMap<>(3); usageStats.put("size", permissions.size()); boolean dls = false; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index b1e5170a202..9093b6a6673 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -195,7 +195,7 @@ public class NativeRolesStore extends AbstractComponent { } public void usageStats(ActionListener> listener) { - Map usageStats = new HashMap<>(); + Map usageStats = new HashMap<>(3); if (securityIndex.indexExists() == false) { usageStats.put("size", 0L); usageStats.put("fls", false); @@ -204,56 +204,56 @@ public class NativeRolesStore extends AbstractComponent { } else { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareMultiSearch() - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .setSize(0)) - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .must(QueryBuilders.boolQuery() - .should(existsQuery("indices.field_security.grant")) - .should(existsQuery("indices.field_security.except")) - // for backwardscompat with 2.x - .should(existsQuery("indices.fields")))) - .setSize(0) - .setTerminateAfter(1)) - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .filter(existsQuery("indices.query"))) - .setSize(0) - .setTerminateAfter(1)) - .request(), - new ActionListener() { - @Override - public void onResponse(MultiSearchResponse items) { - Item[] responses = items.getResponses(); - if (responses[0].isFailure()) { - usageStats.put("size", 0); - } else { - usageStats.put("size", responses[0].getResponse().getHits().getTotalHits()); - } - - if (responses[1].isFailure()) { - usageStats.put("fls", false); - } else { - usageStats.put("fls", responses[1].getResponse().getHits().getTotalHits() > 0L); - } - - if (responses[2].isFailure()) { - usageStats.put("dls", false); - } else { - usageStats.put("dls", responses[2].getResponse().getHits().getTotalHits() > 0L); - } - listener.onResponse(usageStats); + client.prepareMultiSearch() + .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .setSize(0)) + .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .must(QueryBuilders.boolQuery() + .should(existsQuery("indices.field_security.grant")) + .should(existsQuery("indices.field_security.except")) + // for backwardscompat with 2.x + .should(existsQuery("indices.fields")))) + .setSize(0) + .setTerminateAfter(1)) + .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .filter(existsQuery("indices.query"))) + .setSize(0) + .setTerminateAfter(1)) + .request(), + new ActionListener() { + @Override + public void onResponse(MultiSearchResponse items) { + Item[] responses = items.getResponses(); + if (responses[0].isFailure()) { + usageStats.put("size", 0); + } else { + usageStats.put("size", responses[0].getResponse().getHits().getTotalHits()); } - @Override - public void onFailure(Exception e) { - listener.onFailure(e); + if (responses[1].isFailure()) { + usageStats.put("fls", false); + } else { + usageStats.put("fls", responses[1].getResponse().getHits().getTotalHits() > 0L); } - }, client::multiSearch)); + + if (responses[2].isFailure()) { + usageStats.put("dls", false); + } else { + usageStats.put("dls", responses[2].getResponse().getHits().getTotalHits() > 0L); + } + listener.onResponse(usageStats); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }, client::multiSearch)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java index c169d62c6b1..076ce6c9fcb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java @@ -146,7 +146,11 @@ public class SecurityFeatureSetTests extends ESTestCase { realmUsage.put("key2", Arrays.asList(i)); realmUsage.put("key3", Arrays.asList(i % 2 == 0)); } - when(realms.usageStats()).thenReturn(realmsUsageStats); + doAnswer(invocationOnMock -> { + ActionListener> listener = (ActionListener) invocationOnMock.getArguments()[0]; + listener.onResponse(realmsUsageStats); + return Void.TYPE; + }).when(realms).usageStats(any(ActionListener.class)); final boolean anonymousEnabled = randomBoolean(); if (anonymousEnabled) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java index 2bc3d58471b..ff4c30ddf8c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security.authc; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; @@ -454,9 +455,11 @@ public class RealmsTests extends ESTestCase { .put("xpack.security.authc.realms.bar.order", "1"); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); - Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm ); + Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); - Map usageStats = realms.usageStats(); + PlainActionFuture> future = new PlainActionFuture<>(); + realms.usageStats(future); + Map usageStats = future.get(); assertThat(usageStats.size(), is(factories.size())); // first check type_0 @@ -482,7 +485,9 @@ public class RealmsTests extends ESTestCase { // disable ALL using license when(licenseState.isAuthAllowed()).thenReturn(false); when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NONE); - usageStats = realms.usageStats(); + future = new PlainActionFuture<>(); + realms.usageStats(future); + usageStats = future.get(); assertThat(usageStats.size(), is(factories.size())); for (Entry entry : usageStats.entrySet()) { Map typeMap = (Map) entry.getValue(); @@ -494,7 +499,9 @@ public class RealmsTests extends ESTestCase { // check native or internal realms enabled only when(licenseState.isAuthAllowed()).thenReturn(true); when(licenseState.allowedRealmType()).thenReturn(randomFrom(AllowedRealmType.NATIVE, AllowedRealmType.DEFAULT)); - usageStats = realms.usageStats(); + future = new PlainActionFuture<>(); + realms.usageStats(future); + usageStats = future.get(); assertThat(usageStats.size(), is(factories.size())); for (Entry entry : usageStats.entrySet()) { final String type = entry.getKey(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index a238576e413..a0550b4c1ce 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -22,6 +22,10 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.test.SecuritySettingsSourceField; +import org.elasticsearch.xpack.core.XPackFeatureSet; +import org.elasticsearch.xpack.core.action.XPackUsageRequestBuilder; +import org.elasticsearch.xpack.core.action.XPackUsageResponse; +import org.elasticsearch.xpack.core.security.SecurityFeatureSetUsage; import org.elasticsearch.xpack.core.security.action.role.DeleteRoleResponse; import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.core.security.action.role.PutRoleResponse; @@ -49,6 +53,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; @@ -662,6 +667,28 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { assertThat(usage.get("dls"), is(dls)); } + public void testRealmUsageStats() { + final int numNativeUsers = scaledRandomIntBetween(1, 32); + SecurityClient securityClient = new SecurityClient(client()); + for (int i = 0; i < numNativeUsers; i++) { + securityClient.preparePutUser("joe" + i, "s3krit".toCharArray(), "superuser").get(); + } + + XPackUsageResponse response = new XPackUsageRequestBuilder(client()).get(); + Optional securityUsage = response.getUsages().stream() + .filter(usage -> usage instanceof SecurityFeatureSetUsage) + .findFirst(); + assertTrue(securityUsage.isPresent()); + SecurityFeatureSetUsage securityFeatureSetUsage = (SecurityFeatureSetUsage) securityUsage.get(); + Map realmsUsage = securityFeatureSetUsage.getRealmsUsage(); + assertNotNull(realmsUsage); + assertNotNull(realmsUsage.get("native")); + assertNotNull(((Map) realmsUsage.get("native")).get("size")); + List sizeList = (List) ((Map) realmsUsage.get("native")).get("size"); + assertEquals(1, sizeList.size()); + assertEquals(numNativeUsers, Math.toIntExact(sizeList.get(0))); + } + public void testSetEnabled() throws Exception { securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.TEST_ROLE).get(); final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java index b0f53229377..7295e48d003 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileRealmTests.java @@ -248,7 +248,9 @@ public class FileRealmTests extends ESTestCase { threadContext); FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore, threadPool); - Map usage = realm.usageStats(); + PlainActionFuture> future = new PlainActionFuture<>(); + realm.usageStats(future); + Map usage = future.get(); assertThat(usage, is(notNullValue())); assertThat(usage, hasEntry("name", "file-realm")); assertThat(usage, hasEntry("order", order)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java index 52026cc8af5..6ab4dbf3e0c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java @@ -320,7 +320,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase { DnRoleMapper roleMapper = new DnRoleMapper(config, resourceWatcherService); LdapRealm realm = new LdapRealm(LdapRealmSettings.AD_TYPE, config, sessionFactory, roleMapper, threadPool); - Map stats = realm.usageStats(); + PlainActionFuture> future = new PlainActionFuture<>(); + realm.usageStats(future); + Map stats = future.get(); assertThat(stats, is(notNullValue())); assertThat(stats, hasEntry("name", realm.name())); assertThat(stats, hasEntry("order", realm.order())); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 042664fa670..ea1b9117922 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -360,7 +360,9 @@ public class LdapRealmTests extends LdapTestCase { LdapRealm realm = new LdapRealm(LdapRealmSettings.LDAP_TYPE, config, ldapFactory, new DnRoleMapper(config, resourceWatcherService), threadPool); - Map stats = realm.usageStats(); + PlainActionFuture> future = new PlainActionFuture<>(); + realm.usageStats(future); + Map stats = future.get(); assertThat(stats, is(notNullValue())); assertThat(stats, hasEntry("name", "ldap-realm")); assertThat(stats, hasEntry("order", realm.order()));