Skip to content

[v25.3.x] [CORE-15822] security/audit: make audit initialization controller-leader independent - #31587

Closed
vbotbuildovich wants to merge 4 commits into
redpanda-data:v25.3.xfrom
vbotbuildovich:ai-backport-pr-31411-v25.3.x-1786713552
Closed

[v25.3.x] [CORE-15822] security/audit: make audit initialization controller-leader independent#31587
vbotbuildovich wants to merge 4 commits into
redpanda-data:v25.3.xfrom
vbotbuildovich:ai-backport-pr-31411-v25.3.x-1786713552

Conversation

@vbotbuildovich

Copy link
Copy Markdown
Collaborator

Backport of PR #31411

  • Command: git cherry-pick -x 93b416b fd294c6 110deaf 183963b
  • Commits backported: 4
  • Conflicts resolved: 1
  • Commits skipped (already on target): 0
  • Backport branch: ai-backport-pr-31411-v25.3.x-1786713552

Conflict details

  • 93b416b (src/v/security/audit/BUILD): the commit adds //src/v/cluster:topic_table to implementation_deps, but v25.3.x has no fine-grained //src/v/cluster:* targets (that split is dev-only) — topic_table.{cc,h} are still part of the monolithic //src/v/cluster library, which this rule already lists in deps. Resolved by taking the target-branch side, leaving the BUILD file unchanged; the new cluster/topic_table.h include in client.cc is already satisfied.

bartoszpiekny-redpanda and others added 4 commits August 14, 2026 13:20
create_internal_topic() unconditionally sent CreateTopics to the
controller, so every audit enable/startup depended on controller
availability even when the audit topic already existed. In two analyzed
production incidents (CORE-15822, CORE-12785) the topic existed and the
produce path was viable the whole time, yet the audit client looped on
this step forever, which left the audit queues undrained and rejected
authentication cluster-wide.

Check the local topic table first and skip creation when the topic is
already known. RPC sink only: in the Kafka-client sink the create step
doubles as the ephemeral-credential bootstrap -- its SASL failure is
what triggers inform() to the brokers -- so skipping it there leaves
the client unable to authenticate. A stale-negative table read falls
back to the old path; a stale-positive read (topic deleted mid-enable)
has the same exposure as today's create-then-delete race.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 93b416b)
Disable and re-enable auditing on a cluster where the audit topic
already exists. RPC sink: assert creation is skipped and events
generated after the re-enable still reach the audit log. Kafka-client
sink: assert creation is deliberately NOT skipped -- its SASL failure
is what triggers ephemeral-credential propagation (inform), so this
pins the contract against a future "harmonizing" refactor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fd294c6)
Give the kafka-client sink the same controller-leader independence the
RPC sink got from the exists-skip, so re-initialization (broker restart,
audit re-enable) cannot wedge on a leaderless controller:

- proactively inform() all brokers of the audit principal's credential
  right after minting it. Previously propagation rode on the CreateTopics
  SASL failure via mitigate_error; a broker that minted a fresh
  credential while its peers were unreachable wedged on connect() even
  after quorum returned -- peers reject SASL, the client aggregates the
  per-broker failures into broker_not_available with no node id, and the
  sasl_authentication_failed mitigation never fires. Only a broker
  restart healed it.
- skip create_acls when both audit-topic bindings are readable locally;
  the write is a raft-0 command needing an elected leader.
- skip CreateTopics when the audit topic exists in the local topic
  table; without a leader the request cannot even be routed to the
  controller broker. Safe now that inform-all owns credential
  propagation instead of the create's SASL failure.
- treat topic_authorization_failed like illegal_sasl_state in
  update_status, so a missing-ACL state (e.g. a stale positive from the
  ACL exists-skip) degrades loudly through _misconfigured instead of
  silently dropping batches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 110deaf)
A leaderless controller must not block auditing: both sinks initialize
during a leaderless window, an admin event audited during the window is
consumed back from the audit topic while the controller is still
leaderless, and events keep flowing after quorum returns -- all with no
broker restart. Covered:

- AuditLogLeaderlessControllerTest (matrix over both transports): 5
  brokers, the single audit partition pinned to the first three, stop 3
  of 5 so raft group 0 cannot elect a leader while the audit partition
  keeps 2/3 replicas; restart a survivor, assert initialization
  completes through the exists-skips with no failure/retry loop, read
  the in-window admin event back from the audit topic, then the
  post-recovery event too.
- AuditLogTopicRecreateTest (both transports): false-positive guard --
  when the audit topic genuinely does not exist the skip must not fire
  and initialization goes through CreateTopics again.
- AuditLogTopicExistsTest: the kafka-client expectation flips from
  "must keep issuing CreateTopics" to the new contract: skip, ACL skip
  and the proactive inform-all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 183963b)
@vbotbuildovich vbotbuildovich added this to the v25.3.x-next milestone Aug 14, 2026
@vbotbuildovich vbotbuildovich added the kind/backport PRs targeting a stable branch label Aug 14, 2026
@bartoszpiekny-redpanda

Copy link
Copy Markdown
Contributor

Closing it - we might backport it only if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/redpanda kind/backport PRs targeting a stable branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants