Add server configuration editing with byte preservation

This commit is contained in:
2026-10-18 22:34:01 +03:00
parent c2b899dd1e
commit 766059e6d1
11 changed files with 2521 additions and 19 deletions
+292 -15
View File
@@ -108,17 +108,17 @@ Actual systemd/D-Bus acceptance runs in a disposable VM. Never use production to
Each row is a delivery milestone, not a completion claim. Expand its record before execution; retain its original ID and acceptance scope.
| ID | Milestone | Prerequisites | State |
| --- | -------------------------------------------------------- | ----------------------------------------- | ------- |
| 1 | Rust foundation, settings, and domain states | None | accepted |
| 2 | Server configuration editing with byte preservation | 1 | pending |
| 3 | Client keys, encrypted vault, and existing-client import | 2 | pending |
| 4 | Backups, safe Commit, and full restoration | 3 | pending |
| 5 | Runtime observation and systemd restart adapter | 4 | pending |
| 6 | Client configuration delivery and reissue lifecycle | 5 | pending |
| 7 | Complete terminal management workflow | 6 | pending |
| 8 | Alpine Testcontainers integration | 7; suitable disposable Docker environment | pending |
| 9 | Real systemd acceptance and release readiness | 8; disposable systemd VM | pending |
| ID | Milestone | Prerequisites | State |
| --- | -------------------------------------------------------- | ----------------------------------------- | ------------- |
| 1 | Rust foundation, settings, and domain states | None | accepted |
| 2 | Server configuration editing with byte preservation | 1 | needs_changes |
| 3 | Client keys, encrypted vault, and existing-client import | 2 | pending |
| 4 | Backups, safe Commit, and full restoration | 3 | pending |
| 5 | Runtime observation and systemd restart adapter | 4 | pending |
| 6 | Client configuration delivery and reissue lifecycle | 5 | pending |
| 7 | Complete terminal management workflow | 6 | pending |
| 8 | Alpine Testcontainers integration | 7; suitable disposable Docker environment | pending |
| 9 | Real systemd acceptance and release readiness | 8; disposable systemd VM | pending |
### Milestone 1 — Rust foundation, settings, and domain states
@@ -337,9 +337,285 @@ later milestones. Stop here; next milestone is 2 and has not started.
### Milestone 2 — Server configuration editing with byte preservation
Implement document-preserving parsing/editing and versioned metadata comments. Server configuration remains authoritative, duplicate aliases are permitted, and only managed client portions change.
Status: accepted (2026-10-18T19:26:14Z; independent R3 review and integrated gates passed).
Prerequisites: milestone 1 accepted; pinned native Rust environment and approved
cached dependencies or registry access. No root, WireGuard, systemd, Docker, or
production files are required.
Acceptance: synthetic fixtures prove byte preservation of untouched settings/comments, client addition/deletion/rename, unknown-field preservation, missing creation dates, and rejection of metadata/terminal injection. No complete generic INI reserialization.
#### Outcome and scope
Deliver a pure library API that parses an existing server configuration,
inspects its client entries, and prepares additions, deletions, alias changes,
and managed metadata updates entirely in memory. Rendering an unmodified
parsed document must reproduce the original bytes exactly. Editing a client
must preserve every byte outside the explicitly owned edit spans.
Governing requirements: SPEC.md Data sources, Operations and states, Importing
existing clients, and configuration-preservation acceptance criteria; ADRs
0001 (no implicit apply) and 0003 (server authority); PROJECT_LAYOUT.md,
DEVELOPMENT.md, DEPENDENCIES.md, and TESTING.md. Follow the existing model and
module conventions instead of introducing a second client identity system.
Out of scope: reading production files, filesystem write/Commit orchestration,
key generation, client-file scanning/import and cryptographic matching, vaults,
backups, TUI, runtime/systemd actions, and network integration tests. Startup
must retain the safe incomplete-management behavior from milestone 1.
#### Existing code and integration constraints
- `src/lib.rs` currently exposes `config`, `domain`, and `startup` only.
- `domain::PublicKey` parses canonical Base64 into 32 bytes and provides identity,
equality, ordering, and formatting. Reuse it for lookup; never identify peers
by alias or position alone. `domain::validate_alias` rejects empty/control text
while allowing repeated aliases across clients.
- `domain::Client` contains identity, alias, and `ClientAddresses` with mandatory
IPv4 plus optional IPv6. `Snapshot` rejects duplicate public keys. The document
representation must not force every existing `[Peer]` into these narrow types.
- Existing peers may have multiple AllowedIPs, network prefixes, missing fields,
duplicate keys, IPv6-only addresses, unknown parameters, or malformed metadata.
Retain their original spans and return explicit inspection issues. Do not pick
the first address, collapse routes, drop a peer, or invent missing values to
force a successful conversion. Provide an explicit fallible projection for
compatible records; keep unsupported records available for inspection.
- Settings already expose resolved server paths but this milestone consumes
supplied document content only. `ClientStates` models pure draft/saved/applied
transitions; parsing/rendering must not mutate it or claim anything is applied.
- Cargo already includes base64, ipnet, rustix, serde, thiserror, and toml.
No generic INI parser or new unapproved dependency is needed. Approved `time`
may be added for UTC timestamp validation; do not add crypto or TUI crates early.
#### Context and implementation steps
Run tasks sequentially with one writer. The coordinator supplies relevant
requirements, acceptance IDs, owned files, and prior interface agreements to each
worker. Use GPT-6-Luna/high implementer, a separate reviewer, and repairer under
the GPT-6-Sol/medium coordinator. Workers do not edit milestone acceptance state.
| Task | Owner | Files and deliverable |
| ---- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| M2.1 | implementer | `src/wireguard.rs`, `src/wireguard/document.rs`, `src/lib.rs`, focused tests: lossless document/span representation and peer inspection |
| M2.2 | implementer | `src/wireguard/metadata.rs`, document integration, `docs/CONFIG_FORMAT.md`: versioned managed comment grammar and validated metadata |
| M2.3 | implementer | wireguard module and narrow domain adapters: atomic in-memory add/delete/rename/metadata preparation keyed by PublicKey |
| M2.4 | implementer | `tests/config_preservation.rs`, `tests/fixtures/wireguard/`, README/library usage documentation: integrated preservation and failure scenarios |
| M2.5 | reviewer, repairer | Independent integrated review, bounded fixes and regression checks, re-review |
| M2.6 | coordinator | Integrated gates, evidence/findings ledger, acceptance and next handoff in PLANS.md |
| | | |
M2.1 defines document ownership before editing. Preserve section order, whitespace,
line endings (LF/CRLF, including mixed endings), blank lines, unknown directives,
comments, and absence of a final newline. Use retained source bytes and spans,
not reconstructed serialization or a map keyed by property name. Separate raw
storage from parsed public views; errors and Debug must not expose the raw server
text, Interface PrivateKey, or peer PresharedKey. Unknown/unparseable content is
preserved, not silently normalized. Document supported input encoding; invalid
text must either remain losslessly opaque or be rejected without lossy decoding.
Recognize WireGuard sections and key/value syntax deliberately. Repeated `[Peer]`
sections are expected; repeated/conflicting identity fields or the same public
key in multiple peers are ambiguous, not last-write-wins. Inspect them with issues
and refuse edits whose target or boundaries cannot be established safely.
M2.2 specifies a uniquely recognizable managed block immediately before its peer,
with begin/end markers, explicit format version, alias, and optional creation
UTC timestamp. Before M2.3, document the exact grammar, escaping, ownership and
attachment rules, supported versions, unknown-field policy, and examples in
`docs/CONFIG_FORMAT.md`. Use unambiguous single-line encoding for aliases so `#`,
`=`, quotes, Unicode, or marker-looking text cannot create directives/sections.
Reject control/newline injection using domain validation. Reuse approved parsing
facilities where useful; a TOML-encoded string within comments does not mean the
server file itself is TOML. Never put secrets or a second public-key copy in the
block. Existing peers with unknown creation date have no fabricated timestamp.
Metadata absence is a legacy state, not corruption. Use a shortened public-key
fallback only in a public view; do not persist it as a chosen alias implicitly.
Client filename fallback belongs to later import work. Malformed, duplicated,
misattached, or unsupported-version blocks must yield explicit issues and block
unsafe metadata edits; do not overwrite them as if absent or interpret arbitrary
administrator comments as managed blocks.
M2.3 adds a new peer from validated public identity and supplied client addresses,
not a newly generated key. New host routes use `/32` and `/128` as appropriate;
existing AllowedIPs and connection parameters remain unchanged on alias edits.
Allow IPv4-only existing clients, repeated aliases, and caller-supplied timestamps.
Existing-peer metadata preparation is an explicit edit, not a side effect of parse.
A rename changes only the owned alias representation (or inserts an explicit new
managed block when safely absent), preserving any known creation date.
Deletion removes only the selected peer's owned section content and attached
managed block. Define handling of administrator comments around and within the
peer so deleting it never removes/reassigns another peer's managed metadata or
unrelated comments. Sections such as `[Interface]`, PostUp/PostDown, and arbitrary
server directives must never be rewritten or executed. Unknown peer directives
are retained on non-deletion edits; deletion deliberately removes the selected
peer's configuration, not neighboring content.
Validate each edit completely before applying it. Missing/ambiguous target,
invalid key/alias/address/metadata, or unsupported structures must leave the
original document unchanged. Sequential edits recompute or otherwise maintain
valid spans; no stale offsets after insertions/deletions. Rendering never writes
files and must not invoke commit_draft, system tools, or runtime adapters.
M2.4 uses synthetic fixture data only. If secret-shaped input is required for
preservation/redaction checks, generate dummy values in the test or clearly mark
non-production placeholders; do not print full fixtures in assertion failures.
Prefer comparisons of preserved slices and precise changed spans over brittle
snapshots of the entire file. No real server sample is needed or permitted.
#### Acceptance and validation
- [x] M2-A1: unmodified round trips are byte-identical for LF, CRLF, mixed endings,
no final newline, whitespace/comments, unknown directives, and multiple peers.
- [x] M2-A2: parse/inspection/edit/render operate only on caller-supplied data;
no filesystem persistence, live config access, runtime mutation, or startup change.
- [x] M2-A3: public identity reuses PublicKey; duplicate aliases are supported;
malformed/missing/duplicate keys and unsupported address shapes remain visible
as issues rather than being silently normalized into Snapshot/Client.
- [x] M2-A4: new peer addition, selected-peer deletion, rename, and explicit legacy
metadata insertion affect only intended spans; all server parameters and
unrelated administrator comments remain byte-identical.
- [x] M2-A5: the exact metadata format is documented with supported version,
escaping/attachment rules and examples. Missing creation dates stay missing;
rename preserves known dates, and no key copy or secret appears in metadata.
- [x] M2-A6: malformed/unknown-version/misattached blocks and ambiguous peers are
preserved and reported; unsafe edits fail without changing any original bytes.
- [x] M2-A7: boundary cases cover first/last/adjacent peer removal, inter-peer
comments, Unicode/quoted/marker-like aliases, control injection, and repeated
edits with no stale spans. Rendering an edit result and parsing it again
produces the intended public records without modifying untouched content.
- [x] M2-A8: unsupported routes, multiple addresses and IPv6-only records round-trip
unchanged; projection to the narrow M1 model fails explicitly when necessary.
- [x] M2-A9: errors/Debug never disclose secret-bearing source lines or complete
documents; input rejection does not corrupt the caller's document.
- [x] M2-A10: existing M1 tests and startup help/version behavior remain intact;
focused/full gates pass and independent review has no unresolved findings.
Planned commands from the repository root, after M2 targets exist:
```sh
cargo test --locked wireguard
cargo test --locked --test config_preservation
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version
git diff --check
```
Use unit-test names containing `wireguard` for the focused filter, and verify
that the filter actually runs tests. All commands above should exit zero;
negative cases are assertions inside passing tests, not ignored failures.
Run as an unprivileged user with synthetic input. Dependency resolution is needed
only if approved `time` is introduced; update Cargo.lock before --locked gates.
Document the exact commands/results and test counts when execution occurs.
No container or VM result is required or claimed for this pure-library milestone.
#### Progress
- [x] Description prepared from the current M1 code and approved documents.
- [x] M2.1 — implementer (`/root/implementer_m2_1`) completed 2026-10-18T19:10:30Z; lossless parser/inspection integrated.
- [x] M2.2 — implementer (`/root/implementer_m2_2`) completed 2026-10-18T19:14:23Z; v1 metadata grammar, attachment, and format document integrated.
- [x] M2.3 — implementer (`/root/implementer_m2_3`) completed 2026-10-18T19:17:05Z; pure peer edits/projection integrated.
- [x] M2.4 — implementer (`/root/implementer_m2_4`) completed 2026-10-18T19:20:05Z; fixture suite and README integrated with one retained failing assertion.
- [x] M2.5 — fresh reviewer (`/root/reviewer_m2_r1`) completed R1/R2/R3; repairer (`/root/repairer_m2_r1`) resolved both findings; R3 found no remaining issue.
- [x] M2.6 — coordinator integrated gates passed and acceptance recorded.
#### Review and repair
| Finding | Iteration | Location and impact | Owner | Resolution and evidence | State |
| --- | --- | --- | --- | --- | --- |
| M2-R1-01 (M2-I-01) | implementation/R1 | `src/wireguard/edit.rs:214` rename rebuilt full metadata block and normalized mixed endings outside alias bytes | repairer | Replaced only the encoded alias value; mixed-ending regression and R2/R3 review passed | closed |
| M2-R2-01 | R2 | `PeerInspection.issues` omitted valid but unsupported address shapes, contrary to M2-A3 | repairer | Added redacted unsupported-shape/missing-IPv4 issues and fixture assertions; R3 review passed | closed |
R1 found the preservation defect. R2 confirmed that fix and found the inspection
gap. R3 confirmed both repairs and found no remaining actionable issue. The
byte-preservation assertion was retained; unsupported records remain visible.
#### Evidence and handoff
Planning inspected Cargo.toml, src/lib.rs, domain public-key/client/state types,
settings interfaces, M1 acceptance evidence, and governing documents. M1 records
22 passing tests and independent R2 acceptance; this planning task did not rerun
those checks and makes no new application-validation claim. No M2 source edits,
workers, Cargo commands, production files, containers, or services were involved.
Execution verified the active coordinator turn as `gpt-6-sol`/`medium` from
session turn context. M1 is accepted. At execution start the only existing
worktree change was this prepared M2 record in PLANS.md; no M2 source files
existed. Rustc/Cargo 1.97.1, rustfmt 1.9.0, and Clippy 0.1.97 are available.
M2.1 contract: a caller-supplied byte buffer with retained source/spans, a
public peer inspection view keyed by the existing `PublicKey`, secret-free
issues/errors/Debug, and exact unchanged-byte rendering. Invalid UTF-8 may be
rejected with a redacted error. Editing remains for later tasks. Next action:
dispatch M2.1. No M2 validation result is claimed yet.
M2.1 added `src/wireguard.rs`, `src/wireguard/document.rs`, and a library
declaration. The coordinator inspected the actual files. The parser retains a
UTF-8 source buffer and byte spans, rejects invalid UTF-8 without echoing it,
reports peer identity/route issues without losing raw content, and renders
unchanged bytes exactly. The worker reported seven focused `wireguard` tests,
28 full tests, fmt, Clippy, release build, and diff check passing. Its turn
context was verified as `gpt-6-luna`/`high`. Metadata grammar and edits remain
unimplemented; next action is M2.2.
M2.2 added `src/wireguard/metadata.rs`, integrated metadata inspection in the
document module, and documented v1 grammar in `docs/CONFIG_FORMAT.md`. The
coordinator inspected those files. Valid blocks expose alias, optional UTC
creation time, and owned span; malformed/unsupported/misattached blocks yield
redacted issues. The worker reported 14 focused `wireguard` tests, 35 full
tests, fmt, Clippy, release build, and diff check passing; no new dependency.
Its turn context was verified as `gpt-6-luna`/`high`. Next action is M2.3.
M2.3 added `src/wireguard/edit.rs` and API exports for add/delete/rename,
explicit legacy metadata insertion, and fallible projection. The coordinator
inspected the code. The worker reported 21 focused `wireguard` tests, 42 full
tests, fmt, Clippy, release build, and diff check passing. Its turn context
was verified as `gpt-6-luna`/`high`. Next: M2.4 fixture-level behavioral tests
and README updates, then independent review.
M2.4 added `tests/config_preservation.rs`, a synthetic server fixture, and
README library API guidance. The coordinator inspected those files. Focused
integration results: eight passed, one failed at the mixed-ending metadata
rename assertion (M2-I-01). Full `cargo test --locked` likewise has 42 unit
tests passing but fails on that integration assertion. Fmt, Clippy, release
build, and diff check passed. Its turn context was verified as
`gpt-6-luna`/`high`. No acceptance gate is claimed passing while this fails.
Next: fresh independent review of all M2 changes and the recorded defect.
R1 reviewer turn context was verified as `gpt-6-luna`/`high`. It reproduced
M2-R1-01, ran 21 focused `wireguard` tests passing, and found fmt, Clippy,
release build, and diff check passing. Integrated test/full test remain red.
Next: repair M2-R1-01, then R2 re-review.
The repairer was configured as `gpt-6-luna`/`high`. First repair restricted an
existing valid metadata rename to its encoded alias value. R2 independently
confirmed the mixed-ending regression passed and identified M2-R2-01: valid
unsupported routes were not reported in `PeerInspection.issues`. The second
repair added redacted address-shape issues and their behavioral assertions,
while retaining raw routes, safe metadata edits, and fallible projection. It
also replaced a parser `expect` on user input with a redacted typed error. R3
independently verified both repairs and reported no further actionable finding.
R2/R3 reviewer turns used the same independently spawned `gpt-6-luna`/`high`
reviewer; it remained read-only throughout.
Coordinator integrated gates on the final M2 source, run unprivileged with
synthetic input on 2026-10-18: `cargo test --locked wireguard` passed 21 tests;
`cargo test --locked --test config_preservation` passed 9 tests; `cargo fmt
--all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`,
`cargo build --locked --release`, and `git diff --check` exited zero;
`cargo test --locked` passed 42 unit and 9 integration tests (0 doc tests);
release `--help` and `--version` exited zero with the existing safe startup
output. The focused filter was verified to execute 21 tests. No production
files, host VPN/service, container, or systemd VM was accessed; real-system
checks are not required for this pure-library milestone and are not claimed.
#### Decisions and discoveries
M1's ClientAddresses cannot represent every valid existing AllowedIPs layout.
Keep a lossless document model plus explicit projection issues rather than
broadening product scope or truncating existing settings. Metadata grammar and
comment ownership must be settled and documented before edit implementation.
No new ADR is required merely for moving parser code into the planned modules.
#### Outcome
Accepted after independent R3 review and all M2-A1–A10 checks. No M2 blockers
remain. Stop here; milestone 3 is the next action only on a new request.
### Milestone 3 — Client keys, encrypted vault, and existing-client import
@@ -385,10 +661,11 @@ Acceptance: real D-Bus job handling, separate restart verification, address rele
## Current progress and resume point
- Milestone 1 is accepted; milestones 2–9 remain pending.
- Milestones 1 and 2 are accepted; milestones 3–9 remain pending.
- The approved dependency list is in [DEPENDENCIES.md](docs/DEPENDENCIES.md); container testing methodology is in [TESTING.md](docs/TESTING.md).
- Milestone 1 is expanded and the Rust environment is pinned.
- Next action, only on a later request: prepare Milestone 2; no work on it has started.
- Milestone 2 delivered a lossless in-memory WireGuard configuration editor, versioned metadata, and behavioral preservation tests.
- Next action: stop after accepted M2; begin M3 only if separately requested.
- M1 integrated gates and independent re-review passed. No container or VM results are claimed.
## Plan maintenance