Keychain

Set 3AM up once, and every install of yours has it. Site keys, pairing and sealed files; what it carries and never carries; changing the home; when an install is lost; managing it; the cryptography.

One Keychain, every door. Set 3AM up once, on one install, the home. Every other install of yours, a member, takes the same setup from it and stays in step: connections and their secrets, sign-in, mail, policy and settings, branding, people and teams. Enterprise and Strategic.

                    ┌──────────── Mumbai DR        (member, in step)
   Production ──────┼──────────── Frankfurt staging (member, catching up)
   (the home)       └ ─ ─ ─ ─ ─ ─ Dev pod          (gone: not synced for a day)

It moves only between your own installs, encrypted end to end. 3AM's licence authority never sees a connection, a secret or a person.

TierInstalls on the licenceActive instancesKeychain
StarterProduction + DR1
EnterpriseUp to 10, any purpose1 per installYes
StrategicAny install of yoursUnlimitedYes

Quick start

1. On the install you set up first (usually production)
# Console: Keychain → Make this the home   (or in setup: the Keychain step)
curl -s -X POST -H "Authorization: Bearer $T" -d '{"label":"Production · Mumbai"}' $PROD/v1/keychain/home
2. On the home: a one-time key for the new site
# Console: Keychain → Add a site → A new host → Make its key
curl -s -X POST -H "Authorization: Bearer $T" \
  -d '{"labels":["Mumbai DR"],"purpose":"dr","licence":true,"hours":24}' $PROD/v1/keychain/site-keys
3. On the new host, in the bundle's folder: the line the home showed
printf '%s' '3AMSITE1.…' > site.key
./install.sh --keychain-join https://3am-prod.bank.internal --keychain-site-key-file site.key \
  --model-url … --model … --model-key-file …

The new site installs, takes everything the Keychain carries within a minute, and (with "licence": true) joins the licence with the home vouching. Nobody types a code.

What it carries, and what never leaves an install

Carried (the home chooses each part)Never carried
Licence: taken only where it is good for that installThe audit log: each install keeps its own, untouched
Connections and their secretsIncidents and their evidence
Mail: the provider and its keySessions and the admin token
Sign-in: single sign-on, who may sign inEach install's own keys: every install remains its own identity
People and teamsThe halt, and actions taken: stopping one install never stops another
Policy and settings: rules, budgets, autonomy per service, the fix mode, services always covered
Branding
Your numbers and estate fixes: the business figures, corrections to the estate

Connections a member adds itself stay its own.

Three ways in

WayForHow
Site key (recommended)A new host, where 3AM isn't installed yetThe home makes a key for one site; the site's installer uses it and sets itself up, licence included
PairingAn install already running that can reach the homeThe home opens for 10 minutes; both consoles show six digits; each person types what the other screen shows
Sealed fileA site that can't reach the homeThe Keychain in one file, encrypted under a passphrase; carry the file and the passphrase apart

In setup, a new site answers We run 3AM elsewhere already on the licence step and joins by any of the three.

Site keys

On the home: Keychain → Add a site → A new host.

  1. Describe the site: its name, what it's for (production, DR, staging, development, test), how long the key works (1 hour, 1 day, 3 days or 7 days), and whether to add it to the licence.

  2. Run one line on the new host, shown once, in the folder where you unpacked the bundle:

    printf '%s' '3AMSITE1.<pin>.<secret>' > site.key
    ./install.sh --keychain-join https://3am-prod.bank.internal --keychain-site-key-file site.key

    3AM already installed there? Its console: Keychain → Join a Keychain → Site key, with the address and the key.

  3. It arrives: the step updates by itself when the site is set up. It shows under Members and syncs every minute.

The key's rules. 3AMSITE1.<pin>.<secret> works once, until it expires, and can be withdrawn. The home keeps only its hash. The pin is a hash of the home's signing key, so the new site checks it reached the right home: a key pointed at another install is refused there (security.keychain_wrong_home). An expired, withdrawn or reused key is refused by the home (security.keychain_site_key_refused). The new site retries every 30 seconds for up to two hours while the home is unreachable.

Pairing

  1. On the home: Keychain → Add a site → An install already running → Open for 10 minutes.
  2. On the other install: Keychain → Join a Keychain → Pair, with the home's console address.
  3. Both consoles show six digits. Type, on each, what the other shows. If they differ, someone is between the two installs: nothing is paired.

Sealed file

  1. On the home: Keychain → Add a site → No network between them → a passphrase of 12 characters or more → Seal and download (3am-keychain.json).

  2. Carry the file and the passphrase by different routes.

  3. On the other site: Keychain → Join a Keychain → Sealed file, or on a new host:

    ./install.sh --keychain 3am-keychain.json --keychain-passphrase-file pass.txt

    Taken once per version; an upgrade doesn't repeat it. A sealed file is a copy, not a member: it doesn't sync afterwards.

From then on

  • Each member syncs with the home every minute and takes what changed.
  • What the Keychain carries is read-only on a member ("managed by Keychain from Production"). Change it once, on the home.
  • Leave keeps everything a member has, and makes it editable there again.

Managing it

Who can change it. Everyone signed in sees the Keychain and Installs tabs. Owners and admins change them; the server refuses anyone else. Taking over as the home is for owners only.

What it carries. On the home: Keychain → What it carries, a switch per part. A member keeps what it has of a part the home stops carrying, as its own. Nothing is deleted anywhere.

Keeping a member's own copy. On a member: Keychain → Each part → Keep my own. That part becomes editable there, and the home's changes to it no longer arrive. Take the Keychain's later replaces the member's copy with the home's. The home's Members list shows what each member keeps.

Names. The home and each member can be renamed where the name is shown (the pencil).

Moving a member. On a member: Keychain → Move to another home, then pair with the new home. It keeps everything it has.

Changing the home

When the home itself must move (new hardware, a datacentre exit), hand it to a member:

  1. On the home: Members → Make it the home, on a member that is in step, keeps nothing of its own, and has a console address (--console-url).
  2. The home shows six digits for 10 minutes. The member sees the offer at its next sync; an owner or admin there types the digits.
  3. The member signs its acceptance and sends it to the home. The home stands down and becomes a member of the new home.
  4. Every other member is answered, at its next sync, with the home's signed offer and the new home's signed acceptance. It checks both against keys it already trusts, and moves.

A wrong code moves nothing. If the answer is lost halfway, the next sync finishes the move. A forged move (one not signed by both homes) is ignored and recorded (security.keychain_bad_handover).

When an install is lost

Every member syncs every minute, so the Keychain knows when a key stops: a destroyed VM, a pod that didn't come back, a host rebuilt with a new identity.

AfterThe ring showsAudit event
3 minutes without a syncNot answering, on the home and in every member's viewkeychain.member_silent / keychain.home_silent
A dayGone?, with Remove beside itkeychain.member_gone
The home silent 15 minutesOn members: If the home is gone, with Take overkeychain.home_gone
It syncs againBack in stepkeychain.member_back / keychain.home_back

Nothing is removed by itself. A person removes a gone install (and takes it off the licence on the Installs tab).

The home lost. After 15 minutes of silence, an owner on a member can take over as the home. It becomes the home with everything it has. No other install follows it on its say-so: each pairs with it again (Keychain → Move to another home), six digits each. If the old home comes back, it is no longer anyone's home.

The cryptography

PartHowWhy
IdentityEach install signs with its audit-log key (Ed25519, or ECDSA P-256 under FIPS) and has a P-256 key for key agreement (data/keychain/agree.pem, 0600)Each install is a key, not a name
PairingThe home opens a window; the member offers its keys and a nonce; the home answers with its own, signed. Six digits from the hash of the whole exchange, compared by peopleA man in the middle must substitute keys, which changes the digits
Site keySingle use, expiring, stored as a hash; pins the home's signing keyThe site can't be pointed at another home; a copied key works once
A syncSigned by the member (who, when, a nonce, its version, its install-log head); the home checks the pinned key, the time (2 minutes) and that the nonce is newReplays, forgeries, members that left
The answerA fresh P-256 key per answer; ECDH with the member's key; HKDF-SHA256 bound to both fingerprints, the version and the nonce; AES-256-GCM; header and ciphertext hash signed by the homeOnly that member can read it; a changed byte is found; a stolen home key opens no past answer
HandoverThe home signs an offer naming the new home's keys and every member's; the new home signs its acceptanceMembers re-pin only on both homes' signatures
Sealed filescrypt (N=2^17, r=8, p=1), or PBKDF2-HMAC-SHA256 600,000 rounds under FIPS; AES-256-GCM; signed by the sealing installBrute-forcing the passphrase is expensive; a changed file is found
GossipInstall-log heads compared at every sync, both waysA forked history of the licence's installs is noticed

Every algorithm is FIPS-approved (P-256, ECDSA, SHA-256, HKDF, AES-GCM, PBKDF2).

API

CallWhoPurpose
GET /v1/keychainviewerRole (off, home, member), what it carries, members and their health, site keys, the ring, offers; can says what you may change
POST /v1/keychain/home {"label"}adminMake this install the home
POST /v1/keychain/site-keys {"labels","purpose","licence","hours"}adminMake site keys (shown once)
POST /v1/keychain/site-keys/revoke {"id"}adminWithdraw a key
POST /v1/keychain/enrol {"address","key"}adminSet this install up with a site key
POST /v1/keychain/windowadminOpen pairing for 10 minutes
POST /v1/keychain/members/confirm {"fingerprint","code"} / /members/removeadminPair a member by its code / remove one
POST /v1/keychain/join {"address","label"}, /join/confirm {"code"}, /join/canceladminPair with a home
POST /v1/keychain/joins/approve {"request"}adminAdd a paired member to the licence
POST /v1/keychain/scope {"skip": [parts]}adminOn the home: the parts left out
POST /v1/keychain/own {"part","own"}adminOn a member: keep its own copy, or take the Keychain's again
POST /v1/keychain/rename {"label","fingerprint"?}adminRename this install, or a member
POST /v1/keychain/handover {"fingerprint"}, /handover/cancel, /handover/accept {"code"}adminChange the home
POST /v1/keychain/take-overownerTake over as the home (after 15 minutes of silence)
POST /v1/keychain/pull, /leaveadminSync now; leave
POST /v1/keychain/seal {"passphrase"}, /unseal {"file","passphrase"}adminThe sealed file
POST /v1/keychain/peer/*installs, by keyBetween installs (no session: authenticated by pinned keys, throttled per address)

Parts: licence, connections, mail, signin, people, policy, branding, numbers.

Audit events

keychain.home, keychain.window, keychain.member_paired, keychain.member_enrolled, keychain.member_removed, keychain.joined, keychain.enrolled, keychain.left, keychain.applied, keychain.sealed, keychain.refused, keychain.site_keys, keychain.site_key_revoked, keychain.auto_vouch_failed, keychain.scope, keychain.override, keychain.override_dropped, keychain.renamed, keychain.handover_offered, keychain.handover_cancelled, keychain.home_handed_over, keychain.home_received, keychain.home_moved, keychain.home_taken_over, keychain.member_silent, keychain.member_gone, keychain.member_back, keychain.home_silent, keychain.home_gone, keychain.home_back, keychain.pairing_cancelled; security.keychain_code_mismatch, security.keychain_site_key_refused, security.keychain_wrong_home, security.keychain_bad_handover, security.install_log_fork.

On this page