Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Role interface and allow multiple level of limiting #81403

Merged
merged 4 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import org.elasticsearch.core.Nullable;
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
import org.elasticsearch.xpack.core.security.authz.store.RoleReference;
import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection;
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.List;
import java.util.Map;

import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES;
Expand Down Expand Up @@ -85,27 +85,23 @@ public Map<String, Object> getMetadata() {
return metadata;
}

/**
* Return a List of RoleReferences that represents role definitions associated to the subject.
* The final role of this subject should be the intersection of all role references in the list.
*/
public List<RoleReference> getRoleReferences(@Nullable AnonymousUser anonymousUser) {
public RoleReferenceIntersection getRoleReferenceIntersection(@Nullable AnonymousUser anonymousUser) {
switch (type) {
case USER:
return buildRoleReferencesForUser(anonymousUser);
case API_KEY:
return buildRoleReferencesForApiKey();
case SERVICE_ACCOUNT:
return List.of(new RoleReference.ServiceAccountRoleReference(user.principal()));
return new RoleReferenceIntersection(new RoleReference.ServiceAccountRoleReference(user.principal()));
default:
assert false : "unknown subject type: [" + type + "]";
throw new IllegalStateException("unknown subject type: [" + type + "]");
}
}

private List<RoleReference> buildRoleReferencesForUser(AnonymousUser anonymousUser) {
private RoleReferenceIntersection buildRoleReferencesForUser(AnonymousUser anonymousUser) {
if (user.equals(anonymousUser)) {
return List.of(new RoleReference.NamedRoleReference(user.roles()));
return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(user.roles()));
}
final String[] allRoleNames;
if (anonymousUser == null || false == anonymousUser.enabled()) {
Expand All @@ -117,10 +113,10 @@ private List<RoleReference> buildRoleReferencesForUser(AnonymousUser anonymousUs
}
allRoleNames = ArrayUtils.concat(user.roles(), anonymousUser.roles());
}
return List.of(new RoleReference.NamedRoleReference(allRoleNames));
return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(allRoleNames));
}

private List<RoleReference> buildRoleReferencesForApiKey() {
private RoleReferenceIntersection buildRoleReferencesForApiKey() {
if (version.before(VERSION_API_KEY_ROLES_AS_BYTES)) {
return buildRolesReferenceForApiKeyBwc();
}
Expand All @@ -136,16 +132,19 @@ private List<RoleReference> buildRoleReferencesForApiKey() {
"apikey_limited_role"
);
if (isEmptyRoleDescriptorsBytes(roleDescriptorsBytes)) {
return List.of(limitedByRoleReference);
return new RoleReferenceIntersection(limitedByRoleReference);
}
return List.of(new RoleReference.ApiKeyRoleReference(apiKeyId, roleDescriptorsBytes, "apikey_role"), limitedByRoleReference);
return new RoleReferenceIntersection(
new RoleReference.ApiKeyRoleReference(apiKeyId, roleDescriptorsBytes, "apikey_role"),
limitedByRoleReference
);
}

private boolean isEmptyRoleDescriptorsBytes(BytesReference roleDescriptorsBytes) {
return roleDescriptorsBytes == null || (roleDescriptorsBytes.length() == 2 && "{}".equals(roleDescriptorsBytes.utf8ToString()));
}

private List<RoleReference> buildRolesReferenceForApiKeyBwc() {
private RoleReferenceIntersection buildRolesReferenceForApiKeyBwc() {
final String apiKeyId = (String) metadata.get(AuthenticationField.API_KEY_ID_KEY);
final Map<String, Object> roleDescriptorsMap = getRoleDescriptorMap(API_KEY_ROLE_DESCRIPTORS_KEY);
final Map<String, Object> limitedByRoleDescriptorsMap = getRoleDescriptorMap(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY);
Expand All @@ -158,9 +157,9 @@ private List<RoleReference> buildRolesReferenceForApiKeyBwc() {
"_limited_role_desc"
);
if (roleDescriptorsMap == null || roleDescriptorsMap.isEmpty()) {
return List.of(limitedByRoleReference);
return new RoleReferenceIntersection(limitedByRoleReference);
} else {
return List.of(
return new RoleReferenceIntersection(
new RoleReference.BwcApiKeyRoleReference(apiKeyId, roleDescriptorsMap, "_role_desc"),
limitedByRoleReference
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,30 @@
import java.util.Set;
import java.util.function.Predicate;

// TODO: extract a Role interface so limitedRole can be more than 2 levels
/**
* A {@link Role} limited by another role.<br>
* The effective permissions returned on {@link #authorize(String, Set, Map, FieldPermissionsCache)} call would be limited by the
* provided role.
*/
public final class LimitedRole extends Role {
private final Role limitedBy;

LimitedRole(
ClusterPermission cluster,
IndicesPermission indices,
ApplicationPermission application,
RunAsPermission runAs,
Role limitedBy
) {
super(Objects.requireNonNull(limitedBy, "limiting role is required").names(), cluster, indices, application, runAs);
this.limitedBy = limitedBy;
public final class LimitedRole implements Role {
private final Role baseRole;
private final Role limitedByRole;

/**
* Create a new role defined by given role and the limited role.
*
* @param baseRole existing role {@link Role}
* @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role}
*/
public LimitedRole(Role baseRole, Role limitedByRole) {
this.baseRole = Objects.requireNonNull(baseRole);
this.limitedByRole = Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role");
}

@Override
public String[] names() {
// TODO: this is to retain existing behaviour, but it is not accurate
return limitedByRole.names();
}

@Override
Expand All @@ -64,7 +70,7 @@ public RunAsPermission runAs() {

@Override
public boolean hasFieldOrDocumentLevelSecurity() {
return super.hasFieldOrDocumentLevelSecurity() || limitedBy.hasFieldOrDocumentLevelSecurity();
return baseRole.hasFieldOrDocumentLevelSecurity() || limitedByRole.hasFieldOrDocumentLevelSecurity();
}

@Override
Expand All @@ -75,16 +81,13 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
if (super.equals(o) == false) {
return false;
}
LimitedRole that = (LimitedRole) o;
return this.limitedBy.equals(that.limitedBy);
return baseRole.equals(that.baseRole) && this.limitedByRole.equals(that.limitedByRole);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), limitedBy);
return Objects.hash(baseRole, limitedByRole);
}

@Override
Expand All @@ -94,13 +97,13 @@ public IndicesAccessControl authorize(
Map<String, IndexAbstraction> aliasAndIndexLookup,
FieldPermissionsCache fieldPermissionsCache
) {
IndicesAccessControl indicesAccessControl = super.authorize(
IndicesAccessControl indicesAccessControl = baseRole.authorize(
action,
requestedIndicesOrAliases,
aliasAndIndexLookup,
fieldPermissionsCache
);
IndicesAccessControl limitedByIndicesAccessControl = limitedBy.authorize(
IndicesAccessControl limitedByIndicesAccessControl = limitedByRole.authorize(
action,
requestedIndicesOrAliases,
aliasAndIndexLookup,
Expand All @@ -115,15 +118,15 @@ public IndicesAccessControl authorize(
*/
@Override
public Predicate<IndexAbstraction> allowedIndicesMatcher(String action) {
Predicate<IndexAbstraction> predicate = super.indices().allowedIndicesMatcher(action);
predicate = predicate.and(limitedBy.indices().allowedIndicesMatcher(action));
Predicate<IndexAbstraction> predicate = baseRole.indices().allowedIndicesMatcher(action);
predicate = predicate.and(limitedByRole.indices().allowedIndicesMatcher(action));
return predicate;
}

@Override
public Automaton allowedActionsMatcher(String index) {
final Automaton allowedMatcher = super.allowedActionsMatcher(index);
final Automaton limitedByMatcher = limitedBy.allowedActionsMatcher(index);
final Automaton allowedMatcher = baseRole.allowedActionsMatcher(index);
final Automaton limitedByMatcher = limitedByRole.allowedActionsMatcher(index);
return Automatons.intersectAndMinimize(allowedMatcher, limitedByMatcher);
}

Expand All @@ -135,7 +138,7 @@ public Automaton allowedActionsMatcher(String index) {
*/
@Override
public boolean checkIndicesAction(String action) {
return super.checkIndicesAction(action) && limitedBy.checkIndicesAction(action);
return baseRole.checkIndicesAction(action) && limitedByRole.checkIndicesAction(action);
}

/**
Expand All @@ -155,12 +158,9 @@ public ResourcePrivilegesMap checkIndicesPrivileges(
boolean allowRestrictedIndices,
Set<String> checkForPrivileges
) {
ResourcePrivilegesMap resourcePrivilegesMap = super.indices().checkResourcePrivileges(
checkForIndexPatterns,
allowRestrictedIndices,
checkForPrivileges
);
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.indices()
ResourcePrivilegesMap resourcePrivilegesMap = baseRole.indices()
.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges);
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.indices()
.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges);
return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole);
}
Expand All @@ -177,7 +177,8 @@ public ResourcePrivilegesMap checkIndicesPrivileges(
*/
@Override
public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) {
return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication);
return baseRole.checkClusterAction(action, request, authentication)
&& limitedByRole.checkClusterAction(action, request, authentication);
}

/**
Expand All @@ -189,7 +190,7 @@ public boolean checkClusterAction(String action, TransportRequest request, Authe
*/
@Override
public boolean grants(ClusterPrivilege clusterPrivilege) {
return super.grants(clusterPrivilege) && limitedBy.grants(clusterPrivilege);
return baseRole.grants(clusterPrivilege) && limitedByRole.grants(clusterPrivilege);
}

/**
Expand All @@ -212,31 +213,15 @@ public ResourcePrivilegesMap checkApplicationResourcePrivileges(
Set<String> checkForPrivilegeNames,
Collection<ApplicationPrivilegeDescriptor> storedPrivileges
) {
ResourcePrivilegesMap resourcePrivilegesMap = super.application().checkResourcePrivileges(
applicationName,
checkForResources,
checkForPrivilegeNames,
storedPrivileges
);
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.application()
ResourcePrivilegesMap resourcePrivilegesMap = baseRole.application()
.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges);
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.application()
.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges);
return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole);
}

@Override
public boolean checkRunAs(String runAs) {
return super.checkRunAs(runAs) && limitedBy.checkRunAs(runAs);
}

/**
* Create a new role defined by given role and the limited role.
*
* @param fromRole existing role {@link Role}
* @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role}
* @return {@link LimitedRole}
*/
public static LimitedRole createLimitedRole(Role fromRole, Role limitedByRole) {
Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role");
return new LimitedRole(fromRole.cluster(), fromRole.indices(), fromRole.application(), fromRole.runAs(), limitedByRole);
return baseRole.checkRunAs(runAs) && limitedByRole.checkRunAs(runAs);
}
}
Loading