Skip to content

feat: wire SASL/SCRAM authentication into streaming producer#2123

Merged
ClaraTersi merged 1 commit into
developfrom
feature/event-streaming-broker-auth
May 28, 2026
Merged

feat: wire SASL/SCRAM authentication into streaming producer#2123
ClaraTersi merged 1 commit into
developfrom
feature/event-streaming-broker-auth

Conversation

@ClaraTersi
Copy link
Copy Markdown
Member

Adds SASL authentication support to the ledger's lib-streaming producer, so the service can publish business events to brokers that require auth (Redpanda Cloud, managed Kafka, internal staging clusters, Clotilde dev). Until now BuildStreamingEmitter only configured brokers + topics + catalog, and any broker enforcing SASL closed the connection — observable as class=context_canceled: context deadline exceeded on the first emit because franz-go retried the handshake silently until our 5s per-emit deadline fired.

Changes

New env vars (components/ledger/.env.example)

Var Purpose Default
STREAMING_SASL_MECHANISM PLAIN | SCRAM-SHA-256 | SCRAM-SHA-512 (case-insensitive). Empty = no auth, preserving today's behaviour empty
STREAMING_SASL_USERNAME Required when MECHANISM is set empty
STREAMING_SASL_PASSWORD Required when MECHANISM is set empty
STREAMING_ALLOW_PLAINTEXT_SASL Dev-only opt-in for SASL without TLS. Required when targeting a SASL_PLAINTEXT listener (e.g. local Redpanda) false

Also documents STREAMING_IMPORTANT_EMIT_TIMEOUT_MS — the knob was already wired in pkg/streaming/emit.go but wasn't discoverable from the env template. Useful for tuning the per-emit deadline against slow or remote brokers.

Bootstrap (components/ledger/internal/bootstrap/streaming.go)

  • New helper resolveSASLMechanism(*Config) (sasl.Mechanism, name, error) — maps the configured mechanism to the matching franz-go sasl.Mechanism (plain.Auth, scram.Auth.AsSha256Mechanism, scram.Auth.AsSha512Mechanism).
  • BuildStreamingEmitter now calls Builder.SASL(...) and Builder.AllowPlaintextSASL() when configured.
  • The bootstrap log line reports auth=<mechanism> (or auth=none). Username and password are never logged, even at debug level.
  • SASL without TLS and without the explicit STREAMING_ALLOW_PLAINTEXT_SASL opt-in is rejected at Build time with lib-streaming.ErrPlaintextSASLNotAllowed — matches the lib-streaming contract.
  • Empty credentials with a non-empty mechanism, or unsupported mechanisms (e.g. OAUTHBEARER, GSSAPI), fail closed at bootstrap with an actionable error message.

Config (components/ledger/internal/bootstrap/config.go)

Adds the four fields to the Config struct so the SASL knobs sit in the same env inventory as the rest of STREAMING_*.

Dependency

Promotes github.com/twmb/franz-go from indirect to direct dependency (we now import its sasl/plain and sasl/scram packages explicitly). Already on the version that lib-streaming pulls in transitively, so no version bump.

Tests (components/ledger/internal/bootstrap/streaming_test.go, +296 LOC)

  • TestResolveSASLMechanism_Disabled — empty / whitespace / credentials-without-mechanism all return nil mechanism (back-compat path).
  • TestResolveSASLMechanism_Supported — all 7 accepted spellings (case-insensitive PLAIN, SCRAM-SHA-256, SCRAM-SHA-512) build the right franz-go mechanism.
  • TestResolveSASLMechanism_MissingCredentials — missing username/password fails closed.
  • TestResolveSASLMechanism_Unsupported — typos and unsupported mechanisms (OAUTHBEARER, GSSAPI, SCRAM-SHA-1, etc.) fail closed with an enumerated error.
  • TestBuildStreamingEmitter_NilConfig / _DisabledReturnsNoop — preserves existing nil-guard and disabled-pilot contracts.
  • TestBuildStreamingEmitter_SASLWithoutTLSFailsClosed — locks in that we never silently downgrade SASL to plaintext: must return errors.Is(err, libStreaming.ErrPlaintextSASLNotAllowed).
  • TestBuildStreamingEmitter_SASLWithAllowPlaintextSucceeds — dev-broker happy path with SCRAM-SHA-256 + AllowPlaintextSASL builds without dialling the broker.
  • TestBuildStreamingEmitter_UnsupportedMechanismFailsClosed — resolveSASLMechanism errors propagate out of BuildStreamingEmitter.

Backwards compatibility

Default for every new env var is empty / false. Services that don't set STREAMING_SASL_MECHANISM get the exact same unauthenticated producer they had before. No migration required for existing deployments. Existing STREAMING_* knobs (BROKERS, CLIENT_ID, COMPRESSION, etc.) are unchanged.

@ClaraTersi ClaraTersi requested review from a team as code owners May 28, 2026 11:18
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Review Change Stack

Walkthrough

This PR adds SASL authentication support to the ledger's streaming producer. It extends the bootstrap configuration with SASL fields, implements mechanism resolution and validation logic, wires SASL into the streaming builder with plaintext guards, and includes comprehensive tests for all code paths.

Changes

Streaming SASL Authentication

Layer / File(s) Summary
SASL configuration contract and environment documentation
components/ledger/internal/bootstrap/config.go, components/ledger/.env.example, go.mod
Config struct gains StreamingSASLMechanism, StreamingSASLUsername, StreamingSASLPassword, and StreamingAllowPlaintextSASL fields wired to environment variables; .env.example documents SASL/TLS authentication options with warnings; franz-go upgraded to direct dependency at v1.21.0.
SASL mechanism resolution and emitter integration
components/ledger/internal/bootstrap/streaming.go
Adds franz-go SASL imports and canonical mechanism name constants; introduces resolveSASLMechanism to parse mechanism, validate credentials, and map to franz-go SASL objects; BuildStreamingEmitter calls resolver and conditionally applies SASL and plaintext-allowance settings while logging the auth mode.
SASL resolution and emitter test coverage
components/ledger/internal/bootstrap/streaming_test.go
Tests for resolveSASLMechanism cover disabled (empty/whitespace), supported (PLAIN, SCRAM-SHA-256, SCRAM-SHA-512 with normalization), missing-credentials, and unsupported-mechanism cases; tests for BuildStreamingEmitter cover nil config, disabled streaming, plaintext-SASL security guard, success with opt-in, and unsupported-mechanism error propagation.

Comment @coderabbitai help to get the list of available commands and usage tips.

@lerian-studio
Copy link
Copy Markdown
Contributor

🔒 Security Scan Results — crm

Trivy

Filesystem Scan

✅ No vulnerabilities or secrets found.

Docker Image Scan

✅ No vulnerabilities found.


Docker Hub Health Score Compliance

✅ Policies — 4/4 met

Policy Status
Default non-root user ✅ Passed
No fixable critical/high CVEs ✅ Passed
No high-profile vulnerabilities ✅ Passed
No AGPL v3 licenses ✅ Passed

Pre-release Version Check

🚫 Found 1 unstable version pin(s). Only stable releases (x.y.z) and SHA-based pins are allowed.

File Line Content
./go.mod 103 github.com/LerianStudio/lib-commons/v5 v5.2.0-beta.11

Replace pre-release suffixes (-alpha, -beta, -rc, -dev, etc.) with stable releases.


🔍 View full scan logs

@lerian-studio
Copy link
Copy Markdown
Contributor

🔒 Security Scan Results — ledger

Trivy

Filesystem Scan

✅ No vulnerabilities or secrets found.

Docker Image Scan

✅ No vulnerabilities found.


Docker Hub Health Score Compliance

✅ Policies — 4/4 met

Policy Status
Default non-root user ✅ Passed
No fixable critical/high CVEs ✅ Passed
No high-profile vulnerabilities ✅ Passed
No AGPL v3 licenses ✅ Passed

Pre-release Version Check

🚫 Found 1 unstable version pin(s). Only stable releases (x.y.z) and SHA-based pins are allowed.

File Line Content
./go.mod 103 github.com/LerianStudio/lib-commons/v5 v5.2.0-beta.11

Replace pre-release suffixes (-alpha, -beta, -rc, -dev, etc.) with stable releases.


🔍 View full scan logs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/ledger/internal/bootstrap/streaming.go`:
- Around line 121-152: BuildStreamingEmitter performs non-trivial setup before
calling builder.Build(ctx) but doesn’t check for context cancellation; add an
early guard that checks ctx.Err() (and returns nil, noopStreamingCloser,
fmt.Errorf("context canceled") or wrap ctx.Err()) immediately at the start of
BuildStreamingEmitter to avoid doing work when the context is canceled, and add
a second quick ctx.Err() check just before calling builder.Build(ctx) (after
SASL/TLS config and resolveSASLMechanism) to avoid the final Build if
cancellation occurred; reference resolveSASLMechanism, builder,
builder.Build(ctx), cfg.StreamingAllowPlaintextSASL, noopStreamingCloser and
streamingPrimaryTargetName when making the changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65434e92-e810-48e5-ae14-7917c3e1b211

📥 Commits

Reviewing files that changed from the base of the PR and between 3812799 and 19fdacb.

📒 Files selected for processing (5)
  • components/ledger/.env.example
  • components/ledger/internal/bootstrap/config.go
  • components/ledger/internal/bootstrap/streaming.go
  • components/ledger/internal/bootstrap/streaming_test.go
  • go.mod

Comment thread components/ledger/internal/bootstrap/streaming.go
@ClaraTersi ClaraTersi merged commit 8250a7c into develop May 28, 2026
20 of 22 checks passed
@ClaraTersi ClaraTersi deleted the feature/event-streaming-broker-auth branch May 28, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants