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