From 28948f89309548d3192d3b8a35fe65268bc5d793 Mon Sep 17 00:00:00 2001 From: jaymode Date: Mon, 12 Oct 2015 12:31:13 -0400 Subject: [PATCH] shield: enable and disable features based on license type Shield now supports the ability to disable or enable individual features based on the type of license that is currently installed. The change replaces the LicenseService in shield with a ShieldLicensee that is notified on changes to the license. The ShieldLicensee then updates a ShieldLicenseState object, which contains the logic and methods to check for features being enabled or disabled. The ShieldLicenseState object is used by consumers to check the status of a feature. The decoupling of the feature enablement from the ShieldLicensee class was done to work around circular dependency issues. Closes elastic/elasticsearch#689 Original commit: elastic/x-pack-elasticsearch@442514496d6efbb1dba031e2244c06a75dfbbe51 --- .../marvel/license/MarvelLicensee.java | 3 + .../shield/ShieldDisabledModule.java | 4 +- .../elasticsearch/shield/ShieldPlugin.java | 5 +- .../shield/action/ShieldActionFilter.java | 61 +++++----- .../ShieldIndexSearcherWrapper.java | 14 ++- .../shield/license/LicenseEventsNotifier.java | 41 ------- .../shield/license/LicenseModule.java | 4 +- .../shield/license/ShieldLicenseState.java | 50 ++++++++ ...icenseService.java => ShieldLicensee.java} | 66 ++++------- .../shield/rest/ShieldRestFilter.java | 21 ++-- .../rest/action/RestShieldInfoAction.java | 14 ++- .../ShieldServerTransportService.java | 38 +++--- .../shield/transport/filter/IPFilter.java | 11 +- .../integration/LicensingTests.java | 112 +++++++++++++++--- .../ShieldPluginEnabledDisabledTests.java | 23 +++- .../shield/VersionCompatibilityTests.java | 3 +- .../action/ShieldActionFilterTests.java | 27 +++-- ...dIndexSearcherWrapperIntegrationTests.java | 7 +- .../ShieldIndexSearcherWrapperUnitTests.java | 21 +++- .../license/ShieldLicenseStateTests.java | 80 +++++++++++++ .../shield/rest/ShieldRestFilterTests.java | 15 ++- .../transport/TransportFilterTests.java | 4 +- .../transport/filter/IPFilterTests.java | 44 +++++-- .../IPFilterNettyUpstreamHandlerTests.java | 5 +- .../test/ShieldIntegTestCase.java | 3 - 25 files changed, 471 insertions(+), 205 deletions(-) delete mode 100644 shield/src/main/java/org/elasticsearch/shield/license/LicenseEventsNotifier.java create mode 100644 shield/src/main/java/org/elasticsearch/shield/license/ShieldLicenseState.java rename shield/src/main/java/org/elasticsearch/shield/license/{LicenseService.java => ShieldLicensee.java} (59%) create mode 100644 shield/src/test/java/org/elasticsearch/shield/license/ShieldLicenseStateTests.java diff --git a/marvel/src/main/java/org/elasticsearch/marvel/license/MarvelLicensee.java b/marvel/src/main/java/org/elasticsearch/marvel/license/MarvelLicensee.java index 8cfb3c7ab41..c48611949a7 100644 --- a/marvel/src/main/java/org/elasticsearch/marvel/license/MarvelLicensee.java +++ b/marvel/src/main/java/org/elasticsearch/marvel/license/MarvelLicensee.java @@ -52,6 +52,9 @@ public class MarvelLicensee extends AbstractLicenseeComponent im } public boolean collectionEnabled() { + // when checking multiple parts of the status, we should get a local reference to the status object since it is + // volatile and can change between check statements... + Status status = this.status; return status.getMode() != License.OperationMode.NONE && status.getLicenseState() != LicenseState.DISABLED; } diff --git a/shield/src/main/java/org/elasticsearch/shield/ShieldDisabledModule.java b/shield/src/main/java/org/elasticsearch/shield/ShieldDisabledModule.java index 2d1f1f2f482..0bd5d528aa9 100644 --- a/shield/src/main/java/org/elasticsearch/shield/ShieldDisabledModule.java +++ b/shield/src/main/java/org/elasticsearch/shield/ShieldDisabledModule.java @@ -7,7 +7,7 @@ package org.elasticsearch.shield; import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.license.LicenseService; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.shield.support.AbstractShieldModule; public class ShieldDisabledModule extends AbstractShieldModule { @@ -21,7 +21,7 @@ public class ShieldDisabledModule extends AbstractShieldModule { assert !shieldEnabled : "shield disabled module should only get loaded with shield disabled"; if (!clientMode) { // required by the shield info rest action (when shield is disabled) - bind(LicenseService.class).toProvider(Providers.of(null)); + bind(ShieldLicenseState.class).toProvider(Providers.of(null)); } } } diff --git a/shield/src/main/java/org/elasticsearch/shield/ShieldPlugin.java b/shield/src/main/java/org/elasticsearch/shield/ShieldPlugin.java index d639da0b864..e26df0606e0 100644 --- a/shield/src/main/java/org/elasticsearch/shield/ShieldPlugin.java +++ b/shield/src/main/java/org/elasticsearch/shield/ShieldPlugin.java @@ -32,12 +32,11 @@ import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.authz.AuthorizationModule; import org.elasticsearch.index.SearcherWrapperInstaller; import org.elasticsearch.shield.authz.accesscontrol.OptOutQueryCache; -import org.elasticsearch.shield.authz.accesscontrol.ShieldIndexSearcherWrapper; import org.elasticsearch.shield.authz.store.FileRolesStore; import org.elasticsearch.shield.crypto.CryptoModule; import org.elasticsearch.shield.crypto.InternalCryptoService; import org.elasticsearch.shield.license.LicenseModule; -import org.elasticsearch.shield.license.LicenseService; +import org.elasticsearch.shield.license.ShieldLicensee; import org.elasticsearch.shield.rest.ShieldRestModule; import org.elasticsearch.shield.rest.action.RestShieldInfoAction; import org.elasticsearch.shield.rest.action.authc.cache.RestClearRealmCacheAction; @@ -125,7 +124,7 @@ public class ShieldPlugin extends Plugin { @Override public Collection> nodeServices() { if (enabled && clientMode == false) { - return Arrays.>asList(LicenseService.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class); + return Arrays.>asList(ShieldLicensee.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class); } return Collections.emptyList(); } diff --git a/shield/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java b/shield/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java index 69aff42244f..8ecab39b7c8 100644 --- a/shield/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java +++ b/shield/src/main/java/org/elasticsearch/shield/action/ShieldActionFilter.java @@ -16,8 +16,8 @@ import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicenseState; import org.elasticsearch.license.plugin.core.LicenseUtils; +import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.User; import org.elasticsearch.shield.action.interceptor.RequestInterceptor; import org.elasticsearch.shield.audit.AuditTrail; @@ -25,8 +25,7 @@ import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.authz.Privilege; import org.elasticsearch.shield.crypto.CryptoService; -import org.elasticsearch.shield.license.LicenseEventsNotifier; -import org.elasticsearch.shield.license.LicenseService; +import org.elasticsearch.shield.license.ShieldLicenseState; import java.io.IOException; import java.util.ArrayList; @@ -49,24 +48,18 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte private final AuditTrail auditTrail; private final ShieldActionMapper actionMapper; private final Set requestInterceptors; - - private volatile boolean licenseEnabled = true; + private final ShieldLicenseState licenseState; @Inject public ShieldActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, CryptoService cryptoService, - AuditTrail auditTrail, LicenseEventsNotifier licenseEventsNotifier, ShieldActionMapper actionMapper, Set requestInterceptors) { + AuditTrail auditTrail, ShieldLicenseState licenseState, ShieldActionMapper actionMapper, Set requestInterceptors) { super(settings); this.authcService = authcService; this.authzService = authzService; this.cryptoService = cryptoService; this.auditTrail = auditTrail; this.actionMapper = actionMapper; - licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() { - @Override - public void notify(LicenseState state) { - licenseEnabled = state != LicenseState.DISABLED; - } - }); + this.licenseState = licenseState; this.requestInterceptors = requestInterceptors; } @@ -77,36 +70,40 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte A functional requirement - when the license of shield is disabled (invalid/expires), shield will continue to operate normally, except all read operations will be blocked. */ - if (!licenseEnabled && LICENSE_EXPIRATION_ACTION_MATCHER.test(action)) { + if (!licenseState.statsAndHealthEnabled() && LICENSE_EXPIRATION_ACTION_MATCHER.test(action)) { logger.error("blocking [{}] operation due to expired license. Cluster health, cluster stats and indices stats \n" + "operations are blocked on shield license expiration. All data operations (read and write) continue to work. \n" + "If you have a new license, please update it. Otherwise, please reach out to your support contact.", action); - throw LicenseUtils.newExpirationException(LicenseService.FEATURE_NAME); + throw LicenseUtils.newExpirationException(ShieldPlugin.NAME); } try { - /** - here we fallback on the system user. Internal system requests are requests that are triggered by - the system itself (e.g. pings, update mappings, share relocation, etc...) and were not originated - by user interaction. Since these requests are triggered by es core modules, they are security - agnostic and therefore not associated with any user. When these requests execute locally, they - are executed directly on their relevant action. Since there is no other way a request can make - it to the action without an associated user (not via REST or transport - this is taken care of by - the {@link Rest} filter and the {@link ServerTransport} filter respectively), it's safe to assume a system user - here if a request is not associated with any other user. - */ + if (licenseState.securityEnabled()) { + /** + here we fallback on the system user. Internal system requests are requests that are triggered by + the system itself (e.g. pings, update mappings, share relocation, etc...) and were not originated + by user interaction. Since these requests are triggered by es core modules, they are security + agnostic and therefore not associated with any user. When these requests execute locally, they + are executed directly on their relevant action. Since there is no other way a request can make + it to the action without an associated user (not via REST or transport - this is taken care of by + the {@link Rest} filter and the {@link ServerTransport} filter respectively), it's safe to assume a system user + here if a request is not associated with any other user. + */ - String shieldAction = actionMapper.action(action, request); - User user = authcService.authenticate(shieldAction, request, User.SYSTEM); - authzService.authorize(user, shieldAction, request); - request = unsign(user, shieldAction, request); + String shieldAction = actionMapper.action(action, request); + User user = authcService.authenticate(shieldAction, request, User.SYSTEM); + authzService.authorize(user, shieldAction, request); + request = unsign(user, shieldAction, request); - for (RequestInterceptor interceptor : requestInterceptors) { - if (interceptor.supports(request)) { - interceptor.intercept(request, user); + for (RequestInterceptor interceptor : requestInterceptors) { + if (interceptor.supports(request)) { + interceptor.intercept(request, user); + } } + chain.proceed(action, request, new SigningListener(this, listener)); + } else { + chain.proceed(action, request, listener); } - chain.proceed(action, request, new SigningListener(this, listener)); } catch (Throwable t) { listener.onFailure(t); } diff --git a/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java b/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java index 9b9298b75c4..52afb3298d2 100644 --- a/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java +++ b/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java @@ -30,6 +30,7 @@ import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardUtils; import org.elasticsearch.shield.authz.InternalAuthorizationService; import org.elasticsearch.shield.authz.accesscontrol.DocumentSubsetReader.DocumentSubsetDirectoryReader; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.shield.support.Exceptions; import java.io.IOException; @@ -54,13 +55,16 @@ public final class ShieldIndexSearcherWrapper extends AbstractComponent implemen private final Set allowedMetaFields; private final IndexQueryParserService parserService; private final BitsetFilterCache bitsetFilterCache; + private final ShieldLicenseState shieldLicenseState; @Inject - public ShieldIndexSearcherWrapper(@IndexSettings Settings indexSettings, IndexQueryParserService parserService, MapperService mapperService, BitsetFilterCache bitsetFilterCache) { + public ShieldIndexSearcherWrapper(@IndexSettings Settings indexSettings, IndexQueryParserService parserService, + MapperService mapperService, BitsetFilterCache bitsetFilterCache, ShieldLicenseState shieldLicenseState) { super(indexSettings); this.mapperService = mapperService; this.parserService = parserService; this.bitsetFilterCache = bitsetFilterCache; + this.shieldLicenseState = shieldLicenseState; Set allowedMetaFields = new HashSet<>(); allowedMetaFields.addAll(Arrays.asList(MapperService.getAllMetaFields())); @@ -73,6 +77,10 @@ public final class ShieldIndexSearcherWrapper extends AbstractComponent implemen @Override public DirectoryReader wrap(DirectoryReader reader) { + if (shieldLicenseState.documentAndFieldLevelSecurityEnabled() == false) { + return reader; + } + final Set allowedMetaFields = this.allowedMetaFields; try { RequestContext context = RequestContext.current(); @@ -124,6 +132,10 @@ public final class ShieldIndexSearcherWrapper extends AbstractComponent implemen @Override public IndexSearcher wrap(EngineConfig engineConfig, IndexSearcher searcher) throws EngineException { + if (shieldLicenseState.documentAndFieldLevelSecurityEnabled() == false) { + return searcher; + } + final DirectoryReader directoryReader = (DirectoryReader) searcher.getIndexReader(); if (directoryReader instanceof DocumentSubsetDirectoryReader) { // The reasons why we return a custom searcher: diff --git a/shield/src/main/java/org/elasticsearch/shield/license/LicenseEventsNotifier.java b/shield/src/main/java/org/elasticsearch/shield/license/LicenseEventsNotifier.java deleted file mode 100644 index 1ea9f42b10a..00000000000 --- a/shield/src/main/java/org/elasticsearch/shield/license/LicenseEventsNotifier.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.license; - -import org.elasticsearch.license.plugin.core.LicenseState; - -import java.util.HashSet; -import java.util.Set; - -/** - * Serves as a registry of license event listeners and enables notifying them about the - * different events. - * - * This class is required to serves as a bridge between the license service and any other - * service that needs to recieve license events. The reason for that is that some services - * that require such notifications also serves as a dependency for the licensing service - * which introdues a circular dependency in guice (e.g. TransportService). This class then - * serves as a bridge between the different services to eliminate such circular dependencies. - */ -public class LicenseEventsNotifier { - - private final Set listeners = new HashSet<>(); - - public void register(Listener listener) { - listeners.add(listener); - } - - protected void notify(LicenseState state) { - for (Listener listener : listeners) { - listener.notify(state); - } - } - - public static interface Listener { - - void notify(LicenseState state); - } -} diff --git a/shield/src/main/java/org/elasticsearch/shield/license/LicenseModule.java b/shield/src/main/java/org/elasticsearch/shield/license/LicenseModule.java index 2f5b8a50728..9c7d015adb8 100644 --- a/shield/src/main/java/org/elasticsearch/shield/license/LicenseModule.java +++ b/shield/src/main/java/org/elasticsearch/shield/license/LicenseModule.java @@ -20,8 +20,8 @@ public class LicenseModule extends AbstractShieldModule.Node { @Override protected void configureNode() { - bind(LicenseService.class).asEagerSingleton(); - bind(LicenseEventsNotifier.class).asEagerSingleton(); + bind(ShieldLicensee.class).asEagerSingleton(); + bind(ShieldLicenseState.class).asEagerSingleton(); } private void verifyLicensePlugin() { diff --git a/shield/src/main/java/org/elasticsearch/shield/license/ShieldLicenseState.java b/shield/src/main/java/org/elasticsearch/shield/license/ShieldLicenseState.java new file mode 100644 index 00000000000..f811e8e206e --- /dev/null +++ b/shield/src/main/java/org/elasticsearch/shield/license/ShieldLicenseState.java @@ -0,0 +1,50 @@ +/* + * 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.license; + +import org.elasticsearch.license.core.License.OperationMode; +import org.elasticsearch.license.plugin.core.LicenseState; +import org.elasticsearch.license.plugin.core.Licensee.Status; + + +/** + * This class serves to decouple shield code that needs to check the license state from the {@link ShieldLicensee} as the + * tight coupling causes issues with guice injection and circular dependencies + */ +public class ShieldLicenseState { + + // if we start disabled then we can emit false disabled messages and block legitimate requests... + protected volatile Status status = new Status(OperationMode.TRIAL, LicenseState.ENABLED); + + /** + * @return true if the license allows for security features to be enabled (authc, authz, ip filter, audit, etc) + */ + public boolean securityEnabled() { + return status.getMode() != OperationMode.BASIC; + } + + /** + * Indicates whether the stats and health API calls should be allowed. If a license is expired and past the grace + * period then we deny these calls. + * + * @return true if the license allows for the stats and health apis to be used. + */ + public boolean statsAndHealthEnabled() { + return status.getLicenseState() != LicenseState.DISABLED; + } + + /** + * @return true if the license enables DLS and FLS + */ + public boolean documentAndFieldLevelSecurityEnabled() { + Status status = this.status; + return status.getMode() == OperationMode.PLATINUM || status.getMode() == OperationMode.TRIAL; + } + + void updateStatus(Status status) { + this.status = status; + } +} diff --git a/shield/src/main/java/org/elasticsearch/shield/license/LicenseService.java b/shield/src/main/java/org/elasticsearch/shield/license/ShieldLicensee.java similarity index 59% rename from shield/src/main/java/org/elasticsearch/shield/license/LicenseService.java rename to shield/src/main/java/org/elasticsearch/shield/license/ShieldLicensee.java index 95ae692bfda..82a48bd43a0 100644 --- a/shield/src/main/java/org/elasticsearch/shield/license/LicenseService.java +++ b/shield/src/main/java/org/elasticsearch/shield/license/ShieldLicensee.java @@ -7,37 +7,33 @@ package org.elasticsearch.shield.license; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; +import org.elasticsearch.license.core.License.OperationMode; +import org.elasticsearch.license.plugin.core.*; import org.elasticsearch.shield.ShieldPlugin; /** * */ -public class LicenseService extends AbstractLifecycleComponent implements Licensee { +public class ShieldLicensee extends AbstractLicenseeComponent implements Licensee { - public static final String FEATURE_NAME = ShieldPlugin.NAME; - - private final LicenseeRegistry licenseeRegistry; - private final LicenseEventsNotifier notifier; - - private volatile LicenseState state = LicenseState.DISABLED; + private final boolean isTribeNode; + private final ShieldLicenseState shieldLicenseState; @Inject - public LicenseService(Settings settings, LicenseeRegistry licenseeRegistry, LicenseEventsNotifier notifier) { - super(settings); - this.licenseeRegistry = licenseeRegistry; - this.notifier = notifier; - } - - @Override - public String id() { - return FEATURE_NAME; + public ShieldLicensee(Settings settings, LicenseeRegistry clientService, + LicensesManagerService managerService, ShieldLicenseState shieldLicenseState) { + super(settings, ShieldPlugin.NAME, clientService, managerService); + add(new Listener() { + @Override + public void onChange(License license, Status status) { + shieldLicenseState.updateStatus(status); + } + }); + this.shieldLicenseState = shieldLicenseState; + this.isTribeNode = settings.getGroups("tribe", true).isEmpty() == false; } @Override @@ -78,31 +74,13 @@ public class LicenseService extends AbstractLifecycleComponent i } @Override - public void onChange(License license, LicenseState state) { - synchronized (this) { - this.state = state; - notifier.notify(state); - } - } - public LicenseState state() { - return state; - } - - @Override - protected void doStart() throws ElasticsearchException { - if (settings.getGroups("tribe", true).isEmpty()) { - licenseeRegistry.register(this); - } else { + protected void doStart() throws ElasticsearchException {; + if (isTribeNode) { //TODO currently we disable licensing on tribe node. remove this once es core supports merging cluster - onChange(null, LicenseState.ENABLED); + this.status = new Status(OperationMode.TRIAL, LicenseState.ENABLED); + shieldLicenseState.updateStatus(status); + } else { + super.doStart(); } } - - @Override - protected void doStop() throws ElasticsearchException { - } - - @Override - protected void doClose() throws ElasticsearchException { - } } diff --git a/shield/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java b/shield/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java index 221def2fe18..dbab5d25ec2 100644 --- a/shield/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java +++ b/shield/src/main/java/org/elasticsearch/shield/rest/ShieldRestFilter.java @@ -13,6 +13,7 @@ import org.elasticsearch.http.netty.NettyHttpRequest; import org.elasticsearch.rest.*; import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authc.pki.PkiRealm; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.shield.transport.SSLClientAuth; import org.elasticsearch.shield.transport.netty.ShieldNettyHttpServerTransport; import org.jboss.netty.handler.ssl.SslHandler; @@ -28,11 +29,13 @@ public class ShieldRestFilter extends RestFilter { private final AuthenticationService service; private final ESLogger logger; + private final ShieldLicenseState licenseState; private final boolean extractClientCertificate; @Inject - public ShieldRestFilter(AuthenticationService service, RestController controller, Settings settings) { + public ShieldRestFilter(AuthenticationService service, RestController controller, Settings settings, ShieldLicenseState licenseState) { this.service = service; + this.licenseState = licenseState; controller.registerFilter(this); boolean ssl = settings.getAsBoolean(ShieldNettyHttpServerTransport.HTTP_SSL_SETTING, ShieldNettyHttpServerTransport.HTTP_SSL_DEFAULT); extractClientCertificate = ssl && SSLClientAuth.parse(settings.get(ShieldNettyHttpServerTransport.HTTP_CLIENT_AUTH_SETTING), ShieldNettyHttpServerTransport.HTTP_CLIENT_AUTH_DEFAULT).enabled(); @@ -47,15 +50,17 @@ public class ShieldRestFilter extends RestFilter { @Override public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception { - // CORS - allow for preflight unauthenticated OPTIONS request - if (request.method() != RestRequest.Method.OPTIONS) { - if (extractClientCertificate) { - putClientCertificateInContext(request, logger); + if (licenseState.securityEnabled()) { + // CORS - allow for preflight unauthenticated OPTIONS request + if (request.method() != RestRequest.Method.OPTIONS) { + if (extractClientCertificate) { + putClientCertificateInContext(request, logger); + } + service.authenticate(request); } - service.authenticate(request); - } - RemoteHostHeader.process(request); + RemoteHostHeader.process(request); + } filterChain.continueProcessing(request, channel); } diff --git a/shield/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java b/shield/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java index 86dd59d3137..46b33685ab1 100644 --- a/shield/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java +++ b/shield/src/main/java/org/elasticsearch/shield/rest/action/RestShieldInfoAction.java @@ -12,11 +12,10 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.license.plugin.core.LicenseState; import org.elasticsearch.rest.*; import org.elasticsearch.shield.ShieldBuild; import org.elasticsearch.shield.ShieldPlugin; -import org.elasticsearch.shield.license.LicenseService; +import org.elasticsearch.shield.license.ShieldLicenseState; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.HEAD; @@ -24,14 +23,14 @@ import static org.elasticsearch.rest.RestRequest.Method.HEAD; public class RestShieldInfoAction extends BaseRestHandler { private final ClusterName clusterName; - private final LicenseService licenseService; + private final ShieldLicenseState shieldLicenseState; private final boolean shieldEnabled; @Inject - public RestShieldInfoAction(Settings settings, RestController controller, Client client, ClusterName clusterName, @Nullable LicenseService licenseService) { + public RestShieldInfoAction(Settings settings, RestController controller, Client client, ClusterName clusterName, @Nullable ShieldLicenseState licenseState) { super(settings, controller, client); this.clusterName = clusterName; - this.licenseService = licenseService; + this.shieldLicenseState = licenseState; this.shieldEnabled = ShieldPlugin.shieldEnabled(settings); controller.registerHandler(GET, "/_shield", this); controller.registerHandler(HEAD, "/_shield", this); @@ -72,7 +71,10 @@ public class RestShieldInfoAction extends BaseRestHandler { private Status resolveStatus() { if (shieldEnabled) { - if (licenseService.state() != LicenseState.DISABLED) { + assert shieldLicenseState != null; + // TODO this is error prone since the state could change between checks. We can also make this status better + // but we may remove this endpoint since it no longer serves much purpose + if (shieldLicenseState.securityEnabled() && shieldLicenseState.statsAndHealthEnabled()) { return Status.ENABLED; } return Status.UNLICENSED; diff --git a/shield/src/main/java/org/elasticsearch/shield/transport/ShieldServerTransportService.java b/shield/src/main/java/org/elasticsearch/shield/transport/ShieldServerTransportService.java index 5252e07a4cd..c694d6af651 100644 --- a/shield/src/main/java/org/elasticsearch/shield/transport/ShieldServerTransportService.java +++ b/shield/src/main/java/org/elasticsearch/shield/transport/ShieldServerTransportService.java @@ -12,6 +12,7 @@ import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.authz.accesscontrol.RequestContext; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.shield.transport.netty.ShieldNettyTransport; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.*; @@ -35,6 +36,7 @@ public class ShieldServerTransportService extends TransportService { protected final AuthorizationService authzService; protected final ShieldActionMapper actionMapper; protected final ClientTransportFilter clientFilter; + protected final ShieldLicenseState licenseState; protected final Map profileFilters; @@ -43,12 +45,14 @@ public class ShieldServerTransportService extends TransportService { AuthenticationService authcService, AuthorizationService authzService, ShieldActionMapper actionMapper, - ClientTransportFilter clientTransportFilter) { + ClientTransportFilter clientTransportFilter, + ShieldLicenseState licenseState) { super(settings, transport, threadPool); this.authcService = authcService; this.authzService = authzService; this.actionMapper = actionMapper; this.clientFilter = clientTransportFilter; + this.licenseState = licenseState; this.profileFilters = initializeProfileFilters(); } @@ -64,13 +68,13 @@ public class ShieldServerTransportService extends TransportService { @Override public void registerRequestHandler(String action, Supplier requestFactory, String executor, TransportRequestHandler handler) { - TransportRequestHandler wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters); + TransportRequestHandler wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters, licenseState); super.registerRequestHandler(action, requestFactory, executor, wrappedHandler); } @Override public void registerRequestHandler(String action, Supplier request, String executor, boolean forceExecution, TransportRequestHandler handler) { - TransportRequestHandler wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters); + TransportRequestHandler wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters, licenseState); super.registerRequestHandler(action, request, executor, forceExecution, wrappedHandler); } @@ -104,7 +108,7 @@ public class ShieldServerTransportService extends TransportService { profileFilters.put(NettyTransport.DEFAULT_PROFILE, new ServerTransportFilter.NodeProfile(authcService, authzService, actionMapper, extractClientCert)); } - return profileFilters; + return Collections.unmodifiableMap(profileFilters); } ServerTransportFilter transportFilter(String profile) { @@ -116,30 +120,34 @@ public class ShieldServerTransportService extends TransportService { protected final String action; protected final TransportRequestHandler handler; private final Map profileFilters; + private final ShieldLicenseState licenseState; - public ProfileSecuredRequestHandler(String action, TransportRequestHandler handler, Map profileFilters) { + public ProfileSecuredRequestHandler(String action, TransportRequestHandler handler, Map profileFilters, ShieldLicenseState licenseState) { this.action = action; this.handler = handler; this.profileFilters = profileFilters; + this.licenseState = licenseState; } @Override @SuppressWarnings("unchecked") public void messageReceived(T request, TransportChannel channel) throws Exception { try { - String profile = channel.getProfileName(); - ServerTransportFilter filter = profileFilters.get(profile); + if (licenseState.securityEnabled()) { + String profile = channel.getProfileName(); + ServerTransportFilter filter = profileFilters.get(profile); - if (filter == null) { - if (TransportService.DIRECT_RESPONSE_PROFILE.equals(profile)) { - // apply the default filter to local requests. We never know what the request is or who sent it... - filter = profileFilters.get("default"); - } else { - throw new IllegalStateException("transport profile [" + profile + "] is not associated with a transport filter"); + if (filter == null) { + if (TransportService.DIRECT_RESPONSE_PROFILE.equals(profile)) { + // apply the default filter to local requests. We never know what the request is or who sent it... + filter = profileFilters.get("default"); + } else { + throw new IllegalStateException("transport profile [" + profile + "] is not associated with a transport filter"); + } } + assert filter != null; + filter.inbound(action, request, channel); } - assert filter != null; - filter.inbound(action, request, channel); RequestContext context = new RequestContext(request); RequestContext.setCurrent(context); handler.messageReceived(request, channel); diff --git a/shield/src/main/java/org/elasticsearch/shield/transport/filter/IPFilter.java b/shield/src/main/java/org/elasticsearch/shield/transport/filter/IPFilter.java index 9cbc69811cd..c565fa98612 100644 --- a/shield/src/main/java/org/elasticsearch/shield/transport/filter/IPFilter.java +++ b/shield/src/main/java/org/elasticsearch/shield/transport/filter/IPFilter.java @@ -20,6 +20,7 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.shield.audit.AuditTrail; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.transport.Transport; import java.net.InetAddress; @@ -73,16 +74,19 @@ public class IPFilter extends AbstractLifecycleComponent { private NodeSettingsService nodeSettingsService; private final AuditTrail auditTrail; private final Transport transport; + private final ShieldLicenseState licenseState; private final boolean alwaysAllowBoundAddresses; private Map rules = Collections.EMPTY_MAP; private HttpServerTransport httpServerTransport = null; @Inject - public IPFilter(final Settings settings, AuditTrail auditTrail, NodeSettingsService nodeSettingsService, Transport transport) { + public IPFilter(final Settings settings, AuditTrail auditTrail, NodeSettingsService nodeSettingsService, + Transport transport, ShieldLicenseState licenseState) { super(settings); this.nodeSettingsService = nodeSettingsService; this.auditTrail = auditTrail; this.transport = transport; + this.licenseState = licenseState; this.alwaysAllowBoundAddresses = settings.getAsBoolean("shield.filter.always_allow_bound_address", true); } @@ -122,7 +126,12 @@ public class IPFilter extends AbstractLifecycleComponent { } public boolean accept(String profile, InetAddress peerAddress) { + if (licenseState.securityEnabled() == false) { + return true; + } + if (!rules.containsKey(profile)) { + // FIXME we need to audit here return true; } diff --git a/shield/src/test/java/org/elasticsearch/integration/LicensingTests.java b/shield/src/test/java/org/elasticsearch/integration/LicensingTests.java index 1be68833056..bd7e79dbb62 100644 --- a/shield/src/test/java/org/elasticsearch/integration/LicensingTests.java +++ b/shield/src/test/java/org/elasticsearch/integration/LicensingTests.java @@ -13,26 +13,31 @@ import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; +import org.elasticsearch.client.support.Headers; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.core.License; +import org.elasticsearch.license.core.License.OperationMode; import org.elasticsearch.license.plugin.core.LicenseState; import org.elasticsearch.license.plugin.core.Licensee; import org.elasticsearch.license.plugin.core.LicenseeRegistry; +import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.shield.license.LicenseService; +import org.elasticsearch.shield.ShieldPlugin; +import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.test.ShieldIntegTestCase; import org.elasticsearch.test.ShieldSettingsSource; +import org.elasticsearch.transport.Transport; +import org.junit.After; import org.junit.Test; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; @@ -95,6 +100,18 @@ public class LicensingTests extends ShieldIntegTestCase { return InternalLicensePlugin.NAME; } + @Override + public Settings nodeSettings(int nodeOrdinal) { + return Settings.builder().put(super.nodeSettings(nodeOrdinal)) + .put(Node.HTTP_ENABLED, true) + .build(); + } + + @After + public void resetLicensing() { + enableLicensing(); + } + @Test public void testEnableDisableBehaviour() throws Exception { IndexResponse indexResponse = index("test", "type", jsonBuilder() @@ -121,7 +138,7 @@ public class LicensingTests extends ShieldIntegTestCase { fail("expected an license expired exception when executing an index stats action"); } catch (ElasticsearchSecurityException ee) { // expected - assertThat(ee.getHeader("es.license.expired.feature"), hasItem(LicenseService.FEATURE_NAME)); + assertThat(ee.getHeader("es.license.expired.feature"), hasItem(ShieldPlugin.NAME)); assertThat(ee.status(), is(RestStatus.UNAUTHORIZED)); } @@ -130,7 +147,7 @@ public class LicensingTests extends ShieldIntegTestCase { fail("expected an license expired exception when executing cluster stats action"); } catch (ElasticsearchSecurityException ee) { // expected - assertThat(ee.getHeader("es.license.expired.feature"), hasItem(LicenseService.FEATURE_NAME)); + assertThat(ee.getHeader("es.license.expired.feature"), hasItem(ShieldPlugin.NAME)); assertThat(ee.status(), is(RestStatus.UNAUTHORIZED)); } @@ -139,7 +156,7 @@ public class LicensingTests extends ShieldIntegTestCase { fail("expected an license expired exception when executing cluster health action"); } catch (ElasticsearchSecurityException ee) { // expected - assertThat(ee.getHeader("es.license.expired.feature"), hasItem(LicenseService.FEATURE_NAME)); + assertThat(ee.getHeader("es.license.expired.feature"), hasItem(ShieldPlugin.NAME)); assertThat(ee.status(), is(RestStatus.UNAUTHORIZED)); } @@ -148,11 +165,11 @@ public class LicensingTests extends ShieldIntegTestCase { fail("expected an license expired exception when executing cluster health action"); } catch (ElasticsearchSecurityException ee) { // expected - assertThat(ee.getHeader("es.license.expired.feature"), hasItem(LicenseService.FEATURE_NAME)); + assertThat(ee.getHeader("es.license.expired.feature"), hasItem(ShieldPlugin.NAME)); assertThat(ee.status(), is(RestStatus.UNAUTHORIZED)); } - enableLicensing(); + enableLicensing(LicensingTests.generateLicense(randomFrom(OperationMode.values()))); IndicesStatsResponse indicesStatsResponse = client.admin().indices().prepareStats().get(); assertNoFailures(indicesStatsResponse); @@ -170,18 +187,77 @@ public class LicensingTests extends ShieldIntegTestCase { assertThat(nodeStats, notNullValue()); } + @Test + public void testRestAuthenticationByLicenseType() throws Exception { + // the default of the licensing tests is basic + assertThat(httpClient().path("/").execute().getStatusCode(), is(200)); + + // generate a new license with a mode that enables auth + OperationMode mode = randomFrom(OperationMode.GOLD, OperationMode.TRIAL, OperationMode.PLATINUM); + enableLicensing(generateLicense(mode)); + assertThat(httpClient().path("/").execute().getStatusCode(), is(401)); + } + + @Test + public void testTransportClientAuthenticationByLicenseType() throws Exception { + Settings.Builder builder = Settings.builder() + .put(internalCluster().transportClient().settings()); + // remove user info + builder.remove("shield.user"); + builder.remove(Headers.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER); + + // basic has no auth + try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(ShieldPlugin.class).build()) { + client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); + assertGreenClusterState(client); + } + + // enable a license that enables security + OperationMode mode = randomFrom(OperationMode.GOLD, OperationMode.PLATINUM, OperationMode.TRIAL); + enableLicensing(generateLicense(mode)); + + try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(ShieldPlugin.class).build()) { + client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); + client.admin().cluster().prepareHealth().get(); + fail("should not have been able to connect to a node!"); + } catch (NoNodeAvailableException e) { + // expected + } + } + public static void disableLicensing() { + disableLicensing(InternalLicenseeRegistry.DUMMY_LICENSE); + } + + public static void disableLicensing(License license) { for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) { - service.disable(); + service.disable(license); } } public static void enableLicensing() { + enableLicensing(InternalLicenseeRegistry.DUMMY_LICENSE); + } + + public static void enableLicensing(License license) { for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) { - service.enable(); + service.enable(license); } } + public static License generateLicense(OperationMode operationMode) { + return License.builder() + .expiryDate(System.currentTimeMillis()) + .issueDate(System.currentTimeMillis()) + .issuedTo("LicensingTests") + .issuer("test") + .maxNodes(Integer.MAX_VALUE) + .signature("_signature") + .type(operationMode.toString().toLowerCase(Locale.ROOT)) + .uid(String.valueOf(randomLong()) + System.identityHashCode(LicensingTests.class)) + .build(); + } + public static class InternalLicensePlugin extends Plugin { public static final String NAME = "internal-licensing"; @@ -228,24 +304,24 @@ public class LicensingTests extends ShieldIntegTestCase { @Inject public InternalLicenseeRegistry(Settings settings) { super(settings); - enable(); + enable(DUMMY_LICENSE); } @Override public void register(Licensee licensee) { licensees.add(licensee); - enable(); + enable(DUMMY_LICENSE); } - void enable() { + void enable(License license) { for (Licensee licensee : licensees) { - licensee.onChange(DUMMY_LICENSE, LicenseState.ENABLED); + licensee.onChange(license, LicenseState.ENABLED); } } - void disable() { + void disable(License license) { for (Licensee licensee : licensees) { - licensee.onChange(DUMMY_LICENSE, LicenseState.DISABLED); + licensee.onChange(license, LicenseState.DISABLED); } } } diff --git a/shield/src/test/java/org/elasticsearch/shield/ShieldPluginEnabledDisabledTests.java b/shield/src/test/java/org/elasticsearch/shield/ShieldPluginEnabledDisabledTests.java index 5063c81298a..dd24dca63f5 100644 --- a/shield/src/test/java/org/elasticsearch/shield/ShieldPluginEnabledDisabledTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/ShieldPluginEnabledDisabledTests.java @@ -10,6 +10,7 @@ import org.apache.http.impl.client.HttpClients; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.integration.LicensingTests; +import org.elasticsearch.license.core.License.OperationMode; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.shield.authc.support.SecuredString; @@ -104,11 +105,31 @@ public class ShieldPluginEnabledDisabledTests extends ShieldIntegTestCase { @Test public void testShieldInfoStatus() throws IOException { HttpServerTransport httpServerTransport = internalCluster().getDataNodeInstance(HttpServerTransport.class); + OperationMode mode; + if (enabled) { + mode = randomFrom(OperationMode.values()); + LicensingTests.enableLicensing(LicensingTests.generateLicense(mode)); + } else { + // this is the default right now + mode = OperationMode.BASIC; + } + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpResponse response = new HttpRequestBuilder(httpClient).httpTransport(httpServerTransport).method("GET").path("/_shield").addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, basicAuthHeaderValue(ShieldSettingsSource.DEFAULT_USER_NAME, new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray()))).execute(); assertThat(response.getStatusCode(), is(OK.getStatus())); - assertThat(new JsonPath(response.getBody()).evaluate("status").toString(), equalTo(enabled ? "enabled" : "disabled")); + + String expectedValue; + if (enabled) { + if (mode == OperationMode.BASIC) { + expectedValue = "unlicensed"; + } else { + expectedValue = "enabled"; + } + } else { + expectedValue = "disabled"; + } + assertThat(new JsonPath(response.getBody()).evaluate("status").toString(), equalTo(expectedValue)); if (enabled) { LicensingTests.disableLicensing(); diff --git a/shield/src/test/java/org/elasticsearch/shield/VersionCompatibilityTests.java b/shield/src/test/java/org/elasticsearch/shield/VersionCompatibilityTests.java index 5980ad6c75e..4d8a02b1779 100644 --- a/shield/src/test/java/org/elasticsearch/shield/VersionCompatibilityTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/VersionCompatibilityTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.shield; import org.elasticsearch.Version; +import org.elasticsearch.shield.license.ShieldLicensee; import org.elasticsearch.test.ESTestCase; import org.junit.Test; @@ -28,7 +29,7 @@ public class VersionCompatibilityTests extends ESTestCase { @Test public void testCompatibility() { /** - * see https://github.com/elasticsearch/elasticsearch/issues/9372 {@link org.elasticsearch.shield.license.LicenseService} + * see https://github.com/elasticsearch/elasticsearch/issues/9372 {@link ShieldLicensee} * Once es core supports merging cluster level custom metadata (licenses in our case), the tribe node will see some license coming from the tribe and everything will be ok. * */ diff --git a/shield/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java b/shield/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java index 99d0d9e7192..2c4a8fbd42c 100644 --- a/shield/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/action/ShieldActionFilterTests.java @@ -11,14 +11,13 @@ import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicenseState; import org.elasticsearch.shield.User; import org.elasticsearch.shield.action.interceptor.RequestInterceptor; import org.elasticsearch.shield.audit.AuditTrail; import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authz.AuthorizationService; import org.elasticsearch.shield.crypto.CryptoService; -import org.elasticsearch.shield.license.LicenseEventsNotifier; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESTestCase; import org.junit.Before; import org.junit.Test; @@ -39,7 +38,7 @@ public class ShieldActionFilterTests extends ESTestCase { private AuthorizationService authzService; private CryptoService cryptoService; private AuditTrail auditTrail; - private LicenseEventsNotifier licenseEventsNotifier; + private ShieldLicenseState shieldLicenseState; private ShieldActionFilter filter; @Before @@ -48,8 +47,10 @@ public class ShieldActionFilterTests extends ESTestCase { authzService = mock(AuthorizationService.class); cryptoService = mock(CryptoService.class); auditTrail = mock(AuditTrail.class); - licenseEventsNotifier = new MockLicenseEventsNotifier(); - filter = new ShieldActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, licenseEventsNotifier, new ShieldActionMapper(), new HashSet()); + shieldLicenseState = mock(ShieldLicenseState.class); + when(shieldLicenseState.securityEnabled()).thenReturn(true); + when(shieldLicenseState.statsAndHealthEnabled()).thenReturn(true); + filter = new ShieldActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, shieldLicenseState, new ShieldActionMapper(), new HashSet()); } @Test @@ -110,10 +111,16 @@ public class ShieldActionFilterTests extends ESTestCase { verifyNoMoreInteractions(chain); } - private class MockLicenseEventsNotifier extends LicenseEventsNotifier { - @Override - public void register(MockLicenseEventsNotifier.Listener listener) { - listener.notify(LicenseState.ENABLED); - } + @Test + public void testApplyUnlicensed() throws Exception { + ActionRequest request = mock(ActionRequest.class); + ActionListener listener = mock(ActionListener.class); + ActionFilterChain chain = mock(ActionFilterChain.class); + when(shieldLicenseState.securityEnabled()).thenReturn(false); + filter.apply("_action", request, listener, chain); + verifyZeroInteractions(authcService); + verifyZeroInteractions(authzService); + verify(chain).proceed(eq("_action"), eq(request), eq(listener)); } + } diff --git a/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperIntegrationTests.java b/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperIntegrationTests.java index dfe6818be90..4a7c365316c 100644 --- a/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperIntegrationTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperIntegrationTests.java @@ -37,8 +37,8 @@ import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.query.ParsedQuery; import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.indices.IndicesLifecycle; import org.elasticsearch.shield.authz.InternalAuthorizationService; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; import org.mockito.Matchers; @@ -78,7 +78,6 @@ public class ShieldIndexSearcherWrapperIntegrationTests extends ESTestCase { request.putInContext(InternalAuthorizationService.INDICES_PERMISSIONS_KEY, new IndicesAccessControl(true, singletonMap("_index", indexAccessControl))); IndexQueryParserService parserService = mock(IndexQueryParserService.class); - IndicesLifecycle indicesLifecycle = mock(IndicesLifecycle.class); BitsetFilterCache bitsetFilterCache = mock(BitsetFilterCache.class); when(bitsetFilterCache.getBitSetProducer(Matchers.any(Query.class))).then(new Answer() { @Override @@ -100,8 +99,10 @@ public class ShieldIndexSearcherWrapperIntegrationTests extends ESTestCase { }; } }); + ShieldLicenseState licenseState = mock(ShieldLicenseState.class); + when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(true); ShieldIndexSearcherWrapper wrapper = new ShieldIndexSearcherWrapper( - Settings.EMPTY, parserService, mapperService, bitsetFilterCache + Settings.EMPTY, parserService, mapperService, bitsetFilterCache, licenseState ); Directory directory = newDirectory(); diff --git a/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperUnitTests.java b/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperUnitTests.java index b34c5ae529f..304e87242d2 100644 --- a/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperUnitTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapperUnitTests.java @@ -47,6 +47,7 @@ import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.LeafBucketCollector; import org.elasticsearch.shield.authz.InternalAuthorizationService; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; import org.junit.After; @@ -73,6 +74,7 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase { private MapperService mapperService; private ShieldIndexSearcherWrapper shieldIndexSearcherWrapper; private ElasticsearchDirectoryReader esIn; + private ShieldLicenseState licenseState; @Before public void before() throws Exception { @@ -84,7 +86,9 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase { mapperService = new MapperService(index, settings, analysisService, similarityService, scriptService); shardId = new ShardId(index, 0); - shieldIndexSearcherWrapper = new ShieldIndexSearcherWrapper(settings, null, mapperService, null); + licenseState = mock(ShieldLicenseState.class); + when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(true); + shieldIndexSearcherWrapper = new ShieldIndexSearcherWrapper(settings, null, mapperService, null, licenseState); IndexShard indexShard = mock(IndexShard.class); when(indexShard.shardId()).thenReturn(shardId); @@ -130,6 +134,21 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase { assertThat(result.getFieldNames().contains("_all"), is(false)); // _all contains actual user data and therefor can't be included by default } + public void testWrapReaderWhenFeatureDisabled() throws Exception { + when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(false); + DirectoryReader reader = shieldIndexSearcherWrapper.wrap(esIn); + assertThat(reader, sameInstance(esIn)); + } + + public void testWrapSearcherWhenFeatureDisabled() throws Exception { + ShardId shardId = new ShardId("_index", 0); + EngineConfig engineConfig = new EngineConfig(shardId, null, null, Settings.EMPTY, null, null, null, null, null, null, new BM25Similarity(), null, null, null, new NoneQueryCache(shardId.index(), Settings.EMPTY), QueryCachingPolicy.ALWAYS_CACHE, null); // can't mock... + + IndexSearcher indexSearcher = new IndexSearcher(esIn); + IndexSearcher result = shieldIndexSearcherWrapper.wrap(engineConfig, indexSearcher); + assertThat(result, sameInstance(indexSearcher)); + } + public void testWildcards() throws Exception { XContentBuilder mappingSource = jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("field1_a").field("type", "string").endObject() diff --git a/shield/src/test/java/org/elasticsearch/shield/license/ShieldLicenseStateTests.java b/shield/src/test/java/org/elasticsearch/shield/license/ShieldLicenseStateTests.java new file mode 100644 index 00000000000..4d24e0aed82 --- /dev/null +++ b/shield/src/test/java/org/elasticsearch/shield/license/ShieldLicenseStateTests.java @@ -0,0 +1,80 @@ +/* + * 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.license; + +import org.elasticsearch.license.core.License; +import org.elasticsearch.license.plugin.core.LicenseState; +import org.elasticsearch.license.plugin.core.Licensee; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.*; + +/** + * Unit tests for the {@link ShieldLicenseState} + */ +public class ShieldLicenseStateTests extends ESTestCase { + + public void testDefaults() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + assertThat(licenseState.securityEnabled(), is(true)); + assertThat(licenseState.statsAndHealthEnabled(), is(true)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); + } + + public void testBasic() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.BASIC, randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); + + assertThat(licenseState.securityEnabled(), is(false)); + assertThat(licenseState.statsAndHealthEnabled(), is(true)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); + } + + public void testBasicExpired() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.BASIC, LicenseState.DISABLED)); + + assertThat(licenseState.securityEnabled(), is(false)); + assertThat(licenseState.statsAndHealthEnabled(), is(false)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); + } + + public void testGold() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.GOLD, randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); + + assertThat(licenseState.securityEnabled(), is(true)); + assertThat(licenseState.statsAndHealthEnabled(), is(true)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); + } + + public void testGoldExpired() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.GOLD, LicenseState.DISABLED)); + + assertThat(licenseState.securityEnabled(), is(true)); + assertThat(licenseState.statsAndHealthEnabled(), is(false)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); + } + + public void testPlatinum() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.PLATINUM, randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); + + assertThat(licenseState.securityEnabled(), is(true)); + assertThat(licenseState.statsAndHealthEnabled(), is(true)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); + } + + public void testPlatinumExpired() { + ShieldLicenseState licenseState = new ShieldLicenseState(); + licenseState.updateStatus(new Licensee.Status(License.OperationMode.PLATINUM, LicenseState.DISABLED)); + + assertThat(licenseState.securityEnabled(), is(true)); + assertThat(licenseState.statsAndHealthEnabled(), is(false)); + assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); + } +} diff --git a/shield/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java b/shield/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java index 73ca0366161..3a8272c0cb4 100644 --- a/shield/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/rest/ShieldRestFilterTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.rest.RestFilterChain; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.shield.User; import org.elasticsearch.shield.authc.AuthenticationService; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESTestCase; import org.junit.Before; import org.junit.Test; @@ -30,6 +31,7 @@ public class ShieldRestFilterTests extends ESTestCase { private RestChannel channel; private RestFilterChain chain; private ShieldRestFilter filter; + private ShieldLicenseState licenseState; @Before public void init() throws Exception { @@ -37,7 +39,9 @@ public class ShieldRestFilterTests extends ESTestCase { RestController restController = mock(RestController.class); channel = mock(RestChannel.class); chain = mock(RestFilterChain.class); - filter = new ShieldRestFilter(authcService, restController, Settings.EMPTY); + licenseState = mock(ShieldLicenseState.class); + when(licenseState.securityEnabled()).thenReturn(true); + filter = new ShieldRestFilter(authcService, restController, Settings.EMPTY, licenseState); verify(restController).registerFilter(filter); } @@ -51,6 +55,15 @@ public class ShieldRestFilterTests extends ESTestCase { verifyZeroInteractions(channel); } + @Test + public void testProcessBasicLicense() throws Exception { + RestRequest request = mock(RestRequest.class); + when(licenseState.securityEnabled()).thenReturn(false); + filter.process(request, channel, chain); + verify(chain).continueProcessing(request, channel); + verifyZeroInteractions(channel, authcService); + } + @Test public void testProcess_AuthenticationError() throws Exception { RestRequest request = mock(RestRequest.class); diff --git a/shield/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java b/shield/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java index 74d1e1752a7..c2d2cbce353 100644 --- a/shield/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/transport/TransportFilterTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.shield.action.ShieldActionMapper; import org.elasticsearch.shield.authc.AuthenticationService; import org.elasticsearch.shield.authz.AuthorizationService; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.*; @@ -298,7 +299,8 @@ public class TransportFilterTests extends ESIntegTestCase { @Inject public InternalPluginServerTransportService(Settings settings, Transport transport, ThreadPool threadPool, AuthenticationService authcService, AuthorizationService authzService, ShieldActionMapper actionMapper, ClientTransportFilter clientTransportFilter) { - super(settings, transport, threadPool, authcService, authzService, actionMapper, clientTransportFilter); + super(settings, transport, threadPool, authcService, authzService, actionMapper, clientTransportFilter, mock(ShieldLicenseState.class)); + when(licenseState.securityEnabled()).thenReturn(true); } protected Map initializeProfileFilters() { diff --git a/shield/src/test/java/org/elasticsearch/shield/transport/filter/IPFilterTests.java b/shield/src/test/java/org/elasticsearch/shield/transport/filter/IPFilterTests.java index dfed968f21f..8c92e710319 100644 --- a/shield/src/test/java/org/elasticsearch/shield/transport/filter/IPFilterTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/transport/filter/IPFilterTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.shield.audit.AuditTrail; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.junit.annotations.Network; import org.elasticsearch.transport.Transport; @@ -35,6 +36,7 @@ import static org.mockito.Mockito.*; public class IPFilterTests extends ESTestCase { private IPFilter ipFilter; + private ShieldLicenseState licenseState; private AuditTrail auditTrail; private Transport transport; private HttpServerTransport httpTransport; @@ -42,6 +44,8 @@ public class IPFilterTests extends ESTestCase { @Before public void init() { + licenseState = mock(ShieldLicenseState.class); + when(licenseState.securityEnabled()).thenReturn(true); auditTrail = mock(AuditTrail.class); nodeSettingsService = mock(NodeSettingsService.class); @@ -66,7 +70,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.transport.filter.allow", "127.0.0.1") .put("shield.transport.filter.deny", "10.0.0.0/8") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("127.0.0.1"); assertAddressIsDenied("10.2.3.4"); @@ -80,7 +84,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.transport.filter.allow", "2001:0db8:1234::/48") .putArray("shield.transport.filter.deny", "1234:db8:85a3:0:0:8a2e:370:7334", "4321:db8:1234::/48") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("2001:0db8:1234:0000:0000:8a2e:0370:7334"); assertAddressIsDenied("1234:0db8:85a3:0000:0000:8a2e:0370:7334"); @@ -94,7 +98,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.transport.filter.allow", "127.0.0.1") .put("shield.transport.filter.deny", "*.google.com") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("127.0.0.1"); assertAddressIsDenied("8.8.8.8"); @@ -105,7 +109,7 @@ public class IPFilterTests extends ESTestCase { Settings settings = settingsBuilder() .put("shield.transport.filter.allow", "_all") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("127.0.0.1"); assertAddressIsAllowed("173.194.70.100"); @@ -119,7 +123,7 @@ public class IPFilterTests extends ESTestCase { .put("transport.profiles.client.shield.filter.allow", "192.168.0.1") .put("transport.profiles.client.shield.filter.deny", "_all") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("127.0.0.1"); assertAddressIsDenied("192.168.0.1"); @@ -133,7 +137,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.transport.filter.allow", "10.0.0.1") .put("shield.transport.filter.deny", "10.0.0.0/8") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("10.0.0.1"); assertAddressIsDenied("10.0.0.2"); @@ -142,7 +146,7 @@ public class IPFilterTests extends ESTestCase { @Test public void testDefaultAllow() throws Exception { Settings settings = settingsBuilder().build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); assertAddressIsAllowed("10.0.0.1"); assertAddressIsAllowed("10.0.0.2"); @@ -156,7 +160,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.http.filter.allow", "10.0.0.0/8") .put("shield.http.filter.deny", "192.168.0.1") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); ipFilter.setHttpServerTransport(httpTransport); assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "10.2.3.4"); @@ -169,7 +173,7 @@ public class IPFilterTests extends ESTestCase { .put("shield.transport.filter.allow", "127.0.0.1") .put("shield.transport.filter.deny", "10.0.0.0/8") .build(); - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); ipFilter.setHttpServerTransport(httpTransport); assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1"); @@ -189,7 +193,7 @@ public class IPFilterTests extends ESTestCase { } else { settings = settingsBuilder().put("shield.transport.filter.deny", "_all").build(); } - ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start(); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); ipFilter.setHttpServerTransport(httpTransport); for (String addressString : addressStrings) { @@ -198,6 +202,26 @@ public class IPFilterTests extends ESTestCase { } } + @Test + public void testThatAllAddressesAreAllowedWhenLicenseDisablesSecurity() { + Settings settings = settingsBuilder() + .put("shield.transport.filter.deny", "_all") + .build(); + when(licenseState.securityEnabled()).thenReturn(false); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); + + // don't use the assert helper because we don't want the audit trail to be invoked here + String message = String.format(Locale.ROOT, "Expected address %s to be allowed", "8.8.8.8"); + InetAddress address = InetAddresses.forString("8.8.8.8"); + assertThat(message, ipFilter.accept("default", address), is(true)); + verifyZeroInteractions(auditTrail); + + // for sanity enable license and check that it is denied + when(licenseState.securityEnabled()).thenReturn(true); + ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport, licenseState).start(); + assertAddressIsDeniedForProfile("default", "8.8.8.8"); + } + private void assertAddressIsAllowedForProfile(String profile, String ... inetAddresses) { for (String inetAddress : inetAddresses) { String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress); diff --git a/shield/src/test/java/org/elasticsearch/shield/transport/netty/IPFilterNettyUpstreamHandlerTests.java b/shield/src/test/java/org/elasticsearch/shield/transport/netty/IPFilterNettyUpstreamHandlerTests.java index f0f35554ab6..478bda5fa54 100644 --- a/shield/src/test/java/org/elasticsearch/shield/transport/netty/IPFilterNettyUpstreamHandlerTests.java +++ b/shield/src/test/java/org/elasticsearch/shield/transport/netty/IPFilterNettyUpstreamHandlerTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.settings.NodeSettingsService; import org.elasticsearch.shield.audit.AuditTrail; +import org.elasticsearch.shield.license.ShieldLicenseState; import org.elasticsearch.shield.transport.filter.IPFilter; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.Transport; @@ -52,7 +53,9 @@ public class IPFilterNettyUpstreamHandlerTests extends ESTestCase { when(transport.lifecycleState()).thenReturn(Lifecycle.State.STARTED); NodeSettingsService nodeSettingsService = mock(NodeSettingsService.class); - IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP, nodeSettingsService, transport).start(); + ShieldLicenseState licenseState = mock(ShieldLicenseState.class); + when(licenseState.securityEnabled()).thenReturn(true); + IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP, nodeSettingsService, transport, licenseState).start(); if (isHttpEnabled) { HttpServerTransport httpTransport = mock(HttpServerTransport.class); diff --git a/shield/src/test/java/org/elasticsearch/test/ShieldIntegTestCase.java b/shield/src/test/java/org/elasticsearch/test/ShieldIntegTestCase.java index 1df9c56ea2a..18c5b27f0b7 100644 --- a/shield/src/test/java/org/elasticsearch/test/ShieldIntegTestCase.java +++ b/shield/src/test/java/org/elasticsearch/test/ShieldIntegTestCase.java @@ -12,7 +12,6 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.PluginInfo; import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.test.ESIntegTestCase.SuppressLocalMode; @@ -22,8 +21,6 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.rules.ExternalResource; -import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors;