[
https://issues.apache.org/jira/browse/RANGER-5655?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Ramachandran Krishnan updated RANGER-5655:
------------------------------------------
Description:
Ranger plugins (HDFS, Hive, Trino, etc.) send audit events to
{*}audit-ingestor{*}, which writes them to Kafka topic {{{}ranger_audits{}}}.
To isolate hot plugins, ingestor can route each plugin to dedicated Kafka
partitions; unknown plugins share a *buffer* pool.
*Today (static mode):* Plugin → partition mapping is computed once at startup
from XML. Changing assignments requires config edits, topic partition growth,
and {*}ingestor restart{*}. Early-plugin changes can *reshuffle* later plugins
because allocation uses contiguous ranges.
Plugins POST audits to {{{}POST
/api/audit/access?serviceName=<repo>&appId=<agent>{}}}. After
Kerberos/JWT/basic *authentication* (401 on failure), ingestor performs
{*}authorization{*}: the authenticated short username must appear in
{{{}ranger.audit.ingestor.service.<repo>.allowed.users{}}}.
Today that map is loaded *once at JVM startup* from
{{{}ranger-audit-ingestor-site.xml{}}}. Adding a new Ranger service repo (e.g.
{{{}dev_trino{}}}) requires XML edits and *ingestor restart* — even when
partition routing is already dynamic via the partition-plan topic.
*Proposed (dynamic mode):*
Ranger plugins POST audits to the *Audit Ingestor* ({{{}POST
/api/audit/access{}}}). This feature adds:
# *Dynamic Kafka partition routing* — layout stored in Admin
({{{}x_ranger_global_state{}}}), synced by a background poller.
# *Dynamic allow-list* — who may POST audits per service comes from
{{policy.download.auth.users}} in the partition plan.
# *SPIFFE workload identity* at two boundaries:
** *Plugin → Ingestor* — {{X-Spiffe-Id}} on audit POSTs
({{{}AuditHeaderAuthFilter{}}}).
** *Ingestor → Admin* — {{X-Spiffe-Id}} as {{rangerauditserver}} for plan
download (no password).
Same pattern for Hive, HDFS, Ozone, Knox, Kafka, HBase, KMS, Trino, Atlas, etc.
was:
Implement a dynamic unified ingestor registry for Ranger audit-ingestor so
operators can change Kafka partition routing and per-repo service allowlists at
runtime — without restarting ingestor pods.
The registry is stored in a Kafka compacted topic
({{ranger_audit_partition_plan}}) and managed via REST. All ingestor replicas
converge on the same versioned plan through {{PartitionPlanWatcher}};
{{AuditPartitioner}} routes audit records on the hot path from in-memory state
only.
*Feature flag (default off):*
{{ranger.audit.ingestor.kafka.partition.plan.dynamic.enabled=false}}
*Pull request:* [GitHub PR #1032|https://github.com/apache/ranger/pull/1032]
----
h2. Problem
Today (static mode), audit-ingestor loads two kinds of configuration from XML
at startup only:
|| Job || Question || Static behavior ||
| Service allowlist | May this Kerberos principal POST audits for repo R? |
{{ranger.audit.ingestor.service.<repo>.allowed.users}} in site XML |
| Partition routing | After accept, which {{ranger_audits}} partition? |
{{kafka.configured.plugins}} + per-plugin overrides in site XML |
Changing either requires editing XML and restarting every ingestor replica.
Contiguous-range static allocation can also reshuffle later plugins when an
early plugin's partition count changes.
*Goals:*
* Onboard new plugins/repos and scale hot plugins without ingestor restart
* Append-only partition growth (no reshuffle of existing plugin assignments)
* One shared source of truth across all ingestor pods
* No new infra (no Postgres / ZooKeeper for the registry)
----
h2. Solution
Introduce a unified partition plan document (versioned JSON) in Kafka topic
{{ranger_audit_partition_plan}} (1 partition, compacted). One document holds:
* {{plugins}} — dedicated partition IDs per plugin id (Kafka record key / agent
id)
* {{buffer}} — partition pool for not-yet-promoted plugins (sticky hash)
* {{services}} — per-repo {{allowedUsers}} for {{POST /api/audit/access}}
(optional {{pluginId}} for ownership)
* {{topicPartitionCount}} — must match live {{ranger_audits}} partition count
* {{version}} — optimistic locking for REST mutations
{code}
Control plane: GET/PATCH /api/audit/partition-plan → Kafka registry topic →
PartitionPlanWatcher
Data plane: POST /api/audit/access (Kerberos) → allowlist check
→ optional auto-onboard
→ AuditPartitioner → ranger_audits
{code}
Solr/HDFS dispatchers are unchanged; they consume all partitions of
{{ranger_audits}}.
h3. REST API (v2 — two public endpoints)
Plan admin endpoints are *unauthenticated* (same as health/status):
{{permitAll()}} in Spring Security and bypass in
{{AuditDelegationTokenFilter}}. Audit ingestion remains Kerberos-protected on
{{POST /api/audit/access}}.
All mutations use optimistic locking via {{expectedVersion}} (from GET). Stale
writes return *409* with the current plan body.
|| Method || Endpoint || Purpose ||
| GET | {{/api/audit/partition-plan}} | Read current plan (503 when dynamic
mode off) |
| PATCH | {{/api/audit/partition-plan}} | Partial update via
{{PartitionPlanReplacement}} JSON body |
*PATCH body semantics* ({{PartitionPlanReplacement}}):
* {{expectedVersion}} — required; stale → 409
* {{plugins}} / {{services}} — merge *new keys only* (promote/onboard entries
not already in plan)
* {{pluginScales}} — map of {{pluginId → additionalPartitions}}; append-only
tail growth
* {{buffer}} / {{topicPartitionCount}} — optional; omitted fields inherited
from current plan
*Removed (superseded by PATCH + auto-onboard):*
* {{POST /api/audit/partition-plan/plugins}}
* {{PATCH /api/audit/partition-plan/plugins/\{pluginId\}}}
h3. Auto-onboard on audit access
When dynamic mode is enabled and a Kerberos-authenticated {{POST
/api/audit/access}} includes {{appId}} for a plugin not yet in the plan:
# Allowlist check passes for {{serviceName}} + authenticated principal
# Ingestor promotes the plugin from buffer (default partition count from
config) and upserts {{services[serviceName]}} in one plan version bump
# Audit batch is then produced using the updated in-memory plan
No separate onboard REST call is required for first-time plugin discovery.
Operators can still pre-promote plugins and allowlists via PATCH.
h3. Allowlist updates
{{services}} entries in the registry drive {{POST /access}} authorization. In
dynamic mode, {{AuthToLocalRuleComposer}} recomposes {{auth_to_local}} from the
union of all {{services[*].allowedUsers}} whenever the plan changes. Removing
or tightening a repo's allowlist causes *403* on subsequent access attempts
without pod restart.
----
h2. Key deliverables (PR #1032)
* Kafka registry — {{KafkaPartitionPlanRegistry}}, bootstrap from XML on
greenfield, brownfield pre-seed support
* REST API — {{GET}} / {{PATCH}} {{/api/audit/partition-plan}} with
{{PartitionPlanReplacement}} and {{expectedVersion}} (409 on stale)
* Auto-onboard — {{PartitionPlanService.ensurePluginOnboarded()}} on {{POST
/api/audit/access}} when {{appId}} is unknown
* Dynamic partitioner — {{AuditPartitioner}} reads {{PartitionPlanHolder}};
round-robin for promoted plugins; buffer sticky hash for unknown plugins;
post-scale routing uses {{max(cluster, plan)}} when metadata lags
* Unified allowlist — {{services}} map in same registry; per-repo POST checks
use each repo's own allowlist
* Topic grow — grow {{ranger_audits}} before registry write when
promoting/scaling
* Unit tests — partition-plan, allowlist, partitioner, and bootstrap coverage
in {{audit-common}} + {{audit-ingestor}}
*Out of scope for PR #1032 (follow-up / lab validation):* Docker E2E harness,
ops runbooks, brownfield migration guide (tracked separately in dev-support /
audit-server docs).
----
h2. Configuration
{code:xml}
<property>
<name>ranger.audit.ingestor.kafka.partition.plan.dynamic.enabled</name>
<value>true</value>
</property>
<property>
<name>ranger.audit.ingestor.kafka.partition.plan.topic</name>
<value>ranger_audit_partition_plan</value>
</property>
{code}
When dynamic mode is on and the registry topic is empty, the first ingestor pod
bootstraps an initial plan from existing XML properties
({{kafka.configured.plugins}}, buffer/per-plugin counts, service allowlists).
h3. Example plan JSON
{code:json}
{
"topic": "ranger_audits",
"version": 12,
"topicPartitionCount": 48,
"plugins": {
"hdfs": { "partitions": [0, 1, 2, 3, 4, 5] },
"hiveServer2": { "partitions": [6, 7, 8, 9, 10, 11] }
},
"buffer": { "partitions": [12, 13, "..."] },
"services": {
"dev_hive": { "allowedUsers": ["hive"], "pluginId": "hiveServer2" },
"dev_ozone": { "allowedUsers": ["om", "ozone"], "pluginId": "ozone" },
"dev_hdfs": { "allowedUsers": ["hdfs", "nn"], "pluginId": "hdfs" }
}
}
{code}
h3. Example PATCH (promote + scale)
{code:json}
{
"expectedVersion": 12,
"plugins": {
"storm": { "partitions": [12, 13, 14] }
},
"services": {
"dev_storm": { "allowedUsers": ["storm"], "pluginId": "storm" }
},
"pluginScales": {
"hdfs": 2
}
}
{code}
----
h2. Testing
h3. Unit tests and quality gates
{code:bash}
mvn verify -pl audit-server/audit-common,audit-server/audit-ingestor
-Drat.skip=true
{code}
|| Gate || Result ||
| Unit tests | 104 passed (audit-ingestor module) |
| Checkstyle | Pass |
| PMD / SpotBugs | Pass (existing audit-ingestor findings unchanged) |
h3. Validated scenarios (manual + lab)
|| # || Scenario || Pass criteria ||
| 1 | Static mode (default off) | {{GET /partition-plan}} → 503; health 200;
plugin audits unchanged |
| 2 | Dynamic bootstrap | Plan topic created; watcher active; {{GET
/partition-plan}} → 200 with v≥1 |
| 3 | Public plan REST | {{GET}} / {{PATCH}} without Kerberos succeed when
dynamic mode on |
| 4 | PATCH promote/onboard | New {{plugins}} + {{services}} keys merged in one
version bump |
| 5 | PATCH {{pluginScales}} | Tail partitions appended; {{ranger_audits}}
grown; no reshuffle |
| 6 | Optimistic locking | Stale {{expectedVersion}} → 409 with current plan
body |
| 7 | Auto-onboard on {{/access}} | Unknown {{appId}} + allowlisted principal →
plan updated, audit accepted |
| 8 | Multi-pod convergence | All replicas report same version after PATCH on
one pod |
| 9 | Routing | Kafka record partition ∈ plugin assignment from plan |
| 10 | Allowlist enforcement | Allow/deny/cross-repo → 200/403; recomposition
on plan change |
----
h2. Acceptance criteria
# With {{dynamic.enabled=false}}, behavior matches existing static XML
partitioning; {{GET /partition-plan}} returns 503
# With {{dynamic.enabled=true}}, registry topic created (1 partition,
compacted); bootstrap plan published on greenfield
# {{PATCH /api/audit/partition-plan}} promotes/scales/mutates allowlists; stale
{{expectedVersion}} returns 409
# Unknown {{appId}} on authenticated {{POST /access}} auto-onboards plugin when
allowlist permits
# All ingestor replicas converge to same plan within watcher refresh interval
# Audits for promoted plugin land only on assigned {{ranger_audits}} partitions
# Service allowlists in registry drive {{/access}} authorization; unauthorized
principal returns 403
# Post-scale routing works when Kafka metadata lags plan ({{AuditPartitioner}}
bound logic)
> Ranger Admin-Managed Kafka Audit Partition Plan & SPIFFE Auth
> -------------------------------------------------------------
>
> Key: RANGER-5655
> URL: https://issues.apache.org/jira/browse/RANGER-5655
> Project: Ranger
> Issue Type: Improvement
> Components: Ranger
> Reporter: Ramachandran Krishnan
> Assignee: Ramachandran Krishnan
> Priority: Major
> Fix For: 3.0.0
>
> Time Spent: 0.5h
> Remaining Estimate: 0h
>
> Ranger plugins (HDFS, Hive, Trino, etc.) send audit events to
> {*}audit-ingestor{*}, which writes them to Kafka topic {{{}ranger_audits{}}}.
> To isolate hot plugins, ingestor can route each plugin to dedicated Kafka
> partitions; unknown plugins share a *buffer* pool.
> *Today (static mode):* Plugin → partition mapping is computed once at startup
> from XML. Changing assignments requires config edits, topic partition growth,
> and {*}ingestor restart{*}. Early-plugin changes can *reshuffle* later
> plugins because allocation uses contiguous ranges.
> Plugins POST audits to {{{}POST
> /api/audit/access?serviceName=<repo>&appId=<agent>{}}}. After
> Kerberos/JWT/basic *authentication* (401 on failure), ingestor performs
> {*}authorization{*}: the authenticated short username must appear in
> {{{}ranger.audit.ingestor.service.<repo>.allowed.users{}}}.
> Today that map is loaded *once at JVM startup* from
> {{{}ranger-audit-ingestor-site.xml{}}}. Adding a new Ranger service repo
> (e.g. {{{}dev_trino{}}}) requires XML edits and *ingestor restart* — even
> when partition routing is already dynamic via the partition-plan topic.
> *Proposed (dynamic mode):*
> Ranger plugins POST audits to the *Audit Ingestor* ({{{}POST
> /api/audit/access{}}}). This feature adds:
> # *Dynamic Kafka partition routing* — layout stored in Admin
> ({{{}x_ranger_global_state{}}}), synced by a background poller.
> # *Dynamic allow-list* — who may POST audits per service comes from
> {{policy.download.auth.users}} in the partition plan.
> # *SPIFFE workload identity* at two boundaries:
> ** *Plugin → Ingestor* — {{X-Spiffe-Id}} on audit POSTs
> ({{{}AuditHeaderAuthFilter{}}}).
> ** *Ingestor → Admin* — {{X-Spiffe-Id}} as {{rangerauditserver}} for plan
> download (no password).
> Same pattern for Hive, HDFS, Ozone, Knox, Kafka, HBase, KMS, Trino, Atlas,
> etc.
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)