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; + } }
