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:
jaymode 2015-08-25 09:59:11 -04:00
parent 7ea8c85e4b
commit 154b10e901
46 changed files with 2051 additions and 218 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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 + "]");
}

View File

@ -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");

View File

@ -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 + "*")

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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() {

View File

@ -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());
}

View File

@ -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 {

View File

@ -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() {

View File

@ -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()));

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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",

View File

@ -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"));
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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",

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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));
}

View File

@ -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> {
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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()));
}
}

View File

@ -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";

View File

@ -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)) {

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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());

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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]

View File

@ -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