Security: make native realm usage stats accurate (#30824)
The native realm's usage stats were previously pulled from the cache, which only contains the number of users that had authenticated in the past 20 minutes. This commit changes this so that we pull the current value from the security index by executing a search request. In order to support this, the usage stats for realms is now asynchronous so that we do not block while waiting on the search to complete.
This commit is contained in:
parent
f4a412fe21
commit
8aa58887e2
|
@ -92,4 +92,8 @@ public class SecurityFeatureSetUsage extends XPackFeatureSet.Usage {
|
|||
builder.field(ANONYMOUS_XFIELD, anonymousUsage);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getRealmsUsage() {
|
||||
return Collections.unmodifiableMap(realmsUsage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,11 +119,11 @@ public abstract class Realm implements Comparable<Realm> {
|
|||
*/
|
||||
public abstract void lookupUser(String username, ActionListener<User> listener);
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
public void usageStats(ActionListener<Map<String, Object>> listener) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("name", name());
|
||||
stats.put("order", order());
|
||||
return stats;
|
||||
listener.onResponse(stats);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -86,7 +86,6 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
|||
|
||||
@Override
|
||||
public void usage(ActionListener<XPackFeatureSet.Usage> listener) {
|
||||
Map<String, Object> realmsUsage = buildRealmsUsage(realms);
|
||||
Map<String, Object> sslUsage = sslUsage(settings);
|
||||
Map<String, Object> auditUsage = auditUsage(settings);
|
||||
Map<String, Object> ipFilterUsage = ipFilterUsage(ipFilter);
|
||||
|
@ -94,10 +93,11 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
|||
|
||||
final AtomicReference<Map<String, Object>> rolesUsageRef = new AtomicReference<>();
|
||||
final AtomicReference<Map<String, Object>> roleMappingUsageRef = new AtomicReference<>();
|
||||
final CountDown countDown = new CountDown(2);
|
||||
final AtomicReference<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> buildRealmsUsage(Realms realms) {
|
||||
if (realms == null) {
|
||||
return Collections.emptyMap();
|
||||
realmsUsageListener.onResponse(Collections.emptyMap());
|
||||
} else {
|
||||
realms.usageStats(realmsUsageListener);
|
||||
}
|
||||
return realms.usageStats();
|
||||
}
|
||||
|
||||
static Map<String, Object> sslUsage(Settings settings) {
|
||||
|
|
|
@ -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<Realm> {
|
|||
return realms;
|
||||
}
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
public void usageStats(ActionListener<Map<String, Object>> listener) {
|
||||
Map<String, Object> realmMap = new HashMap<>();
|
||||
for (Realm realm : this) {
|
||||
if (ReservedRealm.TYPE.equals(realm.type())) {
|
||||
continue;
|
||||
final AtomicBoolean failed = new AtomicBoolean(false);
|
||||
final List<Realm> 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.<String, Object>newMapBuilder()
|
||||
.put("enabled", false)
|
||||
.put("available", isRealmTypeAvailable(allowedRealmType, type))
|
||||
.map();
|
||||
}
|
||||
|
||||
assert value instanceof Map;
|
||||
Map<String, Object> realmTypeUsage = (Map<String, Object>) 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<String, Object>) 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<String, Object>) 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.<String, Object>newMapBuilder()
|
||||
.put("enabled", false)
|
||||
.put("available", isRealmTypeAvailable(allowedRealmType, type))
|
||||
.map();
|
||||
}
|
||||
|
||||
assert value instanceof Map;
|
||||
Map<String, Object> realmTypeUsage = (Map<String, Object>) 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<Realm> realms) throws Exception {
|
||||
|
|
|
@ -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<Map<String, Object>> 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();
|
||||
|
|
|
@ -150,6 +150,30 @@ public class NativeUsersStore extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
void getUserCount(final ActionListener<Long> 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<SearchResponse>() {
|
||||
@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
|
||||
*/
|
||||
|
|
|
@ -55,11 +55,11 @@ public class FileRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> usageStats() {
|
||||
Map<String, Object> 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<Map<String, Object>> listener) {
|
||||
super.usageStats(ActionListener.wrap(stats -> {
|
||||
stats.put("size", userPasswdStore.usersCount());
|
||||
listener.onResponse(stats);
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -160,12 +160,14 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> usageStats() {
|
||||
Map<String, Object> 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<Map<String, Object>> 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<AuthenticationResult> listener,
|
||||
|
|
|
@ -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<String, Object> usageStats() {
|
||||
Map<String, Object> stats = super.usageStats();
|
||||
stats.put("size", cache.count());
|
||||
return stats;
|
||||
public void usageStats(ActionListener<Map<String, Object>> 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<AuthenticationResult> listener);
|
||||
|
|
|
@ -87,7 +87,7 @@ public class FileRolesStore extends AbstractComponent {
|
|||
}
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
Map<String, Object> usageStats = new HashMap<>();
|
||||
Map<String, Object> usageStats = new HashMap<>(3);
|
||||
usageStats.put("size", permissions.size());
|
||||
|
||||
boolean dls = false;
|
||||
|
|
|
@ -195,7 +195,7 @@ public class NativeRolesStore extends AbstractComponent {
|
|||
}
|
||||
|
||||
public void usageStats(ActionListener<Map<String, Object>> listener) {
|
||||
Map<String, Object> usageStats = new HashMap<>();
|
||||
Map<String, Object> 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<MultiSearchResponse>() {
|
||||
@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<MultiSearchResponse>() {
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Map<String, Object>> listener = (ActionListener) invocationOnMock.getArguments()[0];
|
||||
listener.onResponse(realmsUsageStats);
|
||||
return Void.TYPE;
|
||||
}).when(realms).usageStats(any(ActionListener.class));
|
||||
|
||||
final boolean anonymousEnabled = randomBoolean();
|
||||
if (anonymousEnabled) {
|
||||
|
|
|
@ -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<String, Object> usageStats = realms.usageStats();
|
||||
PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
|
||||
realms.usageStats(future);
|
||||
Map<String, Object> 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<String, Object> entry : usageStats.entrySet()) {
|
||||
Map<String, Object> typeMap = (Map<String, Object>) 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<String, Object> entry : usageStats.entrySet()) {
|
||||
final String type = entry.getKey();
|
||||
|
|
|
@ -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<XPackFeatureSet.Usage> securityUsage = response.getUsages().stream()
|
||||
.filter(usage -> usage instanceof SecurityFeatureSetUsage)
|
||||
.findFirst();
|
||||
assertTrue(securityUsage.isPresent());
|
||||
SecurityFeatureSetUsage securityFeatureSetUsage = (SecurityFeatureSetUsage) securityUsage.get();
|
||||
Map<String, Object> realmsUsage = securityFeatureSetUsage.getRealmsUsage();
|
||||
assertNotNull(realmsUsage);
|
||||
assertNotNull(realmsUsage.get("native"));
|
||||
assertNotNull(((Map<String, Object>) realmsUsage.get("native")).get("size"));
|
||||
List<Long> sizeList = (List<Long>) ((Map<String, Object>) 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()));
|
||||
|
|
|
@ -248,7 +248,9 @@ public class FileRealmTests extends ESTestCase {
|
|||
threadContext);
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore, threadPool);
|
||||
|
||||
Map<String, Object> usage = realm.usageStats();
|
||||
PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
|
||||
realm.usageStats(future);
|
||||
Map<String, Object> usage = future.get();
|
||||
assertThat(usage, is(notNullValue()));
|
||||
assertThat(usage, hasEntry("name", "file-realm"));
|
||||
assertThat(usage, hasEntry("order", order));
|
||||
|
|
|
@ -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<String, Object> stats = realm.usageStats();
|
||||
PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
|
||||
realm.usageStats(future);
|
||||
Map<String, Object> stats = future.get();
|
||||
assertThat(stats, is(notNullValue()));
|
||||
assertThat(stats, hasEntry("name", realm.name()));
|
||||
assertThat(stats, hasEntry("order", realm.order()));
|
||||
|
|
|
@ -360,7 +360,9 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapRealm realm = new LdapRealm(LdapRealmSettings.LDAP_TYPE, config, ldapFactory,
|
||||
new DnRoleMapper(config, resourceWatcherService), threadPool);
|
||||
|
||||
Map<String, Object> stats = realm.usageStats();
|
||||
PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
|
||||
realm.usageStats(future);
|
||||
Map<String, Object> stats = future.get();
|
||||
assertThat(stats, is(notNullValue()));
|
||||
assertThat(stats, hasEntry("name", "ldap-realm"));
|
||||
assertThat(stats, hasEntry("order", realm.order()));
|
||||
|
|
Loading…
Reference in New Issue