Skip to content

feat(canopy): add unregister command and stop resurrecting device keys#653

Open
passcod wants to merge 2 commits into
mainfrom
canopy-reset-reenrol
Open

feat(canopy): add unregister command and stop resurrecting device keys#653
passcod wants to merge 2 commits into
mainfrom
canopy-reset-reenrol

Conversation

@passcod

@passcod passcod commented Jul 4, 2026

Copy link
Copy Markdown
Member

🤖 Resetting and re-enrolling a canopy server kept hitting a conflict because a leftover device key was being reused across every enrolment. This makes a fresh enrolment use only the new identity, adds a way to wipe every trace of an old one, and removes the DB-backed resurrection path that quietly brought old keys back.

canopy register

  • Refuses when a registration already exists, directing the operator to run canopy unregister first, rather than silently reusing whatever identity is on disk.
  • Always mints a fresh device key. Previously it reused a leftover key from an existing registration, so a re-enrolment would present a stale SPKI and conflict with the server record it was claiming.

canopy unregister (new)

Erases every place a canopy enrolment is stored:

  • the encrypted registration (%ProgramData%\bestool / /etc/bestool, or a --config dir);
  • the legacy plaintext identity files (device-key.pem, server-id);
  • the cached tags (new and legacy locations);
  • the legacy deviceKey / metaServerId rows in local_system_facts, gated on the database being reachable — skipped with a warning otherwise.

Prompts for confirmation, listing every target, with a -y/--yes bypass. Elevates up front to remove root/admin-owned files, matching register.

Stop reading the legacy DB rows

server_info no longer reads deviceKey / metaServerId from local_system_facts; identity now resolves from the registration, then the standard file, and nothing else. This removes the footgun where the daemon copied a stale deviceKey from the DB back onto disk, re-establishing an old identity even after the files were deleted. The rows are still deleted by unregister.

Move cached tags alongside the registration

Tags now cache next to the registration instead of in C:\Tamanu / /etc/tamanu. The legacy location is read as a fallback so a host that hasn't refreshed since the move keeps its cached tags until the next online fetch rewrites them.

Restart the daemon on an identity change

The daemon reads the device key and server id once at startup and caches them for its lifetime, so enrolling or unenrolling only takes effect once it restarts. register and unregister now ask a running daemon to restart via the existing /restart control endpoint, so it picks up the new identity (or drops the removed one) without a manual restart. Best-effort: a daemon that isn't running is fine — it reads the registration afresh on its next start. Only wired up when the daemon is built into the binary.

Notes

  • Replaced the DB-backed get_or_create_server_id tests with file-based equivalents (no database needed now) and removed the write_device_key_file tests along with the function; added coverage for registration::delete_in and the tags legacy-fallback.

passcod and others added 2 commits July 5, 2026 03:15
canopy register now refuses when a registration already exists, telling the
operator to run canopy unregister first, and always mints a fresh device key
rather than reusing a leftover one — so a re-enrolment presents only the new
identity and can't conflict with the server record it's claiming.

canopy unregister erases every trace of an enrolment: the encrypted
registration, the legacy Tamanu device-key.pem/server-id files, the cached
tags, and (when postgres is reachable) the legacy deviceKey/metaServerId rows
in local_system_facts. Confirmation prompt with a -y/--yes bypass.

The legacy deviceKey/metaServerId DB rows are no longer *read*: server_info
resolves identity from the registration then the standard files only, removing
the footgun where alertd copied a stale deviceKey back onto disk. The rows are
still deleted by unregister.

Cached tags now live alongside the registration (%ProgramData%\bestool /
/etc/bestool); the legacy C:\Tamanu / /etc/tamanu location is read as a
fallback until the next online refresh.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The daemon reads the device key and server id once at startup and caches them
for its lifetime, so enrolling or unenrolling only takes effect on restart.
register and unregister now ask a running daemon to restart via the existing
/restart control (best-effort: a daemon that isn't running is fine, it reads
the registration afresh on its next start). Only wired up when the daemon is
built into the binary.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@passcod passcod force-pushed the canopy-reset-reenrol branch from e0b3bd6 to 3c6bca7 Compare July 4, 2026 18:07
@passcod passcod added this pull request to the merge queue Jul 4, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant