Added security realm feature usage stats

- if active, `file` realm size
- if active, `native` realm size
- if active, `ldap` realm size, whether SSL is used, load balance type used, user search used
- if active, `active_directory` realm size, whether SSL is used, load balance type used

 `size` is scale estimation based on the local cache. Scales are: `small` (under 10 users), `medium` (under 50 users), `large` (under 250 users) and `x-large` (above 250 users).

Original commit: elastic/x-pack-elasticsearch@c6efb17aa4
This commit is contained in:
uboness 2016-05-11 20:53:16 +02:00
parent 9dbbfd09f8
commit 084179f457
34 changed files with 838 additions and 144 deletions

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.graph;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
*
*/
public class GraphFeatureSetTests extends ESTestCase {
private GraphLicensee licensee;
private NamedWriteableRegistry namedWriteableRegistry;
@Before
public void init() throws Exception {
licensee = mock(GraphLicensee.class);
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
}
public void testWritableRegistration() throws Exception {
new GraphFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry);
verify(namedWriteableRegistry).register(eq(GraphFeatureSet.Usage.class), eq("xpack.usage.graph"), anyObject());
}
public void testAvailable() throws Exception {
GraphFeatureSet featureSet = new GraphFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry);
boolean available = randomBoolean();
when(licensee.isAvailable()).thenReturn(available);
assertThat(featureSet.available(), is(available));
}
public void testEnabled() throws Exception {
boolean enabled = randomBoolean();
Settings.Builder settings = Settings.builder();
if (enabled) {
if (randomBoolean()) {
settings.put("xpack.graph.enabled", enabled);
}
} else {
settings.put("xpack.graph.enabled", enabled);
}
GraphFeatureSet featureSet = new GraphFeatureSet(settings.build(), licensee, namedWriteableRegistry);
assertThat(featureSet.enabled(), is(enabled));
}
}

View File

@ -23,6 +23,7 @@ import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
*/ */
@ESIntegTestCase.ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0) @ESIntegTestCase.ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTestCase { public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTestCase {
@Override @Override
protected Settings nodeSettings(int nodeOrdinal) { protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder() return Settings.builder()

View File

@ -46,7 +46,7 @@ public class Monitoring {
public Monitoring(Settings settings) { public Monitoring(Settings settings) {
this.settings = settings; this.settings = settings;
this.enabled = MonitoringSettings.ENABLED.get(settings); this.enabled = enabled(settings);
this.transportClientMode = XPackPlugin.transportClientMode(settings); this.transportClientMode = XPackPlugin.transportClientMode(settings);
this.tribeNode = XPackPlugin.isTribeNode(settings); this.tribeNode = XPackPlugin.isTribeNode(settings);
} }
@ -62,9 +62,9 @@ public class Monitoring {
public Collection<Module> nodeModules() { public Collection<Module> nodeModules() {
List<Module> modules = new ArrayList<>(); List<Module> modules = new ArrayList<>();
modules.add(new MonitoringModule(enabled, transportClientMode)); modules.add(new MonitoringModule(enabled, transportClientMode));
modules.add(new ExporterModule(settings));
if (enabled && transportClientMode == false && tribeNode == false) { if (enabled && transportClientMode == false && tribeNode == false) {
modules.add(new CollectorModule()); modules.add(new CollectorModule());
modules.add(new ExporterModule(settings));
modules.add(new MonitoringClientModule()); modules.add(new MonitoringClientModule());
} }
return modules; return modules;
@ -100,4 +100,8 @@ public class Monitoring {
module.registerLazyInitializable(MonitoringClientProxy.class); module.registerLazyInitializable(MonitoringClientProxy.class);
} }
} }
public static boolean enabled(Settings settings) {
return MonitoringSettings.ENABLED.get(settings);
}
} }

View File

@ -10,7 +10,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.marvel.agent.exporter.Exporter; import org.elasticsearch.marvel.agent.exporter.Exporter;
import org.elasticsearch.marvel.agent.exporter.Exporters; import org.elasticsearch.marvel.agent.exporter.Exporters;
@ -27,10 +29,10 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
private final boolean enabled; private final boolean enabled;
private final MonitoringLicensee licensee; private final MonitoringLicensee licensee;
private final Exporters exporters; private final org.elasticsearch.marvel.agent.exporter.Exporters exporters;
@Inject @Inject
public MonitoringFeatureSet(Settings settings, @Nullable MonitoringLicensee licensee, Exporters exporters, public MonitoringFeatureSet(Settings settings, @Nullable MonitoringLicensee licensee, @Nullable Exporters exporters,
NamedWriteableRegistry namedWriteableRegistry) { NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = MonitoringSettings.ENABLED.get(settings); this.enabled = MonitoringSettings.ENABLED.get(settings);
this.licensee = licensee; this.licensee = licensee;
@ -60,48 +62,48 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
@Override @Override
public Usage usage() { public Usage usage() {
return new Usage(available(), enabled(), exportersUsage(exporters));
}
int enabledLocalExporters = 0; static Usage.Exporters exportersUsage(Exporters exporters) {
int enabledHttpExporters = 0; if (exporters == null) {
int enabledUnknownExporters = 0; return null;
}
int local = 0;
int http = 0;
int unknown = 0;
for (Exporter exporter : exporters) { for (Exporter exporter : exporters) {
if (exporter.config().enabled()) { if (exporter.config().enabled()) {
switch (exporter.type()) { switch (exporter.type()) {
case LocalExporter.TYPE: case LocalExporter.TYPE:
enabledLocalExporters++; local++;
break; break;
case HttpExporter.TYPE: case HttpExporter.TYPE:
enabledHttpExporters++; http++;
break; break;
default: default:
enabledUnknownExporters++; unknown++;
} }
} }
} }
return new Usage.Exporters(local, http, unknown);
return new Usage(available(), enabled(), enabledLocalExporters, enabledHttpExporters, enabledUnknownExporters);
} }
static class Usage extends XPackFeatureSet.Usage { static class Usage extends XPackFeatureSet.Usage {
private static String WRITEABLE_NAME = writeableName(Monitoring.NAME); private static String WRITEABLE_NAME = writeableName(Monitoring.NAME);
private final int enabledLocalExporters; private @Nullable
private final int enabledHttpExporters; Exporters exporters;
private final int enabledUnknownExporters;
public Usage(StreamInput in) throws IOException { public Usage(StreamInput in) throws IOException {
super(in); super(in);
this.enabledLocalExporters = in.readVInt(); exporters = new Exporters(in);
this.enabledHttpExporters = in.readVInt();
this.enabledUnknownExporters = in.readVInt();
} }
public Usage(boolean available, boolean enabled, int enabledLocalExporters, int enabledHttpExporters, int enabledUnknownExporters) { public Usage(boolean available, boolean enabled, Exporters exporters) {
super(Monitoring.NAME, available, enabled); super(Monitoring.NAME, available, enabled);
this.enabledLocalExporters = enabledLocalExporters; this.exporters = exporters;
this.enabledHttpExporters = enabledHttpExporters;
this.enabledUnknownExporters = enabledUnknownExporters;
} }
@Override @Override
@ -122,6 +124,38 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeOptionalWriteable(exporters);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.AVAILABLE, available);
builder.field(Field.ENABLED, enabled);
if (exporters != null) {
builder.field(Field.ENABLED_EXPORTERS, exporters);
}
return builder.endObject();
}
static class Exporters implements Writeable, ToXContent {
private final int enabledLocalExporters;
private final int enabledHttpExporters;
private final int enabledUnknownExporters;
public Exporters(StreamInput in) throws IOException {
this(in.readVInt(), in.readVInt(), in.readVInt());
}
public Exporters(int enabledLocalExporters, int enabledHttpExporters, int enabledUnknownExporters) {
this.enabledLocalExporters = enabledLocalExporters;
this.enabledHttpExporters = enabledHttpExporters;
this.enabledUnknownExporters = enabledUnknownExporters;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(enabledLocalExporters); out.writeVInt(enabledLocalExporters);
out.writeVInt(enabledHttpExporters); out.writeVInt(enabledHttpExporters);
out.writeVInt(enabledUnknownExporters); out.writeVInt(enabledUnknownExporters);
@ -130,19 +164,14 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Field.AVAILABLE, available);
builder.field(Field.ENABLED, enabled);
builder.startObject(Field.ENABLED_EXPORTERS);
builder.field(Field.LOCAL, enabledLocalExporters); builder.field(Field.LOCAL, enabledLocalExporters);
builder.field(Field.HTTP, enabledHttpExporters); builder.field(Field.HTTP, enabledHttpExporters);
if (enabledUnknownExporters > 0) { if (enabledUnknownExporters > 0) {
builder.field(Field.UNKNOWN, enabledUnknownExporters); builder.field(Field.UNKNOWN, enabledUnknownExporters);
} }
builder.endObject();
return builder.endObject(); return builder.endObject();
} }
}
interface Field extends XPackFeatureSet.Usage.Field { interface Field extends XPackFeatureSet.Usage.Field {
String ENABLED_EXPORTERS = "enabled_exporters"; String ENABLED_EXPORTERS = "enabled_exporters";
@ -151,4 +180,5 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
String UNKNOWN = "_unknown"; String UNKNOWN = "_unknown";
} }
} }
} }

View File

@ -7,18 +7,20 @@ package org.elasticsearch.marvel.agent.exporter;
import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.multibindings.MapBinder; import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.marvel.Monitoring;
import org.elasticsearch.marvel.agent.exporter.http.HttpExporter; import org.elasticsearch.marvel.agent.exporter.http.HttpExporter;
import org.elasticsearch.marvel.agent.exporter.local.LocalExporter; import org.elasticsearch.marvel.agent.exporter.local.LocalExporter;
import org.elasticsearch.xpack.XPackPlugin;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class ExporterModule extends AbstractModule { public class ExporterModule extends AbstractModule {
private final Map<String, Class<? extends Exporter.Factory<? extends Exporter>>> exporterFactories = new HashMap<>();
private final Settings settings; private final Settings settings;
private final Map<String, Class<? extends Exporter.Factory<? extends Exporter>>> exporterFactories = new HashMap<>();
public ExporterModule(Settings settings) { public ExporterModule(Settings settings) {
this.settings = settings; this.settings = settings;
@ -28,13 +30,17 @@ public class ExporterModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
if (Monitoring.enabled(settings) && XPackPlugin.transportClientMode(settings) == false
&& XPackPlugin.isTribeNode(settings) == false) {
bind(Exporters.class).asEagerSingleton(); bind(Exporters.class).asEagerSingleton();
MapBinder<String, Exporter.Factory> factoryBinder = MapBinder.newMapBinder(binder(), String.class, Exporter.Factory.class); MapBinder<String, Exporter.Factory> factoryBinder = MapBinder.newMapBinder(binder(), String.class, Exporter.Factory.class);
for (Map.Entry<String, Class<? extends Exporter.Factory<? extends Exporter>>> entry : exporterFactories.entrySet()) { for (Map.Entry<String, Class<? extends Exporter.Factory<? extends Exporter>>> entry : exporterFactories.entrySet()) {
bind(entry.getValue()).asEagerSingleton(); bind(entry.getValue()).asEagerSingleton();
factoryBinder.addBinding(entry.getKey()).to(entry.getValue()); factoryBinder.addBinding(entry.getKey()).to(entry.getValue());
} }
} else {
bind(Exporters.class).toProvider(Providers.of(null));
}
} }
public void registerExporter(String type, Class<? extends Exporter.Factory<? extends Exporter>> factory) { public void registerExporter(String type, Class<? extends Exporter.Factory<? extends Exporter>> factory) {

View File

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.marvel;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.marvel.agent.exporter.Exporters;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
*
*/
public class MonitoringFeatureSetTests extends ESTestCase {
private MonitoringLicensee licensee;
private NamedWriteableRegistry namedWriteableRegistry;
private Exporters exporters;
@Before
public void init() throws Exception {
licensee = mock(MonitoringLicensee.class);
exporters = mock(Exporters.class);
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
}
public void testWritableRegistration() throws Exception {
new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry);
verify(namedWriteableRegistry).register(eq(MonitoringFeatureSet.Usage.class), eq("xpack.usage.monitoring"), anyObject());
}
public void testAvailable() throws Exception {
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry);
boolean available = randomBoolean();
when(licensee.available()).thenReturn(available);
assertThat(featureSet.available(), is(available));
}
public void testEnabled() throws Exception {
boolean enabled = randomBoolean();
Settings.Builder settings = Settings.builder();
if (enabled) {
if (randomBoolean()) {
settings.put("xpack.monitoring.enabled", enabled);
}
} else {
settings.put("xpack.monitoring.enabled", enabled);
}
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(settings.build(), licensee, exporters, namedWriteableRegistry);
assertThat(featureSet.enabled(), is(enabled));
}
}

View File

@ -73,8 +73,8 @@ import org.elasticsearch.shield.rest.action.user.RestChangePasswordAction;
import org.elasticsearch.shield.rest.action.user.RestDeleteUserAction; import org.elasticsearch.shield.rest.action.user.RestDeleteUserAction;
import org.elasticsearch.shield.rest.action.user.RestGetUsersAction; import org.elasticsearch.shield.rest.action.user.RestGetUsersAction;
import org.elasticsearch.shield.rest.action.user.RestPutUserAction; import org.elasticsearch.shield.rest.action.user.RestPutUserAction;
import org.elasticsearch.shield.ssl.SSLModule;
import org.elasticsearch.shield.ssl.SSLConfiguration; import org.elasticsearch.shield.ssl.SSLConfiguration;
import org.elasticsearch.shield.ssl.SSLModule;
import org.elasticsearch.shield.support.OptionalSettings; import org.elasticsearch.shield.support.OptionalSettings;
import org.elasticsearch.shield.transport.ShieldClientTransportService; import org.elasticsearch.shield.transport.ShieldClientTransportService;
import org.elasticsearch.shield.transport.ShieldServerTransportService; import org.elasticsearch.shield.transport.ShieldServerTransportService;
@ -125,27 +125,28 @@ public class Security {
public Collection<Module> nodeModules() { public Collection<Module> nodeModules() {
List<Module> modules = new ArrayList<>(); List<Module> modules = new ArrayList<>();
// we can't load that at construction time since the license plugin might not have been loaded at that point if (transportClientMode) {
// which might not be the case during Plugin class instantiation. Once nodeModules are pulled
// everything should have been loaded
if (enabled && transportClientMode == false) {
securityLicenseState = new SecurityLicenseState();
}
modules.add(new SecurityModule(settings, securityLicenseState));
if (enabled == false) { if (enabled == false) {
return modules; return modules;
} }
modules.add(new SecurityModule(settings, securityLicenseState));
if (transportClientMode == true) {
modules.add(new ShieldTransportModule(settings)); modules.add(new ShieldTransportModule(settings));
modules.add(new SSLModule(settings)); modules.add(new SSLModule(settings));
return modules; return modules;
} }
modules.add(new CryptoModule(settings));
modules.add(new AuthenticationModule(settings)); modules.add(new AuthenticationModule(settings));
if (enabled == false) {
modules.add(new SecurityModule(settings, securityLicenseState));
return modules;
}
// we can't load that at construction time since the license plugin might not have been loaded at that point
// which might not be the case during Plugin class instantiation. Once nodeModules are pulled
// everything should have been loaded
securityLicenseState = new SecurityLicenseState();
modules.add(new SecurityModule(settings, securityLicenseState));
modules.add(new CryptoModule(settings));
modules.add(new AuthorizationModule(settings)); modules.add(new AuthorizationModule(settings));
modules.add(new AuditTrailModule(settings)); modules.add(new AuditTrailModule(settings));
modules.add(new ShieldRestModule(settings)); modules.add(new ShieldRestModule(settings));

View File

@ -9,12 +9,21 @@ import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.marvel.Monitoring; import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.Realms;
import org.elasticsearch.shield.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.XPackFeatureSet; import org.elasticsearch.xpack.XPackFeatureSet;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* *
@ -23,12 +32,14 @@ public class SecurityFeatureSet implements XPackFeatureSet {
private final boolean enabled; private final boolean enabled;
private final SecurityLicenseState licenseState; private final SecurityLicenseState licenseState;
private final @Nullable Realms realms;
@Inject @Inject
public SecurityFeatureSet(Settings settings, @Nullable SecurityLicenseState licenseState, public SecurityFeatureSet(Settings settings, @Nullable SecurityLicenseState licenseState,
NamedWriteableRegistry namedWriteableRegistry) { @Nullable Realms realms, NamedWriteableRegistry namedWriteableRegistry) {
this.enabled = Security.enabled(settings); this.enabled = Security.enabled(settings);
this.licenseState = licenseState; this.licenseState = licenseState;
this.realms = realms;
namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new); namedWriteableRegistry.register(Usage.class, Usage.WRITEABLE_NAME, Usage::new);
} }
@ -53,20 +64,39 @@ public class SecurityFeatureSet implements XPackFeatureSet {
} }
@Override @Override
public XPackFeatureSet.Usage usage() { public Usage usage() {
return new Usage(available(), enabled()); List<Map<String, Object>> enabledRealms = buildEnabledRealms(realms);
return new Usage(available(), enabled(), enabledRealms);
}
static List<Map<String, Object>> buildEnabledRealms(Realms realms) {
if (realms == null) {
return Collections.emptyList();
}
List<Map<String, Object>> enabledRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm instanceof ReservedRealm) {
continue; // we don't need usage of this one
}
Map<String, Object> stats = realm.usageStats();
enabledRealms.add(stats);
}
return enabledRealms;
} }
static class Usage extends XPackFeatureSet.Usage { static class Usage extends XPackFeatureSet.Usage {
private static final String WRITEABLE_NAME = writeableName(Security.NAME); private static final String WRITEABLE_NAME = writeableName(Security.NAME);
private List<Map<String, Object>> enabledRealms;
public Usage(StreamInput input) throws IOException { public Usage(StreamInput in) throws IOException {
super(input); super(in);
enabledRealms = in.readList(StreamInput::readMap);
} }
public Usage(boolean available, boolean enabled) { public Usage(boolean available, boolean enabled, List<Map<String, Object>> enabledRealms) {
super(Security.NAME, available, enabled); super(Security.NAME, available, enabled);
this.enabledRealms = enabledRealms;
} }
@Override @Override
@ -75,12 +105,24 @@ public class SecurityFeatureSet implements XPackFeatureSet {
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public void writeTo(StreamOutput out) throws IOException {
return builder.startObject() super.writeTo(out);
.field(Field.AVAILABLE, available) out.writeList(enabledRealms.stream().map((m) -> (Writeable) o -> o.writeMap(m)).collect(Collectors.toList()));
.field(Field.ENABLED, enabled) }
.endObject();
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.AVAILABLE, available);
builder.field(Field.ENABLED, enabled);
if (enabled) {
builder.field(Field.ENABLED_REALMS, enabledRealms);
}
return builder.endObject();
}
interface Field extends XPackFeatureSet.Usage.Field {
String ENABLED_REALMS = "enabled_realms";
} }
} }
} }

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc; package org.elasticsearch.shield.authc;
import org.elasticsearch.common.inject.multibindings.MapBinder; import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.common.inject.util.Providers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.activedirectory.ActiveDirectoryRealm; import org.elasticsearch.shield.authc.activedirectory.ActiveDirectoryRealm;
import org.elasticsearch.shield.authc.esnative.NativeRealm; import org.elasticsearch.shield.authc.esnative.NativeRealm;
@ -42,6 +43,11 @@ public class AuthenticationModule extends AbstractShieldModule.Node {
@Override @Override
protected void configureNode() { protected void configureNode() {
if (!shieldEnabled) {
bind(Realms.class).toProvider(Providers.of(null));
return;
}
AnonymousUser.initialize(settings); AnonymousUser.initialize(settings);
MapBinder<String, Realm.Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class); MapBinder<String, Realm.Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class);
mapBinder.addBinding(FileRealm.TYPE).to(FileRealm.Factory.class).asEagerSingleton(); mapBinder.addBinding(FileRealm.TYPE).to(FileRealm.Factory.class).asEagerSingleton();

View File

@ -9,6 +9,9 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import java.util.HashMap;
import java.util.Map;
/** /**
* An authentication mechanism to which the default authentication {@link org.elasticsearch.shield.authc.AuthenticationService service} * An authentication mechanism to which the default authentication {@link org.elasticsearch.shield.authc.AuthenticationService service}
* delegates the authentication process. Different realms may be defined, each may be based on different * delegates the authentication process. Different realms may be defined, each may be based on different
@ -84,6 +87,14 @@ public abstract class Realm<T extends AuthenticationToken> implements Comparable
*/ */
public abstract User lookupUser(String username); public abstract User lookupUser(String username);
public Map<String, Object> usageStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("type", type);
stats.put("name", name());
stats.put("order", order());
return stats;
}
/** /**
* Indicates whether this realm supports user lookup. * Indicates whether this realm supports user lookup.
* @return true if the realm supports user lookup * @return true if the realm supports user lookup

View File

@ -9,14 +9,16 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.RefreshListener; import org.elasticsearch.shield.authc.support.RefreshListener;
import org.elasticsearch.shield.authc.support.UsernamePasswordRealm; import org.elasticsearch.shield.authc.support.UsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
import java.util.Map;
/** /**
* *
*/ */
@ -55,6 +57,14 @@ public class FileRealm extends CachingUsernamePasswordRealm {
return null; return null;
} }
@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", UserbaseScale.resolve(userPasswdStore.usersCount()));
return stats;
}
@Override @Override
public boolean userLookupSupported() { public boolean userLookupSupported() {
return true; return true;

View File

@ -17,6 +17,7 @@ import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
/** /**
* Authenticates username/password tokens against ldap, locates groups and maps them to roles. * Authenticates username/password tokens against ldap, locates groups and maps them to roles.
@ -29,6 +30,13 @@ public class LdapRealm extends AbstractLdapRealm {
super(TYPE, config, ldap, roleMapper); super(TYPE, config, ldap, roleMapper);
} }
@Override
public Map<String, Object> usageStats() {
Map<String, Object> stats = super.usageStats();
stats.put("user_search", Factory.userSearchSettings(config).isEmpty() == false);
return stats;
}
public static class Factory extends AbstractLdapRealm.Factory<LdapRealm> { public static class Factory extends AbstractLdapRealm.Factory<LdapRealm> {
private final ResourceWatcherService watcherService; private final ResourceWatcherService watcherService;
@ -53,7 +61,7 @@ public class LdapRealm extends AbstractLdapRealm {
} }
static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws IOException { static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws IOException {
Settings searchSettings = config.settings().getAsSettings("user_search"); Settings searchSettings = userSearchSettings(config);
if (!searchSettings.names().isEmpty()) { if (!searchSettings.names().isEmpty()) {
if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) {
throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " +
@ -64,5 +72,9 @@ public class LdapRealm extends AbstractLdapRealm {
} }
return new LdapSessionFactory(config, clientSSLService).init(); return new LdapSessionFactory(config, clientSSLService).init();
} }
static Settings userSearchSettings(RealmConfig config) {
return config.settings().getAsSettings("user_search");
}
} }
} }

View File

@ -15,6 +15,7 @@ import org.elasticsearch.shield.authc.support.UsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -65,6 +66,14 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm {
return sessionFactory.supportsUnauthenticatedSession(); return sessionFactory.supportsUnauthenticatedSession();
} }
@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.sslUsed);
return usage;
}
private void logException(String action, Exception e, String principal) { private void logException(String action, Exception e, String principal) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("{} failed for user [{}]", e, action, principal); logger.debug("{} failed for user [{}]", e, action, principal);

View File

@ -16,7 +16,6 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
/** /**
@ -83,25 +82,23 @@ public enum LdapLoadBalancing {
@Override @Override
public String toString() { public String toString() {
return name().toLowerCase(Locale.ENGLISH); return name().toLowerCase(Locale.ROOT);
}
public static LdapLoadBalancing resolve(Settings settings) {
Settings loadBalanceSettings = settings.getAsSettings(LOAD_BALANCE_SETTINGS);
String type = loadBalanceSettings.get(LOAD_BALANCE_TYPE_SETTING, LOAD_BALANCE_TYPE_DEFAULT);
try {
return valueOf(type.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ilae) {
throw new IllegalArgumentException("unknown load balance type [" + type + "]", ilae);
}
} }
public static ServerSet serverSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory, public static ServerSet serverSet(String[] addresses, int[] ports, Settings settings, @Nullable SocketFactory socketFactory,
@Nullable LDAPConnectionOptions options) { @Nullable LDAPConnectionOptions options) {
LdapLoadBalancing loadBalancing = resolve(settings);
Settings loadBalanceSettings = settings.getAsSettings(LOAD_BALANCE_SETTINGS); Settings loadBalanceSettings = settings.getAsSettings(LOAD_BALANCE_SETTINGS);
String type = loadBalanceSettings.get(LOAD_BALANCE_TYPE_SETTING, LOAD_BALANCE_TYPE_DEFAULT); return loadBalancing.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
switch (type.toLowerCase(Locale.ENGLISH)) {
case "failover":
return FAILOVER.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "dns_failover":
return DNS_FAILOVER.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "round_robin":
return ROUND_ROBIN.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
case "dns_round_robin":
return DNS_ROUND_ROBIN.buildServerSet(addresses, ports, loadBalanceSettings, socketFactory, options);
default:
throw new IllegalArgumentException("unknown server set type [" + type + "]. value must be one of " +
Arrays.toString(LdapLoadBalancing.values()));
}
} }
} }

View File

@ -52,7 +52,9 @@ public abstract class SessionFactory {
protected final RealmConfig config; protected final RealmConfig config;
protected final TimeValue timeout; protected final TimeValue timeout;
protected final ClientSSLService sslService; protected final ClientSSLService sslService;
protected ServerSet serverSet; protected ServerSet serverSet;
protected boolean sslUsed;
protected SessionFactory(RealmConfig config, ClientSSLService sslService) { protected SessionFactory(RealmConfig config, ClientSSLService sslService) {
this.config = config; this.config = config;
@ -118,7 +120,9 @@ public abstract class SessionFactory {
} }
public <T extends SessionFactory> T init() { public <T extends SessionFactory> T init() {
this.serverSet = serverSet(config.settings(), sslService, ldapServers(config.settings())); LDAPServers ldapServers = ldapServers(config.settings());
this.serverSet = serverSet(config.settings(), sslService, ldapServers);
this.sslUsed = ldapServers.ssl;
return (T) this; return (T) this;
} }

View File

@ -14,6 +14,7 @@ import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.support.Exceptions; import org.elasticsearch.shield.support.Exceptions;
import org.elasticsearch.shield.user.User; import org.elasticsearch.shield.user.User;
import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -164,6 +165,13 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
} }
} }
@Override
public Map<String, Object> usageStats() {
Map<String, Object> stats = super.usageStats();
stats.put("size", UserbaseScale.resolve(cache.count()).toString());
return stats;
}
protected abstract User doAuthenticate(UsernamePasswordToken token); protected abstract User doAuthenticate(UsernamePasswordToken token);
protected abstract User doLookupUser(String username); protected abstract User doLookupUser(String username);

View File

@ -11,6 +11,8 @@ import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.Realm; import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import java.util.Locale;
/** /**
* *
*/ */
@ -36,4 +38,30 @@ public abstract class UsernamePasswordRealm extends Realm<UsernamePasswordToken>
restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER); restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
} }
} }
public enum UserbaseScale {
SMALL,
MEDIUM,
LARGE,
XLARGE;
public static UserbaseScale resolve(int count) {
if (count < 10) {
return SMALL;
}
if (count < 50) {
return MEDIUM;
}
if (count < 250) {
return LARGE;
}
return XLARGE;
}
@Override
public String toString() {
return this == XLARGE ? "x-large" : name().toLowerCase(Locale.ROOT);
}
}
} }

View File

@ -0,0 +1,120 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.Realms;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
import org.junit.Before;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
*
*/
public class SecurityFeatureSetTests extends ESTestCase {
private SecurityLicenseState licenseState;
private Realms realms;
private NamedWriteableRegistry namedWriteableRegistry;
@Before
public void init() throws Exception {
licenseState = mock(SecurityLicenseState.class);
realms = mock(Realms.class);
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
}
public void testWritableRegistration() throws Exception {
new SecurityFeatureSet(Settings.EMPTY, licenseState, realms, namedWriteableRegistry);
verify(namedWriteableRegistry).register(eq(SecurityFeatureSet.Usage.class), eq("xpack.usage.security"), anyObject());
}
public void testAvailable() throws Exception {
SecurityFeatureSet featureSet = new SecurityFeatureSet(Settings.EMPTY, licenseState, realms, namedWriteableRegistry);
boolean available = randomBoolean();
when(licenseState.authenticationAndAuthorizationEnabled()).thenReturn(available);
assertThat(featureSet.available(), is(available));
}
public void testEnabled() throws Exception {
boolean enabled = randomBoolean();
Settings.Builder settings = Settings.builder();
if (enabled) {
if (randomBoolean()) {
settings.put("xpack.security.enabled", enabled);
}
} else {
settings.put("xpack.security.enabled", enabled);
}
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, namedWriteableRegistry);
assertThat(featureSet.enabled(), is(enabled));
}
public void testUsage() throws Exception {
boolean available = randomBoolean();
when(licenseState.authenticationAndAuthorizationEnabled()).thenReturn(available);
Settings.Builder settings = Settings.builder();
boolean enabled = randomBoolean();
if (enabled) {
if (randomBoolean()) {
settings.put("xpack.security.enabled", enabled);
}
} else {
settings.put("xpack.security.enabled", enabled);
}
List<Realm> realmsList= new ArrayList<>();
for (int i = 0; i < 5; i++) {
Realm realm = mock(Realm.class);
when(realm.type()).thenReturn("type" + i);
realmsList.add(realm);
Map<String, Object> realmUsage = new HashMap<>();
realmUsage.put("key1", "value" + i);
realmUsage.put("key2", i);
realmUsage.put("key3", i % 2 == 0);
when(realm.usageStats()).thenReturn(realmUsage);
}
when(realms.iterator()).thenReturn(realmsList.iterator());
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, namedWriteableRegistry);
SecurityFeatureSet.Usage usage = featureSet.usage();
assertThat(usage, is(notNullValue()));
assertThat(usage.name(), is(Security.NAME));
assertThat(usage.enabled(), is(enabled));
assertThat(usage.available(), is(available));
XContentSource source = new XContentSource(usage);
if (enabled) {
for (int i = 0; i < 5; i++) {
assertThat(source.getValue("enabled_realms." + i + ".key1"), is("value" + i));
assertThat(source.getValue("enabled_realms." + i + ".key2"), is(i));
assertThat(source.getValue("enabled_realms." + i + ".key3"), is(i % 2 == 0));
}
} else {
assertThat(source.getValue("enabled_realms"), is(nullValue()));
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.activedirectory;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.shield.ssl.SSLConfiguration.Global;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.junit.annotations.Network;
import org.junit.Before;
import java.nio.file.Path;
@Network
public class AbstractActiveDirectoryIntegTests extends ESTestCase {
public static final String AD_LDAP_URL = "ldaps://54.213.145.20:636";
public static final String PASSWORD = "NickFuryHeartsES";
public static final String AD_DOMAIN = "ad.test.elasticsearch.com";
protected ClientSSLService clientSSLService;
protected Settings globalSettings;
protected boolean useGlobalSSL;
@Before
public void initializeSslSocketFactory() throws Exception {
useGlobalSSL = randomBoolean();
Path keystore = getDataPath("../ldap/support/ldaptrust.jks");
/*
* Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext.
* If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname
* verification tests since a re-established connection does not perform hostname verification.
*/
Settings.Builder builder = Settings.builder().put("path.home", createTempDir());
if (useGlobalSSL) {
builder.put("xpack.security.ssl.keystore.path", keystore)
.put("xpack.security.ssl.keystore.password", "changeit");
} else {
builder.put(Global.AUTO_GENERATE_SSL_SETTING.getKey(), false);
}
globalSettings = builder.build();
Environment environment = new Environment(globalSettings);
clientSSLService = new ClientSSLService(globalSettings, new Global(globalSettings));
clientSSLService.setEnvironment(environment);
}
Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, LdapSearchScope scope,
boolean hostnameVerification) {
Settings.Builder builder = Settings.builder()
.putArray(ActiveDirectorySessionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN)
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_SCOPE_SETTING, scope)
.put(ActiveDirectorySessionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification);
if (useGlobalSSL == false) {
builder.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
.put("ssl.truststore.password", "changeit");
}
return builder.build();
}
}

View File

@ -57,6 +57,7 @@ import static org.mockito.Mockito.verify;
* additional bind DN with a password in the test setup since it really is not a DN in the ldif file * additional bind DN with a password in the test setup since it really is not a DN in the ldif file
*/ */
public class ActiveDirectoryRealmTests extends ESTestCase { public class ActiveDirectoryRealmTests extends ESTestCase {
private static final String PASSWORD = "password"; private static final String PASSWORD = "password";
protected static int numberOfLdapServers; protected static int numberOfLdapServers;

View File

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.activedirectory;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.authc.support.DnRoleMapper;
import org.elasticsearch.test.junit.annotations.Network;
import java.util.Map;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
@Network
public class ActiveDirectoryRealmUsageTests extends AbstractActiveDirectoryIntegTests {
public void testUsageStats() throws Exception {
String loadBalanceType = randomFrom("failover", "round_robin");
Settings settings = Settings.builder()
.put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com",
LdapSearchScope.BASE, false))
.put("load_balance.type", loadBalanceType)
.build();
RealmConfig config = new RealmConfig("ad-test", settings, globalSettings);
ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init();
ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, mock(DnRoleMapper.class));
Map<String, Object> stats = realm.usageStats();
assertThat(stats, is(notNullValue()));
assertThat(stats, hasEntry("type", "active_directory"));
assertThat(stats, hasEntry("name", "ad-test"));
assertThat(stats, hasEntry("order", realm.order()));
assertThat(stats, hasEntry("size", "small"));
assertThat(stats, hasEntry("ssl", true));
assertThat(stats, hasEntry("load_balance_type", loadBalanceType));
}
}

View File

@ -7,7 +7,6 @@ package org.elasticsearch.shield.authc.activedirectory;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.LdapSessionFactory; import org.elasticsearch.shield.authc.ldap.LdapSessionFactory;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope; import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
@ -15,14 +14,9 @@ import org.elasticsearch.shield.authc.ldap.support.LdapSession;
import org.elasticsearch.shield.authc.ldap.support.LdapTestCase; import org.elasticsearch.shield.authc.ldap.support.LdapTestCase;
import org.elasticsearch.shield.authc.ldap.support.SessionFactory; import org.elasticsearch.shield.authc.ldap.support.SessionFactory;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.shield.ssl.SSLConfiguration.Global;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.junit.annotations.Network; import org.elasticsearch.test.junit.annotations.Network;
import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import static org.elasticsearch.test.ShieldTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.ShieldTestsUtils.assertAuthenticationException;
@ -32,36 +26,7 @@ import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@Network @Network
public class ActiveDirectorySessionFactoryTests extends ESTestCase { public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryIntegTests {
public static final String AD_LDAP_URL = "ldaps://54.213.145.20:636";
public static final String PASSWORD = "NickFuryHeartsES";
public static final String AD_DOMAIN = "ad.test.elasticsearch.com";
private ClientSSLService clientSSLService;
private Settings globalSettings;
private boolean useGlobalSSL;
@Before
public void initializeSslSocketFactory() throws Exception {
useGlobalSSL = randomBoolean();
Path keystore = getDataPath("../ldap/support/ldaptrust.jks");
/*
* Prior to each test we reinitialize the socket factory with a new SSLService so that we get a new SSLContext.
* If we re-use a SSLContext, previously connected sessions can get re-established which breaks hostname
* verification tests since a re-established connection does not perform hostname verification.
*/
Settings.Builder builder = Settings.builder().put("path.home", createTempDir());
if (useGlobalSSL) {
builder.put("xpack.security.ssl.keystore.path", keystore)
.put("xpack.security.ssl.keystore.password", "changeit");
} else {
builder.put(Global.AUTO_GENERATE_SSL_SETTING.getKey(), false);
}
globalSettings = builder.build();
Environment environment = new Environment(globalSettings);
clientSSLService = new ClientSSLService(globalSettings, new Global(globalSettings));
clientSSLService.setEnvironment(environment);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testAdAuth() throws Exception { public void testAdAuth() throws Exception {
@ -333,19 +298,4 @@ public class ActiveDirectorySessionFactoryTests extends ESTestCase {
} }
return builder.build(); return builder.build();
} }
Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, LdapSearchScope scope,
boolean hostnameVerification) {
Settings.Builder builder = Settings.builder()
.putArray(ActiveDirectorySessionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectorySessionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN)
.put(ActiveDirectorySessionFactory.AD_USER_SEARCH_SCOPE_SETTING, scope)
.put(ActiveDirectorySessionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification);
if (useGlobalSSL == false) {
builder.put("ssl.truststore.path", getDataPath("../ldap/support/ldaptrust.jks"))
.put("ssl.truststore.password", "changeit");
}
return builder.build();
}
} }

View File

@ -7,23 +7,25 @@ package org.elasticsearch.shield.authc.file;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.Hasher; import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.UsernamePasswordRealm;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.Before; import org.junit.Before;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -161,6 +163,27 @@ public class FileRealmTests extends ESTestCase {
assertThat(user5, sameInstance(user6)); assertThat(user5, sameInstance(user6));
} }
public void testUsageStats() throws Exception {
int userCount = randomIntBetween(0, 1000);
when(userPasswdStore.usersCount()).thenReturn(userCount);
Settings.Builder settings = Settings.builder();
int order = randomIntBetween(0, 10);
settings.put("order", order);
RealmConfig config = new RealmConfig("file-realm", settings.build(), globalSettings);
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
Map<String, Object> usage = realm.usageStats();
assertThat(usage, is(notNullValue()));
assertThat(usage, hasEntry("type", "file"));
assertThat(usage, hasEntry("name", "file-realm"));
assertThat(usage, hasEntry("order", order));
assertThat(usage, hasEntry("size", UsernamePasswordRealm.UserbaseScale.resolve(userCount)));
}
static class UserPasswdStore extends FileUserPasswdStore { static class UserPasswdStore extends FileUserPasswdStore {
public UserPasswdStore(RealmConfig config) { public UserPasswdStore(RealmConfig config) {
super(config, mock(ResourceWatcherService.class)); super(config, mock(ResourceWatcherService.class));

View File

@ -6,7 +6,6 @@
package org.elasticsearch.shield.authc.ldap; package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope; import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.authc.ldap.support.LdapTestCase; import org.elasticsearch.shield.authc.ldap.support.LdapTestCase;
@ -15,16 +14,20 @@ import org.elasticsearch.shield.authc.support.DnRoleMapper;
import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.util.Map;
import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.USER_DN_TEMPLATES_SETTING; import static org.elasticsearch.shield.authc.ldap.LdapSessionFactory.USER_DN_TEMPLATES_SETTING;
import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.HOSTNAME_VERIFICATION_SETTING; import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.HOSTNAME_VERIFICATION_SETTING;
import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.URLS_SETTING; import static org.elasticsearch.shield.authc.ldap.support.SessionFactory.URLS_SETTING;
import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@ -35,6 +38,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
public class LdapRealmTests extends LdapTestCase { public class LdapRealmTests extends LdapTestCase {
public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas"; public static final String VALID_USER_TEMPLATE = "cn={0},ou=people,o=sevenSeas";
public static final String VALID_USERNAME = "Thomas Masterman Hardy"; public static final String VALID_USERNAME = "Thomas Masterman Hardy";
public static final String PASSWORD = "pass"; public static final String PASSWORD = "pass";
@ -217,4 +221,37 @@ public class LdapRealmTests extends LdapTestCase {
assertThat(user, notNullValue()); assertThat(user, notNullValue());
assertThat(user.roles(), arrayContaining("avenger")); assertThat(user.roles(), arrayContaining("avenger"));
} }
public void testUsageStats() throws Exception {
String groupSearchBase = "o=sevenSeas";
Settings.Builder settings = Settings.builder()
.putArray(URLS_SETTING, ldapUrls())
.put("bind_dn", "cn=Thomas Masterman Hardy,ou=people,o=sevenSeas")
.put("bind_password", PASSWORD)
.put("group_search.base_dn", groupSearchBase)
.put("group_search.scope", LdapSearchScope.SUB_TREE)
.put(HOSTNAME_VERIFICATION_SETTING, false);
int order = randomIntBetween(0, 10);
settings.put("order", order);
boolean userSearch = randomBoolean();
if (userSearch) {
settings.put("user_search.base_dn", "");
}
RealmConfig config = new RealmConfig("ldap-realm", settings.build(), globalSettings);
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init();
LdapRealm realm = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null));
Map<String, Object> stats = realm.usageStats();
assertThat(stats, is(notNullValue()));
assertThat(stats, hasEntry("type", "ldap"));
assertThat(stats, hasEntry("name", "ldap-realm"));
assertThat(stats, hasEntry("order", realm.order()));
assertThat(stats, hasEntry("size", "small"));
assertThat(stats, hasEntry("ssl", false));
assertThat(stats, hasEntry("user_search", userSearch));
}
} }

View File

@ -59,6 +59,7 @@ import static org.hamcrest.Matchers.nullValue;
LdapUserSearchSessionFactoryTests.BackgroundConnectThreadLeakFilter.class LdapUserSearchSessionFactoryTests.BackgroundConnectThreadLeakFilter.class
}) })
public class LdapUserSearchSessionFactoryTests extends LdapTestCase { public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
private ClientSSLService clientSSLService; private ClientSSLService clientSSLService;
private Settings globalSettings; private Settings globalSettings;

View File

@ -13,6 +13,7 @@ import org.elasticsearch.shield.authc.ldap.support.LdapSearchScope;
import org.elasticsearch.shield.authc.ldap.support.LdapSession; import org.elasticsearch.shield.authc.ldap.support.LdapSession;
import org.elasticsearch.shield.authc.ldap.support.LdapTestCase; import org.elasticsearch.shield.authc.ldap.support.LdapTestCase;
import org.elasticsearch.shield.authc.ldap.support.SessionFactory; import org.elasticsearch.shield.authc.ldap.support.SessionFactory;
import org.elasticsearch.shield.authc.support.DnRoleMapper;
import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.ssl.ClientSSLService; import org.elasticsearch.shield.ssl.ClientSSLService;
import org.elasticsearch.shield.ssl.SSLConfiguration.Global; import org.elasticsearch.shield.ssl.SSLConfiguration.Global;
@ -22,9 +23,14 @@ import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.mock;
@Network @Network
public class OpenLdapTests extends ESTestCase { public class OpenLdapTests extends ESTestCase {
@ -91,6 +97,36 @@ public class OpenLdapTests extends ESTestCase {
} }
} }
public void testUsageStats() throws Exception {
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
Settings.Builder settings = Settings.builder()
.put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL))
.put("group_search.filter", "(&(objectclass=posixGroup)(memberUID={0}))")
.put("group_search.user_attribute", "uid");
boolean userSearch = randomBoolean();
if (userSearch) {
settings.put("user_search.base_dn", "");
}
String loadBalanceType = randomFrom("failover", "round_robin");
settings.put("load_balance.type", loadBalanceType);
RealmConfig config = new RealmConfig("oldap-test", settings.build(), globalSettings);
LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init();
LdapRealm realm = new LdapRealm(config, sessionFactory, mock(DnRoleMapper.class));
Map<String, Object> stats = realm.usageStats();
assertThat(stats, is(notNullValue()));
assertThat(stats, hasEntry("size", "small"));
assertThat(stats, hasEntry("ssl", true));
assertThat(stats, hasEntry("user_search", userSearch));
assertThat(stats, hasEntry("load_balance_type", loadBalanceType));
}
public void testCustomFilter() throws Exception { public void testCustomFilter() throws Exception {
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";

View File

@ -12,6 +12,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
public class LDAPServersTests extends ESTestCase { public class LDAPServersTests extends ESTestCase {
public void testConfigure1ldaps() { public void testConfigure1ldaps() {
String[] urls = new String[] { "ldaps://example.com:636" }; String[] urls = new String[] { "ldaps://example.com:636" };

View File

@ -26,7 +26,7 @@ public class LdapLoadBalancingTests extends ESTestCase {
LdapLoadBalancing.serverSet(null, null, settings, null, null); LdapLoadBalancing.serverSet(null, null, settings, null, null);
fail("using type [" + badType + "] should have thrown an exception"); fail("using type [" + badType + "] should have thrown an exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("unknown server set type")); assertThat(e.getMessage(), containsString("unknown load balance type"));
} }
} }

View File

@ -7,9 +7,9 @@ package org.elasticsearch.shield.authc.support;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.shield.authc.Realm; import org.elasticsearch.shield.authc.Realm;
import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.user.User;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.junit.Before; import org.junit.Before;
@ -25,6 +25,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.Matchers.sameInstance;
public class CachingUsernamePasswordRealmTests extends ESTestCase { public class CachingUsernamePasswordRealmTests extends ESTestCase {
private Settings globalSettings; private Settings globalSettings;
@Before @Before

View File

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.authc.support;
import org.elasticsearch.test.ESTestCase;
import java.util.Locale;
import static org.hamcrest.core.Is.is;
/**
*
*/
public class UsernamePasswordRealmTests extends ESTestCase {
public void testUserbaseScaelResolve() throws Exception {
int count = randomIntBetween(0, 1000);
UsernamePasswordRealm.UserbaseScale scale = UsernamePasswordRealm.UserbaseScale.resolve(count);
if (count < 10) {
assertThat(scale, is(UsernamePasswordRealm.UserbaseScale.SMALL));
} else if (count < 50) {
assertThat(scale, is(UsernamePasswordRealm.UserbaseScale.MEDIUM));
} else if (count < 250) {
assertThat(scale, is(UsernamePasswordRealm.UserbaseScale.LARGE));
} else {
assertThat(scale, is(UsernamePasswordRealm.UserbaseScale.XLARGE));
}
}
public void testUserbaseScaleToString() throws Exception {
UsernamePasswordRealm.UserbaseScale scale = randomFrom(UsernamePasswordRealm.UserbaseScale.values());
String value = scale.toString();
if (scale == UsernamePasswordRealm.UserbaseScale.XLARGE) {
assertThat(value , is("x-large"));
} else {
assertThat(value , is(scale.name().toLowerCase(Locale.ROOT)));
}
}
}

View File

@ -75,6 +75,7 @@ cluster:admin/script/delete
cluster:admin/script/put cluster:admin/script/put
indices:data/write/update indices:data/write/update
cluster:monitor/xpack/info cluster:monitor/xpack/info
cluster:monitor/xpack/usage
cluster:monitor/xpack/license/get cluster:monitor/xpack/license/get
cluster:admin/xpack/license/delete cluster:admin/xpack/license/delete
cluster:admin/xpack/license/put cluster:admin/xpack/license/put

View File

@ -0,0 +1,14 @@
{
"xpack.info": {
"documentation": "Retrieve information about xpack features usage",
"methods": [ "GET" ],
"url": {
"path": "/_xpack/usage",
"paths": [ "/_xpack/usage" ],
"parts": {},
"params": {
}
},
"body": null
}
}

View File

@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
/** /**
* Encapsulates the xcontent source * Encapsulates the xcontent source
*/ */
@ -48,6 +50,10 @@ public class XContentSource implements ToXContent {
this(builder.bytes(), builder.contentType()); this(builder.bytes(), builder.contentType());
} }
public XContentSource(ToXContent content) throws IOException {
this(content.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS));
}
/** /**
* @return The bytes reference of the source * @return The bytes reference of the source
*/ */

View File

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.watcher;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
*
*/
public class WatcherFeatureSetTests extends ESTestCase {
private WatcherLicensee licensee;
private NamedWriteableRegistry namedWriteableRegistry;
@Before
public void init() throws Exception {
licensee = mock(WatcherLicensee.class);
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
}
public void testWritableRegistration() throws Exception {
new WatcherFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry);
verify(namedWriteableRegistry).register(eq(WatcherFeatureSet.Usage.class), eq("xpack.usage.watcher"), anyObject());
}
public void testAvailable() throws Exception {
WatcherFeatureSet featureSet = new WatcherFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry);
boolean available = randomBoolean();
when(licensee.available()).thenReturn(available);
assertThat(featureSet.available(), is(available));
}
public void testEnabled() throws Exception {
boolean enabled = randomBoolean();
Settings.Builder settings = Settings.builder();
if (enabled) {
if (randomBoolean()) {
settings.put("xpack.watcher.enabled", enabled);
}
} else {
settings.put("xpack.watcher.enabled", enabled);
}
WatcherFeatureSet featureSet = new WatcherFeatureSet(settings.build(), licensee, namedWriteableRegistry);
assertThat(featureSet.enabled(), is(enabled));
}
}