Skip to content

Commit

Permalink
RCS 2.0 - include remote indices privileges in GetUserPrivilege (#9…
Browse files Browse the repository at this point in the history
…0826)

This PR updates the GetUserPrivileges API to include remote_indices
privileges assigned to a given user's role.
  • Loading branch information
n1v0lg committed Nov 7, 2022
1 parent 7d10fff commit 7d2f51b
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
*/
public final class GetUserPrivilegesResponse extends ActionResponse {

private Set<String> cluster;
private Set<ConfigurableClusterPrivilege> configurableClusterPrivileges;
private Set<Indices> index;
private Set<RoleDescriptor.ApplicationResourcePrivileges> application;
private Set<String> runAs;
private final Set<String> cluster;
private final Set<ConfigurableClusterPrivilege> configurableClusterPrivileges;
private final Set<Indices> index;
private final Set<RoleDescriptor.ApplicationResourcePrivileges> application;
private final Set<String> runAs;
private final Set<RemoteIndices> remoteIndex;

public GetUserPrivilegesResponse(StreamInput in) throws IOException {
super(in);
Expand All @@ -46,20 +47,27 @@ public GetUserPrivilegesResponse(StreamInput in) throws IOException {
index = Collections.unmodifiableSet(in.readSet(Indices::new));
application = Collections.unmodifiableSet(in.readSet(RoleDescriptor.ApplicationResourcePrivileges::new));
runAs = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
if (in.getVersion().onOrAfter(RoleDescriptor.VERSION_REMOTE_INDICES)) {
remoteIndex = Collections.unmodifiableSet(in.readSet(RemoteIndices::new));
} else {
remoteIndex = Collections.emptySet();
}
}

public GetUserPrivilegesResponse(
Set<String> cluster,
Set<ConfigurableClusterPrivilege> conditionalCluster,
Set<Indices> index,
Set<RoleDescriptor.ApplicationResourcePrivileges> application,
Set<String> runAs
Set<String> runAs,
Set<RemoteIndices> remoteIndex
) {
this.cluster = Collections.unmodifiableSet(cluster);
this.configurableClusterPrivileges = Collections.unmodifiableSet(conditionalCluster);
this.index = Collections.unmodifiableSet(index);
this.application = Collections.unmodifiableSet(application);
this.runAs = Collections.unmodifiableSet(runAs);
this.remoteIndex = Collections.unmodifiableSet(remoteIndex);
}

public Set<String> getClusterPrivileges() {
Expand All @@ -74,6 +82,10 @@ public Set<Indices> getIndexPrivileges() {
return index;
}

public Set<RemoteIndices> getRemoteIndexPrivileges() {
return remoteIndex;
}

public Set<RoleDescriptor.ApplicationResourcePrivileges> getApplicationPrivileges() {
return application;
}
Expand All @@ -82,13 +94,28 @@ public Set<String> getRunAs() {
return runAs;
}

public boolean hasRemoteIndicesPrivileges() {
return false == remoteIndex.isEmpty();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeCollection(cluster, StreamOutput::writeString);
out.writeCollection(configurableClusterPrivileges, ConfigurableClusterPrivileges.WRITER);
out.writeCollection(index);
out.writeCollection(application);
out.writeCollection(runAs, StreamOutput::writeString);
if (out.getVersion().onOrAfter(RoleDescriptor.VERSION_REMOTE_INDICES)) {
out.writeCollection(remoteIndex);
} else if (hasRemoteIndicesPrivileges()) {
throw new IllegalArgumentException(
"versions of Elasticsearch before ["
+ RoleDescriptor.VERSION_REMOTE_INDICES
+ "] can't handle remote indices privileges and attempted to send to ["
+ out.getVersion()
+ "]"
);
}
}

@Override
Expand All @@ -104,12 +131,34 @@ public boolean equals(Object other) {
&& Objects.equals(configurableClusterPrivileges, that.configurableClusterPrivileges)
&& Objects.equals(index, that.index)
&& Objects.equals(application, that.application)
&& Objects.equals(runAs, that.runAs);
&& Objects.equals(runAs, that.runAs)
&& Objects.equals(remoteIndex, that.remoteIndex);
}

@Override
public int hashCode() {
return Objects.hash(cluster, configurableClusterPrivileges, index, application, runAs);
return Objects.hash(cluster, configurableClusterPrivileges, index, application, runAs, remoteIndex);
}

public record RemoteIndices(Indices indices, Set<String> remoteClusters) implements ToXContentObject, Writeable {

public RemoteIndices(StreamInput in) throws IOException {
this(new Indices(in), Collections.unmodifiableSet(new TreeSet<>(in.readSet(StreamInput::readString))));
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
indices.innerToXContent(builder);
builder.field(RoleDescriptor.Fields.REMOTE_CLUSTERS.getPreferredName(), remoteClusters);
return builder.endObject();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
indices.writeTo(out);
out.writeStringCollection(remoteClusters);
}
}

/**
Expand Down Expand Up @@ -215,6 +264,11 @@ public int hashCode() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
innerToXContent(builder);
return builder.endObject();
}

void innerToXContent(XContentBuilder builder) throws IOException {
builder.field(RoleDescriptor.Fields.NAMES.getPreferredName(), indices);
builder.field(RoleDescriptor.Fields.PRIVILEGES.getPreferredName(), privileges);
if (fieldSecurity.stream().anyMatch(g -> nonEmpty(g.getGrantedFields()) || nonEmpty(g.getExcludedFields()))) {
Expand Down Expand Up @@ -242,7 +296,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.endArray();
}
builder.field(RoleDescriptor.Fields.ALLOW_RESTRICTED_INDICES.getPreferredName(), allowRestrictedIndices);
return builder.endObject();
}

private static boolean nonEmpty(String[] grantedFields) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package org.elasticsearch.xpack.core.security.action.user;

import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
Expand All @@ -18,7 +19,9 @@
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup;
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
Expand All @@ -38,6 +41,7 @@
import java.util.stream.Collectors;

import static java.util.Collections.emptySet;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

public class GetUserPrivilegesResponseTests extends ESTestCase {
Expand All @@ -57,6 +61,55 @@ public void testSerialization() throws IOException {
assertThat(sorted(copy.getIndexPrivileges()), equalTo(sorted(original.getIndexPrivileges())));
assertThat(copy.getApplicationPrivileges(), equalTo(original.getApplicationPrivileges()));
assertThat(copy.getRunAs(), equalTo(original.getRunAs()));
assertThat(copy.getRemoteIndexPrivileges(), equalTo(original.getRemoteIndexPrivileges()));
}

public void testSerializationForCurrentVersion() throws Exception {
final Version version = VersionUtils.randomCompatibleVersion(random(), Version.CURRENT);
final boolean canIncludeRemoteIndices = version.onOrAfter(RoleDescriptor.VERSION_REMOTE_INDICES);

final GetUserPrivilegesResponse original = randomResponse(canIncludeRemoteIndices);

final BytesStreamOutput out = new BytesStreamOutput();
out.setVersion(version);
original.writeTo(out);

final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin().getNamedWriteables());
StreamInput in = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(out.bytes())), registry);
in.setVersion(version);
final GetUserPrivilegesResponse copy = new GetUserPrivilegesResponse(in);
assertThat(copy, equalTo(original));
}

public void testSerializationWithRemoteIndicesThrowsOnUnsupportedVersions() throws IOException {
final BytesStreamOutput out = new BytesStreamOutput();
final Version versionBeforeRemoteIndices = VersionUtils.getPreviousVersion(RoleDescriptor.VERSION_REMOTE_INDICES);
final Version version = VersionUtils.randomVersionBetween(
random(),
versionBeforeRemoteIndices.minimumCompatibilityVersion(),
versionBeforeRemoteIndices
);
out.setVersion(version);

final GetUserPrivilegesResponse original = randomResponse();
if (original.hasRemoteIndicesPrivileges()) {
final var ex = expectThrows(IllegalArgumentException.class, () -> original.writeTo(out));
assertThat(
ex.getMessage(),
containsString(
"versions of Elasticsearch before [8.6.0] can't handle remote indices privileges and attempted to send to ["
+ version
+ "]"
)
);
} else {
original.writeTo(out);
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin().getNamedWriteables());
StreamInput in = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(out.bytes())), registry);
in.setVersion(out.getVersion());
final GetUserPrivilegesResponse copy = new GetUserPrivilegesResponse(in);
assertThat(copy, equalTo(original));
}
}

public void testEqualsAndHashCode() throws IOException {
Expand All @@ -66,10 +119,10 @@ public void testEqualsAndHashCode() throws IOException {
original.getConditionalClusterPrivileges(),
original.getIndexPrivileges(),
original.getApplicationPrivileges(),
original.getRunAs()
original.getRunAs(),
original.getRemoteIndexPrivileges()
);
final EqualsHashCodeTestUtils.MutateFunction<GetUserPrivilegesResponse> mutate = new EqualsHashCodeTestUtils.MutateFunction<
GetUserPrivilegesResponse>() {
final EqualsHashCodeTestUtils.MutateFunction<GetUserPrivilegesResponse> mutate = new EqualsHashCodeTestUtils.MutateFunction<>() {
@Override
public GetUserPrivilegesResponse mutate(GetUserPrivilegesResponse original) {
final int random = randomIntBetween(1, 0b11111);
Expand Down Expand Up @@ -103,7 +156,22 @@ public GetUserPrivilegesResponse mutate(GetUserPrivilegesResponse original) {
.build()
);
final Set<String> runAs = maybeMutate(random, 4, original.getRunAs(), () -> randomAlphaOfLength(8));
return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs);
final Set<GetUserPrivilegesResponse.RemoteIndices> remoteIndex = maybeMutate(
random,
5,
original.getRemoteIndexPrivileges(),
() -> new GetUserPrivilegesResponse.RemoteIndices(
new GetUserPrivilegesResponse.Indices(
randomStringSet(1),
randomStringSet(1),
emptySet(),
emptySet(),
randomBoolean()
),
randomStringSet(1)
)
);
return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs, remoteIndex);
}

private <T> Set<T> maybeMutate(int random, int index, Set<T> original, Supplier<T> supplier) {
Expand All @@ -121,31 +189,16 @@ private <T> Set<T> maybeMutate(int random, int index, Set<T> original, Supplier<
}

private GetUserPrivilegesResponse randomResponse() {
return randomResponse(true);
}

private GetUserPrivilegesResponse randomResponse(boolean allowRemoteIndices) {
final Set<String> cluster = randomStringSet(5);
final Set<ConfigurableClusterPrivilege> conditionalCluster = Sets.newHashSet(
randomArray(3, ConfigurableClusterPrivilege[]::new, () -> new ManageApplicationPrivileges(randomStringSet(3)))
);
final Set<GetUserPrivilegesResponse.Indices> index = Sets.newHashSet(
randomArray(
5,
GetUserPrivilegesResponse.Indices[]::new,
() -> new GetUserPrivilegesResponse.Indices(
randomStringSet(6),
randomStringSet(8),
Sets.newHashSet(
randomArray(
3,
FieldGrantExcludeGroup[]::new,
() -> new FieldGrantExcludeGroup(
generateRandomStringArray(3, 5, false, false),
generateRandomStringArray(3, 5, false, false)
)
)
),
randomStringSet(3).stream().map(BytesArray::new).collect(Collectors.toSet()),
randomBoolean()
)
)
randomArray(5, GetUserPrivilegesResponse.Indices[]::new, this::randomIndices)
);
final Set<ApplicationResourcePrivileges> application = Sets.newHashSet(
randomArray(
Expand All @@ -159,7 +212,36 @@ private GetUserPrivilegesResponse randomResponse() {
)
);
final Set<String> runAs = randomStringSet(3);
return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs);
final Set<GetUserPrivilegesResponse.RemoteIndices> remoteIndex = allowRemoteIndices
? Sets.newHashSet(
randomArray(
5,
GetUserPrivilegesResponse.RemoteIndices[]::new,
() -> new GetUserPrivilegesResponse.RemoteIndices(randomIndices(), randomStringSet(6))
)
)
: Set.of();

return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs, remoteIndex);
}

private GetUserPrivilegesResponse.Indices randomIndices() {
return new GetUserPrivilegesResponse.Indices(
randomStringSet(6),
randomStringSet(8),
Sets.newHashSet(
randomArray(
3,
FieldGrantExcludeGroup[]::new,
() -> new FieldGrantExcludeGroup(
generateRandomStringArray(3, 5, false, false),
generateRandomStringArray(3, 5, false, false)
)
)
),
randomStringSet(3).stream().map(BytesArray::new).collect(Collectors.toSet()),
randomBoolean()
);
}

private List<GetUserPrivilegesResponse.Indices> sorted(Collection<GetUserPrivilegesResponse.Indices> indices) {
Expand Down

0 comments on commit 7d2f51b

Please sign in to comment.