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:
parent
7e3875d781
commit
fe5789ada1
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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/*");
|
||||||
|
|
Loading…
Reference in New Issue