security: add metadata to roles
This commit adds the ability to define metadata for roles. This metadata is currently only used for the API and to indicate that a role is reserved. We can continue passing on the metadata as needed, when necessary. Closes elastic/elasticsearch#2036 Original commit: elastic/x-pack-elasticsearch@8b5f606138
This commit is contained in:
parent
f42f8cf756
commit
5be3832889
|
@ -82,7 +82,8 @@ public class SecurityTemplateService extends AbstractComponent implements Cluste
|
|||
if (securityIndexRouting == null) {
|
||||
if (event.localNodeMaster()) {
|
||||
ClusterState state = event.state();
|
||||
// TODO for the future need to add some checking in the event the template needs to be updated...
|
||||
// norelease we need to add some checking in the event the template needs to be updated and also the mappings need to be
|
||||
// updated on index too!
|
||||
IndexTemplateMetaData templateMeta = state.metaData().templates().get(SECURITY_TEMPLATE_NAME);
|
||||
final boolean createTemplate = (templateMeta == null);
|
||||
|
||||
|
|
|
@ -8,18 +8,19 @@ package org.elasticsearch.xpack.security.action.role;
|
|||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
|
@ -33,6 +34,7 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
|
||||
private String[] runAs = Strings.EMPTY_ARRAY;
|
||||
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
public PutRoleRequest() {
|
||||
}
|
||||
|
@ -43,6 +45,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
if (name == null) {
|
||||
validationException = addValidationError("role name is missing", validationException);
|
||||
}
|
||||
if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) {
|
||||
validationException =
|
||||
addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
|
@ -86,6 +92,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
return refreshPolicy;
|
||||
}
|
||||
|
||||
public void metadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
@ -102,6 +112,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
return runAs;
|
||||
}
|
||||
|
||||
public Map<String, Object> metadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
|
@ -114,6 +128,7 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
}
|
||||
runAs = in.readStringArray();
|
||||
refreshPolicy = RefreshPolicy.readFrom(in);
|
||||
metadata = in.readMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -127,12 +142,14 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
|
|||
}
|
||||
out.writeStringArray(runAs);
|
||||
refreshPolicy.writeTo(out);
|
||||
out.writeMap(metadata);
|
||||
}
|
||||
|
||||
RoleDescriptor roleDescriptor() {
|
||||
return new RoleDescriptor(name,
|
||||
clusterPrivileges,
|
||||
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]),
|
||||
runAs);
|
||||
runAs,
|
||||
metadata);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ import org.elasticsearch.common.Nullable;
|
|||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Builder for requests to add a role to the administrative index
|
||||
*/
|
||||
|
@ -33,6 +35,7 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
|||
request.cluster(descriptor.getClusterPrivileges());
|
||||
request.addIndex(descriptor.getIndicesPrivileges());
|
||||
request.runAs(descriptor.getRunAs());
|
||||
request.metadata(descriptor.getMetadata());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -56,4 +59,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
|||
request.addIndex(indices, privileges, fields, query);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutRoleRequestBuilder metadata(Map<String, Object> metadata) {
|
||||
request.metadata(metadata);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,15 @@ import org.elasticsearch.common.xcontent.XContentHelper;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.xpack.security.support.Validation;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A holder for a Role that contains user-readable information about the Role
|
||||
|
@ -39,16 +42,26 @@ public class RoleDescriptor implements ToXContent {
|
|||
private final String[] clusterPrivileges;
|
||||
private final IndicesPrivileges[] indicesPrivileges;
|
||||
private final String[] runAs;
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
public RoleDescriptor(String name,
|
||||
@Nullable String[] clusterPrivileges,
|
||||
@Nullable IndicesPrivileges[] indicesPrivileges,
|
||||
@Nullable String[] runAs) {
|
||||
this(name, clusterPrivileges, indicesPrivileges, runAs, null);
|
||||
}
|
||||
|
||||
public RoleDescriptor(String name,
|
||||
@Nullable String[] clusterPrivileges,
|
||||
@Nullable IndicesPrivileges[] indicesPrivileges,
|
||||
@Nullable String[] runAs,
|
||||
@Nullable Map<String, Object> metadata) {
|
||||
|
||||
this.name = name;
|
||||
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
|
||||
this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE;
|
||||
this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY;
|
||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
@ -67,6 +80,10 @@ public class RoleDescriptor implements ToXContent {
|
|||
return this.runAs;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("Role[");
|
||||
|
@ -77,6 +94,8 @@ public class RoleDescriptor implements ToXContent {
|
|||
sb.append(group.toString()).append(",");
|
||||
}
|
||||
sb.append("], runAs=[").append(Strings.arrayToCommaDelimitedString(runAs));
|
||||
sb.append("], metadata=[");
|
||||
MetadataUtils.writeValue(sb, metadata);
|
||||
sb.append("]]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
@ -91,6 +110,7 @@ public class RoleDescriptor implements ToXContent {
|
|||
if (!name.equals(that.name)) return false;
|
||||
if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false;
|
||||
if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false;
|
||||
if (!metadata.equals(that.getMetadata())) return false;
|
||||
return Arrays.equals(runAs, that.runAs);
|
||||
}
|
||||
|
||||
|
@ -100,16 +120,18 @@ public class RoleDescriptor implements ToXContent {
|
|||
result = 31 * result + Arrays.hashCode(clusterPrivileges);
|
||||
result = 31 * result + Arrays.hashCode(indicesPrivileges);
|
||||
result = 31 * result + Arrays.hashCode(runAs);
|
||||
result = 31 * result + metadata.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field("cluster", (Object[]) clusterPrivileges);
|
||||
builder.field("indices", (Object[]) indicesPrivileges);
|
||||
builder.field(Fields.CLUSTER.getPreferredName(), (Object[]) clusterPrivileges);
|
||||
builder.field(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges);
|
||||
if (runAs != null) {
|
||||
builder.field("run_as", runAs);
|
||||
builder.field(Fields.RUN_AS.getPreferredName(), runAs);
|
||||
}
|
||||
builder.field(Fields.METADATA.getPreferredName(), metadata);
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
|
@ -122,7 +144,8 @@ public class RoleDescriptor implements ToXContent {
|
|||
indicesPrivileges[i] = IndicesPrivileges.createFrom(in);
|
||||
}
|
||||
String[] runAs = in.readStringArray();
|
||||
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs);
|
||||
Map<String, Object> metadata = in.readMap();
|
||||
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs, metadata);
|
||||
}
|
||||
|
||||
public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
|
||||
|
@ -133,6 +156,7 @@ public class RoleDescriptor implements ToXContent {
|
|||
group.writeTo(out);
|
||||
}
|
||||
out.writeStringArray(descriptor.runAs);
|
||||
out.writeMap(descriptor.metadata);
|
||||
}
|
||||
|
||||
public static RoleDescriptor parse(String name, BytesReference source) throws IOException {
|
||||
|
@ -160,6 +184,7 @@ public class RoleDescriptor implements ToXContent {
|
|||
IndicesPrivileges[] indicesPrivileges = null;
|
||||
String[] clusterPrivileges = null;
|
||||
String[] runAsUsers = null;
|
||||
Map<String, Object> metadata = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
|
@ -169,11 +194,17 @@ public class RoleDescriptor implements ToXContent {
|
|||
runAsUsers = readStringArray(name, parser, true);
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.CLUSTER)) {
|
||||
clusterPrivileges = readStringArray(name, parser, true);
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.METADATA)) {
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type object, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
metadata = parser.map();
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
|
||||
}
|
||||
}
|
||||
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers);
|
||||
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers, metadata);
|
||||
}
|
||||
|
||||
private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException {
|
||||
|
@ -355,9 +386,7 @@ public class RoleDescriptor implements ToXContent {
|
|||
this.indices = in.readStringArray();
|
||||
this.fields = in.readOptionalStringArray();
|
||||
this.privileges = in.readStringArray();
|
||||
if (in.readBoolean()) {
|
||||
this.query = new BytesArray(in.readByteArray());
|
||||
}
|
||||
this.query = in.readOptionalBytesReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -365,12 +394,7 @@ public class RoleDescriptor implements ToXContent {
|
|||
out.writeStringArray(indices);
|
||||
out.writeOptionalStringArray(fields);
|
||||
out.writeStringArray(privileges);
|
||||
if (query != null) {
|
||||
out.writeBoolean(true);
|
||||
out.writeByteArray(BytesReference.toBytes(query));
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
out.writeOptionalBytesReference(query);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -424,5 +448,6 @@ public class RoleDescriptor implements ToXContent {
|
|||
ParseField QUERY = new ParseField("query");
|
||||
ParseField PRIVILEGES = new ParseField("privileges");
|
||||
ParseField FIELDS = new ParseField("fields");
|
||||
ParseField METADATA = new ParseField("metadata");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction;
|
|||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -20,7 +21,8 @@ public class KibanaRole extends Role {
|
|||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build() };
|
||||
|
||||
public static final String NAME = "kibana";
|
||||
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null);
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final KibanaRole INSTANCE = new KibanaRole();
|
||||
|
||||
private KibanaRole() {
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission;
|
|||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
public class KibanaUserRole extends Role {
|
||||
|
||||
|
@ -16,7 +17,8 @@ public class KibanaUserRole extends Role {
|
|||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete").build() };
|
||||
|
||||
public static final String NAME = "kibana_user";
|
||||
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null);
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final KibanaUserRole INSTANCE = new KibanaUserRole();
|
||||
|
||||
private KibanaUserRole() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
|||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -19,7 +20,8 @@ public class SuperuserRole extends Role {
|
|||
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, new String[] { "all" },
|
||||
new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()},
|
||||
new String[] { "*" });
|
||||
new String[] { "*" },
|
||||
MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final SuperuserRole INSTANCE = new SuperuserRole();
|
||||
|
||||
private SuperuserRole() {
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission;
|
|||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
* Reserved role for the transport client
|
||||
|
@ -17,7 +18,8 @@ public class TransportClientRole extends Role {
|
|||
public static final String NAME = "transport_client";
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "transport_client" };
|
||||
|
||||
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null);
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final TransportClientRole INSTANCE = new TransportClientRole();
|
||||
|
||||
private TransportClientRole() {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.support;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class MetadataUtils {
|
||||
|
||||
public static final String RESERVED_PREFIX = "_";
|
||||
public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
|
||||
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Collections.singletonMap(RESERVED_METADATA_KEY, true);
|
||||
|
||||
private MetadataUtils() {}
|
||||
|
||||
public static void writeValue(StringBuilder sb, Object object) {
|
||||
if (object instanceof Map) {
|
||||
sb.append("{");
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
|
||||
sb.append(entry.getKey()).append("=");
|
||||
writeValue(sb, entry.getValue());
|
||||
}
|
||||
sb.append("}");
|
||||
|
||||
} else if (object instanceof Collection) {
|
||||
sb.append("[");
|
||||
boolean first = true;
|
||||
for (Object item : (Collection) object) {
|
||||
if (!first) {
|
||||
sb.append(",");
|
||||
}
|
||||
writeValue(sb, item);
|
||||
first = false;
|
||||
}
|
||||
sb.append("]");
|
||||
} else if (object.getClass().isArray()) {
|
||||
sb.append("[");
|
||||
for (int i = 0; i < Array.getLength(object); i++) {
|
||||
if (i != 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
writeValue(sb, Array.get(object, i));
|
||||
}
|
||||
sb.append("]");
|
||||
} else {
|
||||
sb.append(object);
|
||||
}
|
||||
}
|
||||
|
||||
public static void verifyNoReservedMetadata(Map<String, Object> metadata) {
|
||||
for (String key : metadata.keySet()) {
|
||||
if (key.startsWith(RESERVED_PREFIX)) {
|
||||
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsReservedMetadata(Map<String, Object> metadata) {
|
||||
for (String key : metadata.keySet()) {
|
||||
if (key.startsWith(RESERVED_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -14,12 +14,11 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
|||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -28,8 +27,6 @@ import java.util.Map;
|
|||
*/
|
||||
public class User implements ToXContent {
|
||||
|
||||
static final String RESERVED_PREFIX = "_";
|
||||
|
||||
private final String username;
|
||||
private final String[] roles;
|
||||
private final User runAs;
|
||||
|
@ -53,7 +50,7 @@ public class User implements ToXContent {
|
|||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
this.runAs = null;
|
||||
verifyNoReservedMetadata(this.username, this.metadata);
|
||||
verifyNoReservedMetadata(this.metadata);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, User runAs) {
|
||||
|
@ -67,7 +64,7 @@ public class User implements ToXContent {
|
|||
throw new ElasticsearchSecurityException("invalid run_as user");
|
||||
}
|
||||
this.runAs = runAs;
|
||||
verifyNoReservedMetadata(this.username, this.metadata);
|
||||
verifyNoReservedMetadata(this.metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +122,7 @@ public class User implements ToXContent {
|
|||
sb.append(",fullName=").append(fullName);
|
||||
sb.append(",email=").append(email);
|
||||
sb.append(",metadata=");
|
||||
append(sb, metadata);
|
||||
MetadataUtils.writeValue(sb, metadata);
|
||||
if (runAs != null) {
|
||||
sb.append(",runAs=[").append(runAs.toString()).append("]");
|
||||
}
|
||||
|
@ -172,16 +169,12 @@ public class User implements ToXContent {
|
|||
return builder.endObject();
|
||||
}
|
||||
|
||||
void verifyNoReservedMetadata(String principal, Map<String, Object> metadata) {
|
||||
void verifyNoReservedMetadata(Map<String, Object> metadata) {
|
||||
if (this instanceof ReservedUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : metadata.keySet()) {
|
||||
if (key.startsWith(RESERVED_PREFIX)) {
|
||||
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses");
|
||||
}
|
||||
}
|
||||
MetadataUtils.verifyNoReservedMetadata(metadata);
|
||||
}
|
||||
|
||||
public static User readFrom(StreamInput input) throws IOException {
|
||||
|
@ -245,49 +238,10 @@ public class User implements ToXContent {
|
|||
}
|
||||
}
|
||||
|
||||
public static void append(StringBuilder sb, Object object) {
|
||||
if (object == null) {
|
||||
sb.append((Object) null);
|
||||
}
|
||||
if (object instanceof Map) {
|
||||
sb.append("{");
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
|
||||
sb.append(entry.getKey()).append("=");
|
||||
append(sb, entry.getValue());
|
||||
}
|
||||
sb.append("}");
|
||||
|
||||
} else if (object instanceof Collection) {
|
||||
sb.append("[");
|
||||
boolean first = true;
|
||||
for (Object item : (Collection) object) {
|
||||
if (!first) {
|
||||
sb.append(",");
|
||||
}
|
||||
append(sb, item);
|
||||
first = false;
|
||||
}
|
||||
sb.append("]");
|
||||
} else if (object.getClass().isArray()) {
|
||||
sb.append("[");
|
||||
for (int i = 0; i < Array.getLength(object); i++) {
|
||||
if (i != 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
append(sb, Array.get(object, i));
|
||||
}
|
||||
sb.append("]");
|
||||
} else {
|
||||
sb.append(object);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class ReservedUser extends User {
|
||||
|
||||
private static final String RESERVED_KEY = User.RESERVED_PREFIX + "reserved";
|
||||
|
||||
ReservedUser(String username, String... roles) {
|
||||
super(username, roles, null, null, Collections.singletonMap(RESERVED_KEY, true));
|
||||
super(username, roles, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,10 @@
|
|||
},
|
||||
"run_as" : {
|
||||
"type" : "keyword"
|
||||
},
|
||||
"metadata" : {
|
||||
"type" : "object",
|
||||
"dynamic" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase {
|
|||
RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs);
|
||||
assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd),
|
||||
equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," +
|
||||
"\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[]}"));
|
||||
"\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[],\"metadata\":{}}"));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ import static org.hamcrest.Matchers.is;
|
|||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
/**
|
||||
* Tests for the ESNativeUsersStore and ESNativeRolesStore
|
||||
* Tests for the NativeUsersStore and NativeRolesStore
|
||||
*/
|
||||
public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
|
||||
|
||||
|
@ -144,12 +144,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
|
|||
SecurityClient c = securityClient();
|
||||
final List<RoleDescriptor> existingRoles = Arrays.asList(c.prepareGetRoles().get().roles());
|
||||
final int existing = existingRoles.size();
|
||||
final Map<String, Object> metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10));
|
||||
logger.error("--> creating role");
|
||||
c.preparePutRole("test_role")
|
||||
.cluster("all", "none")
|
||||
.runAs("root", "nobody")
|
||||
.addIndices(new String[]{"index"}, new String[]{"read"},
|
||||
new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}"))
|
||||
.metadata(metadata)
|
||||
.get();
|
||||
logger.error("--> waiting for .security index");
|
||||
ensureGreen(SecurityTemplateService.SECURITY_INDEX_NAME);
|
||||
|
@ -158,6 +160,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
|
|||
assertTrue("role should exist", resp.hasRoles());
|
||||
RoleDescriptor testRole = resp.roles()[0];
|
||||
assertNotNull(testRole);
|
||||
assertThat(testRole.getMetadata().size(), is(1));
|
||||
assertThat(testRole.getMetadata().get("key"), is(metadata.get("key")));
|
||||
|
||||
c.preparePutRole("test_role2")
|
||||
.cluster("all", "none")
|
||||
|
|
|
@ -7,10 +7,17 @@ package org.elasticsearch.xpack.security.authz;
|
|||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
|
@ -37,7 +44,7 @@ public class RoleDescriptorTests extends ESTestCase {
|
|||
};
|
||||
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
|
||||
assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], " +
|
||||
"privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]"));
|
||||
"privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo], metadata=[{}]]"));
|
||||
}
|
||||
|
||||
public void testToXContent() throws Exception {
|
||||
|
@ -49,7 +56,8 @@ public class RoleDescriptorTests extends ESTestCase {
|
|||
.query("{\"query\": {\"match_all\": {}}}")
|
||||
.build()
|
||||
};
|
||||
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
|
||||
Map<String, Object> metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null;
|
||||
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata);
|
||||
XContentBuilder builder = descriptor.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS);
|
||||
RoleDescriptor parsed = RoleDescriptor.parse("test", builder.bytes());
|
||||
assertThat(parsed, is(descriptor));
|
||||
|
@ -88,5 +96,34 @@ public class RoleDescriptorTests extends ESTestCase {
|
|||
assertEquals(1, rd.getIndicesPrivileges().length);
|
||||
assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices());
|
||||
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
|
||||
|
||||
q = "{\"cluster\":[\"a\", \"b\"], \"metadata\":{\"foo\":\"bar\"}}";
|
||||
rd = RoleDescriptor.parse("test", new BytesArray(q));
|
||||
assertEquals("test", rd.getName());
|
||||
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
|
||||
assertEquals(0, rd.getIndicesPrivileges().length);
|
||||
assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs());
|
||||
assertNotNull(rd.getMetadata());
|
||||
assertThat(rd.getMetadata().size(), is(1));
|
||||
assertThat(rd.getMetadata().get("foo"), is("bar"));
|
||||
}
|
||||
|
||||
public void testSerialization() throws Exception {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices("i1", "i2")
|
||||
.privileges("read")
|
||||
.fields("body", "title")
|
||||
.query("{\"query\": {\"match_all\": {}}}")
|
||||
.build()
|
||||
};
|
||||
Map<String, Object> metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null;
|
||||
final RoleDescriptor descriptor =
|
||||
new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata);
|
||||
RoleDescriptor.writeTo(descriptor, output);
|
||||
StreamInput streamInput = ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes()));
|
||||
final RoleDescriptor serialized = RoleDescriptor.readFrom(streamInput);
|
||||
assertEquals(descriptor, serialized);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@ package org.elasticsearch.xpack.security.user;
|
|||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.XPackClient;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -127,7 +126,7 @@ public class UserTests extends ESTestCase {
|
|||
|
||||
public void testReservedMetadata() throws Exception {
|
||||
Map<String, Object> validMetadata = Collections.singletonMap("foo", "bar");
|
||||
Map<String, Object> invalidMetadata = Collections.singletonMap(User.RESERVED_PREFIX + "foo", "bar");
|
||||
Map<String, Object> invalidMetadata = Collections.singletonMap(MetadataUtils.RESERVED_PREFIX + "foo", "bar");
|
||||
|
||||
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
|
||||
new User("john", Strings.EMPTY_ARRAY, "John Doe", "john@doe.com", invalidMetadata));
|
||||
|
|
|
@ -38,6 +38,10 @@ teardown:
|
|||
body: >
|
||||
{
|
||||
"cluster": ["all"],
|
||||
"metadata": {
|
||||
"key1" : "val1",
|
||||
"key2" : "val2"
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"names": "*",
|
||||
|
@ -73,5 +77,7 @@ teardown:
|
|||
xpack.security.get_role:
|
||||
name: "admin_role"
|
||||
- match: { admin_role.cluster.0: "all" }
|
||||
- match: { admin_role.metadata.key1: "val1" }
|
||||
- match: { admin_role.metadata.key2: "val2" }
|
||||
- match: { admin_role.indices.0.names.0: "*" }
|
||||
- match: { admin_role.indices.0.privileges.0: "all" }
|
||||
|
|
Loading…
Reference in New Issue