Skip to content

Customer-held signing keys

Every job a host runs is signed with your organization’s key, and the agent refuses anything else. Normally that key lives in Google Cloud KMS and Updawg signs approved jobs with it. With a customer-held key, you hold it: our key endorses yours once, and from then on Updawg signs nothing. Each approved job waits until you sign it in your own environment, and a job you did not sign is one no host will run — whatever happens to Updawg.

The cost is that the key is yours to keep. If you lose it, Updawg cannot recover it: every host has to be re-enrolled. Keep a copy somewhere safe, or keep it in an HSM.

Where the key will live — a workstation, a CI runner, a machine in front of your HSM, never a managed host:

Terminal window
updawgctl keygen --out org.key

It writes the private key to org.key, readable only by you, and prints the public key. updawgctl is in the agent package; any Linux machine with it installed will do, enrolled or not. A key you made with openssl genpkey -algorithm ed25519 works too.

Under Settings → Signing keys, an admin or owner chooses Use your own key. The dialog shows your organization’s id; prove you hold the key:

Terminal window
updawgctl prove-key --org org_… --key org.key

and paste the line it prints. Updawg checks the proof — a hand-over to a key nobody holds cannot be undone, so a mistyped key fails here — and our key endorses yours. A few seconds later yours is the active key and ours is retiring.

Existing hosts move over within minutes: Updawg asks each one to renew its certificate, and it picks up your key after checking our endorsement of it. Agents released before 2026-10-11 move at their next scheduled renewal instead, within a month. sudo updawgctl renew on a host does it at its next check-in. Until a host has moved, it is still sent jobs signed with our key.

For hosts you enrol from now on, set the key in /etc/updawg/agent.toml, or pass it to the installer:

Terminal window
curl -fsSL https://get.updawg.net | sudo UPDAWG_TOKEN=enr_… UPDAWG_ORG_KEY=MCowBQYDK2VwAyEA… sh

A host with trusted_org_key set pins only your key and keys it endorsed. If the server offered it anything else, it would refuse to enrol and write nothing. You can add trusted_org_key to existing hosts too; sudo updawgctl status says whether the host has pinned it yet.

Approving a proposal now makes jobs that wait for your signature: the portal shows them as Awaiting your signature, and the Signing keys card says how many there are. Sign them with an API token that has the read and approve scopes:

Terminal window
export UPDAWG_API_TOKEN=upd_…
updawgctl sign --org acme --key org.key

For each job it shows what the job will do — the host, the exact package versions, whether it may reboot — decoded from the bytes it is about to sign, and asks. It signs only jobs for your key, checks each signature before sending it, and the host verifies it again before running anything.

To sign on a schedule, add --yes. To sign with an HSM or a cloud KMS instead of a key file, give a command that reads the bytes to sign on stdin and prints the Ed25519 signature:

Terminal window
updawgctl sign --org acme --yes \
--command 'my-hsm-sign --key updawg-org' \
--public-key MCowBQYDK2VwAyEA…

--yes on a schedule signs every job waiting for your key, so anyone who can get a proposal approved gets your signature. A policy file narrows that to what you decided in advance; anything outside it is left waiting for a person, and the run says why:

sign-policy.toml
# Job kinds it may sign. Anything else waits.
allow = ["refresh_inventory", "run_preflight", "apply_updates"]
# Whether a job may restart the host: a reboot job, or an update that asks
# for one. Off unless you say so.
reboot = false
# An update touching more packages than this waits.
max_packages = 50
# An update touching any package matching these waits. `*` matches anything.
deny_packages = ["linux-image-*", "kernel*", "openssh-server"]
Terminal window
updawgctl sign --org acme --key org.key --yes --policy sign-policy.toml

The policy is checked against each job’s own document, the bytes about to be signed. A misspelt key or job kind is an error rather than ignored, and a policy that allows nothing signs nothing.

A job not signed by the time it expires expires, and its proposal halts, as it would for a host that never picked it up. Notification rules for jobs.awaiting_signature hear about waiting jobs twice: when they are made, and again four hours before the first of them expires.

Each key on the Signing keys card says how many hosts pinned it at their last check-in, and each host’s page lists the keys it holds. Once every host has your key, choose Retire next to Updawg’s key. From then on Updawg is never asked to sign for your organization, and hosts refuse anything our key signed once they next renew. We then destroy the key in Cloud KMS.

A host that has not picked your key up by then gets no job it can run until it renews, so retire after the hosts have moved — or re-enrol the stragglers.

What this does and does not protect against

Section titled “What this does and does not protect against”

With your key held by you and ours retired, nobody who compromises Updawg — our servers, our database, our cloud account, or us — can make a job your hosts will run. They can still stop sending jobs, or show you wrong information in the portal; updawgctl sign shows each job from the document itself so that what you sign is what you see.

To replace your key with a new one, your current key endorses the next, where your keys live, exactly as ours endorsed yours. Hosts follow the endorsement within minutes, as they did the hand-over; nothing needs re-enrolling.

  1. Make the next key: updawgctl keygen --out next.key.

  2. Endorse it with the active one. --from is the active key’s id from the Signing keys card, and --next-public-key is what keygen printed:

    Terminal window
    updawgctl endorse --org org_… --from sk_… \
    --next-public-key MCowBQYDK2VwAyEA… --key org.key

    With an HSM, use --command and --public-key for the active key, as for sign.

  3. Prove you hold the next key: updawgctl prove-key --org org_… --key next.key.

  4. Under Settings → Signing keys, choose Rotate your key and paste both lines. The next key is active at once, and the previous one is retiring: hosts still accept it until they renew.

  5. Sign with the new key from now on. When every host pins it, retire the previous one on the card, and set the new public key as trusted_org_key on hosts you enrol from now on.

Both lines are good for a day. The endorsement names the active key by id and by its public key, so one made before somebody else rotated is refused.