Security on Basic License

This adds support for using security on a basic license.
It includes:

- AllowedRealmType.NATIVE realms (reserved, native, file)
- Roles / RBAC
- TLS (already supported)

It does not support:

- Audit
- IP filters
- Token Service & API Keys
- Advanced realms (AD, LDAP, SAML, etc)
- Advanced roles (DLS, FLS)
- Pluggable security

As with trial licences, security is disabled by default.

This commit does not include any new automated tests, but existing tests have been updated.
This commit is contained in:
Tim Vernum 2019-04-11 10:30:56 +10:00 committed by Jason Tedor
parent f08ac103ee
commit 0ee16d0115
No known key found for this signature in database
GPG Key ID: FA89F05560F16BC5
32 changed files with 607 additions and 220 deletions

View File

@ -126,6 +126,10 @@ public class License implements ToXContentObject {
throw new IllegalArgumentException("unknown type [" + type + "]");
}
}
public String description() {
return name().toLowerCase(Locale.ROOT);
}
}
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,

View File

@ -331,8 +331,17 @@ public class XPackLicenseState {
OperationMode mode = status.mode;
final boolean isSecurityCurrentlyEnabled =
isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled);
return isSecurityCurrentlyEnabled && (mode == OperationMode.STANDARD || mode == OperationMode.GOLD
|| mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL);
if (isSecurityCurrentlyEnabled) {
switch (mode) {
case BASIC:
case STANDARD:
case GOLD:
case PLATINUM:
case TRIAL:
return true;
}
}
return false;
}
/**
@ -405,6 +414,7 @@ public class XPackLicenseState {
return AllowedRealmType.ALL;
case GOLD:
return AllowedRealmType.DEFAULT;
case BASIC:
case STANDARD:
return AllowedRealmType.NATIVE;
default:
@ -425,6 +435,24 @@ public class XPackLicenseState {
&& status.active;
}
/**
* @return whether the Elasticsearch {@code TokenService} is allowed based on the license {@link OperationMode}
*/
public synchronized boolean isTokenServiceAllowed() {
final OperationMode mode = status.mode;
final boolean isSecurityCurrentlyEnabled = isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled);
return isSecurityCurrentlyEnabled && (mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL);
}
/**
* @return whether the Elasticsearch {@code ApiKeyService} is allowed based on the license {@link OperationMode}
*/
public synchronized boolean isApiKeyServiceAllowed() {
final OperationMode mode = status.mode;
final boolean isSecurityCurrentlyEnabled = isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled);
return isSecurityCurrentlyEnabled && (mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL);
}
/**
* @return whether "authorization_realms" are allowed based on the license {@link OperationMode}
* @see org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings
@ -674,24 +702,35 @@ public class XPackLicenseState {
public synchronized boolean isSecurityAvailable() {
OperationMode mode = status.mode;
return mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.STANDARD ||
mode == OperationMode.TRIAL;
mode == OperationMode.TRIAL || mode == OperationMode.BASIC;
}
/**
* @return true if security has been disabled by a trial license which is the case of the
* default distribution post 6.3.0. The conditions necessary for this are:
* @return true if security has been disabled due it being the default setting for this license type.
* The conditions necessary for this are:
* <ul>
* <li>A trial license</li>
* <li>A trial or basic license</li>
* <li>xpack.security.enabled not specified as a setting</li>
* </ul>
*/
public synchronized boolean isSecurityDisabledByTrialLicense() {
return status.mode == OperationMode.TRIAL && isSecurityEnabled && isSecurityExplicitlyEnabled == false;
public synchronized boolean isSecurityDisabledByLicenseDefaults() {
switch (status.mode) {
case TRIAL:
case BASIC:
return isSecurityEnabled && isSecurityExplicitlyEnabled == false;
}
return false;
}
private static boolean isSecurityEnabled(final OperationMode mode, final boolean isSecurityExplicitlyEnabled,
final boolean isSecurityEnabled) {
return mode == OperationMode.TRIAL ? isSecurityExplicitlyEnabled : isSecurityEnabled;
switch (mode) {
case TRIAL:
case BASIC:
return isSecurityExplicitlyEnabled;
default:
return isSecurityEnabled;
}
}
/**

View File

@ -20,7 +20,6 @@ import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.XPackBuild;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
@ -51,7 +50,7 @@ public class TransportXPackInfoAction extends HandledTransportAction<XPackInfoRe
if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) {
License license = licenseService.getLicense();
if (license != null) {
licenseInfo = new LicenseInfo(license.uid(), license.type(), license.operationMode().name().toLowerCase(Locale.ROOT),
licenseInfo = new LicenseInfo(license.uid(), license.type(), license.operationMode().description(),
license.status(), license.expiryDate());
}
}

View File

@ -92,17 +92,41 @@ public class XPackLicenseStateTests extends ESTestCase {
assertSecurityNotAllowed(licenseState);
}
public void testSecurityBasic() {
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
public void testSecurityBasicWithoutExplicitSecurityEnabled() {
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
licenseState.update(BASIC, true, null);
assertSecurityNotAllowed(licenseState);
assertThat(licenseState.isAuthAllowed(), is(false));
assertThat(licenseState.isIpFilteringAllowed(), is(false));
assertThat(licenseState.isAuditingAllowed(), is(false));
assertThat(licenseState.isStatsAndHealthAllowed(), is(true));
assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false));
assertThat(licenseState.allowedRealmType(), is(XPackLicenseState.AllowedRealmType.NONE));
assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false));
assertThat(licenseState.isSecurityAvailable(), is(true));
assertThat(licenseState.isSecurityDisabledByLicenseDefaults(), is(true));
}
public void testSecurityBasicExpired() {
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
public void testSecurityBasicWithExplicitSecurityEnabled() {
final Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build();
XPackLicenseState licenseState = new XPackLicenseState(settings);
licenseState.update(BASIC, true, null);
assertThat(licenseState.isAuthAllowed(), is(true));
assertThat(licenseState.isIpFilteringAllowed(), is(false));
assertThat(licenseState.isAuditingAllowed(), is(false));
assertThat(licenseState.isStatsAndHealthAllowed(), is(true));
assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false));
assertThat(licenseState.allowedRealmType(), is(XPackLicenseState.AllowedRealmType.NATIVE));
assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false));
assertThat(licenseState.isSecurityAvailable(), is(true));
assertThat(licenseState.isSecurityDisabledByLicenseDefaults(), is(false));
}
public void testSecurityDefaultBasicExpired() {
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
licenseState.update(BASIC, false, null);
assertThat(licenseState.isAuthAllowed(), is(false));
@ -114,6 +138,20 @@ public class XPackLicenseStateTests extends ESTestCase {
assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false));
}
public void testSecurityEnabledBasicExpired() {
XPackLicenseState licenseState = new XPackLicenseState(
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build());
licenseState.update(BASIC, false, null);
assertThat(licenseState.isAuthAllowed(), is(true));
assertThat(licenseState.isIpFilteringAllowed(), is(false));
assertThat(licenseState.isAuditingAllowed(), is(false));
assertThat(licenseState.isStatsAndHealthAllowed(), is(false));
assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false));
assertThat(licenseState.allowedRealmType(), is(XPackLicenseState.AllowedRealmType.NATIVE));
assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false));
}
public void testSecurityStandard() {
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
@ -202,7 +240,7 @@ public class XPackLicenseStateTests extends ESTestCase {
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
licenseState.update(TRIAL, true, VersionUtils.randomVersionBetween(random(), Version.V_6_3_0, Version.CURRENT));
assertThat(licenseState.isSecurityDisabledByTrialLicense(), is(true));
assertThat(licenseState.isSecurityDisabledByLicenseDefaults(), is(true));
assertSecurityNotAllowed(licenseState);
}

View File

@ -195,12 +195,14 @@ import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.RestCreateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.RestGetApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.RestInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectLogoutAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestDeletePrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegesAction;
@ -212,8 +214,6 @@ import org.elasticsearch.xpack.security.rest.action.role.RestPutRoleAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestDeleteRoleMappingAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestGetRoleMappingsAction;
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestPutRoleMappingAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlInvalidateSessionAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlLogoutAction;
@ -408,8 +408,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
securityIndex.set(SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService));
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(),
SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService);
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, getLicenseState(),
securityIndex.get(), SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService);
this.tokenService.set(tokenService);
components.add(tokenService);
@ -450,8 +450,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService));
}
final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex.get(), clusterService,
threadPool);
final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, getLicenseState(), securityIndex.get(),
clusterService, threadPool);
components.add(apiKeyService);
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,

View File

@ -82,7 +82,7 @@ public class SecurityFeatureSet implements XPackFeatureSet {
public boolean enabled() {
if (licenseState != null) {
return XPackSettings.SECURITY_ENABLED.get(settings) &&
licenseState.isSecurityDisabledByTrialLicense() == false;
licenseState.isSecurityDisabledByLicenseDefaults() == false;
}
return false;
}

View File

@ -111,8 +111,9 @@ public class SecurityActionFilter implements ActionFilter {
listener.onFailure(e);
}
} else if (SECURITY_ACTION_MATCHER.test(action)) {
if (licenseState.isSecurityDisabledByTrialLicense()) {
listener.onFailure(new ElasticsearchException("Security must be explicitly enabled when using a trial license. " +
if (licenseState.isSecurityDisabledByLicenseDefaults()) {
listener.onFailure(new ElasticsearchException("Security must be explicitly enabled when using a [" +
licenseState.getOperationMode().description() + "] license. " +
"Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " +
"and restart the node."));
} else {

View File

@ -53,6 +53,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackSettings;
@ -69,6 +71,7 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import javax.crypto.SecretKeyFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
@ -92,8 +95,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.crypto.SecretKeyFactory;
import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
@ -136,6 +137,7 @@ public class ApiKeyService {
private final Clock clock;
private final Client client;
private final XPackLicenseState licenseState;
private final SecurityIndexManager securityIndex;
private final ClusterService clusterService;
private final Hasher hasher;
@ -149,10 +151,11 @@ public class ApiKeyService {
private volatile long lastExpirationRunMs;
public ApiKeyService(Settings settings, Clock clock, Client client, SecurityIndexManager securityIndex, ClusterService clusterService,
ThreadPool threadPool) {
public ApiKeyService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, SecurityIndexManager securityIndex,
ClusterService clusterService, ThreadPool threadPool) {
this.clock = clock;
this.client = client;
this.licenseState = licenseState;
this.securityIndex = securityIndex;
this.clusterService = clusterService;
this.enabled = XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.get(settings);
@ -177,10 +180,10 @@ public class ApiKeyService {
* Asynchronously creates a new API key based off of the request and authentication
* @param authentication the authentication that this api key should be based off of
* @param request the request to create the api key included any permission restrictions
* @param roleDescriptorSet the user's actual roles that we always enforce
* @param userRoles the user's actual roles that we always enforce
* @param listener the listener that will be used to notify of completion
*/
public void createApiKey(Authentication authentication, CreateApiKeyRequest request, Set<RoleDescriptor> roleDescriptorSet,
public void createApiKey(Authentication authentication, CreateApiKeyRequest request, Set<RoleDescriptor> userRoles,
ActionListener<CreateApiKeyResponse> listener) {
ensureEnabled();
if (authentication == null) {
@ -191,12 +194,12 @@ public class ApiKeyService {
* this check is best effort as there could be two nodes executing search and
* then index concurrently allowing a duplicate name.
*/
checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, roleDescriptorSet, listener);
checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, userRoles, listener);
}
}
private void checkDuplicateApiKeyNameAndCreateApiKey(Authentication authentication, CreateApiKeyRequest request,
Set<RoleDescriptor> roleDescriptorSet,
Set<RoleDescriptor> userRoles,
ActionListener<CreateApiKeyResponse> listener) {
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "api_key"))
@ -221,7 +224,7 @@ public class ApiKeyService {
listener.onFailure(traceLog("create api key", new ElasticsearchSecurityException(
"Error creating api key as api key with name [{}] already exists", request.getName())));
} else {
createApiKeyAndIndexIt(authentication, request, roleDescriptorSet, listener);
createApiKeyAndIndexIt(authentication, request, userRoles, listener);
}
},
listener::onFailure)));
@ -240,51 +243,8 @@ public class ApiKeyService {
Version.V_6_7_0);
}
final char[] keyHash = hasher.hash(apiKey);
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
builder.startObject()
.field("doc_type", "api_key")
.field("creation_time", created.toEpochMilli())
.field("expiration_time", expiration == null ? null : expiration.toEpochMilli())
.field("api_key_invalidated", false);
byte[] utf8Bytes = null;
try {
utf8Bytes = CharArrays.toUtf8Bytes(keyHash);
builder.field("api_key_hash").utf8Value(utf8Bytes, 0, utf8Bytes.length);
} finally {
if (utf8Bytes != null) {
Arrays.fill(utf8Bytes, (byte) 0);
}
}
// Save role_descriptors
builder.startObject("role_descriptors");
if (request.getRoleDescriptors() != null && request.getRoleDescriptors().isEmpty() == false) {
for (RoleDescriptor descriptor : request.getRoleDescriptors()) {
builder.field(descriptor.getName(),
(contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true));
}
}
builder.endObject();
// Save limited_by_role_descriptors
builder.startObject("limited_by_role_descriptors");
for (RoleDescriptor descriptor : roleDescriptorSet) {
builder.field(descriptor.getName(),
(contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true));
}
builder.endObject();
builder.field("name", request.getName())
.field("version", version.id)
.startObject("creator")
.field("principal", authentication.getUser().principal())
.field("metadata", authentication.getUser().metadata())
.field("realm", authentication.getLookedUpBy() == null ?
authentication.getAuthenticatedBy().getName() : authentication.getLookedUpBy().getName())
.endObject()
.endObject();
try (XContentBuilder builder = newDocument(apiKey, request.getName(), authentication, roleDescriptorSet, created, expiration,
request.getRoleDescriptors(), version)) {
final IndexRequest indexRequest =
client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME)
.setSource(builder)
@ -298,9 +258,64 @@ public class ApiKeyService {
listener::onFailure)));
} catch (IOException e) {
listener.onFailure(e);
}
}
/**
* package protected for testing
*/
XContentBuilder newDocument(SecureString apiKey, String name, Authentication authentication, Set<RoleDescriptor> userRoles,
Instant created, Instant expiration, List<RoleDescriptor> keyRoles,
Version version) throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject()
.field("doc_type", "api_key")
.field("creation_time", created.toEpochMilli())
.field("expiration_time", expiration == null ? null : expiration.toEpochMilli())
.field("api_key_invalidated", false);
byte[] utf8Bytes = null;
final char[] keyHash = hasher.hash(apiKey);
try {
utf8Bytes = CharArrays.toUtf8Bytes(keyHash);
builder.field("api_key_hash").utf8Value(utf8Bytes, 0, utf8Bytes.length);
} finally {
if (utf8Bytes != null) {
Arrays.fill(utf8Bytes, (byte) 0);
}
Arrays.fill(keyHash, (char) 0);
}
// Save role_descriptors
builder.startObject("role_descriptors");
if (keyRoles != null && keyRoles.isEmpty() == false) {
for (RoleDescriptor descriptor : keyRoles) {
builder.field(descriptor.getName(),
(contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true));
}
}
builder.endObject();
// Save limited_by_role_descriptors
builder.startObject("limited_by_role_descriptors");
for (RoleDescriptor descriptor : userRoles) {
builder.field(descriptor.getName(),
(contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true));
}
builder.endObject();
builder.field("name", name)
.field("version", version.id)
.startObject("creator")
.field("principal", authentication.getUser().principal())
.field("metadata", authentication.getUser().metadata())
.field("realm", authentication.getLookedUpBy() == null ?
authentication.getAuthenticatedBy().getName() : authentication.getLookedUpBy().getName())
.endObject()
.endObject();
return builder;
}
/**
@ -308,7 +323,7 @@ public class ApiKeyService {
* {@code ApiKey }. If found this will attempt to authenticate the key.
*/
void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<AuthenticationResult> listener) {
if (enabled) {
if (isEnabled()) {
final ApiKeyCredentials credentials;
try {
credentials = getCredentialsFromHeader(ctx);
@ -570,7 +585,14 @@ public class ApiKeyService {
}
}
private boolean isEnabled() {
return enabled && licenseState.isApiKeyServiceAllowed();
}
private void ensureEnabled() {
if (licenseState.isApiKeyServiceAllowed() == false) {
throw LicenseUtils.newComplianceException("api keys");
}
if (enabled == false) {
throw new IllegalStateException("api keys are not enabled");
}

View File

@ -74,6 +74,8 @@ import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.xpack.core.XPackField;
@ -198,6 +200,7 @@ public final class TokenService {
private final SecurityIndexManager securityTokensIndex;
private final ExpiredTokenRemover expiredTokenRemover;
private final boolean enabled;
private final XPackLicenseState licenseState;
private volatile TokenKeys keyCache;
private volatile long lastExpirationRunMs;
private final AtomicLong createdTimeStamps = new AtomicLong(-1);
@ -205,8 +208,9 @@ public final class TokenService {
/**
* Creates a new token service
*/
public TokenService(Settings settings, Clock clock, Client client, SecurityIndexManager securityMainIndex,
SecurityIndexManager securityTokensIndex, ClusterService clusterService) throws GeneralSecurityException {
public TokenService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState,
SecurityIndexManager securityMainIndex, SecurityIndexManager securityTokensIndex,
ClusterService clusterService) throws GeneralSecurityException {
byte[] saltArr = new byte[SALT_BYTES];
secureRandom.nextBytes(saltArr);
final SecureString tokenPassphrase = generateTokenKey();
@ -214,6 +218,7 @@ public final class TokenService {
this.clock = clock.withZone(ZoneOffset.UTC);
this.expirationDelay = TOKEN_EXPIRATION.get(settings);
this.client = client;
this.licenseState = licenseState;
this.securityMainIndex = securityMainIndex;
this.securityTokensIndex = securityTokensIndex;
this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis();
@ -302,7 +307,7 @@ public final class TokenService {
* has not been revoked or is expired.
*/
void getAndValidateToken(ThreadContext ctx, ActionListener<UserToken> listener) {
if (enabled) {
if (isEnabled()) {
final String token = getFromHeader(ctx);
if (token == null) {
listener.onResponse(null);
@ -1360,9 +1365,16 @@ public final class TokenService {
}
}
private boolean isEnabled() {
return enabled && licenseState.isTokenServiceAllowed();
}
private void ensureEnabled() {
if (licenseState.isTokenServiceAllowed() == false) {
throw LicenseUtils.newComplianceException("security tokens");
}
if (enabled == false) {
throw new IllegalStateException("tokens are not enabled");
throw new IllegalStateException("security tokens are not enabled");
}
}

View File

@ -71,8 +71,9 @@ public abstract class SecurityBaseRestHandler extends BaseRestHandler {
return new IllegalStateException("Security is not enabled but a security rest handler is registered");
} else if (licenseState.isSecurityAvailable() == false) {
return LicenseUtils.newComplianceException(XPackField.SECURITY);
} else if (licenseState.isSecurityDisabledByTrialLicense()) {
return new ElasticsearchException("Security must be explicitly enabled when using a trial license. " +
} else if (licenseState.isSecurityDisabledByLicenseDefaults()) {
return new ElasticsearchException("Security must be explicitly enabled when using a [" +
licenseState.getOperationMode().description() + "] license. " +
"Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " +
"and restart the node.");
} else {

View File

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
/**
* A base rest handler that handles licensing for ApiKey actions
*/
abstract class ApiKeyBaseRestHandler extends SecurityBaseRestHandler {
ApiKeyBaseRestHandler(Settings settings, XPackLicenseState licenseState) {
super(settings, licenseState);
}
@Override
protected Exception checkFeatureAvailable(RestRequest request) {
Exception failedFeature = super.checkFeatureAvailable(request);
if (failedFeature != null) {
return failedFeature;
} else if (licenseState.isApiKeyServiceAllowed()) {
return null;
} else {
logger.info("API Keys are not available under the current [{}] license", licenseState.getOperationMode().description());
return LicenseUtils.newComplianceException("api keys");
}
}
}

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.node.NodeClient;
@ -24,7 +24,7 @@ import java.io.IOException;
/**
* Rest action to create an API key
*/
public final class RestCreateApiKeyAction extends SecurityBaseRestHandler {
public final class RestCreateApiKeyAction extends ApiKeyBaseRestHandler {
/**
* @param settings the node's settings

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
@ -26,7 +26,7 @@ import java.io.IOException;
/**
* Rest action to get one or more API keys information.
*/
public final class RestGetApiKeyAction extends SecurityBaseRestHandler {
public final class RestGetApiKeyAction extends ApiKeyBaseRestHandler {
public RestGetApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
super(settings, licenseState);

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField;
@ -28,7 +28,7 @@ import java.io.IOException;
/**
* Rest action to invalidate one or more API keys
*/
public final class RestInvalidateApiKeyAction extends SecurityBaseRestHandler {
public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler {
static final ConstructingObjectParser<InvalidateApiKeyRequest, Void> PARSER = new ConstructingObjectParser<>("invalidate_api_key",
a -> {
return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]);

View File

@ -30,7 +30,6 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest;
import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.Arrays;
@ -45,7 +44,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
* specification as this aspect does not make the most sense since the response body is
* expected to be JSON
*/
public final class RestGetTokenAction extends SecurityBaseRestHandler {
public final class RestGetTokenAction extends TokenBaseRestHandler {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetTokenAction.class));
static final ConstructingObjectParser<CreateTokenRequest, Void> PARSER = new ConstructingObjectParser<>("token_request",

View File

@ -24,7 +24,6 @@ import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenResponse;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
@ -33,7 +32,7 @@ import static org.elasticsearch.rest.RestRequest.Method.DELETE;
/**
* Rest handler for handling access token invalidation requests
*/
public final class RestInvalidateTokenAction extends SecurityBaseRestHandler {
public final class RestInvalidateTokenAction extends TokenBaseRestHandler {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestInvalidateTokenAction.class));
static final ConstructingObjectParser<InvalidateTokenRequest, Void> PARSER =

View File

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.oauth2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
/**
* A base rest handler that handles licensing for Token actions
*/
abstract class TokenBaseRestHandler extends SecurityBaseRestHandler {
protected Logger logger = LogManager.getLogger(getClass());
TokenBaseRestHandler(Settings settings, XPackLicenseState licenseState) {
super(settings, licenseState);
}
@Override
protected Exception checkFeatureAvailable(RestRequest request) {
Exception failedFeature = super.checkFeatureAvailable(request);
if (failedFeature != null) {
return failedFeature;
} else if (licenseState.isTokenServiceAllowed()) {
return null;
} else {
logger.info("Security tokens are not available under the current [{}] license", licenseState.getOperationMode().description());
return LicenseUtils.newComplianceException("security tokens");
}
}
}

View File

@ -133,7 +133,7 @@ public class LicensingTests extends SecurityIntegTestCase {
@Before
public void resetLicensing() throws InterruptedException {
enableLicensing(OperationMode.BASIC);
enableLicensing(OperationMode.MISSING);
}
@After
@ -230,7 +230,7 @@ public class LicensingTests extends SecurityIntegTestCase {
// enable a license that enables security
License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL,
License.OperationMode.PLATINUM, License.OperationMode.STANDARD);
License.OperationMode.PLATINUM, License.OperationMode.STANDARD, OperationMode.BASIC);
enableLicensing(mode);
// security actions should work!
try (TransportClient client = new TestXPackTransportClient(settings, LocalStateSecurity.class)) {

View File

@ -81,7 +81,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
rolesStore, roleMappingStore, ipFilter);
assertThat(featureSet.enabled(), is(true));
when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true);
when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
featureSet = new SecurityFeatureSet(settings, licenseState, realms,
rolesStore, roleMappingStore, ipFilter);
assertThat(featureSet.enabled(), is(false));
@ -245,7 +245,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
public void testUsageOnTrialLicenseWithSecurityDisabledByDefault() throws Exception {
when(licenseState.isSecurityAvailable()).thenReturn(true);
when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true);
when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
Settings.Builder settings = Settings.builder().put(this.settings);

View File

@ -30,6 +30,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.threadpool.ThreadPool;
@ -167,7 +168,12 @@ public class TransportOpenIdConnectLogoutActionTests extends OpenIdConnectTestCa
when(securityIndex.isAvailable()).thenReturn(true);
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService);
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState,
securityIndex, securityIndex, clusterService);
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());

View File

@ -45,6 +45,7 @@ import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.tasks.Task;
@ -200,8 +201,11 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase {
when(securityIndex.aliasName()).thenReturn(".security");
when(securityIndex.freeze()).thenReturn(securityIndex);
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService);
tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService);
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());

View File

@ -37,6 +37,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.threadpool.ThreadPool;
@ -202,8 +203,10 @@ public class TransportSamlLogoutActionTests extends SamlTestCase {
}).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
when(securityIndex.isAvailable()).thenReturn(true);
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService);
tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService);
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());

View File

@ -29,6 +29,7 @@ import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.node.Node;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase;
@ -72,6 +73,7 @@ public class TransportCreateTokenActionTests extends ESTestCase {
private ClusterService clusterService;
private AtomicReference<IndexRequest> idxReqReference;
private AuthenticationService authenticationService;
private XPackLicenseState license;
@Before
public void setupClient() {
@ -137,6 +139,9 @@ public class TransportCreateTokenActionTests extends ESTestCase {
any(UsernamePasswordToken.class), any(ActionListener.class));
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
this.license = mock(XPackLicenseState.class);
when(license.isTokenServiceAllowed()).thenReturn(true);
}
@After
@ -147,8 +152,8 @@ public class TransportCreateTokenActionTests extends ESTestCase {
}
public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception {
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex,
clusterService);
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license,
securityIndex, securityIndex, clusterService);
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
authentication.writeToContext(threadPool.getThreadContext());
@ -172,8 +177,8 @@ public class TransportCreateTokenActionTests extends ESTestCase {
}
public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception {
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex,
clusterService);
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license,
securityIndex, securityIndex, clusterService);
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
authentication.writeToContext(threadPool.getThreadContext());

View File

@ -9,6 +9,7 @@ package org.elasticsearch.xpack.security.authc;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
@ -18,10 +19,12 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType;
import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
@ -29,17 +32,20 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials;
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors;
import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import org.elasticsearch.xpack.security.test.SecurityMocks;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Base64;
@ -48,19 +54,26 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ApiKeyServiceTests extends ESTestCase {
private ThreadPool threadPool;
private XPackLicenseState licenseState;
private Client client;
private SecurityIndexManager securityIndex;
@Before
public void createThreadPool() {
@ -72,6 +85,15 @@ public class ApiKeyServiceTests extends ESTestCase {
terminate(threadPool);
}
@Before
public void setupMocks() {
this.licenseState = mock(XPackLicenseState.class);
when(licenseState.isApiKeyServiceAllowed()).thenReturn(true);
this.client = mock(Client.class);
this.securityIndex = SecurityMocks.mockSecurityIndexManager();
}
public void testGetCredentialsFromThreadContext() {
ThreadContext threadContext = threadPool.getThreadContext();
assertNull(ApiKeyService.getCredentialsFromHeader(threadContext));
@ -107,6 +129,57 @@ public class ApiKeyServiceTests extends ESTestCase {
}
}
public void testAuthenticateWithApiKey() throws Exception {
final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true).build();
final ApiKeyService service = createApiKeyService(settings);
final String id = randomAlphaOfLength(12);
final String key = randomAlphaOfLength(16);
mockKeyDocument(service, id, key, new User("hulk", "superuser"));
final AuthenticationResult auth = tryAuthenticate(service, id, key);
assertThat(auth.getStatus(), is(AuthenticationResult.Status.SUCCESS));
assertThat(auth.getUser(), notNullValue());
assertThat(auth.getUser().principal(), is("hulk"));
}
public void testAuthenticationIsSkippedIfLicenseDoesNotAllowIt() throws Exception {
final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true).build();
final ApiKeyService service = createApiKeyService(settings);
final String id = randomAlphaOfLength(12);
final String key = randomAlphaOfLength(16);
mockKeyDocument(service, id, key, new User(randomAlphaOfLength(6), randomAlphaOfLength(12)));
when(licenseState.isApiKeyServiceAllowed()).thenReturn(false);
final AuthenticationResult auth = tryAuthenticate(service, id, key);
assertThat(auth.getStatus(), is(AuthenticationResult.Status.CONTINUE));
assertThat(auth.getUser(), nullValue());
}
public void mockKeyDocument(ApiKeyService service, String id, String key, User user) throws IOException {
final Authentication authentication = new Authentication(user, new RealmRef("realm1", "native", "node01"), null, Version.CURRENT);
final XContentBuilder docSource = service.newDocument(new SecureString(key.toCharArray()), "test", authentication,
Collections.singleton(SUPERUSER_ROLE_DESCRIPTOR), Instant.now(), Instant.now().plusSeconds(3600), null, Version.CURRENT);
SecurityMocks.mockGetRequest(client, id, BytesReference.bytes(docSource));
}
private AuthenticationResult tryAuthenticate(ApiKeyService service, String id, String key) throws Exception {
final ThreadContext threadContext = threadPool.getThreadContext();
final String header = "ApiKey " + Base64.getEncoder().encodeToString((id + ":" + key).getBytes(StandardCharsets.UTF_8));
threadContext.putHeader("Authorization", header);
final PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
service.authenticateWithApiKeyIfPresent(threadContext, future);
final AuthenticationResult auth = future.get();
assertThat(auth, notNullValue());
return auth;
}
public void testValidateApiKey() throws Exception {
final String apiKey = randomAlphaOfLength(16);
Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT);
@ -122,8 +195,7 @@ public class ApiKeyServiceTests extends ESTestCase {
sourceMap.put("creator", creatorMap);
sourceMap.put("api_key_invalidated", false);
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
ApiKeyService service = createApiKeyService(Settings.EMPTY);
ApiKeyService.ApiKeyCredentials creds =
new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
@ -136,7 +208,7 @@ public class ApiKeyServiceTests extends ESTestCase {
assertThat(result.getUser().metadata(), is(Collections.emptyMap()));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
equalTo(sourceMap.get("limited_by_role_descriptors")));
equalTo(sourceMap.get("limited_by_role_descriptors")));
sourceMap.put("expiration_time", Clock.systemUTC().instant().plus(1L, ChronoUnit.HOURS).toEpochMilli());
future = new PlainActionFuture<>();
@ -149,7 +221,7 @@ public class ApiKeyServiceTests extends ESTestCase {
assertThat(result.getUser().metadata(), is(Collections.emptyMap()));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors")));
assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY),
equalTo(sourceMap.get("limited_by_role_descriptors")));
equalTo(sourceMap.get("limited_by_role_descriptors")));
sourceMap.put("expiration_time", Clock.systemUTC().instant().minus(1L, ChronoUnit.HOURS).toEpochMilli());
future = new PlainActionFuture<>();
@ -165,7 +237,7 @@ public class ApiKeyServiceTests extends ESTestCase {
result = future.get();
assertNotNull(result);
assertFalse(result.isAuthenticated());
sourceMap.put("api_key_invalidated", true);
creds = new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(randomAlphaOfLength(15).toCharArray()));
future = new PlainActionFuture<>();
@ -179,7 +251,7 @@ public class ApiKeyServiceTests extends ESTestCase {
Map<String, Object> superUserRdMap;
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
superUserRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(),
BytesReference.bytes(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR
BytesReference.bytes(SUPERUSER_ROLE_DESCRIPTOR
.toXContent(builder, ToXContent.EMPTY_PARAMS, true))
.streamInput(),
false);
@ -187,14 +259,13 @@ public class ApiKeyServiceTests extends ESTestCase {
Map<String, Object> authMetadata = new HashMap<>();
authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12));
authMetadata.put(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY,
Collections.singletonMap(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap));
Collections.singletonMap(SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap));
authMetadata.put(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY,
Collections.singletonMap(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap));
Collections.singletonMap(SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap));
final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null,
Version.CURRENT, AuthenticationType.API_KEY, authMetadata);
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
ApiKeyService service = createApiKeyService(Settings.EMPTY);
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
service.getRoleForApiKey(authentication, roleFuture);
@ -208,22 +279,22 @@ public class ApiKeyServiceTests extends ESTestCase {
authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12));
boolean emptyApiKeyRoleDescriptor = randomBoolean();
final RoleDescriptor roleARoleDescriptor = new RoleDescriptor("a role", new String[] { "monitor" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("monitor").build() },
null);
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("monitor").build() },
null);
Map<String, Object> roleARDMap;
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
roleARDMap = XContentHelper.convertToMap(XContentType.JSON.xContent(),
BytesReference.bytes(roleARoleDescriptor.toXContent(builder, ToXContent.EMPTY_PARAMS, true)).streamInput(), false);
BytesReference.bytes(roleARoleDescriptor.toXContent(builder, ToXContent.EMPTY_PARAMS, true)).streamInput(), false);
}
authMetadata.put(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY,
(emptyApiKeyRoleDescriptor) ? randomFrom(Arrays.asList(null, Collections.emptyMap()))
: Collections.singletonMap("a role", roleARDMap));
(emptyApiKeyRoleDescriptor) ? randomFrom(Arrays.asList(null, Collections.emptyMap()))
: Collections.singletonMap("a role", roleARDMap));
final RoleDescriptor limitedRoleDescriptor = new RoleDescriptor("limited role", new String[] { "all" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build() },
null);
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build() },
null);
Map<String, Object> limitedRdMap;
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
limitedRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(),
@ -235,7 +306,7 @@ public class ApiKeyServiceTests extends ESTestCase {
authMetadata.put(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, Collections.singletonMap("limited role", limitedRdMap));
final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null,
Version.CURRENT, AuthenticationType.API_KEY, authMetadata);
Version.CURRENT, AuthenticationType.API_KEY, authMetadata);
final NativePrivilegeStore privilegesStore = mock(NativePrivilegeStore.class);
doAnswer(i -> {
@ -247,8 +318,7 @@ public class ApiKeyServiceTests extends ESTestCase {
return null;
}
).when(privilegesStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class));
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
ApiKeyService service = createApiKeyService(Settings.EMPTY);
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
service.getRoleForApiKey(authentication, roleFuture);
@ -280,8 +350,7 @@ public class ApiKeyServiceTests extends ESTestCase {
sourceMap.put("creator", creatorMap);
sourceMap.put("api_key_invalidated", false);
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
ApiKeyService service = createApiKeyService(Settings.EMPTY);
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
@ -345,8 +414,7 @@ public class ApiKeyServiceTests extends ESTestCase {
sourceMap.put("creator", creatorMap);
sourceMap.put("api_key_invalidated", false);
ApiKeyService service = new ApiKeyService(settings, Clock.systemUTC(), null, null,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
ApiKeyService service = createApiKeyService(settings);
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
@ -355,4 +423,11 @@ public class ApiKeyServiceTests extends ESTestCase {
CachedApiKeyHashResult cachedApiKeyHashResult = service.getFromCache(creds.getId());
assertNull(cachedApiKeyHashResult);
}
private ApiKeyService createApiKeyService(Settings settings) {
return new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex,
ClusterServiceUtils.createClusterService(threadPool), threadPool);
}
}

View File

@ -179,6 +179,8 @@ public class AuthenticationServiceTests extends ESTestCase {
XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.allowedRealmType()).thenReturn(XPackLicenseState.AllowedRealmType.ALL);
when(licenseState.isAuthAllowed()).thenReturn(true);
when(licenseState.isApiKeyServiceAllowed()).thenReturn(true);
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), Collections.<String, Realm.Factory>emptyMap(),
licenseState, threadContext, mock(ReservedRealm.class), Arrays.asList(firstRealm, secondRealm),
Collections.singletonList(firstRealm)));
@ -219,8 +221,13 @@ public class AuthenticationServiceTests extends ESTestCase {
return null;
}).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
<<<<<<< HEAD
apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex, clusterService, threadPool);
tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService);
=======
apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex, clusterService, threadPool);
tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, clusterService);
>>>>>>> Security on Basic License (#107)
service = new AuthenticationService(settings, realms, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()),
threadPool, new AnonymousUser(settings), tokenService, apiKeyService);
}

