Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Application Privileges with support for Kibana RBAC #32309

Merged
merged 34 commits into from
Jul 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
03e5e72
Introduce Application Privileges to Roles (#30164)
tvernum Jun 7, 2018
53961b0
Merge branch 'master' into security-app-privs
tvernum Jun 12, 2018
4f33b36
Merge branch 'master' into security-app-privs
tvernum Jun 15, 2018
baf9b47
Merge branch 'master' into security-app-privs
tvernum Jun 20, 2018
ecea2e4
Refactor Privileges API/Store to use separate class (#31191)
tvernum Jun 21, 2018
acb0060
Merge branch 'master' into security-app-privs
tvernum Jun 21, 2018
179615f
Merge branch 'master' into security-app-privs
tvernum Jun 25, 2018
d509bd2
Fix case sensitivity bug in application privileges (#31491)
tvernum Jun 26, 2018
2c8f649
Merge branch 'master' into security-app-privs
tvernum Jun 26, 2018
5f36981
Allowing the kibana system role to get/put privileges (#31201)
kobelb Jun 26, 2018
c3f7fcc
Merge branch 'master' into security-app-privs
tvernum Jun 28, 2018
985802a
Merge branch 'master' into security-app-privs
tvernum Jun 29, 2018
b8d7462
Merge branch 'master' into security-app-privs
tvernum Jul 2, 2018
1746510
Merge branch 'master' into security-app-privs
tvernum Jul 3, 2018
95948c7
Serialize application privileges in PutRoleRequest (#31712)
tvernum Jul 3, 2018
9d61760
Merge branch 'master' into security-app-privs
tvernum Jul 4, 2018
4e1031b
Support wider range of application names (#31752)
tvernum Jul 5, 2018
77e69a3
Merge branch 'master' into security-app-privs
tvernum Jul 5, 2018
35c08f4
Merge branch 'master' into security-app-privs
tvernum Jul 6, 2018
02bfc8d
Merge branch 'master' into security-app-privs
tvernum Jul 12, 2018
610e587
Merge branch 'master' into security-app-privs
tvernum Jul 16, 2018
0083161
Extend ClusterPermission to consider requests (#31998)
tvernum Jul 16, 2018
154eb1f
Add test for merging roles (#32008)
tvernum Jul 16, 2018
7fa9f49
Merge branch 'master' into security-app-privs
tvernum Jul 17, 2018
df1ca1f
Introduce "ConditionalClusterPrivilege" (#32073)
tvernum Jul 17, 2018
fd42a9b
Merge branch 'master' into security-app-privs
tvernum Jul 19, 2018
69a42b3
Add manage-application-privileges conditional cluster privilege (#32116)
tvernum Jul 19, 2018
ef7961b
kibana_system can only manage kibana privileges (#32221)
tvernum Jul 23, 2018
ab641d3
Merge branch 'master' into security-app-privs
tvernum Jul 23, 2018
22c0eb4
Merge branch 'master' into security-app-privs
tvernum Jul 24, 2018
1c1240d
Kibana reserved role app privs (#32137)
kobelb Jul 24, 2018
6087767
Require that all app privileges have actions (#32272)
tvernum Jul 24, 2018
32e4f62
Support "source" parameter in _has_privileges (#32310)
tvernum Jul 24, 2018
8b14e2c
Rename "policy" to "global" in role definition (#32324)
tvernum Jul 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
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;
Expand Down Expand Up @@ -931,8 +932,23 @@ public <T extends Streamable> List<T> readStreamableList(Supplier<T> constructor
* 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
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;
Expand Down Expand Up @@ -995,6 +996,16 @@ public void writeList(List<? extends Writeable> list) throws IOException {
}
}

/**
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;
Expand All @@ -42,6 +43,7 @@
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 {

Expand All @@ -65,7 +67,7 @@ public void testBooleanSerialization() throws IOException {
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]);
Expand Down Expand Up @@ -100,7 +102,7 @@ public void testOptionalBooleanSerialization() throws IOException {
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]);
Expand All @@ -119,22 +121,22 @@ public void testRandomVLongSerialization() throws IOException {

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

Expand All @@ -158,7 +160,7 @@ public void testLinkedHashMap() throws IOException {
}
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()) {
Expand All @@ -172,7 +174,8 @@ public void testFilterStreamInputDelegatesAvailable() throws IOException {
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
Expand Down Expand Up @@ -201,7 +204,7 @@ public void testReadArraySize() throws IOException {
}
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);
Expand Down Expand Up @@ -230,6 +233,21 @@ public void testWritableArrays() throws IOException {
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
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;
Expand Down Expand Up @@ -717,6 +718,20 @@ public static String[] generateRandomStringArray(int maxArraySize, int stringSiz
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) {
Expand Down
3 changes: 2 additions & 1 deletion x-pack/docs/en/rest-api/security/privileges.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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"/]
Expand Down
1 change: 1 addition & 0 deletions x-pack/docs/en/rest-api/security/roles.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
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;
Expand Down Expand Up @@ -342,6 +344,11 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Loading