Revision 1 adds managed records to a dynamic zone by update; revision 2
removes and adds one while records of a third party (also at a managed
name) stay; a revision whose update key named does not accept rolls
back; a revision without managed records deletes only DDI's records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revisions for targets serving dynamic zones as primary carry
ddi-suite-update.key and dynamic-records.json. They stay in the revision
directory. Validate checks both strictly (one key matching the records
file and defined in ddi-suite-keys.conf; zones must be dynamic primary
zones of the revision; names inside their zone; types A/AAAA/PTR/CNAME/
MX/TXT/SRV/NS with presentation data on one line). After the reload,
Activate deletes exactly the records the previous revision managed and
the new one does not, adds all new ones with nsupdate -k (new option
dns.nsupdate), retries while a zone is not loaded yet and fails the
activation otherwise, so that the core rolls back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DNS role needs to feed nsupdate scripts on stdin and to log its
dynamic updates. command.InputRunner adds RunInput (errors never quote the
input); roles implementing core.LogSetter get the core's logger.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent installed without token and enrolled with the command of the web
interface never ran as a service: the installer left the unit disabled and
the root-run enrollment left an identity the service user could not read.
The installer now always enables the agent unit (it waits for the
identity via ConditionPathExists), enroll as root hands the state over to
the service user and enables and starts the unit, and rerunning or
upgrading the installer repairs agents enrolled by hand. The web interface
shows the matching installer command next to the enroll command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kea 3.x rejects clear-text users and passwords in HTTP authentication
("use of clear text 'user' is NOT SECURE"). Control socket clients now
use user-file and password-file, HA peers basic-auth-user-file and
basic-auth-password-file. The new secret artifact ddi-suite-ha-user
sits next to the password file (HAConfig.UserFile, set by derive);
keagen requires the file names ddi-suite-ha-password and
ddi-suite-ha-user. The DHCP agent handles both credential files alike:
consistent references in one directory, validation copy pointing at
the revision's files, written before the configuration, restored on a
failed reload, removed when HA goes away, compared by the health check.
Integration test and e2e read the user from the file; ADR 0023 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
named.conf includes ddi-suite-keys.conf; a revision without keys file
gets one without keys, and a revision with TSIG keys and a secondary
zone whose primary is unreachable deploys while the zone waits for its
transfer. The secondary directory gets the owner and mode of zone_dir.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A DHCP revision may carry ddi-suite-ha-password next to
kea-dhcp4.conf. The role writes it to the one path the configuration
references (control socket clients and HA peers must agree, the file
name must be ddi-suite-ha-password, the directory must exist) before
the configuration, restores it with the configuration when the reload
fails and removes the previous revision's file when HA goes away.
kea-dhcp4 -t checks a temporary copy that refers to the revision's
password file, and the health check notices a changed password file
without showing it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DNS role installs ddi-suite-keys.conf (a keys file without keys for
revisions from before M5, so that named.conf can always include it),
includes it before ddi-suite.conf in the validation named.conf, accepts
secondary zone files in <zone_dir>/secondary/ and creates that
directory with the mode and owner of zone_dir without ever touching its
content. The health check queries secondary zones too; a zone still
waiting for its first transfer is reported but does not fail the
deployment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GetDesiredStateResponse.deployment_id set: the desired revision is
applied as a push with that ID, also in manual mode (ADR 0022).
- The reports of the last 20 push deployments are kept in state.json.
A resent push with a handled deployment ID is not applied again; its
report is delivered again unless it is still queued.
- A push already queued with the same deployment ID is not queued twice.
- Reports the server refuses for good (InvalidArgument, NotFound,
AlreadyExists, FailedPrecondition, OutOfRange) are dropped instead of
blocking the report queue.
- Tests for polling pushes, resent pushes, mode changes via a new
Welcome, persistence and bounds of handled deployments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Docker Hub timed out on the shared runner; pulls now retry and fall back
to a local image. The e2e scripts were committed without the executable
bit. The truncation test checked letters that the marker itself contains.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/e2e runs server, PostgreSQL, BIND and Kea with both agents in
containers (make test-e2e, CI job End-to-end): enrollment with the
command from the API, revision 1 and 2 applied and served by BIND and
Kea, unchanged data creates no revision, revoking disconnects the agent
while BIND keeps serving.
Fixes from the first CI run: quote a spec description containing a
comma, exempt the agent lock-out routes in the route protection test,
whole-second certificate lifetimes, a push replacing queued pulls, the
truncation marker containing the letter checked, and curl|grep -q under
pipefail in the server smoke test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Revoking or deleting an agent and deleting enrollment tokens lock agents
out, a security function that must stay available when the license is
read-only. Translations for the new problem codes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A driver is cross-compiled for the Docker daemon's architecture and
copied with fixture revisions into BIND 9.18/9.20 and Kea 2.6.5/3.2.1
containers (docker cp, no bind mounts or published ports). It runs the
core's pipeline: two revisions apply, broken revisions are rejected,
a failed live check and a failed health check roll back, a failed Kea
reload restores the previous file, and leases added with lease4-add
are listed page by page. make test-integration runs it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate requires exactly kea-dhcp4.conf with this agent's control
socket and runs kea-dhcp4 -t. Activate writes the file atomically and
sends config-reload; on failure the previous file is restored and
reloaded. Health uses status-get (pid, uptime, last reload, socket
state) and checks that the installed file is the expected revision.
Leases pages through lease4-get-page and maps Kea lease states
(default, declined, expired-reclaimed, released) to core lease states.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate maps ddi-suite*.conf to conf_dir and zones/db.<zone> to
zone_dir, rejects other files and a zone directory that differs from
dns.zone_dir, and runs named-checkconf on a temporary named.conf plus
strict named-checkzone per zone. Activate backs up all touched files,
writes zone files then includes atomically (unchanged files are kept),
never touches existing create-only files, removes files dropped since
the previous revision and the stale journal of a zone that became
managed, runs named-checkconf -z on the live named.conf and then
rndc reconfig and rndc reload; failures restore the backup (and reload
again). Health runs rndc status and compares the SOA serial of every
managed zone (UDP with TCP fallback, authoritative answers only).
golang.org/x/net becomes a direct dependency (dnsmessage).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared, role-internal helpers: programs run without a shell, with a
timeout and captured, truncated output quoted in errors; configuration
files are replaced atomically (temp file, fsync, rename, directory sync)
with explicit mode and the owner of the existing file or its directory;
an in-memory backup restores touched files after a failed activation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CLI: enroll, run, status, version, help (core.Main with a role
factory); the agent binaries use a placeholder factory until the
BIND and Kea roles are wired in.
- Enrollment: local ECDSA P-256 key and CSR, TLS 1.3 to the agent API
with the server chain pinned to the CA fingerprint.
- ServerSource: mTLS gRPC with keepalive, Connect stream (Hello,
Welcome, heartbeats, apply and renew commands), reconnect with
exponential backoff and jitter, GetDesiredState polling as fallback,
certificate renewal after half of the lifetime.
- Apply pipeline: fetch, bundle verification with replay protection,
store, Validate, Activate, Health with retries, rollback to the last
good revision; outcomes succeeded/rejected/rolled_back/failed are
queued in state.json and reported; failed revisions are retried only
by a newer revision or an explicit push.
- Lease reporting with change detection, persistent sequence and full
snapshots at start, per connection and periodically.
- Tests with fake roles, a fake source and an in-process fake
AgentService over real mTLS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent configuration (agent-<role>.yaml, strict decoding, role section
passed undecoded to the role factory) and the state directory of ADR
0011: identity with interrupted-renewal recovery, verified revisions
under revisions/<n>/, state.json with apply results, lease sequence and
queued deployment reports, atomic writes, pruning and a lock file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared Apache-2.0 packages for the signed bundle format and the agent
certificate conventions, the agent role interface, REST API spec for
agents, enrollment tokens, revisions, deployments and leases, and ADR
0021. Generator settings now hold the target paths on the managed hosts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- internal/brand: central product, binary and path names (ldflags-overridable)
- internal/buildinfo, internal/logging (slog, secret redaction)
- server: env config, HTTP server with /healthz, /readyz, request IDs,
access log, panic recovery, security headers, graceful shutdown
- web: embedded SPA handler with fallback page when UI is not built
- agent: ddi-agent-dns and ddi-agent-dhcp binaries on a shared core CLI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>