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.DeleteUserAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.GetUsersAction;
|
import org.elasticsearch.xpack.security.action.user.GetUsersAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.PutUserAction;
|
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.TransportAuthenticateAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.TransportChangePasswordAction;
|
import org.elasticsearch.xpack.security.action.user.TransportChangePasswordAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.TransportDeleteUserAction;
|
import org.elasticsearch.xpack.security.action.user.TransportDeleteUserAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.TransportGetUsersAction;
|
import org.elasticsearch.xpack.security.action.user.TransportGetUsersAction;
|
||||||
import org.elasticsearch.xpack.security.action.user.TransportPutUserAction;
|
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.AuditTrail;
|
||||||
import org.elasticsearch.xpack.security.audit.AuditTrailService;
|
import org.elasticsearch.xpack.security.audit.AuditTrailService;
|
||||||
import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail;
|
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.RestDeleteUserAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
|
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.RestPutUserAction;
|
||||||
|
import org.elasticsearch.xpack.security.rest.action.user.RestSetEnabledAction;
|
||||||
import org.elasticsearch.xpack.security.transport.SecurityServerTransportService;
|
import org.elasticsearch.xpack.security.transport.SecurityServerTransportService;
|
||||||
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
||||||
import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport;
|
import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport;
|
||||||
|
@ -219,15 +222,15 @@ public class Security implements ActionPlugin, IngestPlugin {
|
||||||
if (enabled == false) {
|
if (enabled == false) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
AnonymousUser.initialize(settings); // TODO: this is sketchy...testing is difficult b/c it is static....
|
|
||||||
|
|
||||||
List<Object> components = new ArrayList<>();
|
List<Object> components = new ArrayList<>();
|
||||||
final SecurityContext securityContext = new SecurityContext(settings, threadPool, cryptoService);
|
final SecurityContext securityContext = new SecurityContext(settings, threadPool, cryptoService);
|
||||||
components.add(securityContext);
|
components.add(securityContext);
|
||||||
|
|
||||||
// realms construction
|
// realms construction
|
||||||
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, threadPool);
|
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client);
|
||||||
final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore);
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
|
final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore, anonymousUser);
|
||||||
Map<String, Realm.Factory> realmFactories = new HashMap<>();
|
Map<String, Realm.Factory> realmFactories = new HashMap<>();
|
||||||
realmFactories.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService));
|
realmFactories.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService));
|
||||||
realmFactories.put(NativeRealm.TYPE, config -> new NativeRealm(config, nativeUsersStore));
|
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);
|
final Realms realms = new Realms(settings, env, realmFactories, licenseState, reservedRealm);
|
||||||
components.add(nativeUsersStore);
|
components.add(nativeUsersStore);
|
||||||
components.add(realms);
|
components.add(realms);
|
||||||
|
components.add(reservedRealm);
|
||||||
|
|
||||||
// audit trails construction
|
// audit trails construction
|
||||||
IndexAuditTrail indexAuditTrail = null;
|
IndexAuditTrail indexAuditTrail = null;
|
||||||
|
@ -294,7 +298,7 @@ public class Security implements ActionPlugin, IngestPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
final AuthenticationService authcService = new AuthenticationService(settings, realms, auditTrailService,
|
final AuthenticationService authcService = new AuthenticationService(settings, realms, auditTrailService,
|
||||||
cryptoService, failureHandler, threadPool);
|
cryptoService, failureHandler, threadPool, anonymousUser);
|
||||||
components.add(authcService);
|
components.add(authcService);
|
||||||
|
|
||||||
final FileRolesStore fileRolesStore = new FileRolesStore(settings, env, resourceWatcherService);
|
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 ReservedRolesStore reservedRolesStore = new ReservedRolesStore(securityContext);
|
||||||
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore);
|
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore);
|
||||||
final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService,
|
final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService,
|
||||||
auditTrailService, failureHandler, threadPool);
|
auditTrailService, failureHandler, threadPool, anonymousUser);
|
||||||
components.add(fileRolesStore); // has lifecycle
|
components.add(fileRolesStore); // has lifecycle
|
||||||
components.add(nativeRolesStore); // used by roles actions
|
components.add(nativeRolesStore); // used by roles actions
|
||||||
components.add(reservedRolesStore); // 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<>(PutRoleAction.INSTANCE, TransportPutRoleAction.class),
|
||||||
new ActionHandler<>(DeleteRoleAction.INSTANCE, TransportDeleteRoleAction.class),
|
new ActionHandler<>(DeleteRoleAction.INSTANCE, TransportDeleteRoleAction.class),
|
||||||
new ActionHandler<>(ChangePasswordAction.INSTANCE, TransportChangePasswordAction.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
|
@Override
|
||||||
|
@ -487,7 +492,8 @@ public class Security implements ActionPlugin, IngestPlugin {
|
||||||
RestGetRolesAction.class,
|
RestGetRolesAction.class,
|
||||||
RestPutRoleAction.class,
|
RestPutRoleAction.class,
|
||||||
RestDeleteRoleAction.class,
|
RestDeleteRoleAction.class,
|
||||||
RestChangePasswordAction.class);
|
RestChangePasswordAction.class,
|
||||||
|
RestSetEnabledAction.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
||||||
Map<String, Object> auditUsage = auditUsage(auditTrailService);
|
Map<String, Object> auditUsage = auditUsage(auditTrailService);
|
||||||
Map<String, Object> ipFilterUsage = ipFilterUsage(ipFilter);
|
Map<String, Object> ipFilterUsage = ipFilterUsage(ipFilter);
|
||||||
Map<String, Object> systemKeyUsage = systemKeyUsage(cryptoService);
|
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,
|
return new Usage(available(), enabled(), realmsUsage, rolesStoreUsage, sslUsage, auditUsage, ipFilterUsage, systemKeyUsage,
|
||||||
anonymousUsage);
|
anonymousUsage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.action.role;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequest;
|
import org.elasticsearch.action.ActionRequest;
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.support.WriteRequest;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
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
|
* 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 String name;
|
||||||
private boolean refresh = true;
|
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||||
|
|
||||||
public DeleteRoleRequest() {
|
public DeleteRoleRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeleteRoleRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
|
||||||
|
this.refreshPolicy = refreshPolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefreshPolicy getRefreshPolicy() {
|
||||||
|
return refreshPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionRequestValidationException validate() {
|
public ActionRequestValidationException validate() {
|
||||||
ActionRequestValidationException validationException = null;
|
ActionRequestValidationException validationException = null;
|
||||||
|
@ -42,25 +54,17 @@ public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(boolean refresh) {
|
|
||||||
this.refresh = refresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean refresh() {
|
|
||||||
return refresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
name = in.readString();
|
name = in.readString();
|
||||||
refresh = in.readBoolean();
|
refreshPolicy = RefreshPolicy.readFrom(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeString(name);
|
out.writeString(name);
|
||||||
out.writeBoolean(refresh);
|
refreshPolicy.writeTo(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
package org.elasticsearch.xpack.security.action.role;
|
package org.elasticsearch.xpack.security.action.role;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestBuilder;
|
import org.elasticsearch.action.ActionRequestBuilder;
|
||||||
|
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
import org.elasticsearch.client.ElasticsearchClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for requests to delete a role from the security index
|
* 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) {
|
public DeleteRoleRequestBuilder(ElasticsearchClient client) {
|
||||||
this(client, DeleteRoleAction.INSTANCE);
|
this(client, DeleteRoleAction.INSTANCE);
|
||||||
|
@ -25,9 +27,4 @@ public class DeleteRoleRequestBuilder extends ActionRequestBuilder<DeleteRoleReq
|
||||||
request.name(name);
|
request.name(name);
|
||||||
return this;
|
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.ActionRequest;
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.support.WriteRequest;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
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.
|
* 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 String username;
|
||||||
private boolean refresh = true;
|
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||||
|
|
||||||
public DeleteUserRequest() {
|
public DeleteUserRequest() {
|
||||||
}
|
}
|
||||||
|
@ -29,6 +30,17 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeleteUserRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
|
||||||
|
this.refreshPolicy = refreshPolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefreshPolicy getRefreshPolicy() {
|
||||||
|
return refreshPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionRequestValidationException validate() {
|
public ActionRequestValidationException validate() {
|
||||||
ActionRequestValidationException validationException = null;
|
ActionRequestValidationException validationException = null;
|
||||||
|
@ -42,18 +54,10 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
||||||
return this.username;
|
return this.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean refresh() {
|
|
||||||
return refresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void username(String username) {
|
public void username(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh(boolean refresh) {
|
|
||||||
this.refresh = refresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] usernames() {
|
public String[] usernames() {
|
||||||
return new String[] { username };
|
return new String[] { username };
|
||||||
|
@ -63,14 +67,14 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> implemen
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
username = in.readString();
|
username = in.readString();
|
||||||
refresh = in.readBoolean();
|
refreshPolicy = RefreshPolicy.readFrom(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeString(username);
|
out.writeString(username);
|
||||||
out.writeBoolean(refresh);
|
refreshPolicy.writeTo(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
package org.elasticsearch.xpack.security.action.user;
|
package org.elasticsearch.xpack.security.action.user;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestBuilder;
|
import org.elasticsearch.action.ActionRequestBuilder;
|
||||||
|
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
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) {
|
public DeleteUserRequestBuilder(ElasticsearchClient client) {
|
||||||
this(client, DeleteUserAction.INSTANCE);
|
this(client, DeleteUserAction.INSTANCE);
|
||||||
|
@ -22,9 +24,4 @@ public class DeleteUserRequestBuilder extends ActionRequestBuilder<DeleteUserReq
|
||||||
request.username(username);
|
request.username(username);
|
||||||
return this;
|
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.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
import org.elasticsearch.xpack.security.authc.support.CharArrays;
|
||||||
|
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -46,6 +47,10 @@ public class PutUserRequest extends ActionRequest<PutUserRequest> implements Use
|
||||||
if (roles == null) {
|
if (roles == null) {
|
||||||
validationException = addValidationError("roles are missing", validationException);
|
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
|
// 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;
|
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.xpack.security.user.User;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +37,7 @@ public class TransportAuthenticateAction extends HandledTransportAction<Authenti
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(AuthenticateRequest request, ActionListener<AuthenticateResponse> listener) {
|
protected void doExecute(AuthenticateRequest request, ActionListener<AuthenticateResponse> listener) {
|
||||||
final User user = securityContext.getUser();
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + user.principal() + "] is internal"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
|
@ -35,10 +36,10 @@ public class TransportChangePasswordAction extends HandledTransportAction<Change
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(ChangePasswordRequest request, ActionListener<ChangePasswordResponse> listener) {
|
protected void doExecute(ChangePasswordRequest request, ActionListener<ChangePasswordResponse> listener) {
|
||||||
final String username = request.username();
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
|
||||||
return;
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
public class TransportDeleteUserAction extends HandledTransportAction<DeleteUserRequest, DeleteUserResponse> {
|
public class TransportDeleteUserAction extends HandledTransportAction<DeleteUserRequest, DeleteUserResponse> {
|
||||||
|
|
||||||
|
@ -34,15 +35,15 @@ public class TransportDeleteUserAction extends HandledTransportAction<DeleteUser
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(DeleteUserRequest request, final ActionListener<DeleteUserResponse> listener) {
|
protected void doExecute(DeleteUserRequest request, final ActionListener<DeleteUserResponse> listener) {
|
||||||
final String username = request.username();
|
final String username = request.username();
|
||||||
if (ReservedRealm.isReserved(username)) {
|
if (ReservedRealm.isReserved(username, settings)) {
|
||||||
if (AnonymousUser.isAnonymousUsername(username)) {
|
if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be deleted"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be deleted"));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is reserved and cannot be deleted"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is reserved and cannot be deleted"));
|
||||||
return;
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
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.SystemUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -29,14 +29,16 @@ import static org.elasticsearch.common.Strings.arrayToDelimitedString;
|
||||||
public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequest, GetUsersResponse> {
|
public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequest, GetUsersResponse> {
|
||||||
|
|
||||||
private final NativeUsersStore usersStore;
|
private final NativeUsersStore usersStore;
|
||||||
|
private final ReservedRealm reservedRealm;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TransportGetUsersAction(Settings settings, ThreadPool threadPool, ActionFilters actionFilters,
|
public TransportGetUsersAction(Settings settings, ThreadPool threadPool, ActionFilters actionFilters,
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver, NativeUsersStore usersStore,
|
IndexNameExpressionResolver indexNameExpressionResolver, NativeUsersStore usersStore,
|
||||||
TransportService transportService) {
|
TransportService transportService, ReservedRealm reservedRealm) {
|
||||||
super(settings, GetUsersAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
super(settings, GetUsersAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||||
GetUsersRequest::new);
|
GetUsersRequest::new);
|
||||||
this.usersStore = usersStore;
|
this.usersStore = usersStore;
|
||||||
|
this.reservedRealm = reservedRealm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,16 +50,13 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
||||||
|
|
||||||
if (specificUsersRequested) {
|
if (specificUsersRequested) {
|
||||||
for (String username : requestedUsers) {
|
for (String username : requestedUsers) {
|
||||||
if (ReservedRealm.isReserved(username)) {
|
if (ReservedRealm.isReserved(username, settings)) {
|
||||||
User user = ReservedRealm.getUser(username);
|
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) {
|
if (user != null) {
|
||||||
users.add(user);
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,7 +64,7 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
users.addAll(ReservedRealm.users());
|
users.addAll(reservedRealm.users());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usersToSearchFor.size() == 1) {
|
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.authc.esnative.ReservedRealm;
|
||||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
public class TransportPutUserAction extends HandledTransportAction<PutUserRequest, PutUserResponse> {
|
public class TransportPutUserAction extends HandledTransportAction<PutUserRequest, PutUserResponse> {
|
||||||
|
|
||||||
|
@ -35,8 +36,8 @@ public class TransportPutUserAction extends HandledTransportAction<PutUserReques
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(final PutUserRequest request, final ActionListener<PutUserResponse> listener) {
|
protected void doExecute(final PutUserRequest request, final ActionListener<PutUserResponse> listener) {
|
||||||
final String username = request.username();
|
final String username = request.username();
|
||||||
if (ReservedRealm.isReserved(username)) {
|
if (ReservedRealm.isReserved(username, settings)) {
|
||||||
if (AnonymousUser.isAnonymousUsername(username)) {
|
if (AnonymousUser.isAnonymousUsername(username, settings)) {
|
||||||
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is anonymous and cannot be modified via the API"));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,7 +45,7 @@ public class TransportPutUserAction extends HandledTransportAction<PutUserReques
|
||||||
"password can be changed"));
|
"password can be changed"));
|
||||||
return;
|
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"));
|
listener.onFailure(new IllegalArgumentException("user [" + username + "] is internal"));
|
||||||
return;
|
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 AuthenticationFailureHandler failureHandler;
|
||||||
private final ThreadContext threadContext;
|
private final ThreadContext threadContext;
|
||||||
private final String nodeName;
|
private final String nodeName;
|
||||||
|
private final AnonymousUser anonymousUser;
|
||||||
private final boolean signUserHeader;
|
private final boolean signUserHeader;
|
||||||
private final boolean runAsEnabled;
|
private final boolean runAsEnabled;
|
||||||
|
private final boolean isAnonymousUserEnabled;
|
||||||
|
|
||||||
public AuthenticationService(Settings settings, Realms realms, AuditTrailService auditTrail, CryptoService cryptoService,
|
public AuthenticationService(Settings settings, Realms realms, AuditTrailService auditTrail, CryptoService cryptoService,
|
||||||
AuthenticationFailureHandler failureHandler, ThreadPool threadPool) {
|
AuthenticationFailureHandler failureHandler, ThreadPool threadPool, AnonymousUser anonymousUser) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.nodeName = Node.NODE_NAME_SETTING.get(settings);
|
this.nodeName = Node.NODE_NAME_SETTING.get(settings);
|
||||||
this.realms = realms;
|
this.realms = realms;
|
||||||
|
@ -62,8 +64,10 @@ public class AuthenticationService extends AbstractComponent {
|
||||||
this.cryptoService = cryptoService;
|
this.cryptoService = cryptoService;
|
||||||
this.failureHandler = failureHandler;
|
this.failureHandler = failureHandler;
|
||||||
this.threadContext = threadPool.getThreadContext();
|
this.threadContext = threadPool.getThreadContext();
|
||||||
|
this.anonymousUser = anonymousUser;
|
||||||
this.signUserHeader = SIGN_USER_HEADER.get(settings);
|
this.signUserHeader = SIGN_USER_HEADER.get(settings);
|
||||||
this.runAsEnabled = RUN_AS_ENABLED.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);
|
throw handleNullUser(token);
|
||||||
}
|
}
|
||||||
user = lookupRunAsUserIfNecessary(user, token);
|
user = lookupRunAsUserIfNecessary(user, token);
|
||||||
|
checkIfUserIsDisabled(user, token);
|
||||||
|
|
||||||
final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy);
|
final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy);
|
||||||
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
authentication.writeToContext(threadContext, cryptoService, signUserHeader);
|
||||||
|
@ -204,9 +209,9 @@ public class AuthenticationService extends AbstractComponent {
|
||||||
if (fallbackUser != null) {
|
if (fallbackUser != null) {
|
||||||
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
|
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
|
||||||
authentication = new Authentication(fallbackUser, authenticatedBy, null);
|
authentication = new Authentication(fallbackUser, authenticatedBy, null);
|
||||||
} else if (AnonymousUser.enabled()) {
|
} else if (isAnonymousUserEnabled) {
|
||||||
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
|
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
|
||||||
authentication = new Authentication(AnonymousUser.INSTANCE, authenticatedBy, null);
|
authentication = new Authentication(anonymousUser, authenticatedBy, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
|
@ -297,6 +302,13 @@ public class AuthenticationService extends AbstractComponent {
|
||||||
return user;
|
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 class AuditableRequest {
|
||||||
|
|
||||||
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
abstract void realmAuthenticationFailed(AuthenticationToken token, String realm);
|
||||||
|
|
|
@ -229,7 +229,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand {
|
||||||
Path usersFile = FileUserPasswdStore.resolveFile(env);
|
Path usersFile = FileUserPasswdStore.resolveFile(env);
|
||||||
Path usersRolesFile = FileUserRolesStore.resolveFile(env);
|
Path usersRolesFile = FileUserRolesStore.resolveFile(env);
|
||||||
terminal.println("importing users from [" + usersFile + "]...");
|
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);
|
Map<String, String[]> userToRoles = FileUserRolesStore.parseFile(usersRolesFile, null);
|
||||||
Set<String> existingUsers;
|
Set<String> existingUsers;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.authc.esnative;
|
package org.elasticsearch.xpack.security.authc.esnative;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||||
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||||
|
@ -19,12 +17,11 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
|
||||||
|
|
||||||
public static final String TYPE = "native";
|
public static final String TYPE = "native";
|
||||||
|
|
||||||
final NativeUsersStore userStore;
|
private final NativeUsersStore userStore;
|
||||||
|
|
||||||
public NativeRealm(RealmConfig config, NativeUsersStore usersStore) {
|
public NativeRealm(RealmConfig config, NativeUsersStore usersStore) {
|
||||||
super(TYPE, config);
|
super(TYPE, config);
|
||||||
this.userStore = usersStore;
|
this.userStore = usersStore;
|
||||||
usersStore.addListener(new Listener());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,14 +38,4 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
|
||||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||||
return userStore.verifyPassword(token.principal(), token.credentials());
|
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;
|
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.message.ParameterizedMessage;
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
import org.apache.logging.log4j.util.Supplier;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.DocWriteResponse;
|
import org.elasticsearch.action.DocWriteResponse;
|
||||||
|
import org.elasticsearch.action.DocWriteResponse.Result;
|
||||||
import org.elasticsearch.action.LatchedActionListener;
|
import org.elasticsearch.action.LatchedActionListener;
|
||||||
import org.elasticsearch.action.delete.DeleteRequest;
|
import org.elasticsearch.action.delete.DeleteRequest;
|
||||||
import org.elasticsearch.action.delete.DeleteResponse;
|
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.search.SearchScrollRequest;
|
||||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||||
import org.elasticsearch.action.update.UpdateResponse;
|
import org.elasticsearch.action.update.UpdateResponse;
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
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.Setting.Property;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
import org.elasticsearch.gateway.GatewayService;
|
||||||
import org.elasticsearch.index.IndexNotFoundException;
|
import org.elasticsearch.index.IndexNotFoundException;
|
||||||
import org.elasticsearch.index.engine.DocumentMissingException;
|
import org.elasticsearch.index.engine.DocumentMissingException;
|
||||||
import org.elasticsearch.index.query.QueryBuilder;
|
import org.elasticsearch.index.query.QueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.search.SearchHit;
|
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.InternalClient;
|
||||||
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
||||||
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheRequest;
|
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.SystemUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
import org.elasticsearch.xpack.security.user.User.Fields;
|
import org.elasticsearch.xpack.security.user.User.Fields;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
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;
|
import static org.elasticsearch.xpack.security.SecurityTemplateService.securityIndexMappingAndTemplateUpToDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ESNativeUsersStore is a {@code UserStore} that, instead of reading from a
|
* NativeUsersStore is a store for users that reads from an Elasticsearch index. This store is responsible for fetching the full
|
||||||
* file, reads from an Elasticsearch index instead. This {@code UserStore} in
|
* {@link User} object, which includes the names of the roles assigned to the user.
|
||||||
* 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.
|
|
||||||
* <p>
|
* <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 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);
|
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);
|
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 {
|
public enum State {
|
||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
STARTING,
|
STARTING,
|
||||||
|
@ -109,25 +96,20 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
FAILED
|
FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String USER_DOC_TYPE = "user";
|
private static final String USER_DOC_TYPE = "user";
|
||||||
static final String RESERVED_USER_DOC_TYPE = "reserved-user";
|
private static final String RESERVED_USER_DOC_TYPE = "reserved-user";
|
||||||
|
|
||||||
private final Hasher hasher = Hasher.BCRYPT;
|
private final Hasher hasher = Hasher.BCRYPT;
|
||||||
private final List<ChangeListener> listeners = new CopyOnWriteArrayList<>();
|
|
||||||
private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED);
|
private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED);
|
||||||
private final InternalClient client;
|
private final InternalClient client;
|
||||||
private final ThreadPool threadPool;
|
|
||||||
|
|
||||||
private Cancellable pollerCancellable;
|
|
||||||
private int scrollSize;
|
private int scrollSize;
|
||||||
private TimeValue scrollKeepAlive;
|
private TimeValue scrollKeepAlive;
|
||||||
|
|
||||||
private volatile boolean securityIndexExists = false;
|
private volatile boolean securityIndexExists = false;
|
||||||
|
|
||||||
public NativeUsersStore(Settings settings, InternalClient client, ThreadPool threadPool) {
|
public NativeUsersStore(Settings settings, InternalClient client) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.client = client;
|
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) {
|
private UserAndPassword getUserAndPassword(final String username) {
|
||||||
final AtomicReference<UserAndPassword> userRef = new AtomicReference<>(null);
|
final AtomicReference<UserAndPassword> userRef = new AtomicReference<>(null);
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
@ -278,6 +263,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
return userRef.get();
|
return userRef.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async method to retrieve a user and their password
|
||||||
|
*/
|
||||||
private void getUserAndPassword(final String user, final ActionListener<UserAndPassword> listener) {
|
private void getUserAndPassword(final String user, final ActionListener<UserAndPassword> listener) {
|
||||||
try {
|
try {
|
||||||
GetRequest request = client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, user).request();
|
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) {
|
public void changePassword(final ChangePasswordRequest request, final ActionListener<Void> listener) {
|
||||||
final String username = request.username();
|
final String username = request.username();
|
||||||
if (SystemUser.NAME.equals(username)) {
|
assert SystemUser.NAME.equals(username) == false && XPackUser.NAME.equals(username) == false : username + "is internal!";
|
||||||
ValidationException validationException = new ValidationException();
|
|
||||||
validationException.addValidationError("changing the password for [" + username + "] is not allowed");
|
|
||||||
listener.onFailure(validationException);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String docType;
|
final String docType;
|
||||||
if (ReservedRealm.isReserved(username)) {
|
if (ReservedRealm.isReserved(username, settings)) {
|
||||||
docType = RESERVED_USER_DOC_TYPE;
|
docType = RESERVED_USER_DOC_TYPE;
|
||||||
} else {
|
} else {
|
||||||
docType = USER_DOC_TYPE;
|
docType = USER_DOC_TYPE;
|
||||||
|
@ -338,33 +325,30 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Exception e) {
|
public void onFailure(Exception e) {
|
||||||
Throwable cause = e;
|
if (isIndexNotFoundOrDocumentMissing(e)) {
|
||||||
if (e instanceof ElasticsearchException) {
|
if (docType.equals(RESERVED_USER_DOC_TYPE)) {
|
||||||
cause = ExceptionsHelper.unwrapCause(e);
|
createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener);
|
||||||
if ((cause instanceof IndexNotFoundException) == false
|
} else {
|
||||||
&& (cause instanceof DocumentMissingException) == false) {
|
logger.debug((Supplier<?>) () ->
|
||||||
listener.onFailure(e);
|
new ParameterizedMessage("failed to change password for user [{}]", request.username()), e);
|
||||||
return;
|
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 {
|
} else {
|
||||||
logger.debug(
|
listener.onFailure(e);
|
||||||
(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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener<Void> listener) {
|
||||||
client.prepareIndex(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
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)
|
.setRefreshPolicy(refresh)
|
||||||
.execute(new ActionListener<IndexResponse>() {
|
.execute(new ActionListener<IndexResponse>() {
|
||||||
@Override
|
@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) {
|
public void putUser(final PutUserRequest request, final ActionListener<Boolean> listener) {
|
||||||
if (state() != State.STARTED) {
|
if (state() != State.STARTED) {
|
||||||
listener.onFailure(new IllegalStateException("user cannot be added as native user service has not been 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) {
|
if (request.passwordHash() == null) {
|
||||||
updateUserWithoutPassword(request, listener);
|
updateUserWithoutPassword(request, listener);
|
||||||
} else {
|
} else {
|
||||||
indexUser(request, listener);
|
upsertUser(request, listener);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error((Supplier<?>) () -> new ParameterizedMessage("unable to put user [{}]", request.username()), 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) {
|
private void updateUserWithoutPassword(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||||
assert putUserRequest.passwordHash() == null;
|
assert putUserRequest.passwordHash() == null;
|
||||||
// We must have an existing document
|
// We must have an existing document
|
||||||
|
@ -416,52 +409,43 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Exception e) {
|
public void onFailure(Exception e) {
|
||||||
Throwable cause = e;
|
Exception failure = e;
|
||||||
if (e instanceof ElasticsearchException) {
|
if (isIndexNotFoundOrDocumentMissing(e)) {
|
||||||
cause = ExceptionsHelper.unwrapCause(e);
|
// if the index doesn't exist we can never update a user
|
||||||
if ((cause instanceof IndexNotFoundException) == false
|
// if the document doesn't exist, then this update is not valid
|
||||||
&& (cause instanceof DocumentMissingException) == false) {
|
logger.debug((Supplier<?>) () -> new ParameterizedMessage("failed to update user document with username [{}]",
|
||||||
listener.onFailure(e);
|
putUserRequest.username()), e);
|
||||||
return;
|
ValidationException validationException = new ValidationException();
|
||||||
}
|
validationException.addValidationError("password must be specified unless you are updating an existing user");
|
||||||
|
failure = validationException;
|
||||||
}
|
}
|
||||||
|
listener.onFailure(failure);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
private void upsertUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||||
assert putUserRequest.passwordHash() != null;
|
assert putUserRequest.passwordHash() != null;
|
||||||
client.prepareIndex(SecurityTemplateService.SECURITY_INDEX_NAME,
|
client.prepareUpdate(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||||
USER_DOC_TYPE, putUserRequest.username())
|
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.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()),
|
||||||
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(),
|
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(),
|
||||||
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(),
|
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(),
|
||||||
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(),
|
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(),
|
||||||
User.Fields.METADATA.getPreferredName(), putUserRequest.metadata())
|
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())
|
.setRefreshPolicy(putUserRequest.getRefreshPolicy())
|
||||||
.execute(new ActionListener<IndexResponse>() {
|
.execute(new ActionListener<UpdateResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(IndexResponse indexResponse) {
|
public void onResponse(UpdateResponse updateResponse) {
|
||||||
// if the document was just created, then we don't need to clear cache
|
clearRealmCache(putUserRequest.username(), listener, updateResponse.getResult() == DocWriteResponse.Result.CREATED);
|
||||||
boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED;
|
|
||||||
if (created) {
|
|
||||||
listener.onResponse(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearRealmCache(putUserRequest.username(), listener, created);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener<Boolean> listener) {
|
||||||
if (state() != State.STARTED) {
|
if (state() != State.STARTED) {
|
||||||
listener.onFailure(new IllegalStateException("user cannot be deleted as native user service has not been 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,
|
DeleteRequest request = client.prepareDelete(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||||
USER_DOC_TYPE, deleteUserRequest.username()).request();
|
USER_DOC_TYPE, deleteUserRequest.username()).request();
|
||||||
request.indicesOptions().ignoreUnavailable();
|
request.indicesOptions().ignoreUnavailable();
|
||||||
request.setRefreshPolicy(deleteUserRequest.refresh() ? RefreshPolicy.IMMEDIATE : RefreshPolicy.WAIT_UNTIL);
|
request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy());
|
||||||
client.delete(request, new ActionListener<DeleteResponse>() {
|
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(DeleteResponse deleteResponse) {
|
public void onResponse(DeleteResponse deleteResponse) {
|
||||||
|
@ -537,15 +597,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
|
if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
|
||||||
this.scrollSize = SCROLL_SIZE_SETTING.get(settings);
|
this.scrollSize = SCROLL_SIZE_SETTING.get(settings);
|
||||||
this.scrollKeepAlive = SCROLL_KEEP_ALIVE_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);
|
state.set(State.STARTED);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -556,14 +607,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
||||||
try {
|
state.set(State.STOPPED);
|
||||||
pollerCancellable.cancel();
|
|
||||||
} catch (Exception e) {
|
|
||||||
state.set(State.FAILED);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
state.set(State.STOPPED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -574,7 +618,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
* @param password the plaintext password to verify
|
* @param password the plaintext password to verify
|
||||||
* @return {@link} User object if successful or {@code null} if verification fails
|
* @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) {
|
if (state() != State.STARTED) {
|
||||||
logger.trace("attempted to verify user credentials for [{}] but service was not started", username);
|
logger.trace("attempted to verify user credentials for [{}] but service was not started", username);
|
||||||
return null;
|
return null;
|
||||||
|
@ -590,11 +634,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(ChangeListener listener) {
|
public boolean started() {
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean started() {
|
|
||||||
return state() == State.STARTED;
|
return state() == State.STARTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,9 +642,9 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
return securityIndexExists;
|
return securityIndexExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] reservedUserPassword(String username) throws Exception {
|
ReservedUserInfo getReservedUserInfo(String username) throws Exception {
|
||||||
assert started();
|
assert started();
|
||||||
final AtomicReference<char[]> passwordHash = new AtomicReference<>();
|
final AtomicReference<ReservedUserInfo> userInfoRef = new AtomicReference<>();
|
||||||
final AtomicReference<Exception> failure = new AtomicReference<>();
|
final AtomicReference<Exception> failure = new AtomicReference<>();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
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()) {
|
if (getResponse.isExists()) {
|
||||||
Map<String, Object> sourceMap = getResponse.getSourceAsMap();
|
Map<String, Object> sourceMap = getResponse.getSourceAsMap();
|
||||||
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
||||||
|
Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName());
|
||||||
if (password == null || password.isEmpty()) {
|
if (password == null || password.isEmpty()) {
|
||||||
failure.set(new IllegalStateException("password hash must not be empty!"));
|
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
|
@Override
|
||||||
public void onFailure(Exception e) {
|
public void onFailure(Exception e) {
|
||||||
if (e instanceof IndexNotFoundException) {
|
if (e instanceof IndexNotFoundException) {
|
||||||
logger.trace(
|
logger.trace((Supplier<?>) () -> new ParameterizedMessage(
|
||||||
(Supplier<?>) () -> new ParameterizedMessage(
|
"could not retrieve built in user [{}] info since security index does not exist", username), e);
|
||||||
"could not retrieve built in user [{}] password since security index does not exist",
|
|
||||||
username),
|
|
||||||
e);
|
|
||||||
} else {
|
} else {
|
||||||
logger.error(
|
logger.error(
|
||||||
(Supplier<?>) () -> new ParameterizedMessage(
|
(Supplier<?>) () -> new ParameterizedMessage(
|
||||||
"failed to retrieve built in user [{}] password", username), e);
|
"failed to retrieve built in user [{}] info", username), e);
|
||||||
failure.set(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...
|
// if there is any sort of failure we need to throw an exception to prevent the fallback to the default password...
|
||||||
throw failureCause;
|
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) {
|
private void clearScrollResponse(String scrollId) {
|
||||||
|
@ -716,7 +814,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
||||||
if (state != State.STOPPED && state != State.FAILED) {
|
if (state != State.STOPPED && state != State.FAILED) {
|
||||||
throw new IllegalStateException("can only reset if stopped!!!");
|
throw new IllegalStateException("can only reset if stopped!!!");
|
||||||
}
|
}
|
||||||
this.listeners.clear();
|
|
||||||
this.securityIndexExists = false;
|
this.securityIndexExists = false;
|
||||||
this.state.set(State.INITIALIZED);
|
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[] roles = ((List<String>) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY);
|
||||||
String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName());
|
String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName());
|
||||||
String email = (String) sourceMap.get(User.Fields.EMAIL.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());
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error((Supplier<?>) () -> new ParameterizedMessage("error in the format of data for user [{}]", username), e);
|
logger.error((Supplier<?>) () -> new ParameterizedMessage("error in the format of data for user [{}]", username), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UserStorePoller extends AbstractRunnable {
|
private static boolean isIndexNotFoundOrDocumentMissing(Exception e) {
|
||||||
|
if (e instanceof ElasticsearchException) {
|
||||||
// this map contains the mapping for username -> version, which is used when polling the index to easily detect of
|
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
||||||
// any changes that may have been missed since the last update.
|
if (cause instanceof IndexNotFoundException || cause instanceof DocumentMissingException) {
|
||||||
private final ObjectLongHashMap<String> userVersionMap = new ObjectLongHashMap<>();
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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) {
|
public static void addSettings(List<Setting<?>> settings) {
|
||||||
settings.add(SCROLL_SIZE_SETTING);
|
settings.add(SCROLL_SIZE_SETTING);
|
||||||
settings.add(SCROLL_KEEP_ALIVE_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.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
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.CachingUsernamePasswordRealm;
|
||||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
||||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
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.KibanaUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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.
|
* 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 class ReservedRealm extends CachingUsernamePasswordRealm {
|
||||||
|
|
||||||
public static final String TYPE = "reserved";
|
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 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));
|
super(TYPE, new RealmConfig(TYPE, Settings.EMPTY, settings, env));
|
||||||
this.nativeUsersStore = nativeUsersStore;
|
this.nativeUsersStore = nativeUsersStore;
|
||||||
nativeUsersStore.addListener(new ChangeListener() {
|
this.anonymousUser = anonymousUser;
|
||||||
@Override
|
this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
|
||||||
public void onUsersChanged(List<String> changedUsers) {
|
|
||||||
changedUsers.stream()
|
|
||||||
.filter(ReservedRealm::isReserved)
|
|
||||||
.forEach(ReservedRealm.this::expire);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||||
final User user = getUser(token.principal());
|
if (isReserved(token.principal(), config.globalSettings()) == false) {
|
||||||
if (user == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final char[] passwordHash = getPasswordHash(user.principal());
|
final ReservedUserInfo userInfo = getUserInfo(token.principal());
|
||||||
if (passwordHash != null) {
|
if (userInfo != null) {
|
||||||
try {
|
try {
|
||||||
if (Hasher.BCRYPT.verify(token.credentials(), passwordHash)) {
|
if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
|
||||||
return user;
|
return getUser(token.principal(), userInfo);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (passwordHash != DEFAULT_PASSWORD_HASH) {
|
if (userInfo.passwordHash != DEFAULT_PASSWORD_HASH) {
|
||||||
Arrays.fill(passwordHash, (char) 0);
|
Arrays.fill(userInfo.passwordHash, (char) 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +73,20 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected User doLookupUser(String username) {
|
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
|
@Override
|
||||||
|
@ -83,54 +94,71 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isReserved(String username) {
|
public static boolean isReserved(String username, Settings settings) {
|
||||||
assert username != null;
|
assert username != null;
|
||||||
switch (username) {
|
switch (username) {
|
||||||
case ElasticUser.NAME:
|
case ElasticUser.NAME:
|
||||||
case KibanaUser.NAME:
|
case KibanaUser.NAME:
|
||||||
return true;
|
return true;
|
||||||
default:
|
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;
|
assert username != null;
|
||||||
switch (username) {
|
switch (username) {
|
||||||
case ElasticUser.NAME:
|
case ElasticUser.NAME:
|
||||||
return ElasticUser.INSTANCE;
|
return new ElasticUser(userInfo.enabled);
|
||||||
case KibanaUser.NAME:
|
case KibanaUser.NAME:
|
||||||
return KibanaUser.INSTANCE;
|
return new KibanaUser(userInfo.enabled);
|
||||||
default:
|
default:
|
||||||
if (AnonymousUser.enabled() && AnonymousUser.isAnonymousUsername(username)) {
|
if (anonymousEnabled && anonymousUser.principal().equals(username)) {
|
||||||
return AnonymousUser.INSTANCE;
|
return anonymousUser;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<User> users() {
|
public Collection<User> users() {
|
||||||
if (AnonymousUser.enabled()) {
|
if (nativeUsersStore.started() == false) {
|
||||||
return Arrays.asList(ElasticUser.INSTANCE, KibanaUser.INSTANCE, AnonymousUser.INSTANCE);
|
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) {
|
if (nativeUsersStore.started() == false) {
|
||||||
// we need to be able to check for the user store being started...
|
// we need to be able to check for the user store being started...
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nativeUsersStore.securityIndexExists() == false) {
|
if (nativeUsersStore.securityIndexExists() == false) {
|
||||||
return DEFAULT_PASSWORD_HASH;
|
return DEFAULT_USER_INFO;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
char[] passwordHash = nativeUsersStore.reservedUserPassword(username);
|
ReservedUserInfo userInfo = nativeUsersStore.getReservedUserInfo(username);
|
||||||
if (passwordHash == null) {
|
if (userInfo == null) {
|
||||||
return DEFAULT_PASSWORD_HASH;
|
return DEFAULT_USER_INFO;
|
||||||
}
|
}
|
||||||
return passwordHash;
|
return userInfo;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
(Supplier<?>) () -> new ParameterizedMessage("failed to retrieve password hash for reserved user [{}]", username), e);
|
(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.apache.logging.log4j.util.Supplier;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.common.inject.internal.Nullable;
|
import org.elasticsearch.common.inject.internal.Nullable;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.watcher.FileChangesListener;
|
import org.elasticsearch.watcher.FileChangesListener;
|
||||||
import org.elasticsearch.watcher.FileWatcher;
|
import org.elasticsearch.watcher.FileWatcher;
|
||||||
|
@ -43,7 +44,8 @@ public class FileUserPasswdStore {
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
private final Path file;
|
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;
|
private volatile Map<String, char[]> users;
|
||||||
|
|
||||||
|
@ -56,7 +58,8 @@ public class FileUserPasswdStore {
|
||||||
FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
|
FileUserPasswdStore(RealmConfig config, ResourceWatcherService watcherService, RefreshListener listener) {
|
||||||
logger = config.logger(FileUserPasswdStore.class);
|
logger = config.logger(FileUserPasswdStore.class);
|
||||||
file = resolveFile(config.env());
|
file = resolveFile(config.env());
|
||||||
users = parseFileLenient(file, logger);
|
settings = config.globalSettings();
|
||||||
|
users = parseFileLenient(file, logger, settings);
|
||||||
FileWatcher watcher = new FileWatcher(file.getParent());
|
FileWatcher watcher = new FileWatcher(file.getParent());
|
||||||
watcher.addListener(new FileListener());
|
watcher.addListener(new FileListener());
|
||||||
try {
|
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
|
* 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.
|
* 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 {
|
try {
|
||||||
return parseFile(path, logger);
|
return parseFile(path, logger, settings);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
(Supplier<?>) () -> new ParameterizedMessage(
|
(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
|
* parses the users file. Should never return {@code null}, if the file doesn't exist an
|
||||||
* empty map is returned
|
* 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) {
|
if (logger == null) {
|
||||||
logger = NoOpLogger.INSTANCE;
|
logger = NoOpLogger.INSTANCE;
|
||||||
}
|
}
|
||||||
|
@ -146,7 +149,7 @@ public class FileUserPasswdStore {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String username = line.substring(0, i);
|
String username = line.substring(0, i);
|
||||||
Validation.Error validationError = Users.validateUsername(username);
|
Validation.Error validationError = Users.validateUsername(username, false, settings);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(),
|
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(),
|
||||||
validationError);
|
validationError);
|
||||||
|
@ -191,7 +194,7 @@ public class FileUserPasswdStore {
|
||||||
public void onFileChanged(Path file) {
|
public void onFileChanged(Path file) {
|
||||||
if (file.equals(FileUserPasswdStore.this.file)) {
|
if (file.equals(FileUserPasswdStore.this.file)) {
|
||||||
logger.info("users file [{}] changed. updating users... )", file.toAbsolutePath());
|
logger.info("users file [{}] changed. updating users... )", file.toAbsolutePath());
|
||||||
users = parseFileLenient(file, logger);
|
users = parseFileLenient(file, logger, settings);
|
||||||
notifyRefresh();
|
notifyRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,21 +84,21 @@ public class UsersTool extends MultiCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
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);
|
||||||
Validation.Error validationError = Users.validateUsername(username);
|
String username = parseUsername(arguments.values(options), env.settings());
|
||||||
|
Validation.Error validationError = Users.validateUsername(username, false, Settings.EMPTY);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
char[] password = parsePassword(terminal, passwordOption.value(options));
|
char[] password = parsePassword(terminal, passwordOption.value(options));
|
||||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
|
||||||
String[] roles = parseRoles(terminal, env, rolesOption.value(options));
|
String[] roles = parseRoles(terminal, env, rolesOption.value(options));
|
||||||
|
|
||||||
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
||||||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
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)) {
|
if (users.containsKey(username)) {
|
||||||
throw new UserException(ExitCodes.CODE_ERROR, "User [" + username + "] already exists");
|
throw new UserException(ExitCodes.CODE_ERROR, "User [" + username + "] already exists");
|
||||||
}
|
}
|
||||||
|
@ -138,13 +138,13 @@ public class UsersTool extends MultiCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
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);
|
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
||||||
|
String username = parseUsername(arguments.values(options), env.settings());
|
||||||
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
Path passwordFile = FileUserPasswdStore.resolveFile(env);
|
||||||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(passwordFile, rolesFile);
|
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) {
|
if (users.containsKey(username) == false) {
|
||||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
}
|
}
|
||||||
|
@ -193,13 +193,13 @@ public class UsersTool extends MultiCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
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));
|
char[] password = parsePassword(terminal, passwordOption.value(options));
|
||||||
|
|
||||||
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
|
|
||||||
Path file = FileUserPasswdStore.resolveFile(env);
|
Path file = FileUserPasswdStore.resolveFile(env);
|
||||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(file);
|
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) {
|
if (users.containsKey(username) == false) {
|
||||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
||||||
}
|
}
|
||||||
|
@ -237,8 +237,8 @@ public class UsersTool extends MultiCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
|
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);
|
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[] addRoles = parseRoles(terminal, env, addOption.value(options));
|
||||||
String[] removeRoles = parseRoles(terminal, env, removeOption.value(options));
|
String[] removeRoles = parseRoles(terminal, env, removeOption.value(options));
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ public class UsersTool extends MultiCommand {
|
||||||
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
Path rolesFile = FileUserRolesStore.resolveFile(env);
|
||||||
FileAttributesChecker attributesChecker = new FileAttributesChecker(usersFile, rolesFile);
|
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)) {
|
if (!usersMap.containsKey(username)) {
|
||||||
throw new UserException(ExitCodes.NO_USER, "User [" + username + "] doesn't exist");
|
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);
|
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
|
||||||
|
|
||||||
Path userFilePath = FileUserPasswdStore.resolveFile(env);
|
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);
|
Path rolesFilePath = FileRolesStore.resolveFile(env);
|
||||||
Set<String> knownRoles = Sets.union(FileRolesStore.parseFileForRoleNames(rolesFilePath, null), ReservedRolesStore.names());
|
Set<String> knownRoles = Sets.union(FileRolesStore.parseFileForRoleNames(rolesFilePath, null), ReservedRolesStore.names());
|
||||||
|
@ -388,14 +388,14 @@ public class UsersTool extends MultiCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pkg private for testing
|
// 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()) {
|
if (args.isEmpty()) {
|
||||||
throw new UserException(ExitCodes.USAGE, "Missing username argument");
|
throw new UserException(ExitCodes.USAGE, "Missing username argument");
|
||||||
} else if (args.size() > 1) {
|
} else if (args.size() > 1) {
|
||||||
throw new UserException(ExitCodes.USAGE, "Expected a single username argument, found extra: " + args.toString());
|
throw new UserException(ExitCodes.USAGE, "Expected a single username argument, found extra: " + args.toString());
|
||||||
}
|
}
|
||||||
String username = args.get(0);
|
String username = args.get(0);
|
||||||
Validation.Error validationError = Users.validateUsername(username);
|
Validation.Error validationError = Users.validateUsername(username, false, settings);
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
|
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.common.unit.TimeValue;
|
||||||
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
|
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
|
||||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||||
import org.elasticsearch.xpack.security.support.Exceptions;
|
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -149,11 +148,11 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
||||||
|
|
||||||
CacheLoader<String, UserWithHash> callback = key -> {
|
CacheLoader<String, UserWithHash> callback = key -> {
|
||||||
if (logger.isDebugEnabled()) {
|
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);
|
User user = doLookupUser(username);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw Exceptions.authenticationError("could not lookup [{}]", username);
|
return null;
|
||||||
}
|
}
|
||||||
return new UserWithHash(user, null, null);
|
return new UserWithHash(user, null, null);
|
||||||
};
|
};
|
||||||
|
@ -162,10 +161,15 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
||||||
UserWithHash userWithHash = cache.computeIfAbsent(username, callback);
|
UserWithHash userWithHash = cache.computeIfAbsent(username, callback);
|
||||||
return userWithHash.user;
|
return userWithHash.user;
|
||||||
} catch (ExecutionException ee) {
|
} catch (ExecutionException ee) {
|
||||||
|
if (ee.getCause() instanceof ElasticsearchSecurityException) {
|
||||||
|
// this should bubble out
|
||||||
|
throw (ElasticsearchSecurityException) ee.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage("realm [{}] could not lookup [{}]", name(), username), ee);
|
logger.trace((Supplier<?>) () -> new ParameterizedMessage("realm [{}] could not lookup [{}]", name(), username), ee);
|
||||||
} else if (logger.isDebugEnabled()) {
|
} else if (logger.isDebugEnabled()) {
|
||||||
logger.debug("realm [{}] could not authenticate [{}]", name(), username);
|
logger.debug("realm [{}] could not lookup [{}]", name(), username);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,11 +77,13 @@ public class AuthorizationService extends AbstractComponent {
|
||||||
private final IndicesAndAliasesResolver[] indicesAndAliasesResolvers;
|
private final IndicesAndAliasesResolver[] indicesAndAliasesResolvers;
|
||||||
private final AuthenticationFailureHandler authcFailureHandler;
|
private final AuthenticationFailureHandler authcFailureHandler;
|
||||||
private final ThreadContext threadContext;
|
private final ThreadContext threadContext;
|
||||||
|
private final AnonymousUser anonymousUser;
|
||||||
|
private final boolean isAnonymousEnabled;
|
||||||
private final boolean anonymousAuthzExceptionEnabled;
|
private final boolean anonymousAuthzExceptionEnabled;
|
||||||
|
|
||||||
public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService,
|
public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService,
|
||||||
AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler,
|
AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler,
|
||||||
ThreadPool threadPool) {
|
ThreadPool threadPool, AnonymousUser anonymousUser) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.rolesStore = rolesStore;
|
this.rolesStore = rolesStore;
|
||||||
this.clusterService = clusterService;
|
this.clusterService = clusterService;
|
||||||
|
@ -91,6 +93,8 @@ public class AuthorizationService extends AbstractComponent {
|
||||||
};
|
};
|
||||||
this.authcFailureHandler = authcFailureHandler;
|
this.authcFailureHandler = authcFailureHandler;
|
||||||
this.threadContext = threadPool.getThreadContext();
|
this.threadContext = threadPool.getThreadContext();
|
||||||
|
this.anonymousUser = anonymousUser;
|
||||||
|
this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
|
||||||
this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings);
|
this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +105,7 @@ public class AuthorizationService extends AbstractComponent {
|
||||||
* @param action The action
|
* @param action The action
|
||||||
*/
|
*/
|
||||||
public List<String> authorizedIndicesAndAliases(User user, String 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();
|
String[] rolesNames = user.roles();
|
||||||
if (rolesNames.length == 0 && anonymousRoles.length == 0) {
|
if (rolesNames.length == 0 && anonymousRoles.length == 0) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
@ -114,7 +118,7 @@ public class AuthorizationService extends AbstractComponent {
|
||||||
predicates.add(role.indices().allowedIndicesMatcher(action));
|
predicates.add(role.indices().allowedIndicesMatcher(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (AnonymousUser.is(user) == false) {
|
if (anonymousUser.equals(user) == false) {
|
||||||
for (String roleName : anonymousRoles) {
|
for (String roleName : anonymousRoles) {
|
||||||
Role role = rolesStore.role(roleName);
|
Role role = rolesStore.role(roleName);
|
||||||
if (role != null) {
|
if (role != null) {
|
||||||
|
@ -360,7 +364,7 @@ public class AuthorizationService extends AbstractComponent {
|
||||||
private ElasticsearchSecurityException denialException(Authentication authentication, String action) {
|
private ElasticsearchSecurityException denialException(Authentication authentication, String action) {
|
||||||
final User user = authentication.getUser();
|
final User user = authentication.getUser();
|
||||||
// Special case for anonymous user
|
// Special case for anonymous user
|
||||||
if (AnonymousUser.enabled() && AnonymousUser.is(user)) {
|
if (isAnonymousEnabled && anonymousUser.equals(user)) {
|
||||||
if (anonymousAuthzExceptionEnabled == false) {
|
if (anonymousAuthzExceptionEnabled == false) {
|
||||||
throw authcFailureHandler.authenticationRequired(action, threadContext);
|
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.SearchRequest;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
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;
|
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
|
* 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
|
* store, ESNativeRolesStore can be used to add a role to the store by inserting
|
||||||
* the document into the administrative index.
|
* the document into the administrative index.
|
||||||
|
@ -264,7 +263,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C
|
||||||
try {
|
try {
|
||||||
DeleteRequest request = client.prepareDelete(SecurityTemplateService.SECURITY_INDEX_NAME,
|
DeleteRequest request = client.prepareDelete(SecurityTemplateService.SECURITY_INDEX_NAME,
|
||||||
ROLE_DOC_TYPE, deleteRoleRequest.name()).request();
|
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>() {
|
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(DeleteResponse deleteResponse) {
|
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.authz.permission.TransportClientRole;
|
||||||
import org.elasticsearch.xpack.security.user.KibanaUser;
|
import org.elasticsearch.xpack.security.user.KibanaUser;
|
||||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||||
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ReservedRolesStore implements RolesStore {
|
public class ReservedRolesStore implements RolesStore {
|
||||||
|
|
||||||
|
private static final User DEFAULT_ENABLED_KIBANA_USER = new KibanaUser(true);
|
||||||
private final SecurityContext securityContext;
|
private final SecurityContext securityContext;
|
||||||
|
|
||||||
public ReservedRolesStore(SecurityContext securityContext) {
|
public ReservedRolesStore(SecurityContext securityContext) {
|
||||||
|
@ -54,8 +56,9 @@ public class ReservedRolesStore implements RolesStore {
|
||||||
case KibanaRole.NAME:
|
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
|
// 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.
|
// 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.
|
// We don't want it to be assigned to other users. The Kibana user here must always be enabled if it is in the
|
||||||
if (KibanaUser.is(securityContext.getUser())) {
|
// security context
|
||||||
|
if (DEFAULT_ENABLED_KIBANA_USER.equals(securityContext.getUser())) {
|
||||||
return KibanaRole.INSTANCE;
|
return KibanaRole.INSTANCE;
|
||||||
}
|
}
|
||||||
return null;
|
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
|
// 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.
|
// 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.
|
// 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 KibanaRole.DESCRIPTOR;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -97,7 +100,7 @@ public class ReservedRolesStore implements RolesStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<RoleDescriptor> roleDescriptors() {
|
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,
|
return Arrays.asList(SuperuserRole.DESCRIPTOR, TransportClientRole.DESCRIPTOR, KibanaUserRole.DESCRIPTOR,
|
||||||
KibanaRole.DESCRIPTOR, MonitoringUserRole.DESCRIPTOR, RemoteMonitoringAgentRole.DESCRIPTOR,
|
KibanaRole.DESCRIPTOR, MonitoringUserRole.DESCRIPTOR, RemoteMonitoringAgentRole.DESCRIPTOR,
|
||||||
IngestAdminRole.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.PutUserRequest;
|
||||||
import org.elasticsearch.xpack.security.action.user.PutUserRequestBuilder;
|
import org.elasticsearch.xpack.security.action.user.PutUserRequestBuilder;
|
||||||
import org.elasticsearch.xpack.security.action.user.PutUserResponse;
|
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;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -163,6 +167,14 @@ public class SecurityClient {
|
||||||
client.execute(ChangePasswordAction.INSTANCE, request, listener);
|
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 */
|
/** Role Management */
|
||||||
|
|
||||||
public GetRolesRequestBuilder prepareGetRoles(String... names) {
|
public GetRolesRequestBuilder prepareGetRoles(String... names) {
|
||||||
|
|
|
@ -17,7 +17,6 @@ import org.elasticsearch.rest.RestRequest;
|
||||||
import org.elasticsearch.rest.RestResponse;
|
import org.elasticsearch.rest.RestResponse;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
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.action.role.DeleteRoleResponse;
|
||||||
import org.elasticsearch.xpack.security.client.SecurityClient;
|
import org.elasticsearch.xpack.security.client.SecurityClient;
|
||||||
|
|
||||||
|
@ -42,18 +41,16 @@ public class RestDeleteRoleAction extends BaseRestHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||||
DeleteRoleRequestBuilder requestBuilder = new SecurityClient(client).prepareDeleteRole(request.param("name"));
|
new SecurityClient(client).prepareDeleteRole(request.param("name"))
|
||||||
if (request.hasParam("refresh")) {
|
.setRefreshPolicy(request.param("refresh"))
|
||||||
requestBuilder.refresh(request.paramAsBoolean("refresh", true));
|
.execute(new RestBuilderListener<DeleteRoleResponse>(channel) {
|
||||||
}
|
@Override
|
||||||
requestBuilder.execute(new RestBuilderListener<DeleteRoleResponse>(channel) {
|
public RestResponse buildResponse(DeleteRoleResponse response, XContentBuilder builder) throws Exception {
|
||||||
@Override
|
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||||
public RestResponse buildResponse(DeleteRoleResponse response, XContentBuilder builder) throws Exception {
|
builder.startObject()
|
||||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
.field("found", response.found())
|
||||||
builder.startObject()
|
.endObject());
|
||||||
.field("found", response.found())
|
}
|
||||||
.endObject());
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class RestChangePasswordAction extends BaseRestHandler {
|
||||||
final User user = securityContext.getUser();
|
final User user = securityContext.getUser();
|
||||||
String username = request.param("username");
|
String username = request.param("username");
|
||||||
if (username == null) {
|
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())
|
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.RestResponse;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
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.action.user.DeleteUserResponse;
|
||||||
import org.elasticsearch.xpack.security.client.SecurityClient;
|
import org.elasticsearch.xpack.security.client.SecurityClient;
|
||||||
|
|
||||||
|
@ -42,20 +41,16 @@ public class RestDeleteUserAction extends BaseRestHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||||
String username = request.param("username");
|
new SecurityClient(client).prepareDeleteUser(request.param("username"))
|
||||||
|
.setRefreshPolicy(request.param("refresh"))
|
||||||
DeleteUserRequestBuilder requestBuilder = new SecurityClient(client).prepareDeleteUser(username);
|
.execute(new RestBuilderListener<DeleteUserResponse>(channel) {
|
||||||
if (request.hasParam("refresh")) {
|
@Override
|
||||||
requestBuilder.refresh(request.paramAsBoolean("refresh", true));
|
public RestResponse buildResponse(DeleteUserResponse response, XContentBuilder builder) throws Exception {
|
||||||
}
|
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||||
requestBuilder.execute(new RestBuilderListener<DeleteUserResponse>(channel) {
|
builder.startObject()
|
||||||
@Override
|
.field("found", response.found())
|
||||||
public RestResponse buildResponse(DeleteUserResponse response, XContentBuilder builder) throws Exception {
|
.endObject());
|
||||||
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
|
@Override
|
||||||
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
public void handleRequest(RestRequest request, final RestChannel channel, NodeClient client) throws Exception {
|
||||||
PutUserRequestBuilder requestBuilder = new SecurityClient(client).preparePutUser(request.param("username"), request.content());
|
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) {
|
requestBuilder.execute(new RestBuilderListener<PutUserResponse>(channel) {
|
||||||
@Override
|
@Override
|
||||||
public RestResponse buildResponse(PutUserResponse putUserResponse, XContentBuilder builder) throws Exception {
|
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) {
|
public static void verifyNoReservedMetadata(Map<String, Object> metadata) {
|
||||||
for (String key : metadata.keySet()) {
|
for (String key : metadata.keySet()) {
|
||||||
if (key.startsWith(RESERVED_PREFIX)) {
|
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;
|
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.authc.esnative.ReservedRealm;
|
||||||
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
||||||
|
|
||||||
|
@ -21,14 +22,21 @@ public final class Validation {
|
||||||
|
|
||||||
private static final int MIN_PASSWD_LENGTH = 6;
|
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) {
|
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. " +
|
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 " +
|
"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 " +
|
"characters can be letters, underscores (`_`), digits (`0-9`) or any of the following " +
|
||||||
"symbols `@`, `-`, `.` or `$`");
|
"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 new Error("Username [" + username + "] is reserved and may not be used.");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -9,22 +9,17 @@ import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Setting.Property;
|
import org.elasticsearch.common.settings.Setting.Property;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.settings.SettingsModule;
|
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||||
import org.elasticsearch.xpack.security.user.User.ReservedUser;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.security.Security.setting;
|
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
|
* The user object for the anonymous user.
|
||||||
* 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).
|
|
||||||
*/
|
*/
|
||||||
public class AnonymousUser extends ReservedUser {
|
public class AnonymousUser extends User {
|
||||||
|
|
||||||
public static final String DEFAULT_ANONYMOUS_USERNAME = "_anonymous";
|
public static final String DEFAULT_ANONYMOUS_USERNAME = "_anonymous";
|
||||||
public static final Setting<String> USERNAME_SETTING =
|
public static final Setting<String> USERNAME_SETTING =
|
||||||
|
@ -32,57 +27,18 @@ public class AnonymousUser extends ReservedUser {
|
||||||
public static final Setting<List<String>> ROLES_SETTING =
|
public static final Setting<List<String>> ROLES_SETTING =
|
||||||
Setting.listSetting(setting("authc.anonymous.roles"), Collections.emptyList(), s -> s, Property.NodeScope);
|
Setting.listSetting(setting("authc.anonymous.roles"), Collections.emptyList(), s -> s, Property.NodeScope);
|
||||||
|
|
||||||
private static String username = DEFAULT_ANONYMOUS_USERNAME;
|
public AnonymousUser(Settings settings) {
|
||||||
private static String[] roles = null;
|
super(USERNAME_SETTING.get(settings), ROLES_SETTING.get(settings).toArray(Strings.EMPTY_ARRAY), null, null,
|
||||||
|
MetadataUtils.DEFAULT_RESERVED_METADATA, isAnonymousEnabled(settings));
|
||||||
public static final AnonymousUser INSTANCE = new AnonymousUser();
|
|
||||||
|
|
||||||
private AnonymousUser() {
|
|
||||||
super(DEFAULT_ANONYMOUS_USERNAME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static boolean isAnonymousEnabled(Settings settings) {
|
||||||
public String principal() {
|
return ROLES_SETTING.exists(settings) && ROLES_SETTING.get(settings).isEmpty() == false;
|
||||||
return username;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static boolean isAnonymousUsername(String username, Settings settings) {
|
||||||
public String[] roles() {
|
// this is possibly the same check but we should not let anything use the default name either
|
||||||
return roles;
|
return USERNAME_SETTING.get(settings).equals(username) || DEFAULT_ANONYMOUS_USERNAME.equals(username);
|
||||||
}
|
|
||||||
|
|
||||||
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 void addSettings(List<Setting<?>> settingsList) {
|
public static void addSettings(List<Setting<?>> settingsList) {
|
||||||
|
|
|
@ -6,37 +6,18 @@
|
||||||
package org.elasticsearch.xpack.security.user;
|
package org.elasticsearch.xpack.security.user;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
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.
|
* 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 NAME = "elastic";
|
||||||
public static final String ROLE_NAME = SuperuserRole.NAME;
|
public static final String ROLE_NAME = SuperuserRole.NAME;
|
||||||
public static final ElasticUser INSTANCE = new ElasticUser();
|
|
||||||
|
|
||||||
private ElasticUser() {
|
public ElasticUser(boolean enabled) {
|
||||||
super(NAME, ROLE_NAME);
|
super(NAME, new String[] { ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,32 +6,17 @@
|
||||||
package org.elasticsearch.xpack.security.user;
|
package org.elasticsearch.xpack.security.user;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.security.authz.permission.KibanaRole;
|
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 NAME = "kibana";
|
||||||
public static final String ROLE_NAME = KibanaRole.NAME;
|
public static final String ROLE_NAME = KibanaRole.NAME;
|
||||||
public static final KibanaUser INSTANCE = new KibanaUser();
|
|
||||||
|
|
||||||
KibanaUser() {
|
public KibanaUser(boolean enabled) {
|
||||||
super(NAME, ROLE_NAME);
|
super(NAME, new String[]{ ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled);
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
|
||||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,40 +30,41 @@ public class User implements ToXContent {
|
||||||
private final String[] roles;
|
private final String[] roles;
|
||||||
private final User runAs;
|
private final User runAs;
|
||||||
private final Map<String, Object> metadata;
|
private final Map<String, Object> metadata;
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
@Nullable private final String fullName;
|
@Nullable private final String fullName;
|
||||||
@Nullable private final String email;
|
@Nullable private final String email;
|
||||||
|
|
||||||
public User(String username, String... roles) {
|
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) {
|
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.username = username;
|
||||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.enabled = enabled;
|
||||||
this.runAs = null;
|
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.username = username;
|
||||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
this.email = email;
|
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";
|
assert (runAs == null || runAs.runAs() == null) : "the run_as user should not be a user that can run as";
|
||||||
if (runAs == SystemUser.INSTANCE) {
|
if (runAs == SystemUser.INSTANCE) {
|
||||||
throw new ElasticsearchSecurityException("invalid run_as user");
|
throw new ElasticsearchSecurityException("invalid run_as user");
|
||||||
}
|
}
|
||||||
this.runAs = runAs;
|
this.runAs = runAs;
|
||||||
verifyNoReservedMetadata(this.metadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,6 +105,13 @@ public class User implements ToXContent {
|
||||||
return email;
|
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
|
* @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
|
* functionality is not being used, then <code>null</code> will be
|
||||||
|
@ -133,7 +140,7 @@ public class User implements ToXContent {
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o instanceof User == false) return false;
|
||||||
|
|
||||||
User user = (User) o;
|
User user = (User) o;
|
||||||
|
|
||||||
|
@ -166,46 +173,28 @@ public class User implements ToXContent {
|
||||||
builder.field(Fields.FULL_NAME.getPreferredName(), fullName());
|
builder.field(Fields.FULL_NAME.getPreferredName(), fullName());
|
||||||
builder.field(Fields.EMAIL.getPreferredName(), email());
|
builder.field(Fields.EMAIL.getPreferredName(), email());
|
||||||
builder.field(Fields.METADATA.getPreferredName(), metadata());
|
builder.field(Fields.METADATA.getPreferredName(), metadata());
|
||||||
|
builder.field(Fields.ENABLED.getPreferredName(), enabled());
|
||||||
return builder.endObject();
|
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 {
|
public static User readFrom(StreamInput input) throws IOException {
|
||||||
if (input.readBoolean()) {
|
final boolean isInternalUser = input.readBoolean();
|
||||||
String name = input.readString();
|
final String username = input.readString();
|
||||||
if (SystemUser.is(name)) {
|
if (isInternalUser) {
|
||||||
|
if (SystemUser.is(username)) {
|
||||||
return SystemUser.INSTANCE;
|
return SystemUser.INSTANCE;
|
||||||
} else if (XPackUser.is(name)) {
|
} else if (XPackUser.is(username)) {
|
||||||
return XPackUser.INSTANCE;
|
return XPackUser.INSTANCE;
|
||||||
}
|
}
|
||||||
User user = ReservedRealm.getUser(name);
|
throw new IllegalStateException("user [" + username + "] is not an internal user");
|
||||||
if (user == null) {
|
|
||||||
throw new IllegalStateException("invalid reserved user");
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
String username = input.readString();
|
|
||||||
String[] roles = input.readStringArray();
|
String[] roles = input.readStringArray();
|
||||||
Map<String, Object> metadata = input.readMap();
|
Map<String, Object> metadata = input.readMap();
|
||||||
String fullName = input.readOptionalString();
|
String fullName = input.readOptionalString();
|
||||||
String email = input.readOptionalString();
|
String email = input.readOptionalString();
|
||||||
if (input.readBoolean()) {
|
boolean enabled = input.readBoolean();
|
||||||
String runAsUsername = input.readString();
|
User runAs = input.readBoolean() ? readFrom(input) : null;
|
||||||
String[] runAsRoles = input.readStringArray();
|
return new User(username, roles, fullName, email, metadata, enabled, runAs);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeTo(User user, StreamOutput output) throws IOException {
|
public static void writeTo(User user, StreamOutput output) throws IOException {
|
||||||
|
@ -215,9 +204,6 @@ public class User implements ToXContent {
|
||||||
} else if (XPackUser.is(user)) {
|
} else if (XPackUser.is(user)) {
|
||||||
output.writeBoolean(true);
|
output.writeBoolean(true);
|
||||||
output.writeString(XPackUser.NAME);
|
output.writeString(XPackUser.NAME);
|
||||||
} else if (ReservedRealm.isReserved(user.principal())) {
|
|
||||||
output.writeBoolean(true);
|
|
||||||
output.writeString(user.principal());
|
|
||||||
} else {
|
} else {
|
||||||
output.writeBoolean(false);
|
output.writeBoolean(false);
|
||||||
output.writeString(user.username);
|
output.writeString(user.username);
|
||||||
|
@ -225,26 +211,16 @@ public class User implements ToXContent {
|
||||||
output.writeMap(user.metadata);
|
output.writeMap(user.metadata);
|
||||||
output.writeOptionalString(user.fullName);
|
output.writeOptionalString(user.fullName);
|
||||||
output.writeOptionalString(user.email);
|
output.writeOptionalString(user.email);
|
||||||
|
output.writeBoolean(user.enabled);
|
||||||
if (user.runAs == null) {
|
if (user.runAs == null) {
|
||||||
output.writeBoolean(false);
|
output.writeBoolean(false);
|
||||||
} else {
|
} else {
|
||||||
output.writeBoolean(true);
|
output.writeBoolean(true);
|
||||||
output.writeString(user.runAs.username);
|
writeTo(user.runAs, output);
|
||||||
output.writeStringArray(user.runAs.roles);
|
|
||||||
output.writeMap(user.runAs.metadata);
|
|
||||||
output.writeOptionalString(user.runAs.fullName);
|
|
||||||
output.writeOptionalString(user.runAs.email);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract static class ReservedUser extends User {
|
|
||||||
|
|
||||||
ReservedUser(String username, String... roles) {
|
|
||||||
super(username, roles, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Fields {
|
public interface Fields {
|
||||||
ParseField USERNAME = new ParseField("username");
|
ParseField USERNAME = new ParseField("username");
|
||||||
ParseField PASSWORD = new ParseField("password");
|
ParseField PASSWORD = new ParseField("password");
|
||||||
|
@ -253,5 +229,6 @@ public class User implements ToXContent {
|
||||||
ParseField FULL_NAME = new ParseField("full_name");
|
ParseField FULL_NAME = new ParseField("full_name");
|
||||||
ParseField EMAIL = new ParseField("email");
|
ParseField EMAIL = new ParseField("email");
|
||||||
ParseField METADATA = new ParseField("metadata");
|
ParseField METADATA = new ParseField("metadata");
|
||||||
|
ParseField ENABLED = new ParseField("enabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
package org.elasticsearch.xpack.security.user;
|
package org.elasticsearch.xpack.security.user;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.security.authz.permission.SuperuserRole;
|
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.
|
* 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 String ROLE_NAME = SuperuserRole.NAME;
|
||||||
public static final XPackUser INSTANCE = new XPackUser();
|
public static final XPackUser INSTANCE = new XPackUser();
|
||||||
|
|
||||||
XPackUser() {
|
private XPackUser() {
|
||||||
super(NAME, ROLE_NAME);
|
super(NAME, ROLE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"type" : "object",
|
"type" : "object",
|
||||||
"dynamic" : true
|
"dynamic" : true
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -109,6 +112,9 @@
|
||||||
"type" : "keyword",
|
"type" : "keyword",
|
||||||
"index" : false,
|
"index" : false,
|
||||||
"doc_values" : 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.transport.filter.IPFilter;
|
||||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||||
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
|
@ -56,11 +55,6 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
cryptoService = mock(CryptoService.class);
|
cryptoService = mock(CryptoService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAvailable() throws Exception {
|
public void testAvailable() throws Exception {
|
||||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
||||||
ipFilter, auditTrail, cryptoService);
|
ipFilter, auditTrail, cryptoService);
|
||||||
|
@ -150,7 +144,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
|
|
||||||
final boolean anonymousEnabled = randomBoolean();
|
final boolean anonymousEnabled = randomBoolean();
|
||||||
if (anonymousEnabled) {
|
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,
|
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.permission.KibanaRole;
|
||||||
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
|
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
|
||||||
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
|
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.xpack.security.user.KibanaUser;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
@ -56,7 +57,9 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
||||||
|
|
||||||
final boolean isKibanaUser = randomBoolean();
|
final boolean isKibanaUser = randomBoolean();
|
||||||
if (isKibanaUser) {
|
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 int size = randomIntBetween(1, ReservedRolesStore.names().size());
|
||||||
final List<String> names = randomSubsetOf(size, ReservedRolesStore.names());
|
final List<String> names = randomSubsetOf(size, ReservedRolesStore.names());
|
||||||
|
@ -116,7 +119,9 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
||||||
|
|
||||||
final boolean isKibanaUser = randomBoolean();
|
final boolean isKibanaUser = randomBoolean();
|
||||||
if (isKibanaUser) {
|
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();
|
GetRolesRequest request = new GetRolesRequest();
|
||||||
|
@ -199,9 +204,10 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKibanaUser) {
|
if (isKibanaUser) {
|
||||||
when(context.getUser()).thenReturn(KibanaUser.INSTANCE);
|
when(context.getUser()).thenReturn(new KibanaUser(true));
|
||||||
} else {
|
} else {
|
||||||
expectedNames.remove(KibanaRole.NAME);
|
expectedNames.remove(KibanaRole.NAME);
|
||||||
|
when(context.getUser()).thenReturn(new ElasticUser(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
GetRolesRequest request = new GetRolesRequest();
|
GetRolesRequest request = new GetRolesRequest();
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.xpack.security.user.User;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@ -31,9 +32,9 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class TransportAuthenticateActionTests extends ESTestCase {
|
public class TransportAuthenticateActionTests extends ESTestCase {
|
||||||
|
|
||||||
public void testSystemUser() {
|
public void testInternalUser() {
|
||||||
SecurityContext securityContext = mock(SecurityContext.class);
|
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),
|
TransportAuthenticateAction action = new TransportAuthenticateAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class),
|
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class),
|
||||||
securityContext);
|
securityContext);
|
||||||
|
@ -83,7 +84,7 @@ public class TransportAuthenticateActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testValidUser() {
|
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);
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
when(securityContext.getUser()).thenReturn(user);
|
when(securityContext.getUser()).thenReturn(user);
|
||||||
TransportAuthenticateAction action = new TransportAuthenticateAction(Settings.EMPTY, mock(ThreadPool.class),
|
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.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.junit.After;
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
@ -43,20 +43,15 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
|
||||||
public class TransportChangePasswordActionTests extends ESTestCase {
|
public class TransportChangePasswordActionTests extends ESTestCase {
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAnonymousUser() {
|
public void testAnonymousUser() {
|
||||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||||
AnonymousUser.initialize(settings);
|
AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
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);
|
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||||
|
|
||||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||||
request.username(AnonymousUser.INSTANCE.principal());
|
request.username(anonymousUser.principal());
|
||||||
request.passwordHash(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray())));
|
request.passwordHash(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray())));
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
|
@ -79,13 +74,13 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
||||||
verifyZeroInteractions(usersStore);
|
verifyZeroInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSystemUser() {
|
public void testInternalUsers() {
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class),
|
TransportChangePasswordAction action = new TransportChangePasswordAction(Settings.EMPTY, mock(ThreadPool.class),
|
||||||
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
mock(TransportService.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore);
|
||||||
|
|
||||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
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())));
|
request.passwordHash(Hasher.BCRYPT.hash(new SecuredString("changeme".toCharArray())));
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
|
@ -109,7 +104,7 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testValidUser() {
|
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);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||||
request.username(user.principal());
|
request.username(user.principal());
|
||||||
|
@ -147,7 +142,7 @@ public class TransportChangePasswordActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testException() {
|
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);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||||
request.username(user.principal());
|
request.username(user.principal());
|
||||||
|
|
|
@ -11,14 +11,15 @@ import org.elasticsearch.action.support.ActionFilters;
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
|
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.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.SystemUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.junit.After;
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
@ -40,19 +41,13 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
|
||||||
public class TransportDeleteUserActionTests extends ESTestCase {
|
public class TransportDeleteUserActionTests extends ESTestCase {
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAnonymousUser() {
|
public void testAnonymousUser() {
|
||||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||||
AnonymousUser.initialize(settings);
|
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
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));
|
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<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -74,12 +69,12 @@ public class TransportDeleteUserActionTests extends ESTestCase {
|
||||||
verifyZeroInteractions(usersStore);
|
verifyZeroInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSystemUser() {
|
public void testInternalUser() {
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
TransportDeleteUserAction action = new TransportDeleteUserAction(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));
|
||||||
|
|
||||||
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<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<DeleteUserResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -102,7 +97,7 @@ public class TransportDeleteUserActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReservedUser() {
|
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);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportDeleteUserAction action = new TransportDeleteUserAction(Settings.EMPTY, mock(ThreadPool.class),
|
TransportDeleteUserAction action = new TransportDeleteUserAction(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));
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.ValidationException;
|
import org.elasticsearch.common.ValidationException;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
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.NativeUsersStore;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
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.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.junit.After;
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class TransportGetUsersActionTests extends ESTestCase {
|
public class TransportGetUsersActionTests extends ESTestCase {
|
||||||
|
|
||||||
private boolean anonymousEnabled;
|
private boolean anonymousEnabled;
|
||||||
|
private Settings settings;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void maybeEnableAnonymous() {
|
public void maybeEnableAnonymous() {
|
||||||
anonymousEnabled = randomBoolean();
|
anonymousEnabled = randomBoolean();
|
||||||
if (anonymousEnabled) {
|
if (anonymousEnabled) {
|
||||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
||||||
AnonymousUser.initialize(settings);
|
} else {
|
||||||
|
settings = Settings.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAnonymousUser() {
|
public void testAnonymousUser() {
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
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),
|
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();
|
GetUsersRequest request = new GetUsersRequest();
|
||||||
request.usernames(AnonymousUser.INSTANCE.principal());
|
request.usernames(anonymousUser.principal());
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -93,20 +97,21 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
||||||
assertThat(responseRef.get(), is(notNullValue()));
|
assertThat(responseRef.get(), is(notNullValue()));
|
||||||
final User[] users = responseRef.get().users();
|
final User[] users = responseRef.get().users();
|
||||||
if (anonymousEnabled) {
|
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 {
|
} else {
|
||||||
assertThat("expected an empty array but got: " + Arrays.toString(users), users, emptyArray());
|
assertThat("expected an empty array but got: " + Arrays.toString(users), users, emptyArray());
|
||||||
}
|
}
|
||||||
verifyZeroInteractions(usersStore);
|
verifyZeroInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSystemUser() {
|
public void testInternalUser() {
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.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();
|
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<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<GetUsersResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -129,12 +134,16 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReservedUsersOnly() {
|
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);
|
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),
|
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();
|
GetUsersRequest request = new GetUsersRequest();
|
||||||
request.usernames(names.toArray(new String[names.size()]));
|
request.usernames(names.toArray(new String[names.size()]));
|
||||||
|
@ -156,15 +165,18 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
||||||
assertThat(throwableRef.get(), is(nullValue()));
|
assertThat(throwableRef.get(), is(nullValue()));
|
||||||
assertThat(responseRef.get(), is(notNullValue()));
|
assertThat(responseRef.get(), is(notNullValue()));
|
||||||
assertThat(responseRef.get().users(), arrayContaining(reservedUsers.toArray(new User[reservedUsers.size()])));
|
assertThat(responseRef.get().users(), arrayContaining(reservedUsers.toArray(new User[reservedUsers.size()])));
|
||||||
verifyZeroInteractions(usersStore);
|
verify(usersStore, times(1 + names.size())).started();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetAllUsers() {
|
public void testGetAllUsers() {
|
||||||
final List<User> storeUsers = randomFrom(Collections.<User>emptyList(), Collections.singletonList(new User("joe")),
|
final List<User> storeUsers = randomFrom(Collections.<User>emptyList(), Collections.singletonList(new User("joe")),
|
||||||
Arrays.asList(new User("jane"), new User("fred")), randomUsers());
|
Arrays.asList(new User("jane"), new User("fred")), randomUsers());
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
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),
|
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();
|
GetUsersRequest request = new GetUsersRequest();
|
||||||
doAnswer(new Answer() {
|
doAnswer(new Answer() {
|
||||||
|
@ -192,7 +204,7 @@ public class TransportGetUsersActionTests extends ESTestCase {
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<User> expectedList = new ArrayList<>();
|
final List<User> expectedList = new ArrayList<>();
|
||||||
expectedList.addAll(ReservedRealm.users());
|
expectedList.addAll(reservedRealm.users());
|
||||||
expectedList.addAll(storeUsers);
|
expectedList.addAll(storeUsers);
|
||||||
|
|
||||||
assertThat(throwableRef.get(), is(nullValue()));
|
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);
|
final String[] storeUsernames = storeUsers.stream().map(User::principal).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY);
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.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();
|
GetUsersRequest request = new GetUsersRequest();
|
||||||
request.usernames(storeUsernames);
|
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);
|
final String[] storeUsernames = storeUsers.stream().map(User::principal).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY);
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
||||||
TransportGetUsersAction action = new TransportGetUsersAction(Settings.EMPTY, mock(ThreadPool.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();
|
GetUsersRequest request = new GetUsersRequest();
|
||||||
request.usernames(storeUsernames);
|
request.usernames(storeUsernames);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.action.support.ActionFilters;
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.common.ValidationException;
|
import org.elasticsearch.common.ValidationException;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
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.NativeUsersStore;
|
||||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
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.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.junit.After;
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.stubbing.Answer;
|
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.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class TransportPutUserActionTests extends ESTestCase {
|
public class TransportPutUserActionTests extends ESTestCase {
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAnonymousUser() {
|
public void testAnonymousUser() {
|
||||||
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "superuser").build();
|
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);
|
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));
|
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||||
|
|
||||||
PutUserRequest request = new PutUserRequest();
|
PutUserRequest request = new PutUserRequest();
|
||||||
request.username(AnonymousUser.INSTANCE.principal());
|
request.username(anonymousUser.principal());
|
||||||
|
|
||||||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
final AtomicReference<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<PutUserResponse> responseRef = 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));
|
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), usersStore, mock(TransportService.class));
|
||||||
|
|
||||||
PutUserRequest request = new PutUserRequest();
|
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<Throwable> throwableRef = new AtomicReference<>();
|
||||||
final AtomicReference<PutUserResponse> responseRef = new AtomicReference<>();
|
final AtomicReference<PutUserResponse> responseRef = new AtomicReference<>();
|
||||||
|
@ -107,8 +104,11 @@ public class TransportPutUserActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReservedUser() {
|
public void testReservedUser() {
|
||||||
final User reserved = randomFrom(ReservedRealm.users().toArray(new User[0]));
|
|
||||||
NativeUsersStore usersStore = mock(NativeUsersStore.class);
|
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),
|
TransportPutUserAction action = new TransportPutUserAction(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));
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ public class TransportPutUserActionTests extends ESTestCase {
|
||||||
assertThat(responseRef.get(), is(nullValue()));
|
assertThat(responseRef.get(), is(nullValue()));
|
||||||
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
assertThat(throwableRef.get(), instanceOf(IllegalArgumentException.class));
|
||||||
assertThat(throwableRef.get().getMessage(), containsString("is reserved and only the password"));
|
assertThat(throwableRef.get().getMessage(), containsString("is reserved and only the password"));
|
||||||
verifyZeroInteractions(usersStore);
|
verify(usersStore).started();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testValidUser() {
|
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.AnonymousUser;
|
||||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException;
|
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 {
|
public class AuthenticationServiceTests extends ESTestCase {
|
||||||
|
|
||||||
AuthenticationService service;
|
private AuthenticationService service;
|
||||||
TransportMessage message;
|
private TransportMessage message;
|
||||||
RestRequest restRequest;
|
private RestRequest restRequest;
|
||||||
Realms realms;
|
private Realms realms;
|
||||||
Realm firstRealm;
|
private Realm firstRealm;
|
||||||
Realm secondRealm;
|
private Realm secondRealm;
|
||||||
AuditTrailService auditTrail;
|
private AuditTrailService auditTrail;
|
||||||
AuthenticationToken token;
|
private AuthenticationToken token;
|
||||||
CryptoService cryptoService;
|
private CryptoService cryptoService;
|
||||||
ThreadPool threadPool;
|
private ThreadPool threadPool;
|
||||||
ThreadContext threadContext;
|
private ThreadContext threadContext;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
@ -109,12 +108,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||||
when(cryptoService.sign(any(String.class))).thenReturn("_signed_auth");
|
when(cryptoService.sign(any(String.class))).thenReturn("_signed_auth");
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings));
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -268,6 +262,33 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
assertThreadContextContainsAuthentication(result);
|
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 {
|
public void testAuthenticateTransportSuccess() throws Exception {
|
||||||
User user = new User("username", "r1", "r2");
|
User user = new User("username", "r1", "r2");
|
||||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||||
|
@ -308,7 +329,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
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.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||||
|
@ -317,12 +338,11 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
verifyZeroInteractions(firstRealm);
|
verifyZeroInteractions(firstRealm);
|
||||||
reset(firstRealm);
|
reset(firstRealm);
|
||||||
|
|
||||||
|
|
||||||
// checking authentication from the user header
|
// checking authentication from the user header
|
||||||
threadContext1 = new ThreadContext(Settings.EMPTY);
|
threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
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));
|
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||||
when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode());
|
when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode());
|
||||||
|
|
||||||
|
@ -334,7 +354,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
|
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
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);
|
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result.getUser(), equalTo(user1));
|
assertThat(result.getUser(), equalTo(user1));
|
||||||
|
@ -344,7 +364,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception {
|
public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception {
|
||||||
Settings settings = Settings.builder().put(AuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
|
Settings settings = Settings.builder().put(AuthenticationService.SIGN_USER_HEADER.getKey(), false).build();
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||||
|
|
||||||
User user1 = new User("username", "r1", "r2");
|
User user1 = new User("username", "r1", "r2");
|
||||||
when(firstRealm.supports(token)).thenReturn(true);
|
when(firstRealm.supports(token)).thenReturn(true);
|
||||||
|
@ -361,7 +381,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY);
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||||
service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService,
|
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.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY));
|
||||||
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY));
|
||||||
Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE);
|
||||||
|
@ -381,7 +401,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
|
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
when(threadPool.getThreadContext()).thenReturn(threadContext1);
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
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);
|
Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result.getUser(), equalTo(user1));
|
assertThat(result.getUser(), equalTo(user1));
|
||||||
|
@ -442,15 +462,15 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
builder.put(AnonymousUser.USERNAME_SETTING.getKey(), username);
|
builder.put(AnonymousUser.USERNAME_SETTING.getKey(), username);
|
||||||
}
|
}
|
||||||
Settings settings = builder.build();
|
Settings settings = builder.build();
|
||||||
AnonymousUser.initialize(settings);
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
|
service = new AuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(),
|
||||||
threadPool);
|
threadPool, anonymousUser);
|
||||||
RestRequest request = new FakeRestRequest();
|
RestRequest request = new FakeRestRequest();
|
||||||
|
|
||||||
Authentication result = service.authenticate(request);
|
Authentication result = service.authenticate(request);
|
||||||
|
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result.getUser(), sameInstance((Object) AnonymousUser.INSTANCE));
|
assertThat(result.getUser(), sameInstance((Object) anonymousUser));
|
||||||
assertThreadContextContainsAuthentication(result);
|
assertThreadContextContainsAuthentication(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,14 +478,14 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
AnonymousUser.initialize(settings);
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||||
InternalMessage message = new InternalMessage();
|
InternalMessage message = new InternalMessage();
|
||||||
|
|
||||||
Authentication result = service.authenticate("_action", message, null);
|
Authentication result = service.authenticate("_action", message, null);
|
||||||
assertThat(result, notNullValue());
|
assertThat(result, notNullValue());
|
||||||
assertThat(result.getUser(), sameInstance(AnonymousUser.INSTANCE));
|
assertThat(result.getUser(), sameInstance(anonymousUser));
|
||||||
assertThreadContextContainsAuthentication(result);
|
assertThreadContextContainsAuthentication(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,9 +493,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
AnonymousUser.initialize(settings);
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
service = new AuthenticationService(settings, realms, auditTrail, cryptoService,
|
||||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser);
|
||||||
|
|
||||||
InternalMessage message = new InternalMessage();
|
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 {
|
private static class InternalMessage extends TransportMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -599,4 +599,27 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
|
||||||
assertThat(usage.get("fls"), is(fls));
|
assertThat(usage.get("fls"), is(fls));
|
||||||
assertThat(usage.get("dls"), is(dls));
|
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();
|
.get();
|
||||||
assertThat(healthResponse.getClusterName(), is(cluster().getClusterName()));
|
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.ElasticsearchSecurityException;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
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.Hasher;
|
||||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
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.containsString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
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.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -41,20 +39,19 @@ public class ReservedRealmTests extends ESTestCase {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupMocks() {
|
public void setupMocks() {
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
usersStore = mock(NativeUsersStore.class);
|
usersStore = mock(NativeUsersStore.class);
|
||||||
when(usersStore.started()).thenReturn(true);
|
when(usersStore.started()).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testUserStoreNotStarted() {
|
public void testUserStoreNotStarted() {
|
||||||
when(usersStore.started()).thenReturn(false);
|
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);
|
final String principal = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
|
||||||
|
|
||||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||||
() -> reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD)));
|
() -> reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD)));
|
||||||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||||
verify(usersStore).addListener(any(ChangeListener.class));
|
|
||||||
verify(usersStore).started();
|
verify(usersStore).started();
|
||||||
verifyNoMoreInteractions(usersStore);
|
verifyNoMoreInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
@ -64,28 +61,29 @@ public class ReservedRealmTests extends ESTestCase {
|
||||||
if (securityIndexExists) {
|
if (securityIndexExists) {
|
||||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||||
}
|
}
|
||||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
final ReservedRealm reservedRealm =
|
||||||
final User expected = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
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 String principal = expected.principal();
|
||||||
|
|
||||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
|
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
|
||||||
assertThat(authenticated, sameInstance(expected));
|
assertEquals(expected, authenticated);
|
||||||
verify(usersStore).addListener(any(ChangeListener.class));
|
|
||||||
verify(usersStore).started();
|
verify(usersStore).started();
|
||||||
verify(usersStore).securityIndexExists();
|
verify(usersStore).securityIndexExists();
|
||||||
if (securityIndexExists) {
|
if (securityIndexExists) {
|
||||||
verify(usersStore).reservedUserPassword(principal);
|
verify(usersStore).getReservedUserInfo(principal);
|
||||||
}
|
}
|
||||||
verifyNoMoreInteractions(usersStore);
|
verifyNoMoreInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAuthenticationWithStoredPassword() throws Throwable {
|
public void testAuthenticationWithStoredPassword() throws Throwable {
|
||||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
final ReservedRealm reservedRealm =
|
||||||
final User expectedUser = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
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 String principal = expectedUser.principal();
|
||||||
final SecuredString newPassword = new SecuredString("foobar".toCharArray());
|
final SecuredString newPassword = new SecuredString("foobar".toCharArray());
|
||||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
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
|
// test default password
|
||||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||||
|
@ -93,52 +91,75 @@ public class ReservedRealmTests extends ESTestCase {
|
||||||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||||
|
|
||||||
// the realm assumes it owns the hashed password so it fills it with 0's
|
// 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
|
// test new password
|
||||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword));
|
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword));
|
||||||
assertThat(authenticated, sameInstance(expectedUser));
|
assertEquals(expectedUser, authenticated);
|
||||||
verify(usersStore).addListener(any(ChangeListener.class));
|
|
||||||
verify(usersStore, times(2)).started();
|
verify(usersStore, times(2)).started();
|
||||||
verify(usersStore, times(2)).securityIndexExists();
|
verify(usersStore, times(2)).securityIndexExists();
|
||||||
verify(usersStore, times(2)).reservedUserPassword(principal);
|
verify(usersStore, times(2)).getReservedUserInfo(principal);
|
||||||
verifyNoMoreInteractions(usersStore);
|
verifyNoMoreInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLookup() {
|
public void testLookup() throws Exception {
|
||||||
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore);
|
final ReservedRealm reservedRealm =
|
||||||
final User expectedUser = randomFrom((User) ElasticUser.INSTANCE, KibanaUser.INSTANCE);
|
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 String principal = expectedUser.principal();
|
||||||
|
|
||||||
final User user = reservedRealm.doLookupUser(principal);
|
final User user = reservedRealm.doLookupUser(principal);
|
||||||
assertThat(user, sameInstance(expectedUser));
|
assertEquals(expectedUser, user);
|
||||||
verify(usersStore).addListener(any(ChangeListener.class));
|
verify(usersStore).started();
|
||||||
verifyNoMoreInteractions(usersStore);
|
verify(usersStore).securityIndexExists();
|
||||||
|
|
||||||
final User doesntExist = reservedRealm.doLookupUser("foobar");
|
final User doesntExist = reservedRealm.doLookupUser("foobar");
|
||||||
assertThat(doesntExist, nullValue());
|
assertThat(doesntExist, nullValue());
|
||||||
|
verifyNoMoreInteractions(usersStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testHelperMethods() {
|
public void testLookupThrows() throws Exception {
|
||||||
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 String principal = expectedUser.principal();
|
||||||
assertThat(ReservedRealm.isReserved(principal), is(true));
|
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||||
assertThat(ReservedRealm.getUser(principal), sameInstance(expectedUser));
|
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));
|
final String notExpected = randomFrom("foobar", "", randomAsciiOfLengthBetween(1, 30));
|
||||||
assertThat(ReservedRealm.isReserved(notExpected), is(false));
|
assertThat(ReservedRealm.isReserved(notExpected, Settings.EMPTY), is(false));
|
||||||
assertThat(ReservedRealm.getUser(notExpected), nullValue());
|
}
|
||||||
|
|
||||||
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() {
|
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
|
// maybe cache a successful auth
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
User user = reservedRealm.authenticate(
|
User user = reservedRealm.authenticate(
|
||||||
new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("changeme".toCharArray())));
|
new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("changeme".toCharArray())));
|
||||||
assertThat(user, sameInstance(ElasticUser.INSTANCE));
|
assertEquals(new ElasticUser(true), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.authc.file;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Level;
|
import org.apache.logging.log4j.Level;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.core.LogEvent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -154,7 +153,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
|
|
||||||
public void testParseFile() throws Exception {
|
public void testParseFile() throws Exception {
|
||||||
Path path = getDataPath("users");
|
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, notNullValue());
|
||||||
assertThat(users.size(), is(6));
|
assertThat(users.size(), is(6));
|
||||||
assertThat(users.get("bcrypt"), notNullValue());
|
assertThat(users.get("bcrypt"), notNullValue());
|
||||||
|
@ -174,7 +173,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
public void testParseFile_Empty() throws Exception {
|
public void testParseFile_Empty() throws Exception {
|
||||||
Path empty = createTempFile();
|
Path empty = createTempFile();
|
||||||
Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG);
|
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));
|
assertThat(users.isEmpty(), is(true));
|
||||||
List<String> events = CapturingLogger.output(logger.getName(), Level.DEBUG);
|
List<String> events = CapturingLogger.output(logger.getName(), Level.DEBUG);
|
||||||
assertThat(events.size(), is(1));
|
assertThat(events.size(), is(1));
|
||||||
|
@ -184,7 +183,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
public void testParseFile_WhenFileDoesNotExist() throws Exception {
|
public void testParseFile_WhenFileDoesNotExist() throws Exception {
|
||||||
Path file = createTempDir().resolve(randomAsciiOfLength(10));
|
Path file = createTempDir().resolve(randomAsciiOfLength(10));
|
||||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
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, notNullValue());
|
||||||
assertThat(users.isEmpty(), is(true));
|
assertThat(users.isEmpty(), is(true));
|
||||||
}
|
}
|
||||||
|
@ -195,7 +194,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16);
|
Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16);
|
||||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
||||||
try {
|
try {
|
||||||
FileUserPasswdStore.parseFile(file, logger);
|
FileUserPasswdStore.parseFile(file, logger, Settings.EMPTY);
|
||||||
fail("expected a parse failure");
|
fail("expected a parse failure");
|
||||||
} catch (IllegalStateException se) {
|
} catch (IllegalStateException se) {
|
||||||
this.logger.info("expected", se);
|
this.logger.info("expected", se);
|
||||||
|
@ -205,7 +204,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
public void testParseFile_InvalidLineDoesNotResultInLoggerNPE() throws Exception {
|
public void testParseFile_InvalidLineDoesNotResultInLoggerNPE() throws Exception {
|
||||||
Path file = createTempFile();
|
Path file = createTempFile();
|
||||||
Files.write(file, Arrays.asList("NotValidUsername=Password", "user:pass"), StandardCharsets.UTF_8);
|
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, notNullValue());
|
||||||
assertThat(users.keySet(), hasSize(1));
|
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
|
// 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);
|
Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16);
|
||||||
Logger logger = CapturingLogger.newCapturingLogger(Level.INFO);
|
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, notNullValue());
|
||||||
assertThat(users.isEmpty(), is(true));
|
assertThat(users.isEmpty(), is(true));
|
||||||
List<String> events = CapturingLogger.output(logger.getName(), Level.ERROR);
|
List<String> events = CapturingLogger.output(logger.getName(), Level.ERROR);
|
||||||
|
@ -226,7 +225,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
||||||
public void testParseFileWithLineWithEmptyPasswordAndWhitespace() throws Exception {
|
public void testParseFileWithLineWithEmptyPasswordAndWhitespace() throws Exception {
|
||||||
Path file = createTempFile();
|
Path file = createTempFile();
|
||||||
Files.write(file, Collections.singletonList("user: "), StandardCharsets.UTF_8);
|
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, notNullValue());
|
||||||
assertThat(users.keySet(), is(empty()));
|
assertThat(users.keySet(), is(empty()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ public class UsersToolTests extends CommandTestCase {
|
||||||
|
|
||||||
public void testParseInvalidUsername() throws Exception {
|
public void testParseInvalidUsername() throws Exception {
|
||||||
UserException e = expectThrows(UserException.class, () -> {
|
UserException e = expectThrows(UserException.class, () -> {
|
||||||
UsersTool.parseUsername(Collections.singletonList("$34dkl"));
|
UsersTool.parseUsername(Collections.singletonList("$34dkl"), Settings.EMPTY);
|
||||||
});
|
});
|
||||||
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
|
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
|
||||||
assertTrue(e.getMessage(), e.getMessage().contains("Invalid username"));
|
assertTrue(e.getMessage(), e.getMessage().contains("Invalid username"));
|
||||||
|
@ -183,7 +183,7 @@ public class UsersToolTests extends CommandTestCase {
|
||||||
|
|
||||||
public void testParseUsernameMissing() throws Exception {
|
public void testParseUsernameMissing() throws Exception {
|
||||||
UserException e = expectThrows(UserException.class, () -> {
|
UserException e = expectThrows(UserException.class, () -> {
|
||||||
UsersTool.parseUsername(Collections.emptyList());
|
UsersTool.parseUsername(Collections.emptyList(), Settings.EMPTY);
|
||||||
});
|
});
|
||||||
assertEquals(ExitCodes.USAGE, e.exitCode);
|
assertEquals(ExitCodes.USAGE, e.exitCode);
|
||||||
assertTrue(e.getMessage(), e.getMessage().contains("Missing username argument"));
|
assertTrue(e.getMessage(), e.getMessage().contains("Missing username argument"));
|
||||||
|
@ -191,7 +191,7 @@ public class UsersToolTests extends CommandTestCase {
|
||||||
|
|
||||||
public void testParseUsernameExtraArgs() throws Exception {
|
public void testParseUsernameExtraArgs() throws Exception {
|
||||||
UserException e = expectThrows(UserException.class, () -> {
|
UserException e = expectThrows(UserException.class, () -> {
|
||||||
UsersTool.parseUsername(Arrays.asList("username", "extra"));
|
UsersTool.parseUsername(Arrays.asList("username", "extra"), Settings.EMPTY);
|
||||||
});
|
});
|
||||||
assertEquals(ExitCodes.USAGE, e.exitCode);
|
assertEquals(ExitCodes.USAGE, e.exitCode);
|
||||||
assertTrue(e.getMessage(), e.getMessage().contains("Expected a single username argument"));
|
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.SystemUser;
|
||||||
import org.elasticsearch.xpack.security.user.User;
|
import org.elasticsearch.xpack.security.user.User;
|
||||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -109,12 +108,7 @@ public class AuthorizationServiceTests extends ESTestCase {
|
||||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||||
|
|
||||||
authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService,
|
authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService,
|
||||||
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY));
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testActionsSystemUserIsAuthorized() {
|
public void testActionsSystemUserIsAuthorized() {
|
||||||
|
@ -352,21 +346,22 @@ public class AuthorizationServiceTests extends ESTestCase {
|
||||||
public void testDenialForAnonymousUser() {
|
public void testDenialForAnonymousUser() {
|
||||||
TransportRequest request = new IndicesExistsRequest("b");
|
TransportRequest request = new IndicesExistsRequest("b");
|
||||||
ClusterState state = mock(ClusterState.class);
|
ClusterState state = mock(ClusterState.class);
|
||||||
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build());
|
Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build();
|
||||||
authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail,
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
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(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||||
when(clusterService.state()).thenReturn(state);
|
when(clusterService.state()).thenReturn(state);
|
||||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||||
|
|
||||||
try {
|
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");
|
fail("indices request for b should be denied since there is no such index");
|
||||||
} catch (ElasticsearchSecurityException e) {
|
} catch (ElasticsearchSecurityException e) {
|
||||||
assertAuthorizationException(e,
|
assertAuthorizationException(e,
|
||||||
containsString("action [indices:a] is unauthorized for user [" + AnonymousUser.INSTANCE.principal() + "]"));
|
containsString("action [indices:a] is unauthorized for user [" + anonymousUser.principal() + "]"));
|
||||||
verify(auditTrail).accessDenied(AnonymousUser.INSTANCE, "indices:a", request);
|
verify(auditTrail).accessDenied(anonymousUser, "indices:a", request);
|
||||||
verifyNoMoreInteractions(auditTrail);
|
verifyNoMoreInteractions(auditTrail);
|
||||||
verify(clusterService, times(2)).state();
|
verify(clusterService, times(2)).state();
|
||||||
verify(state, times(3)).metaData();
|
verify(state, times(3)).metaData();
|
||||||
|
@ -376,14 +371,13 @@ public class AuthorizationServiceTests extends ESTestCase {
|
||||||
public void testDenialForAnonymousUserAuthorizationExceptionDisabled() {
|
public void testDenialForAnonymousUserAuthorizationExceptionDisabled() {
|
||||||
TransportRequest request = new IndicesExistsRequest("b");
|
TransportRequest request = new IndicesExistsRequest("b");
|
||||||
ClusterState state = mock(ClusterState.class);
|
ClusterState state = mock(ClusterState.class);
|
||||||
AnonymousUser.initialize(Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put(AnonymousUser.ROLES_SETTING.getKey(), "a_all")
|
.put(AnonymousUser.ROLES_SETTING.getKey(), "a_all")
|
||||||
.put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false)
|
.put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false)
|
||||||
.build());
|
.build();
|
||||||
User anonymousUser = AnonymousUser.INSTANCE;
|
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||||
authorizationService = new AuthorizationService(
|
authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail,
|
||||||
Settings.builder().put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(),
|
new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings));
|
||||||
rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
|
||||||
|
|
||||||
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||||
when(clusterService.state()).thenReturn(state);
|
when(clusterService.state()).thenReturn(state);
|
||||||
|
|
|
@ -440,7 +440,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase {
|
||||||
|
|
||||||
public void testTemplating() throws Exception {
|
public void testTemplating() throws Exception {
|
||||||
User user = new User("_username", new String[]{"role1", "role2"}, "_full_name", "_email",
|
User user = new User("_username", new String[]{"role1", "role2"}, "_full_name", "_email",
|
||||||
Collections.singletonMap("key", "value"));
|
Collections.singletonMap("key", "value"), true);
|
||||||
securityIndexSearcherWrapper =
|
securityIndexSearcherWrapper =
|
||||||
new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) {
|
new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) {
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
||||||
|
|
||||||
public void testProcessor() throws Exception {
|
public void testProcessor() throws Exception {
|
||||||
User user = new User("_username", new String[]{"role1", "role2"}, "firstname lastname", "_email",
|
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");
|
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||||
|
@ -100,7 +100,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
||||||
|
|
||||||
public void testFullNameProperties() throws Exception {
|
public void testFullNameProperties() throws Exception {
|
||||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
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");
|
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
||||||
|
|
||||||
public void testEmailProperties() throws Exception {
|
public void testEmailProperties() throws Exception {
|
||||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
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");
|
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ public class SetSecurityUserProcessorTests extends ESTestCase {
|
||||||
|
|
||||||
public void testMetadataProperties() throws Exception {
|
public void testMetadataProperties() throws Exception {
|
||||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
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");
|
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
|
||||||
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
|
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.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
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.audit.AuditTrailService;
|
||||||
import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler;
|
import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler;
|
||||||
import org.elasticsearch.xpack.security.authz.AuthorizationService;
|
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.permission.SuperuserRole;
|
||||||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||||
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
|
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 org.junit.Before;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -102,7 +103,8 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
||||||
when(state.metaData()).thenReturn(metaData);
|
when(state.metaData()).thenReturn(metaData);
|
||||||
|
|
||||||
AuthorizationService authzService = new AuthorizationService(settings, rolesStore, clusterService,
|
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);
|
defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService, indexNameExpressionResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
||||||
|
|
||||||
public void testRetrievingReservedRolesNonKibanaUser() {
|
public void testRetrievingReservedRolesNonKibanaUser() {
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
when(securityContext.getUser()).thenReturn(ElasticUser.INSTANCE);
|
when(securityContext.getUser()).thenReturn(new ElasticUser(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(reservedRolesStore.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE));
|
assertThat(reservedRolesStore.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE));
|
||||||
|
@ -77,7 +77,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRetrievingReservedRoleKibanaUser() {
|
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.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE));
|
||||||
assertThat(reservedRolesStore.roleDescriptor(SuperuserRole.NAME), sameInstance(SuperuserRole.DESCRIPTOR));
|
assertThat(reservedRolesStore.roleDescriptor(SuperuserRole.NAME), sameInstance(SuperuserRole.DESCRIPTOR));
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.support;
|
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.authz.store.ReservedRolesStore;
|
||||||
import org.elasticsearch.xpack.security.support.Validation.Error;
|
import org.elasticsearch.xpack.security.support.Validation.Error;
|
||||||
import org.elasticsearch.xpack.security.support.Validation.Users;
|
import org.elasticsearch.xpack.security.support.Validation.Users;
|
||||||
|
@ -55,12 +56,12 @@ public class ValidationTests extends ESTestCase {
|
||||||
public void testUsersValidateUsername() throws Exception {
|
public void testUsersValidateUsername() throws Exception {
|
||||||
int length = randomIntBetween(1, 30);
|
int length = randomIntBetween(1, 30);
|
||||||
String name = new String(generateValidName(length));
|
String name = new String(generateValidName(length));
|
||||||
assertThat(Users.validateUsername(name), nullValue());
|
assertThat(Users.validateUsername(name, false, Settings.EMPTY), nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReservedUsernames() {
|
public void testReservedUsernames() {
|
||||||
final String username = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
|
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);
|
assertNotNull(error);
|
||||||
assertThat(error.toString(), containsString("is reserved"));
|
assertThat(error.toString(), containsString("is reserved"));
|
||||||
}
|
}
|
||||||
|
@ -71,13 +72,13 @@ public class ValidationTests extends ESTestCase {
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
name = generateValidName(length);
|
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 {
|
public void testUsersValidateUsernameInvalidCharacters() throws Exception {
|
||||||
int length = randomIntBetween(1, 30); // valid length
|
int length = randomIntBetween(1, 30); // valid length
|
||||||
String name = new String(generateInvalidName(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 {
|
public void testUsersValidatePassword() throws Exception {
|
||||||
|
@ -112,13 +113,13 @@ public class ValidationTests extends ESTestCase {
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
name = generateValidName(length);
|
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 {
|
public void testRolesValidateRoleNameInvalidCharacters() throws Exception {
|
||||||
int length = randomIntBetween(1, 30); // valid length
|
int length = randomIntBetween(1, 30); // valid length
|
||||||
String name = new String(generateInvalidName(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) {
|
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.io.stream.BytesStreamOutput;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.junit.After;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
@ -16,26 +15,19 @@ import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
public class AnonymousUserTests extends ESTestCase {
|
public class AnonymousUserTests extends ESTestCase {
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetAnonymous() {
|
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testResolveAnonymousUser() throws Exception {
|
public void testResolveAnonymousUser() throws Exception {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put(AnonymousUser.USERNAME_SETTING.getKey(), "anonym1")
|
.put(AnonymousUser.USERNAME_SETTING.getKey(), "anonym1")
|
||||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
AnonymousUser.initialize(settings);
|
AnonymousUser user = new AnonymousUser(settings);
|
||||||
User user = AnonymousUser.INSTANCE;
|
|
||||||
assertThat(user.principal(), equalTo("anonym1"));
|
assertThat(user.principal(), equalTo("anonym1"));
|
||||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||||
|
|
||||||
settings = Settings.builder()
|
settings = Settings.builder()
|
||||||
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
.putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3")
|
||||||
.build();
|
.build();
|
||||||
AnonymousUser.initialize(settings);
|
user = new AnonymousUser(settings);
|
||||||
user = AnonymousUser.INSTANCE;
|
|
||||||
assertThat(user.principal(), equalTo(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME));
|
assertThat(user.principal(), equalTo(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME));
|
||||||
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
assertThat(user.roles(), arrayContainingInAnyOrder("r1", "r2", "r3"));
|
||||||
}
|
}
|
||||||
|
@ -44,8 +36,7 @@ public class AnonymousUserTests extends ESTestCase {
|
||||||
Settings settings = randomBoolean() ?
|
Settings settings = randomBoolean() ?
|
||||||
Settings.EMPTY :
|
Settings.EMPTY :
|
||||||
Settings.builder().put(AnonymousUser.USERNAME_SETTING.getKey(), "user1").build();
|
Settings.builder().put(AnonymousUser.USERNAME_SETTING.getKey(), "user1").build();
|
||||||
AnonymousUser.initialize(settings);
|
assertThat(AnonymousUser.isAnonymousEnabled(settings), is(false));
|
||||||
assertThat(AnonymousUser.enabled(), is(false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAnonymous() throws Exception {
|
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();
|
settings = Settings.builder().put(settings).put(AnonymousUser.USERNAME_SETTING.getKey(), "anon").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
AnonymousUser.initialize(settings);
|
AnonymousUser user = new AnonymousUser(settings);
|
||||||
User user = AnonymousUser.INSTANCE;
|
assertEquals(user, new AnonymousUser(settings));
|
||||||
assertThat(AnonymousUser.is(user), is(true));
|
assertThat(AnonymousUser.isAnonymousUsername(user.principal(), settings), is(true));
|
||||||
assertThat(AnonymousUser.isAnonymousUsername(user.principal()), is(true));
|
|
||||||
// make sure check works with serialization
|
// make sure check works with serialization
|
||||||
BytesStreamOutput output = new BytesStreamOutput();
|
BytesStreamOutput output = new BytesStreamOutput();
|
||||||
User.writeTo(user, output);
|
User.writeTo(user, output);
|
||||||
|
|
||||||
User anonymousSerialized = User.readFrom(output.bytes().streamInput());
|
User anonymousSerialized = User.readFrom(output.bytes().streamInput());
|
||||||
assertThat(AnonymousUser.is(anonymousSerialized), is(true));
|
assertEquals(user, anonymousSerialized);
|
||||||
|
|
||||||
// test with null anonymous
|
// test with anonymous disabled
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
|
||||||
assertThat(AnonymousUser.is(null), is(false));
|
|
||||||
if (user.principal().equals(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME)) {
|
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 {
|
} 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;
|
package org.elasticsearch.xpack.security.user;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
import org.elasticsearch.ElasticsearchSecurityException;
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
@ -103,7 +100,7 @@ public class UserTests extends ESTestCase {
|
||||||
public void testUserToString() throws Exception {
|
public void testUserToString() throws Exception {
|
||||||
User user = new User("u1", "r1");
|
User user = new User("u1", "r1");
|
||||||
assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}]"));
|
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}]"));
|
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"));
|
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," +
|
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 {
|
public void testReservedUserSerialization() throws Exception {
|
||||||
BytesStreamOutput output = new BytesStreamOutput();
|
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());
|
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();
|
output = new BytesStreamOutput();
|
||||||
User.writeTo(KibanaUser.INSTANCE, output);
|
User.writeTo(kibanaUser, output);
|
||||||
readFrom = User.readFrom(output.bytes().streamInput());
|
readFrom = User.readFrom(output.bytes().streamInput());
|
||||||
|
|
||||||
assertThat(readFrom, is(sameInstance(KibanaUser.INSTANCE)));
|
assertEquals(kibanaUser, readFrom);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ cluster:admin/xpack/security/user/change_password
|
||||||
cluster:admin/xpack/security/user/put
|
cluster:admin/xpack/security/user/put
|
||||||
cluster:admin/xpack/security/user/delete
|
cluster:admin/xpack/security/user/delete
|
||||||
cluster:admin/xpack/security/user/get
|
cluster:admin/xpack/security/user/get
|
||||||
|
cluster:admin/xpack/security/user/set_enabled
|
||||||
cluster:admin/xpack/security/role/put
|
cluster:admin/xpack/security/role/put
|
||||||
cluster:admin/xpack/security/role/delete
|
cluster:admin/xpack/security/role/delete
|
||||||
cluster:admin/xpack/security/role/get
|
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/put
|
||||||
cluster:admin/xpack/security/user/delete
|
cluster:admin/xpack/security/user/delete
|
||||||
cluster:admin/xpack/security/user/get
|
cluster:admin/xpack/security/user/get
|
||||||
|
cluster:admin/xpack/security/user/set_enabled
|
||||||
indices:admin/analyze[s]
|
indices:admin/analyze[s]
|
||||||
indices:admin/cache/clear[n]
|
indices:admin/cache/clear[n]
|
||||||
indices:admin/forcemerge[n]
|
indices:admin/forcemerge[n]
|
||||||
|
|
|
@ -14,8 +14,9 @@
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"type" : "boolean",
|
"type" : "enum",
|
||||||
"description" : "Refresh the index after performing the operation"
|
"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": {
|
"params": {
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"type" : "boolean",
|
"type" : "enum",
|
||||||
"description" : "Refresh the index after performing the operation"
|
"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": {
|
"params": {
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"type" : "boolean",
|
"type" : "enum",
|
||||||
"description" : "Refresh the index after performing the operation"
|
"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": {
|
"params": {
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"type" : "boolean",
|
"type" : "enum",
|
||||||
"description" : "Refresh the index after performing the operation"
|
"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": {
|
"params": {
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"type" : "boolean",
|
"type" : "enum",
|
||||||
"description" : "Refresh the index after performing the operation"
|
"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.XPackInfoResponse;
|
||||||
import org.elasticsearch.license.License;
|
import org.elasticsearch.license.License;
|
||||||
import org.elasticsearch.license.LicenseService;
|
import org.elasticsearch.license.LicenseService;
|
||||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.elasticsearch.xpack.XPackFeatureSet;
|
import org.elasticsearch.xpack.XPackFeatureSet;
|
||||||
import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet;
|
import org.elasticsearch.license.XPackInfoResponse.FeatureSetsInfo.FeatureSet;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -40,22 +37,6 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class TransportXPackInfoActionTests extends ESTestCase {
|
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 {
|
public void testDoExecute() throws Exception {
|
||||||
|
|
||||||
LicenseService licenseService = mock(LicenseService.class);
|
LicenseService licenseService = mock(LicenseService.class);
|
||||||
|
|
Loading…
Reference in New Issue