Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions charts/insight/templates/secrets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ stringData:
APP__gears__authenticator__config__redirect_uri: {{ tpl (required "authenticator.oidc.redirectUri is required" .Values.authenticator.oidc.redirectUri) . | quote }}
# Service-token endpoint audience callers sign assertions for.
APP__gears__authenticator__config__service_tokens__audience: {{ printf "http://%s-authenticator.%s.svc.cluster.local:8093/internal/token" .Release.Name .Release.Namespace | quote }}
# Audit events to the platform Redpanda topic (step 10.8). Empty = disabled
# (structured log only) — the global redpanda.brokers wires it when set.
APP__gears__authenticator__config__audit__brokers: {{ .Values.redpanda.brokers | default "" | quote }}

{{- if .Values.identity.deploy }}
---
Expand Down
148 changes: 148 additions & 0 deletions deploy/compose/authenticator-fullauth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# dev-compose full-auth authenticator host config (bind-mounted over
# /app/config/insight.yaml). Same structure as
# services/authenticator/config/insight.yaml, but with the oidc-authn-plugin
# wired to the dev TLS discovery front (`authn-tls`, i.e. the authenticator's
# own issuer) + its self-signed CA at /certs/ca.pem, so the `.authenticated()`
# admin surface (session revoke-by-user) verifies real gateway JWTs in dev.
# Redis/Identity/OIDC leaves come from APP__ env in docker-compose.yml.

server:
home_dir: "/tmp/authenticator"

logging:
default:
console_level: info

gears:
api-gateway:
config:
bind_addr: "0.0.0.0:8083"
enable_docs: true
cors_enabled: true
openapi:
title: "Insight Authenticator"
version: "0.1.0"
description: "OIDC login, opaque sessions, and the cookie-to-JWT exchange (BFF / token-handler)"
auth_disabled: false

gear-orchestrator:
config: {}

grpc-hub:
config:
listen_addr: "uds:///tmp/authenticator-grpc"

authn-resolver:
config:
vendor: "hyperspot"

# Gateway-JWT verification for the `.authenticated()` admin surface. Verifies
# the ES256 gateway JWT against our own JWKS (resolved via OIDC discovery on
# the issuer) and maps claims into the SecurityContext. The deployment
# supplies the real issuer + self-signed CA (dev/e2e) via the config layer;
# the placeholders below (`.invalid`, never resolvable) fail closed.
oidc-authn-plugin:
config:
vendor: "hyperspot"
priority: 50
jwt:
supported_algorithms: ["ES256"]
clock_skew_leeway: 60s
require_audience: true
expected_audience:
- "internal-services"
trusted_issuers:
# = the authenticator's own gateway_issuer (the token `iss`);
# discovery_url omitted → {issuer}/.well-known/openid-configuration,
# served by the authn-tls front.
- issuer: "https://authn-tls:8443"
claim_mapping:
subject_id: "sub"
subject_tenant_id: "tenant_id"
subject_type: "sub_type"
token_scopes: "roles"
required_claims: []
http_client:
request_timeout: 5s
custom_ca_certificate_paths: ["/certs/ca.pem"]
# Client-credentials exchange is UNUSED here (the authenticator makes no
# outbound S2S calls through the plugin) but the block is required config.
s2s_oauth:
discovery_url: "https://authn-tls:8443"
default_subject_type: "service"
token_cache:
ttl: 300s
max_entries: 100

authz-resolver:
config:
vendor: "hyperspot"

static-authz-plugin:
config:
vendor: "hyperspot"
priority: 100

tenant-resolver:
config:
vendor: "hyperspot"

single-tenant-tr-plugin:
config:
vendor: "hyperspot"
priority: 20

authenticator:
config:
# HTTP bind is owned by api-gateway above; retained for diagnostics only.
bind_addr: "0.0.0.0:8083"
# Session + JWT lifecycle defaults are the §4.1 table (baked into the
# config struct); overridden here only when a deployment needs to.
# Connection strings + OIDC client secret are injected via env.
redis_url: ""
signing_keys_path: ""
identity_url: ""
gateway_issuer: ""
jwt_audience: "internal-services"
redirect_uri: ""
default_return_to: "/"
# CSRF Origin-allowlist fallback (10.5): the browser sends Origin on all
# state-changing fetches, so dev flows keep working until the SPA sends
# X-CSRF-Token. Vite dev origin + the gateway entry.
csrf_origins: ["http://localhost:3000", "http://localhost:8080"]
# Login scopes. offline_access is omitted (survives-logout token, wrong for a
# BFF); add it only for an IdP that needs it for a refresh token, e.g. Entra.
oidc_scopes: ["openid", "email", "profile"]
idp:
issuer_url: ""
client_id: ""
client_secret: ""
# id_token claim naming the user's single tenant (string; an array is
# tolerated — first entry wins). fakeidp/Keycloak emit `tenant_id`;
# Entra emits `tid`.
tenant_claim: "tenant_id"
# Fallback tenant for a claim-less IdP (e.g. Okta); empty = fail closed.
default_tenant_id: ""
# Service tokens (§10 G1 / DD-AUTH-05). The token endpoint runs on its own
# listener (token_bind_addr) so it never shares the main port. Service
# tokens are always tenant-scoped; the caller names the tenant.
#
# `testclient` is DEV/TEST ONLY. No key material is committed: dev-compose
# and run-e2e generate a throwaway keypair (like the gateway signing key)
# and drop its public half in public_key_dir; the private half is handed
# to the calling client. Real services land their public key via a gitops
# PR (chart ConfigMap uses inline public_keys), never this test entry.
service_tokens:
token_bind_addr: "0.0.0.0:8093"
audience: "http://localhost:8093/internal/token"
assertion_max_lifetime_seconds: 60
token_ttl_seconds: 300
# Set via env in dev/e2e to the generated-key dir (see run-e2e.sh /
# dev-compose.sh): APP__gears__authenticator__config__service_tokens__public_key_dir
public_key_dir: ""
services:
testclient:
public_key_paths: ["testclient.pub.pem"]
# session_admin authorizes the admin revoke-by-user operation —
# dev/e2e only; real registry entries earn it via a gitops PR.
roles: ["service", "session_admin"]
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,18 @@ services:
# Resolves the testclient public_key_paths against the mounted dev keys
# (dev-compose.sh generates testclient.pub.pem there; nothing committed).
APP__gears__authenticator__config__service_tokens__public_key_dir: "/app/keys"
# Audit events to the platform topic (step 10.8). Delivery failures are
# dropped + counted, so a stack without Redpanda still works.
APP__gears__authenticator__config__audit__brokers: "${AUDIT_BROKERS:-redpanda:9092}"
volumes:
- ./deploy/compose/build/authenticator/authenticator:/app/authenticator:ro
- ./src/backend/services/authenticator/config:/app/config:ro
# Full-auth plugin config (issuer + CA) — bind-mounted over the committed
# placeholder config so the `.authenticated()` admin surface verifies real
# gateway JWTs in dev (the authenticator trusts its own tokens).
- ./deploy/compose/authenticator-fullauth.yaml:/app/config/insight.yaml:ro
# Self-signed CA for the authn-tls discovery front.
- ./deploy/compose/authn-tls-certs:/certs:ro
# dev signing key (current.pem) + dev service-token pubkey, both generated
# by dev-compose.sh into this gitignored dir and mounted at signing_keys_path.
- ./deploy/compose/authenticator-dev-keys:/app/keys:ro
Expand Down
68 changes: 68 additions & 0 deletions src/backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/backend/services/authenticator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ toolkit-canonical-errors = { workspace = true }
# via inventory; mirrors the analytics service's gear set.
api_gateway = { workspace = true }
authn-resolver = { workspace = true }
oidc-authn-plugin = { workspace = true }
authz-resolver = { workspace = true }
tenant-resolver = { workspace = true }
single-tenant-tr-plugin = { workspace = true }
Expand All @@ -51,6 +52,10 @@ futures = { workspace = true }
uuid = { workspace = true, features = ["v5"] }
chrono = { workspace = true }
tracing = { workspace = true }
opentelemetry = "0.31"
# Audit events to the platform Redpanda topic (Kafka-compatible rdkafka API
# only — backend PRD migration constraint). cmake-build vendors librdkafka.
rdkafka = { version = "0.38", features = ["cmake-build", "tokio"] }
tracing-subscriber = { workspace = true }
clap = { workspace = true }
reqwest = { workspace = true }
Expand Down
5 changes: 3 additions & 2 deletions src/backend/services/authenticator/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
# Stage 1: Builder
FROM rust:1.95-bookworm AS builder

# protobuf-compiler is required by grpc-hub -> prost-build.
# protobuf-compiler is required by grpc-hub -> prost-build; cmake builds the
# vendored librdkafka (audit events to Redpanda, step 10.8).
RUN apt-get update && \
apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev && \
apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev cmake && \
rm -rf /var/lib/apt/lists/*

WORKDIR /build
Expand Down
52 changes: 46 additions & 6 deletions src/backend/services/authenticator/config/insight.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
# in this EPIC. The authenticator hosts its endpoints on this REST-host gear and
# runs behind the nginx edge.
#
# Auth is DISABLED on the host because every step-04 endpoint is `.public()`
# (the credential is the session cookie, checked in the handler), so no OIDC
# plugin is registered. The admin session-revoke surface (later step) will flip
# to an authenticated pipeline verifying gateway JWTs.
# Auth is ENABLED on the host for the `.authenticated()` admin surface
# (session revoke-by-user): the oidc-authn-plugin verifies the ES256 gateway
# JWT — the authenticator trusts its own tokens exactly like any downstream
# service. Every browser endpoint stays `.public()` (the credential is the
# session cookie, checked in the handler) and bypasses the plugin.
#
# Deployment-specific leaf values are injected via env overrides
# (APP__gears__authenticator__config__*) from the umbrella Secret / compose;
Expand Down Expand Up @@ -43,7 +44,7 @@ gears:
title: "Insight Authenticator"
version: "0.1.0"
description: "OIDC login, opaque sessions, and the cookie-to-JWT exchange (BFF / token-handler)"
auth_disabled: true
auth_disabled: false

gear-orchestrator:
config: {}
Expand All @@ -56,6 +57,40 @@ gears:
config:
vendor: "hyperspot"

# Gateway-JWT verification for the `.authenticated()` admin surface. Verifies
# the ES256 gateway JWT against our own JWKS (resolved via OIDC discovery on
# the issuer) and maps claims into the SecurityContext. The deployment
# supplies the real issuer + self-signed CA (dev/e2e) via the config layer;
# the placeholders below (`.invalid`, never resolvable) fail closed.
oidc-authn-plugin:
config:
vendor: "hyperspot"
priority: 50
jwt:
supported_algorithms: ["ES256"]
clock_skew_leeway: 60s
require_audience: true
expected_audience:
- "internal-services"
trusted_issuers:
- issuer: "https://gateway.invalid"
claim_mapping:
subject_id: "sub"
subject_tenant_id: "tenant_id"
subject_type: "sub_type"
token_scopes: "roles"
required_claims: []
http_client:
request_timeout: 5s
# Client-credentials exchange is UNUSED here (the authenticator makes no
# outbound S2S calls through the plugin) but the block is required config.
s2s_oauth:
discovery_url: "https://gateway.invalid"
default_subject_type: "service"
token_cache:
ttl: 300s
max_entries: 100

authz-resolver:
config:
vendor: "hyperspot"
Expand Down Expand Up @@ -88,6 +123,9 @@ gears:
jwt_audience: "internal-services"
redirect_uri: ""
default_return_to: "/"
# CSRF Origin-allowlist fallback for state-changing /auth/* (10.5).
# Empty = fail closed: the X-CSRF-Token header is required.
csrf_origins: []
# Login scopes. offline_access is omitted (survives-logout token, wrong for a
# BFF); add it only for an IdP that needs it for a refresh token, e.g. Entra.
oidc_scopes: ["openid", "email", "profile"]
Expand Down Expand Up @@ -121,4 +159,6 @@ gears:
services:
testclient:
public_key_paths: ["testclient.pub.pem"]
roles: ["service"]
# session_admin authorizes the admin revoke-by-user operation —
# dev/e2e only; real registry entries earn it via a gitops PR.
roles: ["service", "session_admin"]
Loading
Loading