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:
parent
e6b9f59e4e
commit
387c3c7f1d
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"/]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 + "]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ public class HasPrivilegesRequestBuilder
|
|||
request.username(username);
|
||||
request.indexPrivileges(role.getIndicesPrivileges());
|
||||
request.clusterPrivileges(role.getClusterPrivileges());
|
||||
request.applicationPrivileges(role.getApplicationPrivileges());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 & 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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))));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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"));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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\":{}" +
|
||||
"}"));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": { }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
|
@ -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-*" }
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue