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@442514496d
This commit is contained in:
jaymode 2015-10-12 12:31:13 -04:00
parent 704f9b12bc
commit 28948f8930
25 changed files with 471 additions and 205 deletions

View File

@ -52,6 +52,9 @@ public class MarvelLicensee extends AbstractLicenseeComponent<MarvelLicensee> 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;
}

View File

@ -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.<LicenseService>of(null));
bind(ShieldLicenseState.class).toProvider(Providers.<ShieldLicenseState>of(null));
}
}
}

View File

@ -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<Class<? extends LifecycleComponent>> nodeServices() {
if (enabled && clientMode == false) {
return Arrays.<Class<? extends LifecycleComponent>>asList(LicenseService.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class);
return Arrays.<Class<? extends LifecycleComponent>>asList(ShieldLicensee.class, InternalCryptoService.class, FileRolesStore.class, Realms.class, IPFilter.class);
}
return Collections.emptyList();
}

View File

@ -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<RequestInterceptor> 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<RequestInterceptor> requestInterceptors) {
AuditTrail auditTrail, ShieldLicenseState licenseState, ShieldActionMapper actionMapper, Set<RequestInterceptor> 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);
}

View File

@ -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<String> 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<String> 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<String> 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:

View File

@ -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<Listener> 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);
}
}

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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<LicenseService> implements Licensee {
public class ShieldLicensee extends AbstractLicenseeComponent<ShieldLicensee> 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<LicenseService> 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 {
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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<String, ServerTransportFilter> 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 <Request extends TransportRequest> void registerRequestHandler(String action, Supplier<Request> requestFactory, String executor, TransportRequestHandler<Request> handler) {
TransportRequestHandler<Request> wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters);
TransportRequestHandler<Request> wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters, licenseState);
super.registerRequestHandler(action, requestFactory, executor, wrappedHandler);
}
@Override
public <Request extends TransportRequest> void registerRequestHandler(String action, Supplier<Request> request, String executor, boolean forceExecution, TransportRequestHandler<Request> handler) {
TransportRequestHandler<Request> wrappedHandler = new ProfileSecuredRequestHandler<>(action, handler, profileFilters);
TransportRequestHandler<Request> 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<T> handler;
private final Map<String, ServerTransportFilter> profileFilters;
private final ShieldLicenseState licenseState;
public ProfileSecuredRequestHandler(String action, TransportRequestHandler<T> handler, Map<String, ServerTransportFilter> profileFilters) {
public ProfileSecuredRequestHandler(String action, TransportRequestHandler<T> handler, Map<String, ServerTransportFilter> 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);

View File

@ -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<IPFilter> {
private NodeSettingsService nodeSettingsService;
private final AuditTrail auditTrail;
private final Transport transport;
private final ShieldLicenseState licenseState;
private final boolean alwaysAllowBoundAddresses;
private Map<String, ShieldIpFilterRule[]> 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<IPFilter> {
}
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;
}

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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.
*
*/

View File

@ -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<RequestInterceptor>());
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<RequestInterceptor>());
}
@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));
}
}

View File

@ -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<BitSetProducer>() {
@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();

View File

@ -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()

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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<String, ServerTransportFilter> initializeProfileFilters() {

View File

@ -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);

View File

@ -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);

View File

@ -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;