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

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


The following commit(s) were added to refs/heads/ranger-2.9 by this push:
     new 3c6cbd3be RANGER-5687: Backport ozone action-matcher feature flag to 
ranger-2.9 (#1094)
3c6cbd3be is described below

commit 3c6cbd3bee736dcdcca329aa3585316b541d5e67
Author: Ramachandran Krishnan <[email protected]>
AuthorDate: Wed Jul 22 11:01:02 2026 +0530

    RANGER-5687: Backport ozone action-matcher feature flag to ranger-2.9 
(#1094)
    
    * 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.
    
    * Fix PMD unused import in TestRangerServiceDefService.
    
    Remove unused java.util.Map import introduced during RANGER-5687 backport.
    
    * Remove unused java.util.Map import from TestRangerServiceDefService.
    
    Fixes PMD UnusedImports failure in security-admin-web CI.
    
    * RANGER-5687: Apply UI add-on patch for ozone action-matcher backport.
    
    Integrate Dhaval Rajpara add-on patch for PR #1094: fix policy condition 
save/load in AddUpdatePolicyForm when action-matches is toggled via the feature 
flag, and align XAUtils with ranger-2.9.
    
    ---------
    
    Co-authored-by: ramk <[email protected]>
---
 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  |   2 +-
 .../views/PolicyListing/AddUpdatePolicyForm.jsx    |  12 +-
 .../service/TestRangerServiceDefService.java       | 114 +++++-
 15 files changed, 984 insertions(+), 260 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..1fa8e2ec9 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
@@ -1494,7 +1494,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 (
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..b1a02e40f 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
@@ -510,7 +510,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,7 +518,9 @@ 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
@@ -700,7 +702,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,7 +710,9 @@ 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) => {
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..d09f49d51 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,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 
 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 +40,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 +56,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 +808,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