Fix Has Privilege API check on restricted indices (#41226)

The Has Privileges API allows to tap into the authorization process, to validate
privileges without actually running the operations to be authorized. This commit
fixes a bug, in which the Has Privilege API returned spurious results when checking
for index privileges over restricted indices (currently .security, .security-6,
.security-7). The actual authorization process is not affected by the bug.
This commit is contained in:
Albert Zaharovits 2019-04-25 12:02:09 +03:00
parent 7e3875d781
commit fe5789ada1
4 changed files with 165 additions and 26 deletions

View File

@ -29,11 +29,13 @@ privilege is assigned to the user.
`index`:: `index`::
`names`::: (list) A list of indices. `names`::: (list) A list of indices.
`allow_restricted_indices`::: (boolean) If `names` contains internal restricted `allow_restricted_indices`::: (boolean) This needs to be set to `true` (default
that also have to be covered by the has-privilege check, then this has to be is `false`) if using wildcards or regexps for patterns that cover restricted
set to `true`. By default this is `false` because restricted indices should indices. Implicitly, restricted indices do not match index patterns because
generaly not be "visible" to APIs. For most use cases it is safe to ignore restricted indices usually have limited privileges and including them in
this parameter. pattern tests would render most such tests `false`. If restricted indices are
explicitly included in the `names` list, privileges will be checked against
them regardless of the value of `allow_restricted_indices`.
`privileges`::: (list) A list of the privileges that you want to check for the `privileges`::: (list) A list of the privileges that you want to check for the
specified indices. specified indices.

View File

@ -138,27 +138,40 @@ public final class IndicesPermission {
final ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder = ResourcePrivilegesMap.builder(); final ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder = ResourcePrivilegesMap.builder();
final Map<IndicesPermission.Group, Automaton> predicateCache = new HashMap<>(); final Map<IndicesPermission.Group, Automaton> predicateCache = new HashMap<>();
for (String forIndexPattern : checkForIndexPatterns) { for (String forIndexPattern : checkForIndexPatterns) {
final Automaton checkIndexAutomaton = IndicesPermission.Group.buildIndexMatcherAutomaton(allowRestrictedIndices, Automaton checkIndexAutomaton = Automatons.patterns(forIndexPattern);
forIndexPattern); if (false == allowRestrictedIndices && false == RestrictedIndicesNames.RESTRICTED_NAMES.contains(forIndexPattern)) {
Automaton allowedIndexPrivilegesAutomaton = null; checkIndexAutomaton = Automatons.minusAndMinimize(checkIndexAutomaton, RestrictedIndicesNames.NAMES_AUTOMATON);
for (Group group : groups) { }
final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group, if (false == Operations.isEmpty(checkIndexAutomaton)) {
g -> IndicesPermission.Group.buildIndexMatcherAutomaton(g.allowRestrictedIndices(), g.indices())); Automaton allowedIndexPrivilegesAutomaton = null;
if (Operations.subsetOf(checkIndexAutomaton, groupIndexAutomaton)) { for (Group group : groups) {
if (allowedIndexPrivilegesAutomaton != null) { final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group,
allowedIndexPrivilegesAutomaton = Automatons g -> IndicesPermission.Group.buildIndexMatcherAutomaton(g.allowRestrictedIndices(), g.indices()));
.unionAndMinimize(Arrays.asList(allowedIndexPrivilegesAutomaton, group.privilege().getAutomaton())); if (Operations.subsetOf(checkIndexAutomaton, groupIndexAutomaton)) {
} else { if (allowedIndexPrivilegesAutomaton != null) {
allowedIndexPrivilegesAutomaton = group.privilege().getAutomaton(); allowedIndexPrivilegesAutomaton = Automatons
.unionAndMinimize(Arrays.asList(allowedIndexPrivilegesAutomaton, group.privilege().getAutomaton()));
} else {
allowedIndexPrivilegesAutomaton = group.privilege().getAutomaton();
}
} }
} }
} for (String privilege : checkForPrivileges) {
for (String privilege : checkForPrivileges) { IndexPrivilege indexPrivilege = IndexPrivilege.get(Collections.singleton(privilege));
IndexPrivilege indexPrivilege = IndexPrivilege.get(Collections.singleton(privilege)); if (allowedIndexPrivilegesAutomaton != null
if (allowedIndexPrivilegesAutomaton != null && Operations.subsetOf(indexPrivilege.getAutomaton(), allowedIndexPrivilegesAutomaton)) {
&& Operations.subsetOf(indexPrivilege.getAutomaton(), allowedIndexPrivilegesAutomaton)) { resourcePrivilegesMapBuilder.addResourcePrivilege(forIndexPattern, privilege, Boolean.TRUE);
resourcePrivilegesMapBuilder.addResourcePrivilege(forIndexPattern, privilege, Boolean.TRUE); } else {
} else { resourcePrivilegesMapBuilder.addResourcePrivilege(forIndexPattern, privilege, Boolean.FALSE);
}
}
} else {
// the index pattern produced the empty automaton, presumably because the requested pattern expands exclusively inside the
// restricted indices namespace - a namespace of indices that are normally hidden when granting/checking privileges - and
// the pattern was not marked as `allowRestrictedIndices`. We try to anticipate this by considering _explicit_ restricted
// indices even if `allowRestrictedIndices` is false.
// TODO The `false` result is a _safe_ default but this is actually an error. Make it an error.
for (String privilege : checkForPrivileges) {
resourcePrivilegesMapBuilder.addResourcePrivilege(forIndexPattern, privilege, Boolean.FALSE); resourcePrivilegesMapBuilder.addResourcePrivilege(forIndexPattern, privilege, Boolean.FALSE);
} }
} }

View File

@ -49,7 +49,6 @@ public class Role {
this.runAs = Objects.requireNonNull(runAs); this.runAs = Objects.requireNonNull(runAs);
} }
public String[] names() { public String[] names() {
return names; return names;
} }
@ -116,7 +115,7 @@ public class Role {
* @return an instance of {@link ResourcePrivilegesMap} * @return an instance of {@link ResourcePrivilegesMap}
*/ */
public ResourcePrivilegesMap checkIndicesPrivileges(Set<String> checkForIndexPatterns, boolean allowRestrictedIndices, public ResourcePrivilegesMap checkIndicesPrivileges(Set<String> checkForIndexPatterns, boolean allowRestrictedIndices,
Set<String> checkForPrivileges) { Set<String> checkForPrivileges) {
return indices.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges); return indices.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges);
} }

View File

@ -48,6 +48,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivileg
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges;
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.User;
@ -493,6 +494,130 @@ public class RBACEngineTests extends ESTestCase {
)); ));
} }
public void testCheckExplicitRestrictedIndexPermissions() throws Exception {
User user = new User(randomAlphaOfLengthBetween(4, 12));
Authentication authentication = mock(Authentication.class);
when(authentication.getUser()).thenReturn(user);
final boolean restrictedIndexPermission = randomBoolean();
final boolean restrictedMonitorPermission = randomBoolean();
Role role = Role.builder("role")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, restrictedIndexPermission, ".sec*")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, restrictedMonitorPermission, ".security*")
.build();
RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null);
String explicitRestrictedIndex = randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES);
HasPrivilegesResponse response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(new String[] {".secret-non-restricted", explicitRestrictedIndex})
.privileges("index", "monitor")
.allowRestrictedIndices(false) // explicit false for test
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".secret-non-restricted") // matches ".sec*" but not ".security*"
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build(),
ResourcePrivileges.builder(explicitRestrictedIndex) // matches both ".sec*" and ".security*"
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", restrictedIndexPermission).put("monitor", restrictedMonitorPermission).map()).build()));
explicitRestrictedIndex = randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES);
response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(new String[] {".secret-non-restricted", explicitRestrictedIndex})
.privileges("index", "monitor")
.allowRestrictedIndices(true) // explicit true for test
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".secret-non-restricted") // matches ".sec*" but not ".security*"
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build(),
ResourcePrivileges.builder(explicitRestrictedIndex) // matches both ".sec*" and ".security*"
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", restrictedIndexPermission).put("monitor", restrictedMonitorPermission).map()).build()));
}
public void testCheckRestrictedIndexWildcardPermissions() throws Exception {
User user = new User(randomAlphaOfLengthBetween(4, 12));
Authentication authentication = mock(Authentication.class);
when(authentication.getUser()).thenReturn(user);
Role role = Role.builder("role")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, false, ".sec*")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, true, ".security*")
.build();
RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null);
HasPrivilegesResponse response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(".sec*", ".security*")
.privileges("index", "monitor")
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".sec*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build(),
ResourcePrivileges.builder(".security*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", true).map()).build()
));
response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(".sec*", ".security*")
.privileges("index", "monitor")
.allowRestrictedIndices(true)
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".sec*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", false).put("monitor", false).map()).build(),
ResourcePrivileges.builder(".security*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", false).put("monitor", true).map()).build()
));
role = Role.builder("role")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, true, ".sec*")
.add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, false, ".security*")
.build();
authzInfo = new RBACAuthorizationInfo(role, null);
response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(".sec*", ".security*")
.privileges("index", "monitor")
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".sec*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build(),
ResourcePrivileges.builder(".security*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", true).map()).build()
));
response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices(".sec*", ".security*")
.privileges("index", "monitor")
.allowRestrictedIndices(true)
.build(), authentication, authzInfo, Collections.emptyList(), Strings.EMPTY_ARRAY);
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
ResourcePrivileges.builder(".sec*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build(),
ResourcePrivileges.builder(".security*")
.addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("monitor", false).map()).build()
));
}
public void testCheckingApplicationPrivilegesOnDifferentApplicationsAndResources() throws Exception { public void testCheckingApplicationPrivilegesOnDifferentApplicationsAndResources() throws Exception {
List<ApplicationPrivilegeDescriptor> privs = new ArrayList<>(); List<ApplicationPrivilegeDescriptor> privs = new ArrayList<>();
final ApplicationPrivilege app1Read = defineApplicationPrivilege(privs, "app1", "read", "data:read/*"); final ApplicationPrivilege app1Read = defineApplicationPrivilege(privs, "app1", "read", "data:read/*");