From 13262cd60ac37f91d83f96b8dba79a2984dcb107 Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Mon, 12 Aug 2024 14:49:13 -0400
Subject: [PATCH 1/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../security/securityconf/ConfigModelV7.java  | 130 ++++++++++++++++++
 .../security/securityconf/impl/v7/RoleV7.java |  62 +++++++++
 2 files changed, 192 insertions(+)

diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
index 84902bba3e..fd7e982526 100644
--- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
@@ -63,6 +63,7 @@
 import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7.Index;
+import org.opensearch.security.securityconf.impl.v7.RoleV7.Resource;
 import org.opensearch.security.securityconf.impl.v7.TenantV7;
 import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.support.WildcardMatcher;
@@ -179,6 +180,16 @@ public SecurityRole call() throws Exception {
 
                     }
 
+                    for (final Resource permittedResourceAliases : securityRole.getValue().getResource_permissions()) {
+
+                        for (String resourcePattern : permittedResourceAliases.getResource_patterns()) {
+                            ResourcePattern _resourcePattern = new ResourcePattern(resourcePattern);
+                            _resourcePattern.addPerm(actionGroups.resolve(permittedResourceAliases.getAllowed_actions()));
+
+                            _securityRole.addResourcePattern(_resourcePattern);
+                        }
+                    }
+
                     return _securityRole.build();
                 }
             });
@@ -912,6 +923,125 @@ public Set<String> getPerms() {
 
     }*/
 
+    public static class ResourcePattern {
+        private final String resourcePattern;
+        private final Set<String> perms = new HashSet<>();
+
+        public ResourcePattern(String resourcePattern) {
+            super();
+            this.resourcePattern = Objects.requireNonNull(resourcePattern);
+        }
+
+        public ResourcePattern addPerm(Set<String> perms) {
+            if (perms != null) {
+                this.perms.addAll(perms);
+            }
+            return this;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((resourcePattern == null) ? 0 : resourcePattern.hashCode());
+            result = prime * result + ((perms == null) ? 0 : perms.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            ResourcePattern other = (ResourcePattern) obj;
+            if (resourcePattern == null) {
+                if (other.resourcePattern != null) return false;
+            } else if (!resourcePattern.equals(other.resourcePattern)) return false;
+            if (perms == null) {
+                if (other.perms != null) return false;
+            } else if (!perms.equals(other.perms)) return false;
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return System.lineSeparator()
+                    + "        resourcePattern="
+                    + resourcePattern
+                    + System.lineSeparator()
+                    + "          perms="
+                    + perms;
+        }
+
+        public String getUnresolvedResourcePattern(User user) {
+            return UserAttributes.replaceProperties(resourcePattern, user);
+        }
+
+        /** Finds the resources accessible to the user and resolves them to concrete names */
+        public Set<String> concreteResourceNames(final User user, final ResourceNameExpressionResolver resolver, final ClusterService cs) {
+            return getResolvedResourcePattern(user, resolver, cs, false);
+        }
+
+        /** Finds the resources accessible to the user and attempts to resolve them to names, also includes any unresolved names */
+        public Set<String> attemptResolveIndexNames(final User user, final ResourceNameExpressionResolver resolver, final ClusterService cs) {
+            return getResolvedResourcePattern(user, resolver, cs, true);
+        }
+
+
+        public Set<String> getResolvedResourcePattern(
+                final User user,
+                final ResourceNameExpressionResolver resolver,
+                final ClusterService cs,
+                final boolean appendUnresolved
+        ) {
+            final String unresolved = getUnresolvedResourcePattern(user);
+            final ImmutableSet.Builder<String> resolvedResources = new ImmutableSet.Builder<>();
+
+            final WildcardMatcher matcher = WildcardMatcher.from(unresolved);
+            boolean includeDataStreams = true;
+            if (!(matcher instanceof WildcardMatcher.Exact)) {
+                final String[] aliasesAndDataStreamsForPermittedPattern = cs.state()
+                        .getMetadata()
+                        .getResourcesLookup()
+                        .entrySet()
+                        .stream()
+                        .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM))
+                        .filter(e -> matcher.test(e.getKey()))
+                        .map(e -> e.getKey())
+                        .toArray(String[]::new);
+                if (aliasesAndDataStreamsForPermittedPattern.length > 0) {
+                    final String[] resolvedAliasesAndDataStreamResources = resolver.concreteResourceNames(
+                            cs.state(),
+                            ResourceOptions.lenientExpandOpen(),
+                            includeDataStreams,
+                            aliasesAndDataStreamsForPermittedPattern
+                    );
+                    resolvedResources.addAll(Arrays.asList(resolvedAliasesAndDataStreamResources));
+                }
+            }
+
+            if (Strings.isNotBlank(unresolved)) {
+                final String[] resolvedResourcesFromPattern = resolver.concreteResourceNames(
+                        cs.state(),
+                        ResourceOptions.lenientExpandOpen(),
+                        includeDataStreams,
+                        unresolved
+                );
+                resolvedResources.addAll(Arrays.asList(resolvedResourcesFromPattern));
+            }
+
+            if (appendUnresolved || resolvedResources.build().isEmpty()) {
+                resolvedResources.add(unresolved);
+            }
+            return resolvedResources.build();
+        }
+
+        public WildcardMatcher getPerms() {
+            return WildcardMatcher.from(perms);
+        }
+
+    }
+
     public static class Tenant {
         private final String tenant;
         private final boolean readWrite;
diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
index 229cae0e6f..73396f500b 100644
--- a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
@@ -29,6 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map.Entry;
@@ -51,6 +52,7 @@ public class RoleV7 implements Hideable, StaticDefinable {
     private List<String> cluster_permissions = Collections.emptyList();
     private List<Index> index_permissions = Collections.emptyList();
     private List<Tenant> tenant_permissions = Collections.emptyList();
+    private List<Resource> resource_permissions = Collections.emptyList();
 
     public RoleV7() {
 
@@ -63,6 +65,7 @@ public RoleV7(RoleV6 roleV6) {
         this.cluster_permissions = roleV6.getCluster();
         index_permissions = new ArrayList<>();
         tenant_permissions = new ArrayList<>();
+        resource_permissions = new ArrayList<>();
 
         for (Entry<String, RoleV6.Index> v6i : roleV6.getIndices().entrySet()) {
             index_permissions.add(new Index(v6i.getKey(), v6i.getValue()));
@@ -225,6 +228,57 @@ public String toString() {
 
     }
 
+    public static class Resource {
+
+        private String uniqueId;
+        private List<String> resource_patterns;
+        private Date lastModifiedAt;
+        private List<String> allowed_actions = Collections.emptyList();
+
+        public Resource(String resourceName, List<String> resourcePattern) {
+            super();
+            uniqueId = resourceName;
+            lastModifiedAt = new Date();
+            Set<String> tmpActions = new HashSet<>();
+            resource_patterns = resourcePattern;
+            allowed_actions = new ArrayList<>(tmpActions);
+        }
+
+        public Resource() {
+            super();
+        }
+
+        public List<String> getAllowed_actions() {
+            return allowed_actions;
+        }
+
+        public void setAllowed_actions(List<String> allowed_actions) {
+            lastModifiedAt = new Date();
+            this.allowed_actions = allowed_actions;
+        }
+
+        public List<String> getResource_patterns() {
+            return resource_patterns;
+        }
+
+        public void setResource_patterns(List<String> resource_patterns) {
+            this.resource_patterns = resource_patterns;
+        }
+
+        @Override
+        public String toString() {
+            return "Resource [uniqueId="
+                    + uniqueId
+                    + ", lastModifiedAt="
+                    + lastModifiedAt
+                    + ", resource_patterns="
+                    + resource_patterns
+                    + ", allowed_actions="
+                    + allowed_actions
+                    + "]";
+        }
+    }
+
     public boolean isHidden() {
         return hidden;
     }
@@ -265,6 +319,14 @@ public void setTenant_permissions(List<Tenant> tenant_permissions) {
         this.tenant_permissions = tenant_permissions;
     }
 
+    public List<Resource> getResource_permissions() {
+        return resource_permissions;
+    }
+
+    public void setResource_permissions(List<Resource> resource_permissions) {
+        this.resource_permissions = resource_permissions;
+    }
+
     public boolean isReserved() {
         return reserved;
     }

From fb40a6f6a176a7a8c095ed849886553e735072e5 Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Mon, 12 Aug 2024 15:04:55 -0400
Subject: [PATCH 2/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../security/OpenSearchSecurityPlugin.java    |   4 +
 .../privileges/ResourceAccessEvaluator.java   | 104 ++++++++++++++++++
 .../security/securityconf/SecurityRoles.java  |   2 +
 3 files changed, 110 insertions(+)
 create mode 100644 src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java

diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
index 509b98f12e..202eae17d0 100644
--- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
+++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
@@ -167,6 +167,7 @@
 import org.opensearch.security.identity.SecurityTokenManager;
 import org.opensearch.security.privileges.PrivilegesEvaluator;
 import org.opensearch.security.privileges.PrivilegesInterceptor;
+import org.opensearch.security.privileges.ResourceAccessEvaluator;
 import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator;
 import org.opensearch.security.resolver.IndexResolverReplacer;
 import org.opensearch.security.rest.DashboardsInfoAction;
@@ -246,6 +247,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
     private volatile PrivilegesEvaluator evaluator;
     private volatile UserService userService;
     private volatile RestLayerPrivilegesEvaluator restLayerEvaluator;
+    private volatile ResourceAccessEvaluator resourceAccessEvaluator;
     private volatile ConfigurationRepository cr;
     private volatile AdminDNs adminDns;
     private volatile ClusterService cs;
@@ -1139,6 +1141,8 @@ public Collection<Object> createComponents(
 
         restLayerEvaluator = new RestLayerPrivilegesEvaluator(clusterService, threadPool);
 
+        resourceAccessEvaluator = new ResourceAccessEvaluator(clusterService, threadPool);
+
         securityRestHandler = new SecurityRestFilter(
             backendRegistry,
             restLayerEvaluator,
diff --git a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
new file mode 100644
index 0000000000..e9cc958a91
--- /dev/null
+++ b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
@@ -0,0 +1,104 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+package org.opensearch.security.privileges;
+
+import java.util.Set;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import org.opensearch.OpenSearchSecurityException;
+import org.opensearch.cluster.service.ClusterService;
+import org.opensearch.common.util.concurrent.ThreadContext;
+import org.opensearch.core.common.transport.TransportAddress;
+import org.opensearch.security.securityconf.ConfigModel;
+import org.opensearch.security.securityconf.SecurityRoles;
+import org.opensearch.security.support.ConfigConstants;
+import org.opensearch.security.user.User;
+import org.opensearch.threadpool.ThreadPool;
+
+import org.greenrobot.eventbus.Subscribe;
+
+public class ResourceAccessEvaluator {
+    protected final Logger log = LogManager.getLogger(this.getClass());
+    private final ClusterService clusterService;
+    private ThreadContext threadContext;
+    private ConfigModel configModel;
+
+    public ResourceAccessEvaluator(final ClusterService clusterService, final ThreadPool threadPool) {
+        this.clusterService = clusterService;
+        this.threadContext = threadPool.getThreadContext();
+    }
+
+    @Subscribe
+    public void onConfigModelChanged(final ConfigModel configModel) {
+        this.configModel = configModel;
+    }
+
+    SecurityRoles getSecurityRoles(final Set<String> roles) {
+        return configModel.getSecurityRoles().filter(roles);
+    }
+
+    boolean isInitialized() {
+        return configModel != null && configModel.getSecurityRoles() != null;
+    }
+
+    public PrivilegesEvaluatorResponse evaluate(final User user, final Set<String> actions) {
+        if (!isInitialized()) {
+            throw new OpenSearchSecurityException("OpenSearch Security is not initialized.");
+        }
+
+        final PrivilegesEvaluatorResponse presponse = new PrivilegesEvaluatorResponse();
+
+        final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS);
+
+        final Set<String> mappedRoles = mapRoles(user, caller);
+
+        presponse.resolvedSecurityRoles.addAll(mappedRoles);
+        final SecurityRoles securityRoles = getSecurityRoles(mappedRoles);
+
+        final boolean isDebugEnabled = log.isDebugEnabled();
+        if (isDebugEnabled) {
+            log.debug("Evaluate permissions for {} on {}", user, clusterService.localNode().getName());
+            log.debug("Action: {}", actions);
+            log.debug("Mapped roles: {}", mappedRoles.toString());
+        }
+
+        for (final String action : actions) {
+            if (!securityRoles.impliesResourcePermission(action)) {
+                presponse.missingPrivileges.add(action);
+                presponse.allowed = false;
+                log.info(
+                        "No permission match for {} [Action [{}]] [RolesChecked {}]. No permissions for {}",
+                        user,
+                        action,
+                        securityRoles.getRoleNames(),
+                        presponse.missingPrivileges
+                );
+            } else {
+                if (isDebugEnabled) {
+                    log.debug("Allowed because we have permissions for {}", actions);
+                }
+                presponse.allowed = true;
+
+                // break the loop as we found the matching permission
+                break;
+            }
+        }
+
+        return presponse;
+    }
+
+    Set<String> mapRoles(final User user, final TransportAddress caller) {
+        return this.configModel.mapSecurityRoles(user, caller);
+    }
+}
diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
index fb25e1a21f..0e1070c6bd 100644
--- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
+++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
@@ -98,4 +98,6 @@ Set<String> getAllPermittedIndicesForDashboards(
     SecurityRoles filter(Set<String> roles);
 
     boolean isPermittedOnSystemIndex(String indexName);
+
+    boolean impliesResourcePermission(String action0);
 }

From 59df3dfd46d409162fcedc3f7f608ca17703b9bc Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Mon, 12 Aug 2024 15:49:33 -0400
Subject: [PATCH 3/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../security/securityconf/ConfigModelV7.java  | 135 +-----------------
 1 file changed, 5 insertions(+), 130 deletions(-)

diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
index fd7e982526..0e6143187f 100644
--- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
@@ -63,7 +63,6 @@
 import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7.Index;
-import org.opensearch.security.securityconf.impl.v7.RoleV7.Resource;
 import org.opensearch.security.securityconf.impl.v7.TenantV7;
 import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.support.WildcardMatcher;
@@ -180,16 +179,6 @@ public SecurityRole call() throws Exception {
 
                     }
 
-                    for (final Resource permittedResourceAliases : securityRole.getValue().getResource_permissions()) {
-
-                        for (String resourcePattern : permittedResourceAliases.getResource_patterns()) {
-                            ResourcePattern _resourcePattern = new ResourcePattern(resourcePattern);
-                            _resourcePattern.addPerm(actionGroups.resolve(permittedResourceAliases.getAllowed_actions()));
-
-                            _securityRole.addResourcePattern(_resourcePattern);
-                        }
-                    }
-
                     return _securityRole.build();
                 }
             });
@@ -515,6 +504,11 @@ public boolean isPermittedOnSystemIndex(String indexName) {
             }
             return isPatternMatched && isPermitted;
         }
+
+        @Override
+        public boolean impliesResourcePermission(String action0) {
+            return roles.stream().filter(r -> r.impliesClusterPermission(action0)).count() > 0;
+        }
     }
 
     public static class SecurityRole {
@@ -923,125 +917,6 @@ public Set<String> getPerms() {
 
     }*/
 
-    public static class ResourcePattern {
-        private final String resourcePattern;
-        private final Set<String> perms = new HashSet<>();
-
-        public ResourcePattern(String resourcePattern) {
-            super();
-            this.resourcePattern = Objects.requireNonNull(resourcePattern);
-        }
-
-        public ResourcePattern addPerm(Set<String> perms) {
-            if (perms != null) {
-                this.perms.addAll(perms);
-            }
-            return this;
-        }
-
-        @Override
-        public int hashCode() {
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + ((resourcePattern == null) ? 0 : resourcePattern.hashCode());
-            result = prime * result + ((perms == null) ? 0 : perms.hashCode());
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) return true;
-            if (obj == null) return false;
-            if (getClass() != obj.getClass()) return false;
-            ResourcePattern other = (ResourcePattern) obj;
-            if (resourcePattern == null) {
-                if (other.resourcePattern != null) return false;
-            } else if (!resourcePattern.equals(other.resourcePattern)) return false;
-            if (perms == null) {
-                if (other.perms != null) return false;
-            } else if (!perms.equals(other.perms)) return false;
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return System.lineSeparator()
-                    + "        resourcePattern="
-                    + resourcePattern
-                    + System.lineSeparator()
-                    + "          perms="
-                    + perms;
-        }
-
-        public String getUnresolvedResourcePattern(User user) {
-            return UserAttributes.replaceProperties(resourcePattern, user);
-        }
-
-        /** Finds the resources accessible to the user and resolves them to concrete names */
-        public Set<String> concreteResourceNames(final User user, final ResourceNameExpressionResolver resolver, final ClusterService cs) {
-            return getResolvedResourcePattern(user, resolver, cs, false);
-        }
-
-        /** Finds the resources accessible to the user and attempts to resolve them to names, also includes any unresolved names */
-        public Set<String> attemptResolveIndexNames(final User user, final ResourceNameExpressionResolver resolver, final ClusterService cs) {
-            return getResolvedResourcePattern(user, resolver, cs, true);
-        }
-
-
-        public Set<String> getResolvedResourcePattern(
-                final User user,
-                final ResourceNameExpressionResolver resolver,
-                final ClusterService cs,
-                final boolean appendUnresolved
-        ) {
-            final String unresolved = getUnresolvedResourcePattern(user);
-            final ImmutableSet.Builder<String> resolvedResources = new ImmutableSet.Builder<>();
-
-            final WildcardMatcher matcher = WildcardMatcher.from(unresolved);
-            boolean includeDataStreams = true;
-            if (!(matcher instanceof WildcardMatcher.Exact)) {
-                final String[] aliasesAndDataStreamsForPermittedPattern = cs.state()
-                        .getMetadata()
-                        .getResourcesLookup()
-                        .entrySet()
-                        .stream()
-                        .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM))
-                        .filter(e -> matcher.test(e.getKey()))
-                        .map(e -> e.getKey())
-                        .toArray(String[]::new);
-                if (aliasesAndDataStreamsForPermittedPattern.length > 0) {
-                    final String[] resolvedAliasesAndDataStreamResources = resolver.concreteResourceNames(
-                            cs.state(),
-                            ResourceOptions.lenientExpandOpen(),
-                            includeDataStreams,
-                            aliasesAndDataStreamsForPermittedPattern
-                    );
-                    resolvedResources.addAll(Arrays.asList(resolvedAliasesAndDataStreamResources));
-                }
-            }
-
-            if (Strings.isNotBlank(unresolved)) {
-                final String[] resolvedResourcesFromPattern = resolver.concreteResourceNames(
-                        cs.state(),
-                        ResourceOptions.lenientExpandOpen(),
-                        includeDataStreams,
-                        unresolved
-                );
-                resolvedResources.addAll(Arrays.asList(resolvedResourcesFromPattern));
-            }
-
-            if (appendUnresolved || resolvedResources.build().isEmpty()) {
-                resolvedResources.add(unresolved);
-            }
-            return resolvedResources.build();
-        }
-
-        public WildcardMatcher getPerms() {
-            return WildcardMatcher.from(perms);
-        }
-
-    }
-
     public static class Tenant {
         private final String tenant;
         private final boolean readWrite;

From 12d8c8c24e1aefc02c35f88f1dadff0039a3106b Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Mon, 12 Aug 2024 16:43:22 -0400
Subject: [PATCH 4/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../privileges/ResourceAccessEvaluator.java   | 33 +++++++++++--------
 .../security/securityconf/ConfigModelV7.java  | 12 ++++---
 .../security/securityconf/SecurityRoles.java  |  3 +-
 3 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
index e9cc958a91..8f6e8ad59b 100644
--- a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
+++ b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
@@ -11,19 +11,24 @@
 
 package org.opensearch.security.privileges;
 
+import java.util.List;
 import java.util.Set;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import org.opensearch.OpenSearchSecurityException;
+import org.opensearch.action.ActionRequest;
+import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
 import org.opensearch.cluster.service.ClusterService;
 import org.opensearch.common.util.concurrent.ThreadContext;
 import org.opensearch.core.common.transport.TransportAddress;
+import org.opensearch.security.resolver.IndexResolverReplacer;
 import org.opensearch.security.securityconf.ConfigModel;
 import org.opensearch.security.securityconf.SecurityRoles;
 import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.user.User;
+import org.opensearch.tasks.Task;
 import org.opensearch.threadpool.ThreadPool;
 
 import org.greenrobot.eventbus.Subscribe;
@@ -52,7 +57,11 @@ boolean isInitialized() {
         return configModel != null && configModel.getSecurityRoles() != null;
     }
 
-    public PrivilegesEvaluatorResponse evaluate(final User user, final Set<String> actions) {
+    public PrivilegesEvaluatorResponse evaluate(final ActionRequest request,
+                                                final String action,
+                                                final SecurityRoles securityRoles,
+                                                final User user,
+                                                final ClusterService clusterService) {
         if (!isInitialized()) {
             throw new OpenSearchSecurityException("OpenSearch Security is not initialized.");
         }
@@ -64,17 +73,22 @@ public PrivilegesEvaluatorResponse evaluate(final User user, final Set<String> a
         final Set<String> mappedRoles = mapRoles(user, caller);
 
         presponse.resolvedSecurityRoles.addAll(mappedRoles);
-        final SecurityRoles securityRoles = getSecurityRoles(mappedRoles);
 
         final boolean isDebugEnabled = log.isDebugEnabled();
         if (isDebugEnabled) {
             log.debug("Evaluate permissions for {} on {}", user, clusterService.localNode().getName());
-            log.debug("Action: {}", actions);
+            log.debug("Action: {}", action);
             log.debug("Mapped roles: {}", mappedRoles.toString());
         }
 
-        for (final String action : actions) {
-            if (!securityRoles.impliesResourcePermission(action)) {
+        List<String> resourcesRequested = action.getRequestedResources();
+        if (resourcesRequested == null || resourcesRequested.isEmpty()) {
+            presponse.allowed = true;
+            return presponse;
+        }
+        presponse.allowed = true;
+        for (String resource : resourcesRequested) {
+            if (!securityRoles.impliesResourcePermission(resource)) {
                 presponse.missingPrivileges.add(action);
                 presponse.allowed = false;
                 log.info(
@@ -84,17 +98,8 @@ public PrivilegesEvaluatorResponse evaluate(final User user, final Set<String> a
                         securityRoles.getRoleNames(),
                         presponse.missingPrivileges
                 );
-            } else {
-                if (isDebugEnabled) {
-                    log.debug("Allowed because we have permissions for {}", actions);
-                }
-                presponse.allowed = true;
-
-                // break the loop as we found the matching permission
-                break;
             }
         }
-
         return presponse;
     }
 
diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
index 0e6143187f..8c833adc1d 100644
--- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
@@ -506,8 +506,8 @@ public boolean isPermittedOnSystemIndex(String indexName) {
         }
 
         @Override
-        public boolean impliesResourcePermission(String action0) {
-            return roles.stream().filter(r -> r.impliesClusterPermission(action0)).count() > 0;
+        public boolean impliesResourcePermission(String resource) {
+            return roles.stream().filter(r -> r.impliesClusterPermission(resource)).count() > 0;
         }
     }
 
@@ -515,11 +515,13 @@ public static class SecurityRole {
         private final String name;
         private final Set<IndexPattern> ipatterns;
         private final WildcardMatcher clusterPerms;
+        private final WildcardMatcher resourcePerms;
 
         public static final class Builder {
             private final String name;
             private final Set<String> clusterPerms = new HashSet<>();
             private final Set<IndexPattern> ipatterns = new HashSet<>();
+            private final Set<String> resourcePerms = new HashSet<>();
 
             public Builder(String name) {
                 this.name = Objects.requireNonNull(name);
@@ -538,20 +540,22 @@ public Builder addClusterPerms(Collection<String> clusterPerms) {
             }
 
             public SecurityRole build() {
-                return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms));
+                return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms),  WildcardMatcher.from(resourcePerms));
             }
         }
 
-        private SecurityRole(String name, Set<IndexPattern> ipatterns, WildcardMatcher clusterPerms) {
+        private SecurityRole(String name, Set<IndexPattern> ipatterns, WildcardMatcher clusterPerms, WildcardMatcher resourcePerms) {
             this.name = Objects.requireNonNull(name);
             this.ipatterns = ipatterns;
             this.clusterPerms = clusterPerms;
+            this.resourcePerms = resourcePerms;
         }
 
         private boolean impliesClusterPermission(String action) {
             return clusterPerms.test(action);
         }
 
+
         // get indices which are permitted for the given types and actions
         // dnfof + opensearchDashboards special only
         private Set<String> getAllResolvedPermittedIndices(
diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
index 0e1070c6bd..16ae95e9a8 100644
--- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
+++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
@@ -29,6 +29,7 @@
 
 import java.util.Set;
 
+import org.opensearch.action.ActionRequest;
 import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
 import org.opensearch.cluster.service.ClusterService;
 import org.opensearch.core.xcontent.NamedXContentRegistry;
@@ -99,5 +100,5 @@ Set<String> getAllPermittedIndicesForDashboards(
 
     boolean isPermittedOnSystemIndex(String indexName);
 
-    boolean impliesResourcePermission(String action0);
+    boolean impliesResourcePermission(String resource);
 }

From 4cb7bc2584c606dd8027c4c961cc3c5a6d12f1af Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Tue, 13 Aug 2024 11:19:53 -0400
Subject: [PATCH 5/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../privileges/ResourceAccessEvaluator.java   | 36 ++++---------------
 .../security/securityconf/ConfigModelV7.java  | 17 +++++++++
 .../security/securityconf/impl/v7/RoleV7.java |  1 +
 3 files changed, 25 insertions(+), 29 deletions(-)

diff --git a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
index 8f6e8ad59b..ef5f2da654 100644
--- a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
+++ b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
@@ -11,48 +11,31 @@
 
 package org.opensearch.security.privileges;
 
-import java.util.List;
-import java.util.Set;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-
+import org.greenrobot.eventbus.Subscribe;
 import org.opensearch.OpenSearchSecurityException;
 import org.opensearch.action.ActionRequest;
-import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
 import org.opensearch.cluster.service.ClusterService;
-import org.opensearch.common.util.concurrent.ThreadContext;
 import org.opensearch.core.common.transport.TransportAddress;
-import org.opensearch.security.resolver.IndexResolverReplacer;
 import org.opensearch.security.securityconf.ConfigModel;
 import org.opensearch.security.securityconf.SecurityRoles;
-import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.user.User;
-import org.opensearch.tasks.Task;
-import org.opensearch.threadpool.ThreadPool;
 
-import org.greenrobot.eventbus.Subscribe;
+import java.util.List;
+import java.util.Set;
 
 public class ResourceAccessEvaluator {
     protected final Logger log = LogManager.getLogger(this.getClass());
-    private final ClusterService clusterService;
-    private ThreadContext threadContext;
     private ConfigModel configModel;
 
-    public ResourceAccessEvaluator(final ClusterService clusterService, final ThreadPool threadPool) {
-        this.clusterService = clusterService;
-        this.threadContext = threadPool.getThreadContext();
-    }
+    public ResourceAccessEvaluator() {}
 
     @Subscribe
     public void onConfigModelChanged(final ConfigModel configModel) {
         this.configModel = configModel;
     }
 
-    SecurityRoles getSecurityRoles(final Set<String> roles) {
-        return configModel.getSecurityRoles().filter(roles);
-    }
-
     boolean isInitialized() {
         return configModel != null && configModel.getSecurityRoles() != null;
     }
@@ -68,20 +51,15 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request,
 
         final PrivilegesEvaluatorResponse presponse = new PrivilegesEvaluatorResponse();
 
-        final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS);
-
-        final Set<String> mappedRoles = mapRoles(user, caller);
-
-        presponse.resolvedSecurityRoles.addAll(mappedRoles);
-
         final boolean isDebugEnabled = log.isDebugEnabled();
         if (isDebugEnabled) {
             log.debug("Evaluate permissions for {} on {}", user, clusterService.localNode().getName());
             log.debug("Action: {}", action);
-            log.debug("Mapped roles: {}", mappedRoles.toString());
+            log.debug("Resource: {}", request.getRequestedResources());
+            log.debug("Security roles: {}", securityRoles.toString());
         }
 
-        List<String> resourcesRequested = action.getRequestedResources();
+        List<String> resourcesRequested = request.getRequestedResources();
         if (resourcesRequested == null || resourcesRequested.isEmpty()) {
             presponse.allowed = true;
             return presponse;
diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
index 8c833adc1d..ce3c175c60 100644
--- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
@@ -63,6 +63,7 @@
 import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7;
 import org.opensearch.security.securityconf.impl.v7.RoleV7.Index;
+import org.opensearch.security.securityconf.impl.v7.RoleV7.Resource;
 import org.opensearch.security.securityconf.impl.v7.TenantV7;
 import org.opensearch.security.support.ConfigConstants;
 import org.opensearch.security.support.WildcardMatcher;
@@ -179,6 +180,10 @@ public SecurityRole call() throws Exception {
 
                     }
 
+                    for (final Resource permittedResources : securityRole.getValue().getResource_permissions()) {
+                        _securityRole.addResourcePerms(permittedResources.getResource_patterns());
+                    }
+
                     return _securityRole.build();
                 }
             });
@@ -539,6 +544,13 @@ public Builder addClusterPerms(Collection<String> clusterPerms) {
                 return this;
             }
 
+            public Builder addResourcePerms(Collection<String> resourcePerms) {
+                if (resourcePerms != null) {
+                    this.resourcePerms.addAll(resourcePerms);
+                }
+                return this;
+            }
+
             public SecurityRole build() {
                 return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms),  WildcardMatcher.from(resourcePerms));
             }
@@ -555,6 +567,11 @@ private boolean impliesClusterPermission(String action) {
             return clusterPerms.test(action);
         }
 
+        private boolean impliesResourcePermission(String action) {
+            return resourcePerms.test(action);
+        }
+
+
 
         // get indices which are permitted for the given types and actions
         // dnfof + opensearchDashboards special only
diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
index 73396f500b..31219d74b2 100644
--- a/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
+++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/RoleV7.java
@@ -262,6 +262,7 @@ public List<String> getResource_patterns() {
         }
 
         public void setResource_patterns(List<String> resource_patterns) {
+            lastModifiedAt = new Date();
             this.resource_patterns = resource_patterns;
         }
 

From b317a1aa8e7f95bb536991c5991ce62bbac66f27 Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Tue, 13 Aug 2024 11:24:48 -0400
Subject: [PATCH 6/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../opensearch/security/privileges/PrivilegesEvaluator.java | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java
index 199442ee03..7568139a2e 100644
--- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java
+++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java
@@ -138,6 +138,7 @@ public class PrivilegesEvaluator {
     private final SnapshotRestoreEvaluator snapshotRestoreEvaluator;
     private final SystemIndexAccessEvaluator systemIndexAccessEvaluator;
     private final ProtectedIndexAccessEvaluator protectedIndexAccessEvaluator;
+    private final ResourceAccessEvaluator resourceAccessEvaluator;
     private final TermsAggregationEvaluator termsAggregationEvaluator;
     private final PitPrivilegesEvaluator pitPrivilegesEvaluator;
     private DynamicConfigModel dcm;
@@ -174,6 +175,7 @@ public PrivilegesEvaluator(
         snapshotRestoreEvaluator = new SnapshotRestoreEvaluator(settings, auditLog);
         systemIndexAccessEvaluator = new SystemIndexAccessEvaluator(settings, auditLog, irr);
         protectedIndexAccessEvaluator = new ProtectedIndexAccessEvaluator(settings, auditLog);
+        resourceAccessEvaluator = new ResourceAccessEvaluator();
         termsAggregationEvaluator = new TermsAggregationEvaluator();
         pitPrivilegesEvaluator = new PitPrivilegesEvaluator();
         this.namedXContentRegistry = namedXContentRegistry;
@@ -347,6 +349,10 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context)
             return presponse;
         }
 
+        if (resourceAccessEvaluator.evaluate(request, action0, securityRoles, user, clusterService).isComplete()) {
+            return presponse;
+        }
+
         // check access for point in time requests
         if (pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, action0, resolver, presponse, irr).isComplete()) {
             return presponse;

From 137ad1217cfc6d0db871c3497284cbe36081673a Mon Sep 17 00:00:00 2001
From: Stephen Crawford <steecraw@amazon.com>
Date: Tue, 13 Aug 2024 13:07:33 -0400
Subject: [PATCH 7/7] Expand RoleV7 to include Resources

Signed-off-by: Stephen Crawford <steecraw@amazon.com>
---
 .../security/privileges/ResourceAccessEvaluator.java          | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
index ef5f2da654..bb62855137 100644
--- a/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
+++ b/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java
@@ -55,11 +55,11 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request,
         if (isDebugEnabled) {
             log.debug("Evaluate permissions for {} on {}", user, clusterService.localNode().getName());
             log.debug("Action: {}", action);
-            log.debug("Resource: {}", request.getRequestedResources());
+            log.debug("Resource: {}", request.getResources());
             log.debug("Security roles: {}", securityRoles.toString());
         }
 
-        List<String> resourcesRequested = request.getRequestedResources();
+        List<String> resourcesRequested = request.getResources();
         if (resourcesRequested == null || resourcesRequested.isEmpty()) {
             presponse.allowed = true;
             return presponse;