refactor(store): wave B.19 — lps + luks repos (#281)#282
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughThe PR adds LPS and LUKS repository contracts and Postgres implementations, wires them into Repos, and migrates handlers to call the new repo APIs for listing current/history records, action-targeted LUKS retrieval, token create/consume, and revocation stream lookup. ChangesLPS and LUKS repository migration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
internal/store/luks.go (1)
10-41: ⚡ Quick winAdd redaction guardrails for secret-bearing structs.
LuksKey.PassphraseandLuksToken.Tokenare plaintext by design, but these structs can be leaked via generic struct logging. Consider adding redactedString()implementations (and using them in logs) to reduce accidental secret exposure.🔒 Example hardening
+func (k LuksKey) String() string { + return fmt.Sprintf("LuksKey{ID:%s DeviceID:%q ActionID:%q DevicePath:%q Passphrase:[REDACTED] IsCurrent:%t}", + k.ID, k.DeviceID, k.ActionID, k.DevicePath, k.IsCurrent) +} + +func (t LuksToken) String() string { + return fmt.Sprintf("LuksToken{ID:%s DeviceID:%q ActionID:%q Token:[REDACTED] Used:%t ExpiresAt:%s}", + t.ID, t.DeviceID, t.ActionID, t.Used, t.ExpiresAt.Format(time.RFC3339)) +}🤖 Prompt for 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. In `@internal/store/luks.go` around lines 10 - 41, LuksKey and LuksToken contain plaintext secrets (LuksKey.Passphrase and LuksToken.Token) and can be accidentally exposed by generic struct logging; implement redacted String() methods for type LuksKey and LuksToken that return the struct representation with Passphrase and Token replaced by a constant redaction marker (e.g., "<redacted>") and ensure any existing log sites use the String() method (or fmt.Stringer) rather than printing the raw struct; update tests or add unit tests asserting String() output hides secrets and preserves other fields.
🤖 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 `@internal/api/device_handler.go`:
- Line 872: The CreateLuksToken call writes token state directly without
recording an event; change the handler to append an event after (or instead of)
the direct mutation by invoking store.AppendEvent with an event type like
"luks.token.created" and include the token details (DeviceID, ActionID, Token,
MinLength, Complexity) and actor/metadata from the request/context, then persist
the mutation through the repository only via the normal event-driven path (or
ensure the repo change and AppendEvent are atomic/inside the same transaction).
Update the code around h.store.Repos().Luks.CreateToken and ensure
store.AppendEvent is called (or used as the primary mutation) so every token
minting follows the required event-append contract.
In `@internal/api/internal_handler.go`:
- Line 248: The handler currently calls h.store.Repos().Luks.ConsumeToken(...)
which mutates durable state but does not append an event; update the handler so
that after a successful call to ConsumeToken (store.ConsumeLuksTokenParams /
h.store.Repos().Luks.ConsumeToken) you also call h.store.AppendEvent(...) to
record the mutation (e.g., an event like "LuksTokenConsumed" including Token and
DeviceID and any actor/context), and ensure AppendEvent error handling is
performed and surfaced; keep the AppendEvent invocation in the same handler flow
so every mutation done by ConsumeToken has a corresponding store.AppendEvent.
---
Nitpick comments:
In `@internal/store/luks.go`:
- Around line 10-41: LuksKey and LuksToken contain plaintext secrets
(LuksKey.Passphrase and LuksToken.Token) and can be accidentally exposed by
generic struct logging; implement redacted String() methods for type LuksKey and
LuksToken that return the struct representation with Passphrase and Token
replaced by a constant redaction marker (e.g., "<redacted>") and ensure any
existing log sites use the String() method (or fmt.Stringer) rather than
printing the raw struct; update tests or add unit tests asserting String()
output hides secrets and preserves other fields.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ea91479f-ae26-43dc-9369-54221f043d9e
📒 Files selected for processing (9)
internal/api/device_handler.gointernal/api/internal_handler.gointernal/control/inbox_worker.gointernal/store/lps.gointernal/store/luks.gointernal/store/postgres/lps.gointernal/store/postgres/luks.gointernal/store/postgres/wire.gointernal/store/repos.go
| MinLength: minLength, | ||
| Complexity: complexity, | ||
| }) | ||
| _, err = h.store.Repos().Luks.CreateToken(ctx, store.CreateLuksTokenParams{DeviceID: req.Msg.DeviceId, ActionID: req.Msg.ActionId, Token: token, MinLength: minLength, Complexity: complexity}) |
There was a problem hiding this comment.
Minting the LUKS token still bypasses store.AppendEvent.
CreateLuksToken is a mutation, but this path writes token state directly through the repo and returns success without recording an event. That leaves the token lifecycle outside the normal handler audit/rebuild contract.
As per coding guidelines "Every mutation must append an event via store.AppendEvent."
🤖 Prompt for 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.
In `@internal/api/device_handler.go` at line 872, The CreateLuksToken call writes
token state directly without recording an event; change the handler to append an
event after (or instead of) the direct mutation by invoking store.AppendEvent
with an event type like "luks.token.created" and include the token details
(DeviceID, ActionID, Token, MinLength, Complexity) and actor/metadata from the
request/context, then persist the mutation through the repository only via the
normal event-driven path (or ensure the repo change and AppendEvent are
atomic/inside the same transaction). Update the code around
h.store.Repos().Luks.CreateToken and ensure store.AppendEvent is called (or used
as the primary mutation) so every token minting follows the required
event-append contract.
| Token: req.Msg.Token, | ||
| DeviceID: req.Msg.DeviceId, | ||
| }) | ||
| token, err := h.store.Repos().Luks.ConsumeToken(ctx, store.ConsumeLuksTokenParams{Token: req.Msg.Token, DeviceID: req.Msg.DeviceId}) |
There was a problem hiding this comment.
Consuming the one-time token also bypasses store.AppendEvent.
ConsumeToken burns durable state, but this handler mutation still happens entirely through the repo with no event appended. That leaves the destructive half of the token lifecycle outside the handler audit trail as well.
As per coding guidelines "Every mutation must append an event via store.AppendEvent."
🤖 Prompt for 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.
In `@internal/api/internal_handler.go` at line 248, The handler currently calls
h.store.Repos().Luks.ConsumeToken(...) which mutates durable state but does not
append an event; update the handler so that after a successful call to
ConsumeToken (store.ConsumeLuksTokenParams / h.store.Repos().Luks.ConsumeToken)
you also call h.store.AppendEvent(...) to record the mutation (e.g., an event
like "LuksTokenConsumed" including Token and DeviceID and any actor/context),
and ensure AppendEvent error handling is performed and surfaced; keep the
AppendEvent invocation in the same handler flow so every mutation done by
ConsumeToken has a corresponding store.AppendEvent.
Two repos for the secret-rotation domains that survived the projector port: LpsRepo (lps_passwords_projection): ListCurrent / ListHistory LuksRepo (luks_keys_projection + luks_tokens): ListCurrent / ListHistory / GetCurrentForAction / CreateToken / ConsumeToken / GetRevocationStreamID Decrypted secrets (Password, Passphrase) cross the boundary as plain strings — caller-side rule: treat as sensitive material, don't log. GetCurrentForAction serves the gateway-proxy retrieval flow. CreateToken/ConsumeToken implement the one-shot retrieval-token mint+burn pattern. GetRevocationStreamID lets the inbox worker append terminal events to the same stream as the original revocation request so the three-phase projection stitches together. Non-goals: - SecurityAlertRepo — only callers are the projector listener (InsertSecurityAlertProjection, AcknowledgeSecurityAlertProjection). Pure projector internals; stays on Queries() until the projector-write wave (F). - UpdateLuksKeyRevocationStatus — projector listener write. Same. Call sites migrated: - device_handler.go (LPS+LUKS reads + CreateToken) - internal_handler.go (GetCurrentForAction ×2 + ConsumeToken) - control/inbox_worker.go (GetRevocationStreamID) Closes #281.
4ca091f to
e3c4c3a
Compare
Summary
Two repos for the secret-rotation domains:
`LpsRepo` (lps_passwords_projection): `ListCurrent`, `ListHistory`.
`LuksRepo` (luks_keys_projection + luks_tokens):
Decrypted secrets cross the boundary as plain strings — treat as sensitive in callers.
Non-goals
Call sites migrated
Acceptance
Part of #242. Closes #281.
Summary by CodeRabbit