Skip to content

Commit

Permalink
Add index permissions api token eval
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Ho <[email protected]>
  • Loading branch information
derek-ho committed Dec 31, 2024
1 parent ad63974 commit 73eb2ab
Showing 1 changed file with 111 additions and 61 deletions.
172 changes: 111 additions & 61 deletions src/main/java/org/opensearch/security/privileges/ActionPrivileges.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,28 +164,6 @@ public PrivilegesEvaluatorResponse hasIndexPrivilege(
return response;
}

// API Token Authz
// TODO: this is very naive implementation
if (context.getUser().getName().startsWith("apitoken")) {
String jti = context.getUser().getName().split(":")[1];
if (context.getApiTokenIndexListenerCache().getJtis().get(jti) != null) {
List<ApiToken.IndexPermission> indexPermissions = context.getApiTokenIndexListenerCache()
.getJtis()
.get(jti)
.getIndexPermission();

boolean hasPermission = indexPermissions.stream().anyMatch(permission -> {
boolean hasAllActions = new HashSet<>(permission.getAllowedActions()).containsAll(actions);
boolean hasAllIndices = new HashSet<>(permission.getIndexPatterns()).containsAll(resolvedIndices.getAllIndices());
return hasAllActions && hasAllIndices;
});

if (hasPermission) {
return PrivilegesEvaluatorResponse.ok();
}
}
}

if (!resolvedIndices.isLocalAll() && resolvedIndices.getAllIndices().isEmpty()) {
// This is necessary for requests which operate on remote indices.
// Access control for the remote indices will be performed on the remote cluster.
Expand Down Expand Up @@ -436,54 +414,55 @@ PrivilegesEvaluatorResponse providesPrivilege(PrivilegesEvaluationContext contex
}

// 4: Evaluate api tokens
return providesClusterPrivilegeForApiToken(context, Set.of(action), false);
return apiTokenProvidesClusterPrivilege(context, Set.of(action), false);
}

/**
* Evaluates cluster privileges for api tokens. It does so by checking exact match, regex match, * match, and action group match in a non-optimized, naive way.
* First it expands all action groups to get all the actions and patterns of actions. Then it checks * if not an explicit check, then for exact match, then for pattern match.
*/
PrivilegesEvaluatorResponse providesClusterPrivilegeForApiToken(
PrivilegesEvaluatorResponse apiTokenProvidesClusterPrivilege(
PrivilegesEvaluationContext context,
Set<String> actions,
Boolean explicit
) {
String userName = context.getUser().getName();
String jti = context.getUser().getName().split(":")[1];
if (userName.startsWith("apitoken") && context.getApiTokenIndexListenerCache().getJtis().get(jti) != null) {
List<String> clusterPermissions = context.getApiTokenIndexListenerCache().getJtis().get(jti).getClusterPerm();
// Expand the action groups
ImmutableSet<String> resolvedClusterPermissions = actionGroups.resolve(
context.getApiTokenIndexListenerCache().getJtis().get(jti).getClusterPerm()
);
log.info(resolvedClusterPermissions);
if (userName.startsWith("apitoken") && userName.contains(":")) {
String jti = context.getUser().getName().split(":")[1];

Check warning on line 431 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L431

Added line #L431 was not covered by tests
if (context.getApiTokenIndexListenerCache().getJtis().get(jti) != null) {
// Expand the action groups
ImmutableSet<String> resolvedClusterPermissions = actionGroups.resolve(
context.getApiTokenIndexListenerCache().getJtis().get(jti).getClusterPerm()

Check warning on line 435 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L434-L435

Added lines #L434 - L435 were not covered by tests
);

// Check for wildcard permission
if (!explicit) {
if (resolvedClusterPermissions.contains("*")) {
return PrivilegesEvaluatorResponse.ok();
// Check for wildcard permission
if (!explicit) {
if (resolvedClusterPermissions.contains("*")) {
return PrivilegesEvaluatorResponse.ok();

Check warning on line 441 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L441

Added line #L441 was not covered by tests
}
}
}

// Check for exact match
if (!Collections.disjoint(resolvedClusterPermissions, actions)) {
return PrivilegesEvaluatorResponse.ok();
}

// Check for pattern matches (like "cluster:*")
for (String permission : resolvedClusterPermissions) {
// Skip exact matches as we already checked those
if (!permission.contains("*")) {
continue;
// Check for exact match
if (!Collections.disjoint(resolvedClusterPermissions, actions)) {
return PrivilegesEvaluatorResponse.ok();

Check warning on line 447 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L447

Added line #L447 was not covered by tests
}

WildcardMatcher permissionMatcher = WildcardMatcher.from(permission);
for (String action : actions) {
if (permissionMatcher.test(action)) {
return PrivilegesEvaluatorResponse.ok();
// Check for pattern matches (like "cluster:*")
for (String permission : resolvedClusterPermissions) {
// Skip exact matches as we already checked those
if (!permission.contains("*")) {
continue;

Check warning on line 454 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L454

Added line #L454 was not covered by tests
}

WildcardMatcher permissionMatcher = WildcardMatcher.from(permission);

Check warning on line 457 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L457

Added line #L457 was not covered by tests
for (String action : actions) {
if (permissionMatcher.test(action)) {
return PrivilegesEvaluatorResponse.ok();

Check warning on line 460 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L460

Added line #L460 was not covered by tests
}
}
}

Check warning on line 463 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L462-L463

Added lines #L462 - L463 were not covered by tests
}

}
if (actions.size() == 1) {
return PrivilegesEvaluatorResponse.insufficient(actions.iterator().next());
Expand Down Expand Up @@ -522,7 +501,7 @@ PrivilegesEvaluatorResponse providesExplicitPrivilege(PrivilegesEvaluationContex
}
}

return providesClusterPrivilegeForApiToken(context, Set.of(action), true);
return apiTokenProvidesClusterPrivilege(context, Set.of(action), true);
}

/**
Expand Down Expand Up @@ -558,7 +537,7 @@ PrivilegesEvaluatorResponse providesAnyPrivilege(PrivilegesEvaluationContext con
}
}

return providesClusterPrivilegeForApiToken(context, actions, false);
return apiTokenProvidesClusterPrivilege(context, actions, false);
}
}

Expand Down Expand Up @@ -617,6 +596,8 @@ static class IndexPrivileges {
*/
private final ImmutableMap<String, ImmutableMap<String, IndexPattern>> rolesToExplicitActionToIndexPattern;

private final FlattenedActionGroups actionGroups;

/**
* Creates pre-computed index privileges based on the given parameters.
* <p>
Expand Down Expand Up @@ -754,6 +735,7 @@ static class IndexPrivileges {

this.wellKnownIndexActions = wellKnownIndexActions;
this.explicitlyRequiredIndexActions = explicitlyRequiredIndexActions;
this.actionGroups = actionGroups;
}

/**
Expand Down Expand Up @@ -856,13 +838,7 @@ PrivilegesEvaluatorResponse providesPrivilege(
return PrivilegesEvaluatorResponse.partiallyOk(availableIndices, checkTable).evaluationExceptions(exceptions);
}

return PrivilegesEvaluatorResponse.insufficient(checkTable)
.reason(
resolvedIndices.getAllIndices().size() == 1
? "Insufficient permissions for the referenced index"
: "None of " + resolvedIndices.getAllIndices().size() + " referenced indices has sufficient permissions"
)
.evaluationExceptions(exceptions);
return apiTokenProvidesIndexPrivilege(checkTable, context, exceptions, resolvedIndices, actions, false);
}

/**
Expand Down Expand Up @@ -928,8 +904,82 @@ PrivilegesEvaluatorResponse providesExplicitPrivilege(
}
}

return apiTokenProvidesIndexPrivilege(checkTable, context, exceptions, resolvedIndices, actions, true);
}

PrivilegesEvaluatorResponse apiTokenProvidesIndexPrivilege(
CheckTable<String, String> checkTable,
PrivilegesEvaluationContext context,
List<PrivilegesEvaluationException> exceptions,
IndexResolverReplacer.Resolved resolvedIndices,
Set<String> actions,
Boolean explicit
) {
String userName = context.getUser().getName();
if (userName.startsWith("apitoken") && userName.contains(":")) {
String jti = context.getUser().getName().split(":")[1];

Check warning on line 920 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L920

Added line #L920 was not covered by tests
if (context.getApiTokenIndexListenerCache().getJtis().get(jti) != null) {
List<ApiToken.IndexPermission> indexPermissions = context.getApiTokenIndexListenerCache()
.getJtis()
.get(jti)
.getIndexPermission();

Check warning on line 925 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L922-L925

Added lines #L922 - L925 were not covered by tests

for (String concreteIndex : resolvedIndices.getAllIndices()) {
boolean indexHasAllPermissions = false;

Check warning on line 928 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L928

Added line #L928 was not covered by tests

// Check each index permission
for (ApiToken.IndexPermission indexPermission : indexPermissions) {
// First check if this permission applies to this index
boolean indexMatched = false;

Check warning on line 933 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L933

Added line #L933 was not covered by tests
for (String pattern : indexPermission.getIndexPatterns()) {
if (WildcardMatcher.from(pattern).test(concreteIndex)) {
indexMatched = true;
break;

Check warning on line 937 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L936-L937

Added lines #L936 - L937 were not covered by tests
}
}

Check warning on line 939 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L939

Added line #L939 was not covered by tests

if (!indexMatched) {
continue;

Check warning on line 942 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L942

Added line #L942 was not covered by tests
}

// Index matched, now check if this permission covers all actions
Set<String> remainingActions = new HashSet<>(actions);
ImmutableSet<String> resolvedIndexPermissions = actionGroups.resolve(indexPermission.getAllowedActions());

Check warning on line 947 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L946-L947

Added lines #L946 - L947 were not covered by tests

for (String permission : resolvedIndexPermissions) {
// Skip global wildcard if explicit is true
if (explicit && permission.equals("*")) {
continue;

Check warning on line 952 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L952

Added line #L952 was not covered by tests
}

WildcardMatcher permissionMatcher = WildcardMatcher.from(permission);
remainingActions.removeIf(action -> permissionMatcher.test(action));

Check warning on line 956 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L955-L956

Added lines #L955 - L956 were not covered by tests

if (remainingActions.isEmpty()) {
indexHasAllPermissions = true;
break;

Check warning on line 960 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L959-L960

Added lines #L959 - L960 were not covered by tests
}
}

Check warning on line 962 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L962

Added line #L962 was not covered by tests

if (indexHasAllPermissions) {
break; // Found a permission that covers all actions for this index

Check warning on line 965 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L965

Added line #L965 was not covered by tests
}
}

Check warning on line 967 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L967

Added line #L967 was not covered by tests

if (!indexHasAllPermissions) {
return PrivilegesEvaluatorResponse.insufficient("Insufficient permissions for index");

Check warning on line 970 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L970

Added line #L970 was not covered by tests
}
}

Check warning on line 972 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L972

Added line #L972 was not covered by tests
// If we get here, all indices had sufficient permissions
return PrivilegesEvaluatorResponse.ok();

Check warning on line 974 in src/main/java/org/opensearch/security/privileges/ActionPrivileges.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/privileges/ActionPrivileges.java#L974

Added line #L974 was not covered by tests
}
}
return PrivilegesEvaluatorResponse.insufficient(checkTable)
.reason("No explicit privileges have been provided for the referenced indices.")
.reason(
resolvedIndices.getAllIndices().size() == 1
? "Insufficient permissions for the referenced index"
: "None of " + resolvedIndices.getAllIndices().size() + " referenced indices has sufficient permissions"
)
.evaluationExceptions(exceptions);
}
}
Expand Down

0 comments on commit 73eb2ab

Please sign in to comment.