This is an automated email from the ASF dual-hosted git repository.

ramackri pushed a commit to branch RANGER-5687-backport-ranger-2.9
in repository https://gitbox.apache.org/repos/asf/ranger.git

commit 872187ae0ed462d942c20d470da7e39808c1d847
Author: ramk <[email protected]>
AuthorDate: Mon Jul 20 22:19:55 2026 +0530

    RANGER-5687: Backport ozone action-matcher feature flag to ranger-2.9
    
    Back-port of master commit 37b6e02c4d3b989790bbd669b7d5d4366f7f7644 (#1073).
    
    Add runtime feature flag 
ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition,
    wire FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION in setup/docker, and gate 
ozone
    action-matches in RangerServiceDefService and RangerOzoneAuthorizer. J10065 
is
    unchanged (schema sync only); flag behavior is runtime-only per merged 
master design.
---
 dev-support/ranger-docker/README.md                | 122 ++++++-
 .../admin/ranger-admin-install-mysql.properties    |   3 +
 .../admin/ranger-admin-install-oracle.properties   |   3 +
 .../admin/ranger-admin-install-postgres.properties |  43 ++-
 .../ranger-admin-install-sqlserver.properties      |   3 +
 .../ozone/authorizer/RangerOzoneAuthorizer.java    | 382 ++++++++++++---------
 .../authorizer/TestRangerOzoneAuthorizer.java      | 131 +++++++
 security-admin/scripts/install.properties          |  16 +
 security-admin/scripts/setup.sh                    |  75 ++++
 .../ranger/service/RangerServiceDefService.java    | 253 ++++++++++----
 .../conf.dist/ranger-admin-default-site.xml        |  38 +-
 .../main/resources/conf.dist/ranger-admin-site.xml |  47 +++
 .../main/webapp/react-webapp/src/utils/XAUtils.js  |  53 ++-
 .../views/PolicyListing/AddUpdatePolicyForm.jsx    | 104 ++++--
 .../service/TestRangerServiceDefService.java       | 115 ++++++-
 15 files changed, 1094 insertions(+), 294 deletions(-)

diff --git a/dev-support/ranger-docker/README.md 
b/dev-support/ranger-docker/README.md
index 64276a4ef..4c09881c5 100644
--- a/dev-support/ranger-docker/README.md
+++ b/dev-support/ranger-docker/README.md
@@ -30,11 +30,11 @@ Use Dockerfiles in this directory to create docker images 
and run them to build
 
 - Set ```dev-support/ranger-docker``` as your working directory.
 
-- Execute following command to download necessary archives to setup 
Ranger/HDFS/Hive/HBase/Kafka/Knox/Ozone services:
+- Execute following command to download necessary archives to setup 
Ranger/HDFS/Hive/HBase/Kafka/Knox/Ozone/OpenSearch services:
    ~~~
    chmod +x download-archives.sh
    # use a subset of the below to download specific services
-   ./download-archives.sh hadoop hive hbase kafka knox ozone
+   ./download-archives.sh hadoop hive hbase kafka knox ozone opensearch
    ~~~
 
 - Execute following commands to set environment variables to build Apache 
Ranger docker containers:
@@ -50,6 +50,9 @@ Use Dockerfiles in this directory to create docker images and 
run them to build
 
 Execute following command to build Apache Ranger:
 ~~~
+
+chmod +x scripts/**/*.sh
+
 # optional step: a fresh build ensures that the correct jdk version is used
 docker compose -f docker-compose.ranger-build.yml build
 
@@ -91,14 +94,112 @@ docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-hadoop.yml
 docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-hadoop.yml -f docker-compose.ranger-hbase.yml up -d
 ~~~
 #### Bring up ozone containers
+
+##### Ozone action-matcher feature flag
+
+The flag is controlled at runtime by
+`ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition` in
+`ranger-admin-site.xml`. Docker maps it from
+`FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION` in
+`scripts/admin/ranger-admin-install-*.properties` during Admin setup.
+
+**Prerequisite:** the admin distribution must include an uncommented property
+entry in `conf.dist/ranger-admin-site.xml`. Rebuild dist before building the
+Admin image:
+
+~~~
+docker compose -f docker-compose.ranger-build.yml build
+docker compose -f docker-compose.ranger-build.yml up
+~~~
+
+**First-time setup:** set `FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=true` in
+`scripts/admin/ranger-admin-install-<db>.properties`, then bring up services.
+
+**Changing the flag later** (choose one):
+
+1. Edit `FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION` in
+   `scripts/admin/ranger-admin-install-<db>.properties` and recreate Admin so
+   setup runs again:
+   ~~~
+   docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-ozone.yml \
+     up -d --build --force-recreate ranger
+   ~~~
+   Works even if the DB already exists; setup re-runs on a fresh container.
+
+2. Edit the live value in the container's
+   `ranger-admin-site.xml` and restart Admin.
+
+`install.properties` is not re-read on restart alone — only when setup runs.
+`RangerServiceDefService` applies the site.xml value when serving the ozone
+service-def.
+
 ~~~
 ./scripts/ozone/ozone-plugin-docker-setup.sh
 docker compose -f docker-compose.ranger.yml -f docker-compose.ranger-ozone.yml 
up -d
 ~~~
+
+Verify (after login):
+
+~~~
+curl -s -u admin:rangerR0cks! 
http://localhost:6080/service/plugins/definitions/name/ozone \
+  | python3 -c "import json,sys; d=json.load(sys.stdin); print('options', 
d.get('options')); print('conditions', [c['name'] for c in 
d.get('policyConditions',[])])"
+~~~
 #### Bring up trino container (requires docker build with jdk 11):
 ~~~
 docker compose -f docker-compose.ranger.yml -f docker-compose.ranger-trino.yml 
up -d
 ~~~
+#### Bring up opensearch container:
+~~~
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-opensearch.yml up -d
+~~~
+
+#### OpenSearch audit flow (replace Solr for access audits)
+
+OpenSearch can replace Solr for **audit storage and UI queries**. Ranger Admin 
reads audits via
+`audit_store=opensearch` using a native low-level REST client (compatible with 
any OpenSearch version).
+
+**Write path:** access audits flow through audit-server ingestor, Kafka, and 
the Java
+`ranger-audit-dispatcher-opensearch` service into the OpenSearch 
`ranger_audits` index.
+Ranger Admin policy/admin transaction audits remain DB-backed; this is the 
same boundary
+used by the Solr audit path.
+
+##### Setup
+
+~~~
+# Prerequisites: build the audit-dispatcher tarball and download archives
+mvn clean package -DskipTests -pl distro -am
+cp target/ranger-*-audit-dispatcher.tar.gz dev-support/ranger-docker/dist/
+cd dev-support/ranger-docker
+./download-archives.sh kafka opensearch hadoop
+
+export RANGER_DB_TYPE=postgres
+
+# 1. Start OpenSearch first (Ranger Admin's bootstrapper needs it on startup)
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-opensearch.yml \
+  -f docker-compose.ranger-kafka.yml -f docker-compose.ranger-hadoop.yml \
+  -f docker-compose.ranger-audit-server.yml \
+  -f docker-compose.ranger-audit-dispatcher-opensearch.yml up -d 
ranger-opensearch
+
+# 2. Start core stack (Ranger Admin, Kafka, Hadoop)
+#    Kafka auto-creates the ranger_audits topic on startup.
+#    Ranger Admin auto-creates the OpenSearch index via 
OpenSearchIndexBootStrapper.
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-opensearch.yml \
+  -f docker-compose.ranger-kafka.yml -f docker-compose.ranger-hadoop.yml \
+  -f docker-compose.ranger-audit-server.yml \
+  -f docker-compose.ranger-audit-dispatcher-opensearch.yml up -d ranger 
ranger-kafka ranger-hadoop
+
+# 3. Start audit ingestor and OpenSearch dispatcher
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-opensearch.yml \
+  -f docker-compose.ranger-kafka.yml -f docker-compose.ranger-hadoop.yml \
+  -f docker-compose.ranger-audit-server.yml \
+  -f docker-compose.ranger-audit-dispatcher-opensearch.yml up -d 
ranger-audit-ingestor ranger-audit-dispatcher-opensearch
+~~~
+
+For **fresh Ranger installs** using OpenSearch for audits, set 
`audit_store=opensearch` in
+`scripts/admin/ranger-admin-install-postgres.properties` and configure the 
`audit_opensearch_*` properties.
+
+For **existing Solr-based installs**, switching stores requires updating 
`audit_store=opensearch`
+in install.properties, configuring the `audit_opensearch_*` properties, and 
restarting Ranger Admin.
 Similarly, check the `depends` section of the 
`docker-compose.ranger-service.yaml` file and add docker-compose files for 
these services when trying to bring up the `service` container.
 
 #### Bring up all containers
@@ -111,3 +212,20 @@ docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-usersync.ym
 ~~~
 docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-usersync.yml -f docker-compose.ranger-tagsync.yml -f 
docker-compose.ranger-kms.yml -f docker-compose.ranger-hadoop.yml -f 
docker-compose.ranger-hbase.yml -f docker-compose.ranger-kafka.yml -f 
docker-compose.ranger-hive.yml -f docker-compose.ranger-trino.yml -f 
docker-compose.ranger-knox.yml up -d --no-deps --force-recreate --build 
<service-1> <service-2>
 ~~~
+
+#### To bring up audit server ingestor + dispatchers. Make sure kafka, solr, 
and hdfs containers are running before bringing up audit server services.
+~~~
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-hadoop.yml -f docker-compose.ranger-kafka.yml -f 
docker-compose.ranger-audit-server.yml up -d
+~~~
+
+#### To bring up audit server services individually:
+~~~
+# Audit ingestor
+docker compose -f docker-compose.ranger.yml -f docker-compose.ranger-kafka.yml 
-f docker-compose.ranger-audit-ingestor.yml up -d
+
+# Solr dispatcher
+docker compose -f docker-compose.ranger.yml -f docker-compose.ranger-kafka.yml 
-f docker-compose.ranger-audit-dispatcher-solr.yml up -d
+
+# HDFS dispatcher
+docker compose -f docker-compose.ranger.yml -f 
docker-compose.ranger-hadoop.yml -f docker-compose.ranger-kafka.yml -f 
docker-compose.ranger-audit-dispatcher-hdfs.yml up -d
+~~~
diff --git 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
index 18a4c8da7..14b4330ad 100644
--- 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
+++ 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
@@ -59,6 +59,9 @@ audit_elasticsearch_bootstrap_enabled=true
 policymgr_external_url=http://ranger-admin:6080
 policymgr_http_enabled=true
 
+# Enable Ozone action-matches policy condition (maps to ranger-admin-site.xml 
at setup)
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=false
+
 unix_user=ranger
 unix_user_pwd=ranger
 unix_group=ranger
diff --git 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
index 22b177c02..4748dc0dc 100644
--- 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
+++ 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
@@ -61,6 +61,9 @@ audit_elasticsearch_bootstrap_enabled=true
 policymgr_external_url=http://ranger-admin:6080
 policymgr_http_enabled=true
 
+# Enable Ozone action-matches policy condition (maps to ranger-admin-site.xml 
at setup)
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=false
+
 unix_user=ranger
 unix_user_pwd=ranger
 unix_group=ranger
diff --git 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
index cf8a58fec..80c18df4e 100644
--- 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
+++ 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
@@ -43,22 +43,40 @@ rangerUsersync_password=rangerR0cks!
 keyadmin_password=rangerR0cks!
 
 
-audit_store=solr
-audit_solr_urls=http://ranger-solr:8983/solr/ranger_audits
-audit_solr_collection_name=ranger_audits
+## --- Audit store: enable ONE of the following blocks ---
 
+## Option 1: Solr (default)
+# audit_store=solr
+# FQDN required for SPNEGO to Solr (HTTP/ranger-solr.rangernw@REALM)
+# audit_solr_urls=http://ranger-solr.rangernw:8983/solr/ranger_audits
+# audit_solr_collection_name=ranger_audits
+
+## Option 2: OpenSearch / Elasticsearch (legacy - uses ES HighLevel REST 
Client)
 # audit_store=elasticsearch
-audit_elasticsearch_urls=
-audit_elasticsearch_port=9200
-audit_elasticsearch_protocol=http
-audit_elasticsearch_user=elastic
-audit_elasticsearch_password=elasticsearch
-audit_elasticsearch_index=ranger_audits
-audit_elasticsearch_bootstrap_enabled=true
+# audit_elasticsearch_urls=ranger-opensearch
+# audit_elasticsearch_port=9200
+# audit_elasticsearch_protocol=http
+# audit_elasticsearch_user=NONE
+# audit_elasticsearch_password=NONE
+# audit_elasticsearch_index=ranger_audits
+# audit_elasticsearch_bootstrap_enabled=true
+
+## Option 3: OpenSearch (native - uses low-level REST client, works with any 
OpenSearch version)
+audit_store=opensearch
+audit_opensearch_urls=ranger-opensearch
+audit_opensearch_port=9200
+audit_opensearch_protocol=http
+audit_opensearch_user=admin
+audit_opensearch_password=admin
+audit_opensearch_index=ranger_audits
+audit_opensearch_bootstrap_enabled=true
 
 policymgr_external_url=http://ranger-admin:6080
 policymgr_http_enabled=true
 
+# Enable Ozone action-matches policy condition (maps to ranger-admin-site.xml 
at setup)
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=false
+
 unix_user=ranger
 unix_user_pwd=ranger
 unix_group=ranger
@@ -103,7 +121,8 @@ 
audit_jaas_client_loginModuleName=com.sun.security.auth.module.Krb5LoginModule
 audit_jaas_client_loginModuleControlFlag=required
 audit_jaas_client_option_useKeyTab=true
 audit_jaas_client_option_storeKey=true
-audit_jaas_client_option_useTicketCache=true
-audit_jaas_client_option_serviceName=ranger
+# Leave empty: SolrJ derives HTTP service from audit URL host (must be FQDN)
+audit_jaas_client_option_serviceName=
+audit_jaas_client_option_useTicketCache=false
 audit_jaas_client_option_keyTab=/etc/keytabs/rangeradmin.keytab
 audit_jaas_client_option_principal=rangeradmin/[email protected]
diff --git 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
index 8a1919374..40f77c3fd 100644
--- 
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
+++ 
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
@@ -68,6 +68,9 @@ audit_elasticsearch_bootstrap_enabled=true
 policymgr_external_url=http://ranger-admin:6080
 policymgr_http_enabled=true
 
+# Enable Ozone action-matches policy condition (maps to ranger-admin-site.xml 
at setup)
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=false
+
 unix_user=ranger
 unix_user_pwd=ranger
 unix_group=ranger
diff --git 
a/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java
 
b/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java
index 44159ce92..95094bd10 100644
--- 
a/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java
+++ 
b/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java
@@ -41,6 +41,7 @@
 import org.apache.ranger.plugin.service.RangerBasePlugin;
 import org.apache.ranger.plugin.util.JsonUtilsV2;
 import org.apache.ranger.plugin.util.RangerPerfTracer;
+import org.apache.ranger.plugin.util.ServiceDefUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,159 +55,171 @@
 import java.util.stream.Collectors;
 
 public class RangerOzoneAuthorizer implements IAccessAuthorizer {
-       public static final String ACCESS_TYPE_READ        = "read";
-       public static final String ACCESS_TYPE_WRITE       = "write";
-       public static final String ACCESS_TYPE_CREATE      = "create";
-       public static final String ACCESS_TYPE_LIST        = "list";
-       public static final String ACCESS_TYPE_DELETE      = "delete";
-       public static final String ACCESS_TYPE_READ_ACL    = "read_acl";
-       public static final String ACCESS_TYPE_WRITE_ACL   = "write_acl";
-       public static final String ACCESS_TYPE_ASSUME_ROLE = "assume_role";
-       public static final String ACCESS_TYPE_ALL         = "all";
+    private static final Logger LOG                        = 
LoggerFactory.getLogger(RangerOzoneAuthorizer.class);
+    private static final Logger PERF_OZONEAUTH_REQUEST_LOG = 
RangerPerfTracer.getPerfLogger("ozoneauth.request");
 
-       public static final String KEY_RESOURCE_VOLUME = "volume";
-       public static final String KEY_RESOURCE_BUCKET = "bucket";
-       public static final String KEY_RESOURCE_KEY    = "key";
-       public static final String KEY_RESOURCE_ROLE   = "role";
+    public static final String ACCESS_TYPE_READ        = "read";
+    public static final String ACCESS_TYPE_WRITE       = "write";
+    public static final String ACCESS_TYPE_CREATE      = "create";
+    public static final String ACCESS_TYPE_LIST        = "list";
+    public static final String ACCESS_TYPE_DELETE      = "delete";
+    public static final String ACCESS_TYPE_READ_ACL    = "read_acl";
+    public static final String ACCESS_TYPE_WRITE_ACL   = "write_acl";
+    public static final String ACCESS_TYPE_ASSUME_ROLE = "assume_role";
+    public static final String ACCESS_TYPE_ALL         = "all";
 
-       private static final String S3_VOLUME_NAME = "s3Vol";
+    public static final String KEY_RESOURCE_VOLUME = "volume";
+    public static final String KEY_RESOURCE_BUCKET = "bucket";
+    public static final String KEY_RESOURCE_KEY    = "key";
+    public static final String KEY_RESOURCE_ROLE   = "role";
 
-       private static final Logger PERF_OZONEAUTH_REQUEST_LOG = 
RangerPerfTracer.getPerfLogger("ozoneauth.request");
+    private static final String S3_VOLUME_NAME = "s3Vol";
 
-    private static final Logger LOG = 
LoggerFactory.getLogger(RangerOzoneAuthorizer.class);
+    private static final String 
OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION = 
"enableActionMatcherInPoliciesCondition";
 
-       private static volatile RangerBasePlugin rangerPlugin = null;
+    private static volatile RangerBasePlugin rangerPlugin;
 
-       public RangerOzoneAuthorizer() {
-               RangerBasePlugin plugin = rangerPlugin;
+    public RangerOzoneAuthorizer() {
+        RangerBasePlugin plugin = rangerPlugin;
+
+        if (plugin == null) {
+            synchronized (RangerOzoneAuthorizer.class) {
+                plugin = rangerPlugin;
 
-               if (plugin == null) {
-                       synchronized (RangerOzoneAuthorizer.class) {
-                               plugin = rangerPlugin;
+                if (plugin == null) {
+                    plugin = new RangerBasePlugin("ozone", "ozone");
 
-                               if (plugin == null) {
-                                       plugin = new RangerBasePlugin("ozone", 
"ozone");
-                                       plugin.init(); // this will initialize 
policy engine and policy refresher
+                    plugin.init(); // this will initialize policy engine and 
policy refresher
 
-                                       RangerDefaultAuditHandler auditHandler 
= new RangerDefaultAuditHandler();
-                                       plugin.setResultProcessor(auditHandler);
+                    RangerDefaultAuditHandler auditHandler = new 
RangerDefaultAuditHandler();
 
-                                       rangerPlugin = plugin;
-                               }
-                       }
-               }
-       }
+                    plugin.setResultProcessor(auditHandler);
 
+                    rangerPlugin = plugin;
+                }
+            }
+        }
+    }
+
+    // for testing only
     RangerOzoneAuthorizer(RangerBasePlugin plugin) {
         rangerPlugin = plugin;
     }
 
-       @Override
-       public boolean checkAccess(IOzoneObj ozoneObject, RequestContext 
context) {
-               boolean returnValue = false;
-               if (ozoneObject == null) {
-                       LOG.error("Ozone object is null!!");
-                       return returnValue;
-               }
-               OzoneObj ozoneObj = (OzoneObj) ozoneObject;
-               UserGroupInformation ugi = context.getClientUgi();
-               ACLType operation = context.getAclRights();
-               String resource = ozoneObj.getPath();
-
-               if (LOG.isDebugEnabled()) {
-                       LOG.debug("==> RangerOzoneAuthorizer.checkAccess with 
operation = " + operation + ", resource = " +
-                                       resource + ", store type = " + 
OzoneObj.StoreType.values() + ", ugi = " + ugi + ", ip = " +
-                                       context.getIp() + ", resourceType = " + 
ozoneObj.getResourceType() + ")");
-               }
-
-               RangerBasePlugin plugin = rangerPlugin;
-
-               if (plugin == null) {
-                       MiscUtil.logErrorMessageByInterval(LOG,
-                                       "Authorizer is still not initialized");
-                       return returnValue;
-               }
-
-               //TODO: If sorce type is S3 and resource is volume, then allow 
it by default
-               if (ozoneObj.getStoreType() == OzoneObj.StoreType.S3 && 
ozoneObj.getResourceType() == OzoneObj.ResourceType.VOLUME) {
-                       if (LOG.isDebugEnabled()) {
-                               LOG.debug("If store type is s3 and resource is 
volume, then we allow it by default!  Returning true");
-                       }
-                       LOG.warn("Allowing access by default since source type 
is S3 and resource type is Volume!!");
-                       return true;
-               }
-
-               RangerPerfTracer perf = null;
-
-               if 
(RangerPerfTracer.isPerfTraceEnabled(PERF_OZONEAUTH_REQUEST_LOG)) {
-                       perf = 
RangerPerfTracer.getPerfTracer(PERF_OZONEAUTH_REQUEST_LOG, 
"RangerOzoneAuthorizer.authorize(resource=" + resource + ")");
-               }
-
-               Date eventTime = new Date();
-               String accessType = mapToRangerAccessType(operation);
-               if (accessType == null) {
-                       MiscUtil.logErrorMessageByInterval(LOG,
-                                       "Unsupported access type. operation=" + 
operation) ;
-                       LOG.error("Unsupported access type. operation=" + 
operation + ", resource=" + resource);
-                       return returnValue;
-               }
-               String clusterName = plugin.getClusterName();
-
-               RangerAccessRequestImpl rangerRequest = new 
RangerAccessRequestImpl();
-               rangerRequest.setUser(ugi.getShortUserName());
-               
rangerRequest.setUserGroups(Sets.newHashSet(ugi.getGroupNames()));
-               
rangerRequest.setClientIPAddress(context.getIp().getHostAddress());
-               
rangerRequest.setRemoteIPAddress(context.getIp().getHostAddress());
-               rangerRequest.setAccessTime(eventTime);
-
-               RangerAccessResourceImpl rangerResource = new 
RangerAccessResourceImpl();
-               rangerResource.setOwnerUser(context.getOwnerName());
-
-               rangerRequest.setResource(rangerResource);
-               rangerRequest.setAccessType(accessType);
-               rangerRequest.setAction(context.getS3Action());
-               rangerRequest.setRequestData(resource);
-               rangerRequest.setClusterName(clusterName);
-
-               if (ozoneObj.getResourceType() == OzoneObj.ResourceType.VOLUME) 
{
-                       rangerResource.setValue(KEY_RESOURCE_VOLUME, 
ozoneObj.getVolumeName());
-               } else if (ozoneObj.getResourceType() == 
OzoneObj.ResourceType.BUCKET || ozoneObj.getResourceType() == 
OzoneObj.ResourceType.KEY) {
-                       rangerResource.setValue(KEY_RESOURCE_VOLUME, 
ozoneObj.getStoreType() == OzoneObj.StoreType.S3 ? S3_VOLUME_NAME : 
ozoneObj.getVolumeName());
-                       rangerResource.setValue(KEY_RESOURCE_BUCKET, 
ozoneObj.getBucketName());
-                       if (ozoneObj.getResourceType() == 
OzoneObj.ResourceType.KEY) {
-                               rangerResource.setValue(KEY_RESOURCE_KEY, 
ozoneObj.getKeyName());
-                       }
-               } else {
-                       LOG.error("Unsupported resource = " + resource);
-                       MiscUtil.logErrorMessageByInterval(LOG, "Unsupported 
resource type " + ozoneObj.getResourceType() + " for resource = " + resource
-                                       + ", request=" + rangerRequest);
-                       return returnValue;
-               }
-
-               try {
-                       if (StringUtils.isNotBlank(context.getSessionPolicy())) 
{
-                               
rangerRequest.setInlinePolicy(JsonUtilsV2.jsonToObj(context.getSessionPolicy(), 
RangerInlinePolicy.class));
-                       }
-
-                       RangerAccessResult result = plugin
-                                       .isAccessAllowed(rangerRequest);
-                       if (result == null) {
-                               LOG.error("Ranger Plugin returned null. 
Returning false");
-                       } else {
-                               returnValue = result.getIsAllowed();
-                       }
-               } catch (Throwable t) {
-                       LOG.error("Error while calling isAccessAllowed(). 
request="
-                                       + rangerRequest, t);
-               }
-               RangerPerfTracer.log(perf);
-
-               if (LOG.isDebugEnabled()) {
-                       LOG.debug("rangerRequest=" + rangerRequest + ", return="
-                                       + returnValue);
-               }
-               return returnValue;
-       }
+    @Override
+    public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) {
+        boolean returnValue = false;
+
+        if (ozoneObject == null) {
+            LOG.error("Ozone object is null!!");
+
+            return false;
+        }
+
+        OzoneObj             ozoneObj  = (OzoneObj) ozoneObject;
+        UserGroupInformation ugi       = context.getClientUgi();
+        ACLType              operation = context.getAclRights();
+        String               resource  = ozoneObj.getPath();
+
+        LOG.debug("==> RangerOzoneAuthorizer.checkAccess with operation = {}, 
resource = {}, store type = {}, ugi = {}, ip = {}, resourceType = {}", 
operation, resource, OzoneObj.StoreType.values(), ugi, context.getIp(), 
ozoneObj.getResourceType());
+
+        RangerBasePlugin plugin = rangerPlugin;
+
+        if (plugin == null) {
+            MiscUtil.logErrorMessageByInterval(LOG, "Authorizer is still not 
initialized");
+
+            return false;
+        }
+
+        //TODO: If source type is S3 and resource is volume, then allow it by 
default
+        if (ozoneObj.getStoreType() == OzoneObj.StoreType.S3 && 
ozoneObj.getResourceType() == OzoneObj.ResourceType.VOLUME) {
+            LOG.debug("If store type is s3 and resource is volume, then we 
allow it by default!  Returning true");
+            LOG.warn("Allowing access by default since source type is S3 and 
resource type is Volume!!");
+
+            return true;
+        }
+
+        RangerPerfTracer perf = null;
+
+        if (RangerPerfTracer.isPerfTraceEnabled(PERF_OZONEAUTH_REQUEST_LOG)) {
+            perf = RangerPerfTracer.getPerfTracer(PERF_OZONEAUTH_REQUEST_LOG, 
String.format("RangerOzoneAuthorizer.authorize(resource = %s)", resource));
+        }
+
+        Date   eventTime  = new Date();
+        String accessType = mapToRangerAccessType(operation);
+
+        if (accessType == null) {
+            String message = String.format("Unsupported access type. operation 
= %s", operation);
+
+            MiscUtil.logErrorMessageByInterval(LOG, message);
+            LOG.error("{}, resource = {}", message, resource);
+
+            return false;
+        }
+
+        String                  clusterName   = plugin.getClusterName();
+        RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl();
+
+        rangerRequest.setUser(ugi.getShortUserName());
+        rangerRequest.setUserGroups(Sets.newHashSet(ugi.getGroupNames()));
+        rangerRequest.setClientIPAddress(context.getIp().getHostAddress());
+        rangerRequest.setRemoteIPAddress(context.getIp().getHostAddress());
+        rangerRequest.setAccessTime(eventTime);
+
+        RangerAccessResourceImpl rangerResource = new 
RangerAccessResourceImpl();
+
+        rangerResource.setOwnerUser(context.getOwnerName());
+        rangerRequest.setResource(rangerResource);
+        rangerRequest.setAccessType(accessType);
+        if (isOzoneActionPolicyEnabled(plugin)) {
+            rangerRequest.setAction(context.getS3Action());
+        } else {
+            rangerRequest.setAction(accessType);
+        }
+        rangerRequest.setRequestData(resource);
+        rangerRequest.setClusterName(clusterName);
+
+        if (ozoneObj.getResourceType() == OzoneObj.ResourceType.VOLUME) {
+            rangerResource.setValue(KEY_RESOURCE_VOLUME, 
ozoneObj.getVolumeName());
+        } else if (ozoneObj.getResourceType() == OzoneObj.ResourceType.BUCKET 
|| ozoneObj.getResourceType() == OzoneObj.ResourceType.KEY) {
+            rangerResource.setValue(KEY_RESOURCE_VOLUME, 
ozoneObj.getStoreType() == OzoneObj.StoreType.S3 ? S3_VOLUME_NAME : 
ozoneObj.getVolumeName());
+            rangerResource.setValue(KEY_RESOURCE_BUCKET, 
ozoneObj.getBucketName());
+
+            if (ozoneObj.getResourceType() == OzoneObj.ResourceType.KEY) {
+                rangerResource.setValue(KEY_RESOURCE_KEY, 
ozoneObj.getKeyName());
+            }
+        } else {
+            LOG.error("Unsupported resource = {}", resource);
+
+            String message = String.format("Unsupported resource type = %s for 
resource = %s, request = %s", ozoneObj.getResourceType(), resource, 
rangerRequest);
+            MiscUtil.logErrorMessageByInterval(LOG, message);
+
+            return false;
+        }
+
+        try {
+            if (StringUtils.isNotBlank(context.getSessionPolicy())) {
+                RangerInlinePolicy inlinePolicy = 
JsonUtilsV2.jsonToObj(context.getSessionPolicy(), RangerInlinePolicy.class);
+                
rangerRequest.setInlinePolicy(sanitizeInlinePolicyForActionFlag(inlinePolicy, 
plugin));
+            }
+
+            RangerAccessResult result = plugin.isAccessAllowed(rangerRequest);
+
+            if (result == null) {
+                LOG.error("Ranger Plugin returned null. Returning false");
+            } else {
+                returnValue = result.getIsAllowed();
+            }
+        } catch (Throwable t) {
+            LOG.error("Error while calling isAccessAllowed(). request = {}", 
rangerRequest, t);
+        }
+
+        RangerPerfTracer.log(perf);
+
+        LOG.debug("rangerRequest = {}, return = {}", rangerRequest, 
returnValue);
+
+        return returnValue;
+    }
 
     @Override
     public String generateAssumeRoleSessionPolicy(AssumeRoleRequest 
assumeRoleRequest) throws OMException {
@@ -263,33 +276,39 @@ public String 
generateAssumeRoleSessionPolicy(AssumeRoleRequest assumeRoleReques
         }
     }
 
-       private String mapToRangerAccessType(ACLType operation) {
-               String rangerAccessType = null;
-               switch (operation) {
-                       case READ:
-                               rangerAccessType = ACCESS_TYPE_READ;
-                               break;
-                       case WRITE:
-                               rangerAccessType = ACCESS_TYPE_WRITE;
-                               break;
-                       case CREATE:
-                               rangerAccessType = ACCESS_TYPE_CREATE;
-                               break;
-                       case DELETE:
-                               rangerAccessType = ACCESS_TYPE_DELETE;
-                               break;
-                       case LIST:
-                               rangerAccessType = ACCESS_TYPE_LIST;
-                               break;
-                       case READ_ACL:
-                               rangerAccessType = ACCESS_TYPE_READ_ACL;
-                               break;
-                       case WRITE_ACL:
-                               rangerAccessType = ACCESS_TYPE_WRITE_ACL;
-                               break;
-               }
-               return rangerAccessType;
-       }
+    private String mapToRangerAccessType(ACLType operation) {
+        final String rangerAccessType;
+
+        switch (operation) {
+            case READ:
+                rangerAccessType = ACCESS_TYPE_READ;
+                break;
+            case WRITE:
+                rangerAccessType = ACCESS_TYPE_WRITE;
+                break;
+            case CREATE:
+                rangerAccessType = ACCESS_TYPE_CREATE;
+                break;
+            case DELETE:
+                rangerAccessType = ACCESS_TYPE_DELETE;
+                break;
+            case LIST:
+                rangerAccessType = ACCESS_TYPE_LIST;
+                break;
+            case READ_ACL:
+                rangerAccessType = ACCESS_TYPE_READ_ACL;
+                break;
+            case WRITE_ACL:
+                rangerAccessType = ACCESS_TYPE_WRITE_ACL;
+                break;
+            default:
+                LOG.error("Unknown operation!");
+                rangerAccessType = null;
+                break;
+        }
+
+        return rangerAccessType;
+    }
 
     private static RangerInlinePolicy.Grant toRangerGrant(OzoneGrant 
ozoneGrant, RangerBasePlugin plugin) {
         RangerInlinePolicy.Grant ret = new RangerInlinePolicy.Grant();
@@ -302,9 +321,12 @@ private static RangerInlinePolicy.Grant 
toRangerGrant(OzoneGrant ozoneGrant, Ran
             
ret.setPermissions(ozoneGrant.getPermissions().stream().map(RangerOzoneAuthorizer::toRangerPermission).filter(Objects::nonNull).collect(Collectors.toSet()));
         }
 
-        final Set<String> s3Actions = ozoneGrant.getS3Actions();
-        if (s3Actions != null && !s3Actions.isEmpty()) {
-            ret.setActions(s3Actions);
+        if (isOzoneActionPolicyEnabled(plugin)) {
+            final Set<String> s3Actions = ozoneGrant.getS3Actions();
+
+            if (s3Actions != null && !s3Actions.isEmpty()) {
+                ret.setActions(s3Actions);
+            }
         }
 
         LOG.debug("toRangerGrant(ozoneGrant={}): ret={}", ozoneGrant, ret);
@@ -348,7 +370,6 @@ private static String toRrn(IOzoneObj obj, RangerBasePlugin 
plugin) {
         return ret;
     }
 
-
     private static String toRangerPermission(ACLType acl) {
         switch (acl) {
             case READ:
@@ -374,5 +395,26 @@ private static String toRangerPermission(ACLType acl) {
 
         return null;
     }
-}
 
+    private static boolean isOzoneActionPolicyEnabled(final RangerBasePlugin 
plugin) {
+        return plugin != null
+                && plugin.getServiceDef() != null
+                && 
ServiceDefUtil.getBooleanValue(plugin.getServiceDef().getOptions(), 
OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, false);
+    }
+
+    /**
+     * When action-policy is disabled, strip grant actions so generic inline 
evaluation
+     * does not enforce S3 actions (same effect as omitting actions in {@link 
#toRangerGrant}).
+     */
+    static RangerInlinePolicy 
sanitizeInlinePolicyForActionFlag(RangerInlinePolicy policy, RangerBasePlugin 
plugin) {
+        if (policy != null && policy.getGrants() != null && 
!isOzoneActionPolicyEnabled(plugin)) {
+            for (RangerInlinePolicy.Grant grant : policy.getGrants()) {
+                if (grant != null) {
+                    grant.setActions(null);
+                }
+            }
+        }
+
+        return policy;
+    }
+}
diff --git 
a/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java
 
b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java
index a715e9c6f..7a90e758d 100644
--- 
a/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java
+++ 
b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java
@@ -37,7 +37,9 @@
 import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -53,8 +55,10 @@ public class TestRangerOzoneAuthorizer {
     private static final String RANGER_APP_ID       = "om";
     private static final String OZONE_SERVICE_ID    = "om";
     private static final String OWNER_NAME          = "ozone";
+    private static final String 
OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION = 
"enableActionMatcherInPoliciesCondition";
 
     private static RangerOzoneAuthorizer ozoneAuthorizer;
+    private static RangerBasePlugin    testPlugin;
 
     private final String               hostname  = "localhost";
     private final InetAddress          ipAddress = 
InetAddress.getLoopbackAddress();
@@ -85,7 +89,9 @@ public static void setUpBeforeClass() {
 
         // loads policies from om_dev_ozone.json, by 
EmbeddedResourcePolicySource configured in ranger-ozone-security.xml
         plugin.init();
+        setOzoneActionPolicyEnabled(plugin);
 
+        testPlugin      = plugin;
         ozoneAuthorizer = new RangerOzoneAuthorizer(plugin);
 
         assertNotNull(ozoneAuthorizer);
@@ -273,4 +279,129 @@ public void testCheckAccessWithS3Action() throws 
Exception {
         assertFalse(ozoneAuthorizer.checkAccess(key1, ctxWriteNoAction),
                 "session-policy should deny write on key1 when no S3 action is 
specified and grant has S3 actions");
     }
+
+    @Test
+    public void testAssumeRoleWithS3ActionsNotPropagatedWhenFlagDisabled() 
throws Exception {
+        final String previous = setOzoneActionPolicyDisabled();
+
+        try {
+            final Set<OzoneGrant>   grants  = 
Collections.singleton(grantWriteWithS3Actions);
+            final AssumeRoleRequest request = new AssumeRoleRequest(hostname, 
ipAddress, user1, role1, grants);
+
+            final String sessionPolicy = 
ozoneAuthorizer.generateAssumeRoleSessionPolicy(request);
+
+            assertNotNull(sessionPolicy);
+
+            final RangerInlinePolicy inlinePolicy = 
JsonUtilsV2.jsonToObj(sessionPolicy, RangerInlinePolicy.class);
+            final RangerInlinePolicy.Grant grant  = 
inlinePolicy.getGrants().iterator().next();
+
+            assertNull(grant.getActions(), "S3 actions should not be 
propagated when ozone action policy is disabled");
+        } finally {
+            restoreOzoneActionPolicyEnabled(previous);
+        }
+    }
+
+    @Test
+    public void testCheckAccessIgnoresS3ActionWhenFlagDisabled() throws 
Exception {
+        final String previous = setOzoneActionPolicyDisabled();
+
+        try {
+            final Set<OzoneGrant>   grants  = 
Collections.singleton(grantWriteWithS3Actions);
+            final AssumeRoleRequest request = new AssumeRoleRequest(hostname, 
ipAddress, user1, role1, grants);
+
+            final String sessionPolicy = 
ozoneAuthorizer.generateAssumeRoleSessionPolicy(request);
+
+            assertNotNull(sessionPolicy);
+
+            final RequestContext ctxWriteGetObject = reqCtxBuilder
+                    .setAclRights(IAccessAuthorizer.ACLType.WRITE)
+                    .setRecursiveAccessCheck(false)
+                    .setSessionPolicy(sessionPolicy)
+                    .setS3Action("GetObject")
+                    .build();
+
+            assertTrue(ozoneAuthorizer.checkAccess(key1, ctxWriteGetObject),
+                    "session-policy should allow write when S3 action 
enforcement is disabled");
+
+            final RequestContext ctxWriteNoAction = reqCtxBuilder
+                    .setAclRights(IAccessAuthorizer.ACLType.WRITE)
+                    .setRecursiveAccessCheck(false)
+                    .setSessionPolicy(sessionPolicy)
+                    .setS3Action(null)
+                    .build();
+
+            assertTrue(ozoneAuthorizer.checkAccess(key1, ctxWriteNoAction),
+                    "session-policy should allow write when S3 action 
enforcement is disabled");
+        } finally {
+            restoreOzoneActionPolicyEnabled(previous);
+        }
+    }
+
+    @Test
+    public void testLegacySessionPolicyActionsStrippedWhenFlagDisabled() 
throws Exception {
+        // Simulate a session policy issued when action-policy was enabled 
(actions embedded in JSON).
+        Set<OzoneGrant>   grants  = 
Collections.singleton(grantWriteWithS3Actions);
+        AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, 
user1, role1, grants);
+
+        String legacySessionPolicy = 
ozoneAuthorizer.generateAssumeRoleSessionPolicy(request);
+
+        RangerInlinePolicy legacyPolicy = 
JsonUtilsV2.jsonToObj(legacySessionPolicy, RangerInlinePolicy.class);
+        RangerInlinePolicy.Grant legacyGrant = 
legacyPolicy.getGrants().iterator().next();
+
+        assertNotNull(legacyGrant.getActions(),
+                "legacy session policy should contain S3 actions as if created 
with flag enabled");
+
+        final String previous = setOzoneActionPolicyDisabled();
+
+        try {
+            RangerInlinePolicy sanitized = 
RangerOzoneAuthorizer.sanitizeInlinePolicyForActionFlag(legacyPolicy, 
testPlugin);
+
+            assertNull(sanitized.getGrants().iterator().next().getActions(),
+                    "sanitizeInlinePolicyForActionFlag should strip grant 
actions when flag is disabled");
+
+            RequestContext ctxWriteGetObject = reqCtxBuilder
+                    .setAclRights(IAccessAuthorizer.ACLType.WRITE)
+                    .setRecursiveAccessCheck(false)
+                    .setSessionPolicy(legacySessionPolicy)
+                    .setS3Action("GetObject")
+                    .build();
+
+            assertTrue(ozoneAuthorizer.checkAccess(key1, ctxWriteGetObject),
+                    "legacy session-policy with embedded S3 actions should 
allow write when flag is disabled");
+        } finally {
+            restoreOzoneActionPolicyEnabled(previous);
+        }
+    }
+
+    private static void setOzoneActionPolicyEnabled(RangerBasePlugin plugin) {
+        if (plugin == null || plugin.getServiceDef() == null) {
+            return;
+        }
+
+        Map<String, String> options = plugin.getServiceDef().getOptions();
+
+        if (options == null) {
+            options = new HashMap<>();
+            plugin.getServiceDef().setOptions(options);
+        }
+
+        options.put(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, 
Boolean.toString(true));
+    }
+
+    private static String setOzoneActionPolicyDisabled() {
+        final Map<String, String> options = 
testPlugin.getServiceDef().getOptions();
+        final String previous = 
options.put(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, 
Boolean.toString(false));
+
+        return previous;
+    }
+
+    private static void restoreOzoneActionPolicyEnabled(String previous) {
+        final Map<String, String> options = 
testPlugin.getServiceDef().getOptions();
+
+        if (previous == null) {
+            options.remove(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION);
+        } else {
+            options.put(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, 
previous);
+        }
+    }
 }
diff --git a/security-admin/scripts/install.properties 
b/security-admin/scripts/install.properties
index 0f5a093f6..6a6ababc0 100644
--- a/security-admin/scripts/install.properties
+++ b/security-admin/scripts/install.properties
@@ -101,6 +101,19 @@ audit_elasticsearch_password=
 audit_elasticsearch_index=
 audit_elasticsearch_bootstrap_enabled=true
 
+# * OpenSearch audit store properties (when audit_store=opensearch)
+audit_opensearch_urls=
+audit_opensearch_port=9200
+audit_opensearch_protocol=http
+audit_opensearch_user=
+audit_opensearch_password=
+audit_opensearch_index=ranger_audits
+audit_opensearch_bootstrap_enabled=true
+# * OpenSearch authentication scheme: kerberos, basic, or none. Leave empty to 
infer from the properties above.
+audit_opensearch_authentication_type=
+audit_opensearch_kerberos_principal=
+audit_opensearch_kerberos_keytab=
+
 
 # * audit_solr_url URL to Solr. E.g. http://<solr_host>:6083/solr/ranger_audits
 audit_solr_urls=
@@ -139,6 +152,9 @@ policymgr_https_keystore_password=
 #Example :  policymgr_supportedcomponents=hive,hbase,hdfs
 policymgr_supportedcomponents=
 
+# Enable Ozone action-matches policy condition in Admin UI and authorizer
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=false
+
 #
 # ------- PolicyManager CONFIG - END ---------------
 #
diff --git a/security-admin/scripts/setup.sh b/security-admin/scripts/setup.sh
index 43ef4b704..b0bc26214 100755
--- a/security-admin/scripts/setup.sh
+++ b/security-admin/scripts/setup.sh
@@ -102,6 +102,16 @@ audit_elasticsearch_user=$(get_prop 
'audit_elasticsearch_user' $PROPFILE)
 audit_elasticsearch_password=$(get_prop 'audit_elasticsearch_password' 
$PROPFILE)
 audit_elasticsearch_index=$(get_prop 'audit_elasticsearch_index' $PROPFILE)
 audit_elasticsearch_bootstrap_enabled=$(get_prop 
'audit_elasticsearch_bootstrap_enabled' $PROPFILE)
+audit_opensearch_urls=$(get_prop 'audit_opensearch_urls' $PROPFILE)
+audit_opensearch_protocol=$(get_prop 'audit_opensearch_protocol' $PROPFILE)
+audit_opensearch_port=$(get_prop 'audit_opensearch_port' $PROPFILE)
+audit_opensearch_user=$(get_prop 'audit_opensearch_user' $PROPFILE)
+audit_opensearch_password=$(get_prop 'audit_opensearch_password' $PROPFILE)
+audit_opensearch_index=$(get_prop 'audit_opensearch_index' $PROPFILE)
+audit_opensearch_bootstrap_enabled=$(get_prop 
'audit_opensearch_bootstrap_enabled' $PROPFILE)
+audit_opensearch_authentication_type=$(get_prop_or_default 
'audit_opensearch_authentication_type' $PROPFILE '')
+audit_opensearch_kerberos_principal=$(get_prop_or_default 
'audit_opensearch_kerberos_principal' $PROPFILE '')
+audit_opensearch_kerberos_keytab=$(get_prop_or_default 
'audit_opensearch_kerberos_keytab' $PROPFILE '')
 audit_solr_urls=$(get_prop 'audit_solr_urls' $PROPFILE)
 audit_solr_user=$(get_prop_or_default 'audit_solr_user' $PROPFILE '')
 audit_solr_password=$(get_prop_or_default 'audit_solr_password' $PROPFILE '')
@@ -165,6 +175,7 @@ cred_keystore_filename=$(eval echo "$(get_prop 
'cred_keystore_filename' $PROPFIL
 sso_enabled=$(get_prop 'sso_enabled' $PROPFILE)
 sso_providerurl=$(get_prop 'sso_providerurl' $PROPFILE)
 sso_publickey=$(get_prop 'sso_publickey' $PROPFILE)
+FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=$(get_prop_or_default 
'FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION' $PROPFILE 'false')
 RANGER_ADMIN_LOG_DIR=$(eval echo "$(get_prop 'RANGER_ADMIN_LOG_DIR' 
$PROPFILE)")
 RANGER_ADMIN_LOGBACK_CONF_FILE=$(eval echo "$(get_prop 
'RANGER_ADMIN_LOGBACK_CONF_FILE' $PROPFILE)")
 RANGER_PID_DIR_PATH=$(eval echo "$(get_prop 'RANGER_PID_DIR_PATH' $PROPFILE)")
@@ -305,6 +316,16 @@ init_variables(){
                        exit 1
                fi
        fi
+       if [ "${audit_store}" == "opensearch" ] ;then
+               if [ "${audit_opensearch_urls}" == "" ] ;then
+                       log "[I] Please provide valid URL for 'opensearch' 
audit store!"
+                       exit 1
+               fi
+               if [ "${audit_opensearch_port}" == "" ] ;then
+                       log "[I] Please provide valid port for 'opensearch' 
audit store!"
+                       exit 1
+               fi
+       fi
 
        if [ "${audit_store}" == "cloudwatch" ] ;then
                if [ "${audit_cloudwatch_region}" == "" ] ;then
@@ -857,6 +878,50 @@ update_properties() {
 
        fi
 
+       if [ "${audit_store}" == "opensearch" ]
+       then
+               propertyName=ranger.audit.opensearch.urls
+               newPropertyValue=${audit_opensearch_urls}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.protocol
+               newPropertyValue=${audit_opensearch_protocol}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.port
+               newPropertyValue=${audit_opensearch_port}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.user
+               newPropertyValue=${audit_opensearch_user}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.password
+               newPropertyValue=${audit_opensearch_password}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.index
+               newPropertyValue=${audit_opensearch_index}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.authentication.type
+               newPropertyValue=${audit_opensearch_authentication_type}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.kerberos.principal
+               newPropertyValue=${audit_opensearch_kerberos_principal}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.kerberos.keytab
+               newPropertyValue=${audit_opensearch_kerberos_keytab}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+               propertyName=ranger.audit.opensearch.bootstrap.enabled
+               newPropertyValue=${audit_opensearch_bootstrap_enabled}
+               updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
+       fi
+
        if [ "${audit_store}" == "cloudwatch" ]
        then
                propertyName=ranger.audit.amazon_cloudwatch.region
@@ -1026,6 +1091,16 @@ update_properties() {
                 updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
 
        fi
+
+       ff_enable_ozone_action_matches_condition=$(echo 
"${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION}" | tr '[:upper:]' '[:lower:]')
+       if [ "${ff_enable_ozone_action_matches_condition}" != "true" ]
+       then
+               ff_enable_ozone_action_matches_condition=false
+       fi
+       
propertyName=ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition
+       newPropertyValue="${ff_enable_ozone_action_matches_condition}"
+       updatePropertyToFilePy $propertyName "${newPropertyValue}" 
$to_file_ranger
+
        if [ "${javax_net_ssl_keyStore}" != "" ]  && [ 
"${javax_net_ssl_keyStorePassword}" != "" ]
        then
                javax_net_ssl_keyStoreAlias=keyStoreAlias
diff --git 
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
 
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
index b0ff26134..78e269aa6 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
@@ -17,85 +17,200 @@
 
 package org.apache.ranger.service;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ranger.authorization.hadoop.config.RangerAdminConfig;
 import org.apache.ranger.entity.XXServiceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef;
+import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
 import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 @Service
 @Scope("singleton")
 public class RangerServiceDefService extends 
RangerServiceDefServiceBase<XXServiceDef, RangerServiceDef> {
-       private final RangerAdminConfig config;
-
-       public RangerServiceDefService() {
-               super();
-
-               this.config = RangerAdminConfig.getInstance();
-       }
-
-       @Override
-       protected void validateForCreate(RangerServiceDef vObj) {
-
-       }
-
-       @Override
-       protected void validateForUpdate(RangerServiceDef vObj, XXServiceDef 
entityObj) {
-
-       }
-
-       @Override
-       protected XXServiceDef mapViewToEntityBean(RangerServiceDef vObj, 
XXServiceDef xObj, int OPERATION_CONTEXT) {
-               return super.mapViewToEntityBean(vObj, xObj, OPERATION_CONTEXT);
-       }
-
-       @Override
-       protected RangerServiceDef mapEntityToViewBean(RangerServiceDef vObj, 
XXServiceDef xObj) {
-               RangerServiceDef ret =  super.mapEntityToViewBean(vObj, xObj);
-
-               Map<String, String> serviceDefOptions = ret.getOptions();
-
-               if 
(serviceDefOptions.get(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES)
 == null) {
-                       boolean enableDenyAndExceptionsInPoliciesHiddenOption = 
config.getBoolean("ranger.servicedef.enableDenyAndExceptionsInPolicies", true);
-                       if (enableDenyAndExceptionsInPoliciesHiddenOption || 
StringUtils.equalsIgnoreCase(ret.getName(), 
EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_TAG_NAME)) {
-                               
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES,
 "true");
-                       } else {
-                               
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES,
 "false");
-                       }
-                       ret.setOptions(serviceDefOptions);
-               }
-
-               if 
(serviceDefOptions.get(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES) == 
null) {
-                       boolean enableTagBasedPoliciesHiddenOption = 
config.getBoolean("ranger.servicedef.enableTagBasedPolicies", true);
-                       if (enableTagBasedPoliciesHiddenOption) {
-                               
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES, 
"true");
-                       } else {
-                               
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES, 
"false");
-                       }
-                       ret.setOptions(serviceDefOptions);
-               }
-               return ret;
-       }
-
-       public List<RangerServiceDef> getAllServiceDefs() {
-               List<XXServiceDef> xxServiceDefList = getDao().getAll();
-               List<RangerServiceDef> serviceDefList = new 
ArrayList<RangerServiceDef>();
-
-               for (XXServiceDef xxServiceDef : xxServiceDefList) {
-                       RangerServiceDef serviceDef = 
populateViewBean(xxServiceDef);
-                       serviceDefList.add(serviceDef);
-               }
-               return serviceDefList;
-       }
-
-       public RangerServiceDef getPopulatedViewObject(XXServiceDef 
xServiceDef) {
-               return this.populateViewBean(xServiceDef);
-       }
+    public static final String 
PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE = 
"ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition";
+    public static final String 
OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION = 
"enableActionMatcherInPoliciesCondition";
+
+    private static final String POLICY_CONDITION_ACTION_MATCHES = 
"action-matches";
+
+    private final RangerAdminConfig config;
+
+    public RangerServiceDefService() {
+        super();
+
+        this.config = RangerAdminConfig.getInstance();
+    }
+
+    public List<RangerServiceDef> getAllServiceDefs() {
+        List<XXServiceDef>     xxServiceDefList = getDao().getAll();
+        List<RangerServiceDef> serviceDefList   = new ArrayList<>();
+
+        for (XXServiceDef xxServiceDef : xxServiceDefList) {
+            RangerServiceDef serviceDef = populateViewBean(xxServiceDef);
+
+            serviceDefList.add(serviceDef);
+        }
+
+        return serviceDefList;
+    }
+
+    public RangerServiceDef getPopulatedViewObject(XXServiceDef xServiceDef) {
+        return this.populateViewBean(xServiceDef);
+    }
+
+    @Override
+    protected void validateForCreate(RangerServiceDef vObj) {
+    }
+
+    @Override
+    protected void validateForUpdate(RangerServiceDef vObj, XXServiceDef 
entityObj) {
+    }
+
+    @Override
+    protected XXServiceDef mapViewToEntityBean(RangerServiceDef vObj, 
XXServiceDef xObj, int operationContext) {
+        return super.mapViewToEntityBean(vObj, xObj, operationContext);
+    }
+
+    @Override
+    protected RangerServiceDef mapEntityToViewBean(RangerServiceDef vObj, 
XXServiceDef xObj) {
+        RangerServiceDef    ret               = 
super.mapEntityToViewBean(vObj, xObj);
+        Map<String, String> serviceDefOptions = ret.getOptions();
+
+        if 
(serviceDefOptions.get(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES)
 == null) {
+            boolean enableDenyAndExceptionsInPoliciesHiddenOption = 
config.getBoolean("ranger.servicedef.enableDenyAndExceptionsInPolicies", true);
+
+            if (enableDenyAndExceptionsInPoliciesHiddenOption || 
StringUtils.equalsIgnoreCase(ret.getName(), 
EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_TAG_NAME)) {
+                
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES,
 "true");
+            } else {
+                
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_DENY_AND_EXCEPTIONS_IN_POLICIES,
 "false");
+            }
+
+            ret.setOptions(serviceDefOptions);
+        }
+
+        if 
(serviceDefOptions.get(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES) == 
null) {
+            boolean enableTagBasedPoliciesHiddenOption = 
config.getBoolean("ranger.servicedef.enableTagBasedPolicies", true);
+
+            if (enableTagBasedPoliciesHiddenOption) {
+                
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES, 
"true");
+            } else {
+                
serviceDefOptions.put(RangerServiceDef.OPTION_ENABLE_TAG_BASED_POLICIES, 
"false");
+            }
+
+            ret.setOptions(serviceDefOptions);
+        }
+
+        return ret;
+    }
+
+    @Override
+    protected RangerServiceDef populateViewBean(XXServiceDef xServiceDef) {
+        final RangerServiceDef ret = super.populateViewBean(xServiceDef);
+
+        applyActionMatcherInPoliciesConditionHiddenOption(ret);
+
+        return ret;
+    }
+
+    void applyActionMatcherInPoliciesConditionHiddenOption(RangerServiceDef 
serviceDef) {
+        if (serviceDef == null) {
+            return;
+        }
+
+        final boolean isOzoneServiceDef = 
StringUtils.equalsIgnoreCase(serviceDef.getName(), 
EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME);
+
+        if (!isOzoneServiceDef) {
+            return;
+        }
+
+        Map<String, String> serviceDefOptions = serviceDef.getOptions();
+
+        if (serviceDefOptions == null) {
+            serviceDefOptions = new HashMap<>();
+            serviceDef.setOptions(serviceDefOptions);
+        }
+
+        // Admin site config is authoritative at runtime; stale def_options 
must not override it.
+        boolean enabled = 
config.getBoolean(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE, 
false);
+
+        
serviceDefOptions.put(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, 
Boolean.toString(enabled));
+        serviceDef.setOptions(serviceDefOptions);
+
+        if (enabled) {
+            ensureActionMatchesPolicyCondition(serviceDef);
+        } else {
+            removeActionMatchesPolicyCondition(serviceDef);
+        }
+    }
+
+    private void ensureActionMatchesPolicyCondition(RangerServiceDef 
serviceDef) {
+        if (hasActionMatchesPolicyCondition(serviceDef)) {
+            return;
+        }
+
+        try {
+            final RangerServiceDef embeddedOzoneServiceDef = 
EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME);
+
+            if (embeddedOzoneServiceDef == null || 
embeddedOzoneServiceDef.getPolicyConditions() == null) {
+                return;
+            }
+
+            for (RangerPolicyConditionDef embeddedPolicyConditionDef : 
embeddedOzoneServiceDef.getPolicyConditions()) {
+                if (StringUtils.equals(embeddedPolicyConditionDef.getName(), 
POLICY_CONDITION_ACTION_MATCHES)) {
+                    List<RangerPolicyConditionDef> policyConditions = 
serviceDef.getPolicyConditions();
+
+                    if (policyConditions == null) {
+                        policyConditions = new ArrayList<>();
+                        serviceDef.setPolicyConditions(policyConditions);
+                    }
+
+                    policyConditions.add(embeddedPolicyConditionDef);
+
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to restore ozone action-matches 
policy condition", e);
+        }
+    }
+
+    private void removeActionMatchesPolicyCondition(RangerServiceDef 
serviceDef) {
+        List<RangerPolicyConditionDef> policyConditions = 
serviceDef.getPolicyConditions();
+
+        if (policyConditions == null || policyConditions.isEmpty()) {
+            return;
+        }
+
+        List<RangerPolicyConditionDef> filteredPolicyConditions = new 
ArrayList<>();
+
+        for (RangerPolicyConditionDef policyConditionDef : policyConditions) {
+            if (!StringUtils.equals(policyConditionDef.getName(), 
POLICY_CONDITION_ACTION_MATCHES)) {
+                filteredPolicyConditions.add(policyConditionDef);
+            }
+        }
+
+        serviceDef.setPolicyConditions(filteredPolicyConditions);
+    }
+
+    private static boolean hasActionMatchesPolicyCondition(RangerServiceDef 
serviceDef) {
+        List<RangerPolicyConditionDef> policyConditions = 
serviceDef.getPolicyConditions();
+
+        if (policyConditions == null || policyConditions.isEmpty()) {
+            return false;
+        }
+
+        for (RangerPolicyConditionDef policyConditionDef : policyConditions) {
+            if (StringUtils.equals(policyConditionDef.getName(), 
POLICY_CONDITION_ACTION_MATCHES)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git 
a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
index 1af339010..164b63d02 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
@@ -152,6 +152,14 @@
                <name>ranger.resource.accessControl.enabled</name>
                <value>true</value>
        </property>
+       <property>
+               
<name>ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition</name>
+               <value>false</value>
+               <description>
+                       When true, enables Ozone action-matches 
(RangerActionMatcher) policy conditions
+                       in the Admin UI and applies S3 action-based evaluation 
in the Ozone authorizer.
+               </description>
+       </property>
        <property>
                <name>ranger.xuser.createdByUserId</name>
                <value>1</value>
@@ -322,7 +330,7 @@
 
        <property>
                <name>ranger.service.https.attrib.ssl.protocol</name>
-               <value>TLSv1.2</value>
+               <value>TLS</value>
        </property>
 
        <property>
@@ -516,6 +524,34 @@
                <name>ranger.audit.elasticsearch.bootstrap.enabled</name>
                <value>true</value>
        </property>
+       <property>
+               <name>ranger.audit.opensearch.urls</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.protocol</name>
+               <value>http</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.port</name>
+               <value>9200</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.user</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.password</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.index</name>
+               <value>ranger_audits</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.bootstrap.enabled</name>
+               <value>true</value>
+       </property>
        <property>
                <name>ranger.audit.solr.max.retry</name>
                <value>30</value>
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
index a75cd05a3..388ce1674 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
@@ -79,6 +79,49 @@
                <name>ranger.audit.elasticsearch.bootstrap.enabled</name>
                <value>true</value>
        </property>
+       <property>
+               <name>ranger.audit.opensearch.urls</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.protocol</name>
+               <value>http</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.port</name>
+               <value>9200</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.user</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.password</name>
+               <value></value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.index</name>
+               <value>ranger_audits</value>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.authentication.type</name>
+               <value></value>
+               <description>OpenSearch authentication scheme: kerberos, basic, 
or none. When left empty it is inferred (kerberos if the password/keytab points 
to a keytab file, basic if user and password are set, otherwise 
none).</description>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.kerberos.principal</name>
+               <value></value>
+               <description>Kerberos principal used when 
authentication.type=kerberos. Falls back to ranger.audit.opensearch.user when 
unset.</description>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.kerberos.keytab</name>
+               <value></value>
+               <description>Path to the Kerberos keytab used when 
authentication.type=kerberos. Falls back to ranger.audit.opensearch.password 
when unset.</description>
+       </property>
+       <property>
+               <name>ranger.audit.opensearch.bootstrap.enabled</name>
+               <value>true</value>
+       </property>
        <property>
                <name>ranger.audit.amazon_cloudwatch.region</name>
                <value>us-east-2</value>
@@ -463,4 +506,8 @@
                <name>xasecure.audit.jaas.Client.option.principal</name>
                <value></value>
        </property>
+    <property>
+        
<name>ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition</name>
+        <value>false</value>
+    </property>
 </configuration>
diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js 
b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js
index 9182d427e..382305c7f 100644
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js
@@ -59,6 +59,7 @@ import folderIcon from "Images/folder-grey.png";
 export const LoginUser = (role) => {
   const userProfile = getUserProfile();
   const roles = userProfile?.userRoleList;
+
   if (!roles || roles.length === 0) {
     return false;
   }
@@ -1083,7 +1084,7 @@ export const InfoIcon = (props) => {
 };
 
 /* Edit Permission Module Infinite Scroll */
-export const CustomInfinteScroll = (props) => {
+export const CustomInfiniteScroll = (props) => {
   const { data, removeUsrGrp, scrollableDiv } = props;
   const [count, setCount] = useState({
     startIndex: 0,
@@ -1494,7 +1495,7 @@ export const requestDataTitle = (serviceType) => {
 //Policy condition evaluation
 
 export const policyConditionUpdatedJSON = (policyCond) => {
-  let newPolicyConditionJSON = [...policyCond];
+  let newPolicyConditionJSON = [...(Array.isArray(policyCond) ? policyCond : 
[])];
   newPolicyConditionJSON.filter(function (key) {
     if (!key?.uiHint || key?.uiHint == "") {
       if (
@@ -1545,8 +1546,9 @@ export const getServiceDefIcon = (serviceDefName) => {
   let imageStyling;
 
   try {
-    const serviceDefIcon =
-      require(`../images/serviceDefIcons/${serviceDefName}/icon.svg`).default;
+    const serviceDefIcon = require(
+      `../images/serviceDefIcons/${serviceDefName}/icon.svg`
+    ).default;
     imagePath = serviceDefIcon;
     imageStyling = { height: "27px", width: "27px" };
   } catch (error) {
@@ -1592,3 +1594,46 @@ export const getPolicyConditionDisplayLbl = (lbl) => {
     ? policyConditionDisplayLabel[lbl]
     : lbl;
 };
+
+// Common function to safeguard JSON parsing
+export const safeJsonParse = (value, fallback) => {
+  try {
+    return JSON.parse(value);
+  } catch (error) {
+    console.error("JSON Parsing Error:", error);
+    return fallback;
+  }
+};
+
+//CommonFunction to get display label for policy permission item
+export const getPolicyPermissionItemDisplayLbl = (
+  serviceDef,
+  policyType,
+  accessType
+) => {
+  let accessTypeDefVal = [];
+  const accesTypeKeys = map(accessType, (item) => {
+    return isObject(item) ? item.type : item;
+  });
+  if (RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value == policyType) {
+    accessTypeDefVal = serviceDef.dataMaskDef.accessTypes;
+  } else if (
+    RangerPolicyType.RANGER_ROW_FILTER_POLICY_TYPE.value == policyType
+  ) {
+    accessTypeDefVal = serviceDef.rowFilterDef.accessTypes;
+  } else {
+    accessTypeDefVal = serviceDef.accessTypes;
+  }
+  if (serviceDef.name == "tag") {
+    return map(sortBy(accesTypeKeys), (val) => ({ label: val }));
+  } else {
+    if (accessType.length == accessTypeDefVal.length) {
+      return sortBy(accessTypeDefVal, "label");
+    } else {
+      const accessTypeDisplayLblObj = filter(accessTypeDefVal, (item) =>
+        includes(accesTypeKeys, { type: item.name })
+      );
+      return sortBy(accessTypeDisplayLblObj, "label");
+    }
+  }
+};
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
index 139592264..5957f7658 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
@@ -53,7 +53,8 @@ import {
   BlockUi,
   Loader,
   scrollToError,
-  selectInputCustomStyles
+  selectInputCustomStyles,
+  trimInputValue
 } from "Components/CommonComponents";
 import { fetchApi } from "Utils/fetchAPI";
 import { RangerPolicyType, getEnumElementByValue } from "Utils/XAEnums";
@@ -71,7 +72,8 @@ import {
   getResourcesDefVal,
   getAllTimeZoneList,
   policyConditionUpdatedJSON,
-  policyInfo
+  policyInfo,
+  getPolicyConditionDisplayLbl
 } from "Utils/XAUtils";
 import { useAccordionButton } from "react-bootstrap/AccordionButton";
 import AccordionContext from "react-bootstrap/AccordionContext";
@@ -292,7 +294,7 @@ export default function AddUpdatePolicyForm() {
   const fetchPolicyLabel = async (inputValue) => {
     let params = {};
     if (inputValue) {
-      params["policyLabel"] = inputValue || "";
+      params["policyLabel"] = inputValue.trim() || "";
     }
     const policyLabelResp = await fetchApi({
       url: "plugins/policyLabels",
@@ -300,8 +302,8 @@ export default function AddUpdatePolicyForm() {
     });
 
     return policyLabelResp.data.map((name) => ({
-      label: name,
-      value: name
+      label: name.trim(),
+      value: name.trim()
     }));
   };
 
@@ -392,12 +394,12 @@ export default function AddUpdatePolicyForm() {
       data.policyName = policyData?.name;
       data.isEnabled = policyData?.isEnabled;
       data.policyPriority = policyData?.policyPriority == 0 ? false : true;
-      data.description = policyData?.description;
+      data.description = policyData?.description?.trim();
       data.isAuditEnabled = policyData?.isAuditEnabled;
       data.policyLabel =
         policyData &&
         policyData?.policyLabels?.map((val) => {
-          return { label: val, value: val };
+          return { label: val?.trim(), value: val?.trim() };
         });
       if (policyData?.resources) {
         if (!isMultiResources) {
@@ -406,7 +408,7 @@ export default function AddUpdatePolicyForm() {
             let setResources = find(serviceCompResourcesDetails, ["name", 
key]);
             data[`resourceName-${setResources?.level}`] = setResources;
             data[`value-${setResources?.level}`] = value.values.map((m) => {
-              return { label: m, value: m };
+              return { label: m?.trim(), value: m?.trim() };
             });
             if (setResources?.excludesSupported) {
               data[`isExcludesSupport-${setResources?.level}`] =
@@ -451,7 +453,7 @@ export default function AddUpdatePolicyForm() {
                   setResources;
                 additionalResourcesObj[`value-${setResources?.level}`] =
                   value.values.map((m) => {
-                    return { label: m, value: m };
+                    return { label: m?.trim(), value: m?.trim() };
                   });
                 if (setResources?.excludesSupported) {
                   additionalResourcesObj[
@@ -510,7 +512,7 @@ export default function AddUpdatePolicyForm() {
         data.conditions = {};
         for (let val of policyData.conditions) {
           let conditionObj = find(
-            policyConditionUpdatedJSON(serviceCompData?.policyConditions),
+            policyConditionUpdatedJSON(serviceCompData?.policyConditions || 
[]),
             function (m) {
               if (m.name == val.type) {
                 return m;
@@ -518,11 +520,13 @@ export default function AddUpdatePolicyForm() {
             }
           );
 
-          if (!isEmpty(conditionObj.uiHint)) {
+          // Ignore policy conditions that are not defined in the service-def.
+          // Without this guard, form initialization can fail and leave the 
page on the loader.
+          if (conditionObj?.uiHint && !isEmpty(conditionObj.uiHint)) {
             data.conditions[val?.type] = JSON.parse(conditionObj.uiHint)
               .isMultiValue
               ? val?.values
-              : val?.values.toString();
+              : val?.values.toString().trim();
           }
         }
       }
@@ -592,7 +596,7 @@ export default function AddUpdatePolicyForm() {
                 type: conditionKey,
                 values: isArray(conditionValue)
                   ? conditionValue.map((m) => {
-                      return m.value;
+                      return m;
                     })
                   : [conditionValue]
               });
@@ -700,7 +704,7 @@ export default function AddUpdatePolicyForm() {
 
         for (let data of val.conditions) {
           let conditionObj = find(
-            policyConditionUpdatedJSON(serviceData?.policyConditions),
+            policyConditionUpdatedJSON(serviceData?.policyConditions || []),
             function (m) {
               if (m.name == data.type) {
                 return m;
@@ -708,13 +712,13 @@ export default function AddUpdatePolicyForm() {
             }
           );
 
-          if (!isEmpty(conditionObj.uiHint)) {
+          // Ignore policy conditions that are not defined in the service-def.
+          // Without this guard, form initialization can fail and leave the 
page on the loader.
+          if (conditionObj?.uiHint && !isEmpty(conditionObj.uiHint)) {
             obj.conditions[data?.type] = JSON.parse(conditionObj.uiHint)
               .isMultiValue
-              ? data?.values.map((m) => {
-                  return { value: m, label: m };
-                })
-              : data?.values.toString();
+              ? data?.values
+              : data?.values.toString().trim();
           }
         }
       }
@@ -890,7 +894,7 @@ export default function AddUpdatePolicyForm() {
       data["conditions"] = [];
     }
 
-    /* For create zoen policy*/
+    /* For create zone policy*/
     if (localStorage.getItem("zoneDetails") != null) {
       data["zoneName"] = JSON.parse(localStorage.getItem("zoneDetails")).label;
     }
@@ -1304,6 +1308,7 @@ export default function AddUpdatePolicyForm() {
                                           : "form-control"
                                       }
                                       data-cy="policyName"
+                                      onBlur={(e) => trimInputValue(e, input)}
                                     />
                                     <InfoIcon
                                       css="input-box-info-icon"
@@ -1394,6 +1399,25 @@ export default function AddUpdatePolicyForm() {
                                   }}
                                   defaultOptions={defaultPolicyLabelOptions}
                                   styles={selectInputCustomStyles}
+                                  // Add this prop to trim the visual "Create" 
label
+                                  formatCreateLabel={(inputValue) =>
+                                    `Create "${inputValue.trim()}"`
+                                  }
+                                  // Add this prop to trim the value when a 
tag is created
+                                  onCreateOption={(inputValue) => {
+                                    const policyLabelVal = inputValue.trim();
+                                    if (policyLabelVal) {
+                                      input.onChange([
+                                        ...input.value,
+                                        {
+                                          label: policyLabelVal,
+                                          value: policyLabelVal
+                                        }
+                                      ]);
+                                    }
+                                  }}
+                                  tabSelectsValue={false}
+                                  placeholder="Add Policy Labels"
                                 />
                               </Col>
                             </FormB.Group>
@@ -1426,6 +1450,7 @@ export default function AddUpdatePolicyForm() {
                                   as="textarea"
                                   rows={3}
                                   data-cy="description"
+                                  onBlur={(e) => trimInputValue(e, input)}
                                 />
                               </Col>
                             </FormB.Group>
@@ -1488,6 +1513,7 @@ export default function AddUpdatePolicyForm() {
                                             handleCloseModal={
                                               policyConditionState
                                             }
+                                            modalHeader="Policy Conditions"
                                           />
                                         )}
                                       />
@@ -1529,23 +1555,25 @@ export default function AddUpdatePolicyForm() {
                                           return (
                                             <tr key={keyName}>
                                               <td>
-                                                <center>
-                                                  {conditionObj.label}
-                                                </center>
+                                                <span>
+                                                  
{getPolicyConditionDisplayLbl(
+                                                    conditionObj.label
+                                                  )}
+                                                </span>
                                               </td>
                                               <td>
                                                 {isArray(
                                                   values?.conditions[keyName]
                                                 ) ? (
-                                                  <center>
+                                                  <span className="line-clamp 
line-clamp-3">
                                                     {values.conditions[
                                                       keyName
                                                     ].join(", ")}
-                                                  </center>
+                                                  </span>
                                                 ) : (
-                                                  <center>
+                                                  <span className="line-clamp 
line-clamp-3">
                                                     
{values.conditions[keyName]}
-                                                  </center>
+                                                  </span>
                                                 )}
                                               </td>
                                             </tr>
@@ -1556,7 +1584,9 @@ export default function AddUpdatePolicyForm() {
                                   ) : (
                                     <tr>
                                       <td>
-                                        <center> No Conditions </center>
+                                        <center className="text-muted">
+                                          No Conditions
+                                        </center>
                                       </td>
                                     </tr>
                                   )}
@@ -1571,7 +1601,11 @@ export default function AddUpdatePolicyForm() {
                     {isMultiResources && (
                       <>
                         <fieldset>
-                          <p className="formHeader">Resources :</p>
+                          <p className="formHeader">
+                            {serviceCompDetails.name == "tag"
+                              ? "Tags :"
+                              : "Resources :"}
+                          </p>
                         </fieldset>
                         <>
                           <FieldArray name="additionalResources">
@@ -1638,7 +1672,7 @@ export default function AddUpdatePolicyForm() {
                           <Accordion defaultActiveKey="0">
                             <>
                               <p className="formHeader">
-                                Allow Conditions:{" "}
+                                Allow Rules:{" "}
                                 <CustomToggle eventKey="0"></CustomToggle>
                               </p>
                               <Accordion.Collapse eventKey="0">
@@ -1666,7 +1700,7 @@ export default function AddUpdatePolicyForm() {
                                       <fieldset>
                                         <p className="wrap-header 
search-header">
                                           <i className="fa-fw fa 
fa-exclamation-triangle fa-fw fa fa-1 text-color-red"></i>
-                                          Exclude from Allow Conditions:
+                                          Exclude from Allow Rules:
                                         </p>
                                       </fieldset>
                                       <div className="wrap">
@@ -1732,7 +1766,7 @@ export default function AddUpdatePolicyForm() {
                                 <Accordion defaultActiveKey="0">
                                   <>
                                     <p className="formHeader">
-                                      Deny Conditions:
+                                      Deny Rules:
                                       <CustomToggle 
eventKey="0"></CustomToggle>
                                     </p>
                                     <Accordion.Collapse eventKey="0">
@@ -1758,7 +1792,7 @@ export default function AddUpdatePolicyForm() {
                                         <fieldset>
                                           <p className="wrap-header 
search-header">
                                             <i className="fa-fw fa 
fa-exclamation-triangle fa-fw fa fa-1 text-color-red"></i>
-                                            Exclude from Deny Conditions:
+                                            Exclude from Deny Rules:
                                           </p>
                                         </fieldset>
                                         <div className="wrap">
@@ -1793,7 +1827,7 @@ export default function AddUpdatePolicyForm() {
                         <Accordion defaultActiveKey="0">
                           <>
                             <p className="formHeader">
-                              Mask Conditions:
+                              Mask Rules:
                               <CustomToggle eventKey="0"></CustomToggle>
                             </p>
                             <Accordion.Collapse eventKey="0">
@@ -1825,7 +1859,7 @@ export default function AddUpdatePolicyForm() {
                           <Accordion defaultActiveKey="0">
                             <>
                               <p className="wrap-header search-header">
-                                Row Filter Conditions:
+                                Row Filter Rules:
                                 <CustomToggle eventKey="0"></CustomToggle>
                               </p>
                               <Accordion.Collapse eventKey="0">
diff --git 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
index 2d8620d0d..b949f3acc 100644
--- 
a/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
+++ 
b/security-admin/src/test/java/org/apache/ranger/service/TestRangerServiceDefService.java
@@ -19,9 +19,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.authorization.hadoop.config.RangerAdminConfig;
 import org.apache.ranger.common.ContextUtil;
 import org.apache.ranger.common.JSONUtil;
 import org.apache.ranger.common.PropertiesUtil;
@@ -38,6 +41,7 @@
 import 
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef;
 import org.apache.ranger.plugin.model.RangerServiceDef.RangerServiceConfigDef;
+import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
 import org.apache.ranger.plugin.util.ServiceDefUtil;
 import org.apache.ranger.security.context.RangerContextHolder;
 import org.apache.ranger.security.context.RangerSecurityContext;
@@ -53,13 +57,15 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import static 
org.apache.ranger.service.RangerServiceDefService.PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE;
 import static 
org.apache.ranger.service.RangerServiceDefService.PROP_ENABLE_IMPLICIT_CONDITION_EXPRESSION;
 
 @RunWith(MockitoJUnitRunner.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class TestRangerServiceDefService {
 
-       private static Long Id = 8L;
+       private static final Long   Id = 8L;
+       private static final String 
OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION = 
RangerServiceDefService.OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION;
 
        @InjectMocks
        RangerServiceDefService serviceDefService = new 
RangerServiceDefService();
@@ -803,4 +809,111 @@ public void testImplicitConditionExpressionDisabled() {
                        
PropertiesUtil.getPropertiesMap().remove(PROP_ENABLE_IMPLICIT_CONDITION_EXPRESSION);
                }
        }
+
+       @Test
+       public void testOzoneActionPolicyOptionDisabled() {
+               
RangerAdminConfig.getInstance().set(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE,
 Boolean.FALSE.toString());
+
+               try {
+                       RangerServiceDef serviceDef = 
buildOzoneServiceDefWithActionMatches();
+
+                       
serviceDefService.applyActionMatcherInPoliciesConditionHiddenOption(serviceDef);
+
+                       Assert.assertEquals("false", 
serviceDef.getOptions().get(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION));
+                       
Assert.assertFalse(hasActionMatchesCondition(serviceDef));
+               } finally {
+                       
RangerAdminConfig.getInstance().unset(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE);
+               }
+       }
+
+       @Test
+       public void testOzoneActionPolicyOptionEnabled() {
+               
RangerAdminConfig.getInstance().set(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE,
 Boolean.TRUE.toString());
+
+               try {
+                       RangerServiceDef serviceDef = 
buildOzoneServiceDefWithActionMatches();
+
+                       
serviceDefService.applyActionMatcherInPoliciesConditionHiddenOption(serviceDef);
+
+                       Assert.assertEquals("true", 
serviceDef.getOptions().get(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION));
+                       
Assert.assertTrue(hasActionMatchesCondition(serviceDef));
+               } finally {
+                       
RangerAdminConfig.getInstance().unset(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE);
+               }
+       }
+
+       @Test
+       public void testOzoneActionPolicyConfigTrueOverridesStoredFalseOption() 
{
+               
RangerAdminConfig.getInstance().set(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE,
 Boolean.TRUE.toString());
+
+               try {
+                       RangerServiceDef serviceDef = 
buildOzoneServiceDefWithActionMatches();
+                       
serviceDef.getOptions().put(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION, 
Boolean.FALSE.toString());
+
+                       
serviceDefService.applyActionMatcherInPoliciesConditionHiddenOption(serviceDef);
+
+                       Assert.assertEquals("true", 
serviceDef.getOptions().get(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION));
+                       
Assert.assertTrue(hasActionMatchesCondition(serviceDef));
+               } finally {
+                       
RangerAdminConfig.getInstance().unset(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE);
+               }
+       }
+
+       @Test
+       public void 
testOzoneActionPolicyConfigTrueRestoresMissingActionMatchesCondition() {
+               
RangerAdminConfig.getInstance().set(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE,
 Boolean.TRUE.toString());
+
+               try {
+                       RangerPolicyConditionDef ipRange = new 
RangerPolicyConditionDef();
+                       ipRange.setName("ip-range");
+
+                       List<RangerPolicyConditionDef> policyConditions = new 
ArrayList<>();
+                       policyConditions.add(ipRange);
+
+                       RangerServiceDef serviceDef = new RangerServiceDef();
+                       
serviceDef.setName(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME);
+                       serviceDef.setOptions(new HashMap<>());
+                       serviceDef.setPolicyConditions(policyConditions);
+
+                       
serviceDefService.applyActionMatcherInPoliciesConditionHiddenOption(serviceDef);
+
+                       Assert.assertEquals("true", 
serviceDef.getOptions().get(OPTION_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION));
+                       
Assert.assertTrue(hasActionMatchesCondition(serviceDef));
+               } finally {
+                       
RangerAdminConfig.getInstance().unset(PROP_ENABLE_ACTION_MATCHER_IN_POLICIES_CONDITION_FOR_OZONE);
+               }
+       }
+
+       private boolean hasActionMatchesCondition(RangerServiceDef serviceDef) {
+               if (serviceDef.getPolicyConditions() == null) {
+                       return false;
+               }
+
+               for (RangerPolicyConditionDef conditionDef : 
serviceDef.getPolicyConditions()) {
+                       if (StringUtils.equals(conditionDef.getName(), 
"action-matches")) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       private RangerServiceDef buildOzoneServiceDefWithActionMatches() {
+               RangerPolicyConditionDef ipRange = new 
RangerPolicyConditionDef();
+               ipRange.setName("ip-range");
+
+               RangerPolicyConditionDef actionMatches = new 
RangerPolicyConditionDef();
+               actionMatches.setName("action-matches");
+
+               List<RangerPolicyConditionDef> policyConditions = new 
ArrayList<>();
+               policyConditions.add(ipRange);
+               policyConditions.add(actionMatches);
+
+               RangerServiceDef serviceDef = new RangerServiceDef();
+               
serviceDef.setName(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME);
+               serviceDef.setOptions(new HashMap<>());
+               serviceDef.setPolicyConditions(policyConditions);
+
+               return serviceDef;
+       }
 }


Reply via email to