security: add support for disabling users
This change adds support for disabling users. Users can be disabled by setting the enabled property to false and the AuthenticationService will check to make sure that the user is enabled. If the user is not enabled, this will be audited as an authentication failure. Also as part of this work, the AnonymousUser was cleaned up to remove having a static instance that caused issues with tests. Finally, the poller of users was removed to simplify the code in the NativeUsersStore. In our other realms we rely on the clear cache APIs and the timeout of the user cache. We should have the same semantics for the native realm. Closes elastic/elasticsearch#2172 Original commit: elastic/x-pack-elasticsearch@0820e40183
This commit is contained in:
parent
ec008ec4a9
commit
1e6a924e74
|
@ -51,11 +51,13 @@ import org.elasticsearch.xpack.security.action.user.ChangePasswordAction;
|
|||
import org.elasticsearch.xpack.security.action.user.DeleteUserAction;
|
||||
import org.elasticsearch.xpack.security.action.user.GetUsersAction;
|
||||
import org.elasticsearch.xpack.security.action.user.PutUserAction;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportAuthenticateAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportChangePasswordAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportDeleteUserAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportGetUsersAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportPutUserAction;
|
||||
import org.elasticsearch.xpack.security.action.user.TransportSetEnabledAction;
|
||||
import org.elasticsearch.xpack.security.audit.AuditTrail;
|
||||
import org.elasticsearch.xpack.security.audit.AuditTrailService;
|
||||
import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail;
|
||||
|
@ -96,6 +98,7 @@ import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordActio
|
|||
import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestSetEnabledAction;
|
||||
import org.elasticsearch.xpack.security.transport.SecurityServerTransportService;
|
||||
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
||||
import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport;
|
||||
|
@ -219,15 +222,15 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
if (enabled == false) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
AnonymousUser.initialize(settings); // TODO: this is sketchy...testing is difficult b/c it is static....
|
||||
|
||||
List<Object> components = new ArrayList<>();
|
||||
final SecurityContext securityContext = new SecurityContext(settings, threadPool, cryptoService);
|
||||
components.add(securityContext);
|
||||
|
||||
// realms construction
|
||||
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, threadPool);
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore);
|
||||
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore, anonymousUser);
|
||||
Map<String, Realm.Factory> realmFactories = new HashMap<>();
|
||||
realmFactories.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService));
|
||||
realmFactories.put(NativeRealm.TYPE, config -> new NativeRealm(config, nativeUsersStore));
|
||||
|
@ -246,6 +249,7 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
final Realms realms = new Realms(settings, env, realmFactories, licenseState, reservedRealm);
|
||||
components.add(nativeUsersStore);
|
||||
components.add(realms);
|
||||
components.add(reservedRealm);
|
||||
|
||||
// audit trails construction
|
||||
IndexAuditTrail indexAuditTrail = null;
|
||||
|
@ -294,7 +298,7 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
}
|
||||
|
||||
final AuthenticationService authcService = new AuthenticationService(settings, realms, auditTrailService,
|
||||
cryptoService, failureHandler, threadPool);
|
||||
cryptoService, failureHandler, threadPool, anonymousUser);
|
||||
components.add(authcService);
|
||||
|
||||
final FileRolesStore fileRolesStore = new FileRolesStore(settings, env, resourceWatcherService);
|
||||
|
@ -302,7 +306,7 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(securityContext);
|
||||
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore);
|
||||
final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService,
|
||||
auditTrailService, failureHandler, threadPool);
|
||||
auditTrailService, failureHandler, threadPool, anonymousUser);
|
||||
components.add(fileRolesStore); // has lifecycle
|
||||
components.add(nativeRolesStore); // used by roles actions
|
||||
components.add(reservedRolesStore); // used by roles actions
|
||||
|
@ -458,7 +462,8 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
new ActionHandler<>(PutRoleAction.INSTANCE, TransportPutRoleAction.class),
|
||||
new ActionHandler<>(DeleteRoleAction.INSTANCE, TransportDeleteRoleAction.class),
|
||||
new ActionHandler<>(ChangePasswordAction.INSTANCE, TransportChangePasswordAction.class),
|
||||
new ActionHandler<>(AuthenticateAction.INSTANCE, TransportAuthenticateAction.class));
|
||||
new ActionHandler<>(AuthenticateAction.INSTANCE, TransportAuthenticateAction.class),
|
||||
new ActionHandler<>(SetEnabledAction.INSTANCE, TransportSetEnabledAction.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -487,7 +492,8 @@ public class Security implements ActionPlugin, IngestPlugin {
|
|||
RestGetRolesAction.class,
|
||||
RestPutRoleAction.class,
|
||||
RestDeleteRoleAction.class,
|
||||
RestChangePasswordAction.class);
|
||||
RestChangePasswordAction.class,
|
||||
RestSetEnabledAction.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -94,7 +94,7 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
|||
Map<String, Object> auditUsage = auditUsage(auditTrailService);
|
||||
Map<String, Object> ipFilterUsage = ipFilterUsage(ipFilter);
|
||||
Map<String, Object> systemKeyUsage = systemKeyUsage(cryptoService);
|
||||
Map<String, Object> anonymousUsage = Collections.singletonMap("enabled", AnonymousUser.enabled());
|
||||
Map<String, Object> anonymousUsage = Collections.singletonMap("enabled", AnonymousUser.isAnonymousEnabled(settings));
|
||||
return new Usage(available(), enabled(), realmsUsage, rolesStoreUsage, sslUsage, auditUsage, ipFilterUsage, systemKeyUsage,
|
||||
anonymousUsage);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.action.role;
|
|||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
|
@ -17,14 +18,25 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|||
/**
|
||||
* A request delete a role from the security index
|
||||
*/
|
||||
public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> {
|
||||
public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> implements WriteRequest<DeleteRoleRequest> {
|
||||
|
||||
private String name;
|
||||
private boolean refresh = true;
|
||||
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
|
||||
public DeleteRoleRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteRoleRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshPolicy getRefreshPolicy() {
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
|
@ -42,25 +54,17 @@ public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> {
|
|||
return name;
|
||||
}
|
||||
|
||||
public void refresh(boolean refresh) {
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
public boolean refresh() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
name = in.readString();
|
||||
refresh = in.readBoolean();
|
||||
refreshPolicy = RefreshPolicy.readFrom(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(name);
|
||||
out.writeBoolean(refresh);
|
||||
refreshPolicy.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
package org.elasticsearch.xpack.security.action.role;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* A builder for requests to delete a role from the security index
|
||||
*/
|
||||
public class DeleteRoleRequestBuilder extends ActionRequestBuilder<DeleteRoleRequest, DeleteRoleResponse, DeleteRoleRequestBuilder> {
|
||||
public class DeleteRoleRequestBuilder extends ActionRequestBuilder<DeleteRoleRequest, DeleteRoleResponse, DeleteRoleRequestBuilder>
|
||||
implements WriteRequestBuilder<DeleteRoleRequestBuilder> {
|
||||
|
||||
public DeleteRoleRequestBuilder(ElasticsearchClient client) {
|
||||
this(client, DeleteRoleAction.INSTANCE);
|
||||
|
@ -25,9 +27,4 @@ public class DeleteRoleRequestBuilder extends ActionRequestBuilder<DeleteRoleReq
|
|||
request.name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeleteRoleRequestBuilder refresh(boolean refresh) {
|
||||
request.refresh(refresh);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.action.user;
|
|||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
|
@ -17,10 +18,10 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|||
/**
|
||||
* A request to delete a native user.
|
||||
*/
|
||||
public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implements UserRequest {
|
||||
public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implements UserRequest, WriteRequest<DeleteUserRequest> {
|
||||
|
||||
private String username;
|
||||
private boolean refresh = true;
|
||||
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
|
||||
public DeleteUserRequest() {
|
||||
}
|
||||
|
@ -29,6 +30,17 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
|||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeleteUserRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshPolicy getRefreshPolicy() {
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
|
@ -42,18 +54,10 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
|||
return this.username;
|
||||
}
|
||||
|
||||
public boolean refresh() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
public void username(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void refresh(boolean refresh) {
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] usernames() {
|
||||
return new String[] { username };
|
||||
|
@ -63,14 +67,14 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
|||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
username = in.readString();
|
||||
refresh = in.readBoolean();
|
||||
refreshPolicy = RefreshPolicy.readFrom(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(username);
|
||||
out.writeBoolean(refresh);
|
||||
refreshPolicy.writeTo(out);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
package org.elasticsearch.xpack.security.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
public class DeleteUserRequestBuilder extends ActionRequestBuilder<DeleteUserRequest, DeleteUserResponse, DeleteUserRequestBuilder> {
|
||||
public class DeleteUserRequestBuilder extends ActionRequestBuilder<DeleteUserRequest, DeleteUserResponse, DeleteUserRequestBuilder>
|
||||
implements WriteRequestBuilder<DeleteUserRequestBuilder> {
|
||||
|
||||
public DeleteUserRequestBuilder(ElasticsearchClient client) {
|
||||
this(client, DeleteUserAction.INSTANCE);
|
||||
|
@ -22,9 +24,4 @@ public class DeleteUserRequestBuilder extends ActionRequestBuilder<DeleteUserReq
|
|||
request.username(username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DeleteUserRequestBuilder refresh(boolean refresh) {
|
||||
request.refresh(refresh);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.elasticsearch.common.bytes.BytesReference;
|
|||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -46,6 +47,10 @@ public class PutUserRequest extends ActionRequest<PutUserRequest> implements Use
|
|||
if (roles == null) {
|
||||
validationException = addValidationError("roles are missing", validationException);
|
||||
}
|
||||
if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) {
|
||||
validationException = addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]",
|
||||
validationException);
|
||||
}
|
||||
// we do not check for a password hash here since it is possible that the user exists and we don't want to update the password
|
||||
return validationException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* This action is for setting the enabled flag on a native or reserved user
|
||||
*/
|
||||
public class SetEnabledAction extends Action<SetEnabledRequest, SetEnabledResponse, SetEnabledRequestBuilder> {
|
||||
|
||||
public static final SetEnabledAction INSTANCE = new SetEnabledAction();
|
||||
public static final String NAME = "cluster:admin/xpack/security/user/set_enabled";
|
||||
|
||||
private SetEnabledAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetEnabledRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new SetEnabledRequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetEnabledResponse newResponse() {
|
||||
return new SetEnabledResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.support.Validation.Error;
|
||||
import org.elasticsearch.xpack.security.support.Validation.Users;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* The request that allows to set a user as enabled or disabled
|
||||
*/
|
||||
public class SetEnabledRequest extends ActionRequest<SetEnabledRequest> implements UserRequest, WriteRequest<SetEnabledRequest> {
|
||||
|
||||
private Boolean enabled;
|
||||
private String username;
|
||||
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
Error error = Users.validateUsername(username, true, Settings.EMPTY);
|
||||
if (error != null) {
|
||||
validationException = addValidationError(error.toString(), validationException);
|
||||
}
|
||||
if (enabled == null) {
|
||||
validationException = addValidationError("enabled must be set", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the user should be set to enabled or not
|
||||
*/
|
||||
public Boolean enabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the user should be enabled or not.
|
||||
*/
|
||||
public void enabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the username that this request applies to.
|
||||
*/
|
||||
public String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username that the request applies to. Must not be {@code null}
|
||||
*/
|
||||
public void username(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] usernames() {
|
||||
return new String[] { username };
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this request trigger a refresh ({@linkplain RefreshPolicy#IMMEDIATE}, the default), wait for a refresh (
|
||||
* {@linkplain RefreshPolicy#WAIT_UNTIL}), or proceed ignore refreshes entirely ({@linkplain RefreshPolicy#NONE}).
|
||||
*/
|
||||
@Override
|
||||
public RefreshPolicy getRefreshPolicy() {
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetEnabledRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
this.enabled = in.readBoolean();
|
||||
this.username = in.readString();
|
||||
this.refreshPolicy = RefreshPolicy.readFrom(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeBoolean(enabled);
|
||||
out.writeString(username);
|
||||
refreshPolicy.writeTo(out);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Request builder for setting a user as enabled or disabled
|
||||
*/
|
||||
public class SetEnabledRequestBuilder extends ActionRequestBuilder<SetEnabledRequest, SetEnabledResponse, SetEnabledRequestBuilder>
|
||||
implements WriteRequestBuilder<SetEnabledRequestBuilder> {
|
||||
|
||||
public SetEnabledRequestBuilder(ElasticsearchClient client) {
|
||||
super(client, SetEnabledAction.INSTANCE, new SetEnabledRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username of the user that should enabled or disabled. Must not be {@code null}
|
||||
*/
|
||||
public SetEnabledRequestBuilder username(String username) {
|
||||
request.username(username);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the user should be enabled or not
|
||||
*/
|
||||
public SetEnabledRequestBuilder enabled(boolean enabled) {
|
||||
request.enabled(enabled);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
|
||||
/**
|
||||
* Empty response for a {@link SetEnabledRequest}
|
||||
*/
|
||||
public class SetEnabledResponse extends ActionResponse {
|
||||
}
|
|
@ -17,6 +17,7 @@ import org.elasticsearch.xpack.security.user.SystemUser;
|
|||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -36,7 +37,7 @@ public class TransportAuthenticateAction extends HandledTransportAction<Authenti
|
|||
@Override
|
||||
protected void doExecute(AuthenticateRequest request, ActionListener<AuthenticateResponse> listener) {
|
||||
final User user = securityContext.getUser();
|
||||
if (SystemUser.is(user)) {
|
||||
if (SystemUser.is(user) || XPackUser.is(user)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + user.principal() + "] is internal"));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
|||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -35,10 +36,10 @@ public class TransportChangePasswordAction extends HandledTransportAction<Change
|
|||
@Override
|
||||
protected void doExecute(ChangePasswordRequest request, ActionListener<ChangePasswordResponse> listener) {
|
||||
final String username = request.username();
|
||||
if (AnonymousUser.isAnonymousUsername(username)) {
|
||||
if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
|
||||
return;
|
||||
} else if (SystemUser.NAME.equals(username)) {
|
||||
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
|||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
public class TransportDeleteUserAction extends HandledTransportAction<DeleteUserRequest, DeleteUserResponse> {
|
||||
|
||||
|
@ -34,15 +35,15 @@ public class TransportDeleteUserAction extends HandledTransportAction<DeleteUser
|
|||
@Override
|
||||
protected void doExecute(DeleteUserRequest request, final ActionListener<DeleteUserResponse> listener) {
|
||||
final String username = request.username();
|
||||
if (ReservedRealm.isReserved(username)) {
|
||||
if (AnonymousUser.isAnonymousUsername(username)) {
|
||||
if (ReservedRealm.isReserved(username, settings)) {
|
||||
if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be deleted"));
|
||||
return;
|
||||
} else {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is reserved and cannot be deleted"));
|
||||
return;
|
||||
}
|
||||
} else if (SystemUser.NAME.equals(username)) {
|
||||
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ import org.elasticsearch.threadpool.ThreadPool;
|
|||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -29,14 +29,16 @@ import static org.elasticsearch.common.Strings.arrayToDelimitedString;
|
|||
public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequest, GetUsersResponse> {
|
||||
|
||||
private final NativeUsersStore usersStore;
|
||||
private final ReservedRealm reservedRealm;
|
||||
|
||||
@Inject
|
||||
public TransportGetUsersAction(Settings settings, ThreadPool threadPool, ActionFilters actionFilters,
|
||||
IndexNameExpressionResolver indexNameExpressionResolver, NativeUsersStore usersStore,
|
||||
TransportService transportService) {
|
||||
TransportService transportService, ReservedRealm reservedRealm) {
|
||||
super(settings, GetUsersAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
GetUsersRequest::new);
|
||||
this.usersStore = usersStore;
|
||||
this.reservedRealm = reservedRealm;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,16 +50,13 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
|
||||
if (specificUsersRequested) {
|
||||
for (String username : requestedUsers) {
|
||||
if (ReservedRealm.isReserved(username)) {
|
||||
User user = ReservedRealm.getUser(username);
|
||||
if (ReservedRealm.isReserved(username, settings)) {
|
||||
User user = reservedRealm.lookupUser(username);
|
||||
// a user could be null if the service isn't ready or we requested the anonymous user and it is not enabled
|
||||
if (user != null) {
|
||||
users.add(user);
|
||||
} else {
|
||||
// the only time a user should be null is if username matches for the anonymous user and the anonymous user is not
|
||||
// enabled!
|
||||
assert AnonymousUser.enabled() == false && AnonymousUser.isAnonymousUsername(username);
|
||||
}
|
||||
} else if (SystemUser.NAME.equals(username)) {
|
||||
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||
return;
|
||||
} else {
|
||||
|
@ -65,7 +64,7 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
}
|
||||
}
|
||||
} else {
|
||||
users.addAll(ReservedRealm.users());
|
||||
users.addAll(reservedRealm.users());
|
||||
}
|
||||
|
||||
if (usersToSearchFor.size() == 1) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
|||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
public class TransportPutUserAction extends HandledTransportAction<PutUserRequest, PutUserResponse> {
|
||||
|
||||
|
@ -35,8 +36,8 @@ public class TransportPutUserAction extends HandledTransportAction<PutUserReques
|
|||
@Override
|
||||
protected void doExecute(final PutUserRequest request, final ActionListener<PutUserResponse> listener) {
|
||||
final String username = request.username();
|
||||
if (ReservedRealm.isReserved(username)) {
|
||||
if (AnonymousUser.isAnonymousUsername(username)) {
|
||||
if (ReservedRealm.isReserved(username, settings)) {
|
||||
if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
|
||||
return;
|
||||
} else {
|
||||
|
@ -44,7 +45,7 @@ public class TransportPutUserAction extends HandledTransportAction<PutUserReques
|
|||
"password can be changed"));
|
||||
return;
|
||||
}
|
||||
} else if (SystemUser.NAME.equals(username)) {
|
||||
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
/**
|
||||
* Transport action that handles setting a native or reserved user to enabled
|
||||
*/
|
||||
public class TransportSetEnabledAction extends HandledTransportAction<SetEnabledRequest, SetEnabledResponse> {
|
||||
|
||||
private final NativeUsersStore usersStore;
|
||||
|
||||
@Inject
|
||||
public TransportSetEnabledAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
NativeUsersStore usersStore) {
|
||||
super(settings, SetEnabledAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
SetEnabledRequest::new);
|
||||
this.usersStore = usersStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(SetEnabledRequest request, ActionListener<SetEnabledResponse> listener) {
|
||||
final String username = request.username();
|
||||
// make sure the user is not disabling themselves
|
||||
if (Authentication.getAuthentication(threadPool.getThreadContext()).getRunAsUser().principal().equals(request.username())) {
|
||||
listener.onFailure(new IllegalArgumentException("users may not update the enabled status of their own account"));
|
||||
return;
|
||||
} else if (SystemUser.NAME.equals(username) || XPackUser.NAME.equals(username)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||
return;
|
||||
} else if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified using the api"));
|
||||
return;
|
||||
}
|
||||
|
||||
usersStore.setEnabled(username, request.enabled(), request.getRefreshPolicy(), new ActionListener<Void>() {
|
||||
@Override
|
||||
public void onResponse(Void v) {
|
||||
listener.onResponse(new SetEnabledResponse());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -50,11 +50,13 @@ public class AuthenticationService extends AbstractComponent {
|
|||
private final AuthenticationFailureHandler failureHandler;
|
||||
private final ThreadContext threadContext;
|
||||
private final String nodeName;
|
||||
private final AnonymousUser anonymousUser;
|
||||
private final boolean signUserHeader;
|
||||
private final boolean runAsEnabled;
|
||||
private final boolean isAnonymousUserEnabled;
|
||||
|
||||
public AuthenticationService(Settings settings, Realms realms, AuditTrailService auditTrail, CryptoService cryptoService,
|
||||
AuthenticationFailureHandler failureHandler, ThreadPool threadPool) {
|
||||
AuthenticationFailureHandler failureHandler, ThreadPool threadPool, AnonymousUser anonymousUser) {
|
||||
super(settings);
|
||||
this.nodeName = Node.NODE_NAME_SETTING.get(settings);
|
||||
this.realms = realms;
|
||||
|
@ -62,8 +64,10 @@ public class AuthenticationService extends AbstractComponent {
|
|||
this.cryptoService = cryptoService;
|
||||
this.failureHandler = failureHandler;
|
||||
this.threadContext = threadPool.getThreadContext();
|
||||
this.anonymousUser = anonymousUser;
|
||||
this.signUserHeader = SIGN_USER_HEADER.get(settings);
|
||||
this.runAsEnabled = RUN_AS_ENABLED.get(settings);
|
||||
this.isAnonymousUserEnabled = AnonymousUser.isAnonymousEnabled(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,6 +161,7 @@ public class AuthenticationService extends AbstractComponent {
|
|||
throw handleNullUser(token);
|
||||
}
|
||||
user = lookupRunAsUserIfNecessary(user, token);
|
||||
checkIfUserIsDisabled(user, token);
|
||||
|
||||
final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy);
|
||||
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||
|
@ -204,9 +209,9 @@ public class AuthenticationService extends AbstractComponent {
|
|||
if (fallbackUser != null) {
|
||||
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
|
||||
authentication = new Authentication(fallbackUser, authenticatedBy, null);
|
||||
} else if (AnonymousUser.enabled()) {
|
||||
} else if (isAnonymousUserEnabled) {
|
||||
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
|
||||
authentication = new Authentication(AnonymousUser.INSTANCE, authenticatedBy, null);
|
||||
authentication = new Authentication(anonymousUser, authenticatedBy, null);
|
||||
}
|
||||
|
||||
if (authentication != null) {
|
||||
|
@ -297,6 +302,13 @@ public class AuthenticationService extends AbstractComponent {
|
|||
return user;
|
||||
}
|
||||
|
||||
void checkIfUserIsDisabled(User user, AuthenticationToken token) {
|
||||
if (user.enabled() == false || (user.runAs() != null && user.runAs().enabled() == false)) {
|
||||
logger.debug("user [{}] is disabled. failing authentication", user);
|
||||
throw request.authenticationFailed(token);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AuditableRequest {
|
||||
|
||||
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
||||
|
|
|
@ -229,7 +229,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand {
|
|||
Path usersFile = FileUserPasswdStore.resolveFile(env);
|
||||
Path usersRolesFile = FileUserRolesStore.resolveFile(env);
|
||||
terminal.println("importing users from [" + usersFile + "]...");
|
||||
Map<String, char[]> userToHashedPW = FileUserPasswdStore.parseFile(usersFile, null);
|
||||
Map<String, char[]> userToHashedPW = FileUserPasswdStore.parseFile(usersFile, null, settings);
|
||||
Map<String, String[]> userToRoles = FileUserRolesStore.parseFile(usersRolesFile, null);
|
||||
Set<String> existingUsers;
|
||||
try {
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.esnative;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||
|
@ -19,12 +17,11 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
|
|||
|
||||
public static final String TYPE = "native";
|
||||
|
||||
final NativeUsersStore userStore;
|
||||
private final NativeUsersStore userStore;
|
||||
|
||||
public NativeRealm(RealmConfig config, NativeUsersStore usersStore) {
|
||||
super(TYPE, config);
|
||||
this.userStore = usersStore;
|
||||
usersStore.addListener(new Listener());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,14 +38,4 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
|
|||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return userStore.verifyPassword(token.principal(), token.credentials());
|
||||
}
|
||||
|
||||
class Listener implements NativeUsersStore.ChangeListener {
|
||||
|
||||
@Override
|
||||
public void onUsersChanged(List<String> usernames) {
|
||||
for (String username : usernames) {
|
||||
expire(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,13 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.esnative;
|
||||
|
||||
import com.carrotsearch.hppc.ObjectHashSet;
|
||||
import com.carrotsearch.hppc.ObjectLongHashMap;
|
||||
import com.carrotsearch.hppc.ObjectLongMap;
|
||||
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.DocWriteResponse;
|
||||
import org.elasticsearch.action.DocWriteResponse.Result;
|
||||
import org.elasticsearch.action.LatchedActionListener;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.delete.DeleteResponse;
|
||||
|
@ -28,7 +25,6 @@ import org.elasticsearch.action.search.SearchResponse;
|
|||
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.ClusterStateListener;
|
||||
|
@ -41,16 +37,12 @@ import org.elasticsearch.common.settings.Setting;
|
|||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
|
||||
import org.elasticsearch.gateway.GatewayService;
|
||||
import org.elasticsearch.index.IndexNotFoundException;
|
||||
import org.elasticsearch.index.engine.DocumentMissingException;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool.Cancellable;
|
||||
import org.elasticsearch.threadpool.ThreadPool.Names;
|
||||
import org.elasticsearch.xpack.security.InternalClient;
|
||||
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
||||
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheRequest;
|
||||
|
@ -64,14 +56,14 @@ import org.elasticsearch.xpack.security.client.SecurityClient;
|
|||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.User.Fields;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
@ -81,25 +73,20 @@ import static org.elasticsearch.xpack.security.Security.setting;
|
|||
import static org.elasticsearch.xpack.security.SecurityTemplateService.securityIndexMappingAndTemplateUpToDate;
|
||||
|
||||
/**
|
||||
* ESNativeUsersStore is a {@code UserStore} that, instead of reading from a
|
||||
* file, reads from an Elasticsearch index instead. This {@code UserStore} in
|
||||
* particular implements both a User store and a UserRoles store, which means it
|
||||
* is responsible for fetching not only {@code User} objects, but also
|
||||
* retrieving the roles for a given username.
|
||||
* NativeUsersStore is a store for users that reads from an Elasticsearch index. This store is responsible for fetching the full
|
||||
* {@link User} object, which includes the names of the roles assigned to the user.
|
||||
* <p>
|
||||
* No caching is done by this class, it is handled at a higher level
|
||||
* No caching is done by this class, it is handled at a higher level and no polling for changes is done by this class. Modification
|
||||
* operations make a best effort attempt to clear the cache on all nodes for the user that was modified.
|
||||
*/
|
||||
public class NativeUsersStore extends AbstractComponent implements ClusterStateListener {
|
||||
|
||||
public static final Setting<Integer> SCROLL_SIZE_SETTING =
|
||||
private static final Setting<Integer> SCROLL_SIZE_SETTING =
|
||||
Setting.intSetting(setting("authc.native.scroll.size"), 1000, Property.NodeScope);
|
||||
|
||||
public static final Setting<TimeValue> SCROLL_KEEP_ALIVE_SETTING =
|
||||
private static final Setting<TimeValue> SCROLL_KEEP_ALIVE_SETTING =
|
||||
Setting.timeSetting(setting("authc.native.scroll.keep_alive"), TimeValue.timeValueSeconds(10L), Property.NodeScope);
|
||||
|
||||
public static final Setting<TimeValue> POLL_INTERVAL_SETTING =
|
||||
Setting.timeSetting(setting("authc.native.reload.interval"), TimeValue.timeValueSeconds(30L), Property.NodeScope);
|
||||
|
||||
public enum State {
|
||||
INITIALIZED,
|
||||
STARTING,
|
||||
|
@ -109,25 +96,20 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
FAILED
|
||||
}
|
||||
|
||||
public static final String USER_DOC_TYPE = "user";
|
||||
static final String RESERVED_USER_DOC_TYPE = "reserved-user";
|
||||
private static final String USER_DOC_TYPE = "user";
|
||||
private static final String RESERVED_USER_DOC_TYPE = "reserved-user";
|
||||
|
||||
private final Hasher hasher = Hasher.BCRYPT;
|
||||
private final List<ChangeListener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED);
|
||||
private final InternalClient client;
|
||||
private final ThreadPool threadPool;
|
||||
|
||||
private Cancellable pollerCancellable;
|
||||
private int scrollSize;
|
||||
private TimeValue scrollKeepAlive;
|
||||
|
||||
private volatile boolean securityIndexExists = false;
|
||||
|
||||
public NativeUsersStore(Settings settings, InternalClient client, ThreadPool threadPool) {
|
||||
public NativeUsersStore(Settings settings, InternalClient client) {
|
||||
super(settings);
|
||||
this.client = client;
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,6 +231,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking method to get the user and their password hash
|
||||
*/
|
||||
private UserAndPassword getUserAndPassword(final String username) {
|
||||
final AtomicReference<UserAndPassword> userRef = new AtomicReference<>(null);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
@ -278,6 +263,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
return userRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Async method to retrieve a user and their password
|
||||
*/
|
||||
private void getUserAndPassword(final String user, final ActionListener<UserAndPassword> listener) {
|
||||
try {
|
||||
GetRequest request = client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, user).request();
|
||||
|
@ -310,17 +298,16 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async method to change the password of a native or reserved user. If a reserved user does not exist, the document will be created
|
||||
* with a hash of the provided password.
|
||||
*/
|
||||
public void changePassword(final ChangePasswordRequest request, final ActionListener<Void> listener) {
|
||||
final String username = request.username();
|
||||
if (SystemUser.NAME.equals(username)) {
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("changing the password for [" + username + "] is not allowed");
|
||||
listener.onFailure(validationException);
|
||||
return;
|
||||
}
|
||||
assert SystemUser.NAME.equals(username) == false && XPackUser.NAME.equals(username) == false : username + "is internal!";
|
||||
|
||||
final String docType;
|
||||
if (ReservedRealm.isReserved(username)) {
|
||||
if (ReservedRealm.isReserved(username, settings)) {
|
||||
docType = RESERVED_USER_DOC_TYPE;
|
||||
} else {
|
||||
docType = USER_DOC_TYPE;
|
||||
|
@ -338,33 +325,30 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
Throwable cause = e;
|
||||
if (e instanceof ElasticsearchException) {
|
||||
cause = ExceptionsHelper.unwrapCause(e);
|
||||
if ((cause instanceof IndexNotFoundException) == false
|
||||
&& (cause instanceof DocumentMissingException) == false) {
|
||||
listener.onFailure(e);
|
||||
return;
|
||||
if (isIndexNotFoundOrDocumentMissing(e)) {
|
||||
if (docType.equals(RESERVED_USER_DOC_TYPE)) {
|
||||
createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener);
|
||||
} else {
|
||||
logger.debug((Supplier<?>) () ->
|
||||
new ParameterizedMessage("failed to change password for user [{}]", request.username()), e);
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("user must exist in order to change password");
|
||||
listener.onFailure(validationException);
|
||||
}
|
||||
}
|
||||
|
||||
if (docType.equals(RESERVED_USER_DOC_TYPE)) {
|
||||
createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener);
|
||||
} else {
|
||||
logger.debug(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"failed to change password for user [{}]", request.username()), cause);
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("user must exist in order to change password");
|
||||
listener.onFailure(validationException);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous method to create a reserved user with the given password hash. The cache for the user will be cleared after the document
|
||||
* has been indexed
|
||||
*/
|
||||
private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener<Void> listener) {
|
||||
client.prepareIndex(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
||||
.setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash))
|
||||
.setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash), Fields.ENABLED.getPreferredName(), true)
|
||||
.setRefreshPolicy(refresh)
|
||||
.execute(new ActionListener<IndexResponse>() {
|
||||
@Override
|
||||
|
@ -379,6 +363,12 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous method to put a user. A put user request without a password hash is treated as an update and will fail with a
|
||||
* {@link ValidationException} if the user does not exist. If a password hash is provided, then we issue a update request with an
|
||||
* upsert document as well; the upsert document sets the enabled flag of the user to true but if the document already exists, this
|
||||
* method will not modify the enabled value.
|
||||
*/
|
||||
public void putUser(final PutUserRequest request, final ActionListener<Boolean> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onFailure(new IllegalStateException("user cannot be added as native user service has not been started"));
|
||||
|
@ -389,7 +379,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
if (request.passwordHash() == null) {
|
||||
updateUserWithoutPassword(request, listener);
|
||||
} else {
|
||||
indexUser(request, listener);
|
||||
upsertUser(request, listener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error((Supplier<?>) () -> new ParameterizedMessage("unable to put user [{}]", request.username()), e);
|
||||
|
@ -397,6 +387,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating a user that should already exist where their password should not change
|
||||
*/
|
||||
private void updateUserWithoutPassword(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||
assert putUserRequest.passwordHash() == null;
|
||||
// We must have an existing document
|
||||
|
@ -416,52 +409,43 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
Throwable cause = e;
|
||||
if (e instanceof ElasticsearchException) {
|
||||
cause = ExceptionsHelper.unwrapCause(e);
|
||||
if ((cause instanceof IndexNotFoundException) == false
|
||||
&& (cause instanceof DocumentMissingException) == false) {
|
||||
listener.onFailure(e);
|
||||
return;
|
||||
}
|
||||
Exception failure = e;
|
||||
if (isIndexNotFoundOrDocumentMissing(e)) {
|
||||
// if the index doesn't exist we can never update a user
|
||||
// if the document doesn't exist, then this update is not valid
|
||||
logger.debug((Supplier<?>) () -> new ParameterizedMessage("failed to update user document with username [{}]",
|
||||
putUserRequest.username()), e);
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("password must be specified unless you are updating an existing user");
|
||||
failure = validationException;
|
||||
}
|
||||
|
||||
// if the index doesn't exist we can never update a user
|
||||
// if the document doesn't exist, then this update is not valid
|
||||
logger.debug(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"failed to update user document with username [{}]",
|
||||
putUserRequest.username()),
|
||||
cause);
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("password must be specified unless you are updating an existing user");
|
||||
listener.onFailure(validationException);
|
||||
listener.onFailure(failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void indexUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||
private void upsertUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||
assert putUserRequest.passwordHash() != null;
|
||||
client.prepareIndex(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||
client.prepareUpdate(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||
USER_DOC_TYPE, putUserRequest.username())
|
||||
.setSource(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(),
|
||||
.setDoc(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(),
|
||||
User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()),
|
||||
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(),
|
||||
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(),
|
||||
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(),
|
||||
User.Fields.METADATA.getPreferredName(), putUserRequest.metadata())
|
||||
.setUpsert(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(),
|
||||
User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()),
|
||||
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(),
|
||||
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(),
|
||||
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(),
|
||||
User.Fields.METADATA.getPreferredName(), putUserRequest.metadata(),
|
||||
User.Fields.ENABLED.getPreferredName(), true)
|
||||
.setRefreshPolicy(putUserRequest.getRefreshPolicy())
|
||||
.execute(new ActionListener<IndexResponse>() {
|
||||
.execute(new ActionListener<UpdateResponse>() {
|
||||
@Override
|
||||
public void onResponse(IndexResponse indexResponse) {
|
||||
// if the document was just created, then we don't need to clear cache
|
||||
boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED;
|
||||
if (created) {
|
||||
listener.onResponse(true);
|
||||
return;
|
||||
}
|
||||
|
||||
clearRealmCache(putUserRequest.username(), listener, created);
|
||||
public void onResponse(UpdateResponse updateResponse) {
|
||||
clearRealmCache(putUserRequest.username(), listener, updateResponse.getResult() == DocWriteResponse.Result.CREATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -471,6 +455,82 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous method that will update the enabled flag of a user. If the user is reserved and the document does not exist, a document
|
||||
* will be created. If the user is not reserved, the user must exist otherwise the operation will fail.
|
||||
*/
|
||||
public void setEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy,
|
||||
final ActionListener<Void> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onFailure(new IllegalStateException("enabled status cannot be changed as native user service has not been started"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReservedRealm.isReserved(username, settings)) {
|
||||
setReservedUserEnabled(username, enabled, refreshPolicy, listener);
|
||||
} else {
|
||||
setRegularUserEnabled(username, enabled, refreshPolicy, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRegularUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy,
|
||||
final ActionListener<Void> listener) {
|
||||
try {
|
||||
client.prepareUpdate(SecurityTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, username)
|
||||
.setDoc(User.Fields.ENABLED.getPreferredName(), enabled)
|
||||
.setRefreshPolicy(refreshPolicy)
|
||||
.execute(new ActionListener<UpdateResponse>() {
|
||||
@Override
|
||||
public void onResponse(UpdateResponse updateResponse) {
|
||||
assert updateResponse.getResult() == Result.UPDATED;
|
||||
clearRealmCache(username, listener, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
Exception failure = e;
|
||||
if (isIndexNotFoundOrDocumentMissing(e)) {
|
||||
// if the index doesn't exist we can never update a user
|
||||
// if the document doesn't exist, then this update is not valid
|
||||
logger.debug((Supplier<?>) () ->
|
||||
new ParameterizedMessage("failed to {} user [{}]", enabled ? "enable" : "disable", username), e);
|
||||
ValidationException validationException = new ValidationException();
|
||||
validationException.addValidationError("only existing users can be " + (enabled ? "enabled" : "disabled"));
|
||||
failure = validationException;
|
||||
}
|
||||
listener.onFailure(failure);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setReservedUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy,
|
||||
final ActionListener<Void> listener) {
|
||||
try {
|
||||
client.prepareUpdate(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
||||
.setDoc(User.Fields.ENABLED.getPreferredName(), enabled)
|
||||
.setUpsert(User.Fields.PASSWORD.getPreferredName(), String.valueOf(ReservedRealm.DEFAULT_PASSWORD_HASH),
|
||||
User.Fields.ENABLED.getPreferredName(), enabled)
|
||||
.setRefreshPolicy(refreshPolicy)
|
||||
.execute(new ActionListener<UpdateResponse>() {
|
||||
@Override
|
||||
public void onResponse(UpdateResponse updateResponse) {
|
||||
assert updateResponse.getResult() == Result.UPDATED || updateResponse.getResult() == Result.CREATED;
|
||||
clearRealmCache(username, listener, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener<Boolean> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onFailure(new IllegalStateException("user cannot be deleted as native user service has not been started"));
|
||||
|
@ -481,7 +541,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
DeleteRequest request = client.prepareDelete(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||
USER_DOC_TYPE, deleteUserRequest.username()).request();
|
||||
request.indicesOptions().ignoreUnavailable();
|
||||
request.setRefreshPolicy(deleteUserRequest.refresh() ? RefreshPolicy.IMMEDIATE : RefreshPolicy.WAIT_UNTIL);
|
||||
request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy());
|
||||
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||
@Override
|
||||
public void onResponse(DeleteResponse deleteResponse) {
|
||||
|
@ -537,15 +597,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
|
||||
this.scrollSize = SCROLL_SIZE_SETTING.get(settings);
|
||||
this.scrollKeepAlive = SCROLL_KEEP_ALIVE_SETTING.get(settings);
|
||||
|
||||
UserStorePoller poller = new UserStorePoller();
|
||||
try {
|
||||
poller.doRun();
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to do initial poll of users", e);
|
||||
}
|
||||
TimeValue interval = settings.getAsTime("shield.authc.native.reload.interval", TimeValue.timeValueSeconds(30L));
|
||||
pollerCancellable = threadPool.scheduleWithFixedDelay(poller, interval, Names.GENERIC);
|
||||
state.set(State.STARTED);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -556,14 +607,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
|
||||
public void stop() {
|
||||
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
||||
try {
|
||||
pollerCancellable.cancel();
|
||||
} catch (Exception e) {
|
||||
state.set(State.FAILED);
|
||||
throw e;
|
||||
} finally {
|
||||
state.set(State.STOPPED);
|
||||
}
|
||||
state.set(State.STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,7 +618,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
* @param password the plaintext password to verify
|
||||
* @return {@link} User object if successful or {@code null} if verification fails
|
||||
*/
|
||||
public User verifyPassword(String username, final SecuredString password) {
|
||||
User verifyPassword(String username, final SecuredString password) {
|
||||
if (state() != State.STARTED) {
|
||||
logger.trace("attempted to verify user credentials for [{}] but service was not started", username);
|
||||
return null;
|
||||
|
@ -590,11 +634,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
return null;
|
||||
}
|
||||
|
||||
public void addListener(ChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
boolean started() {
|
||||
public boolean started() {
|
||||
return state() == State.STARTED;
|
||||
}
|
||||
|
||||
|
@ -602,9 +642,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
return securityIndexExists;
|
||||
}
|
||||
|
||||
char[] reservedUserPassword(String username) throws Exception {
|
||||
ReservedUserInfo getReservedUserInfo(String username) throws Exception {
|
||||
assert started();
|
||||
final AtomicReference<char[]> passwordHash = new AtomicReference<>();
|
||||
final AtomicReference<ReservedUserInfo> userInfoRef = new AtomicReference<>();
|
||||
final AtomicReference<Exception> failure = new AtomicReference<>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
||||
|
@ -614,26 +654,26 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
if (getResponse.isExists()) {
|
||||
Map<String, Object> sourceMap = getResponse.getSourceAsMap();
|
||||
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
||||
Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName());
|
||||
if (password == null || password.isEmpty()) {
|
||||
failure.set(new IllegalStateException("password hash must not be empty!"));
|
||||
return;
|
||||
} else if (enabled == null) {
|
||||
failure.set(new IllegalStateException("enabled must not be null!"));
|
||||
} else {
|
||||
userInfoRef.set(new ReservedUserInfo(password.toCharArray(), enabled));
|
||||
}
|
||||
passwordHash.set(password.toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
if (e instanceof IndexNotFoundException) {
|
||||
logger.trace(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"could not retrieve built in user [{}] password since security index does not exist",
|
||||
username),
|
||||
e);
|
||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage(
|
||||
"could not retrieve built in user [{}] info since security index does not exist", username), e);
|
||||
} else {
|
||||
logger.error(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"failed to retrieve built in user [{}] password", username), e);
|
||||
"failed to retrieve built in user [{}] info", username), e);
|
||||
failure.set(e);
|
||||
}
|
||||
}
|
||||
|
@ -653,7 +693,65 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
// if there is any sort of failure we need to throw an exception to prevent the fallback to the default password...
|
||||
throw failureCause;
|
||||
}
|
||||
return passwordHash.get();
|
||||
return userInfoRef.get();
|
||||
}
|
||||
|
||||
Map<String, ReservedUserInfo> getAllReservedUserInfo() throws Exception {
|
||||
assert started();
|
||||
final Map<String, ReservedUserInfo> userInfos = new HashMap<>();
|
||||
final AtomicReference<Exception> failure = new AtomicReference<>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
client.prepareSearch(SecurityTemplateService.SECURITY_INDEX_NAME)
|
||||
.setTypes(RESERVED_USER_DOC_TYPE)
|
||||
.setQuery(QueryBuilders.matchAllQuery())
|
||||
.setFetchSource(true)
|
||||
.execute(new LatchedActionListener<>(new ActionListener<SearchResponse>() {
|
||||
@Override
|
||||
public void onResponse(SearchResponse searchResponse) {
|
||||
assert searchResponse.getHits().getTotalHits() <= 10 : "there are more than 10 reserved users we need to change " +
|
||||
"this to retrieve them all!";
|
||||
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
|
||||
Map<String, Object> sourceMap = searchHit.getSource();
|
||||
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
||||
Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName());
|
||||
if (password == null || password.isEmpty()) {
|
||||
failure.set(new IllegalStateException("password hash must not be empty!"));
|
||||
break;
|
||||
} else if (enabled == null) {
|
||||
failure.set(new IllegalStateException("enabled must not be null!"));
|
||||
break;
|
||||
} else {
|
||||
userInfos.put(searchHit.getId(), new ReservedUserInfo(password.toCharArray(), enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
if (e instanceof IndexNotFoundException) {
|
||||
logger.trace("could not retrieve built in users since security index does not exist", e);
|
||||
} else {
|
||||
logger.error("failed to retrieve built in users", e);
|
||||
failure.set(e);
|
||||
}
|
||||
}
|
||||
}, latch));
|
||||
|
||||
try {
|
||||
final boolean responseReceived = latch.await(30, TimeUnit.SECONDS);
|
||||
if (responseReceived == false) {
|
||||
failure.set(new TimeoutException("timed out trying to get built in users"));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
failure.set(e);
|
||||
}
|
||||
|
||||
Exception failureCause = failure.get();
|
||||
if (failureCause != null) {
|
||||
// if there is any sort of failure we need to throw an exception to prevent the fallback to the default password...
|
||||
throw failureCause;
|
||||
}
|
||||
return userInfos;
|
||||
}
|
||||
|
||||
private void clearScrollResponse(String scrollId) {
|
||||
|
@ -716,7 +814,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
if (state != State.STOPPED && state != State.FAILED) {
|
||||
throw new IllegalStateException("can only reset if stopped!!!");
|
||||
}
|
||||
this.listeners.clear();
|
||||
this.securityIndexExists = false;
|
||||
this.state.set(State.INITIALIZED);
|
||||
}
|
||||
|
@ -731,158 +828,42 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
String[] roles = ((List<String>) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY);
|
||||
String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName());
|
||||
String email = (String) sourceMap.get(User.Fields.EMAIL.getPreferredName());
|
||||
Boolean enabled = (Boolean) sourceMap.get(User.Fields.ENABLED.getPreferredName());
|
||||
if (enabled == null) {
|
||||
// fallback mechanism as a user from 2.x may not have the enabled field
|
||||
enabled = Boolean.TRUE;
|
||||
}
|
||||
Map<String, Object> metadata = (Map<String, Object>) sourceMap.get(User.Fields.METADATA.getPreferredName());
|
||||
return new UserAndPassword(new User(username, roles, fullName, email, metadata), password.toCharArray());
|
||||
return new UserAndPassword(new User(username, roles, fullName, email, metadata, enabled), password.toCharArray());
|
||||
} catch (Exception e) {
|
||||
logger.error((Supplier<?>) () -> new ParameterizedMessage("error in the format of data for user [{}]", username), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class UserStorePoller extends AbstractRunnable {
|
||||
|
||||
// this map contains the mapping for username -> version, which is used when polling the index to easily detect of
|
||||
// any changes that may have been missed since the last update.
|
||||
private final ObjectLongHashMap<String> userVersionMap = new ObjectLongHashMap<>();
|
||||
private final ObjectLongHashMap<String> reservedUserVersionMap = new ObjectLongHashMap<>();
|
||||
|
||||
@Override
|
||||
public void doRun() {
|
||||
// hold a reference to the client since the poller may run after the class is stopped (we don't interrupt it running) and
|
||||
// we reset when we test which sets the client to null...
|
||||
final Client client = NativeUsersStore.this.client;
|
||||
if (isStopped()) {
|
||||
return;
|
||||
private static boolean isIndexNotFoundOrDocumentMissing(Exception e) {
|
||||
if (e instanceof ElasticsearchException) {
|
||||
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
||||
if (cause instanceof IndexNotFoundException || cause instanceof DocumentMissingException) {
|
||||
return true;
|
||||
}
|
||||
if (securityIndexExists == false) {
|
||||
logger.trace("cannot poll for user changes since security index [{}] does not exist", SecurityTemplateService
|
||||
.SECURITY_INDEX_NAME);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.trace("starting polling of user index to check for changes");
|
||||
List<String> changedUsers = scrollForModifiedUsers(client, USER_DOC_TYPE, userVersionMap);
|
||||
if (isStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
changedUsers.addAll(scrollForModifiedUsers(client, RESERVED_USER_DOC_TYPE, reservedUserVersionMap));
|
||||
if (isStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyListeners(changedUsers);
|
||||
logger.trace("finished polling of user index");
|
||||
}
|
||||
|
||||
private List<String> scrollForModifiedUsers(Client client, String docType, ObjectLongMap<String> usersMap) {
|
||||
// create a copy of all known users
|
||||
ObjectHashSet<String> knownUsers = new ObjectHashSet<>(usersMap.keys());
|
||||
List<String> changedUsers = new ArrayList<>();
|
||||
|
||||
SearchResponse response = null;
|
||||
try {
|
||||
client.admin().indices().prepareRefresh(SecurityTemplateService.SECURITY_INDEX_NAME).get();
|
||||
response = client.prepareSearch(SecurityTemplateService.SECURITY_INDEX_NAME)
|
||||
.setScroll(scrollKeepAlive)
|
||||
.setQuery(QueryBuilders.typeQuery(docType))
|
||||
.setSize(scrollSize)
|
||||
.setVersion(true)
|
||||
.setFetchSource(false) // we only need id and version
|
||||
.get();
|
||||
|
||||
boolean keepScrolling = response.getHits().getHits().length > 0;
|
||||
while (keepScrolling) {
|
||||
for (SearchHit hit : response.getHits().getHits()) {
|
||||
final String username = hit.id();
|
||||
final long version = hit.version();
|
||||
if (knownUsers.contains(username)) {
|
||||
final long lastKnownVersion = usersMap.get(username);
|
||||
if (version != lastKnownVersion) {
|
||||
// version is only changed by this method
|
||||
assert version > lastKnownVersion;
|
||||
usersMap.put(username, version);
|
||||
// there is a chance that the user's cache has already been cleared and we'll clear it again but
|
||||
// this should be ok in most cases as user changes should not be that frequent
|
||||
changedUsers.add(username);
|
||||
}
|
||||
knownUsers.remove(username);
|
||||
} else {
|
||||
usersMap.put(username, version);
|
||||
}
|
||||
}
|
||||
|
||||
if (isStopped()) {
|
||||
// bail here
|
||||
return Collections.emptyList();
|
||||
}
|
||||
response = client.prepareSearchScroll(response.getScrollId()).setScroll(scrollKeepAlive).get();
|
||||
keepScrolling = response.getHits().getHits().length > 0;
|
||||
}
|
||||
} catch (IndexNotFoundException e) {
|
||||
logger.trace("security index does not exist", e);
|
||||
} finally {
|
||||
if (response != null && response.getScrollId() != null) {
|
||||
ClearScrollRequest clearScrollRequest = client.prepareClearScroll().addScrollId(response.getScrollId()).request();
|
||||
client.clearScroll(clearScrollRequest).actionGet();
|
||||
}
|
||||
}
|
||||
|
||||
// we now have a list of users that were in our version map and have been deleted
|
||||
Iterator<ObjectCursor<String>> userIter = knownUsers.iterator();
|
||||
while (userIter.hasNext()) {
|
||||
String user = userIter.next().value;
|
||||
usersMap.remove(user);
|
||||
changedUsers.add(user);
|
||||
}
|
||||
|
||||
return changedUsers;
|
||||
}
|
||||
|
||||
private void notifyListeners(List<String> changedUsers) {
|
||||
if (changedUsers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make the list unmodifiable to prevent modifications by any listeners
|
||||
changedUsers = Collections.unmodifiableList(changedUsers);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("changes detected for users [{}]", changedUsers);
|
||||
}
|
||||
|
||||
// call listeners
|
||||
RuntimeException ex = null;
|
||||
for (ChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.onUsersChanged(changedUsers);
|
||||
} catch (Exception e) {
|
||||
if (ex == null) ex = new RuntimeException("exception while notifying listeners");
|
||||
ex.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (ex != null) throw ex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
logger.error("error occurred while checking the native users for changes", e);
|
||||
}
|
||||
|
||||
private boolean isStopped() {
|
||||
State state = state();
|
||||
return state == State.STOPPED || state == State.STOPPING;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
interface ChangeListener {
|
||||
static class ReservedUserInfo {
|
||||
|
||||
void onUsersChanged(List<String> username);
|
||||
final char[] passwordHash;
|
||||
final boolean enabled;
|
||||
|
||||
ReservedUserInfo(char[] passwordHash, boolean enabled) {
|
||||
this.passwordHash = passwordHash;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSettings(List<Setting<?>> settings) {
|
||||
settings.add(SCROLL_SIZE_SETTING);
|
||||
settings.add(SCROLL_KEEP_ALIVE_SETTING);
|
||||
settings.add(POLL_INTERVAL_SETTING);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.apache.logging.log4j.util.Supplier;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ChangeListener;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
|
||||
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||
|
@ -21,9 +21,12 @@ import org.elasticsearch.xpack.security.user.ElasticUser;
|
|||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A realm for predefined users. These users can only be modified in terms of changing their passwords; no other modifications are allowed.
|
||||
|
@ -32,40 +35,35 @@ import java.util.List;
|
|||
public class ReservedRealm extends CachingUsernamePasswordRealm {
|
||||
|
||||
public static final String TYPE = "reserved";
|
||||
private static final char[] DEFAULT_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray()));
|
||||
static final char[] DEFAULT_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray()));
|
||||
private static final ReservedUserInfo DEFAULT_USER_INFO = new ReservedUserInfo(DEFAULT_PASSWORD_HASH, true);
|
||||
|
||||
private final NativeUsersStore nativeUsersStore;
|
||||
private final AnonymousUser anonymousUser;
|
||||
private final boolean anonymousEnabled;
|
||||
|
||||
public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore) {
|
||||
public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore, AnonymousUser anonymousUser) {
|
||||
super(TYPE, new RealmConfig(TYPE, Settings.EMPTY, settings, env));
|
||||
this.nativeUsersStore = nativeUsersStore;
|
||||
nativeUsersStore.addListener(new ChangeListener() {
|
||||
@Override
|
||||
public void onUsersChanged(List<String> changedUsers) {
|
||||
changedUsers.stream()
|
||||
.filter(ReservedRealm::isReserved)
|
||||
.forEach(ReservedRealm.this::expire);
|
||||
}
|
||||
});
|
||||
|
||||
this.anonymousUser = anonymousUser;
|
||||
this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
final User user = getUser(token.principal());
|
||||
if (user == null) {
|
||||
if (isReserved(token.principal(), config.globalSettings()) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final char[] passwordHash = getPasswordHash(user.principal());
|
||||
if (passwordHash != null) {
|
||||
final ReservedUserInfo userInfo = getUserInfo(token.principal());
|
||||
if (userInfo != null) {
|
||||
try {
|
||||
if (Hasher.BCRYPT.verify(token.credentials(), passwordHash)) {
|
||||
return user;
|
||||
if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
|
||||
return getUser(token.principal(), userInfo);
|
||||
}
|
||||
} finally {
|
||||
if (passwordHash != DEFAULT_PASSWORD_HASH) {
|
||||
Arrays.fill(passwordHash, (char) 0);
|
||||
if (userInfo.passwordHash != DEFAULT_PASSWORD_HASH) {
|
||||
Arrays.fill(userInfo.passwordHash, (char) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +73,20 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
|||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
return getUser(username);
|
||||
if (isReserved(username, config.globalSettings()) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (AnonymousUser.isAnonymousUsername(username, config.globalSettings())) {
|
||||
return anonymousEnabled ? anonymousUser : null;
|
||||
}
|
||||
|
||||
final ReservedUserInfo userInfo = getUserInfo(username);
|
||||
if (userInfo != null) {
|
||||
return getUser(username, userInfo);
|
||||
}
|
||||
// this was a reserved username - don't allow this to go to another realm...
|
||||
throw Exceptions.authenticationError("failed to lookup user [{}]", username);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,54 +94,71 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static boolean isReserved(String username) {
|
||||
public static boolean isReserved(String username, Settings settings) {
|
||||
assert username != null;
|
||||
switch (username) {
|
||||
case ElasticUser.NAME:
|
||||
case KibanaUser.NAME:
|
||||
return true;
|
||||
default:
|
||||
return AnonymousUser.isAnonymousUsername(username);
|
||||
return AnonymousUser.isAnonymousUsername(username, settings);
|
||||
}
|
||||
}
|
||||
|
||||
public static User getUser(String username) {
|
||||
User getUser(String username, ReservedUserInfo userInfo) {
|
||||
assert username != null;
|
||||
switch (username) {
|
||||
case ElasticUser.NAME:
|
||||
return ElasticUser.INSTANCE;
|
||||
return new ElasticUser(userInfo.enabled);
|
||||
case KibanaUser.NAME:
|
||||
return KibanaUser.INSTANCE;
|
||||
return new KibanaUser(userInfo.enabled);
|
||||
default:
|
||||
if (AnonymousUser.enabled() && AnonymousUser.isAnonymousUsername(username)) {
|
||||
return AnonymousUser.INSTANCE;
|
||||
if (anonymousEnabled && anonymousUser.principal().equals(username)) {
|
||||
return anonymousUser;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<User> users() {
|
||||
if (AnonymousUser.enabled()) {
|
||||
return Arrays.asList(ElasticUser.INSTANCE, KibanaUser.INSTANCE, AnonymousUser.INSTANCE);
|
||||
public Collection<User> users() {
|
||||
if (nativeUsersStore.started() == false) {
|
||||
return anonymousEnabled ? Collections.singletonList(anonymousUser) : Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
||||
|
||||
List<User> users = new ArrayList<>(3);
|
||||
try {
|
||||
Map<String, ReservedUserInfo> reservedUserInfos = nativeUsersStore.getAllReservedUserInfo();
|
||||
ReservedUserInfo userInfo = reservedUserInfos.get(ElasticUser.NAME);
|
||||
users.add(new ElasticUser(userInfo == null || userInfo.enabled));
|
||||
userInfo = reservedUserInfos.get(KibanaUser.NAME);
|
||||
users.add(new KibanaUser(userInfo == null || userInfo.enabled));
|
||||
if (anonymousEnabled) {
|
||||
users.add(anonymousUser);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("failed to retrieve reserved users", e);
|
||||
return anonymousEnabled ? Collections.singletonList(anonymousUser) : Collections.emptyList();
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
private char[] getPasswordHash(final String username) {
|
||||
private ReservedUserInfo getUserInfo(final String username) {
|
||||
if (nativeUsersStore.started() == false) {
|
||||
// we need to be able to check for the user store being started...
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nativeUsersStore.securityIndexExists() == false) {
|
||||
return DEFAULT_PASSWORD_HASH;
|
||||
return DEFAULT_USER_INFO;
|
||||
}
|
||||
|
||||
try {
|
||||
char[] passwordHash = nativeUsersStore.reservedUserPassword(username);
|
||||
if (passwordHash == null) {
|
||||
return DEFAULT_PASSWORD_HASH;
|
||||
ReservedUserInfo userInfo = nativeUsersStore.getReservedUserInfo(username);
|
||||
if (userInfo == null) {
|
||||
return DEFAULT_USER_INFO;
|
||||
}
|
||||
return passwordHash;
|
||||
return userInfo;
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
(Supplier<?>) () -> new ParameterizedMessage("failed to retrieve password hash for reserved user [{}]", username), e);
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
|
|||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.watcher.FileChangesListener;
|
||||
import org.elasticsearch.watcher.FileWatcher;
|
||||
|
@ -43,7 +44,8 @@ public class FileUserPasswdStore {
|
|||
private final Logger logger;
|
||||
|
||||
private final Path file;
|
||||
final Hasher hasher = Hasher.BCRYPT;
|
||||
private final Hasher hasher = Hasher.BCRYPT;
|
||||
private final Settings settings;
|
||||
|
||||
private volatile Map<String, char[]> users;
|
||||
|
||||
|
@ -56,7 +58,8 @@ public class FileUserPasswdStore {
|
|||
FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
|
||||
logger = config.logger(FileUserPasswdStore.class);
|
||||
file = resolveFile(config.env());
|
||||
users = parseFileLenient(file, logger);
|
||||
settings = config.globalSettings();
|
||||
users = parseFileLenient(file, logger, settings);
|
||||
FileWatcher watcher = new FileWatcher(file.getParent());
|
||||
watcher.addListener(new FileListener());
|
||||
try {
|
||||
|
@ -96,9 +99,9 @@ public class FileUserPasswdStore {
|
|||
* Internally in this class, we try to load the file, but if for some reason we can't, we're being more lenient by
|
||||
* logging the error and skipping all users. This is aligned with how we handle other auto-loaded files in security.
|
||||
*/
|
||||
static Map<String, char[]> parseFileLenient(Path path, Logger logger) {
|
||||
static Map<String, char[]> parseFileLenient(Path path, Logger logger, Settings settings) {
|
||||
try {
|
||||
return parseFile(path, logger);
|
||||
return parseFile(path, logger, settings);
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
|
@ -111,7 +114,7 @@ public class FileUserPasswdStore {
|
|||
* parses the users file. Should never return {@code null}, if the file doesn't exist an
|
||||
* empty map is returned
|
||||
*/
|
||||
public static Map<String, char[]> parseFile(Path path, @Nullable Logger logger) {
|
||||
public static Map<String, char[]> parseFile(Path path, @Nullable Logger logger, Settings settings) {
|
||||
if (logger == null) {
|
||||
logger = NoOpLogger.INSTANCE;
|
||||
}
|
||||
|
@ -146,7 +149,7 @@ public class FileUserPasswdStore {
|
|||
continue;
|
||||
}
|
||||
String username = line.substring(0, i);
|
||||
Validation.Error validationError = Users.validateUsername(username);
|
||||
Validation.Error validationError = Users.validateUsername(username, false, settings);
|
||||
if (validationError != null) {
|
||||
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(),
|
||||
validationError);
|
||||
|
@ -191,7 +194,7 @@ public class FileUserPasswdStore {
|
|||
public void onFileChanged(Path file) {
|
||||
if (file.equals(FileUserPasswdStore.this.file)) {
|
||||
logger.info("users file [{}] changed. updating users... )", file.toAbsolutePath());
|
||||
users = parseFileLenient(file, logger);
|
||||
users = parseFileLenient(file, logger, settings);
|
||||
notifyRefresh();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,21 +84,21 @@ public class UsersTool extends MultiCommand {
|
|||
|
||||
@Override
|
||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
||||
String username = parseUsername(arguments.values(options));
|
||||
Validation.Error validationError = Users.validateUsername(username);
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
String username = parseUsername(arguments.values(options), env.settings());
|
||||
Validation.Error validationError = Users.validateUsername(username, false, Settings.EMPTY);
|
||||
if (validationError != null) {
|
||||
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
||||
}
|
||||
|
||||
char[] password = parsePassword(terminal, passwordOption.value(options));
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
String[] roles = parseRoles(terminal, env, rolesOption.value(options));
|
||||
|
||||
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
||||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
||||
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null));
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null, env.settings()));
|
||||
if (users.containsKey(username)) {
|
||||
throw new UserException(ExitCodes.CODE_ERROR, "User [" + username + "] already exists");
|
||||
}
|
||||
|
@ -138,13 +138,13 @@ public class UsersTool extends MultiCommand {
|
|||
|
||||
@Override
|
||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
||||
String username = parseUsername(arguments.values(options));
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
String username = parseUsername(arguments.values(options), env.settings());
|
||||
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
||||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
||||
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null));
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(passwordFile, null, env.settings()));
|
||||
if (users.containsKey(username) == false) {
|
||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||
}
|
||||
|
@ -193,13 +193,13 @@ public class UsersTool extends MultiCommand {
|
|||
|
||||
@Override
|
||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
||||
String username = parseUsername(arguments.values(options));
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
String username = parseUsername(arguments.values(options), env.settings());
|
||||
char[] password = parsePassword(terminal, passwordOption.value(options));
|
||||
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
Path file = FileUserPasswdStore.resolveFile(env);
|
||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(file);
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
|
||||
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null, env.settings()));
|
||||
if (users.containsKey(username) == false) {
|
||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||
}
|
||||
|
@ -237,8 +237,8 @@ public class UsersTool extends MultiCommand {
|
|||
|
||||
@Override
|
||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
||||
String username = parseUsername(arguments.values(options));
|
||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||
String username = parseUsername(arguments.values(options), env.settings());
|
||||
String[] addRoles = parseRoles(terminal, env, addOption.value(options));
|
||||
String[] removeRoles = parseRoles(terminal, env, removeOption.value(options));
|
||||
|
||||
|
@ -254,7 +254,7 @@ public class UsersTool extends MultiCommand {
|
|||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(usersFile, rolesFile);
|
||||
|
||||
Map<String, char[]> usersMap = FileUserPasswdStore.parseFile(usersFile, null);
|
||||
Map<String, char[]> usersMap = FileUserPasswdStore.parseFile(usersFile, null, env.settings());
|
||||
if (!usersMap.containsKey(username)) {
|
||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ public class UsersTool extends MultiCommand {
|
|||
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
||||
|
||||
Path userFilePath = FileUserPasswdStore.resolveFile(env);
|
||||
Set<String> users = FileUserPasswdStore.parseFile(userFilePath, null).keySet();
|
||||
Set<String> users = FileUserPasswdStore.parseFile(userFilePath, null, env.settings()).keySet();
|
||||
|
||||
Path rolesFilePath = FileRolesStore.resolveFile(env);
|
||||
Set<String> knownRoles = Sets.union(FileRolesStore.parseFileForRoleNames(rolesFilePath, null), ReservedRolesStore.names());
|
||||
|
@ -388,14 +388,14 @@ public class UsersTool extends MultiCommand {
|
|||
}
|
||||
|
||||
// pkg private for testing
|
||||
static String parseUsername(List<String> args) throws UserException {
|
||||
static String parseUsername(List<String> args, Settings settings) throws UserException {
|
||||
if (args.isEmpty()) {
|
||||
throw new UserException(ExitCodes.USAGE, "Missing username argument");
|
||||
} else if (args.size() > 1) {
|
||||
throw new UserException(ExitCodes.USAGE, "Expected a single username argument, found extra: " + args.toString());
|
||||
}
|
||||
String username = args.get(0);
|
||||
Validation.Error validationError = Users.validateUsername(username);
|
||||
Validation.Error validationError = Users.validateUsername(username, false, settings);
|
||||
if (validationError != null) {
|
||||
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import org.elasticsearch.common.cache.CacheLoader;
|
|||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.support.Exceptions;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -149,11 +148,11 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
|
||||
CacheLoader<String, UserWithHash> callback = key -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user not found in cache, proceeding with normal lookup");
|
||||
logger.debug("user [{}] not found in cache, proceeding with normal lookup", username);
|
||||
}
|
||||
User user = doLookupUser(username);
|
||||
if (user == null) {
|
||||
throw Exceptions.authenticationError("could not lookup [{}]", username);
|
||||
return null;
|
||||
}
|
||||
return new UserWithHash(user, null, null);
|
||||
};
|
||||
|
@ -162,10 +161,15 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
UserWithHash userWithHash = cache.computeIfAbsent(username, callback);
|
||||
return userWithHash.user;
|
||||
} catch (ExecutionException ee) {
|
||||
if (ee.getCause() instanceof ElasticsearchSecurityException) {
|
||||
// this should bubble out
|
||||
throw (ElasticsearchSecurityException) ee.getCause();
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage("realm [{}] could not lookup [{}]", name(), username), ee);
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("realm [{}] could not authenticate [{}]", name(), username);
|
||||
logger.debug("realm [{}] could not lookup [{}]", name(), username);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -77,11 +77,13 @@ public class AuthorizationService extends AbstractComponent {
|
|||
private final IndicesAndAliasesResolver[] indicesAndAliasesResolvers;
|
||||
private final AuthenticationFailureHandler authcFailureHandler;
|
||||
private final ThreadContext threadContext;
|
||||
private final AnonymousUser anonymousUser;
|
||||
private final boolean isAnonymousEnabled;
|
||||
private final boolean anonymousAuthzExceptionEnabled;
|
||||
|
||||
public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService,
|
||||
AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler,
|
||||
ThreadPool threadPool) {
|
||||
ThreadPool threadPool, AnonymousUser anonymousUser) {
|
||||
super(settings);
|
||||
this.rolesStore = rolesStore;
|
||||
this.clusterService = clusterService;
|
||||
|
@ -91,6 +93,8 @@ public class AuthorizationService extends AbstractComponent {
|
|||
};
|
||||
this.authcFailureHandler = authcFailureHandler;
|
||||
this.threadContext = threadPool.getThreadContext();
|
||||
this.anonymousUser = anonymousUser;
|
||||
this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
|
||||
this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings);
|
||||
}
|
||||
|
||||
|
@ -101,7 +105,7 @@ public class AuthorizationService extends AbstractComponent {
|
|||
* @param action The action
|
||||
*/
|
||||
public List<String> authorizedIndicesAndAliases(User user, String action) {
|
||||
final String[] anonymousRoles = AnonymousUser.enabled() ? AnonymousUser.getRoles() : Strings.EMPTY_ARRAY;
|
||||
final String[] anonymousRoles = isAnonymousEnabled ? anonymousUser.roles() : Strings.EMPTY_ARRAY;
|
||||
String[] rolesNames = user.roles();
|
||||
if (rolesNames.length == 0 && anonymousRoles.length == 0) {
|
||||
return Collections.emptyList();
|
||||
|
@ -114,7 +118,7 @@ public class AuthorizationService extends AbstractComponent {
|
|||
predicates.add(role.indices().allowedIndicesMatcher(action));
|
||||
}
|
||||
}
|
||||
if (AnonymousUser.is(user) == false) {
|
||||
if (anonymousUser.equals(user) == false) {
|
||||
for (String roleName : anonymousRoles) {
|
||||
Role role = rolesStore.role(roleName);
|
||||
if (role != null) {
|
||||
|
@ -360,7 +364,7 @@ public class AuthorizationService extends AbstractComponent {
|
|||
private ElasticsearchSecurityException denialException(Authentication authentication, String action) {
|
||||
final User user = authentication.getUser();
|
||||
// Special case for anonymous user
|
||||
if (AnonymousUser.enabled() && AnonymousUser.is(user)) {
|
||||
if (isAnonymousEnabled && anonymousUser.equals(user)) {
|
||||
if (anonymousAuthzExceptionEnabled == false) {
|
||||
throw authcFailureHandler.authenticationRequired(action, threadContext);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.elasticsearch.action.search.MultiSearchResponse.Item;
|
|||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.ClusterStateListener;
|
||||
|
@ -74,7 +73,7 @@ import static org.elasticsearch.xpack.security.Security.setting;
|
|||
import static org.elasticsearch.xpack.security.SecurityTemplateService.securityIndexMappingAndTemplateUpToDate;
|
||||
|
||||
/**
|
||||
* ESNativeRolesStore is a {@code RolesStore} that, instead of reading from a
|
||||
* NativeRolesStore is a {@code RolesStore} that, instead of reading from a
|
||||
* file, reads from an Elasticsearch index instead. Unlike the file-based roles
|
||||
* store, ESNativeRolesStore can be used to add a role to the store by inserting
|
||||
* the document into the administrative index.
|
||||
|
@ -264,7 +263,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C
|
|||
try {
|
||||
DeleteRequest request = client.prepareDelete(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||
ROLE_DOC_TYPE, deleteRoleRequest.name()).request();
|
||||
request.setRefreshPolicy(deleteRoleRequest.refresh() ? RefreshPolicy.IMMEDIATE : RefreshPolicy.WAIT_UNTIL);
|
||||
request.setRefreshPolicy(deleteRoleRequest.getRefreshPolicy());
|
||||
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||
@Override
|
||||
public void onResponse(DeleteResponse deleteResponse) {
|
||||
|
|
|
@ -24,12 +24,14 @@ import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
|||
import org.elasticsearch.xpack.security.authz.permission.TransportClientRole;
|
||||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ReservedRolesStore implements RolesStore {
|
||||
|
||||
private static final User DEFAULT_ENABLED_KIBANA_USER = new KibanaUser(true);
|
||||
private final SecurityContext securityContext;
|
||||
|
||||
public ReservedRolesStore(SecurityContext securityContext) {
|
||||
|
@ -54,8 +56,9 @@ public class ReservedRolesStore implements RolesStore {
|
|||
case KibanaRole.NAME:
|
||||
// The only user that should know about this role is the kibana user itself (who has this role). The reason we want to hide
|
||||
// this role is that it was created specifically for kibana, with all the permissions that the kibana user needs.
|
||||
// We don't want it to be assigned to other users.
|
||||
if (KibanaUser.is(securityContext.getUser())) {
|
||||
// We don't want it to be assigned to other users. The Kibana user here must always be enabled if it is in the
|
||||
// security context
|
||||
if (DEFAULT_ENABLED_KIBANA_USER.equals(securityContext.getUser())) {
|
||||
return KibanaRole.INSTANCE;
|
||||
}
|
||||
return null;
|
||||
|
@ -87,7 +90,7 @@ public class ReservedRolesStore implements RolesStore {
|
|||
// The only user that should know about this role is the kibana user itself (who has this role). The reason we want to hide
|
||||
// this role is that it was created specifically for kibana, with all the permissions that the kibana user needs.
|
||||
// We don't want it to be assigned to other users.
|
||||
if (KibanaUser.is(securityContext.getUser())) {
|
||||
if (DEFAULT_ENABLED_KIBANA_USER.equals(securityContext.getUser())) {
|
||||
return KibanaRole.DESCRIPTOR;
|
||||
}
|
||||
return null;
|
||||
|
@ -97,7 +100,7 @@ public class ReservedRolesStore implements RolesStore {
|
|||
}
|
||||
|
||||
public Collection<RoleDescriptor> roleDescriptors() {
|
||||
if (KibanaUser.is(securityContext.getUser())) {
|
||||
if (DEFAULT_ENABLED_KIBANA_USER.equals(securityContext.getUser())) {
|
||||
return Arrays.asList(SuperuserRole.DESCRIPTOR, TransportClientRole.DESCRIPTOR, KibanaUserRole.DESCRIPTOR,
|
||||
KibanaRole.DESCRIPTOR, MonitoringUserRole.DESCRIPTOR, RemoteMonitoringAgentRole.DESCRIPTOR,
|
||||
IngestAdminRole.DESCRIPTOR);
|
||||
|
|
|
@ -45,6 +45,10 @@ import org.elasticsearch.xpack.security.action.user.PutUserAction;
|
|||
import org.elasticsearch.xpack.security.action.user.PutUserRequest;
|
||||
import org.elasticsearch.xpack.security.action.user.PutUserRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.user.PutUserResponse;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledAction;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledRequest;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -163,6 +167,14 @@ public class SecurityClient {
|
|||
client.execute(ChangePasswordAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
public SetEnabledRequestBuilder prepareSetEnabled(String username, boolean enabled) {
|
||||
return new SetEnabledRequestBuilder(client).username(username).enabled(enabled);
|
||||
}
|
||||
|
||||
public void setEnabled(SetEnabledRequest request, ActionListener<SetEnabledResponse> listener) {
|
||||
client.execute(SetEnabledAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
/** Role Management */
|
||||
|
||||
public GetRolesRequestBuilder prepareGetRoles(String... names) {
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.elasticsearch.rest.RestRequest;
|
|||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.xpack.security.action.role.DeleteRoleRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.role.DeleteRoleResponse;
|
||||
import org.elasticsearch.xpack.security.client.SecurityClient;
|
||||
|
||||
|
@ -42,18 +41,16 @@ public class RestDeleteRoleAction extends BaseRestHandler {
|
|||
|
||||
@Override
|
||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||
DeleteRoleRequestBuilder requestBuilder = new SecurityClient(client).prepareDeleteRole(request.param("name"));
|
||||
if (request.hasParam("refresh")) {
|
||||
requestBuilder.refresh(request.paramAsBoolean("refresh", true));
|
||||
}
|
||||
requestBuilder.execute(new RestBuilderListener<DeleteRoleResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(DeleteRoleResponse response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||
builder.startObject()
|
||||
.field("found", response.found())
|
||||
.endObject());
|
||||
}
|
||||
});
|
||||
new SecurityClient(client).prepareDeleteRole(request.param("name"))
|
||||
.setRefreshPolicy(request.param("refresh"))
|
||||
.execute(new RestBuilderListener<DeleteRoleResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(DeleteRoleResponse response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||
builder.startObject()
|
||||
.field("found", response.found())
|
||||
.endObject());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class RestChangePasswordAction extends BaseRestHandler {
|
|||
final User user = securityContext.getUser();
|
||||
String username = request.param("username");
|
||||
if (username == null) {
|
||||
username = user.runAs() == null ? user.principal() : user.runAs().principal();;
|
||||
username = user.runAs() == null ? user.principal() : user.runAs().principal();
|
||||
}
|
||||
|
||||
new SecurityClient(client).prepareChangePassword(username, request.content())
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.elasticsearch.rest.RestRequest;
|
|||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.xpack.security.action.user.DeleteUserRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.user.DeleteUserResponse;
|
||||
import org.elasticsearch.xpack.security.client.SecurityClient;
|
||||
|
||||
|
@ -42,20 +41,16 @@ public class RestDeleteUserAction extends BaseRestHandler {
|
|||
|
||||
@Override
|
||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||
String username = request.param("username");
|
||||
|
||||
DeleteUserRequestBuilder requestBuilder = new SecurityClient(client).prepareDeleteUser(username);
|
||||
if (request.hasParam("refresh")) {
|
||||
requestBuilder.refresh(request.paramAsBoolean("refresh", true));
|
||||
}
|
||||
requestBuilder.execute(new RestBuilderListener<DeleteUserResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(DeleteUserResponse response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||
builder.startObject()
|
||||
.field("found", response.found())
|
||||
.endObject());
|
||||
}
|
||||
});
|
||||
new SecurityClient(client).prepareDeleteUser(request.param("username"))
|
||||
.setRefreshPolicy(request.param("refresh"))
|
||||
.execute(new RestBuilderListener<DeleteUserResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(DeleteUserResponse response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||
builder.startObject()
|
||||
.field("found", response.found())
|
||||
.endObject());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,7 @@ public class RestPutUserAction extends BaseRestHandler {
|
|||
@Override
|
||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||
PutUserRequestBuilder requestBuilder = new SecurityClient(client).preparePutUser(request.param("username"), request.content());
|
||||
if (request.hasParam("refresh")) {
|
||||
requestBuilder.setRefreshPolicy(request.param("refresh"));
|
||||
}
|
||||
requestBuilder.setRefreshPolicy(request.param("refresh"));
|
||||
requestBuilder.execute(new RestBuilderListener<PutUserResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(PutUserResponse putUserResponse, XContentBuilder builder) throws Exception {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.user;
|
||||
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestChannel;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.xpack.security.action.user.SetEnabledResponse;
|
||||
import org.elasticsearch.xpack.security.client.SecurityClient;
|
||||
|
||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.PUT;
|
||||
|
||||
/**
|
||||
* REST handler for enabling and disabling users. The username is required and we use the path to determine if the user is being
|
||||
* enabled or disabled.
|
||||
*/
|
||||
public class RestSetEnabledAction extends BaseRestHandler {
|
||||
|
||||
@Inject
|
||||
public RestSetEnabledAction(Settings settings, RestController controller) {
|
||||
super(settings);
|
||||
controller.registerHandler(POST, "/_xpack/security/user/{username}/_enable", this);
|
||||
controller.registerHandler(PUT, "/_xpack/security/user/{username}/_enable", this);
|
||||
controller.registerHandler(POST, "/_xpack/security/user/{username}/_disable", this);
|
||||
controller.registerHandler(PUT, "/_xpack/security/user/{username}/_disable", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
|
||||
final boolean enabled = request.path().endsWith("_enable");
|
||||
assert enabled || request.path().endsWith("_disable");
|
||||
new SecurityClient(client).prepareSetEnabled(request.param("username"), enabled)
|
||||
.execute(new RestBuilderListener<SetEnabledResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(SetEnabledResponse setEnabledResponse, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(RestStatus.OK, channel.newBuilder().startObject().endObject());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ public class MetadataUtils {
|
|||
public static void verifyNoReservedMetadata(Map<String, Object> metadata) {
|
||||
for (String key : metadata.keySet()) {
|
||||
if (key.startsWith(RESERVED_PREFIX)) {
|
||||
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses");
|
||||
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal use");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.support;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
||||
|
||||
|
@ -21,14 +22,21 @@ public final class Validation {
|
|||
|
||||
private static final int MIN_PASSWD_LENGTH = 6;
|
||||
|
||||
public static Error validateUsername(String username) {
|
||||
/**
|
||||
* Validate the username
|
||||
* @param username the username to validate
|
||||
* @param allowReserved whether or not to allow reserved user names
|
||||
* @param settings the settings which may contain information about reserved users
|
||||
* @return {@code null} if valid
|
||||
*/
|
||||
public static Error validateUsername(String username, boolean allowReserved, Settings settings) {
|
||||
if (COMMON_NAME_PATTERN.matcher(username).matches() == false) {
|
||||
return new Error("A valid username must be at least 1 character and no longer than 30 characters. " +
|
||||
"It must begin with a letter (`a-z` or `A-Z`) or an underscore (`_`). Subsequent " +
|
||||
"characters can be letters, underscores (`_`), digits (`0-9`) or any of the following " +
|
||||
"symbols `@`, `-`, `.` or `$`");
|
||||
}
|
||||
if (ReservedRealm.isReserved(username)) {
|
||||
if (allowReserved == false && ReservedRealm.isReserved(username, settings)) {
|
||||
return new Error("Username [" + username + "] is reserved and may not be used.");
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -9,22 +9,17 @@ import org.elasticsearch.common.Strings;
|
|||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.xpack.security.user.User.ReservedUser;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.xpack.security.Security.setting;
|
||||
|
||||
/**
|
||||
* The user object for the anonymous user. This class needs to be instantiated with the <code>initialize</code> method since the values
|
||||
* of the user depends on the settings. However, this is still a singleton instance. Ideally we would assert that an instance of this class
|
||||
* is only initialized once, but with the way our tests work the same class will be initialized multiple times (one for each node in a
|
||||
* integration test).
|
||||
* The user object for the anonymous user.
|
||||
*/
|
||||
public class AnonymousUser extends ReservedUser {
|
||||
public class AnonymousUser extends User {
|
||||
|
||||
public static final String DEFAULT_ANONYMOUS_USERNAME = "_anonymous";
|
||||
public static final Setting<String> USERNAME_SETTING =
|
||||
|
@ -32,57 +27,18 @@ public class AnonymousUser extends ReservedUser {
|
|||
public static final Setting<List<String>> ROLES_SETTING =
|
||||
Setting.listSetting(setting("authc.anonymous.roles"), Collections.emptyList(), s -> s, Property.NodeScope);
|
||||
|
||||
private static String username = DEFAULT_ANONYMOUS_USERNAME;
|
||||
private static String[] roles = null;
|
||||
|
||||
public static final AnonymousUser INSTANCE = new AnonymousUser();
|
||||
|
||||
private AnonymousUser() {
|
||||
super(DEFAULT_ANONYMOUS_USERNAME);
|
||||
public AnonymousUser(Settings settings) {
|
||||
super(USERNAME_SETTING.get(settings), ROLES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY), null, null,
|
||||
MetadataUtils.DEFAULT_RESERVED_METADATA, isAnonymousEnabled(settings));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String principal() {
|
||||
return username;
|
||||
public static boolean isAnonymousEnabled(Settings settings) {
|
||||
return ROLES_SETTING.exists(settings) && ROLES_SETTING.get(settings).isEmpty() == false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] roles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public static boolean enabled() {
|
||||
return roles != null;
|
||||
}
|
||||
|
||||
public static boolean is(User user) {
|
||||
return INSTANCE == user;
|
||||
}
|
||||
|
||||
public static boolean isAnonymousUsername(String username) {
|
||||
return AnonymousUser.username.equals(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used to initialize the AnonymousUser instance with the correct username and password
|
||||
* @param settings the settings to initialize the anonymous user with
|
||||
*/
|
||||
public static synchronized void initialize(Settings settings) {
|
||||
username = USERNAME_SETTING.get(settings);
|
||||
List<String> rolesList = ROLES_SETTING.get(settings);
|
||||
if (rolesList.isEmpty()) {
|
||||
roles = null;
|
||||
} else {
|
||||
roles = rolesList.toArray(Strings.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public static List<Setting<?>> getSettings() {
|
||||
return Arrays.asList();
|
||||
public static boolean isAnonymousUsername(String username, Settings settings) {
|
||||
// this is possibly the same check but we should not let anything use the default name either
|
||||
return USERNAME_SETTING.get(settings).equals(username) || DEFAULT_ANONYMOUS_USERNAME.equals(username);
|
||||
}
|
||||
|
||||
public static void addSettings(List<Setting<?>> settingsList) {
|
||||
|
|
|
@ -6,37 +6,18 @@
|
|||
package org.elasticsearch.xpack.security.user;
|
||||
|
||||
import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
||||
import org.elasticsearch.xpack.security.user.User.ReservedUser;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
* The reserved {@code elastic} superuser. As full permission/access to the cluster/indices and can
|
||||
* The reserved {@code elastic} superuser. Has full permission/access to the cluster/indices and can
|
||||
* run as any other user.
|
||||
*/
|
||||
public class ElasticUser extends ReservedUser {
|
||||
public class ElasticUser extends User {
|
||||
|
||||
public static final String NAME = "elastic";
|
||||
public static final String ROLE_NAME = SuperuserRole.NAME;
|
||||
public static final ElasticUser INSTANCE = new ElasticUser();
|
||||
|
||||
private ElasticUser() {
|
||||
super(NAME, ROLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return INSTANCE == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
public static boolean is(User user) {
|
||||
return INSTANCE.equals(user);
|
||||
}
|
||||
|
||||
public static boolean is(String principal) {
|
||||
return NAME.equals(principal);
|
||||
public ElasticUser(boolean enabled) {
|
||||
super(NAME, new String[] { ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,32 +6,17 @@
|
|||
package org.elasticsearch.xpack.security.user;
|
||||
|
||||
import org.elasticsearch.xpack.security.authz.permission.KibanaRole;
|
||||
import org.elasticsearch.xpack.security.user.User.ReservedUser;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* Built in user for the kibana server
|
||||
*/
|
||||
public class KibanaUser extends ReservedUser {
|
||||
public class KibanaUser extends User {
|
||||
|
||||
public static final String NAME = "kibana";
|
||||
public static final String ROLE_NAME = KibanaRole.NAME;
|
||||
public static final KibanaUser INSTANCE = new KibanaUser();
|
||||
|
||||
KibanaUser() {
|
||||
super(NAME, ROLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return INSTANCE == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
public static boolean is(User user) {
|
||||
return INSTANCE.equals(user);
|
||||
public KibanaUser(boolean enabled) {
|
||||
super(NAME, new String[]{ ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
|
||||
|
@ -31,40 +30,41 @@ public class User implements ToXContent {
|
|||
private final String[] roles;
|
||||
private final User runAs;
|
||||
private final Map<String, Object> metadata;
|
||||
private final boolean enabled;
|
||||
|
||||
@Nullable private final String fullName;
|
||||
@Nullable private final String email;
|
||||
|
||||
public User(String username, String... roles) {
|
||||
this(username, roles, null, null, null);
|
||||
this(username, roles, null, null, null, true);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, User runAs) {
|
||||
this(username, roles, null, null, null, runAs);
|
||||
this(username, roles, null, null, null, true, runAs);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata) {
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
this.enabled = enabled;
|
||||
this.runAs = null;
|
||||
verifyNoReservedMetadata(this.metadata);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, User runAs) {
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled, User runAs) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
this.enabled = enabled;
|
||||
assert (runAs == null || runAs.runAs() == null) : "the run_as user should not be a user that can run as";
|
||||
if (runAs == SystemUser.INSTANCE) {
|
||||
throw new ElasticsearchSecurityException("invalid run_as user");
|
||||
}
|
||||
this.runAs = runAs;
|
||||
verifyNoReservedMetadata(this.metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,6 +105,13 @@ public class User implements ToXContent {
|
|||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the user is enabled or not
|
||||
*/
|
||||
public boolean enabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The user that will be used for run as functionality. If run as
|
||||
* functionality is not being used, then <code>null</code> will be
|
||||
|
@ -133,7 +140,7 @@ public class User implements ToXContent {
|
|||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (o instanceof User == false) return false;
|
||||
|
||||
User user = (User) o;
|
||||
|
||||
|
@ -166,46 +173,28 @@ public class User implements ToXContent {
|
|||
builder.field(Fields.FULL_NAME.getPreferredName(), fullName());
|
||||
builder.field(Fields.EMAIL.getPreferredName(), email());
|
||||
builder.field(Fields.METADATA.getPreferredName(), metadata());
|
||||
builder.field(Fields.ENABLED.getPreferredName(), enabled());
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
void verifyNoReservedMetadata(Map<String, Object> metadata) {
|
||||
if (this instanceof ReservedUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
MetadataUtils.verifyNoReservedMetadata(metadata);
|
||||
}
|
||||
|
||||
public static User readFrom(StreamInput input) throws IOException {
|
||||
if (input.readBoolean()) {
|
||||
String name = input.readString();
|
||||
if (SystemUser.is(name)) {
|
||||
final boolean isInternalUser = input.readBoolean();
|
||||
final String username = input.readString();
|
||||
if (isInternalUser) {
|
||||
if (SystemUser.is(username)) {
|
||||
return SystemUser.INSTANCE;
|
||||
} else if (XPackUser.is(name)) {
|
||||
} else if (XPackUser.is(username)) {
|
||||
return XPackUser.INSTANCE;
|
||||
}
|
||||
User user = ReservedRealm.getUser(name);
|
||||
if (user == null) {
|
||||
throw new IllegalStateException("invalid reserved user");
|
||||
}
|
||||
return user;
|
||||
throw new IllegalStateException("user [" + username + "] is not an internal user");
|
||||
}
|
||||
String username = input.readString();
|
||||
String[] roles = input.readStringArray();
|
||||
Map<String, Object> metadata = input.readMap();
|
||||
String fullName = input.readOptionalString();
|
||||
String email = input.readOptionalString();
|
||||
if (input.readBoolean()) {
|
||||
String runAsUsername = input.readString();
|
||||
String[] runAsRoles = input.readStringArray();
|
||||
Map<String, Object> runAsMetadata = input.readMap();
|
||||
String runAsFullName = input.readOptionalString();
|
||||
String runAsEmail = input.readOptionalString();
|
||||
User runAs = new User(runAsUsername, runAsRoles, runAsFullName, runAsEmail, runAsMetadata);
|
||||
return new User(username, roles, fullName, email, metadata, runAs);
|
||||
}
|
||||
return new User(username, roles, fullName, email, metadata);
|
||||
boolean enabled = input.readBoolean();
|
||||
User runAs = input.readBoolean() ? readFrom(input) : null;
|
||||
return new User(username, roles, fullName, email, metadata, enabled, runAs);
|
||||
}
|
||||
|
||||
public static void writeTo(User user, StreamOutput output) throws IOException {
|
||||
|
@ -215,9 +204,6 @@ public class User implements ToXContent {
|
|||
} else if (XPackUser.is(user)) {
|
||||
output.writeBoolean(true);
|
||||
output.writeString(XPackUser.NAME);
|
||||
} else if (ReservedRealm.isReserved(user.principal())) {
|
||||
output.writeBoolean(true);
|
||||
output.writeString(user.principal());
|
||||
} else {
|
||||
output.writeBoolean(false);
|
||||
output.writeString(user.username);
|
||||
|
@ -225,26 +211,16 @@ public class User implements ToXContent {
|
|||
output.writeMap(user.metadata);
|
||||
output.writeOptionalString(user.fullName);
|
||||
output.writeOptionalString(user.email);
|
||||
output.writeBoolean(user.enabled);
|
||||
if (user.runAs == null) {
|
||||
output.writeBoolean(false);
|
||||
} else {
|
||||
output.writeBoolean(true);
|
||||
output.writeString(user.runAs.username);
|
||||
output.writeStringArray(user.runAs.roles);
|
||||
output.writeMap(user.runAs.metadata);
|
||||
output.writeOptionalString(user.runAs.fullName);
|
||||
output.writeOptionalString(user.runAs.email);
|
||||
writeTo(user.runAs, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class ReservedUser extends User {
|
||||
|
||||
ReservedUser(String username, String... roles) {
|
||||
super(username, roles, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Fields {
|
||||
ParseField USERNAME = new ParseField("username");
|
||||
ParseField PASSWORD = new ParseField("password");
|
||||
|
@ -253,5 +229,6 @@ public class User implements ToXContent {
|
|||
ParseField FULL_NAME = new ParseField("full_name");
|
||||
ParseField EMAIL = new ParseField("email");
|
||||
ParseField METADATA = new ParseField("metadata");
|
||||
ParseField ENABLED = new ParseField("enabled");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package org.elasticsearch.xpack.security.user;
|
||||
|
||||
import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
||||
import org.elasticsearch.xpack.security.user.User.ReservedUser;
|
||||
|
||||
/**
|
||||
* XPack internal user that manages xpack. Has all cluster/indices permissions for x-pack to operate.
|
||||
|
@ -17,7 +16,7 @@ public class XPackUser extends User {
|
|||
public static final String ROLE_NAME = SuperuserRole.NAME;
|
||||
public static final XPackUser INSTANCE = new XPackUser();
|
||||
|
||||
XPackUser() {
|
||||
private XPackUser() {
|
||||
super(NAME, ROLE_NAME);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,9 @@
|
|||
"metadata" : {
|
||||
"type" : "object",
|
||||
"dynamic" : true
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -109,6 +112,9 @@
|
|||
"type" : "keyword",
|
||||
"index" : false,
|
||||
"doc_values" : false
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.elasticsearch.xpack.security.crypto.CryptoService;
|
|||
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
|
@ -56,11 +55,6 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
|||
cryptoService = mock(CryptoService.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testAvailable() throws Exception {
|
||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
||||
ipFilter, auditTrail, cryptoService);
|
||||
|
@ -150,7 +144,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
|||
|
||||
final boolean anonymousEnabled = randomBoolean();
|
||||
if (anonymousEnabled) {
|
||||
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "foo").build());
|
||||
settings.put(AnonymousUser.ROLES_SETTING.getKey(), "foo");
|
||||
}
|
||||
|
||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, rolesStore,
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
|||
import org.elasticsearch.xpack.security.authz.permission.KibanaRole;
|
||||
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
|
||||
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
||||
import org.elasticsearch.xpack.security.user.ElasticUser;
|
||||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
@ -56,7 +57,9 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
|||
|
||||
final boolean isKibanaUser = randomBoolean();
|
||||
if (isKibanaUser) {
|
||||
when(context.getUser()).thenReturn(KibanaUser.INSTANCE);
|
||||
when(context.getUser()).thenReturn(new KibanaUser(true));
|
||||
} else {
|
||||
when(context.getUser()).thenReturn(new ElasticUser(true));
|
||||
}
|
||||
final int size = randomIntBetween(1, ReservedRolesStore.names().size());
|
||||
final List<String> names = randomSubsetOf(size, ReservedRolesStore.names());
|
||||
|
@ -116,7 +119,9 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
|||
|
||||
final boolean isKibanaUser = randomBoolean();
|
||||
if (isKibanaUser) {
|
||||
when(context.getUser()).thenReturn(KibanaUser.INSTANCE);
|
||||
when(context.getUser()).thenReturn(new KibanaUser(true));
|
||||
} else {
|
||||
when(context.getUser()).thenReturn(new ElasticUser(true));
|
||||
}
|
||||
|
||||
GetRolesRequest request = new GetRolesRequest();
|
||||
|
@ -199,9 +204,10 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
if (isKibanaUser) {
|
||||
when(context.getUser()).thenReturn(KibanaUser.INSTANCE);
|
||||
when(context.getUser()).thenReturn(new KibanaUser(true));
|
||||
} else {
|
||||
expectedNames.remove(KibanaRole.NAME);
|
||||
when(context.getUser()).thenReturn(new ElasticUser(true));
|
||||
}
|
||||
|
||||
GetRolesRequest request = new GetRolesRequest();
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
@ -31,9 +32,9 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
public class TransportAuthenticateActionTests extends ESTestCase {
|
||||
|
||||
public void testSystemUser() {
|
||||
public void testInternalUser() {
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
when(securityContext.getUser()).thenReturn(SystemUser.INSTANCE);
|
||||
when(securityContext.getUser()).thenReturn(randomFrom(SystemUser.INSTANCE, XPackUser.INSTANCE));
|
||||
TransportAuthenticateAction action = new TransportAuthenticateAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class),
|
||||
securityContext);
|
||||
|
@ -83,7 +84,7 @@ public class TransportAuthenticateActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testValidUser() {
|
||||
final User user = randomFrom(ElasticUser.INSTANCE, KibanaUser.INSTANCE, new User("joe"));
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
when(securityContext.getUser()).thenReturn(user);
|
||||
TransportAuthenticateAction action = new TransportAuthenticateAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.junit.After;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
|
@ -43,20 +43,15 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
|||
|
||||
public class TransportChangePasswordActionTests extends ESTestCase {
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testAnonymousUser() {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
TransportChangePasswordAction action = new TransportChangePasswordAction(settings, mock(ThreadPool.class),
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.username(AnonymousUser.INSTANCE.principal());
|
||||
request.username(anonymousUser.principal());
|
||||
request.passwordHash(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray())));
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
|
@ -79,13 +74,13 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
|||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testSystemUser() {
|
||||
public void testInternalUsers() {
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.username(SystemUser.INSTANCE.principal());
|
||||
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
|
||||
request.passwordHash(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray())));
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
|
@ -109,7 +104,7 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testValidUser() {
|
||||
final User user = randomFrom(ElasticUser.INSTANCE, KibanaUser.INSTANCE, new User("joe"));
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.username(user.principal());
|
||||
|
@ -147,7 +142,7 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testException() {
|
||||
final User user = randomFrom(ElasticUser.INSTANCE, KibanaUser.INSTANCE, new User("joe"));
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.username(user.principal());
|
||||
|
|
|
@ -11,14 +11,15 @@ import org.elasticsearch.action.support.ActionFilters;
|
|||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.ElasticUser;
|
||||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.junit.After;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
|
@ -40,19 +41,13 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
|||
|
||||
public class TransportDeleteUserActionTests extends ESTestCase {
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testAnonymousUser() {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
TransportDeleteUserAction action = new TransportDeleteUserAction(settings, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
||||
DeleteUserRequest request = new DeleteUserRequest(AnonymousUser.INSTANCE.principal());
|
||||
DeleteUserRequest request = new DeleteUserRequest(new AnonymousUser(settings).principal());
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -74,12 +69,12 @@ public class TransportDeleteUserActionTests extends ESTestCase {
|
|||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testSystemUser() {
|
||||
public void testInternalUser() {
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
||||
DeleteUserRequest request = new DeleteUserRequest(SystemUser.INSTANCE.principal());
|
||||
DeleteUserRequest request = new DeleteUserRequest(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -102,7 +97,7 @@ public class TransportDeleteUserActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testReservedUser() {
|
||||
final User reserved = randomFrom(ReservedRealm.users().toArray(new User[0]));
|
||||
final User reserved = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.ValidationException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
|
@ -20,13 +21,14 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.junit.After;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.junit.Before;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -48,32 +50,34 @@ import static org.mockito.Mockito.mock;
|
|||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TransportGetUsersActionTests extends ESTestCase {
|
||||
|
||||
private boolean anonymousEnabled;
|
||||
private Settings settings;
|
||||
|
||||
@Before
|
||||
public void maybeEnableAnonymous() {
|
||||
anonymousEnabled = randomBoolean();
|
||||
if (anonymousEnabled) {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
} else {
|
||||
settings = Settings.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testAnonymousUser() {
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
when(usersStore.started()).thenReturn(true);
|
||||
AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser);
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class),
|
||||
reservedRealm);
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
request.usernames(AnonymousUser.INSTANCE.principal());
|
||||
request.usernames(anonymousUser.principal());
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -93,20 +97,21 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
assertThat(responseRef.get(), is(notNullValue()));
|
||||
final User[] users = responseRef.get().users();
|
||||
if (anonymousEnabled) {
|
||||
assertThat("expected array with anonymous but got: " + Arrays.toString(users), users, arrayContaining(AnonymousUser.INSTANCE));
|
||||
assertThat("expected array with anonymous but got: " + Arrays.toString(users), users, arrayContaining(anonymousUser));
|
||||
} else {
|
||||
assertThat("expected an empty array but got: " + Arrays.toString(users), users, emptyArray());
|
||||
}
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testSystemUser() {
|
||||
public void testInternalUser() {
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore,
|
||||
mock(TransportService.class), mock(ReservedRealm.class));
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
request.usernames(SystemUser.INSTANCE.principal());
|
||||
request.usernames(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -129,12 +134,16 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testReservedUsersOnly() {
|
||||
final int size = randomIntBetween(1, ReservedRealm.users().size());
|
||||
final List<User> reservedUsers = randomSubsetOf(size, ReservedRealm.users());
|
||||
final List<String> names = reservedUsers.stream().map(User::principal).collect(Collectors.toList());
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
when(usersStore.started()).thenReturn(true);
|
||||
ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings));
|
||||
final Collection<User> allReservedUsers = reservedRealm.users();
|
||||
final int size = randomIntBetween(1, allReservedUsers.size());
|
||||
final List<User> reservedUsers = randomSubsetOf(size, allReservedUsers);
|
||||
final List<String> names = reservedUsers.stream().map(User::principal).collect(Collectors.toList());
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class),
|
||||
reservedRealm);
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
request.usernames(names.toArray(new String[names.size()]));
|
||||
|
@ -156,15 +165,18 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
assertThat(throwableRef.get(), is(nullValue()));
|
||||
assertThat(responseRef.get(), is(notNullValue()));
|
||||
assertThat(responseRef.get().users(), arrayContaining(reservedUsers.toArray(new User[reservedUsers.size()])));
|
||||
verifyZeroInteractions(usersStore);
|
||||
verify(usersStore, times(1 + names.size())).started();
|
||||
}
|
||||
|
||||
public void testGetAllUsers() {
|
||||
final List<User> storeUsers = randomFrom(Collections.<User>emptyList(), Collections.singletonList(new User("joe")),
|
||||
Arrays.asList(new User("jane"), new User("fred")), randomUsers());
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
when(usersStore.started()).thenReturn(true);
|
||||
ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings));
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class),
|
||||
reservedRealm);
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
doAnswer(new Answer() {
|
||||
|
@ -192,7 +204,7 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
});
|
||||
|
||||
final List<User> expectedList = new ArrayList<>();
|
||||
expectedList.addAll(ReservedRealm.users());
|
||||
expectedList.addAll(reservedRealm.users());
|
||||
expectedList.addAll(storeUsers);
|
||||
|
||||
assertThat(throwableRef.get(), is(nullValue()));
|
||||
|
@ -207,7 +219,8 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
final String[] storeUsernames = storeUsers.stream().map(User::principal).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class),
|
||||
mock(ReservedRealm.class));
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
request.usernames(storeUsernames);
|
||||
|
@ -268,7 +281,8 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
|||
final String[] storeUsernames = storeUsers.stream().map(User::principal).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class),
|
||||
mock(ReservedRealm.class));
|
||||
|
||||
GetUsersRequest request = new GetUsersRequest();
|
||||
request.usernames(storeUsernames);
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.action.support.ActionFilters;
|
|||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.ValidationException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
||||
|
@ -21,7 +22,7 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.junit.After;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
|
@ -40,23 +41,19 @@ import static org.mockito.Mockito.mock;
|
|||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TransportPutUserActionTests extends ESTestCase {
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testAnonymousUser() {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportPutUserAction action = new TransportPutUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
TransportPutUserAction action = new TransportPutUserAction(settings, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
||||
PutUserRequest request = new PutUserRequest();
|
||||
request.username(AnonymousUser.INSTANCE.principal());
|
||||
request.username(anonymousUser.principal());
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<PutUserResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -84,7 +81,7 @@ public class TransportPutUserActionTests extends ESTestCase {
|
|||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
||||
PutUserRequest request = new PutUserRequest();
|
||||
request.username(SystemUser.INSTANCE.principal());
|
||||
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<PutUserResponse> responseRef = new AtomicReference<>();
|
||||
|
@ -107,8 +104,11 @@ public class TransportPutUserActionTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testReservedUser() {
|
||||
final User reserved = randomFrom(ReservedRealm.users().toArray(new User[0]));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
when(usersStore.started()).thenReturn(true);
|
||||
Settings settings = Settings.builder().put("path.home", createTempDir()).build();
|
||||
ReservedRealm reservedRealm = new ReservedRealm(new Environment(settings), settings, usersStore, new AnonymousUser(settings));
|
||||
final User reserved = randomFrom(reservedRealm.users().toArray(new User[0]));
|
||||
TransportPutUserAction action = new TransportPutUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||
|
||||
|
@ -132,7 +132,7 @@ public class TransportPutUserActionTests extends ESTestCase {
|
|||
assertThat(responseRef.get(), is(nullValue()));
|
||||
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
||||
assertThat(throwableRef.get().getMessage(), containsString("is reserved and only the password"));
|
||||
verifyZeroInteractions(usersStore);
|
||||
verify(usersStore).started();
|
||||
}
|
||||
|
||||
public void testValidUser() {
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* 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.action.user;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.ElasticUser;
|
||||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
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.Matchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link TransportSetEnabledAction}
|
||||
*/
|
||||
public class TransportSetEnabledActionTests extends ESTestCase {
|
||||
|
||||
public void testAnonymousUser() {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(user);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportSetEnabledAction action = new TransportSetEnabledAction(settings, threadPool,
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
SetEnabledRequest request = new SetEnabledRequest();
|
||||
request.username(new AnonymousUser(settings).principal());
|
||||
request.enabled(randomBoolean());
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<SetEnabledResponse> responseRef = new AtomicReference<>();
|
||||
action.doExecute(request, new ActionListener<SetEnabledResponse>() {
|
||||
@Override
|
||||
public void onResponse(SetEnabledResponse setEnabledResponse) {
|
||||
responseRef.set(setEnabledResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
throwableRef.set(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(responseRef.get(), is(nullValue()));
|
||||
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
||||
assertThat(throwableRef.get().getMessage(), containsString("is anonymous"));
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testInternalUser() {
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(user);
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool,
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
SetEnabledRequest request = new SetEnabledRequest();
|
||||
request.username(randomFrom(SystemUser.INSTANCE.principal(), XPackUser.INSTANCE.principal()));
|
||||
request.enabled(randomBoolean());
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<SetEnabledResponse> responseRef = new AtomicReference<>();
|
||||
action.doExecute(request, new ActionListener<SetEnabledResponse>() {
|
||||
@Override
|
||||
public void onResponse(SetEnabledResponse setEnabledResponse) {
|
||||
responseRef.set(setEnabledResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
throwableRef.set(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(responseRef.get(), is(nullValue()));
|
||||
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
||||
assertThat(throwableRef.get().getMessage(), containsString("is internal"));
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testValidUser() {
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(new User("the runner"));
|
||||
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
SetEnabledRequest request = new SetEnabledRequest();
|
||||
request.username(user.principal());
|
||||
request.enabled(randomBoolean());
|
||||
request.setRefreshPolicy(randomFrom(RefreshPolicy.values()));
|
||||
// mock the setEnabled call on the native users store so that it will invoke the action listener with a response
|
||||
doAnswer(new Answer() {
|
||||
public Void answer(InvocationOnMock invocation) {
|
||||
Object[] args = invocation.getArguments();
|
||||
assert args.length == 4;
|
||||
ActionListener<Void> listener = (ActionListener<Void>) args[3];
|
||||
listener.onResponse(null);
|
||||
return null;
|
||||
}
|
||||
}).when(usersStore)
|
||||
.setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class));
|
||||
TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool,
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<SetEnabledResponse> responseRef = new AtomicReference<>();
|
||||
action.doExecute(request, new ActionListener<SetEnabledResponse>() {
|
||||
@Override
|
||||
public void onResponse(SetEnabledResponse setEnabledResponse) {
|
||||
responseRef.set(setEnabledResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
throwableRef.set(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(responseRef.get(), is(notNullValue()));
|
||||
assertThat(responseRef.get(), instanceOf(SetEnabledResponse.class));
|
||||
assertThat(throwableRef.get(), is(nullValue()));
|
||||
verify(usersStore, times(1))
|
||||
.setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class));
|
||||
}
|
||||
|
||||
public void testException() {
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(new User("the runner"));
|
||||
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
SetEnabledRequest request = new SetEnabledRequest();
|
||||
request.username(user.principal());
|
||||
request.enabled(randomBoolean());
|
||||
request.setRefreshPolicy(randomFrom(RefreshPolicy.values()));
|
||||
final Exception e = randomFrom(new ElasticsearchSecurityException(""), new IllegalStateException(), new RuntimeException());
|
||||
// we're mocking the setEnabled call on the native users store so that it will invoke the action listener with an exception
|
||||
doAnswer(new Answer() {
|
||||
public Void answer(InvocationOnMock invocation) {
|
||||
Object[] args = invocation.getArguments();
|
||||
assert args.length == 4;
|
||||
ActionListener<Void> listener = (ActionListener<Void>) args[3];
|
||||
listener.onFailure(e);
|
||||
return null;
|
||||
}
|
||||
}).when(usersStore)
|
||||
.setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class));
|
||||
TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool,
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<SetEnabledResponse> responseRef = new AtomicReference<>();
|
||||
action.doExecute(request, new ActionListener<SetEnabledResponse>() {
|
||||
@Override
|
||||
public void onResponse(SetEnabledResponse setEnabledResponse) {
|
||||
responseRef.set(setEnabledResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
throwableRef.set(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(responseRef.get(), is(nullValue()));
|
||||
assertThat(throwableRef.get(), is(notNullValue()));
|
||||
assertThat(throwableRef.get(), sameInstance(e));
|
||||
verify(usersStore, times(1))
|
||||
.setEnabled(eq(user.principal()), eq(request.enabled()), eq(request.getRefreshPolicy()), any(ActionListener.class));
|
||||
}
|
||||
|
||||
public void testUserModifyingThemselves() {
|
||||
final User user = randomFrom(new ElasticUser(true), new KibanaUser(true), new User("joe"));
|
||||
ThreadPool threadPool = mock(ThreadPool.class);
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
Authentication authentication = mock(Authentication.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
|
||||
when(authentication.getRunAsUser()).thenReturn(user);
|
||||
|
||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||
SetEnabledRequest request = new SetEnabledRequest();
|
||||
request.username(user.principal());
|
||||
request.enabled(randomBoolean());
|
||||
request.setRefreshPolicy(randomFrom(RefreshPolicy.values()));
|
||||
TransportSetEnabledAction action = new TransportSetEnabledAction(Settings.EMPTY, threadPool,
|
||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||
|
||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||
final AtomicReference<SetEnabledResponse> responseRef = new AtomicReference<>();
|
||||
action.doExecute(request, new ActionListener<SetEnabledResponse>() {
|
||||
@Override
|
||||
public void onResponse(SetEnabledResponse setEnabledResponse) {
|
||||
responseRef.set(setEnabledResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
throwableRef.set(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(responseRef.get(), is(nullValue()));
|
||||
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
||||
assertThat(throwableRef.get().getMessage(), containsString("own account"));
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
}
|
|
@ -32,7 +32,6 @@ import org.elasticsearch.xpack.security.crypto.CryptoService;
|
|||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException;
|
||||
|
@ -56,21 +55,21 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
|
||||
/**
|
||||
*
|
||||
* Unit tests for the {@link AuthenticationService}
|
||||
*/
|
||||
public class AuthenticationServiceTests extends ESTestCase {
|
||||
|
||||
AuthenticationService service;
|
||||
TransportMessage message;
|
||||
RestRequest restRequest;
|
||||
Realms realms;
|
||||
Realm firstRealm;
|
||||
Realm secondRealm;
|
||||
AuditTrailService auditTrail;
|
||||
AuthenticationToken token;
|
||||
CryptoService cryptoService;
|
||||
ThreadPool threadPool;
|
||||
ThreadContext threadContext;
|
||||
private AuthenticationService service;
|
||||
private TransportMessage message;
|
||||
private RestRequest restRequest;
|
||||
private Realms realms;
|
||||
private Realm firstRealm;
|
||||
private Realm secondRealm;
|
||||
private AuditTrailService auditTrail;
|
||||
private AuthenticationToken token;
|
||||
private CryptoService cryptoService;
|
||||
private ThreadPool threadPool;
|
||||
private ThreadContext threadContext;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
|
@ -109,12 +108,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
when(cryptoService.sign(any(String.class))).thenReturn("_signed_auth");
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -268,6 +262,33 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
public void testAuthenticateTransportDisabledUser() throws Exception {
|
||||
User user = new User("username", new String[] { "r1", "r2" }, null, null, null, false);
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate("_action", message, fallback));
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
|
||||
public void testAuthenticateRestDisabledUser() throws Exception {
|
||||
User user = new User("username", new String[] { "r1", "r2" }, null, null, null, false);
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate(restRequest));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
|
||||
public void testAuthenticateTransportSuccess() throws Exception {
|
||||
User user = new User("username", "r1", "r2");
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
|
@ -308,7 +329,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
|
||||
threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
|
@ -317,12 +338,11 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
verifyZeroInteractions(firstRealm);
|
||||
reset(firstRealm);
|
||||
|
||||
|
||||
// checking authentication from the user header
|
||||
threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode());
|
||||
|
||||
|
@ -334,7 +354,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
|
@ -344,7 +364,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception {
|
||||
Settings settings = Settings.builder().put(AuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
|
||||
User user1 = new User("username", "r1", "r2");
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
|
@ -361,7 +381,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||
Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||
|
@ -381,7 +401,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), equalTo(user1));
|
||||
|
@ -442,15 +462,15 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
builder.put(AnonymousUser.USERNAME_SETTING.getKey(), username);
|
||||
}
|
||||
Settings settings = builder.build();
|
||||
AnonymousUser.initialize(settings);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
|
||||
threadPool);
|
||||
threadPool, anonymousUser);
|
||||
RestRequest request = new FakeRestRequest();
|
||||
|
||||
Authentication result = service.authenticate(request);
|
||||
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance((Object) AnonymousUser.INSTANCE));
|
||||
assertThat(result.getUser(), sameInstance((Object) anonymousUser));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
|
@ -458,14 +478,14 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
Settings settings = Settings.builder()
|
||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||
.build();
|
||||
AnonymousUser.initialize(settings);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
Authentication result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(AnonymousUser.INSTANCE));
|
||||
assertThat(result.getUser(), sameInstance(anonymousUser));
|
||||
assertThreadContextContainsAuthentication(result);
|
||||
}
|
||||
|
||||
|
@ -473,9 +493,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
Settings settings = Settings.builder()
|
||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||
.build();
|
||||
AnonymousUser.initialize(settings);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||
|
||||
InternalMessage message = new InternalMessage();
|
||||
|
||||
|
@ -688,6 +708,40 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testAuthenticateTransportDisabledRunAsUser() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as"))
|
||||
.thenReturn(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate("_action", message, fallback));
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
|
||||
public void testAuthenticateRestDisabledRunAsUser() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as"))
|
||||
.thenReturn(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> service.authenticate(restRequest));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
|
||||
private static class InternalMessage extends TransportMessage {
|
||||
}
|
||||
|
||||
|
|
|
@ -599,4 +599,27 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
|
|||
assertThat(usage.get("fls"), is(fls));
|
||||
assertThat(usage.get("dls"), is(dls));
|
||||
}
|
||||
|
||||
public void testSetEnabled() throws Exception {
|
||||
securityClient().preparePutUser("joe", "s3krit".toCharArray(), SecuritySettingsSource.DEFAULT_ROLE).get();
|
||||
final String token = basicAuthHeaderValue("joe", new SecuredString("s3krit".toCharArray()));
|
||||
ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token))
|
||||
.admin().cluster().prepareHealth().get();
|
||||
assertThat(response.isTimedOut(), is(false));
|
||||
|
||||
securityClient(client()).prepareSetEnabled("joe", false).get();
|
||||
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||
() -> client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get());
|
||||
assertThat(expected.status(), is(RestStatus.UNAUTHORIZED));
|
||||
|
||||
securityClient(client()).prepareSetEnabled("joe", true).get();
|
||||
|
||||
response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get();
|
||||
assertThat(response.isTimedOut(), is(false));
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
||||
() -> securityClient(client()).prepareSetEnabled("not_a_real_user", false).get());
|
||||
assertThat(e.getMessage(), containsString("only existing users can be disabled"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,4 +74,35 @@ public class ReservedRealmIntegTests extends NativeRealmIntegTestCase {
|
|||
.get();
|
||||
assertThat(healthResponse.getClusterName(), is(cluster().getClusterName()));
|
||||
}
|
||||
|
||||
public void testDisablingUser() throws Exception {
|
||||
// validate the user works
|
||||
ClusterHealthResponse response = client()
|
||||
.filterWithHeader(singletonMap("Authorization", basicAuthHeaderValue(ElasticUser.NAME, DEFAULT_PASSWORD)))
|
||||
.admin()
|
||||
.cluster()
|
||||
.prepareHealth()
|
||||
.get();
|
||||
assertThat(response.getClusterName(), is(cluster().getClusterName()));
|
||||
|
||||
// disable user
|
||||
securityClient().prepareSetEnabled(ElasticUser.NAME, false).get();
|
||||
ElasticsearchSecurityException elasticsearchSecurityException = expectThrows(ElasticsearchSecurityException.class, () -> client()
|
||||
.filterWithHeader(singletonMap("Authorization", basicAuthHeaderValue(ElasticUser.NAME, DEFAULT_PASSWORD)))
|
||||
.admin()
|
||||
.cluster()
|
||||
.prepareHealth()
|
||||
.get());
|
||||
assertThat(elasticsearchSecurityException.getMessage(), containsString("authenticate"));
|
||||
|
||||
//enable
|
||||
securityClient().prepareSetEnabled(ElasticUser.NAME, true).get();
|
||||
response = client()
|
||||
.filterWithHeader(singletonMap("Authorization", basicAuthHeaderValue(ElasticUser.NAME, DEFAULT_PASSWORD)))
|
||||
.admin()
|
||||
.cluster()
|
||||
.prepareHealth()
|
||||
.get();
|
||||
assertThat(response.getClusterName(), is(cluster().getClusterName()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ package org.elasticsearch.xpack.security.authc.esnative;
|
|||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ChangeListener;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
|
||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||
|
@ -23,8 +23,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
|
|||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -41,20 +39,19 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
|
||||
@Before
|
||||
public void setupMocks() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
usersStore = mock(NativeUsersStore.class);
|
||||
when(usersStore.started()).thenReturn(true);
|
||||
}
|
||||
|
||||
public void testUserStoreNotStarted() {
|
||||
when(usersStore.started()).thenReturn(false);
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final String principal = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
|
||||
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||
() -> reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD)));
|
||||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||
verify(usersStore).addListener(any(ChangeListener.class));
|
||||
verify(usersStore).started();
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
@ -64,28 +61,29 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
if (securityIndexExists) {
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
}
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
||||
final User expected = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expected.principal();
|
||||
|
||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
|
||||
assertThat(authenticated, sameInstance(expected));
|
||||
verify(usersStore).addListener(any(ChangeListener.class));
|
||||
assertEquals(expected, authenticated);
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
if (securityIndexExists) {
|
||||
verify(usersStore).reservedUserPassword(principal);
|
||||
verify(usersStore).getReservedUserInfo(principal);
|
||||
}
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testAuthenticationWithStoredPassword() throws Throwable {
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
||||
final User expectedUser = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
final SecuredString newPassword = new SecuredString("foobar".toCharArray());
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
when(usersStore.reservedUserPassword(principal)).thenReturn(Hasher.BCRYPT.hash(newPassword));
|
||||
when(usersStore.getReservedUserInfo(principal)).thenReturn(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
|
||||
// test default password
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||
|
@ -93,52 +91,75 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||
|
||||
// the realm assumes it owns the hashed password so it fills it with 0's
|
||||
when(usersStore.reservedUserPassword(principal)).thenReturn(Hasher.BCRYPT.hash(newPassword));
|
||||
when(usersStore.getReservedUserInfo(principal)).thenReturn(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
|
||||
// test new password
|
||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword));
|
||||
assertThat(authenticated, sameInstance(expectedUser));
|
||||
verify(usersStore).addListener(any(ChangeListener.class));
|
||||
assertEquals(expectedUser, authenticated);
|
||||
verify(usersStore, times(2)).started();
|
||||
verify(usersStore, times(2)).securityIndexExists();
|
||||
verify(usersStore, times(2)).reservedUserPassword(principal);
|
||||
verify(usersStore, times(2)).getReservedUserInfo(principal);
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testLookup() {
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
||||
final User expectedUser = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
||||
public void testLookup() throws Exception {
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
|
||||
final User user = reservedRealm.doLookupUser(principal);
|
||||
assertThat(user, sameInstance(expectedUser));
|
||||
verify(usersStore).addListener(any(ChangeListener.class));
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
assertEquals(expectedUser, user);
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
|
||||
final User doesntExist = reservedRealm.doLookupUser("foobar");
|
||||
assertThat(doesntExist, nullValue());
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testHelperMethods() {
|
||||
final User expectedUser = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
||||
public void testLookupThrows() throws Exception {
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
assertThat(ReservedRealm.isReserved(principal), is(true));
|
||||
assertThat(ReservedRealm.getUser(principal), sameInstance(expectedUser));
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
final RuntimeException e = new RuntimeException("store threw");
|
||||
when(usersStore.getReservedUserInfo(principal)).thenThrow(e);
|
||||
|
||||
ElasticsearchSecurityException securityException =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> reservedRealm.lookupUser(principal));
|
||||
assertThat(securityException.getMessage(), containsString("failed to lookup"));
|
||||
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
verify(usersStore).getReservedUserInfo(principal);
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testIsReserved() {
|
||||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
assertThat(ReservedRealm.isReserved(principal, Settings.EMPTY), is(true));
|
||||
|
||||
final String notExpected = randomFrom("foobar", "", randomAsciiOfLengthBetween(1, 30));
|
||||
assertThat(ReservedRealm.isReserved(notExpected), is(false));
|
||||
assertThat(ReservedRealm.getUser(notExpected), nullValue());
|
||||
assertThat(ReservedRealm.isReserved(notExpected, Settings.EMPTY), is(false));
|
||||
}
|
||||
|
||||
assertThat(ReservedRealm.users(), containsInAnyOrder((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE));
|
||||
public void testGetUsers() {
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
assertThat(reservedRealm.users(), containsInAnyOrder(new ElasticUser(true), new KibanaUser(true)));
|
||||
}
|
||||
|
||||
public void testFailedAuthentication() {
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
// maybe cache a successful auth
|
||||
if (randomBoolean()) {
|
||||
User user = reservedRealm.authenticate(
|
||||
new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("changeme".toCharArray())));
|
||||
assertThat(user, sameInstance(ElasticUser.INSTANCE));
|
||||
assertEquals(new ElasticUser(true), user);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.authc.file;
|
|||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
@ -154,7 +153,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
|
||||
public void testParseFile() throws Exception {
|
||||
Path path = getDataPath("users");
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(path, null);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(path, null, Settings.EMPTY);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.size(), is(6));
|
||||
assertThat(users.get("bcrypt"), notNullValue());
|
||||
|
@ -174,7 +173,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
public void testParseFile_Empty() throws Exception {
|
||||
Path empty = createTempFile();
|
||||
Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(empty, logger);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(empty, logger, Settings.EMPTY);
|
||||
assertThat(users.isEmpty(), is(true));
|
||||
List<String> events = CapturingLogger.output(logger.getName(), Level.DEBUG);
|
||||
assertThat(events.size(), is(1));
|
||||
|
@ -184,7 +183,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
public void testParseFile_WhenFileDoesNotExist() throws Exception {
|
||||
Path file = createTempDir().resolve(randomAsciiOfLength(10));
|
||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, logger);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, logger, Settings.EMPTY);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.isEmpty(), is(true));
|
||||
}
|
||||
|
@ -195,7 +194,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16);
|
||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
||||
try {
|
||||
FileUserPasswdStore.parseFile(file, logger);
|
||||
FileUserPasswdStore.parseFile(file, logger, Settings.EMPTY);
|
||||
fail("expected a parse failure");
|
||||
} catch (IllegalStateException se) {
|
||||
this.logger.info("expected", se);
|
||||
|
@ -205,7 +204,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
public void testParseFile_InvalidLineDoesNotResultInLoggerNPE() throws Exception {
|
||||
Path file = createTempFile();
|
||||
Files.write(file, Arrays.asList("NotValidUsername=Password", "user:pass"), StandardCharsets.UTF_8);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null, Settings.EMPTY);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.keySet(), hasSize(1));
|
||||
}
|
||||
|
@ -215,7 +214,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
// writing in utf_16 should cause a parsing error as we try to read the file in utf_8
|
||||
Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16);
|
||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFileLenient(file, logger);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFileLenient(file, logger, Settings.EMPTY);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.isEmpty(), is(true));
|
||||
List<String> events = CapturingLogger.output(logger.getName(), Level.ERROR);
|
||||
|
@ -226,7 +225,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
public void testParseFileWithLineWithEmptyPasswordAndWhitespace() throws Exception {
|
||||
Path file = createTempFile();
|
||||
Files.write(file, Collections.singletonList("user: "), StandardCharsets.UTF_8);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null, Settings.EMPTY);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.keySet(), is(empty()));
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ public class UsersToolTests extends CommandTestCase {
|
|||
|
||||
public void testParseInvalidUsername() throws Exception {
|
||||
UserException e = expectThrows(UserException.class, () -> {
|
||||
UsersTool.parseUsername(Collections.singletonList("$34dkl"));
|
||||
UsersTool.parseUsername(Collections.singletonList("$34dkl"), Settings.EMPTY);
|
||||
});
|
||||
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("Invalid username"));
|
||||
|
@ -183,7 +183,7 @@ public class UsersToolTests extends CommandTestCase {
|
|||
|
||||
public void testParseUsernameMissing() throws Exception {
|
||||
UserException e = expectThrows(UserException.class, () -> {
|
||||
UsersTool.parseUsername(Collections.emptyList());
|
||||
UsersTool.parseUsername(Collections.emptyList(), Settings.EMPTY);
|
||||
});
|
||||
assertEquals(ExitCodes.USAGE, e.exitCode);
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("Missing username argument"));
|
||||
|
@ -191,7 +191,7 @@ public class UsersToolTests extends CommandTestCase {
|
|||
|
||||
public void testParseUsernameExtraArgs() throws Exception {
|
||||
UserException e = expectThrows(UserException.class, () -> {
|
||||
UsersTool.parseUsername(Arrays.asList("username", "extra"));
|
||||
UsersTool.parseUsername(Arrays.asList("username", "extra"), Settings.EMPTY);
|
||||
});
|
||||
assertEquals(ExitCodes.USAGE, e.exitCode);
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("Expected a single username argument"));
|
||||
|
|
|
@ -73,7 +73,6 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
|||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -109,12 +108,7 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
|
||||
authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService,
|
||||
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||
}
|
||||
|
||||
public void testActionsSystemUserIsAuthorized() {
|
||||
|
@ -352,21 +346,22 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
public void testDenialForAnonymousUser() {
|
||||
TransportRequest request = new IndicesExistsRequest("b");
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build());
|
||||
authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build();
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||
|
||||
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
||||
try {
|
||||
authorizationService.authorize(createAuthentication(AnonymousUser.INSTANCE), "indices:a", request);
|
||||
authorizationService.authorize(createAuthentication(anonymousUser), "indices:a", request);
|
||||
fail("indices request for b should be denied since there is no such index");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e,
|
||||
containsString("action [indices:a] is unauthorized for user [" + AnonymousUser.INSTANCE.principal() + "]"));
|
||||
verify(auditTrail).accessDenied(AnonymousUser.INSTANCE, "indices:a", request);
|
||||
containsString("action [indices:a] is unauthorized for user [" + anonymousUser.principal() + "]"));
|
||||
verify(auditTrail).accessDenied(anonymousUser, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
verify(clusterService, times(2)).state();
|
||||
verify(state, times(3)).metaData();
|
||||
|
@ -376,14 +371,13 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
public void testDenialForAnonymousUserAuthorizationExceptionDisabled() {
|
||||
TransportRequest request = new IndicesExistsRequest("b");
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
AnonymousUser.initialize(Settings.builder()
|
||||
Settings settings = Settings.builder()
|
||||
.put(AnonymousUser.ROLES_SETTING.getKey(), "a_all")
|
||||
.put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false)
|
||||
.build());
|
||||
User anonymousUser = AnonymousUser.INSTANCE;
|
||||
authorizationService = new AuthorizationService(
|
||||
Settings.builder().put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(),
|
||||
rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
.build();
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings));
|
||||
|
||||
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
|
|
|
@ -440,7 +440,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase {
|
|||
|
||||
public void testTemplating() throws Exception {
|
||||
User user = new User("_username", new String[]{"role1", "role2"}, "_full_name", "_email",
|
||||
Collections.singletonMap("key", "value"));
|
||||
Collections.singletonMap("key", "value"), true);
|
||||
securityIndexSearcherWrapper =
|
||||
new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) {
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
|||
|
||||
public void testProcessor() throws Exception {
|
||||
User user = new User("_username", new String[]{"role1", "role2"}, "firstname lastname", "_email",
|
||||
Collections.singletonMap("key", "value"));
|
||||
Collections.singletonMap("key", "value"), true);
|
||||
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||
|
@ -100,7 +100,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
|||
|
||||
public void testFullNameProperties() throws Exception {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
User user = new User(null, null, "_full_name", null, null);
|
||||
User user = new User(null, null, "_full_name", null, null, true);
|
||||
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||
|
||||
|
@ -116,7 +116,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
|||
|
||||
public void testEmailProperties() throws Exception {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
User user = new User(null, null, null, "_email", null);
|
||||
User user = new User(null, null, null, "_email", null, true);
|
||||
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||
|
||||
|
@ -132,7 +132,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
|||
|
||||
public void testMetadataProperties() throws Exception {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
User user = new User(null, null, null, null, Collections.singletonMap("key", "value"));
|
||||
User user = new User(null, null, null, null, Collections.singletonMap("key", "value"), true);
|
||||
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ import org.elasticsearch.index.IndexNotFoundException;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
||||
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.elasticsearch.xpack.security.audit.AuditTrailService;
|
||||
import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler;
|
||||
import org.elasticsearch.xpack.security.authz.AuthorizationService;
|
||||
|
@ -39,9 +43,6 @@ import org.elasticsearch.xpack.security.authz.permission.Role;
|
|||
import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.Set;
|
||||
|
@ -102,7 +103,8 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
when(state.metaData()).thenReturn(metaData);
|
||||
|
||||
AuthorizationService authzService = new AuthorizationService(settings, rolesStore, clusterService,
|
||||
mock(AuditTrailService.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class));
|
||||
mock(AuditTrailService.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class),
|
||||
new AnonymousUser(settings));
|
||||
defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService, indexNameExpressionResolver);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
|||
|
||||
public void testRetrievingReservedRolesNonKibanaUser() {
|
||||
if (randomBoolean()) {
|
||||
when(securityContext.getUser()).thenReturn(ElasticUser.INSTANCE);
|
||||
when(securityContext.getUser()).thenReturn(new ElasticUser(true));
|
||||
}
|
||||
|
||||
assertThat(reservedRolesStore.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE));
|
||||
|
@ -77,7 +77,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testRetrievingReservedRoleKibanaUser() {
|
||||
when(securityContext.getUser()).thenReturn(KibanaUser.INSTANCE);
|
||||
when(securityContext.getUser()).thenReturn(new KibanaUser(true));
|
||||
assertThat(reservedRolesStore.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE));
|
||||
assertThat(reservedRolesStore.roleDescriptor(SuperuserRole.NAME), sameInstance(SuperuserRole.DESCRIPTOR));
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.support;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
||||
import org.elasticsearch.xpack.security.support.Validation.Error;
|
||||
import org.elasticsearch.xpack.security.support.Validation.Users;
|
||||
|
@ -55,12 +56,12 @@ public class ValidationTests extends ESTestCase {
|
|||
public void testUsersValidateUsername() throws Exception {
|
||||
int length = randomIntBetween(1, 30);
|
||||
String name = new String(generateValidName(length));
|
||||
assertThat(Users.validateUsername(name), nullValue());
|
||||
assertThat(Users.validateUsername(name, false, Settings.EMPTY), nullValue());
|
||||
}
|
||||
|
||||
public void testReservedUsernames() {
|
||||
final String username = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
|
||||
final Error error = Users.validateUsername(username);
|
||||
final Error error = Users.validateUsername(username, false, Settings.EMPTY);
|
||||
assertNotNull(error);
|
||||
assertThat(error.toString(), containsString("is reserved"));
|
||||
}
|
||||
|
@ -71,13 +72,13 @@ public class ValidationTests extends ESTestCase {
|
|||
if (length > 0) {
|
||||
name = generateValidName(length);
|
||||
}
|
||||
assertThat(Users.validateUsername(new String(name)), notNullValue());
|
||||
assertThat(Users.validateUsername(new String(name), false, Settings.EMPTY), notNullValue());
|
||||
}
|
||||
|
||||
public void testUsersValidateUsernameInvalidCharacters() throws Exception {
|
||||
int length = randomIntBetween(1, 30); // valid length
|
||||
String name = new String(generateInvalidName(length));
|
||||
assertThat(Users.validateUsername(name), notNullValue());
|
||||
assertThat(Users.validateUsername(name, false, Settings.EMPTY), notNullValue());
|
||||
}
|
||||
|
||||
public void testUsersValidatePassword() throws Exception {
|
||||
|
@ -112,13 +113,13 @@ public class ValidationTests extends ESTestCase {
|
|||
if (length > 0) {
|
||||
name = generateValidName(length);
|
||||
}
|
||||
assertThat(Users.validateUsername(new String(name)), notNullValue());
|
||||
assertThat(Users.validateUsername(new String(name), false, Settings.EMPTY), notNullValue());
|
||||
}
|
||||
|
||||
public void testRolesValidateRoleNameInvalidCharacters() throws Exception {
|
||||
int length = randomIntBetween(1, 30); // valid length
|
||||
String name = new String(generateInvalidName(length));
|
||||
assertThat(Users.validateUsername(name), notNullValue());
|
||||
assertThat(Users.validateUsername(name, false, Settings.EMPTY), notNullValue());
|
||||
}
|
||||
|
||||
private static char[] generateValidName(int length) {
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.elasticsearch.xpack.security.user;
|
|||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.After;
|
||||
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
@ -16,26 +15,19 @@ import static org.hamcrest.Matchers.is;
|
|||
|
||||
public class AnonymousUserTests extends ESTestCase {
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testResolveAnonymousUser() throws Exception {
|
||||
Settings settings = Settings.builder()
|
||||
.put(AnonymousUser.USERNAME_SETTING.getKey(), "anonym1")
|
||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||
.build();
|
||||
AnonymousUser.initialize(settings);
|
||||
User user = AnonymousUser.INSTANCE;
|
||||
AnonymousUser user = new AnonymousUser(settings);
|
||||
assertThat(user.principal(), equalTo("anonym1"));
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||
|
||||
settings = Settings.builder()
|
||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||
.build();
|
||||
AnonymousUser.initialize(settings);
|
||||
user = AnonymousUser.INSTANCE;
|
||||
user = new AnonymousUser(settings);
|
||||
assertThat(user.principal(), equalTo(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME));
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||
}
|
||||
|
@ -44,8 +36,7 @@ public class AnonymousUserTests extends ESTestCase {
|
|||
Settings settings = randomBoolean() ?
|
||||
Settings.EMPTY :
|
||||
Settings.builder().put(AnonymousUser.USERNAME_SETTING.getKey(), "user1").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
assertThat(AnonymousUser.enabled(), is(false));
|
||||
assertThat(AnonymousUser.isAnonymousEnabled(settings), is(false));
|
||||
}
|
||||
|
||||
public void testAnonymous() throws Exception {
|
||||
|
@ -54,24 +45,21 @@ public class AnonymousUserTests extends ESTestCase {
|
|||
settings = Settings.builder().put(settings).put(AnonymousUser.USERNAME_SETTING.getKey(), "anon").build();
|
||||
}
|
||||
|
||||
AnonymousUser.initialize(settings);
|
||||
User user = AnonymousUser.INSTANCE;
|
||||
assertThat(AnonymousUser.is(user), is(true));
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal()), is(true));
|
||||
AnonymousUser user = new AnonymousUser(settings);
|
||||
assertEquals(user, new AnonymousUser(settings));
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal(), settings), is(true));
|
||||
// make sure check works with serialization
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
User.writeTo(user, output);
|
||||
|
||||
User anonymousSerialized = User.readFrom(output.bytes().streamInput());
|
||||
assertThat(AnonymousUser.is(anonymousSerialized), is(true));
|
||||
assertEquals(user, anonymousSerialized);
|
||||
|
||||
// test with null anonymous
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
assertThat(AnonymousUser.is(null), is(false));
|
||||
// test with anonymous disabled
|
||||
if (user.principal().equals(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME)) {
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal()), is(true));
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal(), Settings.EMPTY), is(true));
|
||||
} else {
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal()), is(false));
|
||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal(), Settings.EMPTY), is(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,11 @@
|
|||
package org.elasticsearch.xpack.security.user;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -103,7 +100,7 @@ public class UserTests extends ESTestCase {
|
|||
public void testUserToString() throws Exception {
|
||||
User user = new User("u1", "r1");
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}]"));
|
||||
user = new User("u1", new String[] { "r1", "r2" }, "user1", "user1@domain.com", Collections.singletonMap("key", "val"));
|
||||
user = new User("u1", new String[] { "r1", "r2" }, "user1", "user1@domain.com", Collections.singletonMap("key", "val"), true);
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=user1,email=user1@domain.com,metadata={key=val}]"));
|
||||
user = new User("u1", new String[] {"r1", "r2"}, new User("u2", "r3"));
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=null,email=null,metadata={},runAs=[User[username=u2," +
|
||||
|
@ -112,27 +109,17 @@ public class UserTests extends ESTestCase {
|
|||
|
||||
public void testReservedUserSerialization() throws Exception {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
User.writeTo(ElasticUser.INSTANCE, output);
|
||||
final ElasticUser elasticUser = new ElasticUser(true);
|
||||
User.writeTo(elasticUser, output);
|
||||
User readFrom = User.readFrom(output.bytes().streamInput());
|
||||
|
||||
assertThat(readFrom, is(sameInstance(ElasticUser.INSTANCE)));
|
||||
assertEquals(elasticUser, readFrom);
|
||||
|
||||
final KibanaUser kibanaUser = new KibanaUser(true);
|
||||
output = new BytesStreamOutput();
|
||||
User.writeTo(KibanaUser.INSTANCE, output);
|
||||
User.writeTo(kibanaUser, output);
|
||||
readFrom = User.readFrom(output.bytes().streamInput());
|
||||
|
||||
assertThat(readFrom, is(sameInstance(KibanaUser.INSTANCE)));
|
||||
}
|
||||
|
||||
public void testReservedMetadata() throws Exception {
|
||||
Map<String, Object> validMetadata = Collections.singletonMap("foo", "bar");
|
||||
Map<String, Object> invalidMetadata = Collections.singletonMap(MetadataUtils.RESERVED_PREFIX + "foo", "bar");
|
||||
|
||||
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
|
||||
new User("john", Strings.EMPTY_ARRAY, "John Doe", "john@doe.com", invalidMetadata));
|
||||
assertThat(exception.getMessage(), containsString("reserved"));
|
||||
|
||||
User user = new User("john", Strings.EMPTY_ARRAY, "John Doe", "john@doe.com", validMetadata);
|
||||
assertNotNull(user);
|
||||
assertEquals(kibanaUser, readFrom);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@ cluster:admin/xpack/security/user/change_password
|
|||
cluster:admin/xpack/security/user/put
|
||||
cluster:admin/xpack/security/user/delete
|
||||
cluster:admin/xpack/security/user/get
|
||||
cluster:admin/xpack/security/user/set_enabled
|
||||
cluster:admin/xpack/security/role/put
|
||||
cluster:admin/xpack/security/role/delete
|
||||
cluster:admin/xpack/security/role/get
|
||||
|
|
|
@ -21,6 +21,7 @@ cluster:admin/xpack/security/user/change_password
|
|||
cluster:admin/xpack/security/user/put
|
||||
cluster:admin/xpack/security/user/delete
|
||||
cluster:admin/xpack/security/user/get
|
||||
cluster:admin/xpack/security/user/set_enabled
|
||||
indices:admin/analyze[s]
|
||||
indices:admin/cache/clear[n]
|
||||
indices:admin/forcemerge[n]
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "boolean",
|
||||
"description" : "Refresh the index after performing the operation"
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "boolean",
|
||||
"description" : "Refresh the index after performing the operation"
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "boolean",
|
||||
"description" : "Refresh the index after performing the operation"
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"xpack.security.disable_user": {
|
||||
"documentation": "https://www.elastic.co/guide/en/x-pack/master/security-api-disable-user.html",
|
||||
"methods": [ "PUT", "POST" ],
|
||||
"url": {
|
||||
"path": "/_xpack/security/user/{username}/_disable",
|
||||
"paths": [ "/_xpack/security/user/{username}/_disable" ],
|
||||
"parts": {
|
||||
"username": {
|
||||
"type" : "string",
|
||||
"description" : "The username of the user to disable",
|
||||
"required" : false
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"xpack.security.enable_user": {
|
||||
"documentation": "https://www.elastic.co/guide/en/x-pack/master/security-api-enable-user.html",
|
||||
"methods": [ "PUT", "POST" ],
|
||||
"url": {
|
||||
"path": "/_xpack/security/user/{username}/_enable",
|
||||
"paths": [ "/_xpack/security/user/{username}/_enable" ],
|
||||
"parts": {
|
||||
"username": {
|
||||
"type" : "string",
|
||||
"description" : "The username of the user to enable",
|
||||
"required" : false
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": null
|
||||
}
|
||||
}
|
|
@ -14,8 +14,9 @@
|
|||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "boolean",
|
||||
"description" : "Refresh the index after performing the operation"
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
},
|
||||
"params": {
|
||||
"refresh": {
|
||||
"type" : "boolean",
|
||||
"description" : "Refresh the index after performing the operation"
|
||||
"type" : "enum",
|
||||
"options": ["true", "false", "wait_for"],
|
||||
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
setup:
|
||||
- skip:
|
||||
features: [headers, catch_unauthorized]
|
||||
- do:
|
||||
cluster.health:
|
||||
wait_for_status: yellow
|
||||
|
||||
- do:
|
||||
xpack.security.put_user:
|
||||
username: "joe"
|
||||
body: >
|
||||
{
|
||||
"password": "s3krit",
|
||||
"roles" : [ "superuser" ]
|
||||
}
|
||||
|
||||
---
|
||||
teardown:
|
||||
- do:
|
||||
xpack.security.delete_user:
|
||||
username: "joe"
|
||||
ignore: 404
|
||||
|
||||
---
|
||||
"Test disable then enable user":
|
||||
# check that the user works
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
# disable the user
|
||||
- do:
|
||||
xpack.security.disable_user:
|
||||
username: "joe"
|
||||
|
||||
# validate user cannot login
|
||||
- do:
|
||||
catch: unauthorized
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
|
||||
# enable the user
|
||||
- do:
|
||||
xpack.security.enable_user:
|
||||
username: "joe"
|
||||
|
||||
# validate that the user can login again
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
---
|
||||
"Test enabling already enabled user":
|
||||
# check that the user works
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
# enable the user
|
||||
- do:
|
||||
xpack.security.enable_user:
|
||||
username: "joe"
|
||||
|
||||
# validate that the user still works
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
---
|
||||
"Test disabling already disabled user":
|
||||
# check that the user works
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
# disable the user
|
||||
- do:
|
||||
xpack.security.disable_user:
|
||||
username: "joe"
|
||||
|
||||
# validate user cannot login
|
||||
- do:
|
||||
catch: unauthorized
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
|
||||
# disable again
|
||||
- do:
|
||||
xpack.security.disable_user:
|
||||
username: "joe"
|
||||
|
||||
- do:
|
||||
xpack.security.enable_user:
|
||||
username: "joe"
|
||||
|
||||
---
|
||||
"Test disabling yourself":
|
||||
# check that the user works
|
||||
- do:
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
- match: { timed_out: false }
|
||||
|
||||
# try to disable yourself
|
||||
- do:
|
||||
catch: '/users may not update the enabled status of their own account/'
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
xpack.security.disable_user:
|
||||
username: "joe"
|
|
@ -12,14 +12,11 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.license.XPackInfoResponse;
|
||||
import org.elasticsearch.license.License;
|
||||
import org.elasticsearch.license.LicenseService;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.XPackFeatureSet;
|
||||
import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
|
@ -40,22 +37,6 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
public class TransportXPackInfoActionTests extends ESTestCase {
|
||||
|
||||
private boolean anonymousEnabled;
|
||||
|
||||
@Before
|
||||
public void maybeEnableAnonymous() {
|
||||
anonymousEnabled = randomBoolean();
|
||||
if (anonymousEnabled) {
|
||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||
AnonymousUser.initialize(settings);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetAnonymous() {
|
||||
AnonymousUser.initialize(Settings.EMPTY);
|
||||
}
|
||||
|
||||
public void testDoExecute() throws Exception {
|
||||
|
||||
LicenseService licenseService = mock(LicenseService.class);
|
||||
|
|
Loading…
Reference in New Issue