diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 7e06004e47cfb..ff7f0cc7b5b40 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -179,6 +179,7 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_FIELD_ATTRIBUTE_PARENT_SIMPLIFIED = def(8_775_00_0); public static final TransportVersion INFERENCE_DONT_PERSIST_ON_READ = def(8_776_00_0); public static final TransportVersion SIMULATE_MAPPING_ADDITION = def(8_777_00_0); + public static final TransportVersion FAILURE_STORE_AUTH = def(8_778_00_0); /* * STOP! READ THIS FIRST! No, really, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java index 46ba00a4f2768..0c67a68197053 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java @@ -114,7 +114,9 @@ public void addIndex( String[] grantedFields, String[] deniedFields, @Nullable BytesReference query, - boolean allowRestrictedIndices + boolean allowRestrictedIndices, + boolean dataSelector, + boolean failureSelector ) { this.indicesPrivileges.add( RoleDescriptor.IndicesPrivileges.builder() @@ -124,6 +126,8 @@ public void addIndex( .deniedFields(deniedFields) .query(query) .allowRestrictedIndices(allowRestrictedIndices) + .dataStoreSelector(dataSelector) + .failureStoreSelector(failureSelector) .build() ); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestBuilder.java index 486a347775264..e2f790381fca5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestBuilder.java @@ -73,9 +73,11 @@ public PutRoleRequestBuilder addIndices( String[] grantedFields, String[] deniedFields, @Nullable BytesReference query, - boolean allowRestrictedIndices + boolean allowRestrictedIndices, + boolean dataSelector, + boolean failureSelector ) { - request.addIndex(indices, privileges, grantedFields, deniedFields, query, allowRestrictedIndices); + request.addIndex(indices, privileges, grantedFields, deniedFields, query, allowRestrictedIndices, dataSelector, failureSelector); return this; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java index 8d069caf0496f..a8eb3c6c1e035 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java @@ -49,6 +49,7 @@ import java.util.Map; import java.util.Objects; +import static org.elasticsearch.TransportVersions.FAILURE_STORE_AUTH; import static org.elasticsearch.common.xcontent.XContentHelper.createParserNotCompressed; import static org.elasticsearch.xpack.core.security.authz.permission.RemoteClusterPermissions.ROLE_REMOTE_CLUSTER_PRIVS; @@ -911,6 +912,8 @@ private static IndicesPrivilegesWithOptionalRemoteClusters parseIndexWithOptiona String[] grantedFields = null; String[] deniedFields = null; boolean allowRestrictedIndices = false; + boolean dataSelector = IndicesPrivileges.DEFAULT_DATA_SELECTOR; + boolean failureSelector = IndicesPrivileges.DEFAULT_FAILURE_SELECTOR; String[] remoteClusters = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { @@ -948,6 +951,55 @@ private static IndicesPrivilegesWithOptionalRemoteClusters parseIndexWithOptiona token ); } + } else if (Fields.SELECTORS.match(currentFieldName, parser.getDeprecationHandler())) { + if (token == XContentParser.Token.START_OBJECT) { + token = parser.nextToken(); + do { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + token = parser.nextToken(); + if (Fields.DATA_SELECTOR.match(currentFieldName, parser.getDeprecationHandler())) { + dataSelector = parser.booleanValue(); + } else if (Fields.FAILURE_SELECTOR.match(currentFieldName, parser.getDeprecationHandler())) { + failureSelector = parser.booleanValue(); + } else { + throw new ElasticsearchParseException( + "failed to parse indices privileges for role [{}]. " + + "\"{}\" only accepts options {} and {}, but got: {}", + roleName, + Fields.FIELD_PERMISSIONS, + Fields.DATA_SELECTOR, + Fields.FAILURE_SELECTOR, + parser.currentName() + ); + } + } else { + if (token == XContentParser.Token.END_OBJECT) { + throw new ElasticsearchParseException( + "failed to parse indices privileges for role [{}]. " + "\"{}\" must not be empty.", + roleName, + Fields.FIELD_PERMISSIONS + ); + } else { + throw new ElasticsearchParseException( + "failed to parse indices privileges for role [{}]. expected {} but " + "got {}.", + roleName, + XContentParser.Token.FIELD_NAME, + token + ); + } + } + } while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT); + } else { + throw new ElasticsearchParseException( + "failed to parse indices privileges for role [{}]. expected {} but got {} in \"{}\".", + roleName, + XContentParser.Token.START_OBJECT, + token, + Fields.SELECTORS + ); + } + } else if (Fields.QUERY.match(currentFieldName, parser.getDeprecationHandler())) { if (token == XContentParser.Token.START_OBJECT) { XContentBuilder builder = JsonXContent.contentBuilder(); @@ -1111,6 +1163,8 @@ private static IndicesPrivilegesWithOptionalRemoteClusters parseIndexWithOptiona .deniedFields(deniedFields) .query(query) .allowRestrictedIndices(allowRestrictedIndices) + .dataStoreSelector(dataSelector) + .failureStoreSelector(failureSelector) .build(), remoteClusters ); @@ -1326,6 +1380,9 @@ public static class IndicesPrivileges implements ToXContentObject, Writeable, Co private static final IndicesPrivileges[] NONE = new IndicesPrivileges[0]; + public static final boolean DEFAULT_DATA_SELECTOR = true; + public static final boolean DEFAULT_FAILURE_SELECTOR = false; + private String[] indices; private String[] privileges; private String[] grantedFields = null; @@ -1335,6 +1392,8 @@ public static class IndicesPrivileges implements ToXContentObject, Writeable, Co // users. Setting this flag eliminates this special status, and any index name pattern in the permission will cover restricted // indices as well. private boolean allowRestrictedIndices = false; + private boolean dataSelector = DEFAULT_DATA_SELECTOR; + private boolean failureSelector = DEFAULT_FAILURE_SELECTOR; private IndicesPrivileges() {} @@ -1345,6 +1404,10 @@ public IndicesPrivileges(StreamInput in) throws IOException { this.privileges = in.readStringArray(); this.query = in.readOptionalBytesReference(); this.allowRestrictedIndices = in.readBoolean(); + if (in.getTransportVersion().onOrAfter(FAILURE_STORE_AUTH)) { + this.dataSelector = in.readBoolean(); + this.failureSelector = in.readBoolean(); + } } @Override @@ -1355,6 +1418,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeStringArray(privileges); out.writeOptionalBytesReference(query); out.writeBoolean(allowRestrictedIndices); + if (out.getTransportVersion().onOrAfter(FAILURE_STORE_AUTH)) { + out.writeBoolean(dataSelector); + out.writeBoolean(failureSelector); + } } public static Builder builder() { @@ -1400,6 +1467,14 @@ public boolean allowRestrictedIndices() { return allowRestrictedIndices; } + public boolean dataSelector() { + return dataSelector; + } + + public boolean failureSelector() { + return failureSelector; + } + public boolean hasDeniedFields() { return deniedFields != null && deniedFields.length > 0; } @@ -1421,6 +1496,8 @@ public String toString() { StringBuilder sb = new StringBuilder("IndicesPrivileges["); sb.append("indices=[").append(Strings.arrayToCommaDelimitedString(indices)); sb.append("], allowRestrictedIndices=[").append(allowRestrictedIndices); + sb.append("], dataSelector=[").append(dataSelector); + sb.append("], failureSelector=[").append(failureSelector); sb.append("], privileges=[").append(Strings.arrayToCommaDelimitedString(privileges)); sb.append("], "); if (grantedFields != null || deniedFields != null) { @@ -1462,6 +1539,8 @@ public boolean equals(Object o) { if (Arrays.equals(privileges, that.privileges) == false) return false; if (Arrays.equals(grantedFields, that.grantedFields) == false) return false; if (Arrays.equals(deniedFields, that.deniedFields) == false) return false; + if (dataSelector != that.dataSelector) return false; + if (failureSelector != that.failureSelector) return false; return Objects.equals(query, that.query); } @@ -1472,6 +1551,8 @@ public int hashCode() { result = 31 * result + Arrays.hashCode(privileges); result = 31 * result + Arrays.hashCode(grantedFields); result = 31 * result + Arrays.hashCode(deniedFields); + result = 31 * result + (dataSelector ? 1 : 0); + result = 31 * result + (failureSelector ? 1 : 0); result = 31 * result + (query != null ? query.hashCode() : 0); return result; } @@ -1498,6 +1579,16 @@ XContentBuilder innerToXContent(XContentBuilder builder, boolean withPrivileges) } builder.endObject(); } + if (dataSelector != DEFAULT_DATA_SELECTOR || failureSelector != DEFAULT_FAILURE_SELECTOR) { + builder.startObject(RoleDescriptor.Fields.SELECTORS.getPreferredName()); + if (dataSelector != DEFAULT_DATA_SELECTOR) { + builder.field(Fields.DATA_SELECTOR.getPreferredName(), dataSelector); + } + if (failureSelector != DEFAULT_FAILURE_SELECTOR) { + builder.field(Fields.FAILURE_SELECTOR.getPreferredName(), failureSelector); + } + builder.endObject(); + } if (query != null) { builder.field("query", query.utf8ToString()); } @@ -1517,6 +1608,14 @@ public int compareTo(IndicesPrivileges o) { if (cmp != 0) { return cmp; } + cmp = Boolean.compare(dataSelector, o.dataSelector); + if (cmp != 0) { + return cmp; + } + cmp = Boolean.compare(failureSelector, o.failureSelector); + if (cmp != 0) { + return cmp; + } cmp = Arrays.compare(indices, o.indices); if (cmp != 0) { return cmp; @@ -1580,6 +1679,16 @@ public Builder allowRestrictedIndices(boolean allow) { return this; } + public Builder dataStoreSelector(boolean selector) { + indicesPrivileges.dataSelector = selector; + return this; + } + + public Builder failureStoreSelector(boolean selector) { + indicesPrivileges.failureSelector = selector; + return this; + } + public Builder query(@Nullable BytesReference query) { if (query == null) { indicesPrivileges.query = null; @@ -1879,6 +1988,9 @@ public interface Fields { ParseField GRANT_FIELDS = new ParseField("grant"); ParseField EXCEPT_FIELDS = new ParseField("except"); ParseField METADATA = new ParseField("metadata"); + ParseField SELECTORS = new ParseField("selectors"); + ParseField DATA_SELECTOR = new ParseField("data"); + ParseField FAILURE_SELECTOR = new ParseField("failure"); ParseField METADATA_FLATTENED = new ParseField("metadata_flattened"); ParseField TRANSIENT_METADATA = new ParseField("transient_metadata"); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java index 97255502bc7be..b6b86ede999b8 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java @@ -76,6 +76,8 @@ public void testValidationErrorWithUnknownIndexPrivilegeName() { null, null, null, + randomBoolean(), + randomBoolean(), randomBoolean() ); @@ -180,6 +182,8 @@ public void testValidationSuccessWithCorrectIndexPrivilegeName() { null, null, null, + randomBoolean(), + randomBoolean(), randomBoolean() ); assertSuccessfulValidation(request); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTestHelper.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTestHelper.java index 77a37cec45b25..18cc689e16018 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTestHelper.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTestHelper.java @@ -147,7 +147,7 @@ public static RoleDescriptor.RemoteIndicesPrivileges[] randomRemoteIndicesPrivil } public static RoleDescriptor.RemoteIndicesPrivileges[] randomRemoteIndicesPrivileges(int min, int max, Set excludedPrivileges) { - final RoleDescriptor.IndicesPrivileges[] innerIndexPrivileges = randomIndicesPrivileges(min, max, excludedPrivileges); + final RoleDescriptor.IndicesPrivileges[] innerIndexPrivileges = randomIndicesPrivileges(min, max, excludedPrivileges, true); final RoleDescriptor.RemoteIndicesPrivileges[] remoteIndexPrivileges = new RoleDescriptor.RemoteIndicesPrivileges[innerIndexPrivileges.length]; for (int i = 0; i < remoteIndexPrivileges.length; i++) { @@ -159,29 +159,39 @@ public static RoleDescriptor.RemoteIndicesPrivileges[] randomRemoteIndicesPrivil return remoteIndexPrivileges; } - public static RoleDescriptor.IndicesPrivileges[] randomIndicesPrivileges(int min, int max) { - return randomIndicesPrivileges(min, max, Set.of()); + public static RoleDescriptor.IndicesPrivileges[] randomIndicesPrivileges(int min, int max, boolean allowSelectors) { + return randomIndicesPrivileges(min, max, Set.of(), allowSelectors); } - public static RoleDescriptor.IndicesPrivileges[] randomIndicesPrivileges(int min, int max, Set excludedPrivileges) { + public static RoleDescriptor.IndicesPrivileges[] randomIndicesPrivileges( + int min, + int max, + Set excludedPrivileges, + boolean allowSelectors + ) { final RoleDescriptor.IndicesPrivileges[] indexPrivileges = new RoleDescriptor.IndicesPrivileges[randomIntBetween(min, max)]; for (int i = 0; i < indexPrivileges.length; i++) { - indexPrivileges[i] = randomIndicesPrivilegesBuilder(excludedPrivileges).build(); + indexPrivileges[i] = randomIndicesPrivilegesBuilder(excludedPrivileges, allowSelectors).build(); } return indexPrivileges; } - public static RoleDescriptor.IndicesPrivileges.Builder randomIndicesPrivilegesBuilder() { - return randomIndicesPrivilegesBuilder(Set.of()); + public static RoleDescriptor.IndicesPrivileges.Builder randomIndicesPrivilegesBuilder(boolean allowSelectors) { + return randomIndicesPrivilegesBuilder(Set.of(), allowSelectors); } - private static RoleDescriptor.IndicesPrivileges.Builder randomIndicesPrivilegesBuilder(Set excludedPrivileges) { + private static RoleDescriptor.IndicesPrivileges.Builder randomIndicesPrivilegesBuilder( + Set excludedPrivileges, + boolean allowSelectors + ) { final Set candidatePrivilegesNames = Sets.difference(IndexPrivilege.names(), excludedPrivileges); assert false == candidatePrivilegesNames.isEmpty() : "no candidate privilege names to random from"; final RoleDescriptor.IndicesPrivileges.Builder builder = RoleDescriptor.IndicesPrivileges.builder() .privileges(randomSubsetOf(randomIntBetween(1, 4), candidatePrivilegesNames)) .indices(generateRandomStringArray(5, randomIntBetween(3, 9), false, false)) - .allowRestrictedIndices(randomBoolean()); + .allowRestrictedIndices(randomBoolean()) + .dataStoreSelector(allowSelectors ? randomBoolean() : RoleDescriptor.IndicesPrivileges.DEFAULT_DATA_SELECTOR) + .failureStoreSelector(allowSelectors ? randomBoolean() : RoleDescriptor.IndicesPrivileges.DEFAULT_FAILURE_SELECTOR); randomDlsFls(builder); return builder; } @@ -275,6 +285,7 @@ public static class Builder { private boolean allowDescription = false; private boolean allowRemoteClusters = false; private boolean allowConfigurableClusterPrivileges = false; + private boolean allowSelectors = false; public Builder() {} @@ -313,6 +324,11 @@ public Builder allowRemoteClusters(boolean allowRemoteClusters) { return this; } + public Builder allowSelectors(boolean allowSelectors) { + this.allowSelectors = allowSelectors; + return this; + } + public RoleDescriptor build() { final RoleDescriptor.RemoteIndicesPrivileges[] remoteIndexPrivileges; if (alwaysIncludeRemoteIndices || (allowRemoteIndices && randomBoolean())) { @@ -329,7 +345,7 @@ public RoleDescriptor build() { return new RoleDescriptor( randomAlphaOfLengthBetween(3, 90), randomSubsetOf(ClusterPrivilegeResolver.names()).toArray(String[]::new), - randomIndicesPrivileges(0, 3), + randomIndicesPrivileges(0, 3, allowSelectors), randomApplicationPrivileges(), allowConfigurableClusterPrivileges ? randomClusterPrivileges() : null, generateRandomStringArray(5, randomIntBetween(2, 8), false, true), diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTests.java index 94430a4ed5bba..e366479e5ae12 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptorTests.java @@ -145,8 +145,9 @@ public void testToString() { is( "Role[name=test, cluster=[all,none]" + ", global=[{APPLICATION:manage:applications=app01,app02},{PROFILE:write:applications=app*}]" - + ", indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], allowRestrictedIndices=[false], privileges=[read]" - + ", field_security=[grant=[body,title], except=null], query={\"match_all\": {}}],]" + + ", indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], allowRestrictedIndices=[false], dataSelector=[true]" + + ", failureSelector=[false], privileges=[read], field_security=[grant=[body,title], except=null]" + + ", query={\"match_all\": {}}],]" + ", applicationPrivileges=[ApplicationResourcePrivileges[application=my_app, privileges=[read,write], resources=[*]],]" + ", runAs=[sudo], metadata=[{}], remoteIndicesPrivileges=[], remoteClusterPrivileges=[]" + ", restriction=Restriction[workflows=[]], description=]" @@ -1088,7 +1089,7 @@ public void testParseIndicesPrivilegesFailsWhenClustersFieldPresent() { } public void testIndicesPrivilegesCompareTo() { - final RoleDescriptor.IndicesPrivileges indexPrivilege = randomIndicesPrivilegesBuilder().build(); + final RoleDescriptor.IndicesPrivileges indexPrivilege = randomIndicesPrivilegesBuilder(true).build(); @SuppressWarnings({ "EqualsWithItself" }) final int actual = indexPrivilege.compareTo(indexPrivilege); assertThat(actual, equalTo(0)); @@ -1106,55 +1107,74 @@ public void testIndicesPrivilegesCompareTo() { .grantedFields(indexPrivilege.getGrantedFields() == null ? null : indexPrivilege.getGrantedFields().clone()) .deniedFields(indexPrivilege.getDeniedFields() == null ? null : indexPrivilege.getDeniedFields().clone()) .allowRestrictedIndices(indexPrivilege.allowRestrictedIndices()) + .dataStoreSelector(indexPrivilege.dataSelector()) + .failureStoreSelector(indexPrivilege.failureSelector()) .build() ), equalTo(0) ); - RoleDescriptor.IndicesPrivileges first = randomIndicesPrivilegesBuilder().allowRestrictedIndices(false).build(); - RoleDescriptor.IndicesPrivileges second = randomIndicesPrivilegesBuilder().allowRestrictedIndices(true).build(); + RoleDescriptor.IndicesPrivileges first = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(false).build(); + RoleDescriptor.IndicesPrivileges second = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(true).build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); - first = randomIndicesPrivilegesBuilder().indices("a", "b").build(); - second = randomIndicesPrivilegesBuilder().indices("b", "a").allowRestrictedIndices(first.allowRestrictedIndices()).build(); + first = randomIndicesPrivilegesBuilder(false).indices("a", "b").build(); + second = randomIndicesPrivilegesBuilder(false).indices("b", "a").allowRestrictedIndices(first.allowRestrictedIndices()).build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); - first = randomIndicesPrivilegesBuilder().privileges("read", "write").build(); - second = randomIndicesPrivilegesBuilder().allowRestrictedIndices(first.allowRestrictedIndices()) + first = randomIndicesPrivilegesBuilder(true).indices("a", "b").build(); + second = randomIndicesPrivilegesBuilder(true).indices("b", "a") + .allowRestrictedIndices(first.allowRestrictedIndices()) + .dataStoreSelector(first.dataSelector()) + .failureStoreSelector(first.failureSelector()) + .build(); + assertThat(first.compareTo(second), lessThan(0)); + assertThat(second.compareTo(first), greaterThan(0)); + + first = randomIndicesPrivilegesBuilder(true).privileges("read", "write").build(); + second = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(first.allowRestrictedIndices()) .privileges("write", "read") .indices(first.getIndices()) + .dataStoreSelector(first.dataSelector()) + .failureStoreSelector(first.failureSelector()) .build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); - first = randomIndicesPrivilegesBuilder().query(randomBoolean() ? null : "{\"match\":{\"field-a\":\"a\"}}").build(); - second = randomIndicesPrivilegesBuilder().allowRestrictedIndices(first.allowRestrictedIndices()) + first = randomIndicesPrivilegesBuilder(true).query(randomBoolean() ? null : "{\"match\":{\"field-a\":\"a\"}}").build(); + second = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(first.allowRestrictedIndices()) .query("{\"match\":{\"field-b\":\"b\"}}") .indices(first.getIndices()) .privileges(first.getPrivileges()) + .dataStoreSelector(first.dataSelector()) + .failureStoreSelector(first.failureSelector()) .build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); - first = randomIndicesPrivilegesBuilder().grantedFields(randomBoolean() ? null : new String[] { "a", "b" }).build(); - second = randomIndicesPrivilegesBuilder().allowRestrictedIndices(first.allowRestrictedIndices()) + first = randomIndicesPrivilegesBuilder(true).grantedFields(randomBoolean() ? null : new String[] { "a", "b" }).build(); + second = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(first.allowRestrictedIndices()) .grantedFields("b", "a") .indices(first.getIndices()) .privileges(first.getPrivileges()) .query(first.getQuery()) + .dataStoreSelector(first.dataSelector()) + .failureStoreSelector(first.failureSelector()) .build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); - first = randomIndicesPrivilegesBuilder().deniedFields(randomBoolean() ? null : new String[] { "a", "b" }).build(); - second = randomIndicesPrivilegesBuilder().allowRestrictedIndices(first.allowRestrictedIndices()) + first = randomIndicesPrivilegesBuilder(true).deniedFields(randomBoolean() ? null : new String[] { "a", "b" }).build(); + second = randomIndicesPrivilegesBuilder(true).allowRestrictedIndices(first.allowRestrictedIndices()) .deniedFields("b", "a") .indices(first.getIndices()) .privileges(first.getPrivileges()) .query(first.getQuery()) .grantedFields(first.getGrantedFields()) + .dataStoreSelector(first.dataSelector()) + .failureStoreSelector(first.failureSelector()) .build(); assertThat(first.compareTo(second), lessThan(0)); assertThat(second.compareTo(first), greaterThan(0)); @@ -1315,7 +1335,7 @@ public void testHasPrivilegesOtherThanIndex() { new RoleDescriptor( "name", null, - randomBoolean() ? null : randomIndicesPrivileges(1, 5), + randomBoolean() ? null : randomIndicesPrivileges(1, 5, false), null, null, null, diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageRolesPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageRolesPrivilegesTests.java index 2d47752063d9d..8d34e788aef03 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageRolesPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageRolesPrivilegesTests.java @@ -276,7 +276,7 @@ private static void assertAllowedIndexPatterns( { final PutRoleRequest putRoleRequest = new PutRoleRequest(); putRoleRequest.name(randomAlphaOfLength(3)); - putRoleRequest.addIndex(indexPatterns, privileges, null, null, null, false); + putRoleRequest.addIndex(indexPatterns, privileges, null, null, null, false, true, false); assertThat(permissionCheck(permission, "cluster:admin/xpack/security/role/put", putRoleRequest), is(expected)); } { diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index e178f4bf3eb6c..414daac139d20 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -130,7 +130,7 @@ public void setupAnonymousRoleIfNecessary() throws Exception { logger.info("anonymous is enabled. creating [native_anonymous] role"); PutRoleResponse response = new PutRoleRequestBuilder(client()).name("native_anonymous") .cluster("ALL") - .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null, null, randomBoolean()) + .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null, null, randomBoolean(), true, randomBoolean()) .get(); assertTrue(response.isCreated()); } else { @@ -240,6 +240,8 @@ private void testAddAndGetRole(String roleName) { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .description(randomAlphaOfLengthBetween(5, 20)) @@ -263,6 +265,8 @@ private void testAddAndGetRole(String roleName) { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .description(randomAlphaOfLengthBetween(5, 20)) @@ -275,6 +279,8 @@ private void testAddAndGetRole(String roleName) { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .description(randomAlphaOfLengthBetween(5, 20)) @@ -324,6 +330,8 @@ private void testAddUserAndRoleThenAuth(String username, String roleName) { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .get(); @@ -427,6 +435,8 @@ public void testCreateAndUpdateRole() { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .get(); @@ -450,6 +460,8 @@ public void testCreateAndUpdateRole() { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .get(); @@ -475,6 +487,8 @@ public void testCreateAndUpdateRole() { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .get(); @@ -521,7 +535,7 @@ public void testSnapshotDeleteRestore() { final String SECURITY_FEATURE_NAME = LocalStateSecurity.class.getSimpleName(); logger.error("--> creating role"); preparePutRole("test_role").cluster("all") - .addIndices(new String[] { "*" }, new String[] { "create_index" }, null, null, null, true) + .addIndices(new String[] { "*" }, new String[] { "create_index" }, null, null, null, true, true, randomBoolean()) .get(); logger.error("--> creating user"); preparePutUser("joe", "s3krit-password", hasher, "test_role", "snapshot_user").get(); @@ -604,6 +618,8 @@ public void testAuthenticateWithDeletedRole() { new String[] { "body", "title" }, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + randomBoolean(), randomBoolean() ) .get(); @@ -625,10 +641,10 @@ public void testAuthenticateWithDeletedRole() { public void testPutUserWithoutPassword() { // create some roles preparePutRole("admin_role").cluster("all") - .addIndices(new String[] { "*" }, new String[] { "all" }, null, null, null, randomBoolean()) + .addIndices(new String[] { "*" }, new String[] { "all" }, null, null, null, randomBoolean(), true, randomBoolean()) .get(); preparePutRole("read_role").cluster("none") - .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, randomBoolean()) + .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, randomBoolean(), true, randomBoolean()) .get(); assertThat(new GetUsersRequestBuilder(client()).usernames("joes").get().hasUsers(), is(false)); @@ -737,7 +753,7 @@ public void testUsersAndRolesDoNotInterfereWithIndicesStats() throws Exception { preparePutUser("joe", "s3krit-password", hasher, SecuritySettingsSource.TEST_ROLE).get(); } else { preparePutRole("read_role").cluster("none") - .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, randomBoolean()) + .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, randomBoolean(), true, randomBoolean()) .get(); } @@ -860,7 +876,7 @@ public void testRolesUsageStats() throws Exception { final boolean dls = randomBoolean(); PutRoleResponse putRoleResponse = new PutRoleRequestBuilder(client()).name("admin_role") .cluster("all") - .addIndices(new String[] { "*" }, new String[] { "all" }, null, null, null, randomBoolean()) + .addIndices(new String[] { "*" }, new String[] { "all" }, null, null, null, randomBoolean(), true, true) .get(); assertThat(putRoleResponse.isCreated(), is(true)); roles++; @@ -878,7 +894,16 @@ public void testRolesUsageStats() throws Exception { } roleResponse = new PutRoleRequestBuilder(client()).name("admin_role_fls") .cluster("all") - .addIndices(new String[] { "*" }, new String[] { "all" }, grantedFields, deniedFields, null, randomBoolean()) + .addIndices( + new String[] { "*" }, + new String[] { "all" }, + grantedFields, + deniedFields, + null, + randomBoolean(), + true, + randomBoolean() + ) .get(); assertThat(roleResponse.isCreated(), is(true)); roles++; @@ -893,6 +918,8 @@ public void testRolesUsageStats() throws Exception { null, null, new BytesArray("{\"match_all\": {}}"), + randomBoolean(), + true, randomBoolean() ) .get(); diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java index a4cadeb953e14..4230882fdaf88 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java @@ -33,7 +33,16 @@ public class SecurityScrollTests extends SecurityIntegTestCase { public void testScrollIsPerUser() throws Exception { createSecurityIndexWithWaitForActiveShards(); new PutRoleRequestBuilder(client()).name("scrollable") - .addIndices(new String[] { randomAlphaOfLengthBetween(4, 12) }, new String[] { "read" }, null, null, null, randomBoolean()) + .addIndices( + new String[] { randomAlphaOfLengthBetween(4, 12) }, + new String[] { "read" }, + null, + null, + null, + randomBoolean(), + true, + randomBoolean() + ) .get(); new PutUserRequestBuilder(client()).username("other") .password(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING, getFastStoredHashAlgoForTests()) diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java index dddc4e7ba1787..811de1e6bae6b 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java @@ -286,7 +286,7 @@ public void testRolesCacheIsClearedWhenPrivilegesIsChanged() { final String testRoleCacheUser = "test_role_cache_user"; final PutRoleResponse putRoleResponse = new PutRoleRequestBuilder(client).name(testRole) .cluster("all") - .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, false) + .addIndices(new String[] { "*" }, new String[] { "read" }, null, null, null, false, true, randomBoolean()) .get(); assertTrue(putRoleResponse.isCreated()); final Hasher hasher = getFastStoredHashAlgoForTests(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java index c1fe553f41334..eae537821bb8b 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatures.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Set; +import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_FAILURE_STORE_AUTH; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MIGRATION_FRAMEWORK; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ORIGIN_FEATURE; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_ROLES_METADATA_FLATTENED; @@ -23,7 +24,7 @@ public class SecurityFeatures implements FeatureSpecification { @Override public Set getFeatures() { - return Set.of(SECURITY_ROLES_METADATA_FLATTENED, SECURITY_MIGRATION_FRAMEWORK); + return Set.of(SECURITY_ROLES_METADATA_FLATTENED, SECURITY_MIGRATION_FRAMEWORK, SECURITY_FAILURE_STORE_AUTH); } @Override diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 4ae17a679d205..3701c21f15087 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -31,6 +31,7 @@ import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.internal.Client; +import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; @@ -95,6 +96,7 @@ import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecurityMigrations.ROLE_METADATA_FLATTENED_MIGRATION_VERSION; +import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_FAILURE_STORE_AUTH; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_ROLES_METADATA_FLATTENED; @@ -452,11 +454,27 @@ private void executeAsyncRolesBulkRequest(BulkRequest bulkRequest, ActionListene private Exception validateRoleDescriptor(RoleDescriptor role) { ActionRequestValidationException validationException = null; validationException = RoleDescriptorRequestValidator.validate(role, validationException); + ClusterState clusterState = clusterService.state(); if (reservedRoleNameChecker.isReserved(role.getName())) { throw addValidationError("Role [" + role.getName() + "] is reserved and may not be used.", validationException); } + if (featureService.clusterHasFeature(clusterState, SECURITY_FAILURE_STORE_AUTH) == false) { + for (var indexPriv : role.getIndicesPrivileges()) { + if (indexPriv.dataSelector() != IndicesPrivileges.DEFAULT_DATA_SELECTOR + && indexPriv.failureSelector() != IndicesPrivileges.DEFAULT_FAILURE_SELECTOR) { + throw addValidationError( + "Role [" + + role.getName() + + "] cannot use selectors because selectors are not supported by all nodes in this cluster. Please ensure " + + "all nodes are up to date in order to use selectors in role descriptors.", + validationException + ); + } + } + } + if (role.isUsingDocumentOrFieldLevelSecurity() && DOCUMENT_LEVEL_SECURITY_FEATURE.checkWithoutTracking(licenseState) == false) { return LicenseUtils.newComplianceException("field and document level security"); } else if (role.hasRemoteIndicesPrivileges() diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java index 36ea14c6e101b..8303f7003002f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java @@ -61,6 +61,7 @@ public class SecuritySystemIndices { public static final NodeFeature SECURITY_PROFILE_ORIGIN_FEATURE = new NodeFeature("security.security_profile_origin"); public static final NodeFeature SECURITY_MIGRATION_FRAMEWORK = new NodeFeature("security.migration_framework"); public static final NodeFeature SECURITY_ROLES_METADATA_FLATTENED = new NodeFeature("security.roles_metadata_flattened"); + public static final NodeFeature SECURITY_FAILURE_STORE_AUTH = new NodeFeature("security.failure_store_auth"); /** * Security managed index mappings used to be updated based on the product version. They are now updated based on per-index mappings @@ -271,6 +272,22 @@ private XContentBuilder getMainIndexMappings(SecurityMainIndexMappingVersion map builder.startObject("allow_restricted_indices"); builder.field("type", "boolean"); builder.endObject(); + + builder.startObject("selectors"); + { + builder.startObject("properties"); + { + builder.startObject("data"); + builder.field("type", "boolean"); + builder.endObject(); + + builder.startObject("failure"); + builder.field("type", "boolean"); + builder.endObject(); + } + builder.endObject(); + } + builder.endObject(); } builder.endObject(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java index 996291c52c71f..e0b264e1f16c6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java @@ -3357,7 +3357,7 @@ private static RoleDescriptor randomRoleDescriptorWithRemotePrivileges() { return new RoleDescriptor( randomAlphaOfLengthBetween(3, 90), randomSubsetOf(ClusterPrivilegeResolver.names()).toArray(String[]::new), - RoleDescriptorTestHelper.randomIndicesPrivileges(0, 3), + RoleDescriptorTestHelper.randomIndicesPrivileges(0, 3, true), RoleDescriptorTestHelper.randomApplicationPrivileges(), RoleDescriptorTestHelper.randomClusterPrivileges(), generateRandomStringArray(5, randomIntBetween(2, 8), false, true), diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java index 2b8a77d63588a..8f2ab49e91f0b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java @@ -756,7 +756,14 @@ public void testManyValidRoles() throws IOException { roleName, randomSubsetOf(ClusterPrivilegeResolver.names()).toArray(String[]::new), new IndicesPrivileges[] { - IndicesPrivileges.builder().privileges("READ").indices("*").grantedFields("*").deniedFields("foo").build() }, + IndicesPrivileges.builder() + .privileges("READ") + .indices("*") + .grantedFields("*") + .deniedFields("foo") + .dataStoreSelector(randomBoolean()) + .failureStoreSelector(randomBoolean()) + .build() }, randomApplicationPrivileges(), randomClusterPrivileges(), generateRandomStringArray(5, randomIntBetween(2, 8), true, true), @@ -835,6 +842,9 @@ public void testAllTopFieldsHaveEmptyDefaultsForUpsert() throws IOException, Ill RoleDescriptor.Fields.INDEX, RoleDescriptor.Fields.NAMES, RoleDescriptor.Fields.ALLOW_RESTRICTED_INDICES, + RoleDescriptor.Fields.SELECTORS, + RoleDescriptor.Fields.DATA_SELECTOR, + RoleDescriptor.Fields.FAILURE_SELECTOR, RoleDescriptor.Fields.RESOURCES, RoleDescriptor.Fields.QUERY, RoleDescriptor.Fields.PRIVILEGES, diff --git a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/ApiKeyBackwardsCompatibilityIT.java b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/ApiKeyBackwardsCompatibilityIT.java index 8a775c7f7d3d8..3904c28d0305a 100644 --- a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/ApiKeyBackwardsCompatibilityIT.java +++ b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/ApiKeyBackwardsCompatibilityIT.java @@ -424,7 +424,7 @@ private static RoleDescriptor randomRoleDescriptor(boolean includeRemoteDescript return new RoleDescriptor( randomAlphaOfLengthBetween(3, 90), randomSubsetOf(Set.of("all", "monitor", "none")).toArray(String[]::new), - randomIndicesPrivileges(0, 3, excludedPrivileges), + randomIndicesPrivileges(0, 3, excludedPrivileges, false), randomApplicationPrivileges(), null, generateRandomStringArray(5, randomIntBetween(2, 8), false, true), diff --git a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RolesBackwardsCompatibilityIT.java b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RolesBackwardsCompatibilityIT.java index ea1b2cdac5a1f..849e853d67bad 100644 --- a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RolesBackwardsCompatibilityIT.java +++ b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RolesBackwardsCompatibilityIT.java @@ -388,7 +388,7 @@ private static RoleDescriptor randomRoleDescriptor(boolean includeDescription, b return new RoleDescriptor( randomAlphaOfLengthBetween(3, 90), randomSubsetOf(Set.of("all", "monitor", "none")).toArray(String[]::new), - randomIndicesPrivileges(0, 3, excludedPrivileges), + randomIndicesPrivileges(0, 3, excludedPrivileges, false), randomApplicationPrivileges(), includeManageRoles ? randomManageRolesPrivileges() : null, generateRandomStringArray(5, randomIntBetween(2, 8), false, true),