This is an automated email from the ASF dual-hosted git repository. ramackri pushed a commit to branch RANGER-5712 in repository https://gitbox.apache.org/repos/asf/ranger.git
commit 9203c3c8e599351a28e70ad996f6b56824cda1c0 Author: ramk <[email protected]> AuthorDate: Thu Jul 30 15:02:53 2026 +0530 RANGER-5712: Add Kerberos audit client config for Ozone docker E2E Configure OM keytab JAAS for audit-ingestor Kerberos outbound auth and disable SPIFFE audit headers so static+Kerberos ingestor testing works. Add verify-ozone-pr1116-e2e.sh with hdfs policy-enforced audit trigger. --- .../ozone/ranger-ozone-plugin-install.properties | 22 +- .../scripts/ozone/verify-ozone-pr1116-e2e.sh | 569 +++++++++++++++++++++ plugin-ozone/conf/ranger-ozone-audit-changes.cfg | 2 + .../conf/ranger-ozone-security-changes.cfg | 9 +- 4 files changed, 593 insertions(+), 9 deletions(-) diff --git a/dev-support/ranger-docker/scripts/ozone/ranger-ozone-plugin-install.properties b/dev-support/ranger-docker/scripts/ozone/ranger-ozone-plugin-install.properties index e101d57d3..82494ef51 100644 --- a/dev-support/ranger-docker/scripts/ozone/ranger-ozone-plugin-install.properties +++ b/dev-support/ranger-docker/scripts/ozone/ranger-ozone-plugin-install.properties @@ -52,14 +52,14 @@ XAAUDIT.SOLR.ZOOKEEPER=NONE XAAUDIT.SOLR.FILE_SPOOL_DIR=/var/log/ozone/audit/solr/spool XAAUDIT.SOLR.USE_INMEMORY_JAAS_CFG=false -XAAUDIT.JAAS.CLIENT.LOGIN_MODULE_NAME=%EMPTY% -XAAUDIT.JAAS.CLIENT.LOGIN_MODULE_CONTROL_FLAG=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.USE_KEY_TAB=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.STORE_KEY=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.USE_TICKET_CACHE=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.SERVICE_NAME=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.KEY_TAB=%EMPTY% -XAAUDIT.JAAS.CLIENT.OPTION.PRINCIPAL=%EMPTY% +XAAUDIT.JAAS.CLIENT.LOGIN_MODULE_NAME=com.sun.security.auth.module.Krb5LoginModule +XAAUDIT.JAAS.CLIENT.LOGIN_MODULE_CONTROL_FLAG=required +XAAUDIT.JAAS.CLIENT.OPTION.USE_KEY_TAB=true +XAAUDIT.JAAS.CLIENT.OPTION.STORE_KEY=true +XAAUDIT.JAAS.CLIENT.OPTION.USE_TICKET_CACHE=true +XAAUDIT.JAAS.CLIENT.OPTION.SERVICE_NAME=om +XAAUDIT.JAAS.CLIENT.OPTION.KEY_TAB=/etc/keytabs/om.keytab +XAAUDIT.JAAS.CLIENT.OPTION.PRINCIPAL=om/[email protected] XAAUDIT.ELASTICSEARCH.ENABLE=false XAAUDIT.ELASTICSEARCH.URL=NONE @@ -91,6 +91,12 @@ XAAUDIT.AMAZON_CLOUDWATCH.LOG_STREAM_PREFIX=NONE XAAUDIT.AMAZON_CLOUDWATCH.FILE_SPOOL_DIR=NONE XAAUDIT.AMAZON_CLOUDWATCH.REGION=NONE +# Audit Server Kerberos outbound (plugin -> ingestor static+Kerberos E2E) +XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.ENABLED=false +XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.HEADER=X-Spiffe-Id +XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.VALUE=%EMPTY% +XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.FILE=%EMPTY% + SSL_KEYSTORE_FILE_PATH=/etc/hadoop/conf/ranger-plugin-keystore.jks SSL_KEYSTORE_PASSWORD=myKeyFilePassword SSL_TRUSTSTORE_FILE_PATH=/etc/hadoop/conf/ranger-plugin-truststore.jks diff --git a/dev-support/ranger-docker/scripts/ozone/verify-ozone-pr1116-e2e.sh b/dev-support/ranger-docker/scripts/ozone/verify-ozone-pr1116-e2e.sh new file mode 100755 index 000000000..177387313 --- /dev/null +++ b/dev-support/ranger-docker/scripts/ozone/verify-ozone-pr1116-e2e.sh @@ -0,0 +1,569 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Apply PR #1116 (RANGER-5712) Ozone Kerberos docker fix to a running stack, +# verify PolicyRefresher policy/role download (Kerberos secureMode), optional +# ozone audit E2E (ingestor static+Kerberos mode), then revert ozone files. +# +# Does NOT permanently modify audit-ingestor SPIFFE / admin header auth; audit +# phase switches ingestor to static+Kerberos temporarily and restores after. +# +# Usage: +# RANGER_DOCKER_STACK=/path/to/ranger-docker ./scripts/ozone/verify-ozone-pr1116-e2e.sh apply +# ./scripts/ozone/verify-ozone-pr1116-e2e.sh verify +# ./scripts/ozone/verify-ozone-pr1116-e2e.sh audit-e2e +# ./scripts/ozone/verify-ozone-pr1116-e2e.sh revert +# ./scripts/ozone/verify-ozone-pr1116-e2e.sh run + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PR_DOCKER_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" +PR_REPO="$(cd "${PR_DOCKER_DIR}/../.." && pwd)" + +RANGER_DOCKER_STACK="${RANGER_DOCKER_STACK:-/Users/ramk/rangerRelease/ranger/dev-support/ranger-docker}" +REVERT_AFTER_TEST="${REVERT_AFTER_TEST:-false}" +RUN_AUDIT_E2E="${RUN_AUDIT_E2E:-true}" +REVERT_INGESTOR_AFTER_AUDIT="${REVERT_INGESTOR_AFTER_AUDIT:-true}" +SKIP_POLICY_CREATE="${SKIP_POLICY_CREATE:-false}" +OZONE_E2E_WAIT_SEC="${OZONE_E2E_WAIT_SEC:-45}" + +STATE_DIR="${RANGER_DOCKER_STACK}/.ozone-pr1116-e2e" +BACKUP_DIR="${STATE_DIR}/backup" +STATE_FILE="${STATE_DIR}/state.env" +INGESTOR_ENV_BACKUP="${STATE_DIR}/ingestor-env.backup" + +COMPOSE=( -f docker-compose.ranger.yml -f docker-compose.ranger-ozone.yml ) +AUDIT_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 +) + +TEST_POLICY_NAME="${TEST_POLICY_NAME:-pr1116-test-policy}" +TEST_ROLE_NAME="${TEST_ROLE_NAME:-pr1116_test_role}" +TEST_VOLUME="${TEST_VOLUME:-testvol-pr1116}" +AUDIT_BUCKET="${AUDIT_BUCKET:-audit-bucket}" +AUDIT_MARKER="${AUDIT_MARKER:-pr1116-audit-marker}" + +PR_GIT_REF="${PR_GIT_REF:-RANGER-5712}" + +die() { echo "ERROR: $*" >&2; exit 1; } + +pr_file() { + local rel="$1" + local path="${PR_DOCKER_DIR}/${rel#dev-support/ranger-docker/}" + if [ -f "${path}" ]; then + echo "${path}" + return 0 + fi + git -C "${PR_REPO}" show "${PR_GIT_REF}:dev-support/ranger-docker/${rel#dev-support/ranger-docker/}" 2>/dev/null \ + || die "missing ${rel} on branch ${PR_GIT_REF}" +} + +pr_file_to() { + local rel="$1" dest="$2" + local src + src="$(pr_file "${rel}")" + if [ -f "${src}" ]; then + cp "${src}" "${dest}" + else + printf '%s' "${src}" > "${dest}" + fi +} + +usage() { + cat <<EOF +Usage: $(basename "$0") <apply|verify|audit-e2e|revert-ingestor|revert|run> + + apply Apply PR #1116 ozone docker files, recreate om/scm/datanode + verify Kerberos + PolicyRefresher policy/role download + audit-e2e Ingestor static+Kerberos, ozone audit -> kafka -> solr + revert-ingestor Restore ingestor dynamic+SPIFFE (if audit phase interrupted) + revert Restore backed-up ozone docker files + run apply + verify + audit-e2e +EOF +} + +require_stack() { + [ -d "${RANGER_DOCKER_STACK}" ] || die "RANGER_DOCKER_STACK not found: ${RANGER_DOCKER_STACK}" + [ -f "${RANGER_DOCKER_STACK}/.env" ] || die "missing ${RANGER_DOCKER_STACK}/.env" +} + +require_pr_files() { + git -C "${PR_REPO}" rev-parse --verify "${PR_GIT_REF}" >/dev/null 2>&1 || \ + die "git ref ${PR_GIT_REF} not found in ${PR_REPO}" + pr_file docker-compose.ranger-ozone.yml >/dev/null + pr_file scripts/ozone/docker-config >/dev/null + pr_file scripts/ozone/ranger-ozone-setup.sh >/dev/null +} + +save_state() { + mkdir -p "${STATE_DIR}" + cat > "${STATE_FILE}" <<EOF +BACKUP_DIR=${BACKUP_DIR} +APPLIED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ) +PR_REPO=${PR_REPO} +RANGER_DOCKER_STACK=${RANGER_DOCKER_STACK} +EOF +} + +load_state() { + [ -f "${STATE_FILE}" ] || die "no apply state at ${STATE_FILE}; run apply first" + # shellcheck source=/dev/null + source "${STATE_FILE}" +} + +backup_stack_files() { + mkdir -p "${BACKUP_DIR}/scripts/ozone" + cp "${RANGER_DOCKER_STACK}/docker-compose.ranger-ozone.yml" "${BACKUP_DIR}/" + cp "${RANGER_DOCKER_STACK}/Dockerfile.ranger-ozone" "${BACKUP_DIR}/" 2>/dev/null || true + cp "${RANGER_DOCKER_STACK}/scripts/ozone/docker-config" "${BACKUP_DIR}/scripts/ozone/" + cp "${RANGER_DOCKER_STACK}/scripts/ozone/ranger-ozone-setup.sh" "${BACKUP_DIR}/scripts/ozone/" + cp "${RANGER_DOCKER_STACK}/scripts/ozone/ranger-ozone-plugin-install.properties" "${BACKUP_DIR}/scripts/ozone/" + echo "Backed up ozone docker files to ${BACKUP_DIR}" +} + +apply_pr_files() { + echo "Applying PR #1116 ozone docker files -> ${RANGER_DOCKER_STACK}" + pr_file_to docker-compose.ranger-ozone.yml \ + "${RANGER_DOCKER_STACK}/docker-compose.ranger-ozone.yml" + pr_file_to Dockerfile.ranger-ozone \ + "${RANGER_DOCKER_STACK}/Dockerfile.ranger-ozone" 2>/dev/null || true + pr_file_to scripts/ozone/docker-config \ + "${RANGER_DOCKER_STACK}/scripts/ozone/docker-config" + pr_file_to scripts/ozone/ranger-ozone-setup.sh \ + "${RANGER_DOCKER_STACK}/scripts/ozone/ranger-ozone-setup.sh" + pr_file_to scripts/ozone/ranger-ozone-plugin-install.properties \ + "${RANGER_DOCKER_STACK}/scripts/ozone/ranger-ozone-plugin-install.properties" +} + +strip_ozone_plugin_spiffe_props() { + local sec="${RANGER_DOCKER_STACK}/dist/ranger-${OZONE_PLUGIN_VERSION:-3.0.0-SNAPSHOT}-ozone-plugin/conf/ranger-ozone-security.xml" + [ -f "${sec}" ] || return 0 + python3 - "${sec}" <<'PY' +import sys +import xml.etree.ElementTree as ET +path = sys.argv[1] +tree = ET.parse(path) +root = tree.getroot() +drop = { + 'ranger.ozone.authn.header.enabled', + 'ranger.ozone.authn.header.spiffe', + 'ranger.ozone.authn.spiffe.value', +} +for prop in list(root.findall('property')): + name = prop.find('name') + if name is not None and name.text in drop: + root.remove(prop) +tree.write(path, encoding='unicode', xml_declaration=False) +print('[verify-ozone-pr1116] stripped SPIFFE props from ozone plugin security.xml') +PY +} + +# Ozone 2.1.x: avoid WadlAutoDiscoverable ClassCastException (not part of PR #1116). +apply_ozone_211_jersey_workaround() { + local stack="${RANGER_DOCKER_STACK}" + # shellcheck source=/dev/null + source "${stack}/.env" + local ozone_lib="${stack}/downloads/ozone-${OZONE_VERSION}/share/ozone/lib" + local disabled="${ozone_lib}/.ranger-jersey-workaround" + [ -d "${ozone_lib}" ] || return 0 + mkdir -p "${disabled}" + for jar in "${ozone_lib}"/jersey-server-*.jar; do + [ -f "${jar}" ] || continue + mv "${jar}" "${disabled}/" + echo " quarantined host $(basename "${jar}") for PolicyRefresher Jersey workaround" + done +} + +ensure_ozone_keytabs() { + local stack="${RANGER_DOCKER_STACK}" + mkdir -p "${stack}/dist/keytabs/om" "${stack}/dist/keytabs/scm" "${stack}/dist/keytabs/datanode" + for spec in "om:om.keytab:om/om.rangernw" "scm:scm.keytab:scm/scm.rangernw" "datanode:dn.keytab:dn/datanode.rangernw"; do + local dir file principal + IFS=: read -r dir file principal <<< "${spec}" + if [ ! -s "${stack}/dist/keytabs/${dir}/${file}" ]; then + echo " provisioning keytab dist/keytabs/${dir}/${file} from ranger-kdc" + docker exec ranger-kdc kadmin.local -q "addprinc -randkey ${principal}@EXAMPLE.COM" 2>/dev/null || true + docker exec ranger-kdc kadmin.local -q "xst -k /tmp/${file} ${principal}@EXAMPLE.COM" + docker cp "ranger-kdc:/tmp/${file}" "${stack}/dist/keytabs/${dir}/${file}" + chmod 600 "${stack}/dist/keytabs/${dir}/${file}" + fi + done +} + +recreate_ozone_only() { + local stack="${RANGER_DOCKER_STACK}" + cd "${stack}" + # shellcheck source=/dev/null + source .env + export OZONE_VERSION + rm -f "downloads/ozone-${OZONE_VERSION}/.setupDone" + pr_file_to scripts/ozone/ozone-plugin-docker-setup.sh \ + "${stack}/scripts/ozone/ozone-plugin-docker-setup.sh" + chmod +x "${stack}/scripts/ozone/ozone-plugin-docker-setup.sh" + ./scripts/ozone/ozone-plugin-docker-setup.sh + strip_ozone_plugin_spiffe_props + apply_ozone_211_jersey_workaround + echo "Recreating om/scm/datanode (admin + audit-ingestor untouched)..." + docker compose "${COMPOSE[@]}" up -d --no-deps --force-recreate --build om scm datanode +} + +ingestor_env_snapshot() { + docker inspect ranger-audit-ingestor \ + --format '{{range .Config.Env}}{{println .}}{{end}}' \ + | grep -E '^RANGER_AUDIT_(PARTITION_PLAN_ADMIN_ENABLED|INGESTOR_SPIFFE_ENABLED)=' \ + > "${INGESTOR_ENV_BACKUP}" 2>/dev/null || true +} + +restart_ingestor_static_kerberos() { + local stack="${RANGER_DOCKER_STACK}" + cd "${stack}" + # shellcheck source=/dev/null + source .env + ingestor_env_snapshot + echo "== Switching audit-ingestor to static partition + Kerberos inbound ==" + RANGER_AUDIT_PARTITION_PLAN_ADMIN_ENABLED=false \ + RANGER_AUDIT_INGESTOR_SPIFFE_ENABLED=false \ + KERBEROS_ENABLED=true \ + docker compose "${AUDIT_COMPOSE[@]}" up -d --no-deps --force-recreate ranger-audit-ingestor + sleep 25 + docker exec ranger-audit-ingestor python3 -c " +import xml.etree.ElementTree as ET +r=ET.parse('/opt/ranger/audit-ingestor/conf/ranger-audit-ingestor-site.xml').getroot() +for n in ['ranger.audit.ingestor.partition.plan.admin.enabled','ranger.audit.ingestor.authn.header.enabled','ranger.audit.ingestor.kerberos.enabled']: + for p in r.findall('property'): + if p.find('name') is not None and p.find('name').text==n: + print(n,'=',p.find('value').text) +" +} + +restart_ingestor_dynamic_spiffe() { + local stack="${RANGER_DOCKER_STACK}" + cd "${stack}" + # shellcheck source=/dev/null + source .env + echo "== Restoring audit-ingestor dynamic partition + SPIFFE inbound ==" + RANGER_AUDIT_PARTITION_PLAN_ADMIN_ENABLED=true \ + RANGER_AUDIT_INGESTOR_SPIFFE_ENABLED=true \ + KERBEROS_ENABLED=true \ + docker compose "${AUDIT_COMPOSE[@]}" up -d --no-deps --force-recreate ranger-audit-ingestor + sleep 25 +} + +verify_kdc_fix() { + echo "== KDC / Kerberos startup ==" + local om_logs + om_logs="$(docker logs ozone-om 2>&1 || true)" + if grep -qE 'Setting up kerberos|KDC ISSUER|krb5:8081' <<< "${om_logs}"; then + echo "FAIL: ozone-om still shows KDC krb5:8081 wait messages" + return 1 + fi + echo "PASS: no KDC krb5:8081 wait in ozone-om logs" + if docker inspect ozone-om --format '{{range .Mounts}}{{.Destination}}{{"\n"}}{{end}}' | grep -qx '/etc/keytabs'; then + echo "PASS: /etc/keytabs mounted on ozone-om" + else + echo "FAIL: /etc/keytabs not mounted on ozone-om (PR #1116 compose not applied?)" + return 1 + fi + if grep -qE 'Login successful for user om/|Ozone Manager login successful' <<< "${om_logs}"; then + echo "PASS: OM Kerberos login succeeded" + else + echo "FAIL: OM Kerberos login not found — PolicyRefresher will use secureMode=false" + grep -iE 'Login|keytab|Kerberos|UGI' <<< "${om_logs}" | tail -5 || true + return 1 + fi +} + +create_test_policy_and_role() { + [ "${SKIP_POLICY_CREATE}" = "true" ] && return 0 + echo "== Create test policy/role in Ranger Admin ==" + local policy_json role_json policy_id role_id + policy_json=$(cat <<EOF +{"isEnabled":true,"service":"dev_ozone","name":"${TEST_POLICY_NAME}","policyType":0, + "resources":{"volume":{"values":["${TEST_VOLUME}"],"isExcludes":false,"isRecursive":false}, + "bucket":{"values":["${AUDIT_BUCKET}"],"isExcludes":false,"isRecursive":false}}, + "policyItems":[{"accesses":[{"type":"read","isAllowed":true},{"type":"list","isAllowed":true}],"users":["hdfs"]}], + "serviceType":"ozone"} +EOF +) + policy_id=$(docker exec ranger curl -s -u admin:rangerR0cks! \ + -H 'Content-Type: application/json' -X POST \ + 'http://localhost:6080/service/public/v2/api/policy' \ + -d "${policy_json}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))") + role_json="{\"name\":\"${TEST_ROLE_NAME}\",\"isEnabled\":true,\"users\":[],\"groups\":[],\"roles\":[]}" + role_id=$(docker exec ranger curl -s -u admin:rangerR0cks! \ + -H 'Content-Type: application/json' -X POST \ + "http://localhost:6080/service/public/v2/api/roles?serviceName=dev_ozone" \ + -d "${role_json}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))") + echo " created policy id=${policy_id} role id=${role_id}" + echo "TEST_POLICY_ID=${policy_id}" >> "${STATE_FILE}" + echo "TEST_ROLE_ID=${role_id}" >> "${STATE_FILE}" +} + +verify_plugin_policy_pull() { + echo "== Plugin PolicyRefresher (Kerberos secure download) ==" + sleep "${OZONE_E2E_WAIT_SEC}" + local om_logs + om_logs="$(docker logs ozone-om 2>&1 || true)" + if grep -q 'WadlAutoDiscoverable cannot be cast' <<< "${om_logs}"; then + echo "FAIL: Jersey ClassCastException in PolicyRefresher (Ozone 2.1.x classpath issue)" + return 1 + fi + if grep -q 'Unauthenticated access not allowed' <<< "${om_logs}"; then + echo "FAIL: PolicyRefresher still hitting unauthenticated admin endpoints (secureMode=false?)" + grep -E 'PolicyRefresher|RangerAdminRESTClient|secureMode' <<< "${om_logs}" | tail -10 + return 1 + fi + if docker logs ozone-om 2>&1 | tail -150 | grep -qE 'secureMode=true.*Error getting policies|secureMode=true.*Error getting Roles'; then + echo "FAIL: secureMode=true but policy/role download errors remain" + return 1 + fi + local pv rv + pv=$(docker exec ozone-om python3 -c "import json; print(json.load(open('/etc/ranger/dev_ozone/policycache/ozone_dev_ozone.json')).get('policyVersion',-1))" 2>/dev/null || echo "-1") + rv=$(docker exec ozone-om python3 -c "import json; print(json.load(open('/etc/ranger/dev_ozone/policycache/ozone_dev_ozone_roles.json')).get('roleVersion',-1))" 2>/dev/null || echo "-1") + echo " policycache policyVersion=${pv} roleVersion=${rv}" + if [ "${pv}" != "-1" ] && [ "${pv}" != "?" ]; then + echo "PASS: policies downloaded (policyVersion=${pv})" + else + echo "FAIL: policyVersion still -1" + return 1 + fi + if [ "${rv}" != "-1" ]; then + echo "PASS: roles downloaded (roleVersion=${rv})" + else + echo "WARN: roleVersion=-1 (role resolution may be disabled in plugin options)" + fi + if [ "${SKIP_POLICY_CREATE}" != "true" ]; then + if docker exec ozone-om grep -q "${TEST_POLICY_NAME}" /etc/ranger/dev_ozone/policycache/ozone_dev_ozone.json 2>/dev/null; then + echo "PASS: test policy in plugin policycache" + else + echo "WARN: test policy not yet in policycache (may need another poll cycle)" + fi + fi +} + +wait_for_om_kerberos() { + echo "== Waiting for OM Kerberos login ==" + local i om_logs + for i in $(seq 1 60); do + om_logs="$(docker logs ozone-om 2>&1 || true)" + if grep -qE 'Login successful for user om/|Ozone Manager login successful' <<< "${om_logs}"; then + echo "PASS: OM ready (${i}*5s)" + return 0 + fi + if grep -q 'Waiting for SCM' <<< "${om_logs}" && [ "${i}" -lt 60 ]; then + sleep 5 + continue + fi + sleep 5 + done + echo "FAIL: OM Kerberos login timed out" + docker logs ozone-om 2>&1 | tail -15 + return 1 +} + +trigger_ozone_audit() { + echo "== Trigger policy-enforced Ozone audit (hdfs -> OM -> ingestor) ==" + local audit_vol="${TEST_VOLUME}" audit_bucket="${AUDIT_BUCKET:-audit-bucket}" + docker exec ozone-om bash -c " + set -e + export KRB5_CONFIG=/etc/krb5.conf + kinit -kt /etc/keytabs/om.keytab om/[email protected] + /opt/hadoop/bin/ozone sh volume create ${audit_vol} 2>/dev/null || true + /opt/hadoop/bin/ozone sh bucket create ${audit_vol}/${audit_bucket} 2>/dev/null || true + " || echo "WARN: om setup for audit volume failed" + + docker cp ranger-hadoop:/etc/keytabs/hdfs.keytab /tmp/ozone-audit-hdfs.keytab + docker cp /tmp/ozone-audit-hdfs.keytab ozone-om:/tmp/hdfs.keytab + docker exec ozone-om bash -c " + set -e + export KRB5_CONFIG=/etc/krb5.conf + kinit -kt /tmp/hdfs.keytab hdfs/[email protected] + /opt/hadoop/bin/ozone sh bucket info ${audit_vol}/${audit_bucket} + /opt/hadoop/bin/ozone sh key list ${audit_vol}/${audit_bucket} 2>/dev/null || true + /opt/hadoop/bin/ozone sh volume info ${audit_vol} + " || echo "WARN: hdfs ozone operation failed (check policy + OM health)" + rm -f /tmp/ozone-audit-hdfs.keytab +} + +verify_ozone_audit_pipeline() { + echo "== Ozone audit pipeline (ingestor -> kafka -> solr dispatcher -> solr) ==" + local found=0 i + for i in $(seq 1 18); do + if docker logs ranger-audit-ingestor 2>&1 | tail -200 | grep -qiE "dev_ozone|${TEST_VOLUME}|${AUDIT_MARKER}"; then + found=1 + break + fi + sleep 5 + done + if [ "${found}" = "1" ]; then + echo "PASS: audit-ingestor received dev_ozone traffic" + else + echo "WARN: no dev_ozone lines in ingestor logs yet" + fi + if docker exec ranger-kafka /opt/kafka/bin/kafka-console-consumer.sh \ + --bootstrap-server localhost:9092 \ + --topic ranger_audits \ + --from-beginning \ + --max-messages 3 \ + --timeout-ms 15000 2>/dev/null | grep -q 'dev_ozone'; then + echo "PASS: dev_ozone audit events on Kafka topic ranger_audits" + else + echo "WARN: could not read dev_ozone from Kafka (Kerberos/SASL may block console consumer)" + fi + if docker logs ranger-audit-dispatcher-solr 2>&1 | tail -100 | grep -qi 'dev_ozone'; then + echo "PASS: solr dispatcher processed dev_ozone audits" + else + echo "WARN: no dev_ozone in solr dispatcher logs yet" + fi + if docker exec ranger-solr curl -sf "http://localhost:8983/solr/ranger_audits/select?q=repo:dev_ozone&rows=1&sort=evtTime+desc&wt=json" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); n=d.get('response',{}).get('numFound',0); print(n); exit(0 if n>0 else 1)" 2>/dev/null; then + echo "PASS: dev_ozone audits indexed in Solr" + else + echo "WARN: no dev_ozone audits found in Solr yet" + fi + if docker exec ranger curl -sf -u admin:rangerR0cks! \ + "http://localhost:6080/service/plugins/policies/service/dev_ozone" >/dev/null; then + echo "PASS: Ranger Admin still reachable after audit test" + else + echo "WARN: Ranger Admin policy API check failed" + fi +} + +verify_admin_audit_untouched() { + echo "== Admin + audit-ingestor health ==" + local ai_status ranger_status + ai_status=$(docker inspect ranger-audit-ingestor --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' 2>/dev/null || echo missing) + ranger_status=$(docker inspect ranger --format '{{.State.Status}}' 2>/dev/null || echo missing) + echo " ranger: ${ranger_status}" + echo " ranger-audit-ingestor: ${ai_status}" + [ "${ranger_status}" = "running" ] || die "ranger container not running" +} + +cleanup_test_artifacts() { + # shellcheck source=/dev/null + [ -f "${STATE_FILE}" ] && source "${STATE_FILE}" || true + if [ -n "${TEST_POLICY_ID:-}" ] && [ "${TEST_POLICY_ID}" != "" ]; then + docker exec ranger curl -s -u admin:rangerR0cks! -X DELETE \ + "http://localhost:6080/service/public/v2/api/policy/${TEST_POLICY_ID}" >/dev/null || true + fi + if [ -n "${TEST_ROLE_ID:-}" ] && [ "${TEST_ROLE_ID}" != "" ]; then + docker exec ranger curl -s -u admin:rangerR0cks! -X DELETE \ + "http://localhost:6080/service/public/v2/api/roles/${TEST_ROLE_ID}" >/dev/null || true + fi +} + +cmd_apply() { + require_stack + require_pr_files + backup_stack_files + save_state + apply_pr_files + ensure_ozone_keytabs + recreate_ozone_only + wait_for_om_kerberos + verify_admin_audit_untouched + echo "Apply complete. Run: $0 verify" +} + +cmd_verify() { + require_stack + load_state + wait_for_om_kerberos || true + verify_kdc_fix + create_test_policy_and_role + verify_plugin_policy_pull + verify_admin_audit_untouched + echo "" + echo "Policy/role verify complete." +} + +cmd_audit_e2e() { + require_stack + load_state + restart_ingestor_static_kerberos + trigger_ozone_audit + verify_ozone_audit_pipeline + if [ "${REVERT_INGESTOR_AFTER_AUDIT}" = "true" ]; then + restart_ingestor_dynamic_spiffe + docker exec ranger-audit-ingestor python3 -c " +import xml.etree.ElementTree as ET +r=ET.parse('/opt/ranger/audit-ingestor/conf/ranger-audit-ingestor-site.xml').getroot() +for n in ['ranger.audit.ingestor.partition.plan.admin.enabled','ranger.audit.ingestor.authn.header.enabled']: + for p in r.findall('property'): + if p.find('name') is not None and p.find('name').text==n: + print('restored', n,'=',p.find('value').text) +" + fi + echo "" + echo "Audit E2E phase complete." +} + +cmd_revert_ingestor() { + restart_ingestor_dynamic_spiffe +} + +cmd_revert() { + require_stack + load_state + [ -d "${BACKUP_DIR}" ] || die "backup missing: ${BACKUP_DIR}" + echo "Restoring ozone files from ${BACKUP_DIR}" + cp "${BACKUP_DIR}/docker-compose.ranger-ozone.yml" "${RANGER_DOCKER_STACK}/" + [ -f "${BACKUP_DIR}/Dockerfile.ranger-ozone" ] && \ + cp "${BACKUP_DIR}/Dockerfile.ranger-ozone" "${RANGER_DOCKER_STACK}/" + cp "${BACKUP_DIR}/scripts/ozone/docker-config" "${RANGER_DOCKER_STACK}/scripts/ozone/" + cp "${BACKUP_DIR}/scripts/ozone/ranger-ozone-setup.sh" "${RANGER_DOCKER_STACK}/scripts/ozone/" + cp "${BACKUP_DIR}/scripts/ozone/ranger-ozone-plugin-install.properties" "${RANGER_DOCKER_STACK}/scripts/ozone/" + cleanup_test_artifacts + recreate_ozone_only + rm -rf "${STATE_DIR}" + verify_admin_audit_untouched + echo "Revert complete." +} + +cmd_run() { + cmd_apply + cmd_verify + if [ "${RUN_AUDIT_E2E}" = "true" ]; then + cmd_audit_e2e + fi + if [ "${REVERT_AFTER_TEST}" = "true" ]; then + cmd_revert + else + echo "" + echo "PR ozone changes left applied. Revert with: $0 revert" + fi +} + +main() { + local cmd="${1:-}" + case "${cmd}" in + apply) cmd_apply ;; + verify) cmd_verify ;; + audit-e2e) cmd_audit_e2e ;; + revert-ingestor) cmd_revert_ingestor ;; + revert) cmd_revert ;; + run) cmd_run ;; + -h|--help|help|"") usage ;; + *) die "unknown command: ${cmd}" ;; + esac +} + +main "$@" diff --git a/plugin-ozone/conf/ranger-ozone-audit-changes.cfg b/plugin-ozone/conf/ranger-ozone-audit-changes.cfg index 619f48d77..9edf9175d 100644 --- a/plugin-ozone/conf/ranger-ozone-audit-changes.cfg +++ b/plugin-ozone/conf/ranger-ozone-audit-changes.cfg @@ -87,3 +87,5 @@ xasecure.audit.destination.log4j.logger %XAAUDIT.LOG4J.DESTINATIO xasecure.audit.destination.auditserver %XAAUDIT.AUDITSERVER.ENABLE% mod create-if-not-exists xasecure.audit.destination.auditserver.url %XAAUDIT.AUDITSERVER.URL% mod create-if-not-exists xasecure.audit.destination.auditserver.batch.filespool.dir %XAAUDIT.AUDITSERVER.FILE_SPOOL_DIR% mod create-if-not-exists +# Audit ingestor SPIFFE header config prefix (optional explicit binding) +xasecure.audit.destination.auditserver.authn.header.config.prefix ranger.ozone mod create-if-not-exists diff --git a/plugin-ozone/conf/ranger-ozone-security-changes.cfg b/plugin-ozone/conf/ranger-ozone-security-changes.cfg index 30eae3ec9..41713dada 100644 --- a/plugin-ozone/conf/ranger-ozone-security-changes.cfg +++ b/plugin-ozone/conf/ranger-ozone-security-changes.cfg @@ -25,4 +25,11 @@ ranger.plugin.ozone.policy.rest.ssl.config.file %COMPONENT_INSTALL_DIR_NAME%/ ranger.plugin.ozone.policy.pollIntervalMs 30000 mod create-if-not-exists ranger.plugin.ozone.policy.cache.dir %POLICY_CACHE_FILE_PATH% mod create-if-not-exists ranger.policy.rest.client.connection.timeoutMs 120000 mod create-if-not-exists -ranger.policy.rest.client.read.timeoutMs 30000 mod create-if-not-exists \ No newline at end of file +ranger.policy.rest.client.read.timeoutMs 30000 mod create-if-not-exists +# +# Audit ingestor SPIFFE header auth (see XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.* in install.properties) +# +ranger.ozone.authn.header.enabled %XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.ENABLED% mod create-if-not-exists +ranger.ozone.authn.header.spiffe %XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.HEADER% mod create-if-not-exists +ranger.ozone.authn.spiffe.value %XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.VALUE% mod create-if-not-exists +ranger.ozone.authn.spiffe.file %XAAUDIT.AUDITSERVER.AUTHN.SPIFFE.FILE% mod create-if-not-exists \ No newline at end of file
