Reject empty strings for required secrets #160

Merged
Alex merged 2 commits from 137-required-secret-empty into main 2026-11-12 11:15:46 +01:00
Owner

Problem

  • A required secret (secret:"required") refused only JSON null. A present "" was stored as "no value", so PUT /vpn/wireguard with "private_key": "" removed the stored key. The tunnel kept running on the kernel key and failed only at its next re-creation, for example after a reboot (#137).
  • A complete import or a first-boot TOML template could stage a WireGuard tunnel without a private key.

Change

  • ApplySecretInput: a required secret refuses "" like null (400, <path>: is required and cannot be cleared). Untagged secrets keep "" = none. The BGP password (nonempty) is unchanged.
  • WireGuard admission (validateWireGuardTarget) refuses a tunnel without a private key with vpn.wireguard[N].private_key: private_key is required. Startup and rollback still apply a stored state without a key; the check sits only in admission, because the runtime plan validation also runs on startup, rollback and recovery.
  • ADR 0022: one bullet for this rule.
  • openapi.yaml: 400 on the WireGuard writes, and the rule on the required secret fields.
  • TOML: no model or schema change; import refuses a keyless tunnel with path and line.

Validation

  • GOFLAGS=-p=2 go vet ./pkg/models/... ./internal/service/... ./internal/api/handlers/...: no findings.
  • New tests failed on the unfixed code and pass now: TestApplySecretInputRejectsEmptyRequiredSecret, TestSecretContractRejectsEmptyRequiredPrivateKey, TestProvisioningImportRejectsWireGuardTunnelWithoutPrivateKey, TestWireGuardTargetRequiresPrivateKey.
  • Related secret, WireGuard, provisioning and admission tests pass (named -run sets).
  • Note: a router whose stored state already holds a keyless tunnel gets draft review and activation refused until a key is set with PUT /vpn/wireguard/{iface}.

Closes #137

🤖 Generated with Claude Code

## Problem - A required secret (`secret:"required"`) refused only JSON `null`. A present `""` was stored as "no value", so `PUT /vpn/wireguard` with `"private_key": ""` removed the stored key. The tunnel kept running on the kernel key and failed only at its next re-creation, for example after a reboot (#137). - A complete import or a first-boot TOML template could stage a WireGuard tunnel without a private key. ## Change - `ApplySecretInput`: a required secret refuses `""` like `null` (400, `<path>: is required and cannot be cleared`). Untagged secrets keep `""` = none. The BGP password (`nonempty`) is unchanged. - WireGuard admission (`validateWireGuardTarget`) refuses a tunnel without a private key with `vpn.wireguard[N].private_key: private_key is required`. Startup and rollback still apply a stored state without a key; the check sits only in admission, because the runtime plan validation also runs on startup, rollback and recovery. - ADR 0022: one bullet for this rule. - `openapi.yaml`: 400 on the WireGuard writes, and the rule on the required secret fields. - TOML: no model or schema change; import refuses a keyless tunnel with path and line. ## Validation - `GOFLAGS=-p=2 go vet ./pkg/models/... ./internal/service/... ./internal/api/handlers/...`: no findings. - New tests failed on the unfixed code and pass now: `TestApplySecretInputRejectsEmptyRequiredSecret`, `TestSecretContractRejectsEmptyRequiredPrivateKey`, `TestProvisioningImportRejectsWireGuardTunnelWithoutPrivateKey`, `TestWireGuardTargetRequiresPrivateKey`. - Related secret, WireGuard, provisioning and admission tests pass (named `-run` sets). - Note: a router whose stored state already holds a keyless tunnel gets draft review and activation refused until a key is set with `PUT /vpn/wireguard/{iface}`. Closes #137 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A required secret such as the WireGuard private key accepted "" in a
write. The request then stored no key, and the next re-creation of the
tunnel failed. ApplySecretInput now refuses a present empty string for a
required field like null, with the same field error.

WireGuard admission also refuses a tunnel without a private key, so a
complete import or a first-boot template cannot stage one either.
Startup and rollback still apply a stored state without a key.

Closes #137

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #137

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alex merged commit 95faa5189e into main 2026-11-12 11:15:46 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Alex/kaido-core!160
No description provided.