Introduce Application Privileges with support for Kibana RBAC (#32309)

This commit introduces "Application Privileges" to the X-Pack security
model.

Application Privileges are managed within Elasticsearch, and can be
tested with the _has_privileges API, but do not grant access to any
actions or resources within Elasticsearch. Their purpose is to allow
applications outside of Elasticsearch to represent and store their own
privileges model within Elasticsearch roles.

Access to manage application privileges is handled in a new way that
grants permission to specific application names only. This lays the
foundation for more OLS on cluster privileges, which is implemented by
allowing a cluster permission to inspect not just the action being
executed, but also the request to which the action is applied.
To support this, a "conditional cluster privilege" is introduced, which
is like the existing cluster privilege, except that it has a Predicate
over the request as well as over the action name.

Specifically, this adds
- GET/PUT/DELETE actions for defining application level privileges
- application privileges in role definitions
- application privileges in the has_privileges API
- changes to the cluster permission class to support checking of request
  objects
- a new "global" element on role definition to provide cluster object
  level security (only for manage application privileges)
- changes to `kibana_user`, `kibana_dashboard_only_user` and
  `kibana_system` roles to use and manage application privileges

Closes #29820
Closes #31559
This commit is contained in:
Tim Vernum 2018-07-25 02:34:46 +10:00 committed by Jay Modi
parent e6b9f59e4e
commit 387c3c7f1d
93 changed files with 7171 additions and 682 deletions

View File

@ -59,6 +59,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
@ -931,8 +932,23 @@ public abstract class StreamInput extends InputStream {
* Reads a list of objects
*/
public <T> List<T> readList(Writeable.Reader<T> reader) throws IOException {
return readCollection(reader, ArrayList::new);
}
/**
* Reads a set of objects
*/
public <T> Set<T> readSet(Writeable.Reader<T> reader) throws IOException {
return readCollection(reader, HashSet::new);
}
/**
* Reads a collection of objects
*/
private <T, C extends Collection<? super T>> C readCollection(Writeable.Reader<T> reader,
IntFunction<C> constructor) throws IOException {
int count = readArraySize();
List<T> builder = new ArrayList<>(count);
C builder = constructor.apply(count);
for (int i=0; i<count; i++) {
builder.add(reader.read(this));
}

View File

@ -55,6 +55,7 @@ import java.nio.file.FileSystemLoopException;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
@ -995,6 +996,16 @@ public abstract class StreamOutput extends OutputStream {
}
}
/**
* Writes a collection of generic objects via a {@link Writer}
*/
public <T> void writeCollection(Collection<T> collection, Writer<T> writer) throws IOException {
writeVInt(collection.size());
for (T val: collection) {
writer.write(this, val);
}
}
/**
* Writes a list of strings
*/

View File

@ -31,6 +31,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
@ -42,6 +43,7 @@ import java.util.stream.IntStream;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.iterableWithSize;
public class StreamTests extends ESTestCase {
@ -65,7 +67,7 @@ public class StreamTests extends ESTestCase {
final Set<Byte> set = IntStream.range(Byte.MIN_VALUE, Byte.MAX_VALUE).mapToObj(v -> (byte) v).collect(Collectors.toSet());
set.remove((byte) 0);
set.remove((byte) 1);
final byte[] corruptBytes = new byte[] { randomFrom(set) };
final byte[] corruptBytes = new byte[]{randomFrom(set)};
final BytesReference corrupt = new BytesArray(corruptBytes);
final IllegalStateException e = expectThrows(IllegalStateException.class, () -> corrupt.streamInput().readBoolean());
final String message = String.format(Locale.ROOT, "unexpected byte [0x%02x]", corruptBytes[0]);
@ -100,7 +102,7 @@ public class StreamTests extends ESTestCase {
set.remove((byte) 0);
set.remove((byte) 1);
set.remove((byte) 2);
final byte[] corruptBytes = new byte[] { randomFrom(set) };
final byte[] corruptBytes = new byte[]{randomFrom(set)};
final BytesReference corrupt = new BytesArray(corruptBytes);
final IllegalStateException e = expectThrows(IllegalStateException.class, () -> corrupt.streamInput().readOptionalBoolean());
final String message = String.format(Locale.ROOT, "unexpected byte [0x%02x]", corruptBytes[0]);
@ -119,22 +121,22 @@ public class StreamTests extends ESTestCase {
public void testSpecificVLongSerialization() throws IOException {
List<Tuple<Long, byte[]>> values =
Arrays.asList(
new Tuple<>(0L, new byte[]{0}),
new Tuple<>(-1L, new byte[]{1}),
new Tuple<>(1L, new byte[]{2}),
new Tuple<>(-2L, new byte[]{3}),
new Tuple<>(2L, new byte[]{4}),
new Tuple<>(Long.MIN_VALUE, new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, 1}),
new Tuple<>(Long.MAX_VALUE, new byte[]{-2, -1, -1, -1, -1, -1, -1, -1, -1, 1})
Arrays.asList(
new Tuple<>(0L, new byte[]{0}),
new Tuple<>(-1L, new byte[]{1}),
new Tuple<>(1L, new byte[]{2}),
new Tuple<>(-2L, new byte[]{3}),
new Tuple<>(2L, new byte[]{4}),
new Tuple<>(Long.MIN_VALUE, new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, 1}),
new Tuple<>(Long.MAX_VALUE, new byte[]{-2, -1, -1, -1, -1, -1, -1, -1, -1, 1})
);
);
for (Tuple<Long, byte[]> value : values) {
BytesStreamOutput out = new BytesStreamOutput();
out.writeZLong(value.v1());
assertArrayEquals(Long.toString(value.v1()), value.v2(), BytesReference.toBytes(out.bytes()));
BytesReference bytes = new BytesArray(value.v2());
assertEquals(Arrays.toString(value.v2()), (long)value.v1(), bytes.streamInput().readZLong());
assertEquals(Arrays.toString(value.v2()), (long) value.v1(), bytes.streamInput().readZLong());
}
}
@ -158,7 +160,7 @@ public class StreamTests extends ESTestCase {
}
BytesStreamOutput out = new BytesStreamOutput();
out.writeGenericValue(write);
LinkedHashMap<String, Integer> read = (LinkedHashMap<String, Integer>)out.bytes().streamInput().readGenericValue();
LinkedHashMap<String, Integer> read = (LinkedHashMap<String, Integer>) out.bytes().streamInput().readGenericValue();
assertEquals(size, read.size());
int index = 0;
for (Map.Entry<String, Integer> entry : read.entrySet()) {
@ -172,7 +174,8 @@ public class StreamTests extends ESTestCase {
final int length = randomIntBetween(1, 1024);
StreamInput delegate = StreamInput.wrap(new byte[length]);
FilterStreamInput filterInputStream = new FilterStreamInput(delegate) {};
FilterStreamInput filterInputStream = new FilterStreamInput(delegate) {
};
assertEquals(filterInputStream.available(), length);
// read some bytes
@ -201,7 +204,7 @@ public class StreamTests extends ESTestCase {
}
stream.writeByteArray(array);
InputStreamStreamInput streamInput = new InputStreamStreamInput(StreamInput.wrap(BytesReference.toBytes(stream.bytes())), array
.length-1);
.length - 1);
expectThrows(EOFException.class, streamInput::readByteArray);
streamInput = new InputStreamStreamInput(StreamInput.wrap(BytesReference.toBytes(stream.bytes())), BytesReference.toBytes(stream
.bytes()).length);
@ -230,6 +233,21 @@ public class StreamTests extends ESTestCase {
assertThat(targetArray, equalTo(sourceArray));
}
public void testSetOfLongs() throws IOException {
final int size = randomIntBetween(0, 6);
final Set<Long> sourceSet = new HashSet<>(size);
for (int i = 0; i < size; i++) {
sourceSet.add(randomLongBetween(i * 1000, (i + 1) * 1000 - 1));
}
assertThat(sourceSet, iterableWithSize(size));
final BytesStreamOutput out = new BytesStreamOutput();
out.writeCollection(sourceSet, StreamOutput::writeLong);
final Set<Long> targetSet = out.bytes().streamInput().readSet(StreamInput::readLong);
assertThat(targetSet, equalTo(sourceSet));
}
static final class WriteableString implements Writeable {
final String string;

View File

@ -149,6 +149,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -717,6 +718,20 @@ public abstract class ESTestCase extends LuceneTestCase {
return generateRandomStringArray(maxArraySize, stringSize, allowNull, true);
}
public static <T> T[] randomArray(int maxArraySize, IntFunction<T[]> arrayConstructor, Supplier<T> valueConstructor) {
return randomArray(0, maxArraySize, arrayConstructor, valueConstructor);
}
public static <T> T[] randomArray(int minArraySize, int maxArraySize, IntFunction<T[]> arrayConstructor, Supplier<T> valueConstructor) {
final int size = randomIntBetween(minArraySize, maxArraySize);
final T[] array = arrayConstructor.apply(size);
for (int i = 0; i < array.length; i++) {
array[i] = valueConstructor.get();
}
return array;
}
private static final String[] TIME_SUFFIXES = new String[]{"d", "h", "ms", "s", "m", "micros", "nanos"};
public static String randomTimeValue(int lower, int upper, String... suffixes) {

View File

@ -84,7 +84,8 @@ The following example output indicates which privileges the "rdeniro" user has:
"read" : true,
"write" : false
}
}
},
"application" : {}
}
--------------------------------------------------
// TESTRESPONSE[s/"rdeniro"/"$body.username"/]

View File

@ -140,6 +140,7 @@ role. If the role is not defined in the `native` realm, the request 404s.
},
"query" : "{\"match\": {\"title\": \"foo\"}}"
} ],
"applications" : [ ],
"run_as" : [ "other_user" ],
"metadata" : {
"version" : 1

View File

@ -133,6 +133,8 @@ import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExceptExpression;
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression;
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction;
@ -342,6 +344,11 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl
new NamedWriteableRegistry.Entry(ClusterState.Custom.class, TokenMetaData.TYPE, TokenMetaData::new),
new NamedWriteableRegistry.Entry(NamedDiff.class, TokenMetaData.TYPE, TokenMetaData::readDiffFrom),
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SECURITY, SecurityFeatureSetUsage::new),
// security : conditional privileges
new NamedWriteableRegistry.Entry(ConditionalClusterPrivilege.class,
ConditionalClusterPrivileges.ManageApplicationPrivileges.WRITEABLE_NAME,
ConditionalClusterPrivileges.ManageApplicationPrivileges::createFrom),
// security : role-mappings
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AllExpression.NAME, AllExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AnyExpression.NAME, AnyExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, FieldExpression.NAME, FieldExpression::new),

View File

@ -0,0 +1,17 @@
/*
* 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.core.security.action.privilege;
import java.util.Collection;
/**
* Interface implemented by all Requests that manage application privileges
*/
public interface ApplicationPrivilegesRequest {
Collection<String> getApplicationNames();
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.Action;
/**
* Action for deleting application privileges.
*/
public final class DeletePrivilegesAction extends Action<DeletePrivilegesResponse> {
public static final DeletePrivilegesAction INSTANCE = new DeletePrivilegesAction();
public static final String NAME = "cluster:admin/xpack/security/privilege/delete";
private DeletePrivilegesAction() {
super(NAME);
}
@Override
public DeletePrivilegesResponse newResponse() {
return new DeletePrivilegesResponse();
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* A request to delete an application privilege.
*/
public final class DeletePrivilegesRequest extends ActionRequest
implements ApplicationPrivilegesRequest, WriteRequest<DeletePrivilegesRequest> {
private String application;
private String[] privileges;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
public DeletePrivilegesRequest() {
this(null, Strings.EMPTY_ARRAY);
}
public DeletePrivilegesRequest(String application, String[] privileges) {
this.application = application;
this.privileges = privileges;
}
@Override
public DeletePrivilegesRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
this.refreshPolicy = refreshPolicy;
return this;
}
@Override
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(application)) {
validationException = addValidationError("application name is missing", validationException);
}
if (privileges == null || privileges.length == 0 || Arrays.stream(privileges).allMatch(Strings::isNullOrEmpty)) {
validationException = addValidationError("privileges are missing", validationException);
}
return validationException;
}
public void application(String application) {
this.application = application;
}
public String application() {
return application;
}
@Override
public Collection<String> getApplicationNames() {
return Collections.singleton(application);
}
public String[] privileges() {
return this.privileges;
}
public void privileges(String[] privileges) {
this.privileges = privileges;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
application = in.readString();
privileges = in.readStringArray();
refreshPolicy = RefreshPolicy.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(application);
out.writeStringArray(privileges);
refreshPolicy.writeTo(out);
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Builder for {@link DeletePrivilegesRequest}
*/
public final class DeletePrivilegesRequestBuilder extends ActionRequestBuilder<DeletePrivilegesRequest, DeletePrivilegesResponse>
implements WriteRequestBuilder<DeletePrivilegesRequestBuilder> {
public DeletePrivilegesRequestBuilder(ElasticsearchClient client, DeletePrivilegesAction action) {
super(client, action, new DeletePrivilegesRequest());
}
public DeletePrivilegesRequestBuilder privileges(String[] privileges) {
request.privileges(privileges);
return this;
}
public DeletePrivilegesRequestBuilder application(String applicationName) {
request.application(applicationName);
return this;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Response when deleting application privileges.
* Returns a collection of privileges that were successfully found and deleted.
*/
public final class DeletePrivilegesResponse extends ActionResponse implements ToXContentObject {
private Set<String> found;
public DeletePrivilegesResponse() {
}
public DeletePrivilegesResponse(Collection<String> found) {
this.found = Collections.unmodifiableSet(new HashSet<>(found));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject().field("found", found).endObject();
return builder;
}
public Set<String> found() {
return this.found;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
this.found = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeCollection(found, StreamOutput::writeString);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.Action;
/**
* Action for retrieving one or more application privileges from the security index
*/
public final class GetPrivilegesAction extends Action<GetPrivilegesResponse> {
public static final GetPrivilegesAction INSTANCE = new GetPrivilegesAction();
public static final String NAME = "cluster:admin/xpack/security/privilege/get";
private GetPrivilegesAction() {
super(NAME);
}
@Override
public GetPrivilegesResponse newResponse() {
return new GetPrivilegesResponse();
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* Request to retrieve one or more application privileges.
*/
public final class GetPrivilegesRequest extends ActionRequest implements ApplicationPrivilegesRequest {
@Nullable
private String application;
private String[] privileges;
public GetPrivilegesRequest() {
privileges = Strings.EMPTY_ARRAY;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (privileges == null) {
validationException = addValidationError("privileges cannot be null", validationException);
}
return validationException;
}
public void application(String application) {
this.application = application;
}
public String application() {
return this.application;
}
@Override
public Collection<String> getApplicationNames() {
return Collections.singleton(application);
}
public void privileges(String... privileges) {
this.privileges = privileges;
}
public String[] privileges() {
return this.privileges;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
application = in.readOptionalString();
privileges = in.readStringArray();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(application);
out.writeStringArray(privileges);
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Builder for {@link GetPrivilegesRequest}
*/
public final class GetPrivilegesRequestBuilder extends ActionRequestBuilder<GetPrivilegesRequest, GetPrivilegesResponse> {
public GetPrivilegesRequestBuilder(ElasticsearchClient client, GetPrivilegesAction action) {
super(client, action, new GetPrivilegesRequest());
}
public GetPrivilegesRequestBuilder privileges(String... privileges) {
request.privileges(privileges);
return this;
}
public GetPrivilegesRequestBuilder application(String applicationName) {
request.application(applicationName);
return this;
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import java.io.IOException;
import java.util.Collection;
/**
* Response containing one or more application privileges retrieved from the security index
*/
public final class GetPrivilegesResponse extends ActionResponse {
private ApplicationPrivilegeDescriptor[] privileges;
public GetPrivilegesResponse(ApplicationPrivilegeDescriptor... privileges) {
this.privileges = privileges;
}
public GetPrivilegesResponse(Collection<ApplicationPrivilegeDescriptor> privileges) {
this(privileges.toArray(new ApplicationPrivilegeDescriptor[privileges.size()]));
}
public ApplicationPrivilegeDescriptor[] privileges() {
return privileges;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
this.privileges = in.readArray(ApplicationPrivilegeDescriptor::new, ApplicationPrivilegeDescriptor[]::new);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeArray(privileges);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.Action;
/**
* Action for putting (adding/updating) one or more application privileges.
*/
public final class PutPrivilegesAction extends Action<PutPrivilegesResponse> {
public static final PutPrivilegesAction INSTANCE = new PutPrivilegesAction();
public static final String NAME = "cluster:admin/xpack/security/privilege/put";
private PutPrivilegesAction() {
super(NAME);
}
@Override
public PutPrivilegesResponse newResponse() {
return new PutPrivilegesResponse();
}
}

View File

@ -0,0 +1,124 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* Request object to put a one or more application privileges.
*/
public final class PutPrivilegesRequest extends ActionRequest implements ApplicationPrivilegesRequest, WriteRequest<PutPrivilegesRequest> {
private List<ApplicationPrivilegeDescriptor> privileges;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
public PutPrivilegesRequest() {
privileges = Collections.emptyList();
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
for (ApplicationPrivilegeDescriptor privilege : privileges) {
try {
ApplicationPrivilege.validateApplicationName(privilege.getApplication());
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
try {
ApplicationPrivilege.validatePrivilegeName(privilege.getName());
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
if (privilege.getActions().isEmpty()) {
validationException = addValidationError("Application privileges must have at least one action", validationException);
}
for (String action : privilege.getActions()) {
if (action.indexOf('/') == -1 && action.indexOf('*') == -1 && action.indexOf(':') == -1) {
validationException = addValidationError("action [" + action + "] must contain one of [ '/' , '*' , ':' ]",
validationException);
}
try {
ApplicationPrivilege.validatePrivilegeOrActionName(action);
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
}
if (MetadataUtils.containsReservedMetadata(privilege.getMetadata())) {
validationException = addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX
+ "] (in privilege " + privilege.getApplication() + ' ' + privilege.getName() + ")", validationException);
}
}
return validationException;
}
/**
* Should this request trigger a refresh ({@linkplain RefreshPolicy#IMMEDIATE}, the default), wait for a refresh (
* {@linkplain RefreshPolicy#WAIT_UNTIL}), or proceed ignore refreshes entirely ({@linkplain RefreshPolicy#NONE}).
*/
@Override
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
@Override
public PutPrivilegesRequest setRefreshPolicy(RefreshPolicy refreshPolicy) {
this.refreshPolicy = refreshPolicy;
return this;
}
public List<ApplicationPrivilegeDescriptor> getPrivileges() {
return privileges;
}
public void setPrivileges(Collection<ApplicationPrivilegeDescriptor> privileges) {
this.privileges = Collections.unmodifiableList(new ArrayList<>(privileges));
}
@Override
public Collection<String> getApplicationNames() {
return Collections.unmodifiableSet(privileges.stream()
.map(ApplicationPrivilegeDescriptor::getApplication)
.collect(Collectors.toSet()));
}
@Override
public String toString() {
return getClass().getSimpleName() + "{[" + privileges.stream().map(Strings::toString).collect(Collectors.joining(","))
+ "];" + refreshPolicy + "}";
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
privileges = Collections.unmodifiableList(in.readList(ApplicationPrivilegeDescriptor::new));
refreshPolicy = RefreshPolicy.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(privileges);
refreshPolicy.writeTo(out);
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Request builder for {@link PutPrivilegesRequest}
*/
public final class PutPrivilegesRequestBuilder extends ActionRequestBuilder<PutPrivilegesRequest, PutPrivilegesResponse>
implements WriteRequestBuilder<PutPrivilegesRequestBuilder> {
public PutPrivilegesRequestBuilder(ElasticsearchClient client, PutPrivilegesAction action) {
super(client, action, new PutPrivilegesRequest());
}
/**
* Populate the put privileges request using the given source, application name and privilege name
* The source must contain a single privilege object which matches the application and privilege names.
*/
public PutPrivilegesRequestBuilder source(String applicationName, String expectedName,
BytesReference source, XContentType xContentType)
throws IOException {
Objects.requireNonNull(xContentType);
// EMPTY is ok here because we never call namedObject
try (InputStream stream = source.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
XContentParser.Token token = parser.currentToken();
if (token == null) {
token = parser.nextToken();
}
if (token == XContentParser.Token.START_OBJECT) {
final ApplicationPrivilegeDescriptor privilege = parsePrivilege(parser, applicationName, expectedName);
this.request.setPrivileges(Collections.singleton(privilege));
} else {
throw new ElasticsearchParseException("expected an object but found {} instead", token);
}
}
return this;
}
ApplicationPrivilegeDescriptor parsePrivilege(XContentParser parser, String applicationName, String privilegeName) throws IOException {
ApplicationPrivilegeDescriptor privilege = ApplicationPrivilegeDescriptor.parse(parser, applicationName, privilegeName, false);
checkPrivilegeName(privilege, applicationName, privilegeName);
return privilege;
}
/**
* Populate the put privileges request using the given source, application name and privilege name
* The source must contain a top-level object, keyed by application name.
* The value for each application-name, is an object keyed by privilege name.
* The value for each privilege-name is a privilege object which much match the application and privilege names in which it is nested.
*/
public PutPrivilegesRequestBuilder source(BytesReference source, XContentType xContentType)
throws IOException {
Objects.requireNonNull(xContentType);
// EMPTY is ok here because we never call namedObject
try (InputStream stream = source.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
XContentParser.Token token = parser.currentToken();
if (token == null) {
token = parser.nextToken();
}
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException("expected object but found {} instead", token);
}
List<ApplicationPrivilegeDescriptor> privileges = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
token = parser.currentToken();
assert token == XContentParser.Token.FIELD_NAME : "Invalid token " + token;
final String applicationName = parser.currentName();
token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException("expected the value for {} to be an object, but found {} instead",
applicationName, token);
}
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
token = parser.currentToken();
assert (token == XContentParser.Token.FIELD_NAME);
final String privilegeName = parser.currentName();
token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException("expected the value for {} to be an object, but found {} instead",
applicationName, token);
}
privileges.add(parsePrivilege(parser, applicationName, privilegeName));
}
}
request.setPrivileges(privileges);
}
return this;
}
private void checkPrivilegeName(ApplicationPrivilegeDescriptor privilege, String applicationName, String providedName) {
final String privilegeName = privilege.getName();
if (Strings.isNullOrEmpty(applicationName) == false && applicationName.equals(privilege.getApplication()) == false) {
throw new IllegalArgumentException("privilege application [" + privilege.getApplication()
+ "] in source does not match the provided application [" + applicationName + "]");
}
if (Strings.isNullOrEmpty(providedName) == false && providedName.equals(privilegeName) == false) {
throw new IllegalArgumentException("privilege name [" + privilegeName
+ "] in source does not match the provided name [" + providedName + "]");
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Response when adding one or more application privileges to the security index.
* Returns a collection of the privileges that were created (by implication, any other privileges were updated).
*/
public final class PutPrivilegesResponse extends ActionResponse implements ToXContentObject {
private Map<String, List<String>> created;
PutPrivilegesResponse() {
this(Collections.emptyMap());
}
public PutPrivilegesResponse(Map<String, List<String>> created) {
this.created = Collections.unmodifiableMap(created);
}
/**
* Get a list of privileges that were created (as opposed to updated)
* @return A map from <em>Application Name</em> to a {@code List} of <em>privilege names</em>
*/
public Map<String, List<String>> created() {
return created;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject().field("created", created).endObject();
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeMap(created, StreamOutput::writeString, StreamOutput::writeStringList);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
this.created = Collections.unmodifiableMap(in.readMap(StreamInput::readString, si -> si.readList(StreamInput::readString)));
}
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.core.security.action.role;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
@ -14,11 +15,15 @@ import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -31,11 +36,13 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
private String name;
private String[] clusterPrivileges = Strings.EMPTY_ARRAY;
private ConditionalClusterPrivilege[] conditionalClusterPrivileges = ConditionalClusterPrivileges.EMPTY_ARRAY;
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
private List<RoleDescriptor.ApplicationResourcePrivileges> applicationPrivileges = new ArrayList<>();
private String[] runAs = Strings.EMPTY_ARRAY;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
private Map<String, Object> metadata;
public PutRoleRequest() {
}
@ -45,9 +52,25 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
if (name == null) {
validationException = addValidationError("role name is missing", validationException);
}
if(applicationPrivileges != null) {
for (RoleDescriptor.ApplicationResourcePrivileges privilege : applicationPrivileges) {
try {
ApplicationPrivilege.validateApplicationNameOrWildcard(privilege.getApplication());
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
for (String name : privilege.getPrivileges()) {
try {
ApplicationPrivilege.validatePrivilegeOrActionName(name);
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
}
}
}
if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) {
validationException =
addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", validationException);
addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", validationException);
}
return validationException;
}
@ -60,6 +83,10 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
this.clusterPrivileges = clusterPrivileges;
}
void conditionalCluster(ConditionalClusterPrivilege... conditionalClusterPrivileges) {
this.conditionalClusterPrivileges = conditionalClusterPrivileges;
}
void addIndex(RoleDescriptor.IndicesPrivileges... privileges) {
this.indicesPrivileges.addAll(Arrays.asList(privileges));
}
@ -75,6 +102,10 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
.build());
}
void addApplicationPrivileges(RoleDescriptor.ApplicationResourcePrivileges... privileges) {
this.applicationPrivileges.addAll(Arrays.asList(privileges));
}
public void runAs(String... usernames) {
this.runAs = usernames;
}
@ -110,6 +141,14 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
return indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]);
}
public List<RoleDescriptor.ApplicationResourcePrivileges> applicationPrivileges() {
return Collections.unmodifiableList(applicationPrivileges);
}
public ConditionalClusterPrivilege[] conditionalClusterPrivileges() {
return conditionalClusterPrivileges;
}
public String[] runAs() {
return runAs;
}
@ -128,6 +167,10 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
for (int i = 0; i < indicesSize; i++) {
indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.createFrom(in));
}
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
applicationPrivileges = in.readList(RoleDescriptor.ApplicationResourcePrivileges::createFrom);
conditionalClusterPrivileges = ConditionalClusterPrivileges.readArray(in);
}
runAs = in.readStringArray();
refreshPolicy = RefreshPolicy.readFrom(in);
metadata = in.readMap();
@ -142,6 +185,10 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
for (RoleDescriptor.IndicesPrivileges index : indicesPrivileges) {
index.writeTo(out);
}
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeStreamableList(applicationPrivileges);
ConditionalClusterPrivileges.writeArray(out, this.conditionalClusterPrivileges);
}
out.writeStringArray(runAs);
refreshPolicy.writeTo(out);
out.writeMap(metadata);
@ -151,7 +198,11 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest<PutRol
return new RoleDescriptor(name,
clusterPrivileges,
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]),
applicationPrivileges.toArray(new RoleDescriptor.ApplicationResourcePrivileges[applicationPrivileges.size()]),
conditionalClusterPrivileges,
runAs,
metadata);
metadata,
Collections.emptyMap());
}
}
}

View File

@ -40,7 +40,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
assert name.equals(descriptor.getName());
request.name(name);
request.cluster(descriptor.getClusterPrivileges());
request.conditionalCluster(descriptor.getConditionalClusterPrivileges());
request.addIndex(descriptor.getIndicesPrivileges());
request.addApplicationPrivileges(descriptor.getApplicationPrivileges());
request.runAs(descriptor.getRunAs());
request.metadata(descriptor.getMetadata());
return this;

View File

@ -5,11 +5,14 @@
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import java.io.IOException;
@ -23,6 +26,7 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
private String username;
private String[] clusterPrivileges;
private RoleDescriptor.IndicesPrivileges[] indexPrivileges;
private ApplicationResourcePrivileges[] applicationPrivileges;
@Override
public ActionRequestValidationException validate() {
@ -33,9 +37,21 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
if (indexPrivileges == null) {
validationException = addValidationError("indexPrivileges must not be null", validationException);
}
if (clusterPrivileges != null && clusterPrivileges.length == 0 && indexPrivileges != null && indexPrivileges.length == 0) {
validationException = addValidationError("clusterPrivileges and indexPrivileges cannot both be empty",
validationException);
if (applicationPrivileges == null) {
validationException = addValidationError("applicationPrivileges must not be null", validationException);
} else {
for (ApplicationResourcePrivileges applicationPrivilege : applicationPrivileges) {
try {
ApplicationPrivilege.validateApplicationName(applicationPrivilege.getApplication());
} catch (IllegalArgumentException e) {
validationException = addValidationError(e.getMessage(), validationException);
}
}
}
if (clusterPrivileges != null && clusterPrivileges.length == 0
&& indexPrivileges != null && indexPrivileges.length == 0
&& applicationPrivileges != null && applicationPrivileges.length == 0) {
validationException = addValidationError("must specify at least one privilege", validationException);
}
return validationException;
}
@ -67,6 +83,10 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
return clusterPrivileges;
}
public ApplicationResourcePrivileges[] applicationPrivileges() {
return applicationPrivileges;
}
public void indexPrivileges(RoleDescriptor.IndicesPrivileges... privileges) {
this.indexPrivileges = privileges;
}
@ -75,6 +95,10 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
this.clusterPrivileges = privileges;
}
public void applicationPrivileges(ApplicationResourcePrivileges... appPrivileges) {
this.applicationPrivileges = appPrivileges;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -85,6 +109,9 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
for (int i = 0; i < indexSize; i++) {
indexPrivileges[i] = RoleDescriptor.IndicesPrivileges.createFrom(in);
}
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
applicationPrivileges = in.readArray(ApplicationResourcePrivileges::createFrom, ApplicationResourcePrivileges[]::new);
}
}
@Override
@ -96,6 +123,9 @@ public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
for (RoleDescriptor.IndicesPrivileges priv : indexPrivileges) {
priv.writeTo(out);
}
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeArray(ApplicationResourcePrivileges::write, applicationPrivileges);
}
}
}

View File

@ -39,6 +39,7 @@ public class HasPrivilegesRequestBuilder
request.username(username);
request.indexPrivileges(role.getIndicesPrivileges());
request.clusterPrivileges(role.getClusterPrivileges());
request.applicationPrivileges(role.getApplicationPrivileges());
return this;
}
}

View File

@ -5,6 +5,11 @@
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@ -14,27 +19,27 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Response for a {@link HasPrivilegesRequest}
*/
public class HasPrivilegesResponse extends ActionResponse {
private boolean completeMatch;
private Map<String, Boolean> cluster;
private List<IndexPrivileges> index;
private List<ResourcePrivileges> index;
private Map<String, List<ResourcePrivileges>> application;
public HasPrivilegesResponse() {
this(true, Collections.emptyMap(), Collections.emptyList());
this(true, Collections.emptyMap(), Collections.emptyList(), Collections.emptyMap());
}
public HasPrivilegesResponse(boolean completeMatch, Map<String, Boolean> cluster, Collection<IndexPrivileges> index) {
public HasPrivilegesResponse(boolean completeMatch, Map<String, Boolean> cluster, Collection<ResourcePrivileges> index,
Map<String, Collection<ResourcePrivileges>> application) {
super();
this.completeMatch = completeMatch;
this.cluster = new HashMap<>(cluster);
this.index = new ArrayList<>(index);
this.application = new HashMap<>();
application.forEach((key, val) -> this.application.put(key, Collections.unmodifiableList(new ArrayList<>(val))));
}
public boolean isCompleteMatch() {
@ -45,44 +50,67 @@ public class HasPrivilegesResponse extends ActionResponse {
return Collections.unmodifiableMap(cluster);
}
public List<IndexPrivileges> getIndexPrivileges() {
public List<ResourcePrivileges> getIndexPrivileges() {
return Collections.unmodifiableList(index);
}
/**
* Retrieves the results from checking application privileges,
* @return A {@code Map} keyed by application-name
*/
public Map<String, List<ResourcePrivileges>> getApplicationPrivileges() {
return Collections.unmodifiableMap(application);
}
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
completeMatch = in.readBoolean();
int count = in.readVInt();
index = new ArrayList<>(count);
index = readResourcePrivileges(in);
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
application = in.readMap(StreamInput::readString, HasPrivilegesResponse::readResourcePrivileges);
}
}
private static List<ResourcePrivileges> readResourcePrivileges(StreamInput in) throws IOException {
final int count = in.readVInt();
final List<ResourcePrivileges> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
final String index = in.readString();
final Map<String, Boolean> privileges = in.readMap(StreamInput::readString, StreamInput::readBoolean);
this.index.add(new IndexPrivileges(index, privileges));
list.add(new ResourcePrivileges(index, privileges));
}
return list;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(completeMatch);
out.writeVInt(index.size());
for (IndexPrivileges index : index) {
out.writeString(index.index);
out.writeMap(index.privileges, StreamOutput::writeString, StreamOutput::writeBoolean);
writeResourcePrivileges(out, index);
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeMap(application, StreamOutput::writeString, HasPrivilegesResponse::writeResourcePrivileges);
}
}
public static class IndexPrivileges {
private final String index;
private static void writeResourcePrivileges(StreamOutput out, List<ResourcePrivileges> privileges) throws IOException {
out.writeVInt(privileges.size());
for (ResourcePrivileges priv : privileges) {
out.writeString(priv.resource);
out.writeMap(priv.privileges, StreamOutput::writeString, StreamOutput::writeBoolean);
}
}
public static class ResourcePrivileges {
private final String resource;
private final Map<String, Boolean> privileges;
public IndexPrivileges(String index, Map<String, Boolean> privileges) {
this.index = Objects.requireNonNull(index);
public ResourcePrivileges(String resource, Map<String, Boolean> privileges) {
this.resource = Objects.requireNonNull(resource);
this.privileges = Collections.unmodifiableMap(privileges);
}
public String getIndex() {
return index;
public String getResource() {
return resource;
}
public Map<String, Boolean> getPrivileges() {
@ -92,14 +120,14 @@ public class HasPrivilegesResponse extends ActionResponse {
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"index='" + index + '\'' +
"resource='" + resource + '\'' +
", privileges=" + privileges +
'}';
}
@Override
public int hashCode() {
int result = index.hashCode();
int result = resource.hashCode();
result = 31 * result + privileges.hashCode();
return result;
}
@ -113,8 +141,8 @@ public class HasPrivilegesResponse extends ActionResponse {
return false;
}
final IndexPrivileges other = (IndexPrivileges) o;
return this.index.equals(other.index) && this.privileges.equals(other.privileges);
final ResourcePrivileges other = (ResourcePrivileges) o;
return this.resource.equals(other.resource) && this.privileges.equals(other.privileges);
}
}
}

View File

@ -18,11 +18,14 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.core.security.xcontent.XContentUtils;
@ -31,9 +34,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A holder for a Role that contains user-readable information about the Role
@ -45,7 +50,9 @@ public class RoleDescriptor implements ToXContentObject {
private final String name;
private final String[] clusterPrivileges;
private final ConditionalClusterPrivilege[] conditionalClusterPrivileges;
private final IndicesPrivileges[] indicesPrivileges;
private final ApplicationResourcePrivileges[] applicationPrivileges;
private final String[] runAs;
private final Map<String, Object> metadata;
private final Map<String, Object> transientMetadata;
@ -57,6 +64,11 @@ public class RoleDescriptor implements ToXContentObject {
this(name, clusterPrivileges, indicesPrivileges, runAs, null);
}
/**
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
* ConditionalClusterPrivilege[], String[], Map, Map)}
*/
@Deprecated
public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@ -65,16 +77,34 @@ public class RoleDescriptor implements ToXContentObject {
this(name, clusterPrivileges, indicesPrivileges, runAs, metadata, null);
}
/**
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
* ConditionalClusterPrivilege[], String[], Map, Map)}
*/
@Deprecated
public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@Nullable String[] runAs,
@Nullable Map<String, Object> metadata,
@Nullable Map<String, Object> transientMetadata) {
this(name, clusterPrivileges, indicesPrivileges, null, null, runAs, metadata, transientMetadata);
}
public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@Nullable ApplicationResourcePrivileges[] applicationPrivileges,
@Nullable ConditionalClusterPrivilege[] conditionalClusterPrivileges,
@Nullable String[] runAs,
@Nullable Map<String, Object> metadata,
@Nullable Map<String, Object> transientMetadata) {
this.name = name;
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
this.conditionalClusterPrivileges = conditionalClusterPrivileges != null
? conditionalClusterPrivileges : ConditionalClusterPrivileges.EMPTY_ARRAY;
this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE;
this.applicationPrivileges = applicationPrivileges != null ? applicationPrivileges : ApplicationResourcePrivileges.NONE;
this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY;
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
this.transientMetadata = transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) :
@ -89,10 +119,18 @@ public class RoleDescriptor implements ToXContentObject {
return this.clusterPrivileges;
}
public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() {
return this.conditionalClusterPrivileges;
}
public IndicesPrivileges[] getIndicesPrivileges() {
return this.indicesPrivileges;
}
public ApplicationResourcePrivileges[] getApplicationPrivileges() {
return this.applicationPrivileges;
}
public String[] getRunAs() {
return this.runAs;
}
@ -114,10 +152,15 @@ public class RoleDescriptor implements ToXContentObject {
StringBuilder sb = new StringBuilder("Role[");
sb.append("name=").append(name);
sb.append(", cluster=[").append(Strings.arrayToCommaDelimitedString(clusterPrivileges));
sb.append("], global=[").append(Strings.arrayToCommaDelimitedString(conditionalClusterPrivileges));
sb.append("], indicesPrivileges=[");
for (IndicesPrivileges group : indicesPrivileges) {
sb.append(group.toString()).append(",");
}
sb.append("], applicationPrivileges=[");
for (ApplicationResourcePrivileges privilege : applicationPrivileges) {
sb.append(privilege.toString()).append(",");
}
sb.append("], runAs=[").append(Strings.arrayToCommaDelimitedString(runAs));
sb.append("], metadata=[");
MetadataUtils.writeValue(sb, metadata);
@ -134,7 +177,9 @@ public class RoleDescriptor implements ToXContentObject {
if (!name.equals(that.name)) return false;
if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false;
if (!Arrays.equals(conditionalClusterPrivileges, that.conditionalClusterPrivileges)) return false;
if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false;
if (!Arrays.equals(applicationPrivileges, that.applicationPrivileges)) return false;
if (!metadata.equals(that.getMetadata())) return false;
return Arrays.equals(runAs, that.runAs);
}
@ -143,7 +188,9 @@ public class RoleDescriptor implements ToXContentObject {
public int hashCode() {
int result = name.hashCode();
result = 31 * result + Arrays.hashCode(clusterPrivileges);
result = 31 * result + Arrays.hashCode(conditionalClusterPrivileges);
result = 31 * result + Arrays.hashCode(indicesPrivileges);
result = 31 * result + Arrays.hashCode(applicationPrivileges);
result = 31 * result + Arrays.hashCode(runAs);
result = 31 * result + metadata.hashCode();
return result;
@ -157,8 +204,8 @@ public class RoleDescriptor implements ToXContentObject {
/**
* Generates x-content for this {@link RoleDescriptor} instance.
*
* @param builder the x-content builder
* @param params the parameters for x-content generation directives
* @param builder the x-content builder
* @param params the parameters for x-content generation directives
* @param docCreation {@code true} if the x-content is being generated for creating a document
* in the security index, {@code false} if the x-content being generated
* is for API display purposes
@ -168,7 +215,12 @@ public class RoleDescriptor implements ToXContentObject {
public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean docCreation) throws IOException {
builder.startObject();
builder.array(Fields.CLUSTER.getPreferredName(), clusterPrivileges);
if (conditionalClusterPrivileges.length != 0) {
builder.field(Fields.GLOBAL.getPreferredName());
ConditionalClusterPrivileges.toXContent(builder, params, Arrays.asList(conditionalClusterPrivileges));
}
builder.array(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges);
builder.array(Fields.APPLICATIONS.getPreferredName(), (Object[]) applicationPrivileges);
if (runAs != null) {
builder.array(Fields.RUN_AS.getPreferredName(), runAs);
}
@ -198,7 +250,19 @@ public class RoleDescriptor implements ToXContentObject {
} else {
transientMetadata = Collections.emptyMap();
}
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs, metadata, transientMetadata);
final ApplicationResourcePrivileges[] applicationPrivileges;
final ConditionalClusterPrivilege[] conditionalClusterPrivileges;
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
applicationPrivileges = in.readArray(ApplicationResourcePrivileges::createFrom, ApplicationResourcePrivileges[]::new);
conditionalClusterPrivileges = ConditionalClusterPrivileges.readArray(in);
} else {
applicationPrivileges = ApplicationResourcePrivileges.NONE;
conditionalClusterPrivileges = ConditionalClusterPrivileges.EMPTY_ARRAY;
}
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, conditionalClusterPrivileges,
runAs, metadata, transientMetadata);
}
public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
@ -213,6 +277,10 @@ public class RoleDescriptor implements ToXContentObject {
if (out.getVersion().onOrAfter(Version.V_5_2_0)) {
out.writeMap(descriptor.transientMetadata);
}
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeArray(ApplicationResourcePrivileges::write, descriptor.applicationPrivileges);
ConditionalClusterPrivileges.writeArray(out, descriptor.getConditionalClusterPrivileges());
}
}
public static RoleDescriptor parse(String name, BytesReference source, boolean allow2xFormat, XContentType xContentType)
@ -221,7 +289,7 @@ public class RoleDescriptor implements ToXContentObject {
// EMPTY is safe here because we never use namedObject
try (InputStream stream = source.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
return parse(name, parser, allow2xFormat);
}
}
@ -243,6 +311,8 @@ public class RoleDescriptor implements ToXContentObject {
String currentFieldName = null;
IndicesPrivileges[] indicesPrivileges = null;
String[] clusterPrivileges = null;
List<ConditionalClusterPrivilege> conditionalClusterPrivileges = Collections.emptyList();
ApplicationResourcePrivileges[] applicationPrivileges = null;
String[] runAsUsers = null;
Map<String, Object> metadata = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -255,6 +325,11 @@ public class RoleDescriptor implements ToXContentObject {
runAsUsers = readStringArray(name, parser, true);
} else if (Fields.CLUSTER.match(currentFieldName, parser.getDeprecationHandler())) {
clusterPrivileges = readStringArray(name, parser, true);
} else if (Fields.APPLICATIONS.match(currentFieldName, parser.getDeprecationHandler())
|| Fields.APPLICATION.match(currentFieldName, parser.getDeprecationHandler())) {
applicationPrivileges = parseApplicationPrivileges(name, parser);
} else if (Fields.GLOBAL.match(currentFieldName, parser.getDeprecationHandler())) {
conditionalClusterPrivileges = ConditionalClusterPrivileges.parse(parser);
} else if (Fields.METADATA.match(currentFieldName, parser.getDeprecationHandler())) {
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException(
@ -266,8 +341,7 @@ public class RoleDescriptor implements ToXContentObject {
// consume object but just drop
parser.map();
} else {
throw new ElasticsearchParseException("expected field [{}] to be an object, but found [{}] instead",
currentFieldName, token);
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
}
} else if (Fields.TYPE.match(currentFieldName, parser.getDeprecationHandler())) {
// don't need it
@ -275,7 +349,9 @@ public class RoleDescriptor implements ToXContentObject {
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
}
}
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers, metadata);
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges,
conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers,
metadata, null);
}
private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException {
@ -291,7 +367,7 @@ public class RoleDescriptor implements ToXContentObject {
throws IOException {
try (InputStream stream = source.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
// advance to the START_OBJECT token
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
@ -301,6 +377,7 @@ public class RoleDescriptor implements ToXContentObject {
String currentFieldName = null;
IndicesPrivileges[] indexPrivileges = null;
String[] clusterPrivileges = null;
ApplicationResourcePrivileges[] applicationPrivileges = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
@ -308,14 +385,17 @@ public class RoleDescriptor implements ToXContentObject {
indexPrivileges = parseIndices(description, parser, false);
} else if (Fields.CLUSTER.match(currentFieldName, parser.getDeprecationHandler())) {
clusterPrivileges = readStringArray(description, parser, true);
} else if (Fields.APPLICATIONS.match(currentFieldName, parser.getDeprecationHandler())
|| Fields.APPLICATION.match(currentFieldName, parser.getDeprecationHandler())) {
applicationPrivileges = parseApplicationPrivileges(description, parser);
} else {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. unexpected field [{}]",
description, currentFieldName);
}
}
if (indexPrivileges == null && clusterPrivileges == null) {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. fields [{}] and [{}] are both missing",
description, Fields.INDEX, Fields.CLUSTER);
if (indexPrivileges == null && clusterPrivileges == null && applicationPrivileges == null) {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. All privilege fields [{},{},{}] are missing",
description, Fields.CLUSTER, Fields.INDEX, Fields.APPLICATIONS);
}
if (indexPrivileges != null) {
if (Arrays.stream(indexPrivileges).anyMatch(IndicesPrivileges::isUsingFieldLevelSecurity)) {
@ -326,7 +406,7 @@ public class RoleDescriptor implements ToXContentObject {
throw new ElasticsearchParseException("Field [{}] is not supported in a has_privileges request", Fields.QUERY);
}
}
return new RoleDescriptor(description, clusterPrivileges, indexPrivileges, null);
return new RoleDescriptor(description, clusterPrivileges, indexPrivileges, applicationPrivileges, null, null, null, null);
}
}
@ -361,7 +441,7 @@ public class RoleDescriptor implements ToXContentObject {
currentFieldName = parser.currentName();
} else if (Fields.NAMES.match(currentFieldName, parser.getDeprecationHandler())) {
if (token == XContentParser.Token.VALUE_STRING) {
names = new String[] { parser.text() };
names = new String[]{parser.text()};
} else if (token == XContentParser.Token.START_ARRAY) {
names = readStringArray(roleName, parser, false);
if (names.length == 0) {
@ -474,6 +554,37 @@ public class RoleDescriptor implements ToXContentObject {
.build();
}
private static ApplicationResourcePrivileges[] parseApplicationPrivileges(String roleName, XContentParser parser)
throws IOException {
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new ElasticsearchParseException("failed to parse application privileges for role [{}]. expected field [{}] value " +
"to be an array, but found [{}] instead", roleName, parser.currentName(), parser.currentToken());
}
List<ApplicationResourcePrivileges> privileges = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
privileges.add(parseApplicationPrivilege(roleName, parser));
}
return privileges.toArray(new ApplicationResourcePrivileges[privileges.size()]);
}
private static ApplicationResourcePrivileges parseApplicationPrivilege(String roleName, XContentParser parser) throws IOException {
XContentParser.Token token = parser.currentToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException("failed to parse application privileges for role [{}]. expected field [{}] value to " +
"be an array of objects, but found an array element of type [{}]", roleName, parser.currentName(), token);
}
final ApplicationResourcePrivileges.Builder builder = ApplicationResourcePrivileges.PARSER.parse(parser, null);
if (builder.hasResources() == false) {
throw new ElasticsearchParseException("failed to parse application privileges for role [{}]. missing required [{}] field",
roleName, Fields.RESOURCES.getPreferredName());
}
if (builder.hasPrivileges() == false) {
throw new ElasticsearchParseException("failed to parse application privileges for role [{}]. missing required [{}] field",
roleName, Fields.PRIVILEGES.getPreferredName());
}
return builder.build();
}
/**
* A class representing permissions for a group of indices mapped to
* privileges, field permissions, and a query.
@ -695,14 +806,176 @@ public class RoleDescriptor implements ToXContentObject {
}
}
public static class ApplicationResourcePrivileges implements ToXContentObject, Streamable {
private static final ApplicationResourcePrivileges[] NONE = new ApplicationResourcePrivileges[0];
private static final ObjectParser<ApplicationResourcePrivileges.Builder, Void> PARSER = new ObjectParser<>("application",
ApplicationResourcePrivileges::builder);
static {
PARSER.declareString(Builder::application, Fields.APPLICATION);
PARSER.declareStringArray(Builder::privileges, Fields.PRIVILEGES);
PARSER.declareStringArray(Builder::resources, Fields.RESOURCES);
}
private String application;
private String[] privileges;
private String[] resources;
private ApplicationResourcePrivileges() {
}
public static Builder builder() {
return new Builder();
}
public String getApplication() {
return application;
}
public String[] getResources() {
return this.resources;
}
public String[] getPrivileges() {
return this.privileges;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName())
.append("[application=")
.append(application)
.append(", privileges=[")
.append(Strings.arrayToCommaDelimitedString(privileges))
.append("], resources=[")
.append(Strings.arrayToCommaDelimitedString(resources))
.append("]]");
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
ApplicationResourcePrivileges that = (ApplicationResourcePrivileges) o;
return Objects.equals(this.application, that.application)
&& Arrays.equals(this.resources, that.resources)
&& Arrays.equals(this.privileges, that.privileges);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(resources);
result = 31 * result + Arrays.hashCode(privileges);
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Fields.APPLICATION.getPreferredName(), application);
builder.array(Fields.PRIVILEGES.getPreferredName(), privileges);
builder.array(Fields.RESOURCES.getPreferredName(), resources);
return builder.endObject();
}
public static ApplicationResourcePrivileges createFrom(StreamInput in) throws IOException {
ApplicationResourcePrivileges ip = new ApplicationResourcePrivileges();
ip.readFrom(in);
return ip;
}
@Override
public void readFrom(StreamInput in) throws IOException {
this.application = in.readString();
this.privileges = in.readStringArray();
this.resources = in.readStringArray();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(application);
out.writeStringArray(privileges);
out.writeStringArray(resources);
}
public static void write(StreamOutput out, ApplicationResourcePrivileges privileges) throws IOException {
privileges.writeTo(out);
}
public static class Builder {
private ApplicationResourcePrivileges applicationPrivileges = new ApplicationResourcePrivileges();
private Builder() {
}
public Builder application(String appName) {
applicationPrivileges.application = appName;
return this;
}
public Builder resources(String... resources) {
applicationPrivileges.resources = resources;
return this;
}
public Builder resources(List<String> resources) {
return resources(resources.toArray(new String[resources.size()]));
}
public Builder privileges(String... privileges) {
applicationPrivileges.privileges = privileges;
return this;
}
public Builder privileges(Collection<String> privileges) {
return privileges(privileges.toArray(new String[privileges.size()]));
}
public boolean hasResources() {
return applicationPrivileges.resources != null;
}
public boolean hasPrivileges() {
return applicationPrivileges.privileges != null;
}
public ApplicationResourcePrivileges build() {
if (Strings.isNullOrEmpty(applicationPrivileges.application)) {
throw new IllegalArgumentException("application privileges must have an application name");
}
if (applicationPrivileges.privileges == null || applicationPrivileges.privileges.length == 0) {
throw new IllegalArgumentException("application privileges must define at least one privilege");
}
if (applicationPrivileges.resources == null || applicationPrivileges.resources.length == 0) {
throw new IllegalArgumentException("application privileges must refer to at least one resource");
}
return applicationPrivileges;
}
}
}
public interface Fields {
ParseField CLUSTER = new ParseField("cluster");
ParseField GLOBAL = new ParseField("global");
ParseField INDEX = new ParseField("index");
ParseField INDICES = new ParseField("indices");
ParseField APPLICATIONS = new ParseField("applications");
ParseField RUN_AS = new ParseField("run_as");
ParseField NAMES = new ParseField("names");
ParseField RESOURCES = new ParseField("resources");
ParseField QUERY = new ParseField("query");
ParseField PRIVILEGES = new ParseField("privileges");
ParseField APPLICATION = new ParseField("application");
ParseField FIELD_PERMISSIONS = new ParseField("field_security");
ParseField FIELD_PERMISSIONS_2X = new ParseField("fields");
ParseField GRANT_FIELDS = new ParseField("grant");

View File

@ -0,0 +1,110 @@
/*
* 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.core.security.authz.permission;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
/**
* A permission that is based on privileges for application (non elasticsearch) capabilities
*/
public final class ApplicationPermission {
public static final ApplicationPermission NONE = new ApplicationPermission(Collections.emptyList());
private final Logger logger;
private final List<PermissionEntry> permissions;
/**
* @param privilegesAndResources A list of (privilege, resources). Each element in the {@link List} is a {@link Tuple} containing
* a single {@link ApplicationPrivilege} and the {@link Set} of resources to which that privilege is
* applied. The resources are treated as a wildcard {@link Automatons#pattern}.
*/
ApplicationPermission(List<Tuple<ApplicationPrivilege, Set<String>>> privilegesAndResources) {
this.logger = Loggers.getLogger(getClass());
Map<ApplicationPrivilege, PermissionEntry> permissionsByPrivilege = new HashMap<>();
privilegesAndResources.forEach(tup -> permissionsByPrivilege.compute(tup.v1(), (k, existing) -> {
final Automaton patterns = Automatons.patterns(tup.v2());
if (existing == null) {
return new PermissionEntry(k, patterns);
} else {
return new PermissionEntry(k, Automatons.unionAndMinimize(Arrays.asList(existing.resources, patterns)));
}
}));
this.permissions = Collections.unmodifiableList(new ArrayList<>(permissionsByPrivilege.values()));
}
/**
* Determines whether this permission grants the specified privilege on the given resource.
* <p>
* An {@link ApplicationPermission} consists of a sequence of permission entries, where each entry contains a single
* {@link ApplicationPrivilege} and one or more resource patterns.
* </p>
* <p>
* This method returns {@code true} if, one or more of those entries meet the following criteria
* </p>
* <ul>
* <li>The entry's application, when interpreted as an {@link Automaton} {@link Automatons#pattern(String) pattern} matches the
* application given in the argument (interpreted as a raw string)
* </li>
* <li>The {@link ApplicationPrivilege#getAutomaton automaton that defines the entry's actions} entirely covers the
* automaton given in the argument (that is, the argument is a subset of the entry's automaton)
* </li>
* <li>The entry's resources, when interpreted as an {@link Automaton} {@link Automatons#patterns(String...)} set of patterns} entirely
* covers the resource given in the argument (also interpreted as an {@link Automaton} {@link Automatons#pattern(String) pattern}.
* </li>
* </ul>
*/
public boolean grants(ApplicationPrivilege other, String resource) {
Automaton resourceAutomaton = Automatons.patterns(resource);
final boolean matched = permissions.stream().anyMatch(e -> e.grants(other, resourceAutomaton));
logger.trace("Permission [{}] {} grant [{} , {}]", this, matched ? "does" : "does not", other, resource);
return matched;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{privileges=" + permissions + "}";
}
private static class PermissionEntry {
private final ApplicationPrivilege privilege;
private final Predicate<String> application;
private final Automaton resources;
private PermissionEntry(ApplicationPrivilege privilege, Automaton resources) {
this.privilege = privilege;
this.application = Automatons.predicate(privilege.getApplication());
this.resources = resources;
}
private boolean grants(ApplicationPrivilege other, Automaton resource) {
return this.application.test(other.getApplication())
&& Operations.isEmpty(privilege.getAutomaton()) == false
&& Operations.subsetOf(other.getAutomaton(), privilege.getAutomaton())
&& Operations.subsetOf(resource, this.resources);
}
@Override
public String toString() {
return privilege.toString();
}
}
}

View File

@ -5,30 +5,97 @@
*/
package org.elasticsearch.xpack.core.security.authz.permission;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A permission that is based on privileges for cluster wide actions
* A permission that is based on privileges for cluster wide actions, with the optional ability to inspect the request object
*/
public final class ClusterPermission {
public static final ClusterPermission NONE = new ClusterPermission(ClusterPrivilege.NONE);
public abstract class ClusterPermission {
private final ClusterPrivilege privilege;
private final Predicate<String> predicate;
ClusterPermission(ClusterPrivilege privilege) {
this.privilege = privilege;
this.predicate = privilege.predicate();
}
public ClusterPrivilege privilege() {
return privilege;
}
public boolean check(String action) {
return predicate.test(action);
public abstract boolean check(String action, TransportRequest request);
/**
* A permission that is based solely on cluster privileges and does not consider request state
*/
public static class SimpleClusterPermission extends ClusterPermission {
public static final SimpleClusterPermission NONE = new SimpleClusterPermission(ClusterPrivilege.NONE);
private final Predicate<String> predicate;
SimpleClusterPermission(ClusterPrivilege privilege) {
super(privilege);
this.predicate = privilege.predicate();
}
@Override
public boolean check(String action, TransportRequest request) {
return predicate.test(action);
}
}
/**
* A permission that makes use of both cluster privileges and request inspection
*/
public static class ConditionalClusterPermission extends ClusterPermission {
private final Predicate<String> actionPredicate;
private final Predicate<TransportRequest> requestPredicate;
public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) {
this(conditionalPrivilege.getPrivilege(), conditionalPrivilege.getRequestPredicate());
}
public ConditionalClusterPermission(ClusterPrivilege privilege, Predicate<TransportRequest> requestPredicate) {
super(privilege);
this.actionPredicate = privilege.predicate();
this.requestPredicate = requestPredicate;
}
@Override
public boolean check(String action, TransportRequest request) {
return actionPredicate.test(action) && requestPredicate.test(request);
}
}
/**
* A permission that composes a number of other cluster permissions
*/
public static class CompositeClusterPermission extends ClusterPermission {
private final Collection<ClusterPermission> children;
public CompositeClusterPermission(Collection<ClusterPermission> children) {
super(buildPrivilege(children));
this.children = children;
}
private static ClusterPrivilege buildPrivilege(Collection<ClusterPermission> children) {
final Set<String> names = children.stream()
.map(ClusterPermission::privilege)
.map(ClusterPrivilege::name)
.flatMap(Set::stream)
.collect(Collectors.toSet());
return ClusterPrivilege.get(names);
}
@Override
public boolean check(String action, TransportRequest request) {
return children.stream().anyMatch(p -> p.check(action, request));
}
}
}

View File

@ -8,14 +8,18 @@ package org.elasticsearch.xpack.core.security.authz.permission;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -29,12 +33,14 @@ public final class Role {
private final String[] names;
private final ClusterPermission cluster;
private final IndicesPermission indices;
private final ApplicationPermission application;
private final RunAsPermission runAs;
Role(String[] names, ClusterPermission cluster, IndicesPermission indices, RunAsPermission runAs) {
Role(String[] names, ClusterPermission cluster, IndicesPermission indices, ApplicationPermission application, RunAsPermission runAs) {
this.names = names;
this.cluster = Objects.requireNonNull(cluster);
this.indices = Objects.requireNonNull(indices);
this.application = Objects.requireNonNull(application);
this.runAs = Objects.requireNonNull(runAs);
}
@ -50,6 +56,10 @@ public final class Role {
return indices;
}
public ApplicationPermission application() {
return application;
}
public RunAsPermission runAs() {
return runAs;
}
@ -70,7 +80,7 @@ public final class Role {
public IndicesAccessControl authorize(String action, Set<String> requestedIndicesOrAliases, MetaData metaData,
FieldPermissionsCache fieldPermissionsCache) {
Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = indices.authorize(
action, requestedIndicesOrAliases, metaData, fieldPermissionsCache
action, requestedIndicesOrAliases, metaData, fieldPermissionsCache
);
// At least one role / indices permission set need to match with all the requested indices/aliases:
@ -87,9 +97,10 @@ public final class Role {
public static class Builder {
private final String[] names;
private ClusterPermission cluster = ClusterPermission.NONE;
private ClusterPermission cluster = ClusterPermission.SimpleClusterPermission.NONE;
private RunAsPermission runAs = RunAsPermission.NONE;
private List<IndicesPermission.Group> groups = new ArrayList<>();
private List<Tuple<ApplicationPrivilege, Set<String>>> applicationPrivs = new ArrayList<>();
private Builder(String[] names) {
this.names = names;
@ -97,20 +108,44 @@ public final class Role {
private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissionsCache) {
this.names = new String[] { rd.getName() };
if (rd.getClusterPrivileges().length == 0) {
cluster = ClusterPermission.NONE;
} else {
this.cluster(ClusterPrivilege.get(Sets.newHashSet(rd.getClusterPrivileges())));
}
cluster(Sets.newHashSet(rd.getClusterPrivileges()), Arrays.asList(rd.getConditionalClusterPrivileges()));
groups.addAll(convertFromIndicesPrivileges(rd.getIndicesPrivileges(), fieldPermissionsCache));
final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = rd.getApplicationPrivileges();
for (int i = 0; i < applicationPrivileges.length; i++) {
applicationPrivs.add(convertApplicationPrivilege(rd.getName(), i, applicationPrivileges[i]));
}
String[] rdRunAs = rd.getRunAs();
if (rdRunAs != null && rdRunAs.length > 0) {
this.runAs(new Privilege(Sets.newHashSet(rdRunAs), rdRunAs));
}
}
public Builder cluster(Set<String> privilegeNames, Iterable<ConditionalClusterPrivilege> conditionalClusterPrivileges) {
List<ClusterPermission> clusterPermissions = new ArrayList<>();
if (privilegeNames.isEmpty() == false) {
clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(ClusterPrivilege.get(privilegeNames)));
}
for (ConditionalClusterPrivilege ccp : conditionalClusterPrivileges) {
clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp));
}
if (clusterPermissions.isEmpty()) {
this.cluster = ClusterPermission.SimpleClusterPermission.NONE;
} else if (clusterPermissions.size() == 1) {
this.cluster = clusterPermissions.get(0);
} else {
this.cluster = new ClusterPermission.CompositeClusterPermission(clusterPermissions);
}
return this;
}
/**
* @deprecated Use {@link #cluster(Set, Iterable)}
*/
@Deprecated
public Builder cluster(ClusterPrivilege privilege) {
cluster = new ClusterPermission(privilege);
cluster = new ClusterPermission.SimpleClusterPermission(privilege);
return this;
}
@ -129,10 +164,17 @@ public final class Role {
return this;
}
public Builder addApplicationPrivilege(ApplicationPrivilege privilege, Set<String> resources) {
applicationPrivs.add(new Tuple<>(privilege, resources));
return this;
}
public Role build() {
IndicesPermission indices = groups.isEmpty() ? IndicesPermission.NONE :
new IndicesPermission(groups.toArray(new IndicesPermission.Group[groups.size()]));
return new Role(names, cluster, indices, runAs);
new IndicesPermission(groups.toArray(new IndicesPermission.Group[groups.size()]));
final ApplicationPermission applicationPermission
= applicationPrivs.isEmpty() ? ApplicationPermission.NONE : new ApplicationPermission(applicationPrivs);
return new Role(names, cluster, indices, applicationPermission, runAs);
}
static List<IndicesPermission.Group> convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges,
@ -144,16 +186,24 @@ public final class Role {
fieldPermissions = fieldPermissionsCache.getFieldPermissions(privilege.getGrantedFields(), privilege.getDeniedFields());
} else {
fieldPermissions = new FieldPermissions(
new FieldPermissionsDefinition(privilege.getGrantedFields(), privilege.getDeniedFields()));
new FieldPermissionsDefinition(privilege.getGrantedFields(), privilege.getDeniedFields()));
}
final Set<BytesReference> query = privilege.getQuery() == null ? null : Collections.singleton(privilege.getQuery());
list.add(new IndicesPermission.Group(IndexPrivilege.get(Sets.newHashSet(privilege.getPrivileges())),
fieldPermissions,
query,
privilege.getIndices()));
fieldPermissions,
query,
privilege.getIndices()));
}
return list;
}
static Tuple<ApplicationPrivilege, Set<String>> convertApplicationPrivilege(String role, int index,
RoleDescriptor.ApplicationResourcePrivileges arp) {
return new Tuple<>(new ApplicationPrivilege(arp.getApplication(),
"role." + role.replaceAll("[^a-zA-Z0-9]", "") + "." + index,
arp.getPrivileges()
), Sets.newHashSet(arp.getResources()));
}
}
}

View File

@ -0,0 +1,225 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.Strings;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* An application privilege has an application name (e.g. {@code "my-app"}) that identifies an application (that exists
* outside of elasticsearch), a privilege name (e.g. {@code "admin}) that is meaningful to that application, and one or
* more "action patterns" (e.g {@code "admin/user/*", "admin/team/*"}).
* Action patterns must contain at least one special character from ({@code /}, {@code :}, {@code *}) to distinguish them
* from privilege names.
* The action patterns are entirely optional - many application will find that simple "privilege names" are sufficient, but
* they allow applications to define high level abstract privileges that map to multiple low level capabilities.
*/
public final class ApplicationPrivilege extends Privilege {
private static final Pattern VALID_APPLICATION_PREFIX = Pattern.compile("^[a-z][A-Za-z0-9]*$");
private static final Pattern WHITESPACE = Pattern.compile("[\\v\\h]");
private static final Pattern VALID_NAME = Pattern.compile("^[a-z][a-zA-Z0-9_.-]*$");
/**
* A name or action must be composed of printable, visible ASCII characters.
* That is: letters, numbers &amp; symbols, but no whitespace.
*/
private static final Pattern VALID_NAME_OR_ACTION = Pattern.compile("^\\p{Graph}*$");
public static final Function<String, ApplicationPrivilege> NONE = app -> new ApplicationPrivilege(app, "none", new String[0]);
private final String application;
private final String[] patterns;
public ApplicationPrivilege(String application, String privilegeName, String... patterns) {
this(application, Collections.singleton(privilegeName), patterns);
}
public ApplicationPrivilege(String application, Set<String> name, String... patterns) {
super(name, patterns);
this.application = application;
this.patterns = patterns;
}
public String getApplication() {
return application;
}
// Package level for testing
String[] getPatterns() {
return patterns;
}
/**
* Validate that the provided application name is valid, and throws an exception otherwise
*
* @throws IllegalArgumentException if the name is not valid
*/
public static void validateApplicationName(String application) {
validateApplicationName(application, false);
}
/**
* Validate that the provided name is a valid application, or a wildcard pattern for an application and throws an exception otherwise
*
* @throws IllegalArgumentException if the name is not valid
*/
public static void validateApplicationNameOrWildcard(String application) {
validateApplicationName(application, true);
}
/**
* Validates that an application name matches the following rules:
* - consist of a "prefix", optionally followed by either "-" or "_" and a suffix
* - the prefix must begin with a lowercase ASCII letter
* - the prefix only contain ASCII letter or digits
* - the prefix must be at least 3 characters long
* - the suffix must only contain {@link Strings#validFileName valid filename} characters
* - no part of the name may contain whitespace
* If {@code allowWildcard} is true, then the names that end with a '*', and would match a valid
* application name are also accepted.
*/
private static void validateApplicationName(String application, boolean allowWildcard) {
if (Strings.isEmpty(application)) {
throw new IllegalArgumentException("Application names cannot be blank");
}
final int asterisk = application.indexOf('*');
if (asterisk != -1) {
if (allowWildcard == false) {
throw new IllegalArgumentException("Application names may not contain '*' (found '" + application + "')");
}
if(application.equals("*")) {
// this is allowed and short-circuiting here makes the later validation simpler
return;
}
if (asterisk != application.length() - 1) {
throw new IllegalArgumentException("Application name patterns only support trailing wildcards (found '" + application
+ "')");
}
}
if (WHITESPACE.matcher(application).find()) {
throw new IllegalArgumentException("Application names may not contain whitespace (found '" + application + "')");
}
final String[] parts = application.split("[_-]", 2);
String prefix = parts[0];
if (prefix.endsWith("*")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
if (VALID_APPLICATION_PREFIX.matcher(prefix).matches() == false) {
throw new IllegalArgumentException("An application name prefix must match the pattern " + VALID_APPLICATION_PREFIX.pattern()
+ " (found '" + prefix + "')");
}
if (prefix.length() < 3 && asterisk == -1) {
throw new IllegalArgumentException("An application name prefix must be at least 3 characters long (found '" + prefix + "')");
}
if (parts.length > 1) {
final String suffix = parts[1];
if (Strings.validFileName(suffix) == false) {
throw new IllegalArgumentException("An application name suffix may not contain any of the characters '" +
Strings.collectionToDelimitedString(Strings.INVALID_FILENAME_CHARS, "") + "' (found '" + suffix + "')");
}
}
}
/**
* Validate that the provided privilege name is valid, and throws an exception otherwise
*
* @throws IllegalArgumentException if the name is not valid
*/
public static void validatePrivilegeName(String name) {
if (isValidPrivilegeName(name) == false) {
throw new IllegalArgumentException("Application privilege names must match the pattern " + VALID_NAME.pattern()
+ " (found '" + name + "')");
}
}
private static boolean isValidPrivilegeName(String name) {
return VALID_NAME.matcher(name).matches();
}
/**
* Validate that the provided name is a valid privilege name or action name, and throws an exception otherwise
*
* @throws IllegalArgumentException if the name is not valid
*/
public static void validatePrivilegeOrActionName(String name) {
if (VALID_NAME_OR_ACTION.matcher(name).matches() == false) {
throw new IllegalArgumentException("Application privilege names and actions must match the pattern "
+ VALID_NAME_OR_ACTION.pattern() + " (found '" + name + "')");
}
}
/**
* Finds or creates an application privileges with the provided names.
* Each element in {@code name} may be the name of a stored privilege (to be resolved from {@code stored}, or a bespoke action pattern.
*/
public static ApplicationPrivilege get(String application, Set<String> name, Collection<ApplicationPrivilegeDescriptor> stored) {
if (name.isEmpty()) {
return NONE.apply(application);
} else {
Map<String, ApplicationPrivilegeDescriptor> lookup = stored.stream()
.filter(apd -> apd.getApplication().equals(application))
.collect(Collectors.toMap(ApplicationPrivilegeDescriptor::getName, Function.identity()));
return resolve(application, name, lookup);
}
}
private static ApplicationPrivilege resolve(String application, Set<String> names, Map<String, ApplicationPrivilegeDescriptor> lookup) {
final int size = names.size();
if (size == 0) {
throw new IllegalArgumentException("empty set should not be used");
}
Set<String> actions = new HashSet<>();
Set<String> patterns = new HashSet<>();
for (String name : names) {
if (isValidPrivilegeName(name)) {
ApplicationPrivilegeDescriptor descriptor = lookup.get(name);
if (descriptor != null) {
patterns.addAll(descriptor.getActions());
}
} else {
actions.add(name);
}
}
patterns.addAll(actions);
return new ApplicationPrivilege(application, names, patterns.toArray(new String[patterns.size()]));
}
@Override
public String toString() {
return application + ":" + super.toString() + "(" + Strings.arrayToCommaDelimitedString(patterns) + ")";
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Objects.hashCode(application);
result = 31 * result + Arrays.hashCode(patterns);
return result;
}
@Override
public boolean equals(Object o) {
return super.equals(o)
&& Objects.equals(this.application, ((ApplicationPrivilege) o).application)
&& Arrays.equals(this.patterns, ((ApplicationPrivilege) o).patterns);
}
}

View File

@ -0,0 +1,194 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* An {@code ApplicationPrivilegeDescriptor} is a representation of a <em>stored</em> {@link ApplicationPrivilege}.
* A user (via a role) can be granted an application privilege by name (e.g. ("myapp", "read").
* In general, this privilege name will correspond to a pre-defined {@link ApplicationPrivilegeDescriptor}, which then
* is used to determine the set of actions granted by the privilege.
*/
public class ApplicationPrivilegeDescriptor implements ToXContentObject, Writeable {
public static final String DOC_TYPE_VALUE = "application-privilege";
private static final ObjectParser<Builder, Boolean> PARSER = new ObjectParser<>(DOC_TYPE_VALUE, Builder::new);
static {
PARSER.declareString(Builder::applicationName, Fields.APPLICATION);
PARSER.declareString(Builder::privilegeName, Fields.NAME);
PARSER.declareStringArray(Builder::actions, Fields.ACTIONS);
PARSER.declareObject(Builder::metadata, (parser, context) -> parser.map(), Fields.METADATA);
PARSER.declareField((parser, builder, allowType) -> builder.type(parser.text(), allowType), Fields.TYPE,
ObjectParser.ValueType.STRING);
}
private String application;
private String name;
private Set<String> actions;
private Map<String, Object> metadata;
public ApplicationPrivilegeDescriptor(String application, String name, Set<String> actions, Map<String, Object> metadata) {
this.application = Objects.requireNonNull(application);
this.name = Objects.requireNonNull(name);
this.actions = Collections.unmodifiableSet(actions);
this.metadata = Collections.unmodifiableMap(metadata);
}
public ApplicationPrivilegeDescriptor(StreamInput input) throws IOException {
this.application = input.readString();
this.name = input.readString();
this.actions = Collections.unmodifiableSet(input.readSet(StreamInput::readString));
this.metadata = Collections.unmodifiableMap(input.readMap());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(application);
out.writeString(name);
out.writeCollection(actions, StreamOutput::writeString);
out.writeMap(metadata);
}
public String getApplication() {
return application;
}
public String getName() {
return name;
}
public Set<String> getActions() {
return actions;
}
public Map<String, Object> getMetadata() {
return metadata;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return toXContent(builder, false);
}
public XContentBuilder toXContent(XContentBuilder builder, boolean includeTypeField) throws IOException {
builder.startObject()
.field(Fields.APPLICATION.getPreferredName(), application)
.field(Fields.NAME.getPreferredName(), name)
.field(Fields.ACTIONS.getPreferredName(), actions)
.field(Fields.METADATA.getPreferredName(), metadata);
if (includeTypeField) {
builder.field(Fields.TYPE.getPreferredName(), DOC_TYPE_VALUE);
}
return builder.endObject();
}
/**
* Construct a new {@link ApplicationPrivilegeDescriptor} from XContent.
*
* @param defaultApplication The application name to use if none is specified in the XContent body
* @param defaultName The privilege name to use if none is specified in the XContent body
* @param allowType If true, accept a "type" field (for which the value must match {@link #DOC_TYPE_VALUE});
*/
public static ApplicationPrivilegeDescriptor parse(XContentParser parser, String defaultApplication, String defaultName,
boolean allowType) throws IOException {
final Builder builder = PARSER.parse(parser, allowType);
if (builder.applicationName == null) {
builder.applicationName(defaultApplication);
}
if (builder.privilegeName == null) {
builder.privilegeName(defaultName);
}
return builder.build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ApplicationPrivilegeDescriptor that = (ApplicationPrivilegeDescriptor) o;
return Objects.equals(this.application, that.application) &&
Objects.equals(this.name, that.name) &&
Objects.equals(this.actions, that.actions) &&
Objects.equals(this.metadata, that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(application, name, actions, metadata);
}
private static final class Builder {
private String applicationName;
private String privilegeName;
private Set<String> actions = Collections.emptySet();
private Map<String, Object> metadata = Collections.emptyMap();
private Builder applicationName(String applicationName) {
this.applicationName = applicationName;
return this;
}
private Builder privilegeName(String privilegeName) {
this.privilegeName = privilegeName;
return this;
}
private Builder actions(Collection<String> actions) {
this.actions = new HashSet<>(actions);
return this;
}
private Builder metadata(Map<String, Object> metadata) {
this.metadata = metadata;
return this;
}
private Builder type(String type, boolean allowed) {
if (allowed == false) {
throw new IllegalStateException("Field " + Fields.TYPE.getPreferredName() + " cannot be specified here");
}
if (ApplicationPrivilegeDescriptor.DOC_TYPE_VALUE.equals(type) == false) {
throw new IllegalStateException("XContent has wrong " + Fields.TYPE.getPreferredName() + " field " + type);
}
return this;
}
private ApplicationPrivilegeDescriptor build() {
return new ApplicationPrivilegeDescriptor(applicationName, privilegeName, actions, metadata);
}
}
public interface Fields {
ParseField APPLICATION = new ParseField("application");
ParseField NAME = new ParseField("name");
ParseField ACTIONS = new ParseField("actions");
ParseField METADATA = new ParseField("metadata");
ParseField TYPE = new ParseField("type");
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.transport.TransportRequest;
import java.io.IOException;
import java.util.Collection;
import java.util.function.Predicate;
/**
* A ConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed)
* with a {@link Predicate} for a {@link TransportRequest} (that determines which requests may be executed).
* The a given execution of an action is considered to be permitted if both the action and the request are permitted.
*/
public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment {
/**
* The category under which this privilege should be rendered when output as XContent.
*/
Category getCategory();
/**
* The action-level privilege that is required by this conditional privilege.
*/
ClusterPrivilege getPrivilege();
/**
* The request-level privilege (as a {@link Predicate}) that is required by this conditional privilege.
*/
Predicate<TransportRequest> getRequestPredicate();
/**
* A {@link ConditionalClusterPrivilege} should generate a fragment of {@code XContent}, which consists of
* a single field name, followed by its value (which may be an object, an array, or a simple value).
*/
@Override
XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException;
/**
* Categories exist for to segment privileges for the purposes of rendering to XContent.
* {@link ConditionalClusterPrivileges#toXContent(XContentBuilder, Params, Collection)} builds one XContent
* object for a collection of {@link ConditionalClusterPrivilege} instances, with the top level fields built
* from the categories.
*/
enum Category {
APPLICATION(new ParseField("application"));
public final ParseField field;
Category(ParseField field) {
this.field = field;
}
}
}

View File

@ -0,0 +1,225 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParseException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.privilege.ApplicationPrivilegesRequest;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege.Category;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.xcontent.XContentUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
/**
* Static utility class for working with {@link ConditionalClusterPrivilege} instances
*/
public final class ConditionalClusterPrivileges {
public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0];
private ConditionalClusterPrivileges() {
}
/**
* Utility method to read an array of {@link ConditionalClusterPrivilege} objects from a {@link StreamInput}
*/
public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException {
return in.readArray(in1 ->
in1.readNamedWriteable(ConditionalClusterPrivilege.class), ConditionalClusterPrivilege[]::new);
}
/**
* Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput}
*/
public static void writeArray(StreamOutput out, ConditionalClusterPrivilege[] privileges) throws IOException {
out.writeArray((out1, value) -> out1.writeNamedWriteable(value), privileges);
}
/**
* Writes a single object value to the {@code builder} that contains each of the provided privileges.
* The privileges are grouped according to their {@link ConditionalClusterPrivilege#getCategory() categories}
*/
public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params,
Collection<ConditionalClusterPrivilege> privileges) throws IOException {
builder.startObject();
for (Category category : Category.values()) {
builder.startObject(category.field.getPreferredName());
for (ConditionalClusterPrivilege privilege : privileges) {
if (category == privilege.getCategory()) {
privilege.toXContent(builder, params);
}
}
builder.endObject();
}
return builder.endObject();
}
/**
* Read a list of privileges from the parser. The parser should be positioned at the
* {@link XContentParser.Token#START_OBJECT} token for the privileges value
*/
public static List<ConditionalClusterPrivilege> parse(XContentParser parser) throws IOException {
List<ConditionalClusterPrivilege> privileges = new ArrayList<>();
expectedToken(parser.currentToken(), parser, XContentParser.Token.START_OBJECT);
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME);
expectFieldName(parser, Category.APPLICATION.field);
expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT);
expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME);
expectFieldName(parser, ManageApplicationPrivileges.Fields.MANAGE);
privileges.add(ManageApplicationPrivileges.parse(parser));
expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT);
}
return privileges;
}
private static void expectedToken(XContentParser.Token read, XContentParser parser, XContentParser.Token expected) {
if (read != expected) {
throw new XContentParseException(parser.getTokenLocation(),
"failed to parse privilege. expected [" + expected + "] but found [" + read + "] instead");
}
}
private static void expectFieldName(XContentParser parser, ParseField... fields) throws IOException {
final String fieldName = parser.currentName();
if (Arrays.stream(fields).anyMatch(pf -> pf.match(fieldName, parser.getDeprecationHandler())) == false) {
throw new XContentParseException(parser.getTokenLocation(),
"failed to parse privilege. expected " + (fields.length == 1 ? "field name" : "one of") + " ["
+ Strings.arrayToCommaDelimitedString(fields) + "] but found [" + fieldName + "] instead");
}
}
/**
* The {@code ManageApplicationPrivileges} privilege is a {@link ConditionalClusterPrivilege} that grants the
* ability to execute actions related to the management of application privileges (Get, Put, Delete) for a subset
* of applications (identified by a wildcard-aware application-name).
*/
public static class ManageApplicationPrivileges implements ConditionalClusterPrivilege {
private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get(
Collections.singleton("cluster:admin/xpack/security/privilege/*")
);
public static final String WRITEABLE_NAME = "manage-application-privileges";
private final Set<String> applicationNames;
private final Predicate<String> applicationPredicate;
private final Predicate<TransportRequest> requestPredicate;
public ManageApplicationPrivileges(Set<String> applicationNames) {
this.applicationNames = Collections.unmodifiableSet(applicationNames);
this.applicationPredicate = Automatons.predicate(applicationNames);
this.requestPredicate = request -> {
if (request instanceof ApplicationPrivilegesRequest) {
final ApplicationPrivilegesRequest privRequest = (ApplicationPrivilegesRequest) request;
return privRequest.getApplicationNames().stream().allMatch(application -> applicationPredicate.test(application));
}
return false;
};
}
@Override
public Category getCategory() {
return Category.APPLICATION;
}
@Override
public ClusterPrivilege getPrivilege() {
return PRIVILEGE;
}
@Override
public Predicate<TransportRequest> getRequestPredicate() {
return this.requestPredicate;
}
public Collection<String> getApplicationNames() {
return Collections.unmodifiableCollection(this.applicationNames);
}
@Override
public String getWriteableName() {
return WRITEABLE_NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeCollection(this.applicationNames, StreamOutput::writeString);
}
public static ManageApplicationPrivileges createFrom(StreamInput in) throws IOException {
final Set<String> applications = in.readSet(StreamInput::readString);
return new ManageApplicationPrivileges(applications);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.field(Fields.MANAGE.getPreferredName(),
Collections.singletonMap(Fields.APPLICATIONS.getPreferredName(), applicationNames)
);
}
public static ManageApplicationPrivileges parse(XContentParser parser) throws IOException {
expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME);
expectFieldName(parser, Fields.MANAGE);
expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT);
expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME);
expectFieldName(parser, Fields.APPLICATIONS);
expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY);
final String[] applications = XContentUtils.readStringArray(parser, false);
expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT);
return new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList(applications)));
}
@Override
public String toString() {
return "{" + getCategory() + ":" + Fields.MANAGE.getPreferredName() + ":" + Fields.APPLICATIONS.getPreferredName() + "="
+ Strings.collectionToDelimitedString(applicationNames, ",") + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ManageApplicationPrivileges that = (ManageApplicationPrivileges) o;
return this.applicationNames.equals(that.applicationNames);
}
@Override
public int hashCode() {
return applicationNames.hashCode();
}
private interface Fields {
ParseField MANAGE = new ParseField("manage");
ParseField APPLICATIONS = new ParseField("applications");
}
}
}

View File

@ -9,6 +9,8 @@ import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.elasticsearch.xpack.core.security.user.KibanaUser;
import org.elasticsearch.xpack.core.security.user.UsernamesField;
@ -27,8 +29,11 @@ public class ReservedRolesStore {
new String[] { "all" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()},
new String[] { "*" },
MetadataUtils.DEFAULT_RESERVED_METADATA);
new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder().application("*").privileges("*").resources("*").build()
},
null, new String[] { "*" },
MetadataUtils.DEFAULT_RESERVED_METADATA, Collections.emptyMap());
public static final Role SUPERUSER_ROLE = Role.builder(SUPERUSER_ROLE_DESCRIPTOR, null).build();
private static final Map<String, RoleDescriptor> RESERVED_ROLES = initializeReservedRoles();
@ -43,7 +48,11 @@ public class ReservedRolesStore {
MetadataUtils.DEFAULT_RESERVED_METADATA))
.put("kibana_user", new RoleDescriptor("kibana_user", null, new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete")
.build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
.build() }, new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana-.kibana").resources("*").privileges("all").build() },
null, null,
MetadataUtils.DEFAULT_RESERVED_METADATA, null))
.put("monitoring_user", new RoleDescriptor("monitoring_user",
new String[] { "cluster:monitor/main" },
new RoleDescriptor.IndicesPrivileges[] {
@ -70,13 +79,19 @@ public class ReservedRolesStore {
"kibana_dashboard_only_user",
null,
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices(".kibana*").privileges("read", "view_index_metadata").build()
RoleDescriptor.IndicesPrivileges.builder()
.indices(".kibana*").privileges("read", "view_index_metadata").build()
},
null,
MetadataUtils.DEFAULT_RESERVED_METADATA))
new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana-.kibana").resources("*").privileges("read").build() },
null, null,
MetadataUtils.DEFAULT_RESERVED_METADATA,
null))
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml" },
new String[] {
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml",
},
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(),
RoleDescriptor.IndicesPrivileges.builder()
@ -84,7 +99,9 @@ public class ReservedRolesStore {
RoleDescriptor.IndicesPrivileges.builder()
.indices(".management-beats").privileges("create_index", "read", "write").build()
},
null, MetadataUtils.DEFAULT_RESERVED_METADATA))
null,
new ConditionalClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) },
null, MetadataUtils.DEFAULT_RESERVED_METADATA, null))
.put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME},
null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
.put("beats_admin", new RoleDescriptor("beats_admin",

View File

@ -10,6 +10,12 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequestBuilder;
@ -167,7 +173,9 @@ public class SecurityClient {
client.execute(HasPrivilegesAction.INSTANCE, request, listener);
}
/** User Management */
/**
* User Management
*/
public GetUsersRequestBuilder prepareGetUsers(String... usernames) {
return new GetUsersRequestBuilder(client).usernames(usernames);
@ -223,7 +231,9 @@ public class SecurityClient {
client.execute(SetEnabledAction.INSTANCE, request, listener);
}
/** Role Management */
/**
* Role Management
*/
public GetRolesRequestBuilder prepareGetRoles(String... names) {
return new GetRolesRequestBuilder(client).names(names);
@ -253,7 +263,9 @@ public class SecurityClient {
client.execute(PutRoleAction.INSTANCE, request, listener);
}
/** Role Mappings */
/**
* Role Mappings
*/
public GetRoleMappingsRequestBuilder prepareGetRoleMappings(String... names) {
return new GetRoleMappingsRequestBuilder(client, GetRoleMappingsAction.INSTANCE)
@ -275,6 +287,27 @@ public class SecurityClient {
.name(name);
}
/* -- Application Privileges -- */
public GetPrivilegesRequestBuilder prepareGetPrivileges(String applicationName, String[] privileges) {
return new GetPrivilegesRequestBuilder(client, GetPrivilegesAction.INSTANCE).application(applicationName).privileges(privileges);
}
public PutPrivilegesRequestBuilder preparePutPrivilege(String applicationName, String privilegeName,
BytesReference bytesReference, XContentType xContentType) throws IOException {
return new PutPrivilegesRequestBuilder(client, PutPrivilegesAction.INSTANCE)
.source(applicationName, privilegeName, bytesReference, xContentType);
}
public PutPrivilegesRequestBuilder preparePutPrivileges(BytesReference bytesReference, XContentType xContentType) throws IOException {
return new PutPrivilegesRequestBuilder(client, PutPrivilegesAction.INSTANCE).source(bytesReference, xContentType);
}
public DeletePrivilegesRequestBuilder prepareDeletePrivileges(String applicationName, String[] privileges) {
return new DeletePrivilegesRequestBuilder(client, DeletePrivilegesAction.INSTANCE)
.application(applicationName)
.privileges(privileges);
}
public CreateTokenRequestBuilder prepareCreateToken() {
return new CreateTokenRequestBuilder(client, CreateTokenAction.INSTANCE);
}
@ -298,7 +331,7 @@ public class SecurityClient {
return builder;
}
public void samlAuthenticate(SamlAuthenticateRequest request, ActionListener< SamlAuthenticateResponse> listener) {
public void samlAuthenticate(SamlAuthenticateRequest request, ActionListener<SamlAuthenticateResponse> listener) {
client.execute(SamlAuthenticateAction.INSTANCE, request, listener);
}

View File

@ -91,6 +91,41 @@
}
}
},
"applications": {
"type": "object",
"properties": {
"application": {
"type": "keyword"
},
"privileges": {
"type": "keyword"
},
"resources": {
"type": "keyword"
}
}
},
"application" : {
"type" : "keyword"
},
"global": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"manage": {
"type": "object",
"properties": {
"applications": {
"type": "keyword"
}
}
}
}
}
}
},
"name" : {
"type" : "keyword"
},
@ -103,6 +138,9 @@
"type" : {
"type" : "keyword"
},
"actions" : {
"type" : "keyword"
},
"expiration_time" : {
"type" : "date",
"format" : "epoch_millis"

View File

@ -5,16 +5,16 @@
*/
package org.elasticsearch.test;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
public class TestMatchers extends Matchers {
public static Matcher<Path> pathExists(Path path, LinkOption... options) {
@ -26,6 +26,19 @@ public class TestMatchers extends Matchers {
};
}
public static <T> Matcher<Predicate<T>> predicateMatches(T value) {
return new CustomMatcher<Predicate<T>>("Matches " + value) {
@Override
public boolean matches(Object item) {
if (Predicate.class.isInstance(item)) {
return ((Predicate<T>) item).test(value);
} else {
return false;
}
}
};
}
public static Matcher<String> matchesPattern(String regex) {
return matchesPattern(Pattern.compile(regex));
}
@ -34,16 +47,17 @@ public class TestMatchers extends Matchers {
return predicate("Matches " + pattern.pattern(), String.class, pattern.asPredicate());
}
private static <T> Matcher<T> predicate(String description, Class<T> type, Predicate<T> stringPredicate) {
private static <T> Matcher<T> predicate(String description, Class<T> type, Predicate<T> predicate) {
return new CustomMatcher<T>(description) {
@Override
public boolean matches(Object item) {
if (type.isInstance(item)) {
return stringPredicate.test(type.cast(item));
return predicate.test(type.cast(item));
} else {
return false;
}
}
};
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import java.io.IOException;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class DeletePrivilegesRequestTests extends ESTestCase {
public void testSerialization() throws IOException {
final DeletePrivilegesRequest original = new DeletePrivilegesRequest(
randomAlphaOfLengthBetween(3, 8), generateRandomStringArray(5, randomIntBetween(3, 8), false, false));
original.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values()));
final BytesStreamOutput output = new BytesStreamOutput();
original.writeTo(output);
output.flush();
final DeletePrivilegesRequest copy = new DeletePrivilegesRequest();
copy.readFrom(output.bytes().streamInput());
assertThat(copy.application(), equalTo(original.application()));
assertThat(copy.privileges(), equalTo(original.privileges()));
assertThat(copy.getRefreshPolicy(), equalTo(original.getRefreshPolicy()));
}
public void testValidation() {
assertValidationFailure(new DeletePrivilegesRequest(null, null), "application name", "privileges");
assertValidationFailure(new DeletePrivilegesRequest("", null), "application name", "privileges");
assertValidationFailure(new DeletePrivilegesRequest(null, new String[0]), "application name", "privileges");
assertValidationFailure(new DeletePrivilegesRequest("", new String[0]), "application name", "privileges");
assertValidationFailure(new DeletePrivilegesRequest(null, new String[]{"all"}), "application name");
assertValidationFailure(new DeletePrivilegesRequest("", new String[]{"all"}), "application name");
assertValidationFailure(new DeletePrivilegesRequest("app", null), "privileges");
assertValidationFailure(new DeletePrivilegesRequest("app", new String[0]), "privileges");
assertValidationFailure(new DeletePrivilegesRequest("app", new String[]{""}), "privileges");
assertThat(new DeletePrivilegesRequest("app", new String[]{"all"}).validate(), nullValue());
assertThat(new DeletePrivilegesRequest("app", new String[]{"all", "some"}).validate(), nullValue());
}
private void assertValidationFailure(DeletePrivilegesRequest request, String... messages) {
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
for (String message : messages) {
assertThat(exception.validationErrors(), Matchers.hasItem(containsString(message)));
}
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import static org.hamcrest.Matchers.equalTo;
public class DeletePrivilegesResponseTests extends ESTestCase {
public void testSerialization() throws IOException {
final DeletePrivilegesResponse original = new DeletePrivilegesResponse(
Arrays.asList(generateRandomStringArray(5, randomIntBetween(3, 8), false, true)));
final BytesStreamOutput output = new BytesStreamOutput();
original.writeTo(output);
output.flush();
final DeletePrivilegesResponse copy = new DeletePrivilegesResponse();
copy.readFrom(output.bytes().streamInput());
assertThat(copy.found(), equalTo(original.found()));
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import java.io.IOException;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class GetPrivilegesRequestTests extends ESTestCase {
public void testSerialization() throws IOException {
final GetPrivilegesRequest original = new GetPrivilegesRequest();
if (randomBoolean()) {
original.application(randomAlphaOfLengthBetween(3, 8));
}
original.privileges(generateRandomStringArray(3, 5, false, true));
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final GetPrivilegesRequest copy = new GetPrivilegesRequest();
copy.readFrom(out.bytes().streamInput());
assertThat(original.application(), Matchers.equalTo(copy.application()));
assertThat(original.privileges(), Matchers.equalTo(copy.privileges()));
}
public void testValidation() {
assertThat(request(null).validate(), nullValue());
assertThat(request(null, "all").validate(), nullValue());
assertThat(request(null, "read", "write").validate(), nullValue());
assertThat(request("my_app").validate(), nullValue());
assertThat(request("my_app", "all").validate(), nullValue());
assertThat(request("my_app", "read", "write").validate(), nullValue());
final ActionRequestValidationException exception = request("my_app", ((String[]) null)).validate();
assertThat(exception, notNullValue());
assertThat(exception.validationErrors(), containsInAnyOrder("privileges cannot be null"));
}
private GetPrivilegesRequest request(String application, String... privileges) {
final GetPrivilegesRequest request = new GetPrivilegesRequest();
request.application(application);
request.privileges(privileges);
return request;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.hamcrest.Matchers;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
public class GetPrivilegesResponseTests extends ESTestCase {
public void testSerialization() throws IOException {
ApplicationPrivilegeDescriptor[] privileges = randomArray(6, ApplicationPrivilegeDescriptor[]::new, () ->
new ApplicationPrivilegeDescriptor(
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
Sets.newHashSet(randomArray(3, String[]::new, () -> randomAlphaOfLength(3).toLowerCase(Locale.ROOT) + "/*")),
Collections.emptyMap()
)
);
final GetPrivilegesResponse original = new GetPrivilegesResponse(privileges);
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final GetPrivilegesResponse copy = new GetPrivilegesResponse();
copy.readFrom(out.bytes().streamInput());
assertThat(copy.privileges(), Matchers.equalTo(original.privileges()));
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.hamcrest.Matchers;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.notNullValue;
public class PutPrivilegesRequestTests extends ESTestCase {
public void testSerialization() throws IOException {
final PutPrivilegesRequest original = request(randomArray(8, ApplicationPrivilegeDescriptor[]::new,
() -> new ApplicationPrivilegeDescriptor(
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
Sets.newHashSet(randomArray(3, String[]::new, () -> randomAlphaOfLength(3).toLowerCase(Locale.ROOT) + "/*")),
Collections.emptyMap()
)
));
original.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values()));
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final PutPrivilegesRequest copy = new PutPrivilegesRequest();
copy.readFrom(out.bytes().streamInput());
assertThat(original.getPrivileges(), Matchers.equalTo(copy.getPrivileges()));
assertThat(original.getRefreshPolicy(), Matchers.equalTo(copy.getRefreshPolicy()));
}
public void testValidation() {
// wildcard app name
final ApplicationPrivilegeDescriptor wildcardApp = descriptor("*", "all", "*");
assertValidationFailure(request(wildcardApp), "Application names may not contain");
// invalid priv names
final ApplicationPrivilegeDescriptor spaceName = descriptor("app", "r e a d", "read/*");
final ApplicationPrivilegeDescriptor numericName = descriptor("app", "7346", "read/*");
assertValidationFailure(request(spaceName), "Application privilege names must match");
assertValidationFailure(request(numericName), "Application privilege names must match");
// no actions
final ApplicationPrivilegeDescriptor nothing = descriptor("*", "nothing");
assertValidationFailure(request(nothing), "Application privileges must have at least one action");
// reserved metadata
final ApplicationPrivilegeDescriptor reservedMetadata = new ApplicationPrivilegeDescriptor("app", "all",
Collections.emptySet(), Collections.singletonMap("_notAllowed", true)
);
assertValidationFailure(request(reservedMetadata), "metadata keys may not start");
ApplicationPrivilegeDescriptor badAction = descriptor("app", "foo", randomFrom("data.read", "data_read", "data+read", "read"));
assertValidationFailure(request(badAction), "must contain one of");
// mixed
assertValidationFailure(request(wildcardApp, numericName, reservedMetadata, badAction),
"Application names may not contain", "Application privilege names must match", "metadata keys may not start",
"must contain one of");
}
private ApplicationPrivilegeDescriptor descriptor(String application, String name, String... actions) {
return new ApplicationPrivilegeDescriptor(application, name, Sets.newHashSet(actions), Collections.emptyMap());
}
private void assertValidationFailure(PutPrivilegesRequest request, String... messages) {
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
for (String message : messages) {
assertThat(exception.validationErrors(), hasItem(containsString(message)));
}
}
private PutPrivilegesRequest request(ApplicationPrivilegeDescriptor... privileges) {
final PutPrivilegesRequest original = new PutPrivilegesRequest();
original.setPrivileges(Arrays.asList(privileges));
return original;
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
public class PutPrivilegesResponseTests extends ESTestCase {
public void testSerialization() throws IOException {
final int applicationCount = randomInt(3);
final Map<String, List<String>> map = new HashMap<>(applicationCount);
for (int i = 0; i < applicationCount; i++) {
map.put(randomAlphaOfLengthBetween(3, 8),
Arrays.asList(generateRandomStringArray(5, 6, false, true))
);
}
final PutPrivilegesResponse original = new PutPrivilegesResponse(map);
final BytesStreamOutput output = new BytesStreamOutput();
original.writeTo(output);
output.flush();
final PutPrivilegesResponse copy = new PutPrivilegesResponse();
copy.readFrom(output.bytes().streamInput());
assertThat(copy.created(), equalTo(original.created()));
assertThat(Strings.toString(copy), equalTo(Strings.toString(original)));
}
}

View File

@ -0,0 +1,164 @@
/*
* 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.core.security.action.role;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.Strings;
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.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class PutRoleRequestTests extends ESTestCase {
public void testValidationOfApplicationPrivileges() {
assertSuccessfulValidation(buildRequestWithApplicationPrivilege("app", new String[]{"read"}, new String[]{"*"}));
assertSuccessfulValidation(buildRequestWithApplicationPrivilege("app", new String[]{"action:login"}, new String[]{"/"}));
assertSuccessfulValidation(buildRequestWithApplicationPrivilege("*", new String[]{"data/read:user"}, new String[]{"user/123"}));
// Fail
assertValidationError("privilege names and actions must match the pattern",
buildRequestWithApplicationPrivilege("app", new String[]{"in valid"}, new String[]{"*"}));
assertValidationError("An application name prefix must match the pattern",
buildRequestWithApplicationPrivilege("000", new String[]{"all"}, new String[]{"*"}));
assertValidationError("An application name prefix must match the pattern",
buildRequestWithApplicationPrivilege("%*", new String[]{"all"}, new String[]{"*"}));
}
public void testSerialization() throws IOException {
final PutRoleRequest original = buildRandomRequest();
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final PutRoleRequest copy = new PutRoleRequest();
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
StreamInput in = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(out.bytes())), registry);
copy.readFrom(in);
assertThat(copy.roleDescriptor(), equalTo(original.roleDescriptor()));
}
public void testSerializationV63AndBefore() throws IOException {
final PutRoleRequest original = buildRandomRequest();
final BytesStreamOutput out = new BytesStreamOutput();
final Version version = VersionUtils.randomVersionBetween(random(), Version.V_5_6_0, Version.V_6_3_2);
out.setVersion(version);
original.writeTo(out);
final PutRoleRequest copy = new PutRoleRequest();
final StreamInput in = out.bytes().streamInput();
in.setVersion(version);
copy.readFrom(in);
assertThat(copy.name(), equalTo(original.name()));
assertThat(copy.cluster(), equalTo(original.cluster()));
assertThat(copy.indices(), equalTo(original.indices()));
assertThat(copy.runAs(), equalTo(original.runAs()));
assertThat(copy.metadata(), equalTo(original.metadata()));
assertThat(copy.getRefreshPolicy(), equalTo(original.getRefreshPolicy()));
assertThat(copy.applicationPrivileges(), iterableWithSize(0));
assertThat(copy.conditionalClusterPrivileges(), arrayWithSize(0));
}
private void assertSuccessfulValidation(PutRoleRequest request) {
final ActionRequestValidationException exception = request.validate();
assertThat(exception, nullValue());
}
private void assertValidationError(String message, PutRoleRequest request) {
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
assertThat(exception.validationErrors(), hasItem(containsString(message)));
}
private PutRoleRequest buildRequestWithApplicationPrivilege(String appName, String[] privileges, String[] resources) {
final PutRoleRequest request = new PutRoleRequest();
request.name("test");
final ApplicationResourcePrivileges privilege = ApplicationResourcePrivileges.builder()
.application(appName)
.privileges(privileges)
.resources(resources)
.build();
request.addApplicationPrivileges(new ApplicationResourcePrivileges[]{privilege});
return request;
}
private PutRoleRequest buildRandomRequest() {
final PutRoleRequest request = new PutRoleRequest();
request.name(randomAlphaOfLengthBetween(4, 9));
request.cluster(randomSubsetOf(Arrays.asList("monitor", "manage", "all", "manage_security", "manage_ml", "monitor_watcher"))
.toArray(Strings.EMPTY_ARRAY));
for (int i = randomIntBetween(0, 4); i > 0; i--) {
request.addIndex(
generateRandomStringArray(randomIntBetween(1, 3), randomIntBetween(3, 8), false, false),
randomSubsetOf(randomIntBetween(1, 2), "read", "write", "index", "all").toArray(Strings.EMPTY_ARRAY),
generateRandomStringArray(randomIntBetween(1, 3), randomIntBetween(3, 8), true),
generateRandomStringArray(randomIntBetween(1, 3), randomIntBetween(3, 8), true),
null
);
}
final Supplier<String> stringWithInitialLowercase = ()
-> randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(3, 12);
final ApplicationResourcePrivileges[] applicationPrivileges = new ApplicationResourcePrivileges[randomIntBetween(0, 5)];
for (int i = 0; i < applicationPrivileges.length; i++) {
applicationPrivileges[i] = ApplicationResourcePrivileges.builder()
.application(stringWithInitialLowercase.get())
.privileges(randomArray(1, 3, String[]::new, stringWithInitialLowercase))
.resources(generateRandomStringArray(5, randomIntBetween(3, 8), false, false))
.build();
}
request.addApplicationPrivileges(applicationPrivileges);
if (randomBoolean()) {
final String[] appNames = randomArray(1, 4, String[]::new, stringWithInitialLowercase);
request.conditionalCluster(new ConditionalClusterPrivileges.ManageApplicationPrivileges(Sets.newHashSet(appNames)));
}
request.runAs(generateRandomStringArray(4, 3, false, true));
final Map<String, Object> metadata = new HashMap<>();
for (String key : generateRandomStringArray(3, 5, false, true)) {
metadata.put(key, randomFrom(Boolean.TRUE, Boolean.FALSE, 1, 2, randomAlphaOfLengthBetween(2, 9)));
}
request.metadata(metadata);
request.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values()));
return request;
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.core.security.action.user;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class HasPrivilegesRequestTests extends ESTestCase {
public void testSerializationV7() throws IOException {
final HasPrivilegesRequest original = randomRequest();
final HasPrivilegesRequest copy = serializeAndDeserialize(original, Version.V_7_0_0_alpha1);
assertThat(copy.username(), equalTo(original.username()));
assertThat(copy.clusterPrivileges(), equalTo(original.clusterPrivileges()));
assertThat(copy.indexPrivileges(), equalTo(original.indexPrivileges()));
assertThat(copy.applicationPrivileges(), equalTo(original.applicationPrivileges()));
}
public void testSerializationV63() throws IOException {
final HasPrivilegesRequest original = randomRequest();
final HasPrivilegesRequest copy = serializeAndDeserialize(original, Version.V_6_3_0);
assertThat(copy.username(), equalTo(original.username()));
assertThat(copy.clusterPrivileges(), equalTo(original.clusterPrivileges()));
assertThat(copy.indexPrivileges(), equalTo(original.indexPrivileges()));
assertThat(copy.applicationPrivileges(), nullValue());
}
public void testValidateNullPrivileges() {
final HasPrivilegesRequest request = new HasPrivilegesRequest();
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
assertThat(exception.validationErrors(), hasItem("clusterPrivileges must not be null"));
assertThat(exception.validationErrors(), hasItem("indexPrivileges must not be null"));
assertThat(exception.validationErrors(), hasItem("applicationPrivileges must not be null"));
}
public void testValidateEmptyPrivileges() {
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.clusterPrivileges(new String[0]);
request.indexPrivileges(new IndicesPrivileges[0]);
request.applicationPrivileges(new ApplicationResourcePrivileges[0]);
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
assertThat(exception.validationErrors(), hasItem("must specify at least one privilege"));
}
public void testValidateNoWildcardApplicationPrivileges() {
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.clusterPrivileges(new String[0]);
request.indexPrivileges(new IndicesPrivileges[0]);
request.applicationPrivileges(new ApplicationResourcePrivileges[] {
ApplicationResourcePrivileges.builder().privileges("read").application("*").resources("item/1").build()
});
final ActionRequestValidationException exception = request.validate();
assertThat(exception, notNullValue());
assertThat(exception.validationErrors(), hasItem("Application names may not contain '*' (found '*')"));
}
private HasPrivilegesRequest serializeAndDeserialize(HasPrivilegesRequest original, Version version) throws IOException {
final BytesStreamOutput out = new BytesStreamOutput();
out.setVersion(version);
original.writeTo(out);
final HasPrivilegesRequest copy = new HasPrivilegesRequest();
final StreamInput in = out.bytes().streamInput();
in.setVersion(version);
copy.readFrom(in);
assertThat(in.read(), equalTo(-1));
return copy;
}
private HasPrivilegesRequest randomRequest() {
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(randomAlphaOfLength(8));
final List<String> clusterPrivileges = randomSubsetOf(Arrays.asList(ClusterPrivilege.MONITOR, ClusterPrivilege.MANAGE,
ClusterPrivilege.MANAGE_ML, ClusterPrivilege.MANAGE_SECURITY, ClusterPrivilege.MANAGE_PIPELINE, ClusterPrivilege.ALL))
.stream().flatMap(p -> p.name().stream()).collect(Collectors.toList());
request.clusterPrivileges(clusterPrivileges.toArray(Strings.EMPTY_ARRAY));
IndicesPrivileges[] indicesPrivileges = new IndicesPrivileges[randomInt(5)];
for (int i = 0; i < indicesPrivileges.length; i++) {
indicesPrivileges[i] = IndicesPrivileges.builder()
.privileges(randomFrom("read", "write", "create", "delete", "all"))
.indices(randomAlphaOfLengthBetween(2, 8) + (randomBoolean() ? "*" : ""))
.build();
}
request.indexPrivileges(indicesPrivileges);
final ApplicationResourcePrivileges[] appPrivileges = new ApplicationResourcePrivileges[randomInt(5)];
for (int i = 0; i < appPrivileges.length; i++) {
appPrivileges[i] = ApplicationResourcePrivileges.builder()
.application(randomAlphaOfLengthBetween(3, 8))
.resources(randomAlphaOfLengthBetween(5, 7) + (randomBoolean() ? "*" : ""))
.privileges(generateRandomStringArray(6, 7, false, false))
.build();
}
request.applicationPrivileges(appPrivileges);
return request;
}
}

View File

@ -0,0 +1,124 @@
/*
* 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.core.security.authz.permission;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.equalTo;
public class ApplicationPermissionTests extends ESTestCase {
private List<ApplicationPrivilegeDescriptor> store = new ArrayList<>();
private ApplicationPrivilege app1All = storePrivilege("app1", "all", "*");
private ApplicationPrivilege app1Empty = storePrivilege("app1", "empty");
private ApplicationPrivilege app1Read = storePrivilege("app1", "read", "read/*");
private ApplicationPrivilege app1Write = storePrivilege("app1", "write", "write/*");
private ApplicationPrivilege app1Delete = storePrivilege("app1", "delete", "write/delete");
private ApplicationPrivilege app1Create = storePrivilege("app1", "create", "write/create");
private ApplicationPrivilege app2Read = storePrivilege("app2", "read", "read/*");
private ApplicationPrivilege storePrivilege(String app, String name, String... patterns) {
store.add(new ApplicationPrivilegeDescriptor(app, name, Sets.newHashSet(patterns), Collections.emptyMap()));
return new ApplicationPrivilege(app, name, patterns);
}
public void testCheckSimplePermission() {
final ApplicationPermission hasPermission = buildPermission(app1Write, "*");
assertThat(hasPermission.grants(app1Write, "*"), equalTo(true));
assertThat(hasPermission.grants(app1Write, "foo"), equalTo(true));
assertThat(hasPermission.grants(app1Delete, "*"), equalTo(true));
assertThat(hasPermission.grants(app1Create, "foo"), equalTo(true));
assertThat(hasPermission.grants(app1Read, "*"), equalTo(false));
assertThat(hasPermission.grants(app1Read, "foo"), equalTo(false));
assertThat(hasPermission.grants(app1All, "*"), equalTo(false));
assertThat(hasPermission.grants(app1All, "foo"), equalTo(false));
}
public void testNonePermission() {
final ApplicationPermission hasPermission = buildPermission(ApplicationPrivilege.NONE.apply("app1"), "*");
for (ApplicationPrivilege privilege : Arrays.asList(app1All, app1Empty, app1Create, app1Delete, app1Read, app1Write, app2Read)) {
assertThat("Privilege " + privilege + " on *", hasPermission.grants(privilege, "*"), equalTo(false));
final String resource = randomAlphaOfLengthBetween(1, 6);
assertThat("Privilege " + privilege + " on " + resource, hasPermission.grants(privilege, resource), equalTo(false));
}
}
public void testResourceMatching() {
final ApplicationPermission hasPermission = buildPermission(app1All, "dashboard/*", "audit/*", "user/12345");
assertThat(hasPermission.grants(app1Write, "*"), equalTo(false));
assertThat(hasPermission.grants(app1Write, "dashboard"), equalTo(false));
assertThat(hasPermission.grants(app1Write, "dashboard/999"), equalTo(true));
assertThat(hasPermission.grants(app1Create, "audit/2018-02-21"), equalTo(true));
assertThat(hasPermission.grants(app1Create, "report/2018-02-21"), equalTo(false));
assertThat(hasPermission.grants(app1Read, "user/12345"), equalTo(true));
assertThat(hasPermission.grants(app1Read, "user/67890"), equalTo(false));
assertThat(hasPermission.grants(app1All, "dashboard/999"), equalTo(true));
assertThat(hasPermission.grants(app1All, "audit/2018-02-21"), equalTo(true));
assertThat(hasPermission.grants(app1All, "user/12345"), equalTo(true));
}
public void testActionMatching() {
final ApplicationPermission hasPermission = buildPermission(app1Write, "allow/*");
final ApplicationPrivilege update = actionPrivilege("app1", "write/update");
assertThat(hasPermission.grants(update, "allow/1"), equalTo(true));
assertThat(hasPermission.grants(update, "deny/1"), equalTo(false));
final ApplicationPrivilege updateCreate = actionPrivilege("app1", "write/update", "write/create");
assertThat(hasPermission.grants(updateCreate, "allow/1"), equalTo(true));
assertThat(hasPermission.grants(updateCreate, "deny/1"), equalTo(false));
final ApplicationPrivilege manage = actionPrivilege("app1", "admin/manage");
assertThat(hasPermission.grants(manage, "allow/1"), equalTo(false));
assertThat(hasPermission.grants(manage, "deny/1"), equalTo(false));
}
public void testDoesNotMatchAcrossApplications() {
assertThat(buildPermission(app1Read, "*").grants(app1Read, "123"), equalTo(true));
assertThat(buildPermission(app1All, "*").grants(app1Read, "123"), equalTo(true));
assertThat(buildPermission(app1Read, "*").grants(app2Read, "123"), equalTo(false));
assertThat(buildPermission(app1All, "*").grants(app2Read, "123"), equalTo(false));
}
public void testMergedPermissionChecking() {
final ApplicationPrivilege app1ReadWrite = ApplicationPrivilege.get("app1", Sets.union(app1Read.name(), app1Write.name()), store);
final ApplicationPermission hasPermission = buildPermission(app1ReadWrite, "allow/*");
assertThat(hasPermission.grants(app1Read, "allow/1"), equalTo(true));
assertThat(hasPermission.grants(app1Write, "allow/1"), equalTo(true));
assertThat(hasPermission.grants(app1Read, "deny/1"), equalTo(false));
assertThat(hasPermission.grants(app1Write, "deny/1"), equalTo(false));
assertThat(hasPermission.grants(app1All, "allow/1"), equalTo(false));
assertThat(hasPermission.grants(app2Read, "allow/1"), equalTo(false));
}
private ApplicationPrivilege actionPrivilege(String appName, String... actions) {
return ApplicationPrivilege.get(appName, Sets.newHashSet(actions), Collections.emptyList());
}
private ApplicationPermission buildPermission(ApplicationPrivilege privilege, String... resources) {
return new ApplicationPermission(singletonList(new Tuple<>(privilege, Sets.newHashSet(resources))));
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.hamcrest.Matchers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.iterableWithSize;
public class ApplicationPrivilegeDescriptorTests extends ESTestCase {
public void testEqualsAndHashCode() {
final ApplicationPrivilegeDescriptor privilege = randomPrivilege();
final EqualsHashCodeTestUtils.MutateFunction<ApplicationPrivilegeDescriptor> mutate = randomFrom(
orig -> new ApplicationPrivilegeDescriptor(
"x" + orig.getApplication(), orig.getName(), orig.getActions(), orig.getMetadata()),
orig -> new ApplicationPrivilegeDescriptor(
orig.getApplication(), "x" + orig.getName(), orig.getActions(), orig.getMetadata()),
orig -> new ApplicationPrivilegeDescriptor(
orig.getApplication(), orig.getName(), Collections.singleton("*"), orig.getMetadata()),
orig -> new ApplicationPrivilegeDescriptor(
orig.getApplication(), orig.getName(), orig.getActions(), Collections.singletonMap("mutate", -1L))
);
EqualsHashCodeTestUtils.checkEqualsAndHashCode(privilege,
original -> new ApplicationPrivilegeDescriptor(
original.getApplication(), original.getName(), original.getActions(), original.getMetadata()),
mutate
);
}
public void testSerialization() throws IOException {
try (BytesStreamOutput out = new BytesStreamOutput()) {
final ApplicationPrivilegeDescriptor original = randomPrivilege();
original.writeTo(out);
final ApplicationPrivilegeDescriptor clone = new ApplicationPrivilegeDescriptor(out.bytes().streamInput());
assertThat(clone, Matchers.equalTo(original));
assertThat(original, Matchers.equalTo(clone));
}
}
public void testXContentGenerationAndParsing() throws IOException {
final boolean includeTypeField = randomBoolean();
final XContent xContent = randomFrom(XContentType.values()).xContent();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
final XContentBuilder builder = new XContentBuilder(xContent, out);
final ApplicationPrivilegeDescriptor original = randomPrivilege();
if (includeTypeField) {
original.toXContent(builder, true);
} else if (randomBoolean()) {
original.toXContent(builder, false);
} else {
original.toXContent(builder, ToXContent.EMPTY_PARAMS);
}
builder.flush();
final byte[] bytes = out.toByteArray();
try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, bytes)) {
final ApplicationPrivilegeDescriptor clone = ApplicationPrivilegeDescriptor.parse(parser,
randomBoolean() ? randomAlphaOfLength(3) : null,
randomBoolean() ? randomAlphaOfLength(3) : null,
includeTypeField);
assertThat(clone, Matchers.equalTo(original));
assertThat(original, Matchers.equalTo(clone));
}
}
}
public void testParseXContentWithDefaultNames() throws IOException {
final String json = "{ \"actions\": [ \"data:read\" ], \"metadata\" : { \"num\": 1, \"bool\":false } }";
final XContent xContent = XContentType.JSON.xContent();
try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, json)) {
final ApplicationPrivilegeDescriptor privilege = ApplicationPrivilegeDescriptor.parse(parser, "my_app", "read", false);
assertThat(privilege.getApplication(), equalTo("my_app"));
assertThat(privilege.getName(), equalTo("read"));
assertThat(privilege.getActions(), contains("data:read"));
assertThat(privilege.getMetadata().entrySet(), iterableWithSize(2));
assertThat(privilege.getMetadata().get("num"), equalTo(1));
assertThat(privilege.getMetadata().get("bool"), equalTo(false));
}
}
public void testParseXContentWithoutUsingDefaultNames() throws IOException {
final String json = "{" +
" \"application\": \"your_app\"," +
" \"name\": \"write\"," +
" \"actions\": [ \"data:write\" ]" +
"}";
final XContent xContent = XContentType.JSON.xContent();
try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, json)) {
final ApplicationPrivilegeDescriptor privilege = ApplicationPrivilegeDescriptor.parse(parser, "my_app", "read", false);
assertThat(privilege.getApplication(), equalTo("your_app"));
assertThat(privilege.getName(), equalTo("write"));
assertThat(privilege.getActions(), contains("data:write"));
assertThat(privilege.getMetadata().entrySet(), iterableWithSize(0));
}
}
private ApplicationPrivilegeDescriptor randomPrivilege() {
final String applicationName;
if (randomBoolean()) {
applicationName = "*";
} else {
applicationName = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(2, 10);
}
final String privilegeName = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(2, 8);
final String[] patterns = new String[randomIntBetween(0, 5)];
for (int i = 0; i < patterns.length; i++) {
final String suffix = randomBoolean() ? "*" : randomAlphaOfLengthBetween(4, 9);
patterns[i] = randomAlphaOfLengthBetween(2, 5) + "/" + suffix;
}
final Map<String, Object> metadata = new HashMap<>();
for (int i = randomInt(3); i > 0; i--) {
metadata.put(randomAlphaOfLengthBetween(2, 5), randomFrom(randomBoolean(), randomInt(10), randomAlphaOfLength(5)));
}
return new ApplicationPrivilegeDescriptor(applicationName, privilegeName, Sets.newHashSet(patterns), metadata);
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.core.security.authz.privilege;
import junit.framework.AssertionFailedError;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.junit.Assert;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import static org.elasticsearch.common.Strings.collectionToCommaDelimitedString;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
public class ApplicationPrivilegeTests extends ESTestCase {
public void testValidationOfApplicationName() {
final String specialCharacters = ":;$#%()+='.{}[]!@^&'";
final Supplier<Character> specialCharacter = () -> specialCharacters.charAt(randomInt(specialCharacters.length() - 1));
assertValidationFailure("a p p", "application name", () -> ApplicationPrivilege.validateApplicationName("a p p"));
assertValidationFailure("ap", "application name", () -> ApplicationPrivilege.validateApplicationName("ap"));
for (String app : Arrays.asList(
"App",// must start with lowercase
"1app", // must start with letter
"app" + specialCharacter.get() // cannot contain special characters unless preceded by a "-" or "_"
)) {
assertValidationFailure(app, "application name", () -> ApplicationPrivilege.validateApplicationName(app));
assertValidationFailure(app, "application name", () -> ApplicationPrivilege.validateApplicationNameOrWildcard(app));
}
// no wildcards
assertValidationFailure("app*", "application names", () -> ApplicationPrivilege.validateApplicationName("app*"));
// no special characters with wildcards
final String appNameWithSpecialCharAndWildcard = "app" + specialCharacter.get() + "*";
assertValidationFailure(appNameWithSpecialCharAndWildcard, "application name",
() -> ApplicationPrivilege.validateApplicationNameOrWildcard(appNameWithSpecialCharAndWildcard));
String appNameWithSpecialChars = "myapp" + randomFrom('-', '_');
for (int i = randomIntBetween(1, 12); i > 0; i--) {
appNameWithSpecialChars = appNameWithSpecialChars + specialCharacter.get();
}
// these should all be OK
for (String app : Arrays.asList("app", "app1", "myApp", "myApp-:;$#%()+='.", "myApp_:;$#%()+='.", appNameWithSpecialChars)) {
assertNoException(app, () -> ApplicationPrivilege.validateApplicationName(app));
assertNoException(app, () -> ApplicationPrivilege.validateApplicationNameOrWildcard(app));
}
}
public void testValidationOfPrivilegeName() {
// must start with lowercase
assertValidationFailure("Read", "privilege names", () -> ApplicationPrivilege.validatePrivilegeName("Read"));
// must start with letter
assertValidationFailure("1read", "privilege names", () -> ApplicationPrivilege.validatePrivilegeName("1read"));
// cannot contain special characters
final String specialChars = ":;$#%()+=/',";
final String withSpecialChar = "read" + specialChars.charAt(randomInt(specialChars.length()-1));
assertValidationFailure(withSpecialChar, "privilege names", () -> ApplicationPrivilege.validatePrivilegeName(withSpecialChar));
// these should all be OK
for (String priv : Arrays.asList("read", "read1", "readData", "read-data", "read.data", "read_data")) {
assertNoException(priv, () -> ApplicationPrivilege.validatePrivilegeName(priv));
assertNoException(priv, () -> ApplicationPrivilege.validatePrivilegeOrActionName(priv));
}
for (String priv : Arrays.asList("r e a d", "read\n", "copy®")) {
assertValidationFailure(priv, "privilege names and action", () -> ApplicationPrivilege.validatePrivilegeOrActionName(priv));
}
for (String priv : Arrays.asList("read:*", "read/*", "read/a_b.c-d+e%f#(g)")) {
assertNoException(priv, () -> ApplicationPrivilege.validatePrivilegeOrActionName(priv));
}
}
public void testNonePrivilege() {
final ApplicationPrivilege none = ApplicationPrivilege.NONE.apply("super-mega-app");
CharacterRunAutomaton run = new CharacterRunAutomaton(none.getAutomaton());
for (int i = randomIntBetween(5, 10); i > 0; i--) {
final String action;
if (randomBoolean()) {
action = randomAlphaOfLengthBetween(3, 12);
} else {
action = randomAlphaOfLengthBetween(3, 6) + randomFrom(":", "/") + randomAlphaOfLengthBetween(3, 8);
}
assertFalse("NONE should not grant " + action, run.run(action));
}
}
public void testGetPrivilegeByName() {
final ApplicationPrivilegeDescriptor descriptor = descriptor("my-app", "read", "data:read/*", "action:login");
final ApplicationPrivilegeDescriptor myWrite = descriptor("my-app", "write", "data:write/*", "action:login");
final ApplicationPrivilegeDescriptor myAdmin = descriptor("my-app", "admin", "data:read/*", "action:*");
final ApplicationPrivilegeDescriptor yourRead = descriptor("your-app", "read", "data:read/*", "action:login");
final Set<ApplicationPrivilegeDescriptor> stored = Sets.newHashSet(descriptor, myWrite, myAdmin, yourRead);
assertEqual(ApplicationPrivilege.get("my-app", Collections.singleton("read"), stored), descriptor);
assertEqual(ApplicationPrivilege.get("my-app", Collections.singleton("write"), stored), myWrite);
final ApplicationPrivilege readWrite = ApplicationPrivilege.get("my-app", Sets.newHashSet("read", "write"), stored);
assertThat(readWrite.getApplication(), equalTo("my-app"));
assertThat(readWrite.name(), containsInAnyOrder("read", "write"));
assertThat(readWrite.getPatterns(), arrayContainingInAnyOrder("data:read/*", "data:write/*", "action:login"));
CharacterRunAutomaton run = new CharacterRunAutomaton(readWrite.getAutomaton());
for (String action : Arrays.asList("data:read/settings", "data:write/user/kimchy", "action:login")) {
assertTrue(run.run(action));
}
for (String action : Arrays.asList("data:delete/user/kimchy", "action:shutdown")) {
assertFalse(run.run(action));
}
}
private void assertEqual(ApplicationPrivilege myReadPriv, ApplicationPrivilegeDescriptor myRead) {
assertThat(myReadPriv.getApplication(), equalTo(myRead.getApplication()));
assertThat(getPrivilegeName(myReadPriv), equalTo(myRead.getName()));
assertThat(Sets.newHashSet(myReadPriv.getPatterns()), equalTo(myRead.getActions()));
}
private ApplicationPrivilegeDescriptor descriptor(String application, String name, String... actions) {
return new ApplicationPrivilegeDescriptor(application, name, Sets.newHashSet(actions), Collections.emptyMap());
}
public void testEqualsAndHashCode() {
final ApplicationPrivilege privilege = randomPrivilege();
final EqualsHashCodeTestUtils.MutateFunction<ApplicationPrivilege> mutate = randomFrom(
orig -> createPrivilege("x" + orig.getApplication(), getPrivilegeName(orig), orig.getPatterns()),
orig -> createPrivilege(orig.getApplication(), "x" + getPrivilegeName(orig), orig.getPatterns()),
orig -> new ApplicationPrivilege(orig.getApplication(), getPrivilegeName(orig), "*")
);
EqualsHashCodeTestUtils.checkEqualsAndHashCode(privilege,
original -> createPrivilege(original.getApplication(), getPrivilegeName(original), original.getPatterns()),
mutate
);
}
private ApplicationPrivilege createPrivilege(String applicationName, String privilegeName, String... patterns) {
return new ApplicationPrivilege(applicationName, privilegeName, patterns);
}
private String getPrivilegeName(ApplicationPrivilege privilege) {
if (privilege.name.size() == 1) {
return privilege.name.iterator().next();
} else {
throw new IllegalStateException(privilege + " has a multivariate name: " + collectionToCommaDelimitedString(privilege.name));
}
}
private void assertValidationFailure(String reason,String messageContent, ThrowingRunnable body) {
final IllegalArgumentException exception;
try {
exception = expectThrows(IllegalArgumentException.class, body);
assertThat(exception.getMessage().toLowerCase(Locale.ROOT), containsString(messageContent.toLowerCase(Locale.ROOT)));
} catch (AssertionFailedError e) {
fail(reason + " - " + e.getMessage());
}
}
private void assertNoException(String reason, ThrowingRunnable body) {
try {
body.run();
// pass
} catch (Throwable e) {
Assert.fail(reason + " - Expected no exception, but got: " + e);
}
}
private ApplicationPrivilege randomPrivilege() {
final String applicationName;
if (randomBoolean()) {
applicationName = "*";
} else {
applicationName = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(2, 10);
}
final String privilegeName = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(2, 8);
final String[] patterns = new String[randomIntBetween(0, 5)];
for (int i = 0; i < patterns.length; i++) {
final String suffix = randomBoolean() ? "*" : randomAlphaOfLengthBetween(4, 9);
patterns[i] = randomAlphaOfLengthBetween(2, 5) + "/" + suffix;
}
final Map<String, Object> metadata = new HashMap<>();
for (int i = randomInt(3); i > 0; i--) {
metadata.put(randomAlphaOfLengthBetween(2, 5), randomFrom(randomBoolean(), randomInt(10), randomAlphaOfLength(5)));
}
return createPrivilege(applicationName, privilegeName, patterns);
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION;
import static org.hamcrest.Matchers.equalTo;
public class ConditionalClusterPrivilegesTests extends ESTestCase {
public void testSerialization() throws Exception {
final ConditionalClusterPrivilege[] original = buildSecurityPrivileges();
try (BytesStreamOutput out = new BytesStreamOutput()) {
ConditionalClusterPrivileges.writeArray(out, original);
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
try (StreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) {
final ConditionalClusterPrivilege[] copy = ConditionalClusterPrivileges.readArray(in);
assertThat(copy, equalTo(original));
assertThat(original, equalTo(copy));
}
}
}
public void testGenerateAndParseXContent() throws Exception {
final XContent xContent = randomFrom(XContentType.values()).xContent();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
final XContentBuilder builder = new XContentBuilder(xContent, out);
final List<ConditionalClusterPrivilege> original = Arrays.asList(buildSecurityPrivileges());
ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, original);
builder.flush();
final byte[] bytes = out.toByteArray();
try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, bytes)) {
assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT));
final List<ConditionalClusterPrivilege> clone = ConditionalClusterPrivileges.parse(parser);
assertThat(clone, equalTo(original));
assertThat(original, equalTo(clone));
}
}
}
private ConditionalClusterPrivilege[] buildSecurityPrivileges() {
return buildSecurityPrivileges(randomIntBetween(4, 7));
}
private ConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) {
return new ConditionalClusterPrivilege[] {
ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength)
};
}
}

View File

@ -0,0 +1,156 @@
/*
* 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.core.security.authz.privilege;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.role.PutRoleAction;
import org.elasticsearch.xpack.core.security.action.rolemapping.DeleteRoleMappingAction;
import org.elasticsearch.xpack.core.security.action.user.GetUsersAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION;
import static org.elasticsearch.test.TestMatchers.predicateMatches;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
public class ManageApplicationPrivilegesTests extends ESTestCase {
public void testSerialization() throws Exception {
final ManageApplicationPrivileges original = buildPrivileges();
try (BytesStreamOutput out = new BytesStreamOutput()) {
original.writeTo(out);
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
try (StreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) {
final ManageApplicationPrivileges copy = ManageApplicationPrivileges.createFrom(in);
assertThat(copy, equalTo(original));
assertThat(original, equalTo(copy));
}
}
}
public void testGenerateAndParseXContent() throws Exception {
final XContent xContent = randomFrom(XContentType.values()).xContent();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
final XContentBuilder builder = new XContentBuilder(xContent, out);
final ManageApplicationPrivileges original = buildPrivileges();
builder.startObject();
original.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
builder.flush();
final byte[] bytes = out.toByteArray();
try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, bytes)) {
assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT));
// ManageApplicationPrivileges.parse requires that the parser be positioned on the "manage" field.
assertThat(parser.nextToken(), equalTo(XContentParser.Token.FIELD_NAME));
final ManageApplicationPrivileges clone = ManageApplicationPrivileges.parse(parser);
assertThat(parser.nextToken(), equalTo(XContentParser.Token.END_OBJECT));
assertThat(clone, equalTo(original));
assertThat(original, equalTo(clone));
}
}
}
public void testEqualsAndHashCode() {
final int applicationNameLength = randomIntBetween(4, 7);
final ManageApplicationPrivileges privileges = buildPrivileges(applicationNameLength);
final EqualsHashCodeTestUtils.MutateFunction<ManageApplicationPrivileges> mutate
= orig -> buildPrivileges(applicationNameLength + randomIntBetween(1, 3));
EqualsHashCodeTestUtils.checkEqualsAndHashCode(privileges, this::clone, mutate);
}
public void testPrivilege() {
final ManageApplicationPrivileges privileges = buildPrivileges();
assertThat(privileges.getPrivilege(), instanceOf(ClusterPrivilege.class));
for (String actionName : Arrays.asList(GetPrivilegesAction.NAME, PutPrivilegesAction.NAME, DeletePrivilegesAction.NAME)) {
assertThat(privileges.getPrivilege().predicate(), predicateMatches(actionName));
}
for (String actionName : Arrays.asList(GetUsersAction.NAME, PutRoleAction.NAME, DeleteRoleMappingAction.NAME,
HasPrivilegesAction.NAME)) {
assertThat(privileges.getPrivilege().predicate(), not(predicateMatches(actionName)));
}
}
public void testRequestPredicate() {
final ManageApplicationPrivileges kibanaAndLogstash = new ManageApplicationPrivileges(Sets.newHashSet("kibana-*", "logstash"));
final ManageApplicationPrivileges cloudAndSwiftype = new ManageApplicationPrivileges(Sets.newHashSet("cloud-*", "swiftype"));
final Predicate<TransportRequest> kibanaAndLogstashPredicate = kibanaAndLogstash.getRequestPredicate();
final Predicate<TransportRequest> cloudAndSwiftypePredicate = cloudAndSwiftype.getRequestPredicate();
assertThat(kibanaAndLogstashPredicate, notNullValue());
assertThat(cloudAndSwiftypePredicate, notNullValue());
final GetPrivilegesRequest getKibana1 = new GetPrivilegesRequest();
getKibana1.application("kibana-1");
assertThat(kibanaAndLogstashPredicate, predicateMatches(getKibana1));
assertThat(cloudAndSwiftypePredicate, not(predicateMatches(getKibana1)));
final DeletePrivilegesRequest deleteLogstash = new DeletePrivilegesRequest("logstash", new String[]{"all"});
assertThat(kibanaAndLogstashPredicate, predicateMatches(deleteLogstash));
assertThat(cloudAndSwiftypePredicate, not(predicateMatches(deleteLogstash)));
final PutPrivilegesRequest putKibana = new PutPrivilegesRequest();
final List<ApplicationPrivilegeDescriptor> kibanaPrivileges = new ArrayList<>();
for (int i = randomIntBetween(2, 6); i > 0; i--) {
kibanaPrivileges.add(new ApplicationPrivilegeDescriptor("kibana-" + i,
randomAlphaOfLengthBetween(3, 6).toLowerCase(Locale.ROOT), Collections.emptySet(), Collections.emptyMap()));
}
putKibana.setPrivileges(kibanaPrivileges);
assertThat(kibanaAndLogstashPredicate, predicateMatches(putKibana));
assertThat(cloudAndSwiftypePredicate, not(predicateMatches(putKibana)));
}
private ManageApplicationPrivileges clone(ManageApplicationPrivileges original) {
return new ManageApplicationPrivileges(new LinkedHashSet<>(original.getApplicationNames()));
}
private ManageApplicationPrivileges buildPrivileges() {
return buildPrivileges(randomIntBetween(4, 7));
}
static ManageApplicationPrivileges buildPrivileges(int applicationNameLength) {
Set<String> applicationNames = Sets.newHashSet(Arrays.asList(generateRandomStringArray(5, applicationNameLength, false, false)));
return new ManageApplicationPrivileges(applicationNames);
}
}

View File

@ -35,6 +35,7 @@ import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.ml.MlMetaIndex;
import org.elasticsearch.xpack.core.ml.action.CloseJobAction;
import org.elasticsearch.xpack.core.ml.action.DeleteDatafeedAction;
@ -75,6 +76,12 @@ import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndex;
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndexFields;
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.role.PutRoleAction;
import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction;
import org.elasticsearch.xpack.core.security.action.saml.SamlPrepareAuthenticationAction;
@ -85,6 +92,8 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.user.BeatsSystemUser;
import org.elasticsearch.xpack.core.security.user.LogstashSystemUser;
import org.elasticsearch.xpack.core.security.user.SystemUser;
@ -104,10 +113,12 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
/**
* Unit tests for the {@link ReservedRolesStore}
@ -139,21 +150,23 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testIngestAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("ingest_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role ingestAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME), is(true));
assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request), is(true));
assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)),
@ -163,25 +176,49 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testKibanaSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_system");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role kibanaRole = Role.builder(roleDescriptor, null).build();
assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
// SAML
assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME), is(true));
assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME), is(false));
assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request), is(true));
assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request), is(false));
// Application Privileges
DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" });
DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" });
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges), is(false));
GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest();
getKibanaPrivileges.application("kibana-.kibana-sales");
GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest();
getApmPrivileges.application("apm");
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges), is(false));
PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest();
putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor(
"kibana-.kibana-" + randomAlphaOfLengthBetween(2,6), "all", Collections.emptySet(), Collections.emptyMap())));
PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest();
putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor(
"swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap())));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges), is(true));
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges), is(false));
// Everything else
assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -238,18 +275,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testKibanaUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role kibanaUserRole = Role.builder(roleDescriptor, null).build();
assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(kibanaUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -271,22 +310,35 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(kibanaUserRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true));
assertThat(kibanaUserRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true));
});
final String randomApplication = "kibana-" + randomAlphaOfLengthBetween(8, 24);
assertThat(kibanaUserRole.application().grants(new ApplicationPrivilege(randomApplication, "app-random", "all"), "*"), is(false));
final String application = "kibana-.kibana";
assertThat(kibanaUserRole.application().grants(new ApplicationPrivilege(application, "app-foo", "foo"), "*"), is(false));
assertThat(kibanaUserRole.application().grants(new ApplicationPrivilege(application, "app-all", "all"), "*"), is(true));
final String applicationWithRandomIndex = "kibana-.kibana_" + randomAlphaOfLengthBetween(8, 24);
assertThat(kibanaUserRole.application().grants(new ApplicationPrivilege(applicationWithRandomIndex, "app-random-index", "all"),
"*"), is(false));
}
public void testMonitoringUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("monitoring_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role monitoringUserRole = Role.builder(roleDescriptor, null).build();
assertThat(monitoringUserRole.cluster().check(MainAction.NAME), is(true));
assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request), is(true));
assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(monitoringUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -313,27 +365,29 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testRemoteMonitoringAgentRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_agent");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build();
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request), is(false));
// we get this from the cluster:monitor privilege
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME), is(true));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -357,18 +411,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testReportingUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("reporting_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role reportingUserRole = Role.builder(roleDescriptor, null).build();
assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(reportingUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -393,18 +449,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testKibanaDashboardOnlyUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build();
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -420,20 +478,35 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(dashboardsOnlyUserRole.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(index), is(true));
assertThat(dashboardsOnlyUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true));
assertThat(dashboardsOnlyUserRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true));
final String randomApplication = "kibana-" + randomAlphaOfLengthBetween(8, 24);
assertThat(dashboardsOnlyUserRole.application().grants(new ApplicationPrivilege(randomApplication, "app-random", "all"), "*"),
is(false));
final String application = "kibana-.kibana";
assertThat(dashboardsOnlyUserRole.application().grants(new ApplicationPrivilege(application, "app-foo", "foo"), "*"), is(false));
assertThat(dashboardsOnlyUserRole.application().grants(new ApplicationPrivilege(application, "app-all", "all"), "*"), is(false));
assertThat(dashboardsOnlyUserRole.application().grants(new ApplicationPrivilege(application, "app-read", "read"), "*"), is(true));
final String applicationWithRandomIndex = "kibana-.kibana_" + randomAlphaOfLengthBetween(8, 24);
assertThat(dashboardsOnlyUserRole.application().grants(
new ApplicationPrivilege(applicationWithRandomIndex, "app-random-index", "all"), "*"), is(false));
}
public void testSuperuserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("superuser");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role superuserRole = Role.builder(roleDescriptor, null).build();
assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME), is(true));
assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(true));
assertThat(superuserRole.cluster().check(PutUserAction.NAME), is(true));
assertThat(superuserRole.cluster().check(PutRoleAction.NAME), is(true));
assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME), is(true));
assertThat(superuserRole.cluster().check("internal:admin/foo"), is(false));
assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutUserAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false));
final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build();
final MetaData metaData = new MetaData.Builder()
@ -472,18 +545,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testLogstashSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_system");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role logstashSystemRole = Role.builder(roleDescriptor, null).build();
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -494,18 +569,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testBeatsAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("beats_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
final Role beatsAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -527,18 +605,20 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testBeatsSystemRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME);
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role logstashSystemRole = Role.builder(roleDescriptor, null).build();
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true));
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -549,46 +629,48 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testMachineLearningAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(CloseJobAction.NAME), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME), is(false)); // internal use only
assertThat(role.cluster().check(FlushJobAction.NAME), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME), is(false)); // internal use only
assertThat(role.cluster().check(OpenJobAction.NAME), is(true));
assertThat(role.cluster().check(PostDataAction.NAME), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME), is(true));
assertThat(role.cluster().check(PutJobAction.NAME), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME), is(true));
assertThat(role.cluster().check(CloseJobAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(FlushJobAction.NAME, request), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(OpenJobAction.NAME, request), is(true));
assertThat(role.cluster().check(PostDataAction.NAME, request), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME, request), is(true));
assertThat(role.cluster().check(PutJobAction.NAME, request), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(true));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -599,46 +681,48 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testMachineLearningUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(CloseJobAction.NAME), is(false));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME), is(false));
assertThat(role.cluster().check(DeleteFilterAction.NAME), is(false));
assertThat(role.cluster().check(DeleteJobAction.NAME), is(false));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME), is(false));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME), is(false));
assertThat(role.cluster().check(FlushJobAction.NAME), is(false));
assertThat(role.cluster().check(GetBucketsAction.NAME), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME), is(false));
assertThat(role.cluster().check(GetInfluencersAction.NAME), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(KillProcessAction.NAME), is(false));
assertThat(role.cluster().check(OpenJobAction.NAME), is(false));
assertThat(role.cluster().check(PostDataAction.NAME), is(false));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(PutDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(PutFilterAction.NAME), is(false));
assertThat(role.cluster().check(PutJobAction.NAME), is(false));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME), is(false));
assertThat(role.cluster().check(StartDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(StopDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME), is(false));
assertThat(role.cluster().check(UpdateJobAction.NAME), is(false));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME), is(false));
assertThat(role.cluster().check(UpdateProcessAction.NAME), is(false));
assertThat(role.cluster().check(ValidateDetectorAction.NAME), is(false));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME), is(false));
assertThat(role.cluster().check(CloseJobAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(false));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false));
assertThat(role.cluster().check(FlushJobAction.NAME, request), is(false));
assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(false));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false));
assertThat(role.cluster().check(OpenJobAction.NAME, request), is(false));
assertThat(role.cluster().check(PostDataAction.NAME, request), is(false));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(PutFilterAction.NAME, request), is(false));
assertThat(role.cluster().check(PutJobAction.NAME, request), is(false));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(false));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -649,19 +733,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testWatcherAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(PutWatchAction.NAME), is(true));
assertThat(role.cluster().check(GetWatchAction.NAME), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME), is(true));
assertThat(role.cluster().check(ExecuteWatchAction.NAME), is(true));
assertThat(role.cluster().check(AckWatchAction.NAME), is(true));
assertThat(role.cluster().check(ActivateWatchAction.NAME), is(true));
assertThat(role.cluster().check(WatcherServiceAction.NAME), is(true));
assertThat(role.cluster().check(WatcherStatsAction.NAME), is(true));
assertThat(role.cluster().check(PutWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(AckWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -674,19 +760,21 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testWatcherUserRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_user");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(PutWatchAction.NAME), is(false));
assertThat(role.cluster().check(GetWatchAction.NAME), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME), is(false));
assertThat(role.cluster().check(ExecuteWatchAction.NAME), is(false));
assertThat(role.cluster().check(AckWatchAction.NAME), is(false));
assertThat(role.cluster().check(ActivateWatchAction.NAME), is(false));
assertThat(role.cluster().check(WatcherServiceAction.NAME), is(false));
assertThat(role.cluster().check(WatcherStatsAction.NAME), is(true));
assertThat(role.cluster().check(PutWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -724,15 +812,17 @@ public class ReservedRolesStoreTests extends ESTestCase {
}
public void testLogstashAdminRole() {
final TransportRequest request = mock(TransportRequest.class);
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_admin");
assertNotNull(roleDescriptor);
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role logstashAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME), is(false));
assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));

View File

@ -92,6 +92,7 @@ public class TransportPutDatafeedAction extends TransportMasterNodeAction<PutDat
.indices(request.getDatafeed().getIndices().toArray(new String[0]))
.privileges(SearchAction.NAME)
.build());
privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]);
client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener);
} else {
@ -107,8 +108,8 @@ public class TransportPutDatafeedAction extends TransportMasterNodeAction<PutDat
} else {
XContentBuilder builder = JsonXContent.contentBuilder();
builder.startObject();
for (HasPrivilegesResponse.IndexPrivileges index : response.getIndexPrivileges()) {
builder.field(index.getIndex());
for (HasPrivilegesResponse.ResourcePrivileges index : response.getIndexPrivileges()) {
builder.field(index.getResource());
builder.map(index.getPrivileges());
}
builder.endObject();

View File

@ -84,6 +84,9 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.SecurityField;
import org.elasticsearch.xpack.core.security.SecuritySettings;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheAction;
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleAction;
@ -118,6 +121,9 @@ import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessCo
import org.elasticsearch.xpack.core.security.authz.accesscontrol.SecurityIndexSearcherWrapper;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
import org.elasticsearch.xpack.security.action.privilege.TransportDeletePrivilegesAction;
import org.elasticsearch.xpack.security.action.privilege.TransportGetPrivilegesAction;
import org.elasticsearch.xpack.security.action.privilege.TransportPutPrivilegesAction;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.core.security.index.IndexAuditTrailField;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
@ -175,6 +181,7 @@ import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.SecuritySearchOperationListener;
import org.elasticsearch.xpack.security.authz.accesscontrol.OptOutQueryCache;
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
@ -182,6 +189,10 @@ import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestDeletePrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegeAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.realm.RestClearRealmCacheAction;
import org.elasticsearch.xpack.security.rest.action.role.RestClearRolesCacheAction;
import org.elasticsearch.xpack.security.rest.action.role.RestDeleteRoleAction;
@ -441,6 +452,9 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
anonymousUser, tokenService));
components.add(authcService.get());
final NativePrivilegeStore privilegeStore = new NativePrivilegeStore(settings, client, securityIndex.get());
components.add(privilegeStore);
final FileRolesStore fileRolesStore = new FileRolesStore(settings, env, resourceWatcherService, getLicenseState());
final NativeRolesStore nativeRolesStore = new NativeRolesStore(settings, client, getLicenseState(), securityIndex.get());
final ReservedRolesStore reservedRolesStore = new ReservedRolesStore();
@ -449,7 +463,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService));
}
final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore,
reservedRolesStore, rolesProviders, threadPool.getThreadContext(), getLicenseState());
reservedRolesStore, privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState());
securityIndex.get().addIndexStateListener(allRolesStore::onSecurityIndexStateChange);
// to keep things simple, just invalidate all cached entries on license change. this happens so rarely that the impact should be
// minimal
@ -697,7 +711,10 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new ActionHandler<>(SamlPrepareAuthenticationAction.INSTANCE, TransportSamlPrepareAuthenticationAction.class),
new ActionHandler<>(SamlAuthenticateAction.INSTANCE, TransportSamlAuthenticateAction.class),
new ActionHandler<>(SamlLogoutAction.INSTANCE, TransportSamlLogoutAction.class),
new ActionHandler<>(SamlInvalidateSessionAction.INSTANCE, TransportSamlInvalidateSessionAction.class)
new ActionHandler<>(SamlInvalidateSessionAction.INSTANCE, TransportSamlInvalidateSessionAction.class),
new ActionHandler<>(GetPrivilegesAction.INSTANCE, TransportGetPrivilegesAction.class),
new ActionHandler<>(PutPrivilegesAction.INSTANCE, TransportPutPrivilegesAction.class),
new ActionHandler<>(DeletePrivilegesAction.INSTANCE, TransportDeletePrivilegesAction.class)
);
}
@ -742,7 +759,11 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new RestSamlPrepareAuthenticationAction(settings, restController, getLicenseState()),
new RestSamlAuthenticateAction(settings, restController, getLicenseState()),
new RestSamlLogoutAction(settings, restController, getLicenseState()),
new RestSamlInvalidateSessionAction(settings, restController, getLicenseState())
new RestSamlInvalidateSessionAction(settings, restController, getLicenseState()),
new RestGetPrivilegesAction(settings, restController, getLicenseState()),
new RestPutPrivilegesAction(settings, restController, getLicenseState()),
new RestPutPrivilegeAction(settings, restController, getLicenseState()),
new RestDeletePrivilegesAction(settings, restController, getLicenseState())
);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.privilege;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesResponse;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import java.util.Collections;
import java.util.Set;
/**
* Transport action to retrieve one or more application privileges from the security index
*/
public class TransportDeletePrivilegesAction extends HandledTransportAction<DeletePrivilegesRequest, DeletePrivilegesResponse> {
private final NativePrivilegeStore privilegeStore;
@Inject
public TransportDeletePrivilegesAction(Settings settings, ActionFilters actionFilters,
NativePrivilegeStore privilegeStore,
TransportService transportService) {
super(settings, DeletePrivilegesAction.NAME, transportService, actionFilters, DeletePrivilegesRequest::new);
this.privilegeStore = privilegeStore;
}
@Override
protected void doExecute(Task task, final DeletePrivilegesRequest request, final ActionListener<DeletePrivilegesResponse> listener) {
if (request.privileges() == null || request.privileges().length == 0) {
listener.onResponse(new DeletePrivilegesResponse(Collections.emptyList()));
return;
}
final Set<String> names = Sets.newHashSet(request.privileges());
this.privilegeStore.deletePrivileges(request.application(), names, request.getRefreshPolicy(), ActionListener.wrap(
privileges -> listener.onResponse(
new DeletePrivilegesResponse(privileges.getOrDefault(request.application(), Collections.emptyList()))
), listener::onFailure
));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.privilege;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesResponse;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.elasticsearch.common.Strings.isNullOrEmpty;
/**
* Transport action to retrieve one or more application privileges from the security index
*/
public class TransportGetPrivilegesAction extends HandledTransportAction<GetPrivilegesRequest, GetPrivilegesResponse> {
private final NativePrivilegeStore privilegeStore;
@Inject
public TransportGetPrivilegesAction(Settings settings, ActionFilters actionFilters,
NativePrivilegeStore privilegeStore, TransportService transportService) {
super(settings, GetPrivilegesAction.NAME, transportService, actionFilters, GetPrivilegesRequest::new);
this.privilegeStore = privilegeStore;
}
@Override
protected void doExecute(Task task, final GetPrivilegesRequest request, final ActionListener<GetPrivilegesResponse> listener) {
final Set<String> names;
if (request.privileges() == null || request.privileges().length == 0) {
names = null;
} else {
names = new HashSet<>(Arrays.asList(request.privileges()));
}
final Collection<String> applications = isNullOrEmpty(request.application()) ? null : Collections.singleton(request.application());
this.privilegeStore.getPrivileges(applications, names, ActionListener.wrap(
privileges -> listener.onResponse(new GetPrivilegesResponse(privileges)),
listener::onFailure
));
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.privilege;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesResponse;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import java.util.Collections;
/**
* Transport action to retrieve one or more application privileges from the security index
*/
public class TransportPutPrivilegesAction extends HandledTransportAction<PutPrivilegesRequest, PutPrivilegesResponse> {
private final NativePrivilegeStore privilegeStore;
@Inject
public TransportPutPrivilegesAction(Settings settings, ActionFilters actionFilters,
NativePrivilegeStore privilegeStore, TransportService transportService) {
super(settings, PutPrivilegesAction.NAME, transportService, actionFilters, PutPrivilegesRequest::new);
this.privilegeStore = privilegeStore;
}
@Override
protected void doExecute(Task task, final PutPrivilegesRequest request, final ActionListener<PutPrivilegesResponse> listener) {
if (request.getPrivileges() == null || request.getPrivileges().size() == 0) {
listener.onResponse(new PutPrivilegesResponse(Collections.emptyMap()));
} else {
this.privilegeStore.putPrivileges(request.getPrivileges(), request.getRefreshPolicy(), ActionListener.wrap(
created -> listener.onResponse(new PutPrivilegesResponse(created)),
listener::onFailure
));
}
}
}

View File

@ -24,19 +24,26 @@ import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Transport action that tests whether a user has the specified
@ -46,13 +53,16 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
private final ThreadPool threadPool;
private final AuthorizationService authorizationService;
private final NativePrivilegeStore privilegeStore;
@Inject
public TransportHasPrivilegesAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, AuthorizationService authorizationService) {
ActionFilters actionFilters, AuthorizationService authorizationService,
NativePrivilegeStore privilegeStore) {
super(settings, HasPrivilegesAction.NAME, transportService, actionFilters, HasPrivilegesRequest::new);
this.threadPool = threadPool;
this.authorizationService = authorizationService;
this.privilegeStore = privilegeStore;
}
@Override
@ -66,15 +76,33 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
}
authorizationService.roles(user, ActionListener.wrap(
role -> checkPrivileges(request, role, listener),
listener::onFailure));
role -> resolveApplicationPrivileges(request, ActionListener.wrap(
applicationPrivilegeLookup -> checkPrivileges(request, role, applicationPrivilegeLookup, listener),
listener::onFailure)),
listener::onFailure));
}
private void resolveApplicationPrivileges(HasPrivilegesRequest request,
ActionListener<Collection<ApplicationPrivilegeDescriptor>> listener) {
final Set<String> applications = getApplicationNames(request);
privilegeStore.getPrivileges(applications, null, listener);
}
private Set<String> getApplicationNames(HasPrivilegesRequest request) {
return Arrays.stream(request.applicationPrivileges())
.map(RoleDescriptor.ApplicationResourcePrivileges::getApplication)
.collect(Collectors.toSet());
}
private void checkPrivileges(HasPrivilegesRequest request, Role userRole,
Collection<ApplicationPrivilegeDescriptor> applicationPrivileges,
ActionListener<HasPrivilegesResponse> listener) {
logger.debug(() -> new ParameterizedMessage("Check whether role [{}] has privileges cluster=[{}] index=[{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), Strings.arrayToCommaDelimitedString(request.clusterPrivileges()),
Strings.arrayToCommaDelimitedString(request.indexPrivileges())));
logger.trace(() -> new ParameterizedMessage("Check whether role [{}] has privileges cluster=[{}] index=[{}] application=[{}]",
Strings.arrayToCommaDelimitedString(userRole.names()),
Strings.arrayToCommaDelimitedString(request.clusterPrivileges()),
Strings.arrayToCommaDelimitedString(request.indexPrivileges()),
Strings.arrayToCommaDelimitedString(request.applicationPrivileges())
));
Map<String, Boolean> cluster = new HashMap<>();
for (String checkAction : request.clusterPrivileges()) {
@ -86,30 +114,62 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
final Map<IndicesPermission.Group, Automaton> predicateCache = new HashMap<>();
final Map<String, HasPrivilegesResponse.IndexPrivileges> indices = new LinkedHashMap<>();
final Map<String, HasPrivilegesResponse.ResourcePrivileges> indices = new LinkedHashMap<>();
for (RoleDescriptor.IndicesPrivileges check : request.indexPrivileges()) {
for (String index : check.getIndices()) {
final Map<String, Boolean> privileges = new HashMap<>();
final HasPrivilegesResponse.IndexPrivileges existing = indices.get(index);
final HasPrivilegesResponse.ResourcePrivileges existing = indices.get(index);
if (existing != null) {
privileges.putAll(existing.getPrivileges());
}
for (String privilege : check.getPrivileges()) {
if (testIndexMatch(index, privilege, userRole, predicateCache)) {
logger.debug(() -> new ParameterizedMessage("Role [{}] has [{}] on [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), privilege, index));
logger.debug(() -> new ParameterizedMessage("Role [{}] has [{}] on index [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), privilege, index));
privileges.put(privilege, true);
} else {
logger.debug(() -> new ParameterizedMessage("Role [{}] does not have [{}] on [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), privilege, index));
logger.debug(() -> new ParameterizedMessage("Role [{}] does not have [{}] on index [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), privilege, index));
privileges.put(privilege, false);
allMatch = false;
}
}
indices.put(index, new HasPrivilegesResponse.IndexPrivileges(index, privileges));
indices.put(index, new HasPrivilegesResponse.ResourcePrivileges(index, privileges));
}
}
listener.onResponse(new HasPrivilegesResponse(allMatch, cluster, indices.values()));
final Map<String, Collection<HasPrivilegesResponse.ResourcePrivileges>> privilegesByApplication = new HashMap<>();
for (String applicationName : getApplicationNames(request)) {
logger.debug("Checking privileges for application {}", applicationName);
final Map<String, HasPrivilegesResponse.ResourcePrivileges> appPrivilegesByResource = new LinkedHashMap<>();
for (RoleDescriptor.ApplicationResourcePrivileges p : request.applicationPrivileges()) {
if (applicationName.equals(p.getApplication())) {
for (String resource : p.getResources()) {
final Map<String, Boolean> privileges = new HashMap<>();
final HasPrivilegesResponse.ResourcePrivileges existing = appPrivilegesByResource.get(resource);
if (existing != null) {
privileges.putAll(existing.getPrivileges());
}
for (String privilege : p.getPrivileges()) {
if (testResourceMatch(applicationName, resource, privilege, userRole, applicationPrivileges)) {
logger.debug(() -> new ParameterizedMessage("Role [{}] has [{} {}] on resource [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), applicationName, privilege, resource));
privileges.put(privilege, true);
} else {
logger.debug(() -> new ParameterizedMessage("Role [{}] does not have [{} {}] on resource [{}]",
Strings.arrayToCommaDelimitedString(userRole.names()), applicationName, privilege, resource));
privileges.put(privilege, false);
allMatch = false;
}
}
appPrivilegesByResource.put(resource, new HasPrivilegesResponse.ResourcePrivileges(resource, privileges));
}
}
}
privilegesByApplication.put(applicationName, appPrivilegesByResource.values());
}
listener.onResponse(new HasPrivilegesResponse(allMatch, cluster, indices.values(), privilegesByApplication));
}
private boolean testIndexMatch(String checkIndex, String checkPrivilegeName, Role userRole,
@ -139,4 +199,17 @@ public class TransportHasPrivilegesAction extends HandledTransportAction<HasPriv
private static boolean testPrivilege(Privilege checkPrivilege, Automaton roleAutomaton) {
return Operations.subsetOf(checkPrivilege.getAutomaton(), roleAutomaton);
}
private boolean testResourceMatch(String application, String checkResource, String checkPrivilegeName, Role userRole,
Collection<ApplicationPrivilegeDescriptor> privileges) {
final Set<String> nameSet = Collections.singleton(checkPrivilegeName);
final ApplicationPrivilege checkPrivilege = ApplicationPrivilege.get(application, nameSet, privileges);
assert checkPrivilege.getApplication().equals(application)
: "Privilege " + checkPrivilege + " should have application " + application;
assert checkPrivilege.name().equals(nameSet)
: "Privilege " + checkPrivilege + " should have name " + nameSet;
return userRole.application().grants(checkPrivilege, checkResource);
}
}

View File

@ -178,8 +178,8 @@ public class AuthorizationService extends AbstractComponent {
// first, we'll check if the action is a cluster action. If it is, we'll only check it against the cluster permissions
if (ClusterPrivilege.ACTION_MATCHER.test(action)) {
ClusterPermission cluster = permission.cluster();
if (cluster.check(action) || checkSameUserPermissions(action, request, authentication)) {
final ClusterPermission cluster = permission.cluster();
if (cluster.check(action, request) || checkSameUserPermissions(action, request, authentication) ) {
putTransientIfNonExisting(AuthorizationServiceField.INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL);
auditTrail.accessGranted(authentication, action, request, permission.names());
return;

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
@ -28,7 +29,8 @@ import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCa
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
@ -51,6 +53,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static org.elasticsearch.common.util.set.Sets.newHashSet;
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isIndexDeleted;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed;
@ -80,6 +83,7 @@ public class CompositeRolesStore extends AbstractComponent {
private final FileRolesStore fileRolesStore;
private final NativeRolesStore nativeRolesStore;
private final ReservedRolesStore reservedRolesStore;
private final NativePrivilegeStore privilegeStore;
private final XPackLicenseState licenseState;
private final Cache<Set<String>, Role> roleCache;
private final Set<String> negativeLookupCache;
@ -88,7 +92,7 @@ public class CompositeRolesStore extends AbstractComponent {
private final List<BiConsumer<Set<String>, ActionListener<Set<RoleDescriptor>>>> customRolesProviders;
public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore,
ReservedRolesStore reservedRolesStore,
ReservedRolesStore reservedRolesStore, NativePrivilegeStore privilegeStore,
List<BiConsumer<Set<String>, ActionListener<Set<RoleDescriptor>>>> rolesProviders,
ThreadContext threadContext, XPackLicenseState licenseState) {
super(settings);
@ -98,6 +102,7 @@ public class CompositeRolesStore extends AbstractComponent {
fileRolesStore.addListener(this::invalidateAll);
this.nativeRolesStore = nativeRolesStore;
this.reservedRolesStore = reservedRolesStore;
this.privilegeStore = privilegeStore;
this.licenseState = licenseState;
CacheBuilder<Set<String>, Role> builder = CacheBuilder.builder();
final int cacheSize = CACHE_SIZE_SETTING.get(settings);
@ -117,31 +122,33 @@ public class CompositeRolesStore extends AbstractComponent {
} else {
final long invalidationCounter = numInvalidation.get();
roleDescriptors(roleNames, ActionListener.wrap(
(descriptors) -> {
final Role role;
descriptors -> {
final Set<RoleDescriptor> effectiveDescriptors;
if (licenseState.isDocumentAndFieldLevelSecurityAllowed()) {
role = buildRoleFromDescriptors(descriptors, fieldPermissionsCache);
effectiveDescriptors = descriptors;
} else {
final Set<RoleDescriptor> filtered = descriptors.stream()
effectiveDescriptors = descriptors.stream()
.filter((rd) -> rd.isUsingDocumentOrFieldLevelSecurity() == false)
.collect(Collectors.toSet());
role = buildRoleFromDescriptors(filtered, fieldPermissionsCache);
}
if (role != null) {
try (ReleasableLock ignored = readLock.acquire()) {
/* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold the write
* lock (fetching stats for instance - which is kinda overkill?) but since we fetching stuff in an async
* fashion we need to make sure that if the cache got invalidated since we started the request we don't
* put a potential stale result in the cache, hence the numInvalidation.get() comparison to the number of
* invalidation when we started. we just try to be on the safe side and don't cache potentially stale
* results*/
if (invalidationCounter == numInvalidation.get()) {
roleCache.computeIfAbsent(roleNames, (s) -> role);
logger.trace("Building role from descriptors [{}] for names [{}]", effectiveDescriptors, roleNames);
buildRoleFromDescriptors(effectiveDescriptors, fieldPermissionsCache, privilegeStore, ActionListener.wrap(role -> {
if (role != null) {
try (ReleasableLock ignored = readLock.acquire()) {
/* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold
* the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching
* stuff in an async fashion we need to make sure that if the cache got invalidated since we
* started the request we don't put a potential stale result in the cache, hence the
* numInvalidation.get() comparison to the number of invalidation when we started. we just try to
* be on the safe side and don't cache potentially stale results
*/
if (invalidationCounter == numInvalidation.get()) {
roleCache.computeIfAbsent(roleNames, (s) -> role);
}
}
}
}
roleActionListener.onResponse(role);
roleActionListener.onResponse(role);
}, roleActionListener::onFailure));
},
roleActionListener::onFailure));
}
@ -238,25 +245,36 @@ public class CompositeRolesStore extends AbstractComponent {
return Sets.difference(roleNames, foundNames);
}
public static Role buildRoleFromDescriptors(Set<RoleDescriptor> roleDescriptors, FieldPermissionsCache fieldPermissionsCache) {
public static void buildRoleFromDescriptors(Collection<RoleDescriptor> roleDescriptors, FieldPermissionsCache fieldPermissionsCache,
NativePrivilegeStore privilegeStore, ActionListener<Role> listener) {
if (roleDescriptors.isEmpty()) {
return Role.EMPTY;
listener.onResponse(Role.EMPTY);
return;
}
Set<String> clusterPrivileges = new HashSet<>();
final List<ConditionalClusterPrivilege> conditionalClusterPrivileges = new ArrayList<>();
Set<String> runAs = new HashSet<>();
Map<Set<String>, MergeableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>();
// Keyed by application + resource
Map<Tuple<String, Set<String>>, Set<String>> applicationPrivilegesMap = new HashMap<>();
List<String> roleNames = new ArrayList<>(roleDescriptors.size());
for (RoleDescriptor descriptor : roleDescriptors) {
roleNames.add(descriptor.getName());
if (descriptor.getClusterPrivileges() != null) {
clusterPrivileges.addAll(Arrays.asList(descriptor.getClusterPrivileges()));
}
if (descriptor.getConditionalClusterPrivileges() != null) {
conditionalClusterPrivileges.addAll(Arrays.asList(descriptor.getConditionalClusterPrivileges()));
}
if (descriptor.getRunAs() != null) {
runAs.addAll(Arrays.asList(descriptor.getRunAs()));
}
IndicesPrivileges[] indicesPrivileges = descriptor.getIndicesPrivileges();
for (IndicesPrivileges indicesPrivilege : indicesPrivileges) {
Set<String> key = Sets.newHashSet(indicesPrivilege.getIndices());
Set<String> key = newHashSet(indicesPrivilege.getIndices());
// if a index privilege is an explicit denial, then we treat it as non-existent since we skipped these in the past when
// merging
final boolean isExplicitDenial =
@ -274,19 +292,44 @@ public class CompositeRolesStore extends AbstractComponent {
});
}
}
for (RoleDescriptor.ApplicationResourcePrivileges appPrivilege : descriptor.getApplicationPrivileges()) {
Tuple<String, Set<String>> key = new Tuple<>(appPrivilege.getApplication(), newHashSet(appPrivilege.getResources()));
applicationPrivilegesMap.compute(key, (k, v) -> {
if (v == null) {
return newHashSet(appPrivilege.getPrivileges());
} else {
v.addAll(Arrays.asList(appPrivilege.getPrivileges()));
return v;
}
});
}
}
final Set<String> clusterPrivs = clusterPrivileges.isEmpty() ? null : clusterPrivileges;
final Privilege runAsPrivilege = runAs.isEmpty() ? Privilege.NONE : new Privilege(runAs, runAs.toArray(Strings.EMPTY_ARRAY));
Role.Builder builder = Role.builder(roleNames.toArray(new String[roleNames.size()]))
.cluster(ClusterPrivilege.get(clusterPrivs))
final Role.Builder builder = Role.builder(roleNames.toArray(new String[roleNames.size()]))
.cluster(clusterPrivileges, conditionalClusterPrivileges)
.runAs(runAsPrivilege);
indicesPrivilegesMap.entrySet().forEach((entry) -> {
MergeableIndicesPrivilege privilege = entry.getValue();
builder.add(fieldPermissionsCache.getFieldPermissions(privilege.fieldPermissionsDefinition), privilege.query,
IndexPrivilege.get(privilege.privileges), privilege.indices.toArray(Strings.EMPTY_ARRAY));
});
return builder.build();
if (applicationPrivilegesMap.isEmpty()) {
listener.onResponse(builder.build());
} else {
final Set<String> applicationNames = applicationPrivilegesMap.keySet().stream()
.map(Tuple::v1)
.collect(Collectors.toSet());
final Set<String> applicationPrivilegeNames = applicationPrivilegesMap.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet());
privilegeStore.getPrivileges(applicationNames, applicationPrivilegeNames, ActionListener.wrap(appPrivileges -> {
applicationPrivilegesMap.forEach((key, names) ->
builder.addApplicationPrivilege(ApplicationPrivilege.get(key.v1(), names, appPrivileges), key.v2()));
listener.onResponse(builder.build());
}, listener::onFailure));
}
}
public void invalidateAll() {
@ -340,11 +383,11 @@ public class CompositeRolesStore extends AbstractComponent {
MergeableIndicesPrivilege(String[] indices, String[] privileges, @Nullable String[] grantedFields, @Nullable String[] deniedFields,
@Nullable BytesReference query) {
this.indices = Sets.newHashSet(Objects.requireNonNull(indices));
this.privileges = Sets.newHashSet(Objects.requireNonNull(privileges));
this.indices = newHashSet(Objects.requireNonNull(indices));
this.privileges = newHashSet(Objects.requireNonNull(privileges));
this.fieldPermissionsDefinition = new FieldPermissionsDefinition(grantedFields, deniedFields);
if (query != null) {
this.query = Sets.newHashSet(query);
this.query = newHashSet(query);
}
}

View File

@ -0,0 +1,278 @@
/*
* 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.authz.store;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.action.support.GroupedActionListener;
import org.elasticsearch.action.support.TransportActions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParseException;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.xpack.core.ClientHelper;
import org.elasticsearch.xpack.core.security.ScrollHelper;
import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheRequest;
import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheResponse;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
import static org.elasticsearch.xpack.core.ClientHelper.stashWithOrigin;
import static org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor.DOC_TYPE_VALUE;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME;
/**
* {@code NativePrivilegeStore} is a store that reads/writes {@link ApplicationPrivilegeDescriptor} objects,
* from an Elasticsearch index.
*/
public class NativePrivilegeStore extends AbstractComponent {
private static final Collector<Tuple<String, String>, ?, Map<String, List<String>>> TUPLES_TO_MAP = Collectors.toMap(
Tuple::v1,
t -> CollectionUtils.newSingletonArrayList(t.v2()), (a, b) -> {
a.addAll(b);
return a;
});
private final Client client;
private final SecurityClient securityClient;
private final SecurityIndexManager securityIndexManager;
public NativePrivilegeStore(Settings settings, Client client, SecurityIndexManager securityIndexManager) {
super(settings);
this.client = client;
this.securityClient = new SecurityClient(client);
this.securityIndexManager = securityIndexManager;
}
public void getPrivileges(Collection<String> applications, Collection<String> names,
ActionListener<Collection<ApplicationPrivilegeDescriptor>> listener) {
if (applications != null && applications.size() == 1 && names != null && names.size() == 1) {
getPrivilege(Objects.requireNonNull(Iterables.get(applications, 0)), Objects.requireNonNull(Iterables.get(names, 0)),
ActionListener.wrap(privilege ->
listener.onResponse(privilege == null ? Collections.emptyList() : Collections.singletonList(privilege)),
listener::onFailure));
} else {
securityIndexManager.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
final QueryBuilder query;
final TermQueryBuilder typeQuery = QueryBuilders
.termQuery(ApplicationPrivilegeDescriptor.Fields.TYPE.getPreferredName(), DOC_TYPE_VALUE);
if (isEmpty(applications) && isEmpty(names)) {
query = typeQuery;
} else if (isEmpty(names)) {
query = QueryBuilders.boolQuery().filter(typeQuery).filter(
QueryBuilders.termsQuery(ApplicationPrivilegeDescriptor.Fields.APPLICATION.getPreferredName(), applications));
} else if (isEmpty(applications)) {
query = QueryBuilders.boolQuery().filter(typeQuery)
.filter(QueryBuilders.termsQuery(ApplicationPrivilegeDescriptor.Fields.NAME.getPreferredName(), names));
} else {
final String[] docIds = applications.stream()
.flatMap(a -> names.stream().map(n -> toDocId(a, n)))
.toArray(String[]::new);
query = QueryBuilders.boolQuery().filter(typeQuery).filter(QueryBuilders.idsQuery("doc").addIds(docIds));
}
final Supplier<ThreadContext.StoredContext> supplier = client.threadPool().getThreadContext().newRestorableContext(false);
try (ThreadContext.StoredContext ignore = stashWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN)) {
SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME)
.setScroll(TimeValue.timeValueSeconds(10L))
.setQuery(query)
.setSize(1000)
.setFetchSource(true)
.request();
logger.trace(() ->
new ParameterizedMessage("Searching for privileges [{}] with query [{}]", names, Strings.toString(query)));
request.indicesOptions().ignoreUnavailable();
ScrollHelper.fetchAllByEntity(client, request, new ContextPreservingActionListener<>(supplier, listener),
hit -> buildPrivilege(hit.getId(), hit.getSourceRef()));
}
});
}
}
private static boolean isEmpty(Collection<String> collection) {
return collection == null || collection.isEmpty();
}
public void getPrivilege(String application, String name, ActionListener<ApplicationPrivilegeDescriptor> listener) {
securityIndexManager.prepareIndexIfNeededThenExecute(listener::onFailure,
() -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareGet(SECURITY_INDEX_NAME, "doc", toDocId(application, name)).request(),
new ActionListener<GetResponse>() {
@Override
public void onResponse(GetResponse response) {
if (response.isExists()) {
listener.onResponse(buildPrivilege(response.getId(), response.getSourceAsBytesRef()));
} else {
listener.onResponse(null);
}
}
@Override
public void onFailure(Exception e) {
// if the index or the shard is not there / available we just claim the privilege is not there
if (TransportActions.isShardNotAvailableException(e)) {
logger.warn(new ParameterizedMessage("failed to load privilege [{}] index not available", name), e);
listener.onResponse(null);
} else {
logger.error(new ParameterizedMessage("failed to load privilege [{}]", name), e);
listener.onFailure(e);
}
}
},
client::get));
}
public void putPrivileges(Collection<ApplicationPrivilegeDescriptor> privileges, WriteRequest.RefreshPolicy refreshPolicy,
ActionListener<Map<String, List<String>>> listener) {
securityIndexManager.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
ActionListener<IndexResponse> groupListener = new GroupedActionListener<>(
ActionListener.wrap((Collection<IndexResponse> responses) -> {
final Map<String, List<String>> createdNames = responses.stream()
.filter(r -> r.getResult() == DocWriteResponse.Result.CREATED)
.map(r -> r.getId())
.map(NativePrivilegeStore::nameFromDocId)
.collect(TUPLES_TO_MAP);
clearRolesCache(listener, createdNames);
}, listener::onFailure), privileges.size(), Collections.emptyList());
for (ApplicationPrivilegeDescriptor privilege : privileges) {
innerPutPrivilege(privilege, refreshPolicy, groupListener);
}
});
}
private void innerPutPrivilege(ApplicationPrivilegeDescriptor privilege, WriteRequest.RefreshPolicy refreshPolicy,
ActionListener<IndexResponse> listener) {
try {
final String name = privilege.getName();
final XContentBuilder xContentBuilder = privilege.toXContent(jsonBuilder(), true);
ClientHelper.executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareIndex(SECURITY_INDEX_NAME, "doc", toDocId(privilege.getApplication(), name))
.setSource(xContentBuilder)
.setRefreshPolicy(refreshPolicy)
.request(), listener, client::index);
} catch (Exception e) {
logger.warn("Failed to put privilege {} - {}", Strings.toString(privilege), e.toString());
listener.onFailure(e);
}
}
public void deletePrivileges(String application, Collection<String> names, WriteRequest.RefreshPolicy refreshPolicy,
ActionListener<Map<String, List<String>>> listener) {
securityIndexManager.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
ActionListener<DeleteResponse> groupListener = new GroupedActionListener<>(
ActionListener.wrap(responses -> {
final Map<String, List<String>> deletedNames = responses.stream()
.filter(r -> r.getResult() == DocWriteResponse.Result.DELETED)
.map(r -> r.getId())
.map(NativePrivilegeStore::nameFromDocId)
.collect(TUPLES_TO_MAP);
clearRolesCache(listener, deletedNames);
}, listener::onFailure), names.size(), Collections.emptyList());
for (String name : names) {
ClientHelper.executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareDelete(SECURITY_INDEX_NAME, "doc", toDocId(application, name))
.setRefreshPolicy(refreshPolicy)
.request(), groupListener, client::delete);
}
});
}
private <T> void clearRolesCache(ActionListener<T> listener, T value) {
// This currently clears _all_ roles, but could be improved to clear only those roles that reference the affected application
ClearRolesCacheRequest request = new ClearRolesCacheRequest();
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
new ActionListener<ClearRolesCacheResponse>() {
@Override
public void onResponse(ClearRolesCacheResponse nodes) {
listener.onResponse(value);
}
@Override
public void onFailure(Exception e) {
logger.error("unable to clear role cache", e);
listener.onFailure(
new ElasticsearchException("clearing the role cache failed. please clear the role cache manually", e));
}
}, securityClient::clearRolesCache);
}
private ApplicationPrivilegeDescriptor buildPrivilege(String docId, BytesReference source) {
logger.trace("Building privilege from [{}] [{}]", docId, source == null ? "<<null>>" : source.utf8ToString());
if (source == null) {
return null;
}
final Tuple<String, String> name = nameFromDocId(docId);
try {
// EMPTY is safe here because we never use namedObject
try (StreamInput input = source.streamInput();
XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY,
LoggingDeprecationHandler.INSTANCE, input)) {
final ApplicationPrivilegeDescriptor privilege = ApplicationPrivilegeDescriptor.parse(parser, null, null, true);
assert privilege.getApplication().equals(name.v1())
: "Incorrect application name for privilege. Expected [" + name.v1() + "] but was " + privilege.getApplication();
assert privilege.getName().equals(name.v2())
: "Incorrect name for application privilege. Expected [" + name.v2() + "] but was " + privilege.getName();
return privilege;
}
} catch (IOException | XContentParseException e) {
logger.error(new ParameterizedMessage("cannot parse application privilege [{}]", name), e);
return null;
}
}
private static Tuple<String, String> nameFromDocId(String docId) {
final String name = docId.substring(DOC_TYPE_VALUE.length() + 1);
assert name != null && name.length() > 0 : "Invalid name '" + name + "'";
final int colon = name.indexOf(':');
assert colon > 0 : "Invalid name '" + name + "' (missing colon)";
return new Tuple<>(name.substring(0, colon), name.substring(colon + 1));
}
private static String toDocId(String application, String name) {
return DOC_TYPE_VALUE + "_" + application + ":" + name;
}
}

View File

@ -13,6 +13,7 @@ import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.MultiSearchResponse.Item;
@ -63,6 +64,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
import static org.elasticsearch.xpack.core.ClientHelper.stashWithOrigin;
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ROLE_TYPE;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME;
/**
* NativeRolesStore is a {@code RolesStore} that, instead of reading from a
@ -173,15 +175,17 @@ public class NativeRolesStore extends AbstractComponent {
listener.onFailure(e);
return;
}
final IndexRequest indexRequest = client.prepareIndex(SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role.getName()))
.setSource(xContentBuilder)
.setRefreshPolicy(request.getRefreshPolicy())
.request();
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareIndex(SecurityIndexManager.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role.getName()))
.setSource(xContentBuilder)
.setRefreshPolicy(request.getRefreshPolicy())
.request(),
indexRequest,
new ActionListener<IndexResponse>() {
@Override
public void onResponse(IndexResponse indexResponse) {
final boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED;
logger.trace("Created role: [{}]", indexRequest);
clearRoleCache(role.getName(), listener, created);
}
@ -234,7 +238,6 @@ public class NativeRolesStore extends AbstractComponent {
} else {
usageStats.put("size", responses[0].getResponse().getHits().getTotalHits());
}
if (responses[1].isFailure()) {
usageStats.put("fls", false);
} else {
@ -289,7 +292,7 @@ public class NativeRolesStore extends AbstractComponent {
private void executeGetRoleRequest(String role, ActionListener<GetResponse> listener) {
securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN,
client.prepareGet(SecurityIndexManager.SECURITY_INDEX_NAME,
client.prepareGet(SECURITY_INDEX_NAME,
ROLE_DOC_TYPE, getIdForUser(role)).request(),
listener,
client::get));

View File

@ -56,7 +56,7 @@ public abstract class SecurityBaseRestHandler extends BaseRestHandler {
/**
* Check whether the given request is allowed within the current license state and setup,
* and return the name of any unlicensed feature.
* By default this returns an exception is security is not available by the current license or
* By default this returns an exception if security is not available by the current license or
* security is not enabled.
* Sub-classes can override this method if they have additional requirements.
*

View File

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.privilege;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesResponse;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
/**
* Rest action to delete one or more privileges from the security index
*/
public class RestDeletePrivilegesAction extends SecurityBaseRestHandler {
public RestDeletePrivilegesAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
super(settings, licenseState);
controller.registerHandler(DELETE, "/_xpack/security/privilege/{application}/{privilege}", this);
}
@Override
public String getName() {
return "xpack_security_delete_privilege_action";
}
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String application = request.param("application");
final String[] privileges = request.paramAsStringArray("privilege", null);
final String refresh = request.param("refresh");
return channel -> new SecurityClient(client).prepareDeletePrivileges(application, privileges)
.setRefreshPolicy(refresh)
.execute(new RestBuilderListener<DeletePrivilegesResponse>(channel) {
@Override
public RestResponse buildResponse(DeletePrivilegesResponse response, XContentBuilder builder) throws Exception {
builder.startObject();
builder.startObject(application);
for (String privilege : new HashSet<>(Arrays.asList(privileges))) {
builder.field(privilege, Collections.singletonMap("found", response.found().contains(privilege)));
}
builder.endObject();
builder.endObject();
return new BytesRestResponse(response.found().isEmpty() ? RestStatus.NOT_FOUND : RestStatus.OK, builder);
}
});
}
}

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.xpack.security.rest.action.privilege;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.elasticsearch.rest.RestRequest.Method.GET;
/**
* Rest action to retrieve an application privilege from the security index
*/
public class RestGetPrivilegesAction extends SecurityBaseRestHandler {
public RestGetPrivilegesAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
super(settings, licenseState);
controller.registerHandler(GET, "/_xpack/security/privilege/", this);
controller.registerHandler(GET, "/_xpack/security/privilege/{application}", this);
controller.registerHandler(GET, "/_xpack/security/privilege/{application}/{privilege}", this);
}
@Override
public String getName() {
return "xpack_security_get_privileges_action";
}
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String application = request.param("application");
final String[] privileges = request.paramAsStringArray("privilege", Strings.EMPTY_ARRAY);
return channel -> new SecurityClient(client).prepareGetPrivileges(application, privileges)
.execute(new RestBuilderListener<GetPrivilegesResponse>(channel) {
@Override
public RestResponse buildResponse(GetPrivilegesResponse response, XContentBuilder builder) throws Exception {
final Map<String, Set<ApplicationPrivilegeDescriptor>> privsByApp = groupByApplicationName(response.privileges());
builder.startObject();
for (String app : privsByApp.keySet()) {
builder.startObject(app);
for (ApplicationPrivilegeDescriptor privilege : privsByApp.get(app)) {
builder.field(privilege.getName(), privilege);
}
builder.endObject();
}
builder.endObject();
// if the user asked for specific privileges, but none of them were found
// we'll return an empty result and 404 status code
if (privileges.length != 0 && response.privileges().length == 0) {
return new BytesRestResponse(RestStatus.NOT_FOUND, builder);
}
// either the user asked for all privileges, or at least one of the privileges
// was found
return new BytesRestResponse(RestStatus.OK, builder);
}
});
}
static Map<String, Set<ApplicationPrivilegeDescriptor>> groupByApplicationName(ApplicationPrivilegeDescriptor[] privileges) {
return Arrays.stream(privileges).collect(Collectors.toMap(
ApplicationPrivilegeDescriptor::getApplication,
Collections::singleton,
Sets::union
));
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.privilege;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
/**
* Rest endpoint to add one or more {@link ApplicationPrivilege} objects to the security index
*/
public class RestPutPrivilegeAction extends SecurityBaseRestHandler {
public RestPutPrivilegeAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
super(settings, licenseState);
controller.registerHandler(PUT, "/_xpack/security/privilege/{application}/{privilege}", this);
controller.registerHandler(POST, "/_xpack/security/privilege/{application}/{privilege}", this);
}
@Override
public String getName() {
return "xpack_security_put_privilege_action";
}
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String application = request.param("application");
final String privilege = request.param("privilege");
PutPrivilegesRequestBuilder requestBuilder = new SecurityClient(client)
.preparePutPrivilege(application, privilege, request.requiredContent(), request.getXContentType())
.setRefreshPolicy(request.param("refresh"));
return RestPutPrivilegesAction.execute(requestBuilder);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.privilege;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.rest.RestRequest.Method.POST;
/**
* Rest endpoint to add one or more {@link ApplicationPrivilege} objects to the security index
*/
public class RestPutPrivilegesAction extends SecurityBaseRestHandler {
public RestPutPrivilegesAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
super(settings, licenseState);
controller.registerHandler(POST, "/_xpack/security/privilege/", this);
}
@Override
public String getName() {
return "xpack_security_put_privileges_action";
}
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
PutPrivilegesRequestBuilder requestBuilder = new SecurityClient(client)
.preparePutPrivileges(request.requiredContent(), request.getXContentType())
.setRefreshPolicy(request.param("refresh"));
return execute(requestBuilder);
}
static RestChannelConsumer execute(PutPrivilegesRequestBuilder requestBuilder) {
return channel -> requestBuilder.execute(new RestBuilderListener<PutPrivilegesResponse>(channel) {
@Override
public RestResponse buildResponse(PutPrivilegesResponse response, XContentBuilder builder) throws Exception {
final List<ApplicationPrivilegeDescriptor> privileges = requestBuilder.request().getPrivileges();
Map<String, Map<String, Map<String, Boolean>>> result = new HashMap<>();
privileges.stream()
.map(ApplicationPrivilegeDescriptor::getApplication)
.distinct()
.forEach(a -> result.put(a, new HashMap<>()));
privileges.forEach(privilege -> {
String name = privilege.getName();
boolean created = response.created().getOrDefault(privilege.getApplication(), Collections.emptyList()).contains(name);
result.get(privilege.getApplication()).put(name, Collections.singletonMap("created", created));
});
builder.map(result);
return new BytesRestResponse(RestStatus.OK, builder);
}
});
}
}

View File

@ -6,8 +6,11 @@
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
@ -24,6 +27,8 @@ import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST;
@ -54,8 +59,8 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String username = getUsername(request);
HasPrivilegesRequestBuilder requestBuilder = new SecurityClient(client)
.prepareHasPrivileges(username, request.requiredContent(), request.getXContentType());
final Tuple<XContentType, BytesReference> content = request.contentOrSourceParam();
HasPrivilegesRequestBuilder requestBuilder = new SecurityClient(client).prepareHasPrivileges(username, content.v2(), content.v1());
return channel -> requestBuilder.execute(new HasPrivilegesRestResponseBuilder(username, channel));
}
@ -84,10 +89,12 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
builder.field("cluster");
builder.map(response.getClusterPrivileges());
builder.startObject("index");
for (HasPrivilegesResponse.IndexPrivileges index : response.getIndexPrivileges()) {
builder.field(index.getIndex());
builder.map(index.getPrivileges());
appendResources(builder, "index", response.getIndexPrivileges());
builder.startObject("application");
final Map<String, List<HasPrivilegesResponse.ResourcePrivileges>> appPrivileges = response.getApplicationPrivileges();
for (String app : appPrivileges.keySet()) {
appendResources(builder, app, appPrivileges.get(app));
}
builder.endObject();
@ -95,5 +102,15 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
return new BytesRestResponse(RestStatus.OK, builder);
}
private void appendResources(XContentBuilder builder, String field, List<HasPrivilegesResponse.ResourcePrivileges> privileges)
throws IOException {
builder.startObject(field);
for (HasPrivilegesResponse.ResourcePrivileges privilege : privileges) {
builder.field(privilege.getResource());
builder.map(privilege.getPrivileges());
}
builder.endObject();
}
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.integration;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import java.util.Locale;
import static java.util.Collections.singletonMap;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.hamcrest.Matchers.is;
public class KibanaSystemRoleIntegTests extends SecurityIntegTestCase {
protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
@Override
public String configUsers() {
final String usersPasswdHashed = new String(getFastStoredHashAlgoForTests().hash(USERS_PASSWD));
return super.configUsers() +
"kibana_system:" + usersPasswdHashed;
}
@Override
public String configUsersRoles() {
return super.configUsersRoles() +
"kibana_system:kibana_system";
}
public void testCreateIndexDeleteInKibanaIndex() throws Exception {
final String index = randomBoolean()? ".kibana" : ".kibana-" + randomAlphaOfLengthBetween(1, 10).toLowerCase(Locale.ENGLISH);
if (randomBoolean()) {
CreateIndexResponse createIndexResponse = client().filterWithHeader(singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
.admin().indices().prepareCreate(index).get();
assertThat(createIndexResponse.isAcknowledged(), is(true));
}
IndexResponse response = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
.prepareIndex()
.setIndex(index)
.setType("dashboard")
.setSource("foo", "bar")
.setRefreshPolicy(IMMEDIATE)
.get();
assertEquals(DocWriteResponse.Result.CREATED, response.getResult());
DeleteResponse deleteResponse = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_system", USERS_PASSWD)))
.prepareDelete(index, "dashboard", response.getId())
.get();
assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
}
}

View File

@ -20,7 +20,7 @@ import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.NativeRealmIntegTestCase;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import java.util.Locale;
@ -36,7 +36,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
public class KibanaUserRoleIntegTests extends SecurityIntegTestCase {
public class KibanaUserRoleIntegTests extends NativeRealmIntegTestCase {
protected static final SecureString USERS_PASSWD = new SecureString("change_me".toCharArray());
@ -154,25 +154,25 @@ public class KibanaUserRoleIntegTests extends SecurityIntegTestCase {
if (randomBoolean()) {
CreateIndexResponse createIndexResponse = client().filterWithHeader(singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.admin().indices().prepareCreate(index).get();
UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.admin().indices().prepareCreate(index).get();
assertThat(createIndexResponse.isAcknowledged(), is(true));
}
IndexResponse response = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.prepareIndex()
.setIndex(index)
.setType("dashboard")
.setSource("foo", "bar")
.setRefreshPolicy(IMMEDIATE)
.get();
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.prepareIndex()
.setIndex(index)
.setType("dashboard")
.setSource("foo", "bar")
.setRefreshPolicy(IMMEDIATE)
.get();
assertEquals(DocWriteResponse.Result.CREATED, response.getResult());
DeleteResponse deleteResponse = client()
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.prepareDelete(index, "dashboard", response.getId())
.get();
.filterWithHeader(singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue("kibana_user", USERS_PASSWD)))
.prepareDelete(index, "dashboard", response.getId())
.get();
assertEquals(DocWriteResponse.Result.DELETED, deleteResponse.getResult());
}

View File

@ -0,0 +1,127 @@
/*
* 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.core.security.action.privilege;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
public class PutPrivilegesRequestBuilderTests extends ESTestCase {
public void testBuildRequestWithMultipleElements() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
builder.source(new BytesArray("{ "
+ "\"foo\":{"
+ " \"read\":{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] },"
+ " \"write\":{ \"application\":\"foo\", \"name\":\"write\", \"actions\":[ \"data:/write/*\", \"admin:*\" ] },"
+ " \"all\":{ \"application\":\"foo\", \"name\":\"all\", \"actions\":[ \"*\" ] }"
+ " }, "
+ "\"bar\":{"
+ " \"read\":{ \"application\":\"bar\", \"name\":\"read\", \"actions\":[ \"read/*\" ] },"
+ " \"write\":{ \"application\":\"bar\", \"name\":\"write\", \"actions\":[ \"write/*\" ] },"
+ " \"all\":{ \"application\":\"bar\", \"name\":\"all\", \"actions\":[ \"*\" ] }"
+ " } "
+ "}"), XContentType.JSON);
final List<ApplicationPrivilegeDescriptor> privileges = builder.request().getPrivileges();
assertThat(privileges, iterableWithSize(6));
assertThat(privileges, contains(
descriptor("foo", "read", "data:/read/*", "admin:/read/*"),
descriptor("foo", "write", "data:/write/*", "admin:*"),
descriptor("foo", "all", "*"),
descriptor("bar", "read", "read/*"),
descriptor("bar", "write", "write/*"),
descriptor("bar", "all", "*")
));
}
private ApplicationPrivilegeDescriptor descriptor(String app, String name, String ... actions) {
return new ApplicationPrivilegeDescriptor(app, name, Sets.newHashSet(actions), Collections.emptyMap());
}
public void testBuildRequestFromJsonObject() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
builder.source("foo", "read", new BytesArray(
"{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] }"
), XContentType.JSON);
final List<ApplicationPrivilegeDescriptor> privileges = builder.request().getPrivileges();
assertThat(privileges, iterableWithSize(1));
assertThat(privileges, contains(descriptor("foo", "read", "data:/read/*", "admin:/read/*")));
}
public void testPrivilegeNameValidationOfSingleElement() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
builder.source("foo", "write", new BytesArray(
"{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] }"
), XContentType.JSON));
assertThat(exception.getMessage(), containsString("write"));
assertThat(exception.getMessage(), containsString("read"));
}
public void testApplicationNameValidationOfSingleElement() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
builder.source("bar", "read", new BytesArray(
"{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] }"
), XContentType.JSON));
assertThat(exception.getMessage(), containsString("foo"));
assertThat(exception.getMessage(), containsString("bar"));
}
public void testPrivilegeNameValidationOfMultipleElement() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
builder.source(new BytesArray("{ \"foo\":{"
+ "\"write\":{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[\"data:/read/*\",\"admin:/read/*\"] },"
+ "\"all\":{ \"application\":\"foo\", \"name\":\"all\", \"actions\":[ \"/*\" ] }"
+ "} }"), XContentType.JSON)
);
assertThat(exception.getMessage(), containsString("write"));
assertThat(exception.getMessage(), containsString("read"));
}
public void testApplicationNameValidationOfMultipleElement() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
builder.source(new BytesArray("{ \"bar\":{"
+ "\"read\":{ \"application\":\"foo\", \"name\":\"read\", \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] },"
+ "\"write\":{ \"application\":\"foo\", \"name\":\"write\", \"actions\":[ \"data:/write/*\", \"admin:/*\" ] },"
+ "\"all\":{ \"application\":\"foo\", \"name\":\"all\", \"actions\":[ \"/*\" ] }"
+ "} }"), XContentType.JSON)
);
assertThat(exception.getMessage(), containsString("bar"));
assertThat(exception.getMessage(), containsString("foo"));
}
public void testInferApplicationNameAndPrivilegeName() throws Exception {
final PutPrivilegesRequestBuilder builder = new PutPrivilegesRequestBuilder(null, PutPrivilegesAction.INSTANCE);
builder.source(new BytesArray("{ \"foo\":{"
+ "\"read\":{ \"actions\":[ \"data:/read/*\", \"admin:/read/*\" ] },"
+ "\"write\":{ \"actions\":[ \"data:/write/*\", \"admin:/*\" ] },"
+ "\"all\":{ \"actions\":[ \"*\" ] }"
+ "} }"), XContentType.JSON);
assertThat(builder.request().getPrivileges(), iterableWithSize(3));
for (ApplicationPrivilegeDescriptor p : builder.request().getPrivileges()) {
assertThat(p.getApplication(), equalTo("foo"));
assertThat(p.getName(), notNullValue());
}
assertThat(builder.request().getPrivileges().get(0).getName(), equalTo("read"));
assertThat(builder.request().getPrivileges().get(1).getName(), equalTo("write"));
assertThat(builder.request().getPrivileges().get(2).getName(), equalTo("all"));
}
}

View File

@ -114,6 +114,6 @@ public class HasPrivilegesRequestBuilderTests extends ESTestCase {
final ElasticsearchParseException parseException = expectThrows(ElasticsearchParseException.class,
() -> builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON)
);
assertThat(parseException.getMessage(), containsString("[index] and [cluster] are both missing"));
assertThat(parseException.getMessage(), containsString("[cluster,index,applications] are missing"));
}
}

View File

@ -15,43 +15,60 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.mock.orig.Mockito;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse.IndexPrivileges;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse.ResourcePrivileges;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
import org.hamcrest.Matchers;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import static java.util.Collections.emptyMap;
import static org.elasticsearch.common.util.set.Sets.newHashSet;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@TestLogging("org.elasticsearch.xpack.security.action.user.TransportHasPrivilegesAction:TRACE," +
"org.elasticsearch.xpack.core.security.authz.permission.ApplicationPermission:DEBUG")
public class TransportHasPrivilegesActionTests extends ESTestCase {
private User user;
private Role role;
private TransportHasPrivilegesAction action;
private List<ApplicationPrivilegeDescriptor> applicationPrivileges;
@Before
public void setup() {
@ -75,7 +92,19 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
return null;
}).when(authorizationService).roles(eq(user), any(ActionListener.class));
action = new TransportHasPrivilegesAction(settings, threadPool, transportService, mock(ActionFilters.class), authorizationService);
applicationPrivileges = new ArrayList<>();
NativePrivilegeStore privilegeStore = mock(NativePrivilegeStore.class);
Mockito.doAnswer(inv -> {
assertThat(inv.getArguments(), arrayWithSize(3));
ActionListener<List<ApplicationPrivilegeDescriptor>> listener
= (ActionListener<List<ApplicationPrivilegeDescriptor>>) inv.getArguments()[2];
logger.info("Privileges for ({}) are {}", Arrays.toString(inv.getArguments()), applicationPrivileges);
listener.onResponse(applicationPrivileges);
return null;
}).when(privilegeStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class));
action = new TransportHasPrivilegesAction(settings, threadPool, transportService, mock(ActionFilters.class), authorizationService,
privilegeStore);
}
/**
@ -92,6 +121,7 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
.indices("academy")
.privileges(DeleteAction.NAME, IndexAction.NAME)
.build());
request.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(mock(Task.class), request, future);
@ -103,8 +133,8 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
assertThat(response.getClusterPrivileges().get(ClusterHealthAction.NAME), equalTo(true));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(1));
final IndexPrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getIndex(), equalTo("academy"));
final ResourcePrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getResource(), equalTo("academy"));
assertThat(result.getPrivileges().size(), equalTo(2));
assertThat(result.getPrivileges().get(DeleteAction.NAME), equalTo(true));
assertThat(result.getPrivileges().get(IndexAction.NAME), equalTo(true));
@ -128,6 +158,7 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
.indices("academy", "initiative", "school")
.privileges("delete", "index", "manage")
.build());
request.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(mock(Task.class), request, future);
@ -139,23 +170,23 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
assertThat(response.getClusterPrivileges().get("manage"), equalTo(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(3));
final IndexPrivileges academy = response.getIndexPrivileges().get(0);
final IndexPrivileges initiative = response.getIndexPrivileges().get(1);
final IndexPrivileges school = response.getIndexPrivileges().get(2);
final ResourcePrivileges academy = response.getIndexPrivileges().get(0);
final ResourcePrivileges initiative = response.getIndexPrivileges().get(1);
final ResourcePrivileges school = response.getIndexPrivileges().get(2);
assertThat(academy.getIndex(), equalTo("academy"));
assertThat(academy.getResource(), equalTo("academy"));
assertThat(academy.getPrivileges().size(), equalTo(3));
assertThat(academy.getPrivileges().get("index"), equalTo(true)); // explicit
assertThat(academy.getPrivileges().get("delete"), equalTo(false));
assertThat(academy.getPrivileges().get("manage"), equalTo(false));
assertThat(initiative.getIndex(), equalTo("initiative"));
assertThat(initiative.getResource(), equalTo("initiative"));
assertThat(initiative.getPrivileges().size(), equalTo(3));
assertThat(initiative.getPrivileges().get("index"), equalTo(true)); // implied by write
assertThat(initiative.getPrivileges().get("delete"), equalTo(true)); // implied by write
assertThat(initiative.getPrivileges().get("manage"), equalTo(false));
assertThat(school.getIndex(), equalTo("school"));
assertThat(school.getResource(), equalTo("school"));
assertThat(school.getPrivileges().size(), equalTo(3));
assertThat(school.getPrivileges().get("index"), equalTo(false));
assertThat(school.getPrivileges().get("delete"), equalTo(false));
@ -177,8 +208,8 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
.build(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(1));
final IndexPrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getIndex(), equalTo("academy"));
final ResourcePrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getResource(), equalTo("academy"));
assertThat(result.getPrivileges().size(), equalTo(2));
assertThat(result.getPrivileges().get("read"), equalTo(false));
assertThat(result.getPrivileges().get("write"), equalTo(false));
@ -191,10 +222,20 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
* <em>does the user have ___ privilege on a wildcard that covers (is a superset of) this pattern?</em>
*/
public void testWildcardHandling() throws Exception {
final ApplicationPrivilege kibanaRead = defineApplicationPrivilege("kibana", "read",
"data:read/*", "action:login", "action:view/dashboard");
final ApplicationPrivilege kibanaWrite = defineApplicationPrivilege("kibana", "write",
"data:write/*", "action:login", "action:view/dashboard");
final ApplicationPrivilege kibanaAdmin = defineApplicationPrivilege("kibana", "admin",
"action:login", "action:manage/*");
final ApplicationPrivilege kibanaViewSpace = defineApplicationPrivilege("kibana", "view-space",
"action:login", "space:view/*");
role = Role.builder("test3")
.add(IndexPrivilege.ALL, "logstash-*", "foo?")
.add(IndexPrivilege.READ, "abc*")
.add(IndexPrivilege.WRITE, "*xyz")
.addApplicationPrivilege(kibanaRead, Collections.singleton("*"))
.addApplicationPrivilege(kibanaViewSpace, newHashSet("space/engineering/*", "space/builds"))
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
@ -230,6 +271,20 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
.privileges("read", "write", "manage") // read = No, write = Yes (WRITE, "*xyz"), manage = No
.build()
);
request.applicationPrivileges(
RoleDescriptor.ApplicationResourcePrivileges.builder()
.resources("*")
.application("kibana")
.privileges(Sets.union(kibanaRead.name(), kibanaWrite.name())) // read = Yes, write = No
.build(),
RoleDescriptor.ApplicationResourcePrivileges.builder()
.resources("space/engineering/project-*", "space/*") // project-* = Yes, space/* = Not
.application("kibana")
.privileges("space:view/dashboard")
.build()
);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(mock(Task.class), request, future);
@ -238,15 +293,28 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(8));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
new IndexPrivileges("logstash-2016-*", Collections.singletonMap("write", true)),
new IndexPrivileges("logstash-*", Collections.singletonMap("read", true)),
new IndexPrivileges("log*", Collections.singletonMap("manage", false)),
new IndexPrivileges("foo?", Collections.singletonMap("read", true)),
new IndexPrivileges("foo*", Collections.singletonMap("read", false)),
new IndexPrivileges("abcd*", mapBuilder().put("read", true).put("write", false).map()),
new IndexPrivileges("abc*xyz", mapBuilder().put("read", true).put("write", true).put("manage", false).map()),
new IndexPrivileges("a*xyz", mapBuilder().put("read", false).put("write", true).put("manage", false).map())
new ResourcePrivileges("logstash-2016-*", Collections.singletonMap("write", true)),
new ResourcePrivileges("logstash-*", Collections.singletonMap("read", true)),
new ResourcePrivileges("log*", Collections.singletonMap("manage", false)),
new ResourcePrivileges("foo?", Collections.singletonMap("read", true)),
new ResourcePrivileges("foo*", Collections.singletonMap("read", false)),
new ResourcePrivileges("abcd*", mapBuilder().put("read", true).put("write", false).map()),
new ResourcePrivileges("abc*xyz", mapBuilder().put("read", true).put("write", true).put("manage", false).map()),
new ResourcePrivileges("a*xyz", mapBuilder().put("read", false).put("write", true).put("manage", false).map())
));
assertThat(response.getApplicationPrivileges().entrySet(), Matchers.iterableWithSize(1));
final List<ResourcePrivileges> kibanaPrivileges = response.getApplicationPrivileges().get("kibana");
assertThat(kibanaPrivileges, Matchers.iterableWithSize(3));
assertThat(Strings.collectionToCommaDelimitedString(kibanaPrivileges), kibanaPrivileges, containsInAnyOrder(
new ResourcePrivileges("*", mapBuilder().put("read", true).put("write", false).map()),
new ResourcePrivileges("space/engineering/project-*", Collections.singletonMap("space:view/dashboard", true)),
new ResourcePrivileges("space/*", Collections.singletonMap("space:view/dashboard", false))
));
}
private ApplicationPrivilege defineApplicationPrivilege(String app, String name, String ... actions) {
this.applicationPrivileges.add(new ApplicationPrivilegeDescriptor(app, name, newHashSet(actions), emptyMap()));
return new ApplicationPrivilege(app, name, actions);
}
public void testCheckingIndexPermissionsDefinedOnDifferentPatterns() throws Exception {
@ -262,27 +330,152 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
new IndexPrivileges("apache-2016-12",
new ResourcePrivileges("apache-2016-12",
MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("delete", true).map()),
new IndexPrivileges("apache-2017-01",
new ResourcePrivileges("apache-2017-01",
MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("delete", false).map()
)
));
}
public void testCheckingApplicationPrivilegesOnDifferentApplicationsAndResources() throws Exception {
final ApplicationPrivilege app1Read = defineApplicationPrivilege("app1", "read", "data:read/*");
final ApplicationPrivilege app1Write = defineApplicationPrivilege("app1", "write", "data:write/*");
final ApplicationPrivilege app1All = defineApplicationPrivilege("app1", "all", "*");
final ApplicationPrivilege app2Read = defineApplicationPrivilege("app2", "read", "data:read/*");
final ApplicationPrivilege app2Write = defineApplicationPrivilege("app2", "write", "data:write/*");
final ApplicationPrivilege app2All = defineApplicationPrivilege("app2", "all", "*");
role = Role.builder("test-role")
.addApplicationPrivilege(app1Read, Collections.singleton("foo/*"))
.addApplicationPrivilege(app1All, Collections.singleton("foo/bar/baz"))
.addApplicationPrivilege(app2Read, Collections.singleton("foo/bar/*"))
.addApplicationPrivilege(app2Write, Collections.singleton("*/bar/*"))
.build();
final HasPrivilegesResponse response = hasPrivileges(new RoleDescriptor.IndicesPrivileges[0],
new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app1")
.resources("foo/1", "foo/bar/2", "foo/bar/baz", "baz/bar/foo")
.privileges("read", "write", "all")
.build(),
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app2")
.resources("foo/1", "foo/bar/2", "foo/bar/baz", "baz/bar/foo")
.privileges("read", "write", "all")
.build()
}, Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.emptyIterable());
assertThat(response.getApplicationPrivileges().entrySet(), Matchers.iterableWithSize(2));
final List<ResourcePrivileges> app1 = response.getApplicationPrivileges().get("app1");
assertThat(app1, Matchers.iterableWithSize(4));
assertThat(Strings.collectionToCommaDelimitedString(app1), app1, containsInAnyOrder(
new ResourcePrivileges("foo/1", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", true).put("write", false).put("all", false).map()),
new ResourcePrivileges("foo/bar/2", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", true).put("write", false).put("all", false).map()),
new ResourcePrivileges("foo/bar/baz", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", true).put("write", true).put("all", true).map()),
new ResourcePrivileges("baz/bar/foo", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", false).put("write", false).put("all", false).map())
));
final List<ResourcePrivileges> app2 = response.getApplicationPrivileges().get("app2");
assertThat(app2, Matchers.iterableWithSize(4));
assertThat(Strings.collectionToCommaDelimitedString(app2), app2, containsInAnyOrder(
new ResourcePrivileges("foo/1", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", false).put("write", false).put("all", false).map()),
new ResourcePrivileges("foo/bar/2", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", true).put("write", true).put("all", false).map()),
new ResourcePrivileges("foo/bar/baz", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", true).put("write", true).put("all", false).map()),
new ResourcePrivileges("baz/bar/foo", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("read", false).put("write", true).put("all", false).map())
));
}
public void testCheckingApplicationPrivilegesWithComplexNames() throws Exception {
final String appName = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(3, 10);
final String action1 = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(2, 5);
final String action2 = randomAlphaOfLength(1).toLowerCase(Locale.ROOT) + randomAlphaOfLengthBetween(6, 9);
final ApplicationPrivilege priv1 = defineApplicationPrivilege(appName, action1, "DATA:read/*", "ACTION:" + action1);
final ApplicationPrivilege priv2 = defineApplicationPrivilege(appName, action2, "DATA:read/*", "ACTION:" + action2);
role = Role.builder("test-write")
.addApplicationPrivilege(priv1, Collections.singleton("user/*/name"))
.build();
final HasPrivilegesResponse response = hasPrivileges(
new RoleDescriptor.IndicesPrivileges[0],
new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application(appName)
.resources("user/hawkeye/name")
.privileges("DATA:read/user/*", "ACTION:" + action1, "ACTION:" + action2, action1, action2)
.build()
},
"monitor");
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getApplicationPrivileges().keySet(), containsInAnyOrder(appName));
assertThat(response.getApplicationPrivileges().get(appName), iterableWithSize(1));
assertThat(response.getApplicationPrivileges().get(appName), containsInAnyOrder(
new ResourcePrivileges("user/hawkeye/name", MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("DATA:read/user/*", true)
.put("ACTION:" + action1, true)
.put("ACTION:" + action2, false)
.put(action1, true)
.put(action2, false)
.map())
));
}
public void testIsCompleteMatch() throws Exception {
final ApplicationPrivilege kibanaRead = defineApplicationPrivilege("kibana", "read", "data:read/*");
final ApplicationPrivilege kibanaWrite = defineApplicationPrivilege("kibana", "write", "data:write/*");
role = Role.builder("test-write")
.cluster(ClusterPrivilege.MONITOR)
.add(IndexPrivilege.READ, "read-*")
.add(IndexPrivilege.ALL, "all-*")
.addApplicationPrivilege(kibanaRead, Collections.singleton("*"))
.build();
assertThat(hasPrivileges(indexPrivileges("read", "read-123", "read-456", "all-999"), "monitor").isCompleteMatch(), is(true));
assertThat(hasPrivileges(indexPrivileges("read", "read-123", "read-456", "all-999"), "manage").isCompleteMatch(), is(false));
assertThat(hasPrivileges(indexPrivileges("write", "read-123", "read-456", "all-999"), "monitor").isCompleteMatch(), is(false));
assertThat(hasPrivileges(indexPrivileges("write", "read-123", "read-456", "all-999"), "manage").isCompleteMatch(), is(false));
assertThat(hasPrivileges(
new RoleDescriptor.IndicesPrivileges[]{
RoleDescriptor.IndicesPrivileges.builder()
.indices("read-a")
.privileges("read")
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("all-b")
.privileges("read", "write")
.build()
},
new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana")
.resources("*")
.privileges("read")
.build()
},
"monitor").isCompleteMatch(), is(true));
assertThat(hasPrivileges(
new RoleDescriptor.IndicesPrivileges[]{indexPrivileges("read", "read-123", "read-456", "all-999")},
new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana").resources("*").privileges("read").build(),
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana").resources("*").privileges("write").build()
},
"monitor").isCompleteMatch(), is(false));
}
private RoleDescriptor.IndicesPrivileges indexPrivileges(String priv, String... indices) {
@ -294,10 +487,21 @@ public class TransportHasPrivilegesActionTests extends ESTestCase {
private HasPrivilegesResponse hasPrivileges(RoleDescriptor.IndicesPrivileges indicesPrivileges, String... clusterPrivileges)
throws Exception {
return hasPrivileges(
new RoleDescriptor.IndicesPrivileges[]{indicesPrivileges},
new RoleDescriptor.ApplicationResourcePrivileges[0],
clusterPrivileges
);
}
private HasPrivilegesResponse hasPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges,
RoleDescriptor.ApplicationResourcePrivileges[] appPrivileges,
String... clusterPrivileges) throws Exception {
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges(clusterPrivileges);
request.indexPrivileges(indicesPrivileges);
request.applicationPrivileges(appPrivileges);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(mock(Task.class), request, future);
final HasPrivilegesResponse response = future.get();

View File

@ -79,7 +79,7 @@ public class AuditTrailTests extends SecurityIntegTestCase {
public String configUsersRoles() {
return super.configUsersRoles()
+ ROLE_CAN_RUN_AS + ":" + AUTHENTICATE_USER + "\n"
+ "kibana_user:" + EXECUTE_USER;
+ "monitoring_user:" + EXECUTE_USER;
}
@Override

View File

@ -47,7 +47,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase {
@Override
protected Environment createEnv(Map<String, String> settings) throws UserException {
Settings.Builder builder = Settings.builder();
settings.forEach((k,v) -> builder.put(k, v));
settings.forEach((k, v) -> builder.put(k, v));
return TestEnvironment.newEnvironment(builder.build());
}
@ -75,9 +75,11 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase {
String[] runAs = Strings.EMPTY_ARRAY;
RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs);
assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd),
equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," +
"\"privileges\":[\"all\"],\"field_security\":{\"grant\":[\"body\"]}}]," +
"\"run_as\":[],\"metadata\":{},\"type\":\"role\"}"));
equalTo("{\"cluster\":[]," +
"\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," +
"\"privileges\":[\"all\"],\"field_security\":{\"grant\":[\"body\"]}}]," +
"\"applications\":[]," +
"\"run_as\":[],\"metadata\":{},\"type\":\"role\"}"));
}
public void testTerminalLogger() throws Exception {

View File

@ -22,6 +22,7 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.NativeRealmIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.test.SecuritySettingsSourceField;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import org.elasticsearch.xpack.core.action.XPackUsageRequestBuilder;
import org.elasticsearch.xpack.core.action.XPackUsageResponse;
@ -66,6 +67,7 @@ import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
/**
* Tests for the NativeUsersStore and NativeRolesStore
@ -358,10 +360,11 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
assertThat(e.status(), is(RestStatus.FORBIDDEN));
}
} else {
final TransportRequest request = mock(TransportRequest.class);
GetRolesResponse getRolesResponse = c.prepareGetRoles().names("test_role").get();
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
assertTrue("any cluster permission should be authorized",
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo"));
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request));
c.preparePutRole("test_role")
.cluster("none")
@ -372,7 +375,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
assertFalse("no cluster permission should be authorized",
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar"));
Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request));
}
}

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authz;
import org.elasticsearch.Version;
import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
@ -24,6 +25,7 @@ import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.containsInAnyOrder;
@ -57,8 +59,10 @@ public class AuthorizedIndicesTests extends ESTestCase {
.putAlias(new AliasMetaData.Builder("ba").build())
.build(), true)
.build();
Role roles = CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(aStarRole, bRole),
new FieldPermissionsCache(Settings.EMPTY));
final PlainActionFuture<Role> future = new PlainActionFuture<>();
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
CompositeRolesStore.buildRoleFromDescriptors(descriptors, new FieldPermissionsCache(Settings.EMPTY), null, future);
Role roles = future.actionGet();
AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, roles, SearchAction.NAME, metaData);
List<String> list = authorizedIndices.get();
assertThat(list, containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab"));

View File

@ -174,8 +174,9 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
if (roleDescriptors.isEmpty()) {
callback.onResponse(Role.EMPTY);
} else {
callback.onResponse(
CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache));
CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, null,
ActionListener.wrap(r -> callback.onResponse(r), callback::onFailure)
);
}
return Void.TYPE;
}).when(rolesStore).roles(any(Set.class), any(FieldPermissionsCache.class), any(ActionListener.class));

View File

@ -10,18 +10,34 @@ 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.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.hamcrest.Matchers;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyArray;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
public class RoleDescriptorTests extends ESTestCase {
@ -45,9 +61,26 @@ public class RoleDescriptorTests extends ESTestCase {
.query("{\"query\": {\"match_all\": {}}}")
.build()
};
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], field_security=[grant=[body,title], except=null], query={\"query\": {\"match_all\": {}}}],]" +
final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("my_app")
.privileges("read", "write")
.resources("*")
.build()
};
final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{
new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02")))
};
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, applicationPrivileges,
conditionalClusterPrivileges, new String[] { "sudo" }, Collections.emptyMap(), Collections.emptyMap());
assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none]" +
", global=[{APPLICATION:manage:applications=app01,app02}]" +
", indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], privileges=[read]" +
", field_security=[grant=[body,title], except=null], query={\"query\": {\"match_all\": {}}}],]" +
", applicationPrivileges=[ApplicationResourcePrivileges[application=my_app, privileges=[read,write], resources=[*]],]" +
", runAs=[sudo], metadata=[{}]]"));
}
@ -60,11 +93,23 @@ public class RoleDescriptorTests extends ESTestCase {
.query("{\"query\": {\"match_all\": {}}}")
.build()
};
final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("my_app")
.privileges("read", "write")
.resources("*")
.build()
};
final ConditionalClusterPrivilege[] conditionalClusterPrivileges = {
new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02")))
};
Map<String, Object> metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null;
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata);
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, applicationPrivileges,
conditionalClusterPrivileges, new String[]{ "sudo" }, metadata, Collections.emptyMap());
XContentBuilder builder = descriptor.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS);
RoleDescriptor parsed = RoleDescriptor.parse("test", BytesReference.bytes(builder), false, XContentType.JSON);
assertEquals(parsed, descriptor);
assertThat(parsed, equalTo(descriptor));
}
public void testParse() throws Exception {
@ -113,6 +158,53 @@ public class RoleDescriptorTests extends ESTestCase {
assertNotNull(rd.getMetadata());
assertThat(rd.getMetadata().size(), is(1));
assertThat(rd.getMetadata().get("foo"), is("bar"));
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]," +
" \"index\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": [\"p1\", \"p2\"]}]," +
" \"applications\": [" +
" {\"resources\": [\"object-123\",\"object-456\"], \"privileges\":[\"read\", \"delete\"], \"application\":\"app1\"}," +
" {\"resources\": [\"*\"], \"privileges\":[\"admin\"], \"application\":\"app2\" }" +
" ]," +
" \"global\": { \"application\": { \"manage\": { \"applications\" : [ \"kibana\", \"logstash\" ] } } }" +
"}";
rd = RoleDescriptor.parse("test", new BytesArray(q), false, XContentType.JSON);
assertThat(rd.getName(), equalTo("test"));
assertThat(rd.getClusterPrivileges(), arrayContaining("a", "b"));
assertThat(rd.getIndicesPrivileges().length, equalTo(1));
assertThat(rd.getIndicesPrivileges()[0].getIndices(), arrayContaining("idx1", "idx2"));
assertThat(rd.getRunAs(), arrayContaining("m", "n"));
assertThat(rd.getIndicesPrivileges()[0].getQuery(), nullValue());
assertThat(rd.getApplicationPrivileges().length, equalTo(2));
assertThat(rd.getApplicationPrivileges()[0].getResources(), arrayContaining("object-123", "object-456"));
assertThat(rd.getApplicationPrivileges()[0].getPrivileges(), arrayContaining("read", "delete"));
assertThat(rd.getApplicationPrivileges()[0].getApplication(), equalTo("app1"));
assertThat(rd.getApplicationPrivileges()[1].getResources(), arrayContaining("*"));
assertThat(rd.getApplicationPrivileges()[1].getPrivileges(), arrayContaining("admin"));
assertThat(rd.getApplicationPrivileges()[1].getApplication(), equalTo("app2"));
assertThat(rd.getConditionalClusterPrivileges(), Matchers.arrayWithSize(1));
final ConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0];
assertThat(conditionalPrivilege.getCategory(), equalTo(ConditionalClusterPrivilege.Category.APPLICATION));
assertThat(conditionalPrivilege, instanceOf(ConditionalClusterPrivileges.ManageApplicationPrivileges.class));
assertThat(((ConditionalClusterPrivileges.ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(),
containsInAnyOrder("kibana", "logstash"));
q = "{\"applications\": [{\"application\": \"myapp\", \"resources\": [\"*\"], \"privileges\": [\"login\" ]}] }";
rd = RoleDescriptor.parse("test", new BytesArray(q), false, XContentType.JSON);
assertThat(rd.getName(), equalTo("test"));
assertThat(rd.getClusterPrivileges(), emptyArray());
assertThat(rd.getIndicesPrivileges(), emptyArray());
assertThat(rd.getApplicationPrivileges().length, equalTo(1));
assertThat(rd.getApplicationPrivileges()[0].getResources(), arrayContaining("*"));
assertThat(rd.getApplicationPrivileges()[0].getPrivileges(), arrayContaining("login"));
assertThat(rd.getApplicationPrivileges()[0].getApplication(), equalTo("myapp"));
assertThat(rd.getConditionalClusterPrivileges(), Matchers.arrayWithSize(0));
final String badJson
= "{\"applications\":[{\"not_supported\": true, \"resources\": [\"*\"], \"privileges\": [\"my-app:login\" ]}] }";
final IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> RoleDescriptor.parse("test", new BytesArray(badJson), false, XContentType.JSON));
assertThat(ex.getMessage(), containsString("not_supported"));
}
public void testSerialization() throws Exception {
@ -125,11 +217,24 @@ public class RoleDescriptorTests extends ESTestCase {
.query("{\"query\": {\"match_all\": {}}}")
.build()
};
final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("my_app")
.privileges("read", "write")
.resources("*")
.build()
};
final ConditionalClusterPrivilege[] conditionalClusterPrivileges = {
new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02")))
};
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);
final RoleDescriptor descriptor = new RoleDescriptor("test", new String[]{"all", "none"}, groups, applicationPrivileges,
conditionalClusterPrivileges, new String[] { "sudo" }, metadata, null);
RoleDescriptor.writeTo(descriptor, output);
StreamInput streamInput = ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes()));
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
StreamInput streamInput = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes())),
registry);
final RoleDescriptor serialized = RoleDescriptor.readFrom(streamInput);
assertEquals(descriptor, serialized);
}

View File

@ -7,6 +7,10 @@ package org.elasticsearch.xpack.security.authz.store;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction;
import org.elasticsearch.action.admin.cluster.state.ClusterStateAction;
import org.elasticsearch.action.get.GetAction;
import org.elasticsearch.action.index.IndexAction;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -22,18 +26,26 @@ import org.elasticsearch.license.License.OperationMode;
import org.elasticsearch.license.TestUtils.UpdatableLicenseState;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
@ -42,6 +54,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.elasticsearch.mock.orig.Mockito.times;
import static org.elasticsearch.mock.orig.Mockito.verifyNoMoreInteractions;
@ -103,7 +116,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, mock(NativeRolesStore.class),
mock(ReservedRolesStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY), licenseState);
mock(ReservedRolesStore.class), mock(NativePrivilegeStore.class), Collections.emptyList(),
new ThreadContext(Settings.EMPTY), licenseState);
FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
@ -163,7 +177,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, mock(NativeRolesStore.class),
mock(ReservedRolesStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY), licenseState);
mock(ReservedRolesStore.class), mock(NativePrivilegeStore.class), Collections.emptyList(),
new ThreadContext(Settings.EMPTY), licenseState);
FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
@ -196,7 +211,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
final CompositeRolesStore compositeRolesStore =
new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
new XPackLicenseState(SECURITY_ENABLED_SETTINGS));
verify(fileRolesStore).addListener(any(Runnable.class)); // adds a listener in ctor
@ -274,8 +289,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
final CompositeRolesStore compositeRolesStore =
new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
Arrays.asList(inMemoryProvider1, inMemoryProvider2), new ThreadContext(SECURITY_ENABLED_SETTINGS),
new XPackLicenseState(SECURITY_ENABLED_SETTINGS));
mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, inMemoryProvider2),
new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS));
final Set<String> roleNames = Sets.newHashSet("roleA", "roleB", "unknown");
PlainActionFuture<Role> future = new PlainActionFuture<>();
@ -328,7 +343,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
.build()
}, null);
FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY);
Role role = CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(flsRole, addsL1Fields), cache);
PlainActionFuture<Role> future = new PlainActionFuture<>();
CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(flsRole, addsL1Fields), cache, null, future);
Role role = future.actionGet();
MetaData metaData = MetaData.builder()
.put(new IndexMetaData.Builder("test")
@ -343,6 +360,111 @@ public class CompositeRolesStoreTests extends ESTestCase {
assertTrue(acls.get("test").getFieldPermissions().grantsAccessTo("L3.foo"));
}
public void testMergingBasicRoles() {
final TransportRequest request1 = mock(TransportRequest.class);
final TransportRequest request2 = mock(TransportRequest.class);
final TransportRequest request3 = mock(TransportRequest.class);
ConditionalClusterPrivilege ccp1 = mock(ConditionalClusterPrivilege.class);
when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY);
when(ccp1.getRequestPredicate()).thenReturn(req -> req == request1);
RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{
IndicesPrivileges.builder()
.indices("abc-*", "xyz-*")
.privileges("read")
.build(),
IndicesPrivileges.builder()
.indices("ind-1-*")
.privileges("all")
.build(),
}, new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app1")
.resources("user/*")
.privileges("read", "write")
.build(),
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app1")
.resources("settings/*")
.privileges("read")
.build()
}, new ConditionalClusterPrivilege[] { ccp1 },
new String[]{"app-user-1"}, null, null);
ConditionalClusterPrivilege ccp2 = mock(ConditionalClusterPrivilege.class);
when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY);
when(ccp2.getRequestPredicate()).thenReturn(req -> req == request2);
RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{
IndicesPrivileges.builder()
.indices("abc-*", "ind-2-*")
.privileges("all")
.build()
}, new RoleDescriptor.ApplicationResourcePrivileges[]{
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app2a")
.resources("*")
.privileges("all")
.build(),
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("app2b")
.resources("*")
.privileges("read")
.build()
}, new ConditionalClusterPrivilege[] { ccp2 },
new String[]{"app-user-2"}, null, null);
FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY);
PlainActionFuture<Role> future = new PlainActionFuture<>();
final NativePrivilegeStore privilegeStore = mock(NativePrivilegeStore.class);
doAnswer(inv -> {
assertTrue(inv.getArguments().length == 3);
ActionListener<Collection<ApplicationPrivilegeDescriptor>> listener
= (ActionListener<Collection<ApplicationPrivilegeDescriptor>>) inv.getArguments()[2];
Set<ApplicationPrivilegeDescriptor> set = new HashSet<>();
Arrays.asList("app1", "app2a", "app2b").forEach(
app -> Arrays.asList("read", "write", "all").forEach(
perm -> set.add(
new ApplicationPrivilegeDescriptor(app, perm, Collections.emptySet(), Collections.emptyMap())
)));
listener.onResponse(set);
return null;
}).when(privilegeStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class));
CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore, future);
Role role = future.actionGet();
assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true));
assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true));
assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3)), equalTo(false));
assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2)), equalTo(true));
assertThat(role.cluster().check(PutUserAction.NAME, request3), equalTo(false));
final Predicate<String> allowedRead = role.indices().allowedIndicesMatcher(GetAction.NAME);
assertThat(allowedRead.test("abc-123"), equalTo(true));
assertThat(allowedRead.test("xyz-000"), equalTo(true));
assertThat(allowedRead.test("ind-1-a"), equalTo(true));
assertThat(allowedRead.test("ind-2-a"), equalTo(true));
assertThat(allowedRead.test("foo"), equalTo(false));
assertThat(allowedRead.test("abc"), equalTo(false));
assertThat(allowedRead.test("xyz"), equalTo(false));
assertThat(allowedRead.test("ind-3-a"), equalTo(false));
final Predicate<String> allowedWrite = role.indices().allowedIndicesMatcher(IndexAction.NAME);
assertThat(allowedWrite.test("abc-123"), equalTo(true));
assertThat(allowedWrite.test("xyz-000"), equalTo(false));
assertThat(allowedWrite.test("ind-1-a"), equalTo(true));
assertThat(allowedWrite.test("ind-2-a"), equalTo(true));
assertThat(allowedWrite.test("foo"), equalTo(false));
assertThat(allowedWrite.test("abc"), equalTo(false));
assertThat(allowedWrite.test("xyz"), equalTo(false));
assertThat(allowedWrite.test("ind-3-a"), equalTo(false));
role.application().grants(new ApplicationPrivilege("app1", "app1-read", "write"), "user/joe");
role.application().grants(new ApplicationPrivilege("app1", "app1-read", "read"), "settings/hostname");
role.application().grants(new ApplicationPrivilege("app2a", "app2a-all", "all"), "user/joe");
role.application().grants(new ApplicationPrivilege("app2b", "app2b-read", "read"), "settings/hostname");
}
public void testCustomRolesProviderFailures() throws Exception {
final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
@ -370,8 +492,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
final CompositeRolesStore compositeRolesStore =
new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
Arrays.asList(inMemoryProvider1, failingProvider), new ThreadContext(SECURITY_ENABLED_SETTINGS),
new XPackLicenseState(SECURITY_ENABLED_SETTINGS));
mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, failingProvider),
new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS));
final Set<String> roleNames = Sets.newHashSet("roleA", "roleB", "unknown");
PlainActionFuture<Role> future = new PlainActionFuture<>();
@ -411,7 +533,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
// these licenses don't allow custom role providers
xPackLicenseState.update(randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.STANDARD), true, null);
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore,
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState);
Set<String> roleNames = Sets.newHashSet("roleA");
@ -424,7 +546,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
assertEquals(0, role.indices().groups().length);
compositeRolesStore = new CompositeRolesStore(
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore,
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState);
// these licenses allow custom role providers
xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), true, null);
@ -439,7 +561,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
// license expired, don't allow custom role providers
compositeRolesStore = new CompositeRolesStore(
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore,
Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState);
xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), false, null);
roleNames = Sets.newHashSet("roleA");
@ -459,7 +581,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(
Settings.EMPTY, mock(FileRolesStore.class), mock(NativeRolesStore.class), mock(ReservedRolesStore.class),
Collections.emptyList(), new ThreadContext(Settings.EMPTY), new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) {
mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY),
new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) {
@Override
public void invalidateAll() {
numInvalidation.incrementAndGet();
@ -502,9 +625,10 @@ public class CompositeRolesStoreTests extends ESTestCase {
public void testCacheClearOnIndexOutOfDateChange() {
final AtomicInteger numInvalidation = new AtomicInteger(0);
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, mock(FileRolesStore.class),
mock(NativeRolesStore.class), mock(ReservedRolesStore.class),
Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) {
CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS,
mock(FileRolesStore.class), mock(NativeRolesStore.class), mock(ReservedRolesStore.class),
mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) {
@Override
public void invalidateAll() {
numInvalidation.incrementAndGet();

View File

@ -16,6 +16,7 @@ import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.audit.logfile.CapturingLogger;
@ -123,7 +124,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role3" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
assertThat(role.indices().groups().length, is(1));
@ -147,7 +148,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role_run_as" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.indices(), is(IndicesPermission.NONE));
assertThat(role.runAs(), notNullValue());
assertThat(role.runAs().check("user1"), is(true));
@ -160,7 +161,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role_run_as1" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.indices(), is(IndicesPermission.NONE));
assertThat(role.runAs(), notNullValue());
assertThat(role.runAs().check("user1"), is(true));
@ -173,7 +174,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role_fields" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.runAs(), is(RunAsPermission.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
@ -195,7 +196,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role_query" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.runAs(), is(RunAsPermission.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
@ -216,7 +217,7 @@ public class FileRolesStoreTests extends ESTestCase {
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role_query_fields" }));
assertThat(role.cluster(), notNullValue());
assertThat(role.cluster(), is(ClusterPermission.NONE));
assertThat(role.cluster(), is(ClusterPermission.SimpleClusterPermission.NONE));
assertThat(role.runAs(), is(RunAsPermission.NONE));
assertThat(role.indices(), notNullValue());
assertThat(role.indices().groups(), notNullValue());
@ -341,14 +342,15 @@ public class FileRolesStoreTests extends ESTestCase {
fail("Waited too long for the updated file to be picked up");
}
final TransportRequest request = mock(TransportRequest.class);
descriptors = store.roleDescriptors(Collections.singleton("role5"));
assertThat(descriptors, notNullValue());
assertEquals(1, descriptors.size());
Role role = Role.builder(descriptors.iterator().next(), null).build();
assertThat(role, notNullValue());
assertThat(role.names(), equalTo(new String[] { "role5" }));
assertThat(role.cluster().check("cluster:monitor/foo/bar"), is(true));
assertThat(role.cluster().check("cluster:admin/foo/bar"), is(false));
assertThat(role.cluster().check("cluster:monitor/foo/bar", request), is(true));
assertThat(role.cluster().check("cluster:admin/foo/bar", request), is(false));
} finally {
if (watcherService != null) {

View File

@ -0,0 +1,300 @@
/*
* 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.authz.store;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchResponseSections;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.client.NoOpClient;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheRequest;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import static java.util.Collections.emptyMap;
import static org.elasticsearch.common.util.set.Sets.newHashSet;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.not;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@TestLogging("org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore:TRACE")
public class NativePrivilegeStoreTests extends ESTestCase {
private NativePrivilegeStore store;
private List<ActionRequest> requests;
private AtomicReference<ActionListener> listener;
private Client client;
@Before
public void setup() {
requests = new ArrayList<>();
listener = new AtomicReference<>();
client = new NoOpClient(getTestName()) {
@Override
protected <Request extends ActionRequest, Response extends ActionResponse>
void doExecute(Action<Response> action, Request request, ActionListener<Response> listener) {
NativePrivilegeStoreTests.this.requests.add(request);
NativePrivilegeStoreTests.this.listener.set(listener);
}
};
final SecurityIndexManager securityIndex = mock(SecurityIndexManager.class);
when(securityIndex.isAvailable()).thenReturn(true);
Mockito.doAnswer(invocationOnMock -> {
assertThat(invocationOnMock.getArguments().length, equalTo(2));
assertThat(invocationOnMock.getArguments()[1], instanceOf(Runnable.class));
((Runnable) invocationOnMock.getArguments()[1]).run();
return null;
}).when(securityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class));
store = new NativePrivilegeStore(Settings.EMPTY, client, securityIndex);
}
@After
public void cleanup() {
client.close();
}
public void testGetSinglePrivilegeByName() throws Exception {
final ApplicationPrivilegeDescriptor sourcePrivilege = new ApplicationPrivilegeDescriptor("myapp", "admin",
newHashSet("action:admin/*", "action:login", "data:read/*"), emptyMap()
);
final PlainActionFuture<ApplicationPrivilegeDescriptor> future = new PlainActionFuture<>();
store.getPrivilege("myapp", "admin", future);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(GetRequest.class));
GetRequest request = (GetRequest) requests.get(0);
assertThat(request.index(), equalTo(SecurityIndexManager.SECURITY_INDEX_NAME));
assertThat(request.type(), equalTo("doc"));
assertThat(request.id(), equalTo("application-privilege_myapp:admin"));
final String docSource = Strings.toString(sourcePrivilege);
listener.get().onResponse(new GetResponse(
new GetResult(request.index(), request.type(), request.id(), 1L, true, new BytesArray(docSource), emptyMap())
));
final ApplicationPrivilegeDescriptor getPrivilege = future.get(1, TimeUnit.SECONDS);
assertThat(getPrivilege, equalTo(sourcePrivilege));
}
public void testGetMissingPrivilege() throws Exception {
final PlainActionFuture<ApplicationPrivilegeDescriptor> future = new PlainActionFuture<>();
store.getPrivilege("myapp", "admin", future);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(GetRequest.class));
GetRequest request = (GetRequest) requests.get(0);
assertThat(request.index(), equalTo(SecurityIndexManager.SECURITY_INDEX_NAME));
assertThat(request.type(), equalTo("doc"));
assertThat(request.id(), equalTo("application-privilege_myapp:admin"));
listener.get().onResponse(new GetResponse(
new GetResult(request.index(), request.type(), request.id(), -1, false, null, emptyMap())
));
final ApplicationPrivilegeDescriptor getPrivilege = future.get(1, TimeUnit.SECONDS);
assertThat(getPrivilege, Matchers.nullValue());
}
public void testGetPrivilegesByApplicationName() throws Exception {
final List<ApplicationPrivilegeDescriptor> sourcePrivileges = Arrays.asList(
new ApplicationPrivilegeDescriptor("myapp", "admin", newHashSet("action:admin/*", "action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("myapp", "user", newHashSet("action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("myapp", "author", newHashSet("action:login", "data:read/*", "data:write/*"), emptyMap())
);
final PlainActionFuture<Collection<ApplicationPrivilegeDescriptor>> future = new PlainActionFuture<>();
store.getPrivileges(Arrays.asList("myapp", "yourapp"), null, future);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(SearchRequest.class));
SearchRequest request = (SearchRequest) requests.get(0);
assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME));
final String query = Strings.toString(request.source().query());
assertThat(query, containsString("{\"terms\":{\"application\":[\"myapp\",\"yourapp\"]"));
assertThat(query, containsString("{\"term\":{\"type\":{\"value\":\"application-privilege\""));
final SearchHit[] hits = buildHits(sourcePrivileges);
listener.get().onResponse(new SearchResponse(new SearchResponseSections(
new SearchHits(hits, hits.length, 0f), null, null, false, false, null, 1), "_scrollId1", 1, 1, 0, 1, null, null));
assertResult(sourcePrivileges, future);
}
public void testGetAllPrivileges() throws Exception {
final List<ApplicationPrivilegeDescriptor> sourcePrivileges = Arrays.asList(
new ApplicationPrivilegeDescriptor("app1", "admin", newHashSet("action:admin/*", "action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("app2", "user", newHashSet("action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("app3", "all", newHashSet("*"), emptyMap())
);
final PlainActionFuture<Collection<ApplicationPrivilegeDescriptor>> future = new PlainActionFuture<>();
store.getPrivileges(null, null, future);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(SearchRequest.class));
SearchRequest request = (SearchRequest) requests.get(0);
assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME));
final String query = Strings.toString(request.source().query());
assertThat(query, containsString("{\"term\":{\"type\":{\"value\":\"application-privilege\""));
assertThat(query, not(containsString("{\"terms\"")));
final SearchHit[] hits = buildHits(sourcePrivileges);
listener.get().onResponse(new SearchResponse(new SearchResponseSections(
new SearchHits(hits, hits.length, 0f), null, null, false, false, null, 1), "_scrollId1", 1, 1, 0, 1, null, null));
assertResult(sourcePrivileges, future);
}
public void testPutPrivileges() throws Exception {
final List<ApplicationPrivilegeDescriptor> putPrivileges = Arrays.asList(
new ApplicationPrivilegeDescriptor("app1", "admin", newHashSet("action:admin/*", "action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("app1", "user", newHashSet("action:login", "data:read/*"), emptyMap()),
new ApplicationPrivilegeDescriptor("app2", "all", newHashSet("*"), emptyMap())
);
final PlainActionFuture<Map<String, List<String>>> future = new PlainActionFuture<>();
store.putPrivileges(putPrivileges, WriteRequest.RefreshPolicy.IMMEDIATE, future);
assertThat(requests, iterableWithSize(putPrivileges.size()));
assertThat(requests, everyItem(instanceOf(IndexRequest.class)));
final List<IndexRequest> indexRequests = new ArrayList<>(requests.size());
requests.stream().map(IndexRequest.class::cast).forEach(indexRequests::add);
requests.clear();
final ActionListener indexListener = listener.get();
final String uuid = UUIDs.randomBase64UUID(random());
for (int i = 0; i < putPrivileges.size(); i++) {
ApplicationPrivilegeDescriptor privilege = putPrivileges.get(i);
IndexRequest request = indexRequests.get(i);
assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME));
assertThat(request.type(), equalTo("doc"));
assertThat(request.id(), equalTo(
"application-privilege_" + privilege.getApplication() + ":" + privilege.getName()
));
final XContentBuilder builder = privilege.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), true);
assertThat(request.source(), equalTo(BytesReference.bytes(builder)));
final boolean created = privilege.getName().equals("user") == false;
indexListener.onResponse(new IndexResponse(
new ShardId(SecurityIndexManager.SECURITY_INDEX_NAME, uuid, i),
request.type(), request.id(), 1, 1, 1, created
));
}
awaitBusy(() -> requests.size() > 0, 1, TimeUnit.SECONDS);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(ClearRolesCacheRequest.class));
listener.get().onResponse(null);
final Map<String, List<String>> map = future.actionGet();
assertThat(map.entrySet(), iterableWithSize(2));
assertThat(map.get("app1"), iterableWithSize(1));
assertThat(map.get("app2"), iterableWithSize(1));
assertThat(map.get("app1"), contains("admin"));
assertThat(map.get("app2"), contains("all"));
}
public void testDeletePrivileges() throws Exception {
final List<String> privilegeNames = Arrays.asList("p1", "p2", "p3");
final PlainActionFuture<Map<String, List<String>>> future = new PlainActionFuture<>();
store.deletePrivileges("app1", privilegeNames, WriteRequest.RefreshPolicy.IMMEDIATE, future);
assertThat(requests, iterableWithSize(privilegeNames.size()));
assertThat(requests, everyItem(instanceOf(DeleteRequest.class)));
final List<DeleteRequest> deletes = new ArrayList<>(requests.size());
requests.stream().map(DeleteRequest.class::cast).forEach(deletes::add);
requests.clear();
final ActionListener deleteListener = listener.get();
final String uuid = UUIDs.randomBase64UUID(random());
for (int i = 0; i < privilegeNames.size(); i++) {
String name = privilegeNames.get(i);
DeleteRequest request = deletes.get(i);
assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME));
assertThat(request.type(), equalTo("doc"));
assertThat(request.id(), equalTo("application-privilege_app1:" + name));
final boolean found = name.equals("p2") == false;
deleteListener.onResponse(new DeleteResponse(
new ShardId(SecurityIndexManager.SECURITY_INDEX_NAME, uuid, i),
request.type(), request.id(), 1, 1, 1, found
));
}
awaitBusy(() -> requests.size() > 0, 1, TimeUnit.SECONDS);
assertThat(requests, iterableWithSize(1));
assertThat(requests.get(0), instanceOf(ClearRolesCacheRequest.class));
listener.get().onResponse(null);
final Map<String, List<String>> map = future.actionGet();
assertThat(map.entrySet(), iterableWithSize(1));
assertThat(map.get("app1"), iterableWithSize(2));
assertThat(map.get("app1"), containsInAnyOrder("p1", "p3"));
}
private SearchHit[] buildHits(List<ApplicationPrivilegeDescriptor> sourcePrivileges) {
final SearchHit[] hits = new SearchHit[sourcePrivileges.size()];
for (int i = 0; i < hits.length; i++) {
final ApplicationPrivilegeDescriptor p = sourcePrivileges.get(i);
hits[i] = new SearchHit(i, "application-privilege_" + p.getApplication() + ":" + p.getName(), null, null);
hits[i].sourceRef(new BytesArray(Strings.toString(p)));
}
return hits;
}
private void assertResult(List<ApplicationPrivilegeDescriptor> sourcePrivileges,
PlainActionFuture<Collection<ApplicationPrivilegeDescriptor>> future) throws Exception {
final Collection<ApplicationPrivilegeDescriptor> getPrivileges = future.get(1, TimeUnit.SECONDS);
assertThat(getPrivileges, iterableWithSize(sourcePrivileges.size()));
assertThat(new HashSet<>(getPrivileges), equalTo(new HashSet<>(sourcePrivileges)));
}
}

View File

@ -5,10 +5,6 @@
*/
package org.elasticsearch.xpack.security.rest.action.user;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
@ -19,6 +15,10 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction.HasPrivilegesRestResponseBuilder;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Mockito.mock;
@ -30,13 +30,13 @@ public class HasPrivilegesRestResponseTests extends ESTestCase {
final HasPrivilegesResponse actionResponse = new HasPrivilegesResponse(false,
Collections.singletonMap("manage", true),
Arrays.asList(
new HasPrivilegesResponse.IndexPrivileges("staff",
new HasPrivilegesResponse.ResourcePrivileges("staff",
MapBuilder.<String, Boolean>newMapBuilder(new LinkedHashMap<>())
.put("read", true).put("index", true).put("delete", false).put("manage", false).map()),
new HasPrivilegesResponse.IndexPrivileges("customers",
new HasPrivilegesResponse.ResourcePrivileges("customers",
MapBuilder.<String, Boolean>newMapBuilder(new LinkedHashMap<>())
.put("read", true).put("index", true).put("delete", true).put("manage", false).map())
));
), Collections.emptyMap());
final XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent());
final RestResponse rest = response.buildResponse(actionResponse, builder);
@ -50,6 +50,8 @@ public class HasPrivilegesRestResponseTests extends ESTestCase {
"\"index\":{" +
"\"staff\":{\"read\":true,\"index\":true,\"delete\":false,\"manage\":false}," +
"\"customers\":{\"read\":true,\"index\":true,\"delete\":true,\"manage\":false}" +
"}}"));
"}," +
"\"application\":{}" +
"}"));
}
}

View File

@ -0,0 +1,30 @@
{
"xpack.security.delete_privileges": {
"documentation": "TODO",
"methods": [ "DELETE" ],
"url": {
"path": "/_xpack/security/privilege/{application}/{name}",
"paths": [ "/_xpack/security/privilege/{application}/{name}" ],
"parts": {
"application": {
"type" : "string",
"description" : "Application name",
"required" : true
},
"name": {
"type" : "string",
"description" : "Privilege name",
"required" : true
}
},
"params": {
"refresh": {
"type" : "enum",
"options": ["true", "false", "wait_for"],
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
}
}
},
"body": null
}
}

View File

@ -0,0 +1,24 @@
{
"xpack.security.get_privileges": {
"documentation": "TODO",
"methods": [ "GET" ],
"url": {
"path": "/_xpack/security/privilege/{application}/{name}",
"paths": [ "/_xpack/security/privilege/{application}/{name}" ],
"parts": {
"application": {
"type" : "string",
"description" : "Application name",
"required" : false
},
"name": {
"type" : "string",
"description" : "Privilege name",
"required" : false
}
},
"params": {}
},
"body": null
}
}

View File

@ -0,0 +1,22 @@
{
"xpack.security.has_privileges": {
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-privileges.html",
"methods": [ "GET", "POST" ],
"url": {
"path": "/_xpack/security/user/_has_privileges",
"paths": [ "/_xpack/security/user/_has_privileges", "/_xpack/security/user/{user}/_has_privileges" ],
"parts": {
"user": {
"type" : "string",
"description" : "Username",
"required" : false
}
},
"params": {}
},
"body": {
"description" : "The privileges to test",
"required" : true
}
}
}

View File

@ -0,0 +1,33 @@
{
"xpack.security.put_privilege": {
"documentation": "TODO",
"methods": [ "POST", "PUT" ],
"url": {
"path": "/_xpack/security/privilege/{application}/{name}",
"paths": [ "/_xpack/security/privilege/{application}/{name}" ],
"parts": {
"application": {
"type" : "string",
"description" : "Application name",
"required" : true
},
"name": {
"type" : "string",
"description" : "Privilege name",
"required" : true
}
},
"params": {
"refresh": {
"type" : "enum",
"options": ["true", "false", "wait_for"],
"description" : "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
}
}
},
"body": {
"description" : "The privilege to add",
"required" : true
}
}
}

View File

@ -0,0 +1,27 @@
{
"xpack.security.put_privileges": {
"documentation": "TODO",
"methods": [ "POST" ],
"url": {
"path": "/_xpack/security/privilege/",
"paths": [
"/_xpack/security/privilege/"
],
"params": {
"refresh": {
"type": "enum",
"options": [
"true",
"false",
"wait_for"
],
"description": "If `true` (the default) then refresh the affected shards to make this operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` then do nothing with refreshes."
}
}
},
"body": {
"description" : "The privilege(s) to add",
"required" : true
}
}
}

View File

@ -0,0 +1,324 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
---
teardown:
- do:
xpack.security.delete_privileges:
application: app
name: "p1,p2,p3"
ignore: 404
- do:
xpack.security.delete_privileges:
application: app2
name: "p1"
ignore: 404
- do:
xpack.security.delete_privileges:
application: app3
name: "p1,p2,p3,p4"
ignore: 404
- do:
xpack.security.delete_privileges:
application: app4
name: "p1"
ignore: 404
---
"Test put and get privileges":
# Single privilege, with names in URL
- do:
xpack.security.put_privilege:
application: app
name: p1
body: >
{
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key1" : "val1a",
"key2" : "val2a"
}
}
- match: { "app.p1" : { created: true } }
# Multiple privileges, no names in URL
- do:
xpack.security.put_privileges:
body: >
{
"app": {
"p2": {
"application": "app",
"name": "p2",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key1" : "val1b",
"key2" : "val2b"
}
},
"p3": {
"application": "app",
"name": "p3",
"actions": [ "data:write/*" , "action:login" ],
"metadata": {
"key1" : "val1c",
"key2" : "val2c"
}
}
},
"app2" : {
"p1" : {
"application": "app2",
"name": "p1",
"actions": [ "*" ]
}
}
}
- match: { "app.p2" : { created: true } }
- match: { "app.p3" : { created: true } }
- match: { "app2.p1" : { created: true } }
# Update existing privilege, with names in URL
- do:
xpack.security.put_privilege:
application: app
name: p1
body: >
{
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key3" : "val3"
}
}
- match: { "app.p1" : { created: false } }
# Get the privilege back
- do:
xpack.security.get_privileges:
application: app
name: p1
- match: {
"app.p1" : {
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key3" : "val3"
}
}
}
# Get 2 privileges back
- do:
xpack.security.get_privileges:
application: app
name: p1,p2
- match: {
"app.p1" : {
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key3" : "val3"
}
}
}
- match: {
"app.p2" : {
"application": "app",
"name": "p2",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key1" : "val1b",
"key2" : "val2b"
}
}
}
# Get all (3) privileges back for "app"
- do:
xpack.security.get_privileges:
application: "app"
name: ""
- match: {
"app.p1" : {
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key3" : "val3"
}
}
}
- match: {
"app.p2" : {
"application": "app",
"name": "p2",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key1" : "val1b",
"key2" : "val2b"
}
}
}
- match: {
"app.p3" : {
"application": "app",
"name": "p3",
"actions": [ "data:write/*" , "action:login" ],
"metadata": {
"key1" : "val1c",
"key2" : "val2c"
}
}
}
# Get all (4) privileges back for all apps
- do:
xpack.security.get_privileges:
application: ""
name: ""
- match: {
"app.p1" : {
"application": "app",
"name": "p1",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key3" : "val3"
}
}
}
- match: {
"app.p2" : {
"application": "app",
"name": "p2",
"actions": [ "data:read/*" , "action:login" ],
"metadata": {
"key1" : "val1b",
"key2" : "val2b"
}
}
}
- match: {
"app.p3" : {
"application": "app",
"name": "p3",
"actions": [ "data:write/*" , "action:login" ],
"metadata": {
"key1" : "val1c",
"key2" : "val2c"
}
}
}
- match: {
"app2.p1" : {
"application": "app2",
"name": "p1",
"actions": [ "*" ],
"metadata": { }
}
}
---
"Test put and delete privileges":
# Store some privileges
- do:
xpack.security.put_privileges:
body: >
{
"app3": {
"p1": {
"application": "app3",
"name": "p1",
"actions": [ "data:read/*" ]
},
"p2": {
"application": "app3",
"name": "p2",
"actions": [ "data:write/*" ]
},
"p3": {
"application": "app3",
"name": "p3",
"actions": [ "data:write/*", "data:read/*" ]
},
"p4": {
"application": "app3",
"name": "p4",
"actions": [ "*" ]
}
},
"app4": {
"p1": {
"application": "app4",
"name": "p1",
"actions": [ "*" ]
}
}
}
- match: { "app3.p1" : { created: true } }
- match: { "app3.p2" : { created: true } }
- match: { "app3.p3" : { created: true } }
- match: { "app3.p4" : { created: true } }
- match: { "app4.p1" : { created: true } }
# Delete 1 privilege
- do:
xpack.security.delete_privileges:
application: app3
name: p1
- match: { "app3.p1" : { "found" : true } }
# Delete 2 more privileges (p2, p3)
# and try to delete two that don't exist (p1, p0)
- do:
xpack.security.delete_privileges:
application: app3
name: p1,p2,p3,p0
- match: { "app3.p1" : { "found" : false} }
- match: { "app3.p2" : { "found" : true } }
- match: { "app3.p3" : { "found" : true } }
- match: { "app3.p0" : { "found" : false} }
# Check the deleted privileges are gone
- do:
catch: missing
xpack.security.get_privileges:
application: app3
name: p1,p2,p3
# Check the non-deleted privileges are there
- do:
xpack.security.get_privileges:
application: ""
name: ""
- match: {
"app3.p4" : {
"application": "app3",
"name": "p4",
"actions": [ "*" ],
"metadata": { }
}
}
- match: {
"app4.p1" : {
"application": "app4",
"name": "p1",
"actions": [ "*" ],
"metadata": { }
}
}

View File

@ -0,0 +1,190 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
# Create some privileges
- do:
xpack.security.put_privileges:
body: >
{
"myapp": {
"user": {
"application": "myapp",
"name": "user",
"actions": [ "action:login", "version:1.0.*" ]
},
"read": {
"application": "myapp",
"name": "read",
"actions": [ "data:read/*" ]
},
"write": {
"application": "myapp",
"name": "write",
"actions": [ "data:write/*" ]
}
}
}
# Store 2 test roles
- do:
xpack.security.put_role:
name: "myapp_engineering_read"
body: >
{
"cluster": [],
"indices": [
{
"names": "engineering-*",
"privileges": ["read"]
}
],
"applications": [
{
"application": "myapp",
"privileges": ["user"],
"resources": ["*"]
},
{
"application": "myapp",
"privileges": ["read"],
"resources": ["engineering/*"]
}
]
}
- do:
xpack.security.put_role:
name: "myapp_engineering_write"
body: >
{
"cluster": [],
"indices": [
{
"names": "engineering-*",
"privileges": ["read"]
}
],
"applications": [
{
"application": "myapp",
"privileges": ["user"],
"resources": ["*"]
},
{
"application": "myapp",
"privileges": ["read", "write"],
"resources": ["engineering/*"]
}
]
}
# And a user for each role
- do:
xpack.security.put_user:
username: "eng_read"
body: >
{
"password": "p@ssw0rd",
"roles" : [ "myapp_engineering_read" ]
}
- do:
xpack.security.put_user:
username: "eng_write"
body: >
{
"password": "p@ssw0rd",
"roles" : [ "myapp_engineering_write" ]
}
---
teardown:
- do:
xpack.security.delete_privileges:
application: myapp
name: "user,read,write"
ignore: 404
- do:
xpack.security.delete_user:
username: "eng_read"
ignore: 404
- do:
xpack.security.delete_user:
username: "eng_write"
ignore: 404
- do:
xpack.security.delete_role:
name: "myapp_engineering_read"
ignore: 404
- do:
xpack.security.delete_role:
name: "myapp_engineering_write"
ignore: 404
---
"Test has_privileges with application-privileges":
- do:
headers: { Authorization: "Basic ZW5nX3JlYWQ6cEBzc3cwcmQ=" } # eng_read
xpack.security.has_privileges:
user: null
body: >
{
"index": [
{
"names" :[ "engineering-logs", "product-logs" ],
"privileges" : [ "read", "index", "write" ]
}
],
"application": [
{
"application" : "myapp",
"resources" : [ "*" ],
"privileges" : [ "action:login", "version:1.0.3" ]
},
{
"application" : "myapp",
"resources" : [ "engineering/logs/*", "product/logs/*" ],
"privileges" : [ "data:read/log/raw", "data:write/log/raw" ]
}
]
}
- match: { "username" : "eng_read" }
- match: { "has_all_requested" : false }
- match: { "index" : {
"engineering-logs" : {
"read": true,
"index": false,
"write": false
},
"product-logs" : {
"read": false,
"index": false,
"write": false
}
} }
- match: { "application" : {
"myapp" : {
"*" : {
"action:login" : true,
"version:1.0.3" : true
},
"engineering/logs/*" : {
"data:read/log/raw" : true,
"data:write/log/raw" : false
},
"product/logs/*" : {
"data:read/log/raw" : false,
"data:write/log/raw" : false
}
}
} }

View File

@ -0,0 +1,131 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
# Create some privileges
- do:
xpack.security.put_privileges:
body: >
{
"app01": {
"user": {
"application": "app01",
"name": "user",
"actions": [ "action:login" ]
},
"read": {
"application": "app01",
"name": "read",
"actions": [ "data:read/*" ]
},
"write": {
"application": "app01",
"name": "write",
"actions": [ "data:write/*" ]
}
},
"app02": {
"user": {
"application": "app02",
"name": "user",
"actions": [ "action:login" ]
},
"read": {
"application": "app02",
"name": "read",
"actions": [ "data:read/*" ]
},
"write": {
"application": "app02",
"name": "write",
"actions": [ "data:write/*" ]
}
}
}
# And a superuser
- do:
xpack.security.put_user:
username: "my_admin"
body: >
{
"password": "admin01",
"roles" : [ "superuser" ]
}
- do:
xpack.security.put_user:
username: "eng_write"
body: >
{
"password": "p@ssw0rd",
"roles" : [ "myapp_engineering_write" ]
}
---
teardown:
- do:
xpack.security.delete_privileges:
application: app01
name: "user,read,write"
ignore: 404
- do:
xpack.security.delete_privileges:
application: app02
name: "user,read,write"
ignore: 404
- do:
xpack.security.delete_user:
username: "my_admin"
ignore: 404
---
"Test superuser has all application-privileges":
- do:
headers: { Authorization: "Basic bXlfYWRtaW46YWRtaW4wMQ==" } # my_admin
xpack.security.has_privileges:
user: null
body: >
{
"cluster": [ "manage" ],
"index": [
{
"names" :[ "*" ],
"privileges" : [ "read", "index", "write" ]
}
],
"application": [
{
"application" : "app01",
"resources" : [ "*" ],
"privileges" : [ "action:login", "data:read/secrets" ]
},
{
"application" : "app02",
"resources" : [ "thing/1" ],
"privileges" : [ "data:write/thing" ]
}
]
}
- match: { "username" : "my_admin" }
- match: { "has_all_requested" : true }
- match: { "application" : {
"app01" : {
"*" : {
"action:login" : true,
"data:read/secrets" : true
}
},
"app02" : {
"thing/1" : {
"data:write/thing" : true
}
}
} }

View File

@ -0,0 +1,51 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_user:
username: "joe"
body: >
{
"password": "s3krit",
"roles" : [ "with_global" ]
}
---
teardown:
- do:
xpack.security.delete_user:
username: "joe"
ignore: 404
- do:
xpack.security.delete_role:
name: "with_global"
ignore: 404
---
"Test put role with conditional security privileges":
- do:
xpack.security.put_role:
name: "with_global"
body: >
{
"global": {
"application": {
"manage": {
"applications": [ "app1-*" , "app2-*" ]
}
}
}
}
- match: { role: { created: true } }
- do:
xpack.security.get_role:
name: "with_global"
- match: { with_global.global.application.manage.applications.0: "app1-*" }
- match: { with_global.global.application.manage.applications.1: "app2-*" }

View File

@ -0,0 +1,132 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_user:
username: "test_user"
body: >
{
"password" : "x-pack-test-password",
"roles" : [ "app_manage" ]
}
- do:
xpack.security.put_role:
name: "app_manage"
body: >
{
"global": {
"application": {
"manage": {
"applications": [ "app" , "app-*" ]
}
}
}
}
- do:
xpack.security.put_privilege:
application: app-allow
name: read
body: >
{
"actions": [ "data:read/*" ]
}
- do:
xpack.security.put_privilege:
application: app_deny
name: read
body: >
{
"actions": [ "data:read/*" ]
}
---
teardown:
- do:
xpack.security.delete_user:
username: "test_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "app_manage"
ignore: 404
- do:
xpack.security.delete_privileges:
application: app
name: read
ignore: 404
- do:
xpack.security.delete_privileges:
application: app-allow
name: read
ignore: 404
- do:
xpack.security.delete_privileges:
application: app_deny
name: read
ignore: 404
---
"Test put application privileges when allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
xpack.security.put_privilege:
application: app
name: read
body: >
{
"actions": [ "data:read/*" ]
}
- match: { "app.read" : { created: true } }
---
"Test get application privileges when allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
xpack.security.get_privileges:
application: app-allow
name: read
- match: {
"app-allow.read" : {
"application": "app-allow",
"name": "read",
"actions": [ "data:read/*" ],
"metadata": {}
}
}
---
"Test put application privileges when not allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
xpack.security.put_privilege:
application: app_deny
name: write
body: >
{
"actions": [ "data:write/*" ]
}
catch: forbidden
---
"Test get application privileges when not allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
xpack.security.get_privileges:
application: app_deny
name: read
catch: forbidden