View File

@ -39,6 +39,7 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.node.Node;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase;
@ -54,6 +55,8 @@ import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationRes
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import org.elasticsearch.xpack.security.test.SecurityMocks;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -71,7 +74,6 @@ import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
@ -103,6 +105,7 @@ public class TokenServiceTests extends ESTestCase {
private DiscoveryNode oldNode;
private Settings tokenServiceEnabledSettings = Settings.builder()
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build();
private XPackLicenseState licenseState;
@Before
public void setupClient() {
@ -128,9 +131,14 @@ public class TokenServiceTests extends ESTestCase {
}).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class));
// setup lifecycle service
this.securityMainIndex = mockSecurityManager();
this.securityTokensIndex = mockSecurityManager();
this.securityMainIndex = SecurityMocks.mockSecurityIndexManager();
this.securityTokensIndex = SecurityMocks.mockSecurityIndexManager();
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
// License state (enabled by default)
licenseState = mock(XPackLicenseState.class);
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
// version 7.1 was an "inflection" point in the Token Service development (access_tokens as UUIDS, multiple concurrent refreshes,
// tokens docs on a separate index), let's test the TokenService works in a mixed cluster with nodes with versions prior to these
// developments
@ -159,8 +167,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testAttachAndGetToken() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -181,8 +188,7 @@ public class TokenServiceTests extends ESTestCase {
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
// verify a second separate token service with its own salt can also verify
TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC());
anotherService.refreshMetaData(tokenService.getTokenMetaData());
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
anotherService.getAndValidateToken(requestContext, future);
@ -192,8 +198,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testInvalidAuthorizationHeader() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
String token = randomFrom("", " ");
String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic ");
@ -208,8 +213,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testRotateKey() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -219,7 +223,7 @@ public class TokenServiceTests extends ESTestCase {
authentication = token.getAuthentication();
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -243,7 +247,7 @@ public class TokenServiceTests extends ESTestCase {
assertNotEquals(getDeprecatedAccessTokenString(tokenService, newToken), getDeprecatedAccessTokenString(tokenService, token));
requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken));
mockGetTokenFromId(newToken, false);
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
@ -262,14 +266,13 @@ public class TokenServiceTests extends ESTestCase {
}
public void testKeyExchange() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
int numRotations = randomIntBetween(1, 5);
for (int i = 0; i < numRotations; i++) {
rotateKeys(tokenService);
}
TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService otherTokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
@ -280,7 +283,7 @@ public class TokenServiceTests extends ESTestCase {
authentication = token.getAuthentication();
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
otherTokenService.getAndValidateToken(requestContext, future);
@ -301,8 +304,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testPruneKeys() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -312,7 +314,7 @@ public class TokenServiceTests extends ESTestCase {
authentication = token.getAuthentication();
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -352,7 +354,7 @@ public class TokenServiceTests extends ESTestCase {
}
requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken));
mockGetTokenFromId(newToken, false);
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -364,8 +366,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testPassphraseWorks() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -375,7 +376,7 @@ public class TokenServiceTests extends ESTestCase {
authentication = token.getAuthentication();
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token));
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -386,8 +387,7 @@ public class TokenServiceTests extends ESTestCase {
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
// verify a second separate token service with its own passphrase cannot verify
TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC());
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
anotherService.getAndValidateToken(requestContext, future);
assertNull(future.get());
@ -395,8 +395,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testGetTokenWhenKeyCacheHasExpired() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
@ -410,8 +409,7 @@ public class TokenServiceTests extends ESTestCase {
public void testInvalidatedToken() throws Exception {
when(securityMainIndex.indexExists()).thenReturn(true);
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -420,7 +418,7 @@ public class TokenServiceTests extends ESTestCase {
mockGetTokenFromId(token, true);
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token));
storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -432,6 +430,10 @@ public class TokenServiceTests extends ESTestCase {
}
}
private void storeTokenHeader(ThreadContext requestContext, String tokenString) throws IOException, GeneralSecurityException {
requestContext.putHeader("Authorization", "Bearer " + tokenString);
}
public void testComputeSecretKeyIsConsistent() throws Exception {
byte[] saltArr = new byte[32];
random().nextBytes(saltArr);
@ -465,8 +467,7 @@ public class TokenServiceTests extends ESTestCase {
public void testTokenExpiry() throws Exception {
ClockMock clock = ClockMock.frozen();
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityMainIndex, securityTokensIndex,
clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, clock);
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -475,7 +476,7 @@ public class TokenServiceTests extends ESTestCase {
authentication = token.getAuthentication();
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token));
storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
// the clock is still frozen, so the cookie should be valid
@ -519,10 +520,10 @@ public class TokenServiceTests extends ESTestCase {
TokenService tokenService = new TokenService(Settings.builder()
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
.build(),
Clock.systemUTC(), client, securityMainIndex, securityTokensIndex, clusterService);
Clock.systemUTC(), client, licenseState, securityMainIndex, securityTokensIndex, clusterService);
IllegalStateException e = expectThrows(IllegalStateException.class,
() -> tokenService.createOAuth2Tokens(null, null, null, true, null));
assertEquals("tokens are not enabled", e.getMessage());
assertEquals("security tokens are not enabled", e.getMessage());
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
tokenService.getAndValidateToken(null, future);
@ -533,7 +534,7 @@ public class TokenServiceTests extends ESTestCase {
tokenService.invalidateAccessToken((String) null, invalidateFuture);
invalidateFuture.actionGet();
});
assertEquals("tokens are not enabled", e.getMessage());
assertEquals("security tokens are not enabled", e.getMessage());
}
public void testBytesKeyEqualsHashCode() {
@ -562,11 +563,10 @@ public class TokenServiceTests extends ESTestCase {
final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32);
final byte[] randomBytes = new byte[numBytes];
random().nextBytes(randomBytes);
TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, securityMainIndex, securityTokensIndex,
clusterService);
TokenService tokenService = createTokenService(Settings.EMPTY, systemUTC());
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes));
storeTokenHeader(requestContext, Base64.getEncoder().encodeToString(randomBytes));
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
@ -576,8 +576,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testIndexNotAvailable() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
@ -586,7 +585,7 @@ public class TokenServiceTests extends ESTestCase {
//mockGetTokenFromId(token, false);
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token));
storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token));
doAnswer(invocationOnMock -> {
ActionListener<GetResponse> listener = (ActionListener<GetResponse>) invocationOnMock.getArguments()[1];
@ -630,8 +629,7 @@ public class TokenServiceTests extends ESTestCase {
}
public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception {
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, securityMainIndex,
securityTokensIndex, clusterService);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, Clock.systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
UserToken expired = new UserToken(authentication, Instant.now().minus(3L, ChronoUnit.DAYS));
mockGetTokenFromId(expired, false);
@ -642,6 +640,27 @@ public class TokenServiceTests extends ESTestCase {
assertEquals(authentication, retrievedAuth);
}
public void testCannotValidateTokenIfLicenseDoesNotAllowTokens() throws Exception {
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, Clock.systemUTC());
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
UserToken token = new UserToken(authentication, Instant.now().plusSeconds(180));
mockGetTokenFromId(token, false);
final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
storeTokenHeader(threadContext, tokenService.getAccessTokenAsString(token));
PlainActionFuture<UserToken> authFuture = new PlainActionFuture<>();
when(licenseState.isTokenServiceAllowed()).thenReturn(false);
tokenService.getAndValidateToken(threadContext, authFuture);
UserToken authToken = authFuture.actionGet();
assertThat(authToken, Matchers.nullValue());
}
private TokenService createTokenService(Settings settings, Clock clock) throws GeneralSecurityException {
return new TokenService(settings, clock, client, licenseState, securityMainIndex, securityTokensIndex, clusterService);
}
private void mockGetTokenFromId(UserToken userToken, boolean isExpired) {
mockGetTokenFromId(userToken, isExpired, client);
}
@ -700,23 +719,6 @@ public class TokenServiceTests extends ESTestCase {
}
}
private SecurityIndexManager mockSecurityManager() {
SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class);
doAnswer(invocationOnMock -> {
Runnable runnable = (Runnable) invocationOnMock.getArguments()[1];
runnable.run();
return null;
}).when(mockSecurityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class));
doAnswer(invocationOnMock -> {
Runnable runnable = (Runnable) invocationOnMock.getArguments()[1];
runnable.run();
return null;
}).when(mockSecurityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
when(mockSecurityIndex.indexExists()).thenReturn(true);
when(mockSecurityIndex.isAvailable()).thenReturn(true);
return mockSecurityIndex;
}
private DiscoveryNode addAnotherDataNodeWithVersion(ClusterService clusterService, Version version) {
final ClusterState currentState = clusterService.state();
final DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder(currentState.getNodes());

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.rest.action;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.License;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.test.ESTestCase;
@ -24,11 +25,13 @@ import static org.mockito.Mockito.when;
public class SecurityBaseRestHandlerTests extends ESTestCase {
public void testSecurityBaseRestHandlerChecksLicenseState() throws Exception {
final boolean securityDisabledByTrial = randomBoolean();
final boolean securityDisabledByLicenseDefaults = randomBoolean();
final AtomicBoolean consumerCalled = new AtomicBoolean(false);
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isSecurityAvailable()).thenReturn(true);
when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(securityDisabledByTrial);
when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(securityDisabledByLicenseDefaults);
when(licenseState.getOperationMode()).thenReturn(
randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD, License.OperationMode.GOLD));
SecurityBaseRestHandler handler = new SecurityBaseRestHandler(Settings.EMPTY, licenseState) {
@Override
@ -46,7 +49,7 @@ public class SecurityBaseRestHandlerTests extends ESTestCase {
}
};
FakeRestRequest fakeRestRequest = new FakeRestRequest();
FakeRestChannel fakeRestChannel = new FakeRestChannel(fakeRestRequest, randomBoolean(), securityDisabledByTrial ? 1 : 0);
FakeRestChannel fakeRestChannel = new FakeRestChannel(fakeRestRequest, randomBoolean(), securityDisabledByLicenseDefaults ? 1 : 0);
NodeClient client = mock(NodeClient.class);
assertFalse(consumerCalled.get());
@ -54,7 +57,7 @@ public class SecurityBaseRestHandlerTests extends ESTestCase {
handler.handleRequest(fakeRestRequest, fakeRestChannel, client);
verify(licenseState).isSecurityAvailable();
if (securityDisabledByTrial == false) {
if (securityDisabledByLicenseDefaults == false) {
assertTrue(consumerCalled.get());
assertEquals(0, fakeRestChannel.responses().get());
assertEquals(0, fakeRestChannel.errors().get());

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
@ -30,6 +30,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse;
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
import java.time.Duration;
import java.time.Instant;
@ -56,6 +57,7 @@ public class RestCreateApiKeyActionTests extends ESTestCase {
.build();
threadPool = new ThreadPool(settings);
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
}
@Override

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
@ -31,6 +31,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.action.ApiKey;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@ -56,6 +57,7 @@ public class RestGetApiKeyActionTests extends ESTestCase {
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
threadPool = new ThreadPool(settings);
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
}
@Override

View File

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
package org.elasticsearch.xpack.security.rest.action.apikey;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchSecurityException;
@ -29,6 +29,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction;
import java.util.Collections;
@ -52,6 +53,7 @@ public class RestInvalidateApiKeyActionTests extends ESTestCase {
.build();
threadPool = new ThreadPool(settings);
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
}
@Override

View File

@ -27,16 +27,9 @@ public class SamlBaseRestHandlerTests extends ESTestCase {
assertThat(handler.checkFeatureAvailable(new FakeRestRequest()), Matchers.nullValue());
}
public void testSecurityNotAvailableOnBasic() {
final SamlBaseRestHandler handler = buildHandler(License.OperationMode.BASIC);
Exception e = handler.checkFeatureAvailable(new FakeRestRequest());
assertThat(e, instanceOf(ElasticsearchException.class));
ElasticsearchException elasticsearchException = (ElasticsearchException) e;
assertThat(elasticsearchException.getMetadata(LicenseUtils.EXPIRED_FEATURE_METADATA), contains("security"));
}
public void testSamlNotAvailableOnStandardOrGold() {
final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.STANDARD, License.OperationMode.GOLD));
public void testSamlNotAvailableOnBasicStandardOrGold() {
final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD,
License.OperationMode.GOLD));
Exception e = handler.checkFeatureAvailable(new FakeRestRequest());
assertThat(e, instanceOf(ElasticsearchException.class));
ElasticsearchException elasticsearchException = (ElasticsearchException) e;

View File

@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.test;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.get.GetAction;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetRequestBuilder;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import org.junit.Assert;
import java.util.function.Consumer;
import static java.util.Collections.emptyMap;
import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_INDEX_NAME;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Utility class for constructing commonly used mock objects.
* <em>Note to maintainers</em>:
* It is not intended that this class cover _all_ mocking scenarios. Consider very carefully before adding methods to this class that are
* only used in one or 2 places. This class is intended for the situations where a common piece of complex mock code is used in multiple
* test suites.
*/
public final class SecurityMocks {
private SecurityMocks() {
throw new IllegalStateException("Cannot instantiate utility class");
}
public static SecurityIndexManager mockSecurityIndexManager() {
return mockSecurityIndexManager(true, true);
}
public static SecurityIndexManager mockSecurityIndexManager(boolean exists, boolean available) {
final SecurityIndexManager securityIndexManager = mock(SecurityIndexManager.class);
doAnswer(invocationOnMock -> {
Runnable runnable = (Runnable) invocationOnMock.getArguments()[1];
runnable.run();
return null;
}).when(securityIndexManager).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class));
doAnswer(invocationOnMock -> {
Runnable runnable = (Runnable) invocationOnMock.getArguments()[1];
runnable.run();
return null;
}).when(securityIndexManager).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
when(securityIndexManager.indexExists()).thenReturn(exists);
when(securityIndexManager.isAvailable()).thenReturn(available);
return securityIndexManager;
}
public static void mockGetRequest(Client client, String documentId, BytesReference source) {
GetResult result = new GetResult(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, documentId, 0, 1, 1, true, source, emptyMap());
mockGetRequest(client, documentId, result);
}
public static void mockGetRequest(Client client, String documentId, GetResult result) {
final GetRequestBuilder requestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE);
requestBuilder.setIndex(SECURITY_INDEX_NAME);
requestBuilder.setType(SINGLE_MAPPING_NAME);
requestBuilder.setId(documentId);
when(client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, documentId)).thenReturn(requestBuilder);
doAnswer(inv -> {
Assert.assertThat(inv.getArguments(), arrayWithSize(2));
Assert.assertThat(inv.getArguments()[0], instanceOf(GetRequest.class));
final GetRequest request = (GetRequest) inv.getArguments()[0];
Assert.assertThat(request.id(), equalTo(documentId));
Assert.assertThat(request.index(), equalTo(SECURITY_INDEX_NAME));
Assert.assertThat(request.type(), equalTo(SINGLE_MAPPING_NAME));
Assert.assertThat(inv.getArguments()[1], instanceOf(ActionListener.class));
ActionListener<GetResponse> listener = (ActionListener<GetResponse>) inv.getArguments()[1];
listener.onResponse(new GetResponse(result));
return null;
}).when(client).get(any(GetRequest.class), any(ActionListener.class));
}
}