- AUDIT-WP-0020-T03: exercise_audit_sender emits tenant:platform and probes tenant:other as the wrong tenant (row E6); contract suite 131 passed against audit-core 4da952c. - Intake doc: audit-core P6 answers (tenant, correlation_origin, privacyIDEA option b, no backfill, 30-day recoverability). - NK-WP-0055-T11 note; NK-WP-0035-T04 and NK-WP-0054-T04 wait reasons now name the founder/next step after the 2026-11-03 owner deliveries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 149903@bnt-lap001 Assistant-Session: 4c20a536-1452-4a85-bc09-e1987d9817d6
27 KiB
| title | status | owner | work | answers | contract | created |
|---|---|---|---|---|---|---|
| audit-core intake packages: Authelia, LLDAP, privacyIDEA | draft | net-kingdom | NK-WP-0055-T11 | audit-core AUDIT-WP-0017-T04 (three of the four identity providers; KeyCape is separate) | canon/standards/audit-correlation_v0.1.md (accepted) | 2026-10-27 |
audit-core intake packages: Authelia, LLDAP, privacyIDEA (draft)
This document gives audit-core the intake package for each of the three identity providers that NetKingdom deploys:
- Authelia (
sso-mfa/k8s/authelia); - LLDAP (
sso-mfa/k8s/lldap); - privacyIDEA (
sso-mfa/k8s/privacyidea).
It follows the common package in audit-core AUDIT-WP-0017 ("Common intake package for every new sender") and
the wire contract in audit-core docs/event-envelope.md. KeyCape's package belongs to key-cape and is not covered here.
Status. Draft for audit-core review. It plans work only:
- it activates no sender;
- it mints no credential;
- it makes no live change.
How each claim is marked:
- [repo]: read from this repository's manifests.
- [upstream]: from upstream product behaviour as we know it. Not yet verified against the pinned image.
- [estimate]: a volume figure that must be measured before audit-core sets thresholds.
- [unknown]: an open question.
1. Choices that apply to all three providers
Separate sources. Each provider gets its own source, as AUDIT-WP-0017-T04 asks, so that silence in one of them
can be detected:
netkingdom.authelianetkingdom.lldapnetkingdom.privacyidea
Owner. The owning repo for all three sources is net-kingdom. ADR-0015 moves runtime packaging to rapp-*
repositories. If that move happens, the source name stays the same and only the owning repo changes.
Tenant policy. All three providers are platform-scoped: there is a single realm and directory, dc=netkingdom,dc=local.
- The envelope requires
tenantto be a non-empty value. - Use the fixed value
tenant:platformfor every event, and give the sender credential onlytenant:platformas a permitted tenant. Settled by the founder on 2026-11-07 (audit-core AUDIT-WP-0020); this replaces theplatformfirst proposed here.
Shipper. One small collector per provider: the intake shipper.
- It runs in the provider's namespace, either as a sidecar or as a CronJob.
- Each shipper has its own sender credential.
- It normalises native records into the envelope and posts them to
POST /v1/events. - On retry it reuses the same
id, so delivery is idempotent.
Envelope mapping:
| Envelope field | Mapping rule |
|---|---|
id and Idempotency-Key |
A deterministic hash of the provider name and a stable native key (defined per provider below). Retries and re-reads after a restart produce the same id. |
type |
One value from the provider's action list below. The receiver stores it as action. |
subject |
The affected object as a pseudonymous reference, e.g. user:<pseudonym>, group:<cn> or token:<serial>. Never a raw username or email address. |
occurred_at |
The provider's native event time as RFC 3339 with an explicit Z. All three providers run in UTC. |
correlation_id |
The 32-hex trace id (AC-1) if a valid traceparent was seen. Otherwise a newly generated trace id, with data.correlation_origin: generated (AC-4). |
data |
Holds the outcome, the actor (pseudonymised), the native record id, correlation_origin, and the fields listed per provider. |
Placement of actor and outcome. The receiver fixes actor=null and outcome=recorded at ingest. The real
actor and outcome therefore go inside data:
data.outcomeis one ofsuccess,failureorrefused.data.actoris the acting principal.
Pseudonymisation of personal data. The rules are the same for all three providers.
-
Usernames, emails and display names:
- Replace them with a keyed pseudonym before sending: HMAC-SHA256 truncated to 32 hex characters.
- The key is a NetKingdom-held value in OpenBao and is never sent to audit-core.
- This keeps records from the three providers joinable on the same user without exposing the name.
- Raw names must never appear in
subjectorid. - [unknown] Whether audit-core prefers its own per-subject encrypted personal payload (
docs/erasure-and-audit.md) instead. If so, the raw name may travel in that payload, and our pseudonym becomes only the join key.
-
Synthetic, test and break-glass accounts: Service and break-glass account names (
admin, the KeyCape bind user,identity-provisioner) are operational identifiers, not personal data. They may be sent in clear. This is what lets break-glass use stand out. -
Client IP addresses: Personal data. Send them as a truncated prefix (IPv4 /24, IPv6 /48) plus a keyed pseudonym of the full address. Ban and regulation events need the full-address pseudonym for correlation. [unknown] Whether security operations need the clear IP for 30 days. That would need an owner decision under a separate retention period.
-
Never sent:
- passwords and password attempts;
- OTP values;
- session cookies;
- JWTs;
- OIDC codes and tokens;
- client secrets;
- enrollment seeds and QR payloads;
- LDAP bind credentials.
The receiver's
secret_policyis the backstop for these, not the control.
Credential. One sender credential per source:
- it is minted through the attended OpenBao path that
warden route find "audit-core sender credential"points to; - it is delivered to the shipper as a mounted file;
- audit-core never holds or prints it;
may_readis false.
Retention. The default applies: a 30-day query window plus archive. privacyIDEA records are the exception (section 4.5).
Heartbeat. Each shipper emits type: source.heartbeat every 15 minutes, even when there are no events. The
heartbeat's data carries:
- the shipper's cursor (last native id or log offset);
- the count of records read and sent since the last heartbeat.
A heartbeat with a stalled cursor while the provider is serving traffic is the completeness signal (audit-core
docs/stream-completeness.md).
2. Authelia: netkingdom.authelia
2.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace sso, Deployment authelia, selector app.kubernetes.io/name=authelia, 1 replica [repo] |
| Image | authelia/authelia 4.39.28, pinned by digest [repo] |
| Emitting identity | A shipper ServiceAccount audit-shipper-authelia in sso. Proposed; it does not exist yet. |
| Role | First-factor login (Authelia checks the password against LLDAP), forward-auth for activity/temporal, and the OIDC upstream for client keycape [repo] |
| Exposure | Behind the Traefik Ingress at auth.coulomb.social and login.coulomb.social [repo] |
2.2 Where the events are
1. Structured logs.
log.level: info. Nolog.formatis set, so output is text [repo].- Proposal: set
log.format: json. The schema is unchanged, and this is a small config change. - At info level, Authelia logs these events [upstream]:
- unsuccessful first-factor and second-factor attempts, with the username and remote IP;
- regulation bans;
- authorization denials;
- OIDC authorize and token errors.
- Successful logins are not reliably logged at info [upstream].
2. The authentication_logs table in Authelia storage.
- Storage here is SQLite at
/var/authelia/data/db.sqlite3on theautheliaPVC [repo]. - Each row records [upstream]:
- one first-factor or second-factor attempt, success or failure;
- username,
auth_type,remote_ip,request_uriandrequest_method; - a monotonic
id.
- Since 4.38, the
banned_userandbanned_iptables also exist [upstream]. - This is the authoritative source for login outcomes. Logs supplement it for OIDC and authorization events.
2.3 Collection
Recommended: a sidecar in the authelia pod with two read-only inputs.
(a) The SQLite file, read-only:
- The sidecar polls
authentication_logsand the ban tables byid, with a cursor kept on a small shipper PVC. - SQLite's WAL mode allows a concurrent read-only reader [upstream].
- The sidecar opens the database with
mode=roand never writes to it.
(b) The container stdout, with log.format: json:
- The sidecar reads it through a shared
emptyDirlog file. This needs a--log.file_pathsetting [upstream]. - Alternative: a node-level collector, if railiance-cluster provides one. [unknown]
Native key for id:
- Table rows:
authelia|authentication_logs|<id>. - Log lines:
authelia|log|<sha256 of the raw JSON line>.
Constraint. Storage is a single-writer SQLite file on an RWO PVC, so the sidecar must run in the same pod. [unknown] Whether a planned move of Authelia storage to PostgreSQL would replace (a) with a DB reader.
2.4 Correlation
- Browser traffic arrives through Traefik. Today Authelia neither reads nor logs a
traceparent, and it does not propagate one [upstream]. - When KeyCape redirects the user to Authelia, that is a browser navigation, so any trace context is lost.
- Default:
correlation_origin: generated. The shipper creates one trace id per record. - Improvement, needs work by railiance-cluster:
- Enable the Traefik access log in JSON with the
traceparentrequest header kept. Header names only; neverCookieorAuthorization. - Join on time, router and client IP to recover propagated ids for OIDC back-channel calls from KeyCape:
/api/oidc/tokenand/api/oidc/userinfo. - Only server-to-server calls can carry an inbound
traceparent. - [unknown] Whether Traefik tracing or header logging is enabled on railiance01.
- Enable the Traefik access log in JSON with the
- Same login, two records: The OIDC
authorize→tokenexchange is the hop KeyCape shares with Authelia.- The KeyCape issuance record holds the propagated id.
- The Authelia record for the same login can carry it only if KeyCape sends
traceparenton the token call and Traefik logs it.
2.5 Action list (scope rows)
type |
From | data beyond the common fields |
|---|---|---|
auth.first_factor.success / .failure |
authentication_logs (1FA) |
auth_method: password, request_host, ip_prefix, ip_pseudonym |
auth.second_factor.success / .failure |
authentication_logs (2FA rows; TOTP/WebAuthn/Duo if ever enabled) |
auth_type |
regulation.user_banned |
Ban table or log | ban_until, failed_count |
regulation.ip_banned |
Ban table (4.38+) | ban_until |
regulation.ban_lifted |
Ban table revoked / expiry |
by (admin or expiry) |
access.denied |
Log, authz deny | request_host (e.g. activity.coulomb.social), rule |
oidc.authorize.success / .failure |
Log | client_id (keycape), scopes, error |
oidc.token.issued / .failure |
Log | client_id, grant_type. No token values. |
oidc.consent.granted |
Log (implicit consent today) | client_id |
identity.reset_password.requested / .completed |
Log, notifier | (Password reset is via LLDAP/Authelia. [unknown] Whether it is enabled.) |
source.heartbeat |
Shipper | cursor, counts |
2.6 Volume, cadence and retention
- Volume [estimate]: a few hundred authentication rows per day across operators, demo users and probes. Peak under 5 events/s, except during a brute-force attempt.
- Brute force: Regulation caps each username at 5 failures per 2 minutes. IP-spread attacks are not capped. Proposed shipper rate limit: 50 events/s, with overflow counted in the heartbeat. Overflow is never silently dropped.
- Heartbeat: every 15 minutes.
- Allowed silence: 30 minutes of shipper silence. Gaps between authentication events are normal at night.
- Retention: default. [unknown] Whether 90 days is wanted for ban events.
3. LLDAP: netkingdom.lldap
3.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace sso, Deployment lldap, 1 replica. Ports: LDAP 3890, HTTP/GraphQL 17170 [repo] |
| Image | lldap/lldap:stable. Not pinned [repo]. Pinning it is a prerequisite for a stable log format. |
| Storage | Default SQLite on the lldap PVC. No LLDAP_DATABASE_URL is set [repo]. |
| Emitting identity | Shipper ServiceAccount audit-shipper-lldap in sso (proposed) |
| LDAP clients | Authelia and KeyCape bind as uid=admin [repo, Authelia config]. Admin automation: lldap_admin.py, identity-provisioner-reconcile.py, break-glass.sh [repo] |
3.2 Where the events are
- Logs. LLDAP has no audit table. Its only audit surface is the
tracinglog on stdout [upstream]. - At default verbosity, LLDAP logs [upstream]:
- LDAP bind attempts with the bind DN and success or failure;
- GraphQL mutations, i.e. admin changes:
- create, update and delete user;
- create and delete group;
- add and remove a member;
- password change through the OPAQUE flow.
- Field coverage at default verbosity [unknown]:
- Which fields appear without
verbose=truemust be verified against the pinned image. verbose=truelogs too much, including search filters that contain usernames.
- Which fields appear without
- Logins to the web UI (
/auth/opaque/login/*,/auth/simple/login) are logged with the username [upstream].
3.3 Collection
- Recommended: a log sidecar, or a node log collector, reading the
lldapcontainer's stdout. - Parsing:
- LLDAP logs are text spans, not JSON [upstream].
- The shipper keeps a strict per-version parser and an allowlist of message patterns.
- A line it does not recognise is counted in the heartbeat and never guessed.
- Native key for
id:lldap|<pod uid>|<container restart count>|<line sequence number>. This stays stable when the shipper re-reads after its own restart. [unknown] Whether the collector exposes a stable offset. If not, use a sha256 of pod uid, timestamp and raw line. - Cross-check through admin tooling:
- The repo's admin tools (
lldap_admin.py,manage-group-members.sh,create-user.sh) could emit their own events, as the actor's view of the change. - Proposal: not in this first package. Rely on the server log so that changes made outside the tools are captured too.
- The repo's admin tools (
3.4 Correlation
- LDAP (3890): There are no headers, so
correlation_origin: generatedalways (AC-2). The calling component (Authelia or KeyCape) records its own inbound id. LLDAP records only its own. - HTTP/GraphQL (17170) is behind Traefik (
ingress.yaml). LLDAP does not logtraceparent[upstream]. The same Traefik access-log join as for Authelia could recover it for admin API calls. [unknown] - Joining LDAP records: A bind from Authelia happens within milliseconds of an Authelia
auth.first_factor.*record for the same user pseudonym. audit-core may record aparent_event_idlink only if the shipper can make it deterministically. Proposal: do not; a time-and-pseudonym join is analysis, not correlation.
3.5 Action list
type |
Notes |
|---|---|
ldap.bind.success / .failure |
bind_dn (service DN in clear; a user DN is pseudonymised), client_ip_prefix. Failures are the main signal. |
ldap.bind.service |
Optional. A successful bind by the uid=admin service account, which is expected high-volume noise. Proposal: count these in the heartbeat instead of sending one event per bind. |
user.created / user.updated / user.deleted |
actor (admin principal), changed attribute names only |
user.password_changed |
actor, self true/false. No password material. |
group.created / group.deleted |
group cn in clear (group names are operational) |
group.member_added / group.member_removed |
group, member pseudonym, actor |
admin.login.success / .failure |
Web UI or API login |
source.heartbeat |
Includes service_bind_count and unparsed_line_count |
3.6 Volume, cadence and retention
- Volume [estimate]:
- Service binds: from hundreds up to a few thousand per day. They follow Authelia logins and KeyCape lookups, and appear only as counts.
- Events actually sent: under 200 per day.
- Admin changes: a handful per week, with bursts during reconciliation runs (
identity-provisioner-reconcile.py).
- Peak: under 5 events/s.
- Heartbeat: every 15 minutes.
- Allowed silence: 30 minutes.
- Retention: default. Group-membership changes are authorization-relevant. [unknown] Whether audit-core should keep them for longer.
4. privacyIDEA: netkingdom.privacyidea
4.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace mfa, Deployment privacyidea, 1 replica [repo] |
| Image | ghcr.io/gpappsoft/privacyidea-docker:3.12.2 [repo] |
| Database | PostgreSQL cluster net-kingdom-pg, namespace databases. Audit uses the default SQL audit module [repo, README; PI_AUDIT_MODULE not set, so the default sqlaudit applies, upstream] |
| Audit signing | PI_AUDIT_KEY_PRIVATE / PI_AUDIT_KEY_PUBLIC at /etc/privacyidea/{private,public}.pem, from Secret privacyidea-auditkeys [repo] |
| Callers | KeyCape (MFA challenge on /authorize/callback), NetKingdom admin scripts (pi_api.py, check-user-mfa-state.sh, onboarding guard) [repo] |
| Emitting identity | Shipper ServiceAccount audit-shipper-privacyidea, with a read-only PostgreSQL role on the audit table only (proposed) |
4.2 Where the events are
The table. The signed audit table pidea_audit in PostgreSQL [upstream schema, 3.x]. Each request writes one
row with these columns:
id,date,startdate,durationaction(e.g.POST /validate/check),success,action_detail,infoserial,token_typeuser,realm,resolver,administratorclient(IP),privacyidea_serverpolicies,container_serialsignature
The signature. Each row is signed with the audit private key.
Proof of the signed history (NK-WP-0049-T20, 2026-10-16):
- All 279,209 stored rows verified against the retained original audit key pair. None were unsigned, invalid or legacy.
- After restore, a new entry was signed with the preserved identity.
- Tampered rows and a wrong key were refused.
Consequence. The audit key pair is a retained identity:
- It must never be regenerated to repair anything (
KEY-RECOVERY.md). - The public key is the verification anchor that audit-core needs.
The application log at /var/log/privacyidea/privacyidea.log (PI_LOGLEVEL=20) [repo] duplicates part of this
and is not signed. Out of scope.
4.3 Collection: database export with the signature preserved
Export method. An exporter (CronJob or sidecar) reads the table:
SELECT ... FROM pidea_audit WHERE id > :cursor ORDER BY id LIMIT 500- It runs under a dedicated read-only role.
- The cursor is kept on the shipper side and echoed in the heartbeat.
What each row becomes:
id= sha256 ofprivacyidea|pidea_audit|<id>|<signature>.- The signature is included so that a restored database with re-used ids cannot collide silently with earlier events.
- [unknown] Whether a restore can roll ids back. T20 restored into an isolated target, not production.
data.nativeholds every signed column, unchanged, plussignature.data.native_signature_algholds the privacyIDEA version and audit module.data.verification_key_fingerprintholds the SHA-256 of the public key.
Personal data in the signed row. The user, client, administrator and info columns can contain usernames and IPs.
- Pseudonymising them in
data.nativewould break the signature. - Proposed resolution: send them in audit-core's per-subject encrypted personal payload, if one exists (
docs/erasure-and-audit.md), so the row stays verifiable. - Use pseudonymous copies in the plain
data,subjectandactorfields. - Owner decision needed: if audit-core cannot hold an encrypted personal payload, we must choose one of:
- (a) keep raw columns in the signed payload and accept them under audit-core's PII handling; or
- (b) send only pseudonymised fields plus the signature and the row hash. Verification would then be possible only against the source database while it is retained.
Verification by audit-core (proposal):
- Store the public key once as source metadata.
- The shipper verifies each row before sending. A failed row is sent as
type: audit.signature_invalidwith the row id and never dropped. - audit-core can re-verify a sample.
- [unknown] Whether privacyIDEA's signed-string format is stable across 3.12.x. Pin the exporter to the image version.
Not used: privacyIDEA's logger or container audit module (PI_AUDIT_MODULE set to a logger-based module) [upstream].
- It would stream audit records to a log handler.
- Its records are not signed and would bypass the T20 continuity property.
- Rejected as the primary path; possibly useful later as a low-latency mirror.
4.4 Correlation
-
privacyIDEA 3.12 neither records nor propagates
traceparent[upstream].pidea_audithas no column for it. -
Default:
correlation_origin: generated. -
Improvement: KeyCape's MFA call (
/validate/check) is server-to-server. If KeyCape sendstraceparentand the privacyIDEA Ingress/Traefik access log records it, the shipper can join on these fields:startdate;client(KeyCape pod IP);serial/user.
The join recovers the propagated id. This is the same dependency on railiance-cluster as for Authelia. [unknown]
-
Cluster-internal calls: If KeyCape calls privacyIDEA by cluster-internal Service address, the call bypasses Traefik. Then only a privacyIDEA-side WSGI middleware could log the header. That is an upstream or plugin change. [unknown] Which address KeyCape uses in production.
4.5 Action list
The type is derived from action, the HTTP method plus endpoint:
type |
Native action |
Notes |
|---|---|---|
mfa.challenge.success / .failure |
POST /validate/check, /validate/samlcheck |
serial, token_type, user pseudonym, realm |
mfa.challenge.triggered |
POST /validate/triggerchallenge |
|
token.enrolled / token.enrollment_confirmed |
POST /token/init (2-step) |
No seed or QR material; info is checked for secret-shaped values |
token.deleted / .disabled / .enabled / .revoked / .reset / .unassigned |
DELETE /token/<serial>, POST /token/disable etc. |
administrator in actor |
token.resync / .setpin |
POST /token/resync, /token/setpin |
|
policy.changed / policy.deleted |
POST/DELETE /policy/* |
Admin |
config.changed |
POST /system/*, /resolver/*, /realm/*, /caconnector/* |
Admin |
admin.auth.success / .failure |
POST /auth |
Includes the bootstrap-admin and script logins |
audit.read |
GET /audit/ |
Who read the audit log |
audit.signature_invalid |
Exporter | Never dropped |
privacyidea.other |
Any other action |
Counted and sent, so the action list can be refined |
source.heartbeat |
Exporter | Cursor, max(id), rows_behind |
4.6 Volume, cadence and retention
- Volume [estimate]:
- Average: about 1,300 rows per day. That is 279,209 rows over about 210 days, from deployment on 2026-03-19 to the T20 count on 2026-10-16.
- The rows are probably dominated by automation: MFA-state checks, the guard, probes.
- [unknown] The split between automated calls and human logins. Measure it per
actionbefore setting thresholds.
- Peak: under 10 rows/s.
- Backfill: Exporting the historical rows is a one-off backfill of about 280k rows.
- Proposal: run it in batches under 50 events/s.
- The cursor starts at
id=0. - [unknown] Whether audit-core wants the history or only the rows from activation onwards.
- Heartbeat: every 15 minutes, with
rows_behind. - Allowed silence: 30 minutes. The automated checks make zero rows over several hours anomalous.
- Retention. A longer retention is requested.
- MFA token lifecycle and admin policy changes are the evidence behind privileged-access claims (SI-X4, aal2).
- Proposal: the default 30-day query window plus an archive that keeps signed rows for at least 1 year.
- The source database stays authoritative. [unknown] Its retention and rotation are not configured in the repo today.
5. Prerequisites and owner asks
| # | Item | Owner |
|---|---|---|
| P1 | Pin the lldap/lldap image to a digest |
net-kingdom (or the rapp-* owner after ADR-0015) |
| P2 | Set Authelia log.format: json and a file path the sidecar can read |
net-kingdom |
| P3 | Read-only PostgreSQL role for pidea_audit |
rapp-postgres / railiance-platform with net-kingdom |
| P4 | Pseudonymisation key in OpenBao, and a sender credential for each source | net-kingdom via the attended OpenBao path (warden route) |
| P5 | Traefik JSON access log with traceparent kept (no cookies or auth headers) |
railiance-cluster |
| P6 | Answered 2026-11-07, see below | audit-core |
| P7 | KeyCape sends traceparent on its back-channel calls to Authelia and privacyIDEA |
key-cape |
audit-core answers to P6 (2026-11-07, founder decisions recorded in AUDIT-WP-0017-T04)
- Tenant:
tenant:platformfor all three sources (AUDIT-WP-0020). This corrects audit-core's first answer ofplatform. correlation_origin: indata.correlation_origin, next todata.actoranddata.outcome; the envelope is unchanged. Senders opt in with"correlation_contract": "audit-correlation-v0.1"(audit-core364e723,docs/event-envelope.md, "The correlation contract"). It is not live until the next receiver image.- Signed privacyIDEA rows: option (b). audit-core has no per-subject encrypted payload (R2, no
crypto-shredding). Send pseudonymised
data,subjectandactor, plussignature,native_signature_alg,verification_key_fingerprintand a row hash, never the raw signed columns. The raw row is verified against the source database while it is retained. The legal basis for keeping raw personal data stays with risk-nexus. - Backfill: none. The cursor starts at
max(id)on activation. - Retention: audit-core is append-only but recoverable for 30 days only (
data.backup) and has no archive class (data.archiveunprocured, reviewed 2026-12-31). The privacyIDEA database therefore stays authoritative for the 1-year horizon, and its retention must be at least that long. - Shipper: one shipper and one sender credential per source; audit-core prepares a per-source ingress NetworkPolicy with each overlay.
Still owed by NetKingdom before audit-core prepares overlays: the section 6 unknowns, P1-P5 and P7, then a per-provider registration (exact source, selectors, cadence, heartbeat class).
6. Unknowns to verify before overlays are prepared
- Log content. Exact Authelia 4.39.28 and LLDAP log messages and fields at info level. Capture them on synthetic accounts in a non-production instance.
- Concurrent SQLite reads. Whether Authelia's SQLite storage tolerates a concurrent read-only reader on the RWO PVC without lock contention. The alternative is to move Authelia storage to PostgreSQL.
- Signed-string format. The privacyIDEA signed-string format and its stability across patch releases. Exporter-side verification depends on it.
- Ingress path and logging. Whether KeyCape reaches Authelia and privacyIDEA through Traefik or through cluster Services, and whether Traefik logs request headers on railiance01.
- Traffic split and volume. The real distribution per action and the human-versus-automation split. Every volume figure above is an estimate.
- Password reset. Whether Authelia password reset is enabled. The notifier is configured, but the reset policy is
not set explicitly in
configmap.yaml. - Deployed config. Whether the deployed configuration still matches the repo manifests. ADR-0015 migration and live drift are not checked in this draft.