add the ability to run as another user
This change adds a new permission that allows authorized users to execute a request as another user. The flow is as follows: 1. The user making the request is authenticated 2. The user that is being impersonated is looked up 3. The requesting user is authorized for the privilege to run as the specified user 4. The impersonated user is then authorized for the given request Additionally, the auditing has been updated to support this capability and indicates when a user has been granted the ability to run as another user and then also indicates both the user who is being impersonated and the requesting user when actions are granted/denied. Closes elastic/elasticsearch#17 Original commit: elastic/x-pack-elasticsearch@00e5a6169b
This commit is contained in:
parent
7ea8c85e4b
commit
154b10e901
|
@ -66,4 +66,14 @@ public class CustomRealm extends Realm<UsernamePasswordToken> {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User lookupUser(String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.shield;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
@ -31,6 +32,12 @@ public abstract class User {
|
|||
*/
|
||||
public abstract String[] roles();
|
||||
|
||||
/**
|
||||
* @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 returned
|
||||
*/
|
||||
public abstract User runAs();
|
||||
|
||||
public final boolean isSystem() {
|
||||
return this == SYSTEM;
|
||||
}
|
||||
|
@ -43,7 +50,14 @@ public abstract class User {
|
|||
}
|
||||
return SYSTEM;
|
||||
}
|
||||
return new Simple(input.readString(), input.readStringArray());
|
||||
String username = input.readString();
|
||||
String[] roles = input.readStringArray();
|
||||
if (input.readBoolean()) {
|
||||
String runAsUsername = input.readString();
|
||||
String[] runAsRoles = input.readStringArray();
|
||||
return new Simple(username, roles, new Simple(runAsUsername, runAsRoles));
|
||||
}
|
||||
return new Simple(username, roles);
|
||||
}
|
||||
|
||||
public static void writeTo(User user, StreamOutput output) throws IOException {
|
||||
|
@ -56,16 +70,33 @@ public abstract class User {
|
|||
Simple simple = (Simple) user;
|
||||
output.writeString(simple.username);
|
||||
output.writeStringArray(simple.roles);
|
||||
if (simple.runAs == null) {
|
||||
output.writeBoolean(false);
|
||||
} else {
|
||||
output.writeBoolean(true);
|
||||
output.writeString(simple.runAs.principal());
|
||||
output.writeStringArray(simple.runAs.roles());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Simple extends User {
|
||||
|
||||
private final String username;
|
||||
private final String[] roles;
|
||||
private final User runAs;
|
||||
|
||||
public Simple(String username, String... roles) {
|
||||
public Simple(String username, String[] roles) {
|
||||
this(username, roles, null);
|
||||
}
|
||||
|
||||
public Simple(String username, String[] roles, User runAs) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
assert (runAs == null || runAs.runAs() == null) : "the runAs user should not be a user that can run as";
|
||||
if (runAs == SYSTEM) {
|
||||
throw new ElasticsearchSecurityException("the runAs user cannot be the internal system user");
|
||||
}
|
||||
this.runAs = runAs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,6 +109,11 @@ public abstract class User {
|
|||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User runAs() {
|
||||
return runAs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -85,16 +121,24 @@ public abstract class User {
|
|||
|
||||
Simple simple = (Simple) o;
|
||||
|
||||
if (!Arrays.equals(roles, simple.roles)) return false;
|
||||
if (!username.equals(simple.username)) return false;
|
||||
if (username != null ? !username.equals(simple.username) : simple.username != null) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(roles, simple.roles)) {
|
||||
return false;
|
||||
}
|
||||
if (runAs != null ? !runAs.equals(simple.runAs) : simple.runAs != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = username.hashCode();
|
||||
int result = username != null ? username.hashCode() : 0;
|
||||
result = 31 * result + Arrays.hashCode(roles);
|
||||
result = 31 * result + (runAs != null ? runAs.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -117,5 +161,9 @@ public abstract class User {
|
|||
return ROLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User runAs() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,14 @@ public interface AuditTrail {
|
|||
@Override
|
||||
public void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsGranted(User user, String action, TransportMessage<?> message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsDenied(User user, String action, TransportMessage<?> message) {
|
||||
}
|
||||
};
|
||||
|
||||
String name();
|
||||
|
@ -108,4 +116,8 @@ public interface AuditTrail {
|
|||
void connectionGranted(InetAddress inetAddress, String profile, ShieldIpFilterRule rule);
|
||||
|
||||
void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule);
|
||||
|
||||
void runAsGranted(User user, String action, TransportMessage<?> message);
|
||||
|
||||
void runAsDenied(User user, String action, TransportMessage<?> message);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.rest.RestRequest;
|
|||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
|
@ -126,4 +127,18 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
|
|||
auditTrail.connectionDenied(inetAddress, profile, rule);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsGranted(User user, String action, TransportMessage<?> message) {
|
||||
for (AuditTrail auditTrail : auditTrails) {
|
||||
auditTrail.runAsGranted(user, action, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsDenied(User user, String action, TransportMessage<?> message) {
|
||||
for (AuditTrail auditTrail : auditTrails) {
|
||||
auditTrail.runAsDenied(user, action, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ public enum IndexAuditLevel {
|
|||
TAMPERED_REQUEST,
|
||||
CONNECTION_GRANTED,
|
||||
CONNECTION_DENIED,
|
||||
SYSTEM_ACCESS_GRANTED;
|
||||
SYSTEM_ACCESS_GRANTED,
|
||||
RUN_AS_GRANTED,
|
||||
RUN_AS_DENIED;
|
||||
|
||||
static EnumSet<IndexAuditLevel> parse(String[] levels) {
|
||||
EnumSet<IndexAuditLevel> enumSet = EnumSet.noneOf(IndexAuditLevel.class);
|
||||
|
@ -52,6 +54,12 @@ public enum IndexAuditLevel {
|
|||
case "system_access_granted":
|
||||
enumSet.add(SYSTEM_ACCESS_GRANTED);
|
||||
break;
|
||||
case "run_as_granted":
|
||||
enumSet.add(RUN_AS_GRANTED);
|
||||
break;
|
||||
case "run_as_denied":
|
||||
enumSet.add(RUN_AS_DENIED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid event name specified [" + level + "]");
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.io.ByteStreams;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
|
||||
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
|
||||
import org.elasticsearch.action.bulk.BulkProcessor;
|
||||
|
@ -99,7 +100,9 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
AUTHENTICATION_FAILED.toString(),
|
||||
CONNECTION_DENIED.toString(),
|
||||
CONNECTION_GRANTED.toString(),
|
||||
TAMPERED_REQUEST.toString()
|
||||
TAMPERED_REQUEST.toString(),
|
||||
RUN_AS_DENIED.toString(),
|
||||
RUN_AS_GRANTED.toString()
|
||||
};
|
||||
|
||||
private static final ImmutableSet<String> forbiddenIndexSettings = ImmutableSet.of("index.mapper.dynamic");
|
||||
|
@ -349,7 +352,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(AUTHENTICATION_FAILED)) {
|
||||
if (!principalIsAuditor(token.principal())) {
|
||||
try {
|
||||
enqueue(message("authentication_failed", action, token.principal(), null, indices(message), message), "authentication_failed");
|
||||
enqueue(message("authentication_failed", action, token, null, indices(message), message), "authentication_failed");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [authentication_failed]", e);
|
||||
}
|
||||
|
@ -362,7 +365,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(AUTHENTICATION_FAILED)) {
|
||||
if (!principalIsAuditor(token.principal())) {
|
||||
try {
|
||||
enqueue(message("authentication_failed", null, token.principal(), null, null, request), "authentication_failed");
|
||||
enqueue(message("authentication_failed", null, token, null, null, request), "authentication_failed");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [authentication_failed]", e);
|
||||
}
|
||||
|
@ -375,7 +378,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(AUTHENTICATION_FAILED)) {
|
||||
if (!principalIsAuditor(token.principal())) {
|
||||
try {
|
||||
enqueue(message("authentication_failed", action, token.principal(), realm, indices(message), message), "authentication_failed");
|
||||
enqueue(message("authentication_failed", action, token, realm, indices(message), message), "authentication_failed");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [authentication_failed]", e);
|
||||
}
|
||||
|
@ -388,7 +391,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(AUTHENTICATION_FAILED)) {
|
||||
if (!principalIsAuditor(token.principal())) {
|
||||
try {
|
||||
enqueue(message("authentication_failed", null, token.principal(), realm, null, request), "authentication_failed");
|
||||
enqueue(message("authentication_failed", null, token, realm, null, request), "authentication_failed");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [authentication_failed]", e);
|
||||
}
|
||||
|
@ -403,14 +406,14 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (user.isSystem() && Privilege.SYSTEM.predicate().test(action)) {
|
||||
if (events.contains(SYSTEM_ACCESS_GRANTED)) {
|
||||
try {
|
||||
enqueue(message("access_granted", action, user.principal(), null, indices(message), message), "access_granted");
|
||||
enqueue(message("access_granted", action, user, indices(message), message), "access_granted");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [access_granted]", e);
|
||||
}
|
||||
}
|
||||
} else if (events.contains(ACCESS_GRANTED)) {
|
||||
try {
|
||||
enqueue(message("access_granted", action, user.principal(), null, indices(message), message), "access_granted");
|
||||
enqueue(message("access_granted", action, user, indices(message), message), "access_granted");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [access_granted]", e);
|
||||
}
|
||||
|
@ -423,7 +426,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(ACCESS_DENIED)) {
|
||||
if (!principalIsAuditor(user.principal())) {
|
||||
try {
|
||||
enqueue(message("access_denied", action, user.principal(), null, indices(message), message), "access_denied");
|
||||
enqueue(message("access_denied", action, user, indices(message), message), "access_denied");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [access_denied]", e);
|
||||
}
|
||||
|
@ -436,7 +439,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (events.contains(TAMPERED_REQUEST)) {
|
||||
if (!principalIsAuditor(user.principal())) {
|
||||
try {
|
||||
enqueue(message("tampered_request", action, user.principal(), null, indices(request), request), "tampered_request");
|
||||
enqueue(message("tampered_request", action, user, indices(request), request), "tampered_request");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [tampered_request]", e);
|
||||
}
|
||||
|
@ -466,11 +469,69 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsGranted(User user, String action, TransportMessage<?> message) {
|
||||
if (events.contains(RUN_AS_GRANTED)) {
|
||||
try {
|
||||
enqueue(message("run_as_granted", action, user, null, message), "access_granted");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [run_as_granted]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsDenied(User user, String action, TransportMessage<?> message) {
|
||||
if (events.contains(RUN_AS_DENIED)) {
|
||||
try {
|
||||
enqueue(message("run_as_denied", action, user, null, message), "access_granted");
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to index audit event: [run_as_denied]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean principalIsAuditor(String principal) {
|
||||
return (principal.equals(auditUser.user().principal()));
|
||||
}
|
||||
|
||||
private Message message(String type, @Nullable String action, @Nullable String principal,
|
||||
private Message message(String type, @Nullable String action, @Nullable User user,
|
||||
@Nullable String[] indices, TransportMessage message) throws Exception {
|
||||
|
||||
Message msg = new Message().start();
|
||||
common("transport", type, msg.builder);
|
||||
originAttributes(message, msg.builder, transport);
|
||||
|
||||
if (action != null) {
|
||||
msg.builder.field(Field.ACTION, action);
|
||||
}
|
||||
if (user != null) {
|
||||
if (user.runAs() != null) {
|
||||
if ("run_as_granted".equals(type) || "run_as_denied".equals(type)) {
|
||||
msg.builder.field(Field.PRINCIPAL, user.principal());
|
||||
msg.builder.field(Field.RUN_AS_PRINCIPAL, user.runAs().principal());
|
||||
} else {
|
||||
msg.builder.field(Field.PRINCIPAL, user.runAs().principal());
|
||||
msg.builder.field(Field.RUN_BY_PRINCIPAL, user.principal());
|
||||
}
|
||||
} else {
|
||||
msg.builder.field(Field.PRINCIPAL, user.principal());
|
||||
}
|
||||
}
|
||||
if (indices != null) {
|
||||
msg.builder.array(Field.INDICES, indices);
|
||||
}
|
||||
|
||||
// FIXME WTF!!! Why does this have anything to do with the logger!!!!!
|
||||
if (logger.isDebugEnabled()) {
|
||||
msg.builder.field(Field.REQUEST, message.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
return msg.end();
|
||||
}
|
||||
|
||||
// FIXME - clean up the message generation
|
||||
private Message message(String type, @Nullable String action, @Nullable AuthenticationToken token,
|
||||
@Nullable String realm, @Nullable String[] indices, TransportMessage message) throws Exception {
|
||||
|
||||
Message msg = new Message().start();
|
||||
|
@ -480,8 +541,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (action != null) {
|
||||
msg.builder.field(Field.ACTION, action);
|
||||
}
|
||||
if (principal != null) {
|
||||
msg.builder.field(Field.PRINCIPAL, principal);
|
||||
if (token != null) {
|
||||
msg.builder.field(Field.PRINCIPAL, token.principal());
|
||||
}
|
||||
if (realm != null) {
|
||||
msg.builder.field(Field.REALM, realm);
|
||||
|
@ -489,6 +550,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (indices != null) {
|
||||
msg.builder.array(Field.INDICES, indices);
|
||||
}
|
||||
|
||||
// FIXME WTF!!! Why does this have anything to do with the logger!!!!!
|
||||
if (logger.isDebugEnabled()) {
|
||||
msg.builder.field(Field.REQUEST, message.getClass().getSimpleName());
|
||||
}
|
||||
|
@ -496,7 +559,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
return msg.end();
|
||||
}
|
||||
|
||||
private Message message(String type, @Nullable String action, @Nullable String principal,
|
||||
private Message message(String type, @Nullable String action, @Nullable AuthenticationToken token,
|
||||
@Nullable String realm, @Nullable String[] indices, RestRequest request) throws Exception {
|
||||
|
||||
Message msg = new Message().start();
|
||||
|
@ -505,9 +568,11 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (action != null) {
|
||||
msg.builder.field(Field.ACTION, action);
|
||||
}
|
||||
if (principal != null) {
|
||||
msg.builder.field(Field.PRINCIPAL, principal);
|
||||
|
||||
if (token != null) {
|
||||
msg.builder.field(Field.PRINCIPAL, token.principal());
|
||||
}
|
||||
|
||||
if (realm != null) {
|
||||
msg.builder.field(Field.REALM, realm);
|
||||
}
|
||||
|
@ -587,6 +652,11 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
}
|
||||
}
|
||||
|
||||
// for testing to ensure we get the proper timestamp and index name...
|
||||
Message peek() {
|
||||
return eventQueue.peek();
|
||||
}
|
||||
|
||||
private void initializeClient() {
|
||||
if (indexToRemoteCluster == false) {
|
||||
// in the absence of client settings for remote indexing, fall back to the client that was passed in.
|
||||
|
@ -681,6 +751,29 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
if (!response.isAcknowledged()) {
|
||||
throw new IllegalStateException("failed to put index template for audit logging");
|
||||
}
|
||||
|
||||
// now we may need to update the mappings of the current index
|
||||
DateTime dateTime;
|
||||
Message message = eventQueue.peek();
|
||||
if (message != null) {
|
||||
dateTime = message.timestamp;
|
||||
} else {
|
||||
dateTime = DateTime.now(DateTimeZone.UTC);
|
||||
}
|
||||
String index = resolve(INDEX_NAME_PREFIX, dateTime, rollover);
|
||||
if (client.admin().indices().prepareExists(index).get().isExists()) {
|
||||
logger.debug("index [{}] exists so we need to update mappings", index);
|
||||
PutMappingResponse putMappingResponse = client.admin().indices()
|
||||
.preparePutMapping(index)
|
||||
.setType(DOC_TYPE)
|
||||
.setSource(request.mappings().get(DOC_TYPE))
|
||||
.get();
|
||||
if (!putMappingResponse.isAcknowledged()) {
|
||||
throw new IllegalStateException("failed to put mappings for audit logging index [" + index + "]");
|
||||
}
|
||||
} else {
|
||||
logger.debug("index [{}] does not exist so we do not need to update mappings", index);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("unexpected exception while putting index template", e);
|
||||
throw new IllegalStateException("failed to load [" + INDEX_TEMPLATE_NAME + ".json]", e);
|
||||
|
@ -825,6 +918,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl
|
|||
XContentBuilderString ORIGIN_ADDRESS = new XContentBuilderString("origin_address");
|
||||
XContentBuilderString ORIGIN_TYPE = new XContentBuilderString("origin_type");
|
||||
XContentBuilderString PRINCIPAL = new XContentBuilderString("principal");
|
||||
XContentBuilderString RUN_AS_PRINCIPAL = new XContentBuilderString("run_as_principal");
|
||||
XContentBuilderString RUN_BY_PRINCIPAL = new XContentBuilderString("run_by_principal");
|
||||
XContentBuilderString ACTION = new XContentBuilderString("action");
|
||||
XContentBuilderString INDICES = new XContentBuilderString("indices");
|
||||
XContentBuilderString REQUEST = new XContentBuilderString("request");
|
||||
|
|
|
@ -21,7 +21,7 @@ public class IndexAuditUserHolder {
|
|||
|
||||
private final User user;
|
||||
public static final Permission.Global.Role ROLE = Permission.Global.Role.builder(ROLE_NAMES[0])
|
||||
.set(Privilege.Cluster.action(PutIndexTemplateAction.NAME))
|
||||
.cluster(Privilege.Cluster.action(PutIndexTemplateAction.NAME))
|
||||
.add(Privilege.Index.CREATE_INDEX, IndexAuditTrail.INDEX_NAME_PREFIX + "*")
|
||||
.add(Privilege.Index.INDEX, IndexAuditTrail.INDEX_NAME_PREFIX + "*")
|
||||
.add(Privilege.Index.action(BulkAction.NAME), IndexAuditTrail.INDEX_NAME_PREFIX + "*")
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.elasticsearch.common.inject.Inject;
|
|||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.network.NetworkUtils;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
|
@ -171,9 +170,9 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
if (user.isSystem() && Privilege.SYSTEM.predicate().test(action)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
if (indices != null) {
|
||||
logger.trace("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, indices, message.getClass().getSimpleName());
|
||||
logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, indices, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.trace("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, message.getClass().getSimpleName());
|
||||
logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, message.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
@ -181,15 +180,15 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
|
||||
if (indices != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, indices, message.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, indices, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.info("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}], indices=[{}]", prefix, originAttributes(message, transport), user.principal(), action, indices);
|
||||
logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}]", prefix, originAttributes(message, transport), principal(user), action, indices);
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, message.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.info("{}[transport] [access_granted]\t{}, principal=[{}], action=[{}]", prefix, originAttributes(message, transport), user.principal(), action);
|
||||
logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}]", prefix, originAttributes(message, transport), principal(user), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,15 +198,15 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
String indices = indicesString(message);
|
||||
if (indices != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [access_denied]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, indices, message.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, indices, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.error("{}[transport] [access_denied]\t{}, principal=[{}], action=[{}], indices=[{}]", prefix, originAttributes(message, transport), user.principal(), action, indices);
|
||||
logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}]", prefix, originAttributes(message, transport), principal(user), action, indices);
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [access_denied]\t{}, principal=[{}], action=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), action, message.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], request=[{}]", prefix, originAttributes(message, transport), principal(user), action, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.error("{}[transport] [access_denied]\t{}, principal=[{}], action=[{}]", prefix, originAttributes(message, transport), user.principal(), action);
|
||||
logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}]", prefix, originAttributes(message, transport), principal(user), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,15 +216,15 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
String indices = indicesString(request);
|
||||
if (indices != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [tampered_request]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]", prefix, request.remoteAddress(), user.principal(), action, indices, request.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, request.remoteAddress(), principal(user), action, indices, request.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.error("{}[transport] [tampered_request]\t{}, principal=[{}], action=[{}], indices=[{}]", prefix, request.remoteAddress(), user.principal(), action, indices);
|
||||
logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}]", prefix, request.remoteAddress(), principal(user), action, indices);
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [tampered_request]\t{}, principal=[{}], action=[{}], request=[{}]", prefix, request.remoteAddress(), user.principal(), action, request.getClass().getSimpleName());
|
||||
logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], request=[{}]", prefix, request.remoteAddress(), principal(user), action, request.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.error("{}[transport] [tampered_request]\t{}, principal=[{}], action=[{}]", prefix, request.remoteAddress(), user.principal(), action);
|
||||
logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}]", prefix, request.remoteAddress(), principal(user), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,6 +241,24 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
logger.error("{}[ip_filter] [connection_denied]\torigin_address=[{}], transport_profile=[{}], rule=[{}]", prefix, NetworkAddress.formatAddress(inetAddress), profile, rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsGranted(User user, String action, TransportMessage<?> message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), user.runAs().principal(), action, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.info("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", prefix, originAttributes(message, transport), user.principal(), user.runAs().principal(), action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAsDenied(User user, String action, TransportMessage<?> message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", prefix, originAttributes(message, transport), user.principal(), user.runAs().principal(), action, message.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", prefix, originAttributes(message, transport), user.principal(), user.runAs().principal(), action);
|
||||
}
|
||||
}
|
||||
|
||||
private static String hostAttributes(RestRequest request) {
|
||||
String formattedAddress;
|
||||
SocketAddress socketAddress = request.getRemoteAddress();
|
||||
|
@ -309,4 +326,12 @@ public class LoggingAuditTrail implements AuditTrail {
|
|||
String[] indices = indices(message);
|
||||
return indices == null ? null : arrayToCommaDelimitedString(indices);
|
||||
}
|
||||
|
||||
static String principal(User user) {
|
||||
StringBuilder builder = new StringBuilder("principal=[");
|
||||
if (user.runAs() != null) {
|
||||
builder.append(user.runAs().principal()).append("], run_by_principal=[");
|
||||
}
|
||||
return builder.append(user.principal()).append("]").toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,26 @@
|
|||
package org.elasticsearch.shield.authc;
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface for a token that is used for authentication. This token is the representation of the authentication
|
||||
* information that is presented with a request. The token will be extracted by a {@link Realm} and subsequently
|
||||
* used by a Realm to attempt authentication of a user.
|
||||
*/
|
||||
public interface AuthenticationToken {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String principal();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object credentials();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void clearCredentials();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import static org.elasticsearch.shield.support.Exceptions.authenticationError;
|
|||
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {
|
||||
|
||||
public static final String SETTING_SIGN_USER_HEADER = "shield.authc.sign_user_header";
|
||||
public static final String SETTING_RUN_AS_ENABLED = "shield.authc.run_as.enabled";
|
||||
public static final String RUN_AS_USER_HEADER = "es-shield-runas-user";
|
||||
|
||||
static final String TOKEN_KEY = "_shield_token";
|
||||
public static final String USER_KEY = "_shield_user";
|
||||
|
@ -41,6 +43,7 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
private final AnonymousService anonymousService;
|
||||
private final AuthenticationFailureHandler failureHandler;
|
||||
private final boolean signUserHeader;
|
||||
private final boolean runAsEnabled;
|
||||
|
||||
@Inject
|
||||
public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService,
|
||||
|
@ -52,6 +55,7 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
this.anonymousService = anonymousService;
|
||||
this.failureHandler = failureHandler;
|
||||
this.signUserHeader = settings.getAsBoolean(SETTING_SIGN_USER_HEADER, true);
|
||||
this.runAsEnabled = settings.getAsBoolean(SETTING_RUN_AS_ENABLED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,6 +98,44 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
if (user == null) {
|
||||
throw failureHandler.unsuccessfulAuthentication(request, token);
|
||||
}
|
||||
if (runAsEnabled) {
|
||||
String runAsUsername = request.header(RUN_AS_USER_HEADER);
|
||||
if (runAsUsername != null) {
|
||||
if (runAsUsername.isEmpty()) {
|
||||
logger.warn("user [{}] attempted to runAs with an empty username", user.principal());
|
||||
auditTrail.authenticationFailed(token, request);
|
||||
throw failureHandler.unsuccessfulAuthentication(request, token);
|
||||
}
|
||||
|
||||
User runAsUser;
|
||||
try {
|
||||
runAsUser = lookupUser(runAsUsername);
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("lookup of run as user failed for principal [{}], uri [{}], run as username [{}]", e, token.principal(), request.uri(), runAsUsername);
|
||||
}
|
||||
auditTrail.authenticationFailed(token, request);
|
||||
throw failureHandler.exceptionProcessingRequest(request, e);
|
||||
}
|
||||
|
||||
// wrap in a try catch because the user constructor could throw an exception if we are trying to runAs the system user
|
||||
try {
|
||||
if (runAsUser != null) {
|
||||
user = new User.Simple(user.principal(), user.roles(), runAsUser);
|
||||
} else {
|
||||
// the requested run as user does not exist, but we don't throw an error here otherwise this could let information leak about users in the system... instead we'll just let the authz service fail throw an authorization error
|
||||
user = new User.Simple(user.principal(), user.roles(), new User.Simple(runAsUsername, null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user creation failed for principal [{}], uri [{}], run as username [{}]", e, token.principal(), request.uri(), runAsUsername);
|
||||
}
|
||||
auditTrail.authenticationFailed(token, request);
|
||||
throw failureHandler.exceptionProcessingRequest(request, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we must put the user in the request context, so it'll be copied to the
|
||||
// transport request - without it, the transport will assume system user
|
||||
request.putInContext(USER_KEY, user);
|
||||
|
@ -221,6 +263,43 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
if (user == null) {
|
||||
throw failureHandler.unsuccessfulAuthentication(message, token, action);
|
||||
}
|
||||
|
||||
if (runAsEnabled) {
|
||||
String runAsUsername = message.getHeader(RUN_AS_USER_HEADER);
|
||||
if (runAsUsername != null) {
|
||||
if (runAsUsername.isEmpty()) {
|
||||
logger.warn("user [{}] attempted to runAs with an empty username", user.principal());
|
||||
auditTrail.authenticationFailed(token, action, message);
|
||||
throw failureHandler.unsuccessfulAuthentication(message, token, action);
|
||||
}
|
||||
User runAsUser;
|
||||
try {
|
||||
runAsUser = lookupUser(runAsUsername);
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("lookup of run as user failed for principal [{}], action [{}], run as username [{}]", e, token.principal(), action, runAsUsername);
|
||||
}
|
||||
auditTrail.authenticationFailed(token, action, message);
|
||||
throw failureHandler.exceptionProcessingRequest(message, e);
|
||||
}
|
||||
|
||||
// wrap in a try catch because the user constructor could throw an exception if we are trying to runAs the system user
|
||||
try {
|
||||
if (runAsUser != null) {
|
||||
user = new User.Simple(user.principal(), user.roles(), runAsUser);
|
||||
} else {
|
||||
// the requested run as user does not exist, but we don't throw an error here otherwise this could let information leak about users in the system... instead we'll just let the authz service fail throw an authorization error
|
||||
user = new User.Simple(user.principal(), user.roles(), new User.Simple(runAsUsername, null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user creation failed for principal [{}], action [{}], run as username [{}]", e, token.principal(), action, runAsUsername);
|
||||
}
|
||||
auditTrail.authenticationFailed(token, action, message);
|
||||
throw failureHandler.exceptionProcessingRequest(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
|
@ -293,4 +372,16 @@ public class InternalAuthenticationService extends AbstractComponent implements
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
User lookupUser(String username) {
|
||||
for (Realm realm : realms) {
|
||||
if (realm.userLookupSupported()) {
|
||||
User user = realm.lookupUser(username);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,21 @@ public abstract class Realm<T extends AuthenticationToken> implements Comparable
|
|||
*/
|
||||
public abstract User authenticate(T token);
|
||||
|
||||
/**
|
||||
* Looks up the user identified the String identifier. A successful lookup will return the {@link User} identified
|
||||
* by the username. An unsuccessful lookup returns {@code null}.
|
||||
*
|
||||
* @param username the String identifier for the user
|
||||
* @return the {@link User} or {@code null} if lookup failed
|
||||
*/
|
||||
public abstract User lookupUser(String username);
|
||||
|
||||
/**
|
||||
* Indicates whether this realm supports user lookup.
|
||||
* @return true if the realm supports user lookup
|
||||
*/
|
||||
public abstract boolean userLookupSupported();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + "/" + config.name;
|
||||
|
|
|
@ -44,6 +44,20 @@ public class ESUsersRealm extends CachingUsernamePasswordRealm {
|
|||
return new User.Simple(token.principal(), roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User doLookupUser(String username) {
|
||||
if (userPasswdStore.userExists(username)){
|
||||
String[] roles = userRolesStore.roles(username);
|
||||
return new User.Simple(username, roles);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
class Listener implements RefreshListener {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
|
|
|
@ -89,6 +89,10 @@ public class FileUserPasswdStore {
|
|||
return hash != null && hasher.verify(password, hash);
|
||||
}
|
||||
|
||||
public boolean userExists(String username) {
|
||||
return users != null && users.containsKey(username);
|
||||
}
|
||||
|
||||
public static Path resolveFile(Settings settings, Environment env) {
|
||||
String location = settings.get("files.users");
|
||||
if (location == null) {
|
||||
|
@ -139,18 +143,22 @@ public class FileUserPasswdStore {
|
|||
if (line.startsWith("#")) { // comment
|
||||
continue;
|
||||
}
|
||||
|
||||
// only trim the line because we have a format, our tool generates the formatted text and we shouldn't be lenient
|
||||
// and allow spaces in the format
|
||||
line = line.trim();
|
||||
int i = line.indexOf(":");
|
||||
if (i <= 0 || i == line.length() - 1) {
|
||||
logger.error("invalid entry in users file [{}], line [{}]. skipping...", path.toAbsolutePath(), lineNr);
|
||||
continue;
|
||||
}
|
||||
String username = line.substring(0, i).trim();
|
||||
String username = line.substring(0, i);
|
||||
Validation.Error validationError = Validation.ESUsers.validateUsername(username);
|
||||
if (validationError != null) {
|
||||
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(), validationError);
|
||||
continue;
|
||||
}
|
||||
String hash = line.substring(i + 1).trim();
|
||||
String hash = line.substring(i + 1);
|
||||
users.put(username, hash.toCharArray());
|
||||
}
|
||||
|
||||
|
|
|
@ -144,15 +144,8 @@ public class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
|
||||
@Override
|
||||
public LdapSession session(String user, SecuredString password) throws Exception {
|
||||
SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), Strings.EMPTY_ARRAY);
|
||||
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));
|
||||
LDAPConnectionPool connectionPool = connectionPool();
|
||||
try {
|
||||
SearchResultEntry entry = searchForEntry(connectionPool, request, logger);
|
||||
if (entry == null) {
|
||||
throw Exceptions.authenticationError("failed to find user [{}] with search base [{}] scope [{}]", user, userSearchBaseDn, scope.toString().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
String dn = entry.getDN();
|
||||
String dn = findUserDN(user);
|
||||
tryBind(dn, password);
|
||||
return new LdapSession(logger, connectionPool, dn, groupResolver, timeout);
|
||||
} catch (LDAPException e) {
|
||||
|
@ -160,6 +153,32 @@ public class LdapUserSearchSessionFactory extends SessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnauthenticatedSession() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LdapSession unauthenticatedSession(String user) throws Exception {
|
||||
try {
|
||||
String dn = findUserDN(user);
|
||||
return new LdapSession(logger, connectionPool, dn, groupResolver, timeout);
|
||||
} catch (LDAPException e) {
|
||||
throw Exceptions.authenticationError("failed to lookup user [{}]", e, user);
|
||||
}
|
||||
}
|
||||
|
||||
private String findUserDN(String user) throws Exception {
|
||||
SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), Strings.EMPTY_ARRAY);
|
||||
request.setTimeLimitSeconds(Ints.checkedCast(timeout.seconds()));
|
||||
LDAPConnectionPool connectionPool = connectionPool();
|
||||
SearchResultEntry entry = searchForEntry(connectionPool, request, logger);
|
||||
if (entry == null) {
|
||||
throw Exceptions.authenticationError("failed to find user [{}] with search base [{}] scope [{}]", user, userSearchBaseDn, scope.toString().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
return entry.getDN();
|
||||
}
|
||||
|
||||
private void tryBind(String dn, SecuredString password) throws IOException {
|
||||
LDAPConnection bindConnection;
|
||||
try {
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Supporting class for JNDI-based Realms
|
||||
* Supporting class for LDAP realms
|
||||
*/
|
||||
public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm {
|
||||
|
||||
|
@ -36,24 +36,49 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm {
|
|||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
try (LdapSession session = sessionFactory.session(token.principal(), token.credentials())) {
|
||||
List<String> groupDNs = session.groups();
|
||||
Set<String> roles = roleMapper.resolveRoles(session.userDn(), groupDNs);
|
||||
return new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));
|
||||
} catch (Throwable e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authentication failed for user [{}]", e, token.principal());
|
||||
} else {
|
||||
String causeMessage = (e.getCause() == null) ? null : e.getCause().getMessage();
|
||||
if (causeMessage == null) {
|
||||
logger.warn("authentication failed for user [{}]: {}", token.principal(), e.getMessage());
|
||||
} else {
|
||||
logger.warn("authentication failed for user [{}]: {}\ncause: {}: {}", token.principal(), e.getMessage(), e.getCause().getClass().getName(), causeMessage);
|
||||
}
|
||||
}
|
||||
return createUser(token.principal(), session);
|
||||
} catch (Exception e) {
|
||||
logException("authentication", e, token.principal());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User doLookupUser(String username) {
|
||||
if (sessionFactory.supportsUnauthenticatedSession()) {
|
||||
try (LdapSession session = sessionFactory.unauthenticatedSession(username)) {
|
||||
return createUser(username, session);
|
||||
} catch (Exception e) {
|
||||
logException("lookup", e, username);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return sessionFactory.supportsUnauthenticatedSession();
|
||||
}
|
||||
|
||||
private void logException(String action, Exception e, String principal) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{} failed for user [{}]", e, action, principal);
|
||||
} else {
|
||||
String causeMessage = (e.getCause() == null) ? null : e.getCause().getMessage();
|
||||
if (causeMessage == null) {
|
||||
logger.warn("{} failed for user [{}]: {}", action, principal, e.getMessage());
|
||||
} else {
|
||||
logger.warn("{} failed for user [{}]: {}\ncause: {}: {}", action, principal, e.getMessage(), e.getCause().getClass().getName(), causeMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private User createUser(String principal, LdapSession session) {
|
||||
List<String> groupDNs = session.groups();
|
||||
Set<String> roles = roleMapper.resolveRoles(session.userDn(), groupDNs);
|
||||
return new User.Simple(principal, roles.toArray(new String[roles.size()]));
|
||||
}
|
||||
|
||||
class Listener implements RefreshListener {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
|
|
|
@ -74,6 +74,25 @@ public abstract class SessionFactory {
|
|||
*/
|
||||
public abstract LdapSession session(String user, SecuredString password) throws Exception;
|
||||
|
||||
/**
|
||||
* Returns a flag to indicate if this session factory supports unauthenticated sessions. This means that a session can
|
||||
* be established without providing any credentials in a call to {@link SessionFactory#unauthenticatedSession(String)}
|
||||
* @return true if the factory supports unauthenticated sessions
|
||||
*/
|
||||
public boolean supportsUnauthenticatedSession() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link LdapSession} for the user identified by the String parameter
|
||||
* @param username the identifier for the user
|
||||
* @return LdapSession representing a connection to LDAP for the provided user.
|
||||
* @throws Exception if an error occurs when creating the session or unauthenticated sessions are not supported
|
||||
*/
|
||||
public LdapSession unauthenticatedSession(String username) throws Exception {
|
||||
throw new UnsupportedOperationException("unauthenticated sessions are not supported");
|
||||
}
|
||||
|
||||
protected static LDAPConnectionOptions connectionOptions(Settings settings) {
|
||||
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||
options.setConnectTimeoutMillis(Ints.checkedCast(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()));
|
||||
|
|
|
@ -81,6 +81,16 @@ public class PkiRealm extends Realm<X509AuthenticationToken> {
|
|||
return new User.Simple(token.principal(), roles.toArray(new String[roles.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public User lookupUser(String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static X509AuthenticationToken token(Object pkiHeaderValue, Pattern principalPattern, ESLogger logger) {
|
||||
if (pkiHeaderValue == null) {
|
||||
return null;
|
||||
|
|
|
@ -64,7 +64,7 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
* @return an authenticated user with roles
|
||||
*/
|
||||
@Override
|
||||
public User authenticate(final UsernamePasswordToken token) {
|
||||
public final User authenticate(final UsernamePasswordToken token) {
|
||||
if (cache == null) {
|
||||
return doAuthenticate(token);
|
||||
}
|
||||
|
@ -85,18 +85,25 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
|
||||
try {
|
||||
UserWithHash userWithHash = cache.get(token.principal(), callback);
|
||||
if (userWithHash.verify(token.credentials())) {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
final boolean hadHash = userWithHash.hasHash();
|
||||
if (hadHash) {
|
||||
if (userWithHash.verify(token.credentials())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
}
|
||||
return userWithHash.user;
|
||||
}
|
||||
return userWithHash.user;
|
||||
}
|
||||
//this handles when a user's password has changed:
|
||||
//this handles when a user's password has changed or the user was looked up for run as and not authenticated
|
||||
expire(token.principal());
|
||||
userWithHash = cache.get(token.principal(), callback);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("cached user's password changed. authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
if (hadHash) {
|
||||
logger.debug("cached user's password changed. authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
} else {
|
||||
logger.debug("cached user came from a lookup and could not be used for authentication. authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
}
|
||||
}
|
||||
return userWithHash.user;
|
||||
|
||||
|
@ -110,20 +117,59 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final User lookupUser(final String username) {
|
||||
if (!userLookupSupported()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Callable<UserWithHash> callback = new Callable<UserWithHash>() {
|
||||
@Override
|
||||
public UserWithHash call() throws Exception {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user not found in cache, proceeding with normal lookup");
|
||||
}
|
||||
User user = doLookupUser(username);
|
||||
if (user == null) {
|
||||
throw Exceptions.authenticationError("could not lookup [{}]", username);
|
||||
}
|
||||
return new UserWithHash(user, null, null);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
UserWithHash userWithHash = cache.get(username, callback);
|
||||
return userWithHash.user;
|
||||
} catch (ExecutionException | UncheckedExecutionException ee) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("realm [" + name() + "] could not lookup [" + username + "]", ee);
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("realm [" + name() + "] could not authenticate [" + username + "]");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract User doAuthenticate(UsernamePasswordToken token);
|
||||
|
||||
public static class UserWithHash {
|
||||
protected abstract User doLookupUser(String username);
|
||||
|
||||
private static class UserWithHash {
|
||||
User user;
|
||||
char[] hash;
|
||||
Hasher hasher;
|
||||
public UserWithHash(User user, SecuredString password, Hasher hasher){
|
||||
this.user = user;
|
||||
this.hash = hasher.hash(password);
|
||||
this.hash = password == null ? null : hasher.hash(password);
|
||||
this.hasher = hasher;
|
||||
}
|
||||
|
||||
public boolean verify(SecuredString password){
|
||||
return hasher.verify(password, hash);
|
||||
public boolean verify(SecuredString password) {
|
||||
return hash != null && hasher.verify(password, hash);
|
||||
}
|
||||
|
||||
public boolean hasHash() {
|
||||
return hash != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,12 +108,35 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
throw denial(user, action, request);
|
||||
}
|
||||
|
||||
Permission.Global permission = permission(user);
|
||||
|
||||
Permission.Global permission = permission(user.roles());
|
||||
final boolean isRunAs = user.runAs() != null;
|
||||
// permission can be null as it might be that the user's role
|
||||
// is unknown
|
||||
if (permission == null || permission.isEmpty()) {
|
||||
throw denial(user, action, request);
|
||||
if (isRunAs) {
|
||||
// the request is a run as request so we should call the specific audit event for a denied run as attempt
|
||||
throw denyRunAs(user, action, request);
|
||||
} else {
|
||||
throw denial(user, action, request);
|
||||
}
|
||||
}
|
||||
|
||||
// check if the request is a run as request
|
||||
if (isRunAs) {
|
||||
// first we must authorize for the RUN_AS action
|
||||
Permission.RunAs runAs = permission.runAs();
|
||||
if (runAs != null && runAs.check(user.runAs().principal())) {
|
||||
grantRunAs(user, action, request);
|
||||
permission = permission(user.runAs().roles());
|
||||
|
||||
// permission can be null as it might be that the user's role
|
||||
// is unknown
|
||||
if (permission == null || permission.isEmpty()) {
|
||||
throw denial(user, action, request);
|
||||
}
|
||||
} else {
|
||||
throw denyRunAs(user, action, request);
|
||||
}
|
||||
}
|
||||
|
||||
// first, we'll check if the action is a cluster action. If it is, we'll only check it
|
||||
|
@ -187,8 +210,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
grant(user, action, request);
|
||||
}
|
||||
|
||||
private Permission.Global permission(User user) {
|
||||
String[] roleNames = user.roles();
|
||||
private Permission.Global permission(String[] roleNames) {
|
||||
if (roleNames.length == 0) {
|
||||
return Permission.Global.NONE;
|
||||
}
|
||||
|
@ -233,17 +255,32 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
|
||||
private ElasticsearchSecurityException denial(User user, String action, TransportRequest request) {
|
||||
auditTrail.accessDenied(user, action, request);
|
||||
// Special case for anonymous user
|
||||
if (anonymousService.isAnonymous(user)) {
|
||||
if (!anonymousService.authorizationExceptionsEnabled()) {
|
||||
throw authcFailureHandler.authenticationRequired(action);
|
||||
}
|
||||
}
|
||||
return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal());
|
||||
return denialException(user, action);
|
||||
}
|
||||
|
||||
private ElasticsearchSecurityException denyRunAs(User user, String action, TransportRequest request) {
|
||||
auditTrail.runAsDenied(user, action, request);
|
||||
return denialException(user, action);
|
||||
}
|
||||
|
||||
private void grant(User user, String action, TransportRequest request) {
|
||||
auditTrail.accessGranted(user, action, request);
|
||||
}
|
||||
|
||||
private void grantRunAs(User user, String action, TransportRequest request) {
|
||||
auditTrail.runAsGranted(user, action, request);
|
||||
}
|
||||
|
||||
private ElasticsearchSecurityException denialException(User user, String action) {
|
||||
// Special case for anonymous user
|
||||
if (anonymousService.isAnonymous(user)) {
|
||||
if (!anonymousService.authorizationExceptionsEnabled()) {
|
||||
throw authcFailureHandler.authenticationRequired(action);
|
||||
}
|
||||
}
|
||||
if (user.runAs() != null) {
|
||||
return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(), user.runAs().principal());
|
||||
}
|
||||
return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ import java.util.function.Predicate;
|
|||
* Indices - a permission that is based on privileges for index related actions executed
|
||||
* on specific indices
|
||||
* </li>
|
||||
* <li>RunAs - a permissions that is based on a general privilege that contains patterns of users that this
|
||||
* user can execute a request as
|
||||
* </li>
|
||||
* <li>
|
||||
* Global - a composite permission that combines a both cluster & indices permissions
|
||||
* </li>
|
||||
|
@ -55,14 +58,16 @@ public interface Permission {
|
|||
|
||||
static class Global implements Permission {
|
||||
|
||||
public static final Global NONE = new Global(Cluster.Core.NONE, Indices.Core.NONE);
|
||||
public static final Global NONE = new Global(Cluster.Core.NONE, Indices.Core.NONE, RunAs.Core.NONE);
|
||||
|
||||
private final Cluster cluster;
|
||||
private final Indices indices;
|
||||
private final RunAs runAs;
|
||||
|
||||
Global(Cluster cluster, Indices indices) {
|
||||
Global(Cluster cluster, Indices indices, RunAs runAs) {
|
||||
this.cluster = cluster;
|
||||
this.indices = indices;
|
||||
this.runAs = runAs;
|
||||
}
|
||||
|
||||
public Cluster cluster() {
|
||||
|
@ -73,9 +78,13 @@ public interface Permission {
|
|||
return indices;
|
||||
}
|
||||
|
||||
public RunAs runAs() {
|
||||
return runAs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return (cluster == null || cluster.isEmpty()) && (indices == null || indices.isEmpty());
|
||||
return (cluster == null || cluster.isEmpty()) && (indices == null || indices.isEmpty()) && (runAs == null || runAs.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,8 +112,8 @@ public interface Permission {
|
|||
|
||||
private final String name;
|
||||
|
||||
private Role(String name, Cluster.Core cluster, Indices.Core indices) {
|
||||
super(cluster, indices);
|
||||
private Role(String name, Cluster.Core cluster, Indices.Core indices, RunAs.Core runAs) {
|
||||
super(cluster, indices, runAs);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
@ -122,6 +131,11 @@ public interface Permission {
|
|||
return (Indices.Core) super.indices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunAs.Core runAs() {
|
||||
return (RunAs.Core) super.runAs();
|
||||
}
|
||||
|
||||
public static Builder builder(String name) {
|
||||
return new Builder(name);
|
||||
}
|
||||
|
@ -130,17 +144,24 @@ public interface Permission {
|
|||
|
||||
private final String name;
|
||||
private Cluster.Core cluster = Cluster.Core.NONE;
|
||||
private RunAs.Core runAs = RunAs.Core.NONE;
|
||||
private List<Indices.Group> groups = new ArrayList<>();
|
||||
|
||||
private Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder set(Privilege.Cluster privilege) {
|
||||
// FIXME we should throw an exception if we have already set cluster or runAs...
|
||||
public Builder cluster(Privilege.Cluster privilege) {
|
||||
cluster = new Cluster.Core(privilege);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder runAs(Privilege.General privilege) {
|
||||
runAs = new RunAs.Core(privilege);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(Privilege.Index privilege, String... indices) {
|
||||
groups.add(new Indices.Group(privilege, null, null, indices));
|
||||
return this;
|
||||
|
@ -153,7 +174,7 @@ public interface Permission {
|
|||
|
||||
public Role build() {
|
||||
Indices.Core indices = groups.isEmpty() ? Indices.Core.NONE : new Indices.Core(groups.toArray(new Indices.Group[groups.size()]));
|
||||
return new Role(name, cluster, indices);
|
||||
return new Role(name, cluster, indices, runAs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +182,7 @@ public interface Permission {
|
|||
static class Compound extends Global {
|
||||
|
||||
public Compound(List<Global> globals) {
|
||||
super(new Cluster.Globals(globals), new Indices.Globals(globals));
|
||||
super(new Cluster.Globals(globals), new Indices.Globals(globals), new RunAs.Globals(globals));
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
|
@ -551,4 +572,69 @@ public interface Permission {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME let's split this up, 11 classes before this in a single file that aren't documented and are extremely important
|
||||
static interface RunAs extends Permission {
|
||||
|
||||
/**
|
||||
* Checks if this permission grants run as to the specified user
|
||||
*/
|
||||
boolean check(String username);
|
||||
|
||||
class Core implements RunAs {
|
||||
|
||||
public static final Core NONE = new Core(Privilege.General.NONE);
|
||||
|
||||
private final Privilege.General privilege;
|
||||
private final Predicate<String> predicate;
|
||||
|
||||
public Core(Privilege.General privilege) {
|
||||
this.privilege = privilege;
|
||||
this.predicate = privilege.predicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String username) {
|
||||
return predicate.test(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this == NONE;
|
||||
}
|
||||
}
|
||||
|
||||
class Globals implements RunAs {
|
||||
private final List<Global> globals;
|
||||
|
||||
public Globals(List<Global> globals) {
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String username) {
|
||||
if (globals == null) {
|
||||
return false;
|
||||
}
|
||||
for (Global global : globals) {
|
||||
if (global.runAs().check(username)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
if (globals == null || globals.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (Global global : globals) {
|
||||
if (!global.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
|
||||
public static class General extends AutomatonPrivilege<General> {
|
||||
|
||||
private static final General NONE = new General(Name.NONE, BasicAutomata.makeEmpty());
|
||||
public static final General NONE = new General(Name.NONE, BasicAutomata.makeEmpty());
|
||||
|
||||
public General(String name, String... patterns) {
|
||||
super(name, patterns);
|
||||
|
|
|
@ -210,7 +210,7 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
|
|||
}
|
||||
if (name != null) {
|
||||
try {
|
||||
permission.set(Privilege.Cluster.get(name));
|
||||
permission.cluster(Privilege.Cluster.get(name));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("invalid role definition [{}] in roles file [{}]. could not resolve cluster privileges [{}]. skipping role...", roleName, path.toAbsolutePath(), name);
|
||||
return null;
|
||||
|
@ -324,6 +324,37 @@ public class FileRolesStore extends AbstractLifecycleComponent<RolesStore> imple
|
|||
roleName, path.toAbsolutePath(), token);
|
||||
return null;
|
||||
}
|
||||
} else if ("run_as".equals(currentFieldName)) {
|
||||
Set<String> names = new HashSet<>();
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
String namesStr = parser.text().trim();
|
||||
if (Strings.hasLength(namesStr)) {
|
||||
String[] namesArr = COMMA_DELIM.split(namesStr);
|
||||
names.addAll(Arrays.asList(namesArr));
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
names.add(parser.text());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error("invalid role definition [{}] in roles file [{}]. [run_as] field value can either " +
|
||||
"be a string or a list of strings, but [{}] was found instead. skipping role...",
|
||||
roleName, path.toAbsolutePath(), token);
|
||||
return null;
|
||||
}
|
||||
if (!names.isEmpty()) {
|
||||
Privilege.Name name = new Privilege.Name(names);
|
||||
try {
|
||||
permission.runAs(new Privilege.General(new Privilege.Name(names), names.toArray(new String[names.size()])));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("invalid role definition [{}] in roles file [{}]. could not resolve run_as privileges [{}]. skipping role...", roleName, path.toAbsolutePath(), name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("unknown field [{}] found in role definition [{}] in roles file [{}]", currentFieldName, roleName, path.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
return permission.build();
|
||||
|
|
|
@ -56,6 +56,16 @@
|
|||
"index": "not_analyzed",
|
||||
"doc_values": true
|
||||
},
|
||||
"run_by_principal": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed",
|
||||
"doc_values": true
|
||||
},
|
||||
"run_as_principal": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed",
|
||||
"doc_values": true
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed",
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.shield;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class UserTests extends ESTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteToAndReadFrom() throws Exception {
|
||||
User user = new User.Simple(randomAsciiOfLengthBetween(4, 30), generateRandomStringArray(20, 30, false));
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
|
||||
User.writeTo(user, output);
|
||||
User readFrom = User.readFrom(ByteBufferStreamInput.wrap(output.bytes()));
|
||||
|
||||
assertThat(readFrom, not(sameInstance(user)));
|
||||
assertThat(readFrom.principal(), is(user.principal()));
|
||||
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
|
||||
assertThat(readFrom.runAs(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteToAndReadFromWithRunAs() throws Exception {
|
||||
User runAs = new User.Simple(randomAsciiOfLengthBetween(4, 30), randomBoolean() ? generateRandomStringArray(20, 30, false) : null);
|
||||
User user = new User.Simple(randomAsciiOfLengthBetween(4, 30), generateRandomStringArray(20, 30, false), runAs);
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
|
||||
User.writeTo(user, output);
|
||||
User readFrom = User.readFrom(ByteBufferStreamInput.wrap(output.bytes()));
|
||||
|
||||
assertThat(readFrom, not(sameInstance(user)));
|
||||
assertThat(readFrom.principal(), is(user.principal()));
|
||||
assertThat(Arrays.equals(readFrom.roles(), user.roles()), is(true));
|
||||
assertThat(readFrom.runAs(), is(notNullValue()));
|
||||
User readFromRunAs = readFrom.runAs();
|
||||
assertThat(readFromRunAs.principal(), is(runAs.principal()));
|
||||
assertThat(Arrays.equals(readFromRunAs.roles(), runAs.roles()), is(true));
|
||||
assertThat(readFromRunAs.runAs(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemReadAndWrite() throws Exception {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
|
||||
User.writeTo(User.SYSTEM, output);
|
||||
User readFrom = User.readFrom(ByteBufferStreamInput.wrap(output.bytes()));
|
||||
|
||||
assertThat(readFrom, is(sameInstance(User.SYSTEM)));
|
||||
assertThat(readFrom.principal(), is(User.SYSTEM.principal()));
|
||||
assertThat(Arrays.equals(readFrom.roles(), User.SYSTEM.roles()), is(true));
|
||||
assertThat(readFrom.runAs(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFakeSystemUserSerialization() throws Exception {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
output.writeBoolean(true);
|
||||
output.writeString(randomAsciiOfLengthBetween(4, 30));
|
||||
try {
|
||||
User.readFrom(ByteBufferStreamInput.wrap(output.bytes()));
|
||||
fail("system user had wrong name");
|
||||
} catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUserRunningAsSystemUser() throws Exception {
|
||||
try {
|
||||
new User.Simple(randomAsciiOfLengthBetween(3, 10), generateRandomStringArray(16, 30, false), User.SYSTEM);
|
||||
fail("should not be able to create a runAs user with the system user");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("system"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ public class ShieldActionFilterTests extends ESTestCase {
|
|||
ActionRequest request = mock(ActionRequest.class);
|
||||
ActionListener listener = mock(ActionListener.class);
|
||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
User user = new User.Simple("username", "r1", "r2");
|
||||
User user = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||
doReturn(request).when(spy(filter)).unsign(user, "_action", request);
|
||||
filter.apply("_action", request, listener, chain);
|
||||
|
@ -70,7 +70,7 @@ public class ShieldActionFilterTests extends ESTestCase {
|
|||
ActionListener listener = mock(ActionListener.class);
|
||||
ActionFilterChain chain = mock(ActionFilterChain.class);
|
||||
RuntimeException exception = new RuntimeException("process-error");
|
||||
User user = new User.Simple("username", "r1", "r2");
|
||||
User user = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(authcService.authenticate("_action", request, User.SYSTEM)).thenReturn(user);
|
||||
doThrow(exception).when(authzService).authorize(user, "_action", request);
|
||||
filter.apply("_action", request, listener, chain);
|
||||
|
|
|
@ -106,7 +106,7 @@ public class AuditTrailServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAccessGranted() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
service.accessGranted(user, "_action", message);
|
||||
for (AuditTrail auditTrail : auditTrails) {
|
||||
verify(auditTrail).accessGranted(user, "_action", message);
|
||||
|
@ -115,7 +115,7 @@ public class AuditTrailServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAccessDenied() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
service.accessDenied(user, "_action", message);
|
||||
for (AuditTrail auditTrail : auditTrails) {
|
||||
verify(auditTrail).accessDenied(user, "_action", message);
|
||||
|
|
|
@ -240,7 +240,7 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
if (message instanceof IndicesRequest) {
|
||||
List<Object> indices = hit.field("indices").getValues();
|
||||
assertThat(indices, contains((Object[]) ((IndicesRequest)message).indices()));
|
||||
assertThat(indices, contains((Object[]) ((IndicesRequest) message).indices()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,7 +322,7 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
if (message instanceof IndicesRequest) {
|
||||
List<Object> indices = hit.field("indices").getValues();
|
||||
assertThat(indices, contains((Object[]) ((IndicesRequest)message).indices()));
|
||||
assertThat(indices, contains((Object[]) ((IndicesRequest) message).indices()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -460,13 +460,25 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
|
||||
initialize();
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.accessGranted(new User.Simple("_username", "r1"), "_action", message);
|
||||
final boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
auditor.accessGranted(user, "_action", message);
|
||||
awaitIndexCreation(resolveIndexName());
|
||||
|
||||
SearchHit hit = getIndexedAuditMessage();
|
||||
assertAuditMessage(hit, "transport", "access_granted");
|
||||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
if (runAs) {
|
||||
assertThat((String) hit.field("principal").getValue(), is("running as"));
|
||||
assertThat((String) hit.field("run_by_principal").getValue(), is("_username"));
|
||||
} else {
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
}
|
||||
assertEquals("_action", hit.field("action").getValue());
|
||||
if (message instanceof IndicesRequest) {
|
||||
List<Object> indices = hit.field("indices").getValues();
|
||||
|
@ -478,7 +490,7 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
public void testAccessGranted_Muted() throws Exception {
|
||||
initialize("access_granted");
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.accessGranted(new User.Simple("_username", "r1"), "_action", message);
|
||||
auditor.accessGranted(new User.Simple("_username", new String[]{"r1"}), "_action", message);
|
||||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
|
@ -510,13 +522,25 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
|
||||
initialize();
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.accessDenied(new User.Simple("_username", "r1"), "_action", message);
|
||||
final boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
auditor.accessDenied(user, "_action", message);
|
||||
awaitIndexCreation(resolveIndexName());
|
||||
|
||||
SearchHit hit = getIndexedAuditMessage();
|
||||
assertAuditMessage(hit, "transport", "access_denied");
|
||||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
if (runAs) {
|
||||
assertThat((String) hit.field("principal").getValue(), is("running as"));
|
||||
assertThat((String) hit.field("run_by_principal").getValue(), is("_username"));
|
||||
} else {
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
}
|
||||
assertEquals("_action", hit.field("action").getValue());
|
||||
if (message instanceof IndicesRequest) {
|
||||
List<Object> indices = hit.field("indices").getValues();
|
||||
|
@ -528,7 +552,7 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
public void testAccessDenied_Muted() throws Exception {
|
||||
initialize("access_denied");
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.accessDenied(new User.Simple("_username", "r1"), "_action", message);
|
||||
auditor.accessDenied(new User.Simple("_username", new String[]{"r1"}), "_action", message);
|
||||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
|
@ -537,14 +561,26 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
|
||||
initialize();
|
||||
TransportRequest message = new RemoteHostMockTransportRequest();
|
||||
auditor.tamperedRequest(new User.Simple("_username", "r1"), "_action", message);
|
||||
final boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
auditor.tamperedRequest(user, "_action", message);
|
||||
awaitIndexCreation(resolveIndexName());
|
||||
|
||||
SearchHit hit = getIndexedAuditMessage();
|
||||
|
||||
assertAuditMessage(hit, "transport", "tampered_request");
|
||||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
if (runAs) {
|
||||
assertThat((String) hit.field("principal").getValue(), is("running as"));
|
||||
assertThat((String) hit.field("run_by_principal").getValue(), is("_username"));
|
||||
} else {
|
||||
assertEquals("_username", hit.field("principal").getValue());
|
||||
}
|
||||
assertEquals("_action", hit.field("action").getValue());
|
||||
}
|
||||
|
||||
|
@ -552,7 +588,7 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
public void testTamperedRequest_Muted() throws Exception {
|
||||
initialize("tampered_request");
|
||||
TransportRequest message = new RemoteHostMockTransportRequest();
|
||||
auditor.tamperedRequest(new User.Simple("_username", "r1"), "_action", message);
|
||||
auditor.tamperedRequest(new User.Simple("_username", new String[]{"r1"}), "_action", message);
|
||||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
|
@ -606,6 +642,54 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsGranted() throws Exception {
|
||||
initialize();
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
User user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
auditor.runAsGranted(user, "_action", message);
|
||||
awaitIndexCreation(resolveIndexName());
|
||||
|
||||
SearchHit hit = getIndexedAuditMessage();
|
||||
assertAuditMessage(hit, "transport", "run_as_granted");
|
||||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
assertThat((String) hit.field("principal").getValue(), is("_username"));
|
||||
assertThat((String) hit.field("run_as_principal").getValue(), is("running as"));
|
||||
assertEquals("_action", hit.field("action").getValue());
|
||||
}
|
||||
|
||||
@Test(expected = IndexNotFoundException.class)
|
||||
public void testRunAsGranted_Muted() throws Exception {
|
||||
initialize("run_as_granted");
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.runAsGranted(new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[]{"r2"})), "_action", message);
|
||||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsDenied() throws Exception {
|
||||
initialize();
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
User user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
auditor.runAsDenied(user, "_action", message);
|
||||
awaitIndexCreation(resolveIndexName());
|
||||
|
||||
SearchHit hit = getIndexedAuditMessage();
|
||||
assertAuditMessage(hit, "transport", "run_as_denied");
|
||||
assertEquals("transport", hit.field("origin_type").getValue());
|
||||
assertThat((String) hit.field("principal").getValue(), is("_username"));
|
||||
assertThat((String) hit.field("run_as_principal").getValue(), is("running as"));
|
||||
assertEquals("_action", hit.field("action").getValue());
|
||||
}
|
||||
|
||||
@Test(expected = IndexNotFoundException.class)
|
||||
public void testRunAsDenied_Muted() throws Exception {
|
||||
initialize("run_as_denied");
|
||||
TransportMessage message = randomFrom(new RemoteHostMockMessage(), new LocalHostMockMessage(), new MockIndicesTransportMessage());
|
||||
auditor.runAsDenied(new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[]{"r2"})), "_action", message);
|
||||
getClient().prepareExists(resolveIndexName()).execute().actionGet();
|
||||
}
|
||||
|
||||
private void assertAuditMessage(SearchHit hit, String layer, String type) {
|
||||
assertThat(hit.field("@timestamp").getValue(), notNullValue());
|
||||
DateTime dateTime = ISODateTimeFormat.dateTimeParser().withZoneUTC().parseDateTime((String) hit.field("@timestamp").getValue());
|
||||
|
@ -698,6 +782,8 @@ public class IndexAuditTrailTests extends ShieldIntegTestCase {
|
|||
"origin_address",
|
||||
"origin_type",
|
||||
"principal",
|
||||
"run_by_principal",
|
||||
"run_as_principal",
|
||||
"action",
|
||||
"indices",
|
||||
"request",
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.shield.audit.index;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.inject.util.Providers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.BoundTransportAddress;
|
||||
import org.elasticsearch.common.transport.DummyTransportAddress;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.test.*;
|
||||
import org.elasticsearch.test.rest.FakeRestRequest;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.Transport;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.shield.audit.index.IndexNameResolver.Rollover.*;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* This test checks to make sure that the index audit trail actually updates the mappings on startups
|
||||
*/
|
||||
public class IndexAuditTrailUpdateMappingTests extends ShieldIntegTestCase {
|
||||
|
||||
private ThreadPool threadPool;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
threadPool = new ThreadPool("index audit trail update mapping tests");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMappingIsUpdated() throws Exception {
|
||||
// Setup
|
||||
IndexNameResolver.Rollover rollover = randomFrom(HOURLY, DAILY, WEEKLY, MONTHLY);
|
||||
AuthenticationService authService = mock(AuthenticationService.class);
|
||||
Settings settings = Settings.builder().put("shield.audit.index.rollover", rollover.name().toLowerCase(Locale.ENGLISH)).put("path.home", createTempDir()).build();
|
||||
Transport transport = mock(Transport.class);
|
||||
when(transport.boundAddress()).thenReturn(new BoundTransportAddress(DummyTransportAddress.INSTANCE, DummyTransportAddress.INSTANCE));
|
||||
Environment env = new Environment(settings);
|
||||
IndexAuditTrail auditor = new IndexAuditTrail(settings, new IndexAuditUserHolder(), env, authService, transport, Providers.of(client()), threadPool, mock(ClusterService.class));
|
||||
|
||||
// before starting we add an event
|
||||
auditor.authenticationFailed(new FakeRestRequest());
|
||||
IndexAuditTrail.Message message = auditor.peek();
|
||||
|
||||
// resolve the index name and force create it
|
||||
final String indexName = IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, message.timestamp, rollover);
|
||||
client().admin().indices().prepareCreate(indexName).get();
|
||||
ensureGreen(indexName);
|
||||
|
||||
// default mapping
|
||||
GetMappingsResponse response = client().admin().indices().prepareGetMappings(indexName).get();
|
||||
|
||||
try {
|
||||
// start the audit trail which should update the mappings since it is the master
|
||||
auditor.start(true);
|
||||
|
||||
// get the updated mappings
|
||||
GetMappingsResponse updated = client().admin().indices().prepareGetMappings(indexName).get();
|
||||
assertThat(response.mappings().get(indexName).get(IndexAuditTrail.DOC_TYPE), nullValue());
|
||||
assertThat(updated.mappings().get(indexName).get(IndexAuditTrail.DOC_TYPE), notNullValue());
|
||||
} finally {
|
||||
auditor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeIndexDeletion() {
|
||||
// no-op here because of the shard counter check
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdown() {
|
||||
if (threadPool != null) {
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -332,8 +332,16 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
CapturingLogger logger = new CapturingLogger(level);
|
||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger);
|
||||
TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);;
|
||||
auditTrail.accessGranted(new User.Simple("_username", "r1"), "_action", message);
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);
|
||||
boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]";
|
||||
auditTrail.accessGranted(user, "_action", message);
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
case WARN:
|
||||
|
@ -341,17 +349,17 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
break;
|
||||
case INFO:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[_action], indices=[idx1,idx2]");
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[_action], indices=[idx1,idx2]");
|
||||
} else {
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[_action]");
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[_action]");
|
||||
}
|
||||
break;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
} else {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[_action], request=[MockMessage]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[_action], request=[MockMessage]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -388,8 +396,16 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
CapturingLogger logger = new CapturingLogger(level);
|
||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger);
|
||||
TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);;
|
||||
auditTrail.accessGranted(new User.Simple("_username"), "internal:_action", message);
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);
|
||||
boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]";
|
||||
auditTrail.accessGranted(user, "internal:_action", message);
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
case WARN:
|
||||
|
@ -397,17 +413,17 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
break;
|
||||
case INFO:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[internal:_action], indices=[idx1,idx2]");
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[internal:_action], indices=[idx1,idx2]");
|
||||
} else {
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[internal:_action]");
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[internal:_action]");
|
||||
}
|
||||
break;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[internal:_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[internal:_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
} else {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", principal=[_username], action=[internal:_action], request=[MockMessage]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + ", action=[internal:_action], request=[MockMessage]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -419,24 +435,32 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
CapturingLogger logger = new CapturingLogger(level);
|
||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger);
|
||||
TransportMessage message = randomBoolean() ? new MockMessage() : new MockIndicesRequest();
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);;
|
||||
auditTrail.accessDenied(new User.Simple("_username", "r1"), "_action", message);
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);
|
||||
boolean runAs = randomBoolean();
|
||||
User user;
|
||||
if (runAs) {
|
||||
user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
} else {
|
||||
user = new User.Simple("_username", new String[]{"r1"});
|
||||
}
|
||||
String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]";
|
||||
auditTrail.accessDenied(user, "_action", message);
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
case WARN:
|
||||
case INFO:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.ERROR, prefix + "[transport] [access_denied]\t" + origins + ", principal=[_username], action=[_action], indices=[idx1,idx2]");
|
||||
assertMsg(logger, Level.ERROR, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + ", action=[_action], indices=[idx1,idx2]");
|
||||
} else {
|
||||
assertMsg(logger, Level.ERROR, prefix + "[transport] [access_denied]\t" + origins + ", principal=[_username], action=[_action]");
|
||||
assertMsg(logger, Level.ERROR, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + ", action=[_action]");
|
||||
}
|
||||
break;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
if (message instanceof IndicesRequest) {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_denied]\t" + origins + ", principal=[_username], action=[_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + ", action=[_action], indices=[idx1,idx2], request=[MockIndicesRequest]");
|
||||
} else {
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_denied]\t" + origins + ", principal=[_username], action=[_action], request=[MockMessage]");
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + ", action=[_action], request=[MockMessage]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -486,6 +510,54 @@ public class LoggingAuditTrailTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsGranted() throws Exception {
|
||||
for (Level level : Level.values()) {
|
||||
CapturingLogger logger = new CapturingLogger(level);
|
||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger);
|
||||
TransportMessage message = new MockMessage();
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);
|
||||
User user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
auditTrail.runAsGranted(user, "_action", message);
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
case WARN:
|
||||
assertEmptyLog(logger);
|
||||
break;
|
||||
case INFO:
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_granted]\t" + origins + ", principal=[_username], run_as_principal=[running as], action=[_action]");
|
||||
break;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [run_as_granted]\t" + origins + ", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsDenied() throws Exception {
|
||||
for (Level level : Level.values()) {
|
||||
CapturingLogger logger = new CapturingLogger(level);
|
||||
LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger);
|
||||
TransportMessage message = new MockMessage();
|
||||
String origins = LoggingAuditTrail.originAttributes(message, transport);
|
||||
User user = new User.Simple("_username", new String[]{"r1"}, new User.Simple("running as", new String[] {"r2"}));
|
||||
auditTrail.runAsDenied(user, "_action", message);
|
||||
switch (level) {
|
||||
case ERROR:
|
||||
case WARN:
|
||||
assertEmptyLog(logger);
|
||||
break;
|
||||
case INFO:
|
||||
assertMsg(logger, Level.INFO, prefix + "[transport] [run_as_denied]\t" + origins + ", principal=[_username], run_as_principal=[running as], action=[_action]");
|
||||
break;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
assertMsg(logger, Level.DEBUG, prefix + "[transport] [run_as_denied]\t" + origins + ", principal=[_username], run_as_principal=[running as], action=[_action], request=[MockMessage]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOriginAttributes() throws Exception {
|
||||
MockMessage message = new MockMessage();
|
||||
|
|
|
@ -51,7 +51,7 @@ public class AnonymousUserHolderTests extends ESTestCase {
|
|||
public void testWhenAnonymousDisabled() {
|
||||
AnonymousService anonymousService = new AnonymousService(Settings.EMPTY);
|
||||
assertThat(anonymousService.enabled(), is(false));
|
||||
assertThat(anonymousService.isAnonymous(new User.Simple(randomAsciiOfLength(10), randomAsciiOfLength(5))), is(false));
|
||||
assertThat(anonymousService.isAnonymous(new User.Simple(randomAsciiOfLength(10), new String[] { randomAsciiOfLength(5) })), is(false));
|
||||
assertThat(anonymousService.anonymousUser(), nullValue());
|
||||
assertThat(anonymousService.authorizationExceptionsEnabled(), is(true));
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthenticate_BothSupport_SecondSucceeds() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(null); // first fails
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
|
@ -135,7 +135,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthenticate_FirstNotSupporting_SecondSucceeds() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
when(firstRealm.supports(token)).thenReturn(false);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
|
@ -157,7 +157,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthenticate_Cached() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
message.putInContext(InternalAuthenticationService.USER_KEY, user);
|
||||
User result = service.authenticate("_action", message, null);
|
||||
assertThat(result, notNullValue());
|
||||
|
@ -201,7 +201,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testEncodeDecodeUser() throws Exception {
|
||||
User user = new User.Simple("username", "r1", "r2", "r3");
|
||||
User user = new User.Simple("username", new String[] { "r1", "r2", "r3" });
|
||||
String text = InternalAuthenticationService.encodeUser(user, null);
|
||||
User user2 = InternalAuthenticationService.decodeUser(text);
|
||||
assertThat(user, equalTo(user2));
|
||||
|
@ -213,7 +213,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testUserHeader() throws Exception {
|
||||
User user = new User.Simple("_username", "r1");
|
||||
User user = new User.Simple("_username", new String[] { "r1" });
|
||||
when(firstRealm.token(message)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
|
@ -260,7 +260,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
public void testAuthenticate_Transport_Fallback() throws Exception {
|
||||
when(firstRealm.token(message)).thenReturn(null);
|
||||
when(secondRealm.token(message)).thenReturn(null);
|
||||
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||
User.Simple user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user1, null))).thenReturn("_signed_user");
|
||||
User user2 = service.authenticate("_action", message, user1);
|
||||
assertThat(user1, sameInstance(user2));
|
||||
|
@ -270,7 +270,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAuthenticate_Transport_Success_NoFallback() throws Exception {
|
||||
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||
User.Simple user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(firstRealm.token(message)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
|
@ -283,7 +283,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAuthenticate_Transport_Success_WithFallback() throws Exception {
|
||||
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||
User.Simple user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(firstRealm.token(message)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
|
@ -296,7 +296,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAuthenticate_Rest_Success() throws Exception {
|
||||
User.Simple user1 = new User.Simple("username", "r1", "r2");
|
||||
User.Simple user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(firstRealm.token(restRequest)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
|
@ -307,7 +307,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAutheticate_Transport_ContextAndHeader() throws Exception {
|
||||
User user1 = new User.Simple("username", "r1", "r2");
|
||||
User user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(firstRealm.token(message)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
|
@ -345,7 +345,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
Settings settings = Settings.builder().put(InternalAuthenticationService.SETTING_SIGN_USER_HEADER, false).build();
|
||||
service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, anonymousService, new DefaultAuthenticationFailureHandler());
|
||||
|
||||
User user1 = new User.Simple("username", "r1", "r2");
|
||||
User user1 = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.token(message)).thenReturn(token);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
|
@ -380,7 +380,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAttachIfMissing_Missing() throws Exception {
|
||||
User user = new User.Simple("username", "r1", "r2");
|
||||
User user = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), nullValue());
|
||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), nullValue());
|
||||
when(cryptoService.sign(InternalAuthenticationService.encodeUser(user, null))).thenReturn("_signed_user");
|
||||
|
@ -400,10 +400,10 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testAttachIfMissing_Exists() throws Exception {
|
||||
User user = new User.Simple("username", "r1", "r2");
|
||||
User user = new User.Simple("username", new String[] { "r1", "r2" });
|
||||
message.putInContext(InternalAuthenticationService.USER_KEY, user);
|
||||
message.putHeader(InternalAuthenticationService.USER_KEY, "_signed_user");
|
||||
service.attachUserHeaderIfMissing(message, new User.Simple("username2", "r3", "r4"));
|
||||
service.attachUserHeaderIfMissing(message, new User.Simple("username2", new String[] { "r3", "r4" }));
|
||||
assertThat(message.getFromContext(InternalAuthenticationService.USER_KEY), sameInstance((Object) user));
|
||||
assertThat(message.getHeader(InternalAuthenticationService.USER_KEY), equalTo((Object) "_signed_user"));
|
||||
}
|
||||
|
@ -541,6 +541,165 @@ public class InternalAuthenticationServiceTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmLookupThrowingException() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
message.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(message)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenThrow(authenticationError("realm doesn't want to lookup"));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't want to lookup"));
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmLookupThrowingException_Rest() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"), Collections.<String, String>emptyMap());
|
||||
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenThrow(authenticationError("realm doesn't want to lookup"));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
fail("exception should bubble out");
|
||||
} catch (ElasticsearchException e) {
|
||||
assertThat(e.getMessage(), is("realm doesn't want to lookup"));
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsLookupSameRealm() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
message.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(message)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenReturn(new User.Simple("looked up user", new String[]{"some role"}));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
User authenticated = service.authenticate("_action", message, null);
|
||||
|
||||
assertThat(authenticated.isSystem(), is(false));
|
||||
assertThat(authenticated.runAs(), is(notNullValue()));
|
||||
assertThat(authenticated.principal(), is("lookup user"));
|
||||
assertThat(authenticated.roles(), arrayContaining("user"));
|
||||
assertThat(authenticated.runAs().principal(), is("looked up user"));
|
||||
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), sameInstance((Object) authenticated));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsLookupSameRealm_Rest() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"), Collections.<String, String>emptyMap());
|
||||
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenReturn(new User.Simple("looked up user", new String[]{"some role"}));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
User authenticated = service.authenticate(restRequest);
|
||||
|
||||
assertThat(authenticated.isSystem(), is(false));
|
||||
assertThat(authenticated.runAs(), is(notNullValue()));
|
||||
assertThat(authenticated.principal(), is("lookup user"));
|
||||
assertThat(authenticated.roles(), arrayContaining("user"));
|
||||
assertThat(authenticated.runAs().principal(), is("looked up user"));
|
||||
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
|
||||
assertThat(restRequest.getContext().get(InternalAuthenticationService.USER_KEY), sameInstance((Object) authenticated));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsLookupDifferentRealm() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
message.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(message)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(firstRealm.userLookupSupported()).thenReturn(true);
|
||||
when(firstRealm.lookupUser("run_as")).thenReturn(new User.Simple("looked up user", new String[]{"some role"}));
|
||||
when(firstRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
User authenticated = service.authenticate("_action", message, null);
|
||||
|
||||
assertThat(authenticated.isSystem(), is(false));
|
||||
assertThat(authenticated.runAs(), is(notNullValue()));
|
||||
assertThat(authenticated.principal(), is("lookup user"));
|
||||
assertThat(authenticated.roles(), arrayContaining("user"));
|
||||
assertThat(authenticated.runAs().principal(), is("looked up user"));
|
||||
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
|
||||
assertThat(message.getContext().get(InternalAuthenticationService.USER_KEY), sameInstance((Object) authenticated));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsLookupDifferentRealm_Rest() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"), Collections.<String, String>emptyMap());
|
||||
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(firstRealm.lookupUser("run_as")).thenReturn(new User.Simple("looked up user", new String[]{"some role"}));
|
||||
when(firstRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
User authenticated = service.authenticate(restRequest);
|
||||
|
||||
assertThat(authenticated.isSystem(), is(false));
|
||||
assertThat(authenticated.runAs(), is(notNullValue()));
|
||||
assertThat(authenticated.principal(), is("lookup user"));
|
||||
assertThat(authenticated.roles(), arrayContaining("user"));
|
||||
assertThat(authenticated.runAs().principal(), is("looked up user"));
|
||||
assertThat(authenticated.runAs().roles(), arrayContaining("some role"));
|
||||
assertThat(restRequest.getContext().get(InternalAuthenticationService.USER_KEY), sameInstance((Object) authenticated));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsWithEmptyRunAsUsername_Rest() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
restRequest = new FakeRestRequest(Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, ""), Collections.<String, String>emptyMap());
|
||||
when(secondRealm.token(restRequest)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate(restRequest);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).authenticationFailed(token, restRequest);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsWithEmptyRunAsUsername() throws Exception {
|
||||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
message.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "");
|
||||
when(secondRealm.token(message)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User.Simple("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
service.authenticate("_action", message, null);
|
||||
fail("exception should be thrown");
|
||||
} catch (ElasticsearchException e) {
|
||||
verify(auditTrail).authenticationFailed(token, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InternalMessage extends TransportMessage<InternalMessage> {
|
||||
}
|
||||
|
||||
|
|
|
@ -167,6 +167,16 @@ public class RealmsTests extends ESTestCase {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User lookupUser(String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static class Factory extends Realm.Factory<DummyRealm> {
|
||||
|
||||
public Factory(String type, boolean internal) {
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* 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.shield.authc;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.authc.support.SecuredStringTests;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.test.ShieldIntegTestCase;
|
||||
import org.elasticsearch.test.ShieldSettingsSource;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RunAsIntegTests extends ShieldIntegTestCase {
|
||||
|
||||
static final String RUN_AS_USER = "run_as_user";
|
||||
static final String TRANSPORT_CLIENT_USER = "transport_user";
|
||||
static final String ROLES =
|
||||
"transport_client:\n" +
|
||||
" cluster: cluster:monitor/nodes/liveness\n" +
|
||||
"run_as_role:\n" +
|
||||
" run_as: " + ShieldSettingsSource.DEFAULT_USER_NAME + ",idontexist\n";
|
||||
|
||||
@Override
|
||||
public Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.builder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put(Node.HTTP_ENABLED, true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sslTransportEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String configRoles() {
|
||||
return ROLES + super.configRoles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String configUsers() {
|
||||
return super.configUsers()
|
||||
+ RUN_AS_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD_HASHED + "\n"
|
||||
+ TRANSPORT_CLIENT_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD_HASHED + "\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String configUsersRoles() {
|
||||
return super.configUsersRoles()
|
||||
+ "run_as_role:" + RUN_AS_USER + "\n"
|
||||
+ "transport_client:" + TRANSPORT_CLIENT_USER;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserImpersonation() throws Exception {
|
||||
try (TransportClient client = getTransportClient(Settings.builder().put("shield.user", TRANSPORT_CLIENT_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD).build())) {
|
||||
//ensure the client can connect
|
||||
awaitBusy(() -> {
|
||||
return client.connectedNodes().size() > 0;
|
||||
});
|
||||
|
||||
// make sure the client can't get health
|
||||
try {
|
||||
client.admin().cluster().prepareHealth().get();
|
||||
fail("the client user should not have privileges to get the health");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("unauthorized"));
|
||||
}
|
||||
|
||||
// let's run as without authorization
|
||||
try {
|
||||
client.admin().cluster().prepareHealth().putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ShieldSettingsSource.DEFAULT_USER_NAME).get();
|
||||
fail("run as should be unauthorized for the transport client user");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("unauthorized"));
|
||||
assertThat(e.getMessage(), containsString("run as"));
|
||||
}
|
||||
|
||||
// lets set the user
|
||||
ClusterHealthResponse response = client.admin().cluster().prepareHealth()
|
||||
.putHeader("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray())))
|
||||
.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ShieldSettingsSource.DEFAULT_USER_NAME).get();
|
||||
assertThat(response.isTimedOut(), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserImpersonationUsingHttp() throws Exception {
|
||||
// use the transport client user and try to run as
|
||||
HttpResponse response = httpClient().method("GET")
|
||||
.path("/_nodes")
|
||||
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(TRANSPORT_CLIENT_USER, SecuredStringTests.build(ShieldSettingsSource.DEFAULT_PASSWORD)))
|
||||
.addHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ShieldSettingsSource.DEFAULT_USER_NAME)
|
||||
.execute();
|
||||
assertThat(response.getStatusCode(), is(403));
|
||||
|
||||
//the run as user shouldn't have access to the nodes api
|
||||
response = httpClient().method("GET")
|
||||
.path("/_nodes")
|
||||
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(ShieldSettingsSource.DEFAULT_PASSWORD)))
|
||||
.execute();
|
||||
assertThat(response.getStatusCode(), is(403));
|
||||
|
||||
// but when running as a different user it should work
|
||||
response = httpClient().method("GET")
|
||||
.path("/_nodes")
|
||||
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(ShieldSettingsSource.DEFAULT_PASSWORD)))
|
||||
.addHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ShieldSettingsSource.DEFAULT_USER_NAME)
|
||||
.execute();
|
||||
assertThat(response.getStatusCode(), is(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyUserImpersonationHeader() throws Exception {
|
||||
try (TransportClient client = getTransportClient(Settings.builder().put("shield.user", TRANSPORT_CLIENT_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD).build())) {
|
||||
//ensure the client can connect
|
||||
awaitBusy(() -> {
|
||||
return client.connectedNodes().size() > 0;
|
||||
});
|
||||
|
||||
try {
|
||||
client.admin().cluster().prepareHealth()
|
||||
.putHeader("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray())))
|
||||
.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "").get();
|
||||
fail("run as header should not be allowed to be empty");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("unable to authenticate"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyHeaderUsingHttp() throws Exception {
|
||||
HttpResponse response = httpClient().method("GET")
|
||||
.path("/_nodes")
|
||||
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(ShieldSettingsSource.DEFAULT_PASSWORD)))
|
||||
.addHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "")
|
||||
.execute();
|
||||
assertThat(response.getStatusCode(), is(401));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonExistentRunAsUser() throws Exception {
|
||||
try (TransportClient client = getTransportClient(Settings.builder().put("shield.user", TRANSPORT_CLIENT_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD).build())) {
|
||||
//ensure the client can connect
|
||||
awaitBusy(() -> {
|
||||
return client.connectedNodes().size() > 0;
|
||||
});
|
||||
|
||||
try {
|
||||
client.admin().cluster().prepareHealth()
|
||||
.putHeader("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(ShieldSettingsSource.DEFAULT_PASSWORD.toCharArray())))
|
||||
.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "idontexist").get();
|
||||
fail("run as header should not accept non-existent users");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("unauthorized"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonExistentRunAsUserUsingHttp() throws Exception {
|
||||
HttpResponse response = httpClient().method("GET")
|
||||
.path("/_nodes")
|
||||
.addHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(ShieldSettingsSource.DEFAULT_PASSWORD)))
|
||||
.addHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "idontexist")
|
||||
.execute();
|
||||
assertThat(response.getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// build our own here to better mimic an actual client...
|
||||
TransportClient getTransportClient(Settings extraSettings) {
|
||||
NodesInfoResponse nodeInfos = client().admin().cluster().prepareNodesInfo().get();
|
||||
NodeInfo[] nodes = nodeInfos.getNodes();
|
||||
assertTrue(nodes.length > 0);
|
||||
TransportAddress publishAddress = randomFrom(nodes).getTransport().address().publishAddress();
|
||||
String clusterName = nodeInfos.getClusterNameAsString();
|
||||
|
||||
Settings settings = Settings.builder()
|
||||
.put(extraSettings)
|
||||
.put("cluster.name", clusterName)
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
|
||||
return TransportClient.builder()
|
||||
.settings(settings)
|
||||
.addPlugin(ShieldPlugin.class)
|
||||
.build()
|
||||
.addTransportAddress(publishAddress);
|
||||
}
|
||||
}
|
|
@ -79,13 +79,14 @@ public class ESUsersRealmTests extends ESTestCase {
|
|||
.build();
|
||||
RealmConfig config = new RealmConfig("esusers-test", settings, globalSettings);
|
||||
when(userPasswdStore.verifyPassword("user1", SecuredStringTests.build("test123"))).thenReturn(true);
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});
|
||||
ESUsersRealm realm = new ESUsersRealm(config, userPasswdStore, userRolesStore);
|
||||
User user1 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
User user2 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
assertThat(user1, sameInstance(user2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate_Caching_Refresh() throws Exception {
|
||||
RealmConfig config = new RealmConfig("esusers-test", Settings.EMPTY, globalSettings);
|
||||
userPasswdStore = spy(new UserPasswdStore(config));
|
||||
|
@ -112,7 +113,7 @@ public class ESUsersRealmTests extends ESTestCase {
|
|||
public void testToken() throws Exception {
|
||||
RealmConfig config = new RealmConfig("esusers-test", Settings.EMPTY, globalSettings);
|
||||
when(userPasswdStore.verifyPassword("user1", SecuredStringTests.build("test123"))).thenReturn(true);
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});
|
||||
ESUsersRealm realm = new ESUsersRealm(config, userPasswdStore, userRolesStore);
|
||||
|
||||
TransportRequest request = new TransportRequest() {};
|
||||
|
@ -125,6 +126,59 @@ public class ESUsersRealmTests extends ESTestCase {
|
|||
assertThat(new String(token.credentials().internalChars()), equalTo("test123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookup() throws Exception {
|
||||
when(userPasswdStore.userExists("user1")).thenReturn(true);
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
|
||||
RealmConfig config = new RealmConfig("esusers-test", Settings.EMPTY, globalSettings);
|
||||
ESUsersRealm realm = new ESUsersRealm(config, userPasswdStore, userRolesStore);
|
||||
|
||||
User user = realm.lookupUser("user1");
|
||||
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.principal(), equalTo("user1"));
|
||||
assertThat(user.roles(), notNullValue());
|
||||
assertThat(user.roles().length, equalTo(2));
|
||||
assertThat(user.roles(), arrayContaining("role1", "role2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupCaching() throws Exception {
|
||||
when(userPasswdStore.userExists("user1")).thenReturn(true);
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
|
||||
RealmConfig config = new RealmConfig("esusers-test", Settings.EMPTY, globalSettings);
|
||||
ESUsersRealm realm = new ESUsersRealm(config, userPasswdStore, userRolesStore);
|
||||
|
||||
User user = realm.lookupUser("user1");
|
||||
User user1 = realm.lookupUser("user1");
|
||||
assertThat(user, sameInstance(user1));
|
||||
verify(userPasswdStore).userExists("user1");
|
||||
verify(userRolesStore).roles("user1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupCachingWithRefresh() throws Exception {
|
||||
RealmConfig config = new RealmConfig("esusers-test", Settings.EMPTY, globalSettings);
|
||||
userPasswdStore = spy(new UserPasswdStore(config));
|
||||
userRolesStore = spy(new UserRolesStore(config));
|
||||
doReturn(true).when(userPasswdStore).userExists("user1");
|
||||
doReturn(new String[] { "role1", "role2" }).when(userRolesStore).roles("user1");
|
||||
ESUsersRealm realm = new ESUsersRealm(config, userPasswdStore, userRolesStore);
|
||||
User user1 = realm.lookupUser("user1");
|
||||
User user2 = realm.lookupUser("user1");
|
||||
assertThat(user1, sameInstance(user2));
|
||||
userPasswdStore.notifyRefresh();
|
||||
User user3 = realm.lookupUser("user1");
|
||||
assertThat(user2, not(sameInstance(user3)));
|
||||
User user4 = realm.lookupUser("user1");
|
||||
assertThat(user3, sameInstance(user4));
|
||||
userRolesStore.notifyRefresh();
|
||||
User user5 = realm.lookupUser("user1");
|
||||
assertThat(user4, not(sameInstance(user5)));
|
||||
User user6 = realm.lookupUser("user1");
|
||||
assertThat(user5, sameInstance(user6));
|
||||
}
|
||||
|
||||
@Test @SuppressWarnings("unchecked")
|
||||
public void testAuthorizationHeaderIsNotCopied() throws Exception {
|
||||
RestController restController = mock(RestController.class);
|
||||
|
|
|
@ -103,6 +103,7 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
}
|
||||
});
|
||||
|
||||
assertThat(store.userExists("bcrypt"), is(true));
|
||||
assertThat(store.verifyPassword("bcrypt", SecuredStringTests.build("test123")), is(true));
|
||||
|
||||
watcherService.start();
|
||||
|
@ -116,8 +117,8 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
fail("Waited too long for the updated file to be picked up");
|
||||
}
|
||||
|
||||
assertThat(store.userExists("foobar"), is(true));
|
||||
assertThat(store.verifyPassword("foobar", SecuredStringTests.build("barfoo")), is(true));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -232,4 +233,12 @@ public class FileUserPasswdStoreTests extends ESTestCase {
|
|||
assertThat(msgs.get(0).text, containsString("failed to parse users file"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseFileWithLineWithEmptyPasswordAndWhitespace() throws Exception {
|
||||
Path file = createTempFile();
|
||||
Files.write(file, Collections.singletonList("user: "), Charsets.UTF_8);
|
||||
Map<String, char[]> users = FileUserPasswdStore.parseFile(file, null);
|
||||
assertThat(users, notNullValue());
|
||||
assertThat(users.keySet(), is(empty()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,24 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
globalSettings = settingsBuilder().put("path.home", createTempDir()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsUnauthenticatedSessions() throws Exception {
|
||||
RealmConfig config = new RealmConfig("ldap_realm", settingsBuilder()
|
||||
.put(buildLdapSettings(ldapUrl(), Strings.EMPTY_ARRAY, "", LdapSearchScope.SUB_TREE))
|
||||
.put("user_search.base_dn", "")
|
||||
.put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas")
|
||||
.put("bind_password", "pass")
|
||||
.put("user_search.attribute", "cn")
|
||||
.build(), globalSettings);
|
||||
|
||||
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null);
|
||||
try {
|
||||
assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true));
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSearchSubTree() throws Exception {
|
||||
String groupSearchBase = "o=sevenSeas";
|
||||
|
@ -87,9 +105,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
try {
|
||||
// auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -114,10 +141,20 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [base]"));
|
||||
try {
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [base]"));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [base]"));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -142,9 +179,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
try {
|
||||
// auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -169,10 +215,20 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [one_level]"));
|
||||
try {
|
||||
// auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [one_level]"));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [one_level]"));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -197,9 +253,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
try {
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString(user));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -223,11 +288,21 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "William Bush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [sub_tree]"));
|
||||
} finally {
|
||||
try {
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [sub_tree]"));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
fail("the user should not have been found");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("failed to find user [William Bush] with search base [o=sevenSeas] scope [sub_tree]"));
|
||||
}
|
||||
}finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
}
|
||||
|
@ -249,9 +324,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String user = "wbush";
|
||||
SecuredString userPass = SecuredStringTests.build("pass");
|
||||
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString("William Bush"));
|
||||
try {
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, userPass)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString("William Bush"));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
String dn = ldap.userDn();
|
||||
assertThat(dn, containsString("William Bush"));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -272,14 +356,28 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService);
|
||||
|
||||
String user = "Bruce Banner";
|
||||
try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(ActiveDirectorySessionFactoryTests.PASSWORD))) {
|
||||
List<String> groups = ldap.groups();
|
||||
try {
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(ActiveDirectorySessionFactoryTests.PASSWORD))) {
|
||||
List<String> groups = ldap.groups();
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists")));
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists")));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
List<String> groups = ldap.groups();
|
||||
|
||||
assertThat(groups, containsInAnyOrder(
|
||||
containsString("Avengers"),
|
||||
containsString("SHIELD"),
|
||||
containsString("Geniuses"),
|
||||
containsString("Philanthropists")));
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
}
|
||||
|
@ -300,10 +398,17 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
String[] users = new String[] { "cap", "hawkeye", "hulk", "ironman", "thor" };
|
||||
try {
|
||||
for (String user : users) {
|
||||
LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(OpenLdapTests.PASSWORD));
|
||||
assertThat(ldap.userDn(), is(equalTo(new MessageFormat("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", Locale.ROOT).format(new Object[] { user }, new StringBuffer(), null).toString())));
|
||||
assertThat(ldap.groups(), hasItem(containsString("Avengers")));
|
||||
ldap.close();
|
||||
//auth
|
||||
try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(OpenLdapTests.PASSWORD))) {
|
||||
assertThat(ldap.userDn(), is(equalTo(new MessageFormat("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", Locale.ROOT).format(new Object[]{user}, new StringBuffer(), null).toString())));
|
||||
assertThat(ldap.groups(), hasItem(containsString("Avengers")));
|
||||
}
|
||||
|
||||
//lookup
|
||||
try (LdapSession ldap = sessionFactory.unauthenticatedSession(user)) {
|
||||
assertThat(ldap.userDn(), is(equalTo(new MessageFormat("uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", Locale.ROOT).format(new Object[]{user}, new StringBuffer(), null).toString())));
|
||||
assertThat(ldap.groups(), hasItem(containsString("Avengers")));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sessionFactory.shutdown();
|
||||
|
@ -394,6 +499,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Network
|
||||
public void testThatLDAPServerConnectErrorDoesNotPreventNodeFromStarting() {
|
||||
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
|
||||
|
|
|
@ -47,6 +47,20 @@ public class SessionFactoryTests extends ESTestCase {
|
|||
assertThat(options.getSSLSocketVerifier(), is(instanceOf(TrustAllSSLSocketVerifier.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sessionFactoryDoesNotSupportUnauthenticated() {
|
||||
assertThat(createSessionFactory().supportsUnauthenticatedSession(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unauthenticatedSessionThrowsUnsupportedOperationException() throws Exception {
|
||||
try {
|
||||
createSessionFactory().unauthenticatedSession(randomAsciiOfLength(5));
|
||||
fail("session factory should throw an unsupported operation exception");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected...
|
||||
}
|
||||
}
|
||||
private SessionFactory createSessionFactory() {
|
||||
Settings global = settingsBuilder().put("path.home", createTempDir()).build();
|
||||
return new SessionFactory(new RealmConfig("_name", Settings.EMPTY, global)) {
|
||||
|
|
|
@ -43,7 +43,17 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return new User.Simple("username", "r1", "r2", "r3");
|
||||
return new User.Simple("username", new String[] { "r1", "r2", "r3" });
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
throw new UnsupportedOperationException("this method should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -51,19 +61,64 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCache(){
|
||||
public void testAuthCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
SecuredString pass = SecuredStringTests.build("pass");
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass));
|
||||
|
||||
assertThat(realm.INVOCATION_COUNTER.intValue(), is(3));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(3));
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass));
|
||||
|
||||
assertThat(realm.INVOCATION_COUNTER.intValue(), is(3));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(3));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
realm.lookupUser("a");
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(3));
|
||||
realm.lookupUser("a");
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(0));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupAndAuthCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
// lookup first
|
||||
User lookedUp = realm.lookupUser("a");
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(0));
|
||||
assertThat(lookedUp.roles(), arrayContaining("lookupRole1", "lookupRole2"));
|
||||
|
||||
// now authenticate
|
||||
User user = realm.authenticate(new UsernamePasswordToken("a", SecuredStringTests.build("pass")));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(1));
|
||||
assertThat(user.roles(), arrayContaining("testRole1", "testRole2"));
|
||||
assertThat(user, not(sameInstance(lookedUp)));
|
||||
|
||||
// authenticate a different user first
|
||||
user = realm.authenticate(new UsernamePasswordToken("b", SecuredStringTests.build("pass")));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
assertThat(user.roles(), arrayContaining("testRole1", "testRole2"));
|
||||
//now lookup b
|
||||
lookedUp = realm.lookupUser("b");
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
assertThat(user, sameInstance(lookedUp));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -77,12 +132,12 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
realm.authenticate(new UsernamePasswordToken(user, pass1));
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass1));
|
||||
|
||||
assertThat(realm.INVOCATION_COUNTER.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(1));
|
||||
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2));
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2));
|
||||
|
||||
assertThat(realm.INVOCATION_COUNTER.intValue(), is(2));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -96,6 +151,32 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
assertThat(user , nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookupContract() throws Exception {
|
||||
Realm<UsernamePasswordToken> realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
User user = realm.lookupUser("user");
|
||||
assertThat(user , nullValue());
|
||||
|
||||
realm = new ThrowingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
user = realm.lookupUser("user");
|
||||
assertThat(user , nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatLookupIsNotCalledIfNotSupported() throws Exception {
|
||||
LookupNotSupportedRealm realm = new LookupNotSupportedRealm(globalSettings);
|
||||
assertThat(realm.userLookupSupported(), is(false));
|
||||
User user = realm.lookupUser("a");
|
||||
assertThat(user, is(nullValue()));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
|
||||
// try to lookup more
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
}
|
||||
|
||||
static class FailingAuthenticationRealm extends CachingUsernamePasswordRealm {
|
||||
|
||||
FailingAuthenticationRealm(Settings settings, Settings global) {
|
||||
|
@ -106,6 +187,16 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static class ThrowingAuthenticationRealm extends CachingUsernamePasswordRealm {
|
||||
|
@ -119,11 +210,21 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
throw new RuntimeException("whatever exception");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
throw new RuntimeException("lookup exception");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static class AlwaysAuthenticateCachingRealm extends CachingUsernamePasswordRealm {
|
||||
|
||||
public final AtomicInteger INVOCATION_COUNTER = new AtomicInteger(0);
|
||||
public final AtomicInteger authInvocationCounter = new AtomicInteger(0);
|
||||
public final AtomicInteger lookupInvocationCounter = new AtomicInteger(0);
|
||||
|
||||
AlwaysAuthenticateCachingRealm(Settings globalSettings) {
|
||||
super("always", new RealmConfig("always-test", Settings.EMPTY, globalSettings));
|
||||
|
@ -131,8 +232,46 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
INVOCATION_COUNTER.incrementAndGet();
|
||||
return new User.Simple(token.principal(), "testRole1", "testRole2");
|
||||
authInvocationCounter.incrementAndGet();
|
||||
return new User.Simple(token.principal(), new String[] { "testRole1", "testRole2" });
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
lookupInvocationCounter.incrementAndGet();
|
||||
return new User.Simple(username, new String[] { "lookupRole1", "lookupRole2" });
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static class LookupNotSupportedRealm extends CachingUsernamePasswordRealm {
|
||||
|
||||
public final AtomicInteger authInvocationCounter = new AtomicInteger(0);
|
||||
public final AtomicInteger lookupInvocationCounter = new AtomicInteger(0);
|
||||
|
||||
LookupNotSupportedRealm(Settings globalSettings) {
|
||||
super("lookup", new RealmConfig("lookup-notsupported-test", Settings.EMPTY, globalSettings));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
authInvocationCounter.incrementAndGet();
|
||||
return new User.Simple(token.principal(), new String[] { "testRole1", "testRole2" });
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
lookupInvocationCounter.incrementAndGet();
|
||||
throw new UnsupportedOperationException("don't call lookup if lookup isn't supported!!!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
|
||||
internalAuthorizationService.authorize(User.SYSTEM, "internal:whatever", request);
|
||||
verify(auditTrail).accessGranted(User.SYSTEM, "internal:whatever", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -74,6 +75,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:] is unauthorized for user [" + User.SYSTEM.principal() + "]"));
|
||||
verify(auditTrail).accessDenied(User.SYSTEM, "indices:", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +88,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [cluster:admin/whatever] is unauthorized for user [" + User.SYSTEM.principal() + "]"));
|
||||
verify(auditTrail).accessDenied(User.SYSTEM, "cluster:admin/whatever", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,39 +101,42 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [cluster:admin/snapshot/status] is unauthorized for user [" + User.SYSTEM.principal() + "]"));
|
||||
verify(auditTrail).accessDenied(User.SYSTEM, "cluster:admin/snapshot/status", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoRolesCausesDenial() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = new User.Simple("test user");
|
||||
User user = new User.Simple("test user", null);
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
fail("user without roles should be denied");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownRoleCausesDenial() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = new User.Simple("test user", "non-existent-role");
|
||||
User user = new User.Simple("test user", new String[] { "non-existent-role" });
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
fail("user with unknown role only should have been denied");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatNonIndicesAndNonClusterActionIsDenied() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = new User.Simple("test user", "a_all");
|
||||
User user = new User.Simple("test user", new String[] { "a_all" });
|
||||
when(rolesStore.role("a_all")).thenReturn(Permission.Global.Role.builder("a_role").add(Privilege.Index.ALL, "a").build());
|
||||
|
||||
try {
|
||||
|
@ -139,14 +145,15 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [whatever] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, "whatever", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatRoleWithNoIndicesIsDenied() {
|
||||
TransportRequest request = new IndicesExistsRequest("a");
|
||||
User user = new User.Simple("test user", "no_indices");
|
||||
when(rolesStore.role("no_indices")).thenReturn(Permission.Global.Role.builder("no_indices").set(Privilege.Cluster.action("")).build());
|
||||
User user = new User.Simple("test user", new String[] { "no_indices" });
|
||||
when(rolesStore.role("no_indices")).thenReturn(Permission.Global.Role.builder("no_indices").cluster(Privilege.Cluster.action("")).build());
|
||||
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
|
@ -154,12 +161,13 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScrollRelatedRequestsAllowed() {
|
||||
User user = new User.Simple("test user", "a_all");
|
||||
User user = new User.Simple("test user", new String[] { "a_all" });
|
||||
when(rolesStore.role("a_all")).thenReturn(Permission.Global.Role.builder("a_role").add(Privilege.Index.ALL, "a").build());
|
||||
|
||||
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
|
||||
|
@ -186,13 +194,14 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
|
||||
internalAuthorizationService.authorize(user, SearchServiceTransportAction.FREE_CONTEXT_SCROLL_ACTION_NAME, request);
|
||||
verify(auditTrail).accessGranted(user, SearchServiceTransportAction.FREE_CONTEXT_SCROLL_ACTION_NAME, request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizeIndicesFailures() {
|
||||
TransportRequest request = new IndicesExistsRequest("b");
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
User user = new User.Simple("test user", "a_all");
|
||||
User user = new User.Simple("test user", new String[] { "a_all" });
|
||||
when(rolesStore.role("a_all")).thenReturn(Permission.Global.Role.builder("a_all").add(Privilege.Index.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
@ -203,6 +212,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
verify(clusterService, times(2)).state();
|
||||
verify(state, times(3)).metaData();
|
||||
}
|
||||
|
@ -213,7 +223,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
CreateIndexRequest request = new CreateIndexRequest("a");
|
||||
request.alias(new Alias("a2"));
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
User user = new User.Simple("test user", "a_all");
|
||||
User user = new User.Simple("test user", new String[] { "a_all" });
|
||||
when(rolesStore.role("a_all")).thenReturn(Permission.Global.Role.builder("a_all").add(Privilege.Index.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
@ -224,6 +234,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [" + IndicesAliasesAction.NAME + "] is unauthorized for user [test user]"));
|
||||
verify(auditTrail).accessDenied(user, IndicesAliasesAction.NAME, request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
verify(clusterService).state();
|
||||
verify(state, times(2)).metaData();
|
||||
}
|
||||
|
@ -234,7 +245,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
CreateIndexRequest request = new CreateIndexRequest("a");
|
||||
request.alias(new Alias("a2"));
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
User user = new User.Simple("test user", "a_all");
|
||||
User user = new User.Simple("test user", new String[] { "a_all" });
|
||||
when(rolesStore.role("a_all")).thenReturn(Permission.Global.Role.builder("a_all").add(Privilege.Index.ALL, "a", "a2").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA);
|
||||
|
@ -249,7 +260,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testIndicesAliasesWithNoRolesUser() {
|
||||
User user = new User.Simple("test user");
|
||||
User user = new User.Simple("test user", null);
|
||||
|
||||
List<String> list = internalAuthorizationService.authorizedIndicesAndAliases(user, "");
|
||||
assertThat(list.isEmpty(), is(true));
|
||||
|
@ -257,7 +268,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
|
||||
@Test
|
||||
public void testIndicesAliasesWithUserHavingRoles() {
|
||||
User user = new User.Simple("test user", "a_star", "b");
|
||||
User user = new User.Simple("test user", new String[] { "a_star", "b" });
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
when(rolesStore.role("a_star")).thenReturn(Permission.Global.Role.builder("a_star").add(Privilege.Index.ALL, "a*").build());
|
||||
when(rolesStore.role("b")).thenReturn(Permission.Global.Role.builder("a_star").add(Privilege.Index.SEARCH, "b").build());
|
||||
|
@ -300,6 +311,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [" + anonymousService.anonymousUser().principal() + "]"));
|
||||
verify(auditTrail).accessDenied(anonymousService.anonymousUser(), "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
verify(clusterService, times(2)).state();
|
||||
verify(state, times(3)).metaData();
|
||||
}
|
||||
|
@ -325,9 +337,109 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e, containsString("action [indices:a] requires authentication"));
|
||||
verify(auditTrail).accessDenied(anonymousService.anonymousUser(), "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
verify(clusterService, times(2)).state();
|
||||
verify(state, times(3)).metaData();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsRequestWithNoRolesUser() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = new User.Simple("test user", null, new User.Simple("run as me", new String[] { "admin" }));
|
||||
assertThat(user.runAs(), is(notNullValue()));
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
fail("user without roles should be denied for run as");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
|
||||
verify(auditTrail).runAsDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsRequestRunningAsUnAllowedUser() {
|
||||
TransportRequest request = mock(TransportRequest.class);
|
||||
User user = new User.Simple("test user", new String[] { "can run as" }, new User.Simple("run as me", new String[] { "doesn't exist" }));
|
||||
assertThat(user.runAs(), is(notNullValue()));
|
||||
when(rolesStore.role("can run as")).thenReturn(Permission.Global.Role
|
||||
.builder("can run as")
|
||||
.runAs(new Privilege.General("", "not the right user"))
|
||||
.add(Privilege.Index.ALL, "a")
|
||||
.build());
|
||||
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
fail("user without roles should be denied for run as");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
|
||||
verify(auditTrail).runAsDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsRequestWithRunAsUserWithoutPermission() {
|
||||
TransportRequest request = new IndicesExistsRequest("a");
|
||||
User user = new User.Simple("test user", new String[] { "can run as" }, new User.Simple("run as me", new String[] { "b" }));
|
||||
assertThat(user.runAs(), is(notNullValue()));
|
||||
when(rolesStore.role("can run as")).thenReturn(Permission.Global.Role
|
||||
.builder("can run as")
|
||||
.runAs(new Privilege.General("", "run as me"))
|
||||
.add(Privilege.Index.ALL, "a")
|
||||
.build());
|
||||
|
||||
if (randomBoolean()) {
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.builder()
|
||||
.put(new IndexMetaData.Builder("a")
|
||||
.settings(Settings.builder().put("index.version.created", Version.CURRENT).build())
|
||||
.numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.build());
|
||||
when(rolesStore.role("b")).thenReturn(Permission.Global.Role
|
||||
.builder("b")
|
||||
.add(Privilege.Index.ALL, "b")
|
||||
.build());
|
||||
}
|
||||
|
||||
try {
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
fail("the run as user's role doesn't exist so they should not get authorized");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]"));
|
||||
verify(auditTrail).runAsGranted(user, "indices:a", request);
|
||||
verify(auditTrail).accessDenied(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAsRequestWithValidPermissions() {
|
||||
TransportRequest request = new IndicesExistsRequest("b");
|
||||
User user = new User.Simple("test user", new String[] { "can run as" }, new User.Simple("run as me", new String[] { "b" }));
|
||||
assertThat(user.runAs(), is(notNullValue()));
|
||||
when(rolesStore.role("can run as")).thenReturn(Permission.Global.Role
|
||||
.builder("can run as")
|
||||
.runAs(new Privilege.General("", "run as me"))
|
||||
.add(Privilege.Index.ALL, "a")
|
||||
.build());
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
when(state.metaData()).thenReturn(MetaData.builder()
|
||||
.put(new IndexMetaData.Builder("b")
|
||||
.settings(Settings.builder().put("index.version.created", Version.CURRENT).build())
|
||||
.numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.build());
|
||||
when(rolesStore.role("b")).thenReturn(Permission.Global.Role
|
||||
.builder("b")
|
||||
.add(Privilege.Index.ALL, "b")
|
||||
.build());
|
||||
|
||||
internalAuthorizationService.authorize(user, "indices:a", request);
|
||||
verify(auditTrail).runAsGranted(user, "indices:a", request);
|
||||
verify(auditTrail).accessGranted(user, "indices:a", request);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class PermissionTests extends ESTestCase {
|
|||
@Test
|
||||
public void testIndicesGlobalsIterator() {
|
||||
Permission.Global.Role.Builder builder = Permission.Global.Role.builder("tc_role");
|
||||
builder.set(Cluster.action("cluster:monitor/nodes/info"));
|
||||
builder.cluster(Cluster.action("cluster:monitor/nodes/info"));
|
||||
Permission.Global.Role noIndicesPermission = builder.build();
|
||||
|
||||
Permission.Indices.Globals indicesGlobals = new Permission.Indices.Globals(Collections.<Permission.Global>unmodifiableList(Arrays.asList(noIndicesPermission, permission)));
|
||||
|
@ -74,6 +74,19 @@ public class PermissionTests extends ESTestCase {
|
|||
Permission.Global.Role.Builder permission = Permission.Global.Role.builder("some_role");
|
||||
Permission.Global.Role role = permission.build();
|
||||
assertThat(role, notNullValue());
|
||||
assertThat(role.cluster(), notNullValue());
|
||||
assertThat(role.indices(), notNullValue());
|
||||
assertThat(role.runAs(), notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunAs() {
|
||||
Permission.Global.Role permission = Permission.Global.Role.builder("some_role")
|
||||
.runAs(new Privilege.General("name", "user1", "run*"))
|
||||
.build();
|
||||
assertThat(permission.runAs().check("user1"), is(true));
|
||||
assertThat(permission.runAs().check("user"), is(false));
|
||||
assertThat(permission.runAs().check("run" + randomAsciiOfLengthBetween(1, 10)), is(true));
|
||||
}
|
||||
|
||||
// "baz_*foo", "/fool.*bar/"
|
||||
|
@ -84,6 +97,4 @@ public class PermissionTests extends ESTestCase {
|
|||
assertThat(indicesMatcher.test("baz_foo"), is(true));
|
||||
assertThat(indicesMatcher.test("barbapapa"), is(false));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
metaData = mdBuilder.build();
|
||||
|
||||
AuthorizationService authzService = mock(AuthorizationService.class);
|
||||
user = new User.Simple("user", "role");
|
||||
user = new User.Simple("user", new String[] { "role" });
|
||||
|
||||
String[] authorizedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed"};
|
||||
when(authzService.authorizedIndicesAndAliases(user, SearchAction.NAME)).thenReturn(Collections.unmodifiableList(Arrays.asList(authorizedIndices)));
|
||||
|
@ -77,7 +77,7 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
when(authzService.authorizedIndicesAndAliases(user, IndicesAliasesAction.NAME)).thenReturn(Collections.unmodifiableList(Arrays.asList(authorizedIndices)));
|
||||
when(authzService.authorizedIndicesAndAliases(user, GetAliasesAction.NAME)).thenReturn(Collections.unmodifiableList(Arrays.asList(authorizedIndices)));
|
||||
when(authzService.authorizedIndicesAndAliases(user, DeleteIndexAction.NAME)).thenReturn(Collections.unmodifiableList(Arrays.asList(authorizedIndices)));
|
||||
userNoIndices = new User.Simple("test", "test");
|
||||
userNoIndices = new User.Simple("test", new String[] { "test" });
|
||||
when(authzService.authorizedIndicesAndAliases(userNoIndices, IndicesAliasesAction.NAME)).thenReturn(Collections.<String>emptyList());
|
||||
when(authzService.authorizedIndicesAndAliases(userNoIndices, GetAliasesAction.NAME)).thenReturn(Collections.<String>emptyList());
|
||||
when(authzService.authorizedIndicesAndAliases(userNoIndices, SearchAction.NAME)).thenReturn(Collections.<String>emptyList());
|
||||
|
|
|
@ -42,7 +42,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
Path path = getDataPath("roles.yml");
|
||||
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(), logger);
|
||||
assertThat(roles, notNullValue());
|
||||
assertThat(roles.size(), is(5));
|
||||
assertThat(roles.size(), is(7));
|
||||
|
||||
Permission.Global.Role role = roles.get("role1");
|
||||
assertThat(role, notNullValue());
|
||||
|
@ -52,6 +52,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
assertThat(role.indices(), notNullValue());
|
||||
assertThat(role.indices().groups(), notNullValue());
|
||||
assertThat(role.indices().groups().length, is(2));
|
||||
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
|
||||
|
||||
Permission.Global.Indices.Group group = role.indices().groups()[0];
|
||||
assertThat(group.indices(), notNullValue());
|
||||
|
@ -76,6 +77,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
assertThat(role.indices(), notNullValue());
|
||||
assertThat(role.indices().groups(), notNullValue());
|
||||
assertThat(role.indices().groups().length, is(0));
|
||||
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
|
||||
|
||||
role = roles.get("role2");
|
||||
assertThat(role, notNullValue());
|
||||
|
@ -84,6 +86,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
assertThat(role.cluster().privilege(), is(Privilege.Cluster.ALL)); // MONITOR is collapsed into ALL
|
||||
assertThat(role.indices(), notNullValue());
|
||||
assertThat(role.indices(), is(Permission.Indices.Core.NONE));
|
||||
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
|
||||
|
||||
role = roles.get("role3");
|
||||
assertThat(role, notNullValue());
|
||||
|
@ -93,6 +96,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
assertThat(role.indices(), notNullValue());
|
||||
assertThat(role.indices().groups(), notNullValue());
|
||||
assertThat(role.indices().groups().length, is(1));
|
||||
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
|
||||
|
||||
group = role.indices().groups()[0];
|
||||
assertThat(group.indices(), notNullValue());
|
||||
|
@ -107,6 +111,29 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
assertThat(role.cluster(), notNullValue());
|
||||
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
|
||||
assertThat(role.indices(), is(Permission.Indices.Core.NONE));
|
||||
assertThat(role.runAs(), is(Permission.RunAs.Core.NONE));
|
||||
|
||||
role = roles.get("role_run_as");
|
||||
assertThat(role, notNullValue());
|
||||
assertThat(role.name(), equalTo("role_run_as"));
|
||||
assertThat(role.cluster(), notNullValue());
|
||||
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
|
||||
assertThat(role.indices(), is(Permission.Indices.Core.NONE));
|
||||
assertThat(role.runAs(), notNullValue());
|
||||
assertThat(role.runAs().check("user1"), is(true));
|
||||
assertThat(role.runAs().check("user2"), is(true));
|
||||
assertThat(role.runAs().check("user" + randomIntBetween(3, 9)), is(false));
|
||||
|
||||
role = roles.get("role_run_as1");
|
||||
assertThat(role, notNullValue());
|
||||
assertThat(role.name(), equalTo("role_run_as1"));
|
||||
assertThat(role.cluster(), notNullValue());
|
||||
assertThat(role.cluster(), is(Permission.Cluster.Core.NONE));
|
||||
assertThat(role.indices(), is(Permission.Indices.Core.NONE));
|
||||
assertThat(role.runAs(), notNullValue());
|
||||
assertThat(role.runAs().check("user1"), is(true));
|
||||
assertThat(role.runAs().check("user2"), is(true));
|
||||
assertThat(role.runAs().check("user" + randomIntBetween(3, 9)), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,7 +263,7 @@ public class FileRolesStoreTests extends ESTestCase {
|
|||
public void testReservedRoles() throws Exception {
|
||||
Set<Permission.Global.Role> reservedRoles = ImmutableSet.<Permission.Global.Role>builder()
|
||||
.add(Permission.Global.Role.builder("reserved")
|
||||
.set(Privilege.Cluster.ALL)
|
||||
.cluster(Privilege.Cluster.ALL)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ShieldRestFilterTests extends ESTestCase {
|
|||
@Test
|
||||
public void testProcess() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
User user = new User.Simple("_user", "r1");
|
||||
User user = new User.Simple("_user", new String[] { "r1" });
|
||||
when(authcService.authenticate(request)).thenReturn(user);
|
||||
filter.process(request, channel, chain);
|
||||
verify(chain).continueProcessing(request, channel);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
bcrypt: $2a$05$zxnP0vdREMxnEpkLCDI2OuSaSk/QEKA2.A42iOpI6U2u.RLLOWm1e
|
||||
md5: $apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP.
|
||||
crypt: hsP1PYSLsEEvs
|
||||
plain: {plain}test123
|
||||
bcrypt:$2a$05$zxnP0vdREMxnEpkLCDI2OuSaSk/QEKA2.A42iOpI6U2u.RLLOWm1e
|
||||
md5:$apr1$R3DdqiAZ$aljIkaIVPSarmDMlJUBBP.
|
||||
crypt:hsP1PYSLsEEvs
|
||||
plain:{plain}test123
|
||||
sha:{SHA}cojt0Pw//L6ToM8G41aOKFIWh7w=
|
||||
# this is a comment line
|
||||
# another comment line
|
||||
bcrypt10: $2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e
|
||||
bcrypt10:$2y$10$FMhmFjwU5.qxQ/BsEciS9OqcJVkFMgXMo4uH5CelOR1j4N9zIv67e
|
|
@ -21,4 +21,12 @@ role4:
|
|||
#adfldkkd
|
||||
'idx2':
|
||||
'': READ
|
||||
'idx1': []
|
||||
'idx1': []
|
||||
|
||||
# role with run_as permissions only
|
||||
role_run_as:
|
||||
run_as: "user1,user2"
|
||||
|
||||
# role with more than run_as
|
||||
role_run_as1:
|
||||
run_as: [user1, user2]
|
|
@ -18,7 +18,7 @@ public class WatcherUserHolder {
|
|||
static final String[] ROLE_NAMES = new String[] { "__watcher_role" };
|
||||
|
||||
public static final Permission.Global.Role ROLE = Permission.Global.Role.builder(ROLE_NAMES[0])
|
||||
.set(Privilege.Cluster.action("indices:admin/template/put"))
|
||||
.cluster(Privilege.Cluster.action("indices:admin/template/put"))
|
||||
|
||||
// for now, the watches will be executed under the watcher user, meaning, all actions
|
||||
// taken as part of the execution will be executed on behalf of this user. this includes
|
||||
|
|
Loading…
Reference in New Issue