Skip to content

Commit ff0f540

Browse files
Permission for restricted indices (#37577)
This grants the capability to grant privileges over certain restricted indices (.security and .security-6 at the moment). It also removes the special status of the superuser role. IndicesPermission.Group is extended by adding the `allow_restricted_indices` boolean flag. By default the flag is false. When it is toggled, you acknowledge that the indices under the scope of the permission group can cover the restricted indices as well. Otherwise, by default, restricted indices are ignored when granting privileges, thus rendering them hidden for authorization purposes. This effectively adds a confirmation "check-box" for roles that might grant privileges to restricted indices. The "special status" of the superuser role has been removed and coded as any other role: ``` new RoleDescriptor("superuser", new String[] { "all" }, new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder() .indices("*") .privileges("all") .allowRestrictedIndices(true) // this ----^ .build() }, new RoleDescriptor.ApplicationResourcePrivileges[] { RoleDescriptor.ApplicationResourcePrivileges.builder() .application("*") .privileges("*") .resources("*") .build() }, null, new String[] { "*" }, MetadataUtils.DEFAULT_RESERVED_METADATA, Collections.emptyMap()); ``` In the context of the Backup .security work, this allows the creation of a "curator role" that would permit listing (get settings) for all indices (including the restricted ones). That way the curator role would be able to ist and snapshot all indices, but not read or restore any of them. Supersedes #36765 Relates #34454
1 parent 5308746 commit ff0f540

File tree

41 files changed

+551
-245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+551
-245
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/AbstractIndicesPrivileges.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@
3939

4040
public abstract class AbstractIndicesPrivileges {
4141
static final ParseField NAMES = new ParseField("names");
42+
static final ParseField ALLOW_RESTRICTED_INDICES = new ParseField("allow_restricted_indices");
4243
static final ParseField PRIVILEGES = new ParseField("privileges");
4344
static final ParseField FIELD_PERMISSIONS = new ParseField("field_security");
4445
static final ParseField QUERY = new ParseField("query");
4546

4647
protected final Set<String> indices;
4748
protected final Set<String> privileges;
49+
protected final boolean allowRestrictedIndices;
4850

49-
AbstractIndicesPrivileges(Collection<String> indices, Collection<String> privileges) {
51+
AbstractIndicesPrivileges(Collection<String> indices, Collection<String> privileges, boolean allowRestrictedIndices) {
5052
if (null == indices || indices.isEmpty()) {
5153
throw new IllegalArgumentException("indices privileges must refer to at least one index name or index name pattern");
5254
}
@@ -55,6 +57,7 @@ public abstract class AbstractIndicesPrivileges {
5557
}
5658
this.indices = Collections.unmodifiableSet(new HashSet<>(indices));
5759
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
60+
this.allowRestrictedIndices = allowRestrictedIndices;
5861
}
5962

6063
/**
@@ -73,6 +76,15 @@ public Set<String> getPrivileges() {
7376
return this.privileges;
7477
}
7578

79+
/**
80+
* True if the privileges cover restricted internal indices too. Certain indices are reserved for internal services and should be
81+
* transparent to ordinary users. For that matter, when granting privileges, you also have to toggle this flag to confirm that all
82+
* indices, including restricted ones, are in the scope of this permission. By default this is false.
83+
*/
84+
public boolean allowRestrictedIndices() {
85+
return this.allowRestrictedIndices;
86+
}
87+
7688
/**
7789
* If {@code true} some documents might not be visible. Only the documents
7890
* matching {@code query} will be readable.

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@ public final class IndicesPrivileges extends AbstractIndicesPrivileges implement
5050
int i = 0;
5151
final Collection<String> indices = (Collection<String>) constructorObjects[i++];
5252
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
53+
final boolean allowRestrictedIndices = (Boolean) constructorObjects[i++];
5354
final FieldSecurity fields = (FieldSecurity) constructorObjects[i++];
5455
final String query = (String) constructorObjects[i];
55-
return new IndicesPrivileges(indices, privileges, fields, query);
56+
return new IndicesPrivileges(indices, privileges, allowRestrictedIndices, fields, query);
5657
});
5758

5859
static {
5960
PARSER.declareStringArray(constructorArg(), NAMES);
6061
PARSER.declareStringArray(constructorArg(), PRIVILEGES);
62+
PARSER.declareBoolean(constructorArg(), ALLOW_RESTRICTED_INDICES);
6163
PARSER.declareObject(optionalConstructorArg(), FieldSecurity::parse, FIELD_PERMISSIONS);
6264
PARSER.declareStringOrNull(optionalConstructorArg(), QUERY);
6365
}
@@ -66,9 +68,9 @@ public final class IndicesPrivileges extends AbstractIndicesPrivileges implement
6668
// missing query means all documents, i.e. no restrictions
6769
private final @Nullable String query;
6870

69-
private IndicesPrivileges(Collection<String> indices, Collection<String> privileges, @Nullable FieldSecurity fieldSecurity,
70-
@Nullable String query) {
71-
super(indices, privileges);
71+
private IndicesPrivileges(Collection<String> indices, Collection<String> privileges, boolean allowRestrictedIndices,
72+
@Nullable FieldSecurity fieldSecurity, @Nullable String query) {
73+
super(indices, privileges, allowRestrictedIndices);
7274
this.fieldSecurity = fieldSecurity;
7375
this.query = query;
7476
}
@@ -118,13 +120,14 @@ public boolean equals(Object o) {
118120
IndicesPrivileges that = (IndicesPrivileges) o;
119121
return indices.equals(that.indices)
120122
&& privileges.equals(that.privileges)
123+
&& allowRestrictedIndices == that.allowRestrictedIndices
121124
&& Objects.equals(this.fieldSecurity, that.fieldSecurity)
122125
&& Objects.equals(query, that.query);
123126
}
124127

125128
@Override
126129
public int hashCode() {
127-
return Objects.hash(indices, privileges, fieldSecurity, query);
130+
return Objects.hash(indices, privileges, allowRestrictedIndices, fieldSecurity, query);
128131
}
129132

130133
@Override
@@ -141,6 +144,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
141144
builder.startObject();
142145
builder.field(NAMES.getPreferredName(), indices);
143146
builder.field(PRIVILEGES.getPreferredName(), privileges);
147+
builder.field(ALLOW_RESTRICTED_INDICES.getPreferredName(), allowRestrictedIndices);
144148
if (fieldSecurity != null) {
145149
builder.field(FIELD_PERMISSIONS.getPreferredName(), fieldSecurity, params);
146150
}
@@ -170,6 +174,7 @@ public static final class Builder {
170174
Collection<String> deniedFields = null;
171175
private @Nullable
172176
String query = null;
177+
boolean allowRestrictedIndices = false;
173178

174179
public Builder() {
175180
}
@@ -223,14 +228,19 @@ public Builder query(@Nullable String query) {
223228
return this;
224229
}
225230

231+
public Builder allowRestrictedIndices(boolean allow) {
232+
this.allowRestrictedIndices = allow;
233+
return this;
234+
}
235+
226236
public IndicesPrivileges build() {
227237
final FieldSecurity fieldSecurity;
228238
if (grantedFields == null && deniedFields == null) {
229239
fieldSecurity = null;
230240
} else {
231241
fieldSecurity = new FieldSecurity(grantedFields, deniedFields);
232242
}
233-
return new IndicesPrivileges(indices, privileges, fieldSecurity, query);
243+
return new IndicesPrivileges(indices, privileges, allowRestrictedIndices, fieldSecurity, query);
234244
}
235245
}
236246

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/UserIndicesPrivileges.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class UserIndicesPrivileges extends AbstractIndicesPrivileges {
5252
static {
5353
PARSER.declareStringArray(constructorArg(), IndicesPrivileges.NAMES);
5454
PARSER.declareStringArray(constructorArg(), IndicesPrivileges.PRIVILEGES);
55+
PARSER.declareBoolean(constructorArg(), IndicesPrivileges.ALLOW_RESTRICTED_INDICES);
5556
PARSER.declareObjectArray(optionalConstructorArg(), IndicesPrivileges.FieldSecurity::parse, IndicesPrivileges.FIELD_PERMISSIONS);
5657
PARSER.declareStringArray(optionalConstructorArg(), IndicesPrivileges.QUERY);
5758
}
@@ -61,30 +62,23 @@ private static UserIndicesPrivileges buildObjectFromParserArgs(Object[] args) {
6162
return new UserIndicesPrivileges(
6263
(List<String>) args[0],
6364
(List<String>) args[1],
64-
(List<IndicesPrivileges.FieldSecurity>) args[2],
65-
(List<String>) args[3]
65+
(Boolean) args[2],
66+
(List<IndicesPrivileges.FieldSecurity>) args[3],
67+
(List<String>) args[4]
6668
);
6769
}
6870

6971
public static UserIndicesPrivileges fromXContent(XContentParser parser) throws IOException {
7072
return PARSER.parse(parser, null);
7173
}
7274

73-
public UserIndicesPrivileges(Collection<String> indices, Collection<String> privileges,
75+
public UserIndicesPrivileges(Collection<String> indices, Collection<String> privileges, boolean allowRestrictedIndices,
7476
Collection<IndicesPrivileges.FieldSecurity> fieldSecurity, Collection<String> query) {
75-
super(indices, privileges);
77+
super(indices, privileges, allowRestrictedIndices);
7678
this.fieldSecurity = fieldSecurity == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(fieldSecurity));
7779
this.query = query == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(query));
7880
}
7981

80-
public Set<String> getIndices() {
81-
return indices;
82-
}
83-
84-
public Set<String> getPrivileges() {
85-
return privileges;
86-
}
87-
8882
public Set<IndicesPrivileges.FieldSecurity> getFieldSecurity() {
8983
return fieldSecurity;
9084
}
@@ -114,20 +108,22 @@ public boolean equals(Object o) {
114108
final UserIndicesPrivileges that = (UserIndicesPrivileges) o;
115109
return Objects.equals(indices, that.indices) &&
116110
Objects.equals(privileges, that.privileges) &&
111+
allowRestrictedIndices == that.allowRestrictedIndices &&
117112
Objects.equals(fieldSecurity, that.fieldSecurity) &&
118113
Objects.equals(query, that.query);
119114
}
120115

121116
@Override
122117
public int hashCode() {
123-
return Objects.hash(indices, privileges, fieldSecurity, query);
118+
return Objects.hash(indices, privileges, allowRestrictedIndices, fieldSecurity, query);
124119
}
125120

126121
@Override
127122
public String toString() {
128123
return "UserIndexPrivilege{" +
129124
"indices=" + indices +
130125
", privileges=" + privileges +
126+
", allow_restricted_indices=" + allowRestrictedIndices +
131127
", fieldSecurity=" + fieldSecurity +
132128
", query=" + query +
133129
'}';

client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ public void testPutRole() throws IOException {
393393
final List<String> indicesPrivilegeDeniedName = Arrays.asList(randomArray(3, String[]::new, () -> randomAlphaOfLength(5)));
394394
final String indicesPrivilegeQuery = randomAlphaOfLengthBetween(0, 7);
395395
final IndicesPrivileges indicesPrivilege = IndicesPrivileges.builder().indices(indicesName).privileges(indicesPrivilegeName)
396-
.grantedFields(indicesPrivilegeGrantedName).deniedFields(indicesPrivilegeDeniedName).query(indicesPrivilegeQuery).build();
396+
.allowRestrictedIndices(randomBoolean()).grantedFields(indicesPrivilegeGrantedName).deniedFields(indicesPrivilegeDeniedName)
397+
.query(indicesPrivilegeQuery).build();
397398
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
398399
final Map<String, String> expectedParams;
399400
if (refreshPolicy != RefreshPolicy.NONE) {

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,10 @@ public void testHasPrivileges() throws Exception {
742742
HasPrivilegesRequest request = new HasPrivilegesRequest(
743743
Sets.newHashSet("monitor", "manage"),
744744
Sets.newHashSet(
745-
IndicesPrivileges.builder().indices("logstash-2018-10-05").privileges("read", "write").build(),
746-
IndicesPrivileges.builder().indices("logstash-2018-*").privileges("read").build()
745+
IndicesPrivileges.builder().indices("logstash-2018-10-05").privileges("read", "write")
746+
.allowRestrictedIndices(false).build(),
747+
IndicesPrivileges.builder().indices("logstash-2018-*").privileges("read")
748+
.allowRestrictedIndices(true).build()
747749
),
748750
null
749751
);

client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public void testFromXContent() throws IOException {
4848
" {\n" +
4949
" \"names\" : [ \"index1\", \"index2\" ],\n" +
5050
" \"privileges\" : [ \"all\" ],\n" +
51+
" \"allow_restricted_indices\" : true,\n" +
5152
" \"field_security\" : {\n" +
5253
" \"grant\" : [ \"title\", \"body\" ]}\n" +
5354
" }\n" +
@@ -81,6 +82,7 @@ public void usedDeprecatedField(String usedName, String replacedWith) {
8182
.indices("index1", "index2")
8283
.privileges("all")
8384
.grantedFields("title", "body")
85+
.allowRestrictedIndices(true)
8486
.build();
8587
assertThat(role.getIndicesPrivileges().contains(expectedIndicesPrivileges), equalTo(true));
8688
final Map<String, Object> expectedMetadata = new HashMap<>();
@@ -106,6 +108,7 @@ public void testEqualsHashCode() {
106108
.privileges("write", "monitor", "delete")
107109
.grantedFields("field1", "field2")
108110
.deniedFields("field3", "field4")
111+
.allowRestrictedIndices(true)
109112
.build();
110113
Map<String, Object> metadata = new HashMap<>();
111114
metadata.put("key", "value");
@@ -125,9 +128,10 @@ public void testEqualsHashCode() {
125128
.privileges("write", "monitor", "delete")
126129
.grantedFields("other_field1", "other_field2")
127130
.deniedFields("other_field3", "other_field4")
131+
.allowRestrictedIndices(false)
128132
.build();
129133
Map<String, Object> metadata2 = new HashMap<>();
130-
metadata.put("other_key", "other_value");
134+
metadata2.put("other_key", "other_value");
131135
final Role role2 = Role.builder()
132136
.name("role2_name")
133137
.clusterPrivileges("monitor", "manage", "manage_saml")
@@ -158,6 +162,7 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
158162
.privileges("write", "monitor", "delete")
159163
.grantedFields("field1", "field2")
160164
.deniedFields("field3", "field4")
165+
.allowRestrictedIndices(true)
161166
.build();
162167
Map<String, Object> metadata = new HashMap<String, Object>();
163168
metadata.put("key", "value");
@@ -179,6 +184,7 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
179184
.privileges("write", "monitor", "delete")
180185
.grantedFields("field1", "field2")
181186
.deniedFields("field3", "field4")
187+
.allowRestrictedIndices(false)
182188
.build();
183189
Map<String, Object> metadata = new HashMap<String, Object>();
184190
metadata.put("key", "value");

client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetUserPrivilegesResponseTests.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,18 @@ public void testParse() throws Exception {
4848
" {\"application\":{\"manage\":{\"applications\":[\"apps-*\"]}}}" +
4949
"]," +
5050
"\"indices\":[" +
51-
" {\"names\":[\"test-1-*\"],\"privileges\":[\"read\"]}," +
52-
" {\"names\":[\"test-4-*\"],\"privileges\":[\"read\"],\"field_security\":[{\"grant\":[\"*\"],\"except\":[\"private-*\"]}]}," +
53-
" {\"names\":[\"test-6-*\",\"test-7-*\"],\"privileges\":[\"read\"]," +
51+
" {\"names\":[\"test-1-*\"],\"privileges\":[\"read\"],\"allow_restricted_indices\": false}," +
52+
" {\"names\":[\"test-4-*\"],\"privileges\":[\"read\"],\"allow_restricted_indices\": true," +
53+
" \"field_security\":[{\"grant\":[\"*\"],\"except\":[\"private-*\"]}]}," +
54+
" {\"names\":[\"test-6-*\",\"test-7-*\"],\"privileges\":[\"read\"],\"allow_restricted_indices\": true," +
5455
" \"query\":[\"{\\\"term\\\":{\\\"test\\\":true}}\"]}," +
55-
" {\"names\":[\"test-2-*\"],\"privileges\":[\"read\"]," +
56+
" {\"names\":[\"test-2-*\"],\"privileges\":[\"read\"],\"allow_restricted_indices\": false," +
5657
" \"field_security\":[{\"grant\":[\"*\"],\"except\":[\"secret-*\",\"private-*\"]},{\"grant\":[\"apps-*\"]}]," +
5758
" \"query\":[\"{\\\"term\\\":{\\\"test\\\":true}}\",\"{\\\"term\\\":{\\\"apps\\\":true}}\"]}," +
58-
" {\"names\":[\"test-3-*\",\"test-6-*\"],\"privileges\":[\"read\",\"write\"]}," +
59-
" {\"names\":[\"test-3-*\",\"test-4-*\",\"test-5-*\"],\"privileges\":[\"read\"]," +
59+
" {\"names\":[\"test-3-*\",\"test-6-*\"],\"privileges\":[\"read\",\"write\"],\"allow_restricted_indices\": true}," +
60+
" {\"names\":[\"test-3-*\",\"test-4-*\",\"test-5-*\"],\"privileges\":[\"read\"],\"allow_restricted_indices\": false," +
6061
" \"field_security\":[{\"grant\":[\"test-*\"]}]}," +
61-
" {\"names\":[\"test-1-*\",\"test-9-*\"],\"privileges\":[\"all\"]}" +
62+
" {\"names\":[\"test-1-*\",\"test-9-*\"],\"privileges\":[\"all\"],\"allow_restricted_indices\": true}" +
6263
"]," +
6364
"\"applications\":[" +
6465
" {\"application\":\"app-dne\",\"privileges\":[\"all\"],\"resources\":[\"*\"]}," +
@@ -80,12 +81,14 @@ public void testParse() throws Exception {
8081
assertThat(response.getIndicesPrivileges().size(), equalTo(7));
8182
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getIndices(), contains("test-1-*"));
8283
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getPrivileges(), contains("read"));
84+
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).allowRestrictedIndices(), equalTo(false));
8385
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getFieldSecurity(), emptyIterable());
8486
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getQueries(), emptyIterable());
8587

8688
final UserIndicesPrivileges test4Privilege = Iterables.get(response.getIndicesPrivileges(), 1);
8789
assertThat(test4Privilege.getIndices(), contains("test-4-*"));
8890
assertThat(test4Privilege.getPrivileges(), contains("read"));
91+
assertThat(test4Privilege.allowRestrictedIndices(), equalTo(true));
8992
assertThat(test4Privilege.getFieldSecurity(), iterableWithSize(1));
9093
final IndicesPrivileges.FieldSecurity test4FLS = test4Privilege.getFieldSecurity().iterator().next();
9194
assertThat(test4FLS.getGrantedFields(), contains("*"));
@@ -95,6 +98,7 @@ public void testParse() throws Exception {
9598
final UserIndicesPrivileges test2Privilege = Iterables.get(response.getIndicesPrivileges(), 3);
9699
assertThat(test2Privilege.getIndices(), contains("test-2-*"));
97100
assertThat(test2Privilege.getPrivileges(), contains("read"));
101+
assertThat(test2Privilege.allowRestrictedIndices(), equalTo(false));
98102
assertThat(test2Privilege.getFieldSecurity(), iterableWithSize(2));
99103
final Iterator<IndicesPrivileges.FieldSecurity> test2FLSIter = test2Privilege.getFieldSecurity().iterator();
100104
final IndicesPrivileges.FieldSecurity test2FLS1 = test2FLSIter.next();
@@ -110,6 +114,7 @@ public void testParse() throws Exception {
110114

111115
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getIndices(), contains("test-1-*", "test-9-*"));
112116
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getPrivileges(), contains("all"));
117+
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).allowRestrictedIndices(), equalTo(true));
113118
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getFieldSecurity(), emptyIterable());
114119
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getQueries(), emptyIterable());
115120

0 commit comments

Comments
 (0)