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:
parent
f08ac103ee
commit
0ee16d0115
|
@ -126,6 +126,10 @@ public class License implements ToXContentObject {
|
||||||
throw new IllegalArgumentException("unknown type [" + type + "]");
|
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,
|
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,
|
||||||
|
|
|
@ -331,8 +331,17 @@ public class XPackLicenseState {
|
||||||
OperationMode mode = status.mode;
|
OperationMode mode = status.mode;
|
||||||
final boolean isSecurityCurrentlyEnabled =
|
final boolean isSecurityCurrentlyEnabled =
|
||||||
isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled);
|
isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled);
|
||||||
return isSecurityCurrentlyEnabled && (mode == OperationMode.STANDARD || mode == OperationMode.GOLD
|
if (isSecurityCurrentlyEnabled) {
|
||||||
|| mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL);
|
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;
|
return AllowedRealmType.ALL;
|
||||||
case GOLD:
|
case GOLD:
|
||||||
return AllowedRealmType.DEFAULT;
|
return AllowedRealmType.DEFAULT;
|
||||||
|
case BASIC:
|
||||||
case STANDARD:
|
case STANDARD:
|
||||||
return AllowedRealmType.NATIVE;
|
return AllowedRealmType.NATIVE;
|
||||||
default:
|
default:
|
||||||
|
@ -425,6 +435,24 @@ public class XPackLicenseState {
|
||||||
&& status.active;
|
&& 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}
|
* @return whether "authorization_realms" are allowed based on the license {@link OperationMode}
|
||||||
* @see org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings
|
* @see org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings
|
||||||
|
@ -674,24 +702,35 @@ public class XPackLicenseState {
|
||||||
public synchronized boolean isSecurityAvailable() {
|
public synchronized boolean isSecurityAvailable() {
|
||||||
OperationMode mode = status.mode;
|
OperationMode mode = status.mode;
|
||||||
return mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.STANDARD ||
|
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
|
* @return true if security has been disabled due it being the default setting for this license type.
|
||||||
* default distribution post 6.3.0. The conditions necessary for this are:
|
* The conditions necessary for this are:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>A trial license</li>
|
* <li>A trial or basic license</li>
|
||||||
* <li>xpack.security.enabled not specified as a setting</li>
|
* <li>xpack.security.enabled not specified as a setting</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public synchronized boolean isSecurityDisabledByTrialLicense() {
|
public synchronized boolean isSecurityDisabledByLicenseDefaults() {
|
||||||
return status.mode == OperationMode.TRIAL && isSecurityEnabled && isSecurityExplicitlyEnabled == false;
|
switch (status.mode) {
|
||||||
|
case TRIAL:
|
||||||
|
case BASIC:
|
||||||
|
return isSecurityEnabled && isSecurityExplicitlyEnabled == false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSecurityEnabled(final OperationMode mode, final boolean isSecurityExplicitlyEnabled,
|
private static boolean isSecurityEnabled(final OperationMode mode, final boolean isSecurityExplicitlyEnabled,
|
||||||
final boolean isSecurityEnabled) {
|
final boolean isSecurityEnabled) {
|
||||||
return mode == OperationMode.TRIAL ? isSecurityExplicitlyEnabled : isSecurityEnabled;
|
switch (mode) {
|
||||||
|
case TRIAL:
|
||||||
|
case BASIC:
|
||||||
|
return isSecurityExplicitlyEnabled;
|
||||||
|
default:
|
||||||
|
return isSecurityEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.elasticsearch.transport.TransportService;
|
||||||
import org.elasticsearch.xpack.core.XPackBuild;
|
import org.elasticsearch.xpack.core.XPackBuild;
|
||||||
import org.elasticsearch.xpack.core.XPackFeatureSet;
|
import org.elasticsearch.xpack.core.XPackFeatureSet;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ public class TransportXPackInfoAction extends HandledTransportAction<XPackInfoRe
|
||||||
if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) {
|
if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) {
|
||||||
License license = licenseService.getLicense();
|
License license = licenseService.getLicense();
|
||||||
if (license != null) {
|
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());
|
license.status(), license.expiryDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,17 +92,41 @@ public class XPackLicenseStateTests extends ESTestCase {
|
||||||
assertSecurityNotAllowed(licenseState);
|
assertSecurityNotAllowed(licenseState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSecurityBasic() {
|
public void testSecurityBasicWithoutExplicitSecurityEnabled() {
|
||||||
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
|
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
|
||||||
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
|
|
||||||
licenseState.update(BASIC, true, null);
|
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() {
|
public void testSecurityBasicWithExplicitSecurityEnabled() {
|
||||||
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
|
final Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build();
|
||||||
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);
|
licenseState.update(BASIC, false, null);
|
||||||
|
|
||||||
assertThat(licenseState.isAuthAllowed(), is(false));
|
assertThat(licenseState.isAuthAllowed(), is(false));
|
||||||
|
@ -114,6 +138,20 @@ public class XPackLicenseStateTests extends ESTestCase {
|
||||||
assertThat(licenseState.isCustomRoleProvidersAllowed(), is(false));
|
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() {
|
public void testSecurityStandard() {
|
||||||
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
|
XPackLicenseState licenseState = new XPackLicenseState(randomFrom(Settings.EMPTY,
|
||||||
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
|
Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()));
|
||||||
|
@ -202,7 +240,7 @@ public class XPackLicenseStateTests extends ESTestCase {
|
||||||
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
|
XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY);
|
||||||
licenseState.update(TRIAL, true, VersionUtils.randomVersionBetween(random(), Version.V_6_3_0, Version.CURRENT));
|
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);
|
assertSecurityNotAllowed(licenseState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -195,12 +195,14 @@ import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
|
||||||
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
|
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
|
||||||
import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
|
import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
|
||||||
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
|
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.RestCreateApiKeyAction;
|
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.RestGetApiKeyAction;
|
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.RestInvalidateApiKeyAction;
|
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.RestGetTokenAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction;
|
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.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.RestDeletePrivilegesAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
|
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegesAction;
|
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.RestDeleteRoleMappingAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestGetRoleMappingsAction;
|
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.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.RestSamlAuthenticateAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlInvalidateSessionAction;
|
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlInvalidateSessionAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlLogoutAction;
|
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));
|
securityIndex.set(SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService));
|
||||||
|
|
||||||
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(),
|
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, getLicenseState(),
|
||||||
SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService);
|
securityIndex.get(), SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService);
|
||||||
this.tokenService.set(tokenService);
|
this.tokenService.set(tokenService);
|
||||||
components.add(tokenService);
|
components.add(tokenService);
|
||||||
|
|
||||||
|
@ -450,8 +450,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
||||||
rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService));
|
rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService));
|
||||||
}
|
}
|
||||||
|
|
||||||
final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex.get(), clusterService,
|
final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, getLicenseState(), securityIndex.get(),
|
||||||
threadPool);
|
clusterService, threadPool);
|
||||||
components.add(apiKeyService);
|
components.add(apiKeyService);
|
||||||
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
|
||||||
privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
|
privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
||||||
public boolean enabled() {
|
public boolean enabled() {
|
||||||
if (licenseState != null) {
|
if (licenseState != null) {
|
||||||
return XPackSettings.SECURITY_ENABLED.get(settings) &&
|
return XPackSettings.SECURITY_ENABLED.get(settings) &&
|
||||||
licenseState.isSecurityDisabledByTrialLicense() == false;
|
licenseState.isSecurityDisabledByLicenseDefaults() == false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,8 +111,9 @@ public class SecurityActionFilter implements ActionFilter {
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
} else if (SECURITY_ACTION_MATCHER.test(action)) {
|
} else if (SECURITY_ACTION_MATCHER.test(action)) {
|
||||||
if (licenseState.isSecurityDisabledByTrialLicense()) {
|
if (licenseState.isSecurityDisabledByLicenseDefaults()) {
|
||||||
listener.onFailure(new ElasticsearchException("Security must be explicitly enabled when using a trial license. " +
|
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 " +
|
"Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " +
|
||||||
"and restart the node."));
|
"and restart the node."));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -53,6 +53,8 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
|
import org.elasticsearch.license.LicenseUtils;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.xpack.core.XPackSettings;
|
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.core.security.user.User;
|
||||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
@ -92,8 +95,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
|
|
||||||
import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
|
import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
|
||||||
import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING;
|
import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING;
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
|
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
|
||||||
|
@ -136,6 +137,7 @@ public class ApiKeyService {
|
||||||
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Client client;
|
private final Client client;
|
||||||
|
private final XPackLicenseState licenseState;
|
||||||
private final SecurityIndexManager securityIndex;
|
private final SecurityIndexManager securityIndex;
|
||||||
private final ClusterService clusterService;
|
private final ClusterService clusterService;
|
||||||
private final Hasher hasher;
|
private final Hasher hasher;
|
||||||
|
@ -149,10 +151,11 @@ public class ApiKeyService {
|
||||||
|
|
||||||
private volatile long lastExpirationRunMs;
|
private volatile long lastExpirationRunMs;
|
||||||
|
|
||||||
public ApiKeyService(Settings settings, Clock clock, Client client, SecurityIndexManager securityIndex, ClusterService clusterService,
|
public ApiKeyService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, SecurityIndexManager securityIndex,
|
||||||
ThreadPool threadPool) {
|
ClusterService clusterService, ThreadPool threadPool) {
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.licenseState = licenseState;
|
||||||
this.securityIndex = securityIndex;
|
this.securityIndex = securityIndex;
|
||||||
this.clusterService = clusterService;
|
this.clusterService = clusterService;
|
||||||
this.enabled = XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.get(settings);
|
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
|
* 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 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 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
|
* @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) {
|
ActionListener<CreateApiKeyResponse> listener) {
|
||||||
ensureEnabled();
|
ensureEnabled();
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
|
@ -191,12 +194,12 @@ public class ApiKeyService {
|
||||||
* this check is best effort as there could be two nodes executing search and
|
* this check is best effort as there could be two nodes executing search and
|
||||||
* then index concurrently allowing a duplicate name.
|
* then index concurrently allowing a duplicate name.
|
||||||
*/
|
*/
|
||||||
checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, roleDescriptorSet, listener);
|
checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, userRoles, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDuplicateApiKeyNameAndCreateApiKey(Authentication authentication, CreateApiKeyRequest request,
|
private void checkDuplicateApiKeyNameAndCreateApiKey(Authentication authentication, CreateApiKeyRequest request,
|
||||||
Set<RoleDescriptor> roleDescriptorSet,
|
Set<RoleDescriptor> userRoles,
|
||||||
ActionListener<CreateApiKeyResponse> listener) {
|
ActionListener<CreateApiKeyResponse> listener) {
|
||||||
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
|
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
|
||||||
.filter(QueryBuilders.termQuery("doc_type", "api_key"))
|
.filter(QueryBuilders.termQuery("doc_type", "api_key"))
|
||||||
|
@ -221,7 +224,7 @@ public class ApiKeyService {
|
||||||
listener.onFailure(traceLog("create api key", new ElasticsearchSecurityException(
|
listener.onFailure(traceLog("create api key", new ElasticsearchSecurityException(
|
||||||
"Error creating api key as api key with name [{}] already exists", request.getName())));
|
"Error creating api key as api key with name [{}] already exists", request.getName())));
|
||||||
} else {
|
} else {
|
||||||
createApiKeyAndIndexIt(authentication, request, roleDescriptorSet, listener);
|
createApiKeyAndIndexIt(authentication, request, userRoles, listener);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
listener::onFailure)));
|
listener::onFailure)));
|
||||||
|
@ -240,51 +243,8 @@ public class ApiKeyService {
|
||||||
Version.V_6_7_0);
|
Version.V_6_7_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final char[] keyHash = hasher.hash(apiKey);
|
try (XContentBuilder builder = newDocument(apiKey, request.getName(), authentication, roleDescriptorSet, created, expiration,
|
||||||
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
|
request.getRoleDescriptors(), version)) {
|
||||||
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();
|
|
||||||
final IndexRequest indexRequest =
|
final IndexRequest indexRequest =
|
||||||
client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME)
|
client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME)
|
||||||
.setSource(builder)
|
.setSource(builder)
|
||||||
|
@ -298,9 +258,64 @@ public class ApiKeyService {
|
||||||
listener::onFailure)));
|
listener::onFailure)));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
listener.onFailure(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 {
|
} finally {
|
||||||
|
if (utf8Bytes != null) {
|
||||||
|
Arrays.fill(utf8Bytes, (byte) 0);
|
||||||
|
}
|
||||||
Arrays.fill(keyHash, (char) 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.
|
* {@code ApiKey }. If found this will attempt to authenticate the key.
|
||||||
*/
|
*/
|
||||||
void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<AuthenticationResult> listener) {
|
void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener<AuthenticationResult> listener) {
|
||||||
if (enabled) {
|
if (isEnabled()) {
|
||||||
final ApiKeyCredentials credentials;
|
final ApiKeyCredentials credentials;
|
||||||
try {
|
try {
|
||||||
credentials = getCredentialsFromHeader(ctx);
|
credentials = getCredentialsFromHeader(ctx);
|
||||||
|
@ -570,7 +585,14 @@ public class ApiKeyService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isEnabled() {
|
||||||
|
return enabled && licenseState.isApiKeyServiceAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureEnabled() {
|
private void ensureEnabled() {
|
||||||
|
if (licenseState.isApiKeyServiceAllowed() == false) {
|
||||||
|
throw LicenseUtils.newComplianceException("api keys");
|
||||||
|
}
|
||||||
if (enabled == false) {
|
if (enabled == false) {
|
||||||
throw new IllegalStateException("api keys are not enabled");
|
throw new IllegalStateException("api keys are not enabled");
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,8 @@ import org.elasticsearch.index.engine.VersionConflictEngineException;
|
||||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.index.seqno.SequenceNumbers;
|
import org.elasticsearch.index.seqno.SequenceNumbers;
|
||||||
|
import org.elasticsearch.license.LicenseUtils;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.xpack.core.XPackField;
|
import org.elasticsearch.xpack.core.XPackField;
|
||||||
|
@ -198,6 +200,7 @@ public final class TokenService {
|
||||||
private final SecurityIndexManager securityTokensIndex;
|
private final SecurityIndexManager securityTokensIndex;
|
||||||
private final ExpiredTokenRemover expiredTokenRemover;
|
private final ExpiredTokenRemover expiredTokenRemover;
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
private final XPackLicenseState licenseState;
|
||||||
private volatile TokenKeys keyCache;
|
private volatile TokenKeys keyCache;
|
||||||
private volatile long lastExpirationRunMs;
|
private volatile long lastExpirationRunMs;
|
||||||
private final AtomicLong createdTimeStamps = new AtomicLong(-1);
|
private final AtomicLong createdTimeStamps = new AtomicLong(-1);
|
||||||
|
@ -205,8 +208,9 @@ public final class TokenService {
|
||||||
/**
|
/**
|
||||||
* Creates a new token service
|
* Creates a new token service
|
||||||
*/
|
*/
|
||||||
public TokenService(Settings settings, Clock clock, Client client, SecurityIndexManager securityMainIndex,
|
public TokenService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState,
|
||||||
SecurityIndexManager securityTokensIndex, ClusterService clusterService) throws GeneralSecurityException {
|
SecurityIndexManager securityMainIndex, SecurityIndexManager securityTokensIndex,
|
||||||
|
ClusterService clusterService) throws GeneralSecurityException {
|
||||||
byte[] saltArr = new byte[SALT_BYTES];
|
byte[] saltArr = new byte[SALT_BYTES];
|
||||||
secureRandom.nextBytes(saltArr);
|
secureRandom.nextBytes(saltArr);
|
||||||
final SecureString tokenPassphrase = generateTokenKey();
|
final SecureString tokenPassphrase = generateTokenKey();
|
||||||
|
@ -214,6 +218,7 @@ public final class TokenService {
|
||||||
this.clock = clock.withZone(ZoneOffset.UTC);
|
this.clock = clock.withZone(ZoneOffset.UTC);
|
||||||
this.expirationDelay = TOKEN_EXPIRATION.get(settings);
|
this.expirationDelay = TOKEN_EXPIRATION.get(settings);
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.licenseState = licenseState;
|
||||||
this.securityMainIndex = securityMainIndex;
|
this.securityMainIndex = securityMainIndex;
|
||||||
this.securityTokensIndex = securityTokensIndex;
|
this.securityTokensIndex = securityTokensIndex;
|
||||||
this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis();
|
this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis();
|
||||||
|
@ -302,7 +307,7 @@ public final class TokenService {
|
||||||
* has not been revoked or is expired.
|
* has not been revoked or is expired.
|
||||||
*/
|
*/
|
||||||
void getAndValidateToken(ThreadContext ctx, ActionListener<UserToken> listener) {
|
void getAndValidateToken(ThreadContext ctx, ActionListener<UserToken> listener) {
|
||||||
if (enabled) {
|
if (isEnabled()) {
|
||||||
final String token = getFromHeader(ctx);
|
final String token = getFromHeader(ctx);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
listener.onResponse(null);
|
listener.onResponse(null);
|
||||||
|
@ -1360,9 +1365,16 @@ public final class TokenService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isEnabled() {
|
||||||
|
return enabled && licenseState.isTokenServiceAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureEnabled() {
|
private void ensureEnabled() {
|
||||||
|
if (licenseState.isTokenServiceAllowed() == false) {
|
||||||
|
throw LicenseUtils.newComplianceException("security tokens");
|
||||||
|
}
|
||||||
if (enabled == false) {
|
if (enabled == false) {
|
||||||
throw new IllegalStateException("tokens are not enabled");
|
throw new IllegalStateException("security tokens are not enabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,9 @@ public abstract class SecurityBaseRestHandler extends BaseRestHandler {
|
||||||
return new IllegalStateException("Security is not enabled but a security rest handler is registered");
|
return new IllegalStateException("Security is not enabled but a security rest handler is registered");
|
||||||
} else if (licenseState.isSecurityAvailable() == false) {
|
} else if (licenseState.isSecurityAvailable() == false) {
|
||||||
return LicenseUtils.newComplianceException(XPackField.SECURITY);
|
return LicenseUtils.newComplianceException(XPackField.SECURITY);
|
||||||
} else if (licenseState.isSecurityDisabledByTrialLicense()) {
|
} else if (licenseState.isSecurityDisabledByLicenseDefaults()) {
|
||||||
return new ElasticsearchException("Security must be explicitly enabled when using a trial license. " +
|
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 " +
|
"Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " +
|
||||||
"and restart the node.");
|
"and restart the node.");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.action.support.WriteRequest;
|
||||||
import org.elasticsearch.client.node.NodeClient;
|
import org.elasticsearch.client.node.NodeClient;
|
||||||
|
@ -24,7 +24,7 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Rest action to create an API key
|
* 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
|
* @param settings the node's settings
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.client.node.NodeClient;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
@ -26,7 +26,7 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Rest action to get one or more API keys information.
|
* 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) {
|
public RestGetApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
|
||||||
super(settings, licenseState);
|
super(settings, licenseState);
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.client.node.NodeClient;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
|
@ -28,7 +28,7 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Rest action to invalidate one or more API keys
|
* 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",
|
static final ConstructingObjectParser<InvalidateApiKeyRequest, Void> PARSER = new ConstructingObjectParser<>("invalidate_api_key",
|
||||||
a -> {
|
a -> {
|
||||||
return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]);
|
return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]);
|
|
@ -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.CreateTokenRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
|
import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
|
||||||
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
|
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
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
|
* specification as this aspect does not make the most sense since the response body is
|
||||||
* expected to be JSON
|
* 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));
|
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetTokenAction.class));
|
||||||
static final ConstructingObjectParser<CreateTokenRequest, Void> PARSER = new ConstructingObjectParser<>("token_request",
|
static final ConstructingObjectParser<CreateTokenRequest, Void> PARSER = new ConstructingObjectParser<>("token_request",
|
||||||
|
|
|
@ -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.InvalidateTokenAction;
|
||||||
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest;
|
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenResponse;
|
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenResponse;
|
||||||
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ import static org.elasticsearch.rest.RestRequest.Method.DELETE;
|
||||||
/**
|
/**
|
||||||
* Rest handler for handling access token invalidation requests
|
* 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));
|
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestInvalidateTokenAction.class));
|
||||||
static final ConstructingObjectParser<InvalidateTokenRequest, Void> PARSER =
|
static final ConstructingObjectParser<InvalidateTokenRequest, Void> PARSER =
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -133,7 +133,7 @@ public class LicensingTests extends SecurityIntegTestCase {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void resetLicensing() throws InterruptedException {
|
public void resetLicensing() throws InterruptedException {
|
||||||
enableLicensing(OperationMode.BASIC);
|
enableLicensing(OperationMode.MISSING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -230,7 +230,7 @@ public class LicensingTests extends SecurityIntegTestCase {
|
||||||
|
|
||||||
// enable a license that enables security
|
// enable a license that enables security
|
||||||
License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL,
|
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);
|
enableLicensing(mode);
|
||||||
// security actions should work!
|
// security actions should work!
|
||||||
try (TransportClient client = new TestXPackTransportClient(settings, LocalStateSecurity.class)) {
|
try (TransportClient client = new TestXPackTransportClient(settings, LocalStateSecurity.class)) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
rolesStore, roleMappingStore, ipFilter);
|
rolesStore, roleMappingStore, ipFilter);
|
||||||
assertThat(featureSet.enabled(), is(true));
|
assertThat(featureSet.enabled(), is(true));
|
||||||
|
|
||||||
when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true);
|
when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
|
||||||
featureSet = new SecurityFeatureSet(settings, licenseState, realms,
|
featureSet = new SecurityFeatureSet(settings, licenseState, realms,
|
||||||
rolesStore, roleMappingStore, ipFilter);
|
rolesStore, roleMappingStore, ipFilter);
|
||||||
assertThat(featureSet.enabled(), is(false));
|
assertThat(featureSet.enabled(), is(false));
|
||||||
|
@ -245,7 +245,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
|
|
||||||
public void testUsageOnTrialLicenseWithSecurityDisabledByDefault() throws Exception {
|
public void testUsageOnTrialLicenseWithSecurityDisabledByDefault() throws Exception {
|
||||||
when(licenseState.isSecurityAvailable()).thenReturn(true);
|
when(licenseState.isSecurityAvailable()).thenReturn(true);
|
||||||
when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true);
|
when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
|
||||||
|
|
||||||
Settings.Builder settings = Settings.builder().put(this.settings);
|
Settings.Builder settings = Settings.builder().put(this.settings);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.TestEnvironment;
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.test.ClusterServiceUtils;
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
@ -167,7 +168,12 @@ public class TransportOpenIdConnectLogoutActionTests extends OpenIdConnectTestCa
|
||||||
when(securityIndex.isAvailable()).thenReturn(true);
|
when(securityIndex.isAvailable()).thenReturn(true);
|
||||||
|
|
||||||
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
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,
|
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.elasticsearch.env.TestEnvironment;
|
||||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilder;
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
import org.elasticsearch.index.query.TermQueryBuilder;
|
import org.elasticsearch.index.query.TermQueryBuilder;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.search.SearchHits;
|
import org.elasticsearch.search.SearchHits;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
|
@ -200,8 +201,11 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase {
|
||||||
when(securityIndex.aliasName()).thenReturn(".security");
|
when(securityIndex.aliasName()).thenReturn(".security");
|
||||||
when(securityIndex.freeze()).thenReturn(securityIndex);
|
when(securityIndex.freeze()).thenReturn(securityIndex);
|
||||||
|
|
||||||
|
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
||||||
|
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
|
||||||
|
|
||||||
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
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,
|
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.TestEnvironment;
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.test.ClusterServiceUtils;
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
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).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
|
||||||
when(securityIndex.isAvailable()).thenReturn(true);
|
when(securityIndex.isAvailable()).thenReturn(true);
|
||||||
|
|
||||||
|
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
||||||
|
when(licenseState.isTokenServiceAllowed()).thenReturn(true);
|
||||||
final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
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,
|
final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null,
|
||||||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet());
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.elasticsearch.common.UUIDs;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.index.shard.ShardId;
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.node.Node;
|
import org.elasticsearch.node.Node;
|
||||||
import org.elasticsearch.test.ClusterServiceUtils;
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -72,6 +73,7 @@ public class TransportCreateTokenActionTests extends ESTestCase {
|
||||||
private ClusterService clusterService;
|
private ClusterService clusterService;
|
||||||
private AtomicReference<IndexRequest> idxReqReference;
|
private AtomicReference<IndexRequest> idxReqReference;
|
||||||
private AuthenticationService authenticationService;
|
private AuthenticationService authenticationService;
|
||||||
|
private XPackLicenseState license;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupClient() {
|
public void setupClient() {
|
||||||
|
@ -137,6 +139,9 @@ public class TransportCreateTokenActionTests extends ESTestCase {
|
||||||
any(UsernamePasswordToken.class), any(ActionListener.class));
|
any(UsernamePasswordToken.class), any(ActionListener.class));
|
||||||
|
|
||||||
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
||||||
|
|
||||||
|
this.license = mock(XPackLicenseState.class);
|
||||||
|
when(license.isTokenServiceAllowed()).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -147,8 +152,8 @@ public class TransportCreateTokenActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception {
|
public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception {
|
||||||
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex,
|
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license,
|
||||||
clusterService);
|
securityIndex, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
||||||
authentication.writeToContext(threadPool.getThreadContext());
|
authentication.writeToContext(threadPool.getThreadContext());
|
||||||
|
|
||||||
|
@ -172,8 +177,8 @@ public class TransportCreateTokenActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception {
|
public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception {
|
||||||
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex,
|
final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license,
|
||||||
clusterService);
|
securityIndex, securityIndex, clusterService);
|
||||||
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null);
|
||||||
authentication.writeToContext(threadPool.getThreadContext());
|
authentication.writeToContext(threadPool.getThreadContext());
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.elasticsearch.xpack.security.authc;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.client.Client;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
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.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.test.ClusterServiceUtils;
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.TestThreadPool;
|
import org.elasticsearch.threadpool.TestThreadPool;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
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;
|
||||||
import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType;
|
import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType;
|
||||||
import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
|
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.authc.support.Hasher;
|
||||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||||
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
|
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.core.security.user.User;
|
||||||
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials;
|
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials;
|
||||||
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors;
|
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors;
|
||||||
import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult;
|
import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult;
|
||||||
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
|
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.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
@ -48,19 +54,26 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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.arrayContaining;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
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.hamcrest.Matchers.sameInstance;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class ApiKeyServiceTests extends ESTestCase {
|
public class ApiKeyServiceTests extends ESTestCase {
|
||||||
|
|
||||||
private ThreadPool threadPool;
|
private ThreadPool threadPool;
|
||||||
|
private XPackLicenseState licenseState;
|
||||||
|
private Client client;
|
||||||
|
private SecurityIndexManager securityIndex;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void createThreadPool() {
|
public void createThreadPool() {
|
||||||
|
@ -72,6 +85,15 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
terminate(threadPool);
|
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() {
|
public void testGetCredentialsFromThreadContext() {
|
||||||
ThreadContext threadContext = threadPool.getThreadContext();
|
ThreadContext threadContext = threadPool.getThreadContext();
|
||||||
assertNull(ApiKeyService.getCredentialsFromHeader(threadContext));
|
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 {
|
public void testValidateApiKey() throws Exception {
|
||||||
final String apiKey = randomAlphaOfLength(16);
|
final String apiKey = randomAlphaOfLength(16);
|
||||||
Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT);
|
Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT);
|
||||||
|
@ -122,8 +195,7 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
sourceMap.put("creator", creatorMap);
|
sourceMap.put("creator", creatorMap);
|
||||||
sourceMap.put("api_key_invalidated", false);
|
sourceMap.put("api_key_invalidated", false);
|
||||||
|
|
||||||
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
|
ApiKeyService service = createApiKeyService(Settings.EMPTY);
|
||||||
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
|
||||||
ApiKeyService.ApiKeyCredentials creds =
|
ApiKeyService.ApiKeyCredentials creds =
|
||||||
new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
||||||
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
||||||
|
@ -179,7 +251,7 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
Map<String, Object> superUserRdMap;
|
Map<String, Object> superUserRdMap;
|
||||||
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
|
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
|
||||||
superUserRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(),
|
superUserRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(),
|
||||||
BytesReference.bytes(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR
|
BytesReference.bytes(SUPERUSER_ROLE_DESCRIPTOR
|
||||||
.toXContent(builder, ToXContent.EMPTY_PARAMS, true))
|
.toXContent(builder, ToXContent.EMPTY_PARAMS, true))
|
||||||
.streamInput(),
|
.streamInput(),
|
||||||
false);
|
false);
|
||||||
|
@ -187,14 +259,13 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
Map<String, Object> authMetadata = new HashMap<>();
|
Map<String, Object> authMetadata = new HashMap<>();
|
||||||
authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12));
|
authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12));
|
||||||
authMetadata.put(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY,
|
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,
|
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,
|
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);
|
||||||
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
|
ApiKeyService service = createApiKeyService(Settings.EMPTY);
|
||||||
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
|
||||||
|
|
||||||
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
|
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
|
||||||
service.getRoleForApiKey(authentication, roleFuture);
|
service.getRoleForApiKey(authentication, roleFuture);
|
||||||
|
@ -247,8 +318,7 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
).when(privilegesStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class));
|
).when(privilegesStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class));
|
||||||
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
|
ApiKeyService service = createApiKeyService(Settings.EMPTY);
|
||||||
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
|
||||||
|
|
||||||
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
|
PlainActionFuture<ApiKeyRoleDescriptors> roleFuture = new PlainActionFuture<>();
|
||||||
service.getRoleForApiKey(authentication, roleFuture);
|
service.getRoleForApiKey(authentication, roleFuture);
|
||||||
|
@ -280,8 +350,7 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
sourceMap.put("creator", creatorMap);
|
sourceMap.put("creator", creatorMap);
|
||||||
sourceMap.put("api_key_invalidated", false);
|
sourceMap.put("api_key_invalidated", false);
|
||||||
|
|
||||||
ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null,
|
ApiKeyService service = createApiKeyService(Settings.EMPTY);
|
||||||
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
|
||||||
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
||||||
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
||||||
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
|
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
|
||||||
|
@ -345,8 +414,7 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
sourceMap.put("creator", creatorMap);
|
sourceMap.put("creator", creatorMap);
|
||||||
sourceMap.put("api_key_invalidated", false);
|
sourceMap.put("api_key_invalidated", false);
|
||||||
|
|
||||||
ApiKeyService service = new ApiKeyService(settings, Clock.systemUTC(), null, null,
|
ApiKeyService service = createApiKeyService(settings);
|
||||||
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
|
||||||
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray()));
|
||||||
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
||||||
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
|
service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future);
|
||||||
|
@ -355,4 +423,11 @@ public class ApiKeyServiceTests extends ESTestCase {
|
||||||
CachedApiKeyHashResult cachedApiKeyHashResult = service.getFromCache(creds.getId());
|
CachedApiKeyHashResult cachedApiKeyHashResult = service.getFromCache(creds.getId());
|
||||||
assertNull(cachedApiKeyHashResult);
|
assertNull(cachedApiKeyHashResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ApiKeyService createApiKeyService(Settings settings) {
|
||||||
|
return new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex,
|
||||||
|
ClusterServiceUtils.createClusterService(threadPool), threadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,8 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
||||||
when(licenseState.allowedRealmType()).thenReturn(XPackLicenseState.AllowedRealmType.ALL);
|
when(licenseState.allowedRealmType()).thenReturn(XPackLicenseState.AllowedRealmType.ALL);
|
||||||
when(licenseState.isAuthAllowed()).thenReturn(true);
|
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(),
|
realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), Collections.<String, Realm.Factory>emptyMap(),
|
||||||
licenseState, threadContext, mock(ReservedRealm.class), Arrays.asList(firstRealm, secondRealm),
|
licenseState, threadContext, mock(ReservedRealm.class), Arrays.asList(firstRealm, secondRealm),
|
||||||
Collections.singletonList(firstRealm)));
|
Collections.singletonList(firstRealm)));
|
||||||
|
@ -219,8 +221,13 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
return null;
|
return null;
|
||||||
}).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
|
}).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class));
|
||||||
ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
||||||
|
<<<<<<< HEAD
|
||||||
apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex, clusterService, threadPool);
|
apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex, clusterService, threadPool);
|
||||||
tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService);
|
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()),
|
service = new AuthenticationService(settings, realms, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()),
|
||||||
threadPool, new AnonymousUser(settings), tokenService, apiKeyService);
|
threadPool, new AnonymousUser(settings), tokenService, apiKeyService);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
import org.elasticsearch.index.shard.ShardId;
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.node.Node;
|
import org.elasticsearch.node.Node;
|
||||||
import org.elasticsearch.test.ClusterServiceUtils;
|
import org.elasticsearch.test.ClusterServiceUtils;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
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.security.user.User;
|
||||||
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
|
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
|
||||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
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.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -71,7 +74,6 @@ import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import javax.crypto.CipherOutputStream;
|
import javax.crypto.CipherOutputStream;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
@ -103,6 +105,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
private DiscoveryNode oldNode;
|
private DiscoveryNode oldNode;
|
||||||
private Settings tokenServiceEnabledSettings = Settings.builder()
|
private Settings tokenServiceEnabledSettings = Settings.builder()
|
||||||
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build();
|
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build();
|
||||||
|
private XPackLicenseState licenseState;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupClient() {
|
public void setupClient() {
|
||||||
|
@ -128,9 +131,14 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class));
|
}).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class));
|
||||||
|
|
||||||
// setup lifecycle service
|
// setup lifecycle service
|
||||||
this.securityMainIndex = mockSecurityManager();
|
this.securityMainIndex = SecurityMocks.mockSecurityIndexManager();
|
||||||
this.securityTokensIndex = mockSecurityManager();
|
this.securityTokensIndex = SecurityMocks.mockSecurityIndexManager();
|
||||||
this.clusterService = ClusterServiceUtils.createClusterService(threadPool);
|
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,
|
// 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
|
// tokens docs on a separate index), let's test the TokenService works in a mixed cluster with nodes with versions prior to these
|
||||||
// developments
|
// developments
|
||||||
|
@ -159,8 +167,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAttachAndGetToken() throws Exception {
|
public void testAttachAndGetToken() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -181,8 +188,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
|
|
||||||
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
// verify a second separate token service with its own salt can also verify
|
// verify a second separate token service with its own salt can also verify
|
||||||
TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
anotherService.refreshMetaData(tokenService.getTokenMetaData());
|
anotherService.refreshMetaData(tokenService.getTokenMetaData());
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
anotherService.getAndValidateToken(requestContext, future);
|
anotherService.getAndValidateToken(requestContext, future);
|
||||||
|
@ -192,8 +198,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInvalidAuthorizationHeader() throws Exception {
|
public void testInvalidAuthorizationHeader() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
||||||
String token = randomFrom("", " ");
|
String token = randomFrom("", " ");
|
||||||
String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic ");
|
String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic ");
|
||||||
|
@ -208,8 +213,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRotateKey() throws Exception {
|
public void testRotateKey() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -219,7 +223,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
authentication = token.getAuthentication();
|
authentication = token.getAuthentication();
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -243,7 +247,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
assertNotEquals(getDeprecatedAccessTokenString(tokenService, newToken), getDeprecatedAccessTokenString(tokenService, token));
|
assertNotEquals(getDeprecatedAccessTokenString(tokenService, newToken), getDeprecatedAccessTokenString(tokenService, token));
|
||||||
|
|
||||||
requestContext = new ThreadContext(Settings.EMPTY);
|
requestContext = new ThreadContext(Settings.EMPTY);
|
||||||
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken));
|
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken));
|
||||||
mockGetTokenFromId(newToken, false);
|
mockGetTokenFromId(newToken, false);
|
||||||
|
|
||||||
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
|
@ -262,14 +266,13 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testKeyExchange() throws Exception {
|
public void testKeyExchange() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
int numRotations = randomIntBetween(1, 5);
|
int numRotations = randomIntBetween(1, 5);
|
||||||
for (int i = 0; i < numRotations; i++) {
|
for (int i = 0; i < numRotations; i++) {
|
||||||
rotateKeys(tokenService);
|
rotateKeys(tokenService);
|
||||||
}
|
}
|
||||||
TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService otherTokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
|
otherTokenService.refreshMetaData(tokenService.getTokenMetaData());
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
|
@ -280,7 +283,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
authentication = token.getAuthentication();
|
authentication = token.getAuthentication();
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
otherTokenService.getAndValidateToken(requestContext, future);
|
otherTokenService.getAndValidateToken(requestContext, future);
|
||||||
|
@ -301,8 +304,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPruneKeys() throws Exception {
|
public void testPruneKeys() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -312,7 +314,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
authentication = token.getAuthentication();
|
authentication = token.getAuthentication();
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -352,7 +354,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestContext = new ThreadContext(Settings.EMPTY);
|
requestContext = new ThreadContext(Settings.EMPTY);
|
||||||
requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken));
|
storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken));
|
||||||
mockGetTokenFromId(newToken, false);
|
mockGetTokenFromId(newToken, false);
|
||||||
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -364,8 +366,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPassphraseWorks() throws Exception {
|
public void testPassphraseWorks() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -375,7 +376,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
authentication = token.getAuthentication();
|
authentication = token.getAuthentication();
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -386,8 +387,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
|
|
||||||
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
// verify a second separate token service with its own passphrase cannot verify
|
// verify a second separate token service with its own passphrase cannot verify
|
||||||
TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
anotherService.getAndValidateToken(requestContext, future);
|
anotherService.getAndValidateToken(requestContext, future);
|
||||||
assertNull(future.get());
|
assertNull(future.get());
|
||||||
|
@ -395,8 +395,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetTokenWhenKeyCacheHasExpired() throws Exception {
|
public void testGetTokenWhenKeyCacheHasExpired() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
|
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
|
@ -410,8 +409,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
|
|
||||||
public void testInvalidatedToken() throws Exception {
|
public void testInvalidatedToken() throws Exception {
|
||||||
when(securityMainIndex.indexExists()).thenReturn(true);
|
when(securityMainIndex.indexExists()).thenReturn(true);
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -420,7 +418,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
mockGetTokenFromId(token, true);
|
mockGetTokenFromId(token, true);
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
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 {
|
public void testComputeSecretKeyIsConsistent() throws Exception {
|
||||||
byte[] saltArr = new byte[32];
|
byte[] saltArr = new byte[32];
|
||||||
random().nextBytes(saltArr);
|
random().nextBytes(saltArr);
|
||||||
|
@ -465,8 +467,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
|
|
||||||
public void testTokenExpiry() throws Exception {
|
public void testTokenExpiry() throws Exception {
|
||||||
ClockMock clock = ClockMock.frozen();
|
ClockMock clock = ClockMock.frozen();
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityMainIndex, securityTokensIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, clock);
|
||||||
clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -475,7 +476,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
authentication = token.getAuthentication();
|
authentication = token.getAuthentication();
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
// the clock is still frozen, so the cookie should be valid
|
// 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()
|
TokenService tokenService = new TokenService(Settings.builder()
|
||||||
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
|
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
|
||||||
.build(),
|
.build(),
|
||||||
Clock.systemUTC(), client, securityMainIndex, securityTokensIndex, clusterService);
|
Clock.systemUTC(), client, licenseState, securityMainIndex, securityTokensIndex, clusterService);
|
||||||
IllegalStateException e = expectThrows(IllegalStateException.class,
|
IllegalStateException e = expectThrows(IllegalStateException.class,
|
||||||
() -> tokenService.createOAuth2Tokens(null, null, null, true, null));
|
() -> 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<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
tokenService.getAndValidateToken(null, future);
|
tokenService.getAndValidateToken(null, future);
|
||||||
|
@ -533,7 +534,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
tokenService.invalidateAccessToken((String) null, invalidateFuture);
|
tokenService.invalidateAccessToken((String) null, invalidateFuture);
|
||||||
invalidateFuture.actionGet();
|
invalidateFuture.actionGet();
|
||||||
});
|
});
|
||||||
assertEquals("tokens are not enabled", e.getMessage());
|
assertEquals("security tokens are not enabled", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBytesKeyEqualsHashCode() {
|
public void testBytesKeyEqualsHashCode() {
|
||||||
|
@ -562,11 +563,10 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32);
|
final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32);
|
||||||
final byte[] randomBytes = new byte[numBytes];
|
final byte[] randomBytes = new byte[numBytes];
|
||||||
random().nextBytes(randomBytes);
|
random().nextBytes(randomBytes);
|
||||||
TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, securityMainIndex, securityTokensIndex,
|
TokenService tokenService = createTokenService(Settings.EMPTY, systemUTC());
|
||||||
clusterService);
|
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
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)) {
|
try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) {
|
||||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||||
|
@ -576,8 +576,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexNotAvailable() throws Exception {
|
public void testIndexNotAvailable() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
||||||
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
PlainActionFuture<Tuple<UserToken, String>> tokenFuture = new PlainActionFuture<>();
|
||||||
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture);
|
||||||
|
@ -586,7 +585,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
//mockGetTokenFromId(token, false);
|
//mockGetTokenFromId(token, false);
|
||||||
|
|
||||||
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
ThreadContext requestContext = new ThreadContext(Settings.EMPTY);
|
||||||
requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token));
|
storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token));
|
||||||
|
|
||||||
doAnswer(invocationOnMock -> {
|
doAnswer(invocationOnMock -> {
|
||||||
ActionListener<GetResponse> listener = (ActionListener<GetResponse>) invocationOnMock.getArguments()[1];
|
ActionListener<GetResponse> listener = (ActionListener<GetResponse>) invocationOnMock.getArguments()[1];
|
||||||
|
@ -630,8 +629,7 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception {
|
public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception {
|
||||||
TokenService tokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, securityMainIndex,
|
TokenService tokenService = createTokenService(tokenServiceEnabledSettings, Clock.systemUTC());
|
||||||
securityTokensIndex, clusterService);
|
|
||||||
Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null);
|
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));
|
UserToken expired = new UserToken(authentication, Instant.now().minus(3L, ChronoUnit.DAYS));
|
||||||
mockGetTokenFromId(expired, false);
|
mockGetTokenFromId(expired, false);
|
||||||
|
@ -642,6 +640,27 @@ public class TokenServiceTests extends ESTestCase {
|
||||||
assertEquals(authentication, retrievedAuth);
|
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) {
|
private void mockGetTokenFromId(UserToken userToken, boolean isExpired) {
|
||||||
mockGetTokenFromId(userToken, isExpired, client);
|
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) {
|
private DiscoveryNode addAnotherDataNodeWithVersion(ClusterService clusterService, Version version) {
|
||||||
final ClusterState currentState = clusterService.state();
|
final ClusterState currentState = clusterService.state();
|
||||||
final DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder(currentState.getNodes());
|
final DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder(currentState.getNodes());
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.rest.action;
|
||||||
|
|
||||||
import org.elasticsearch.client.node.NodeClient;
|
import org.elasticsearch.client.node.NodeClient;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.license.License;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.rest.RestRequest;
|
import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -24,11 +25,13 @@ import static org.mockito.Mockito.when;
|
||||||
public class SecurityBaseRestHandlerTests extends ESTestCase {
|
public class SecurityBaseRestHandlerTests extends ESTestCase {
|
||||||
|
|
||||||
public void testSecurityBaseRestHandlerChecksLicenseState() throws Exception {
|
public void testSecurityBaseRestHandlerChecksLicenseState() throws Exception {
|
||||||
final boolean securityDisabledByTrial = randomBoolean();
|
final boolean securityDisabledByLicenseDefaults = randomBoolean();
|
||||||
final AtomicBoolean consumerCalled = new AtomicBoolean(false);
|
final AtomicBoolean consumerCalled = new AtomicBoolean(false);
|
||||||
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
|
||||||
when(licenseState.isSecurityAvailable()).thenReturn(true);
|
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) {
|
SecurityBaseRestHandler handler = new SecurityBaseRestHandler(Settings.EMPTY, licenseState) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,7 +49,7 @@ public class SecurityBaseRestHandlerTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
FakeRestRequest fakeRestRequest = new FakeRestRequest();
|
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);
|
NodeClient client = mock(NodeClient.class);
|
||||||
|
|
||||||
assertFalse(consumerCalled.get());
|
assertFalse(consumerCalled.get());
|
||||||
|
@ -54,7 +57,7 @@ public class SecurityBaseRestHandlerTests extends ESTestCase {
|
||||||
handler.handleRequest(fakeRestRequest, fakeRestChannel, client);
|
handler.handleRequest(fakeRestRequest, fakeRestChannel, client);
|
||||||
|
|
||||||
verify(licenseState).isSecurityAvailable();
|
verify(licenseState).isSecurityAvailable();
|
||||||
if (securityDisabledByTrial == false) {
|
if (securityDisabledByLicenseDefaults == false) {
|
||||||
assertTrue(consumerCalled.get());
|
assertTrue(consumerCalled.get());
|
||||||
assertEquals(0, fakeRestChannel.responses().get());
|
assertEquals(0, fakeRestChannel.responses().get());
|
||||||
assertEquals(0, fakeRestChannel.errors().get());
|
assertEquals(0, fakeRestChannel.errors().get());
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.apache.lucene.util.SetOnce;
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
import org.elasticsearch.ElasticsearchSecurityException;
|
||||||
|
@ -30,6 +30,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
|
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse;
|
import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse;
|
||||||
|
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -56,6 +57,7 @@ public class RestCreateApiKeyActionTests extends ESTestCase {
|
||||||
.build();
|
.build();
|
||||||
threadPool = new ThreadPool(settings);
|
threadPool = new ThreadPool(settings);
|
||||||
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
||||||
|
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.apache.lucene.util.SetOnce;
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
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.ApiKey;
|
||||||
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
|
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
|
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
|
||||||
|
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
@ -56,6 +57,7 @@ public class RestGetApiKeyActionTests extends ESTestCase {
|
||||||
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
|
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
|
||||||
threadPool = new ThreadPool(settings);
|
threadPool = new ThreadPool(settings);
|
||||||
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
||||||
|
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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.apache.lucene.util.SetOnce;
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
import org.elasticsearch.ElasticsearchSecurityException;
|
||||||
|
@ -29,6 +29,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
|
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
|
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
|
||||||
|
import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ public class RestInvalidateApiKeyActionTests extends ESTestCase {
|
||||||
.build();
|
.build();
|
||||||
threadPool = new ThreadPool(settings);
|
threadPool = new ThreadPool(settings);
|
||||||
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
when(mockLicenseState.isSecurityAvailable()).thenReturn(true);
|
||||||
|
when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -27,16 +27,9 @@ public class SamlBaseRestHandlerTests extends ESTestCase {
|
||||||
assertThat(handler.checkFeatureAvailable(new FakeRestRequest()), Matchers.nullValue());
|
assertThat(handler.checkFeatureAvailable(new FakeRestRequest()), Matchers.nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSecurityNotAvailableOnBasic() {
|
public void testSamlNotAvailableOnBasicStandardOrGold() {
|
||||||
final SamlBaseRestHandler handler = buildHandler(License.OperationMode.BASIC);
|
final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD,
|
||||||
Exception e = handler.checkFeatureAvailable(new FakeRestRequest());
|
License.OperationMode.GOLD));
|
||||||
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));
|
|
||||||
Exception e = handler.checkFeatureAvailable(new FakeRestRequest());
|
Exception e = handler.checkFeatureAvailable(new FakeRestRequest());
|
||||||
assertThat(e, instanceOf(ElasticsearchException.class));
|
assertThat(e, instanceOf(ElasticsearchException.class));
|
||||||
ElasticsearchException elasticsearchException = (ElasticsearchException) e;
|
ElasticsearchException elasticsearchException = (ElasticsearchException) e;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue