Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ACCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ python -m messaging_service set-access --principal assistant-work \
--chats chat-work --permissions read

python -m messaging_service list-keys --principal assistant-work
python -m messaging_service list-keys --all
python -m messaging_service key-info --key-file /dev/stdin < /private/assistant-work.token
python -m messaging_service revoke-key --key-id PUBLIC_CREDENTIAL_UUID
python -m messaging_service extend-key --key-id PUBLIC_CREDENTIAL_UUID --days 365
python -m messaging_service set-access --principal assistant-work --disable
Expand All @@ -122,6 +124,16 @@ without `--chats`/`--permissions` keeps its restrictions; the account list must
explicitly. Rotation with the same permissions does not reset the ACK. Issuing a key to a disabled
principal does not enable it again.

### Key inventory

`list-keys --all` lists every key of every principal (exactly one of `--principal` or `--all`):
public key ID, principal, `created_at` (the operator `issue_key` audit row, `null` when none
exists), `expires_at` and `revoked`. `key-info --key-file FILE` reads one token per line (at most
100 lines and 64 KiB; `/dev/stdin` for a batch) and prints, in input order, `found` with the
public key ID, principal, `expires_at` and `revoked`, or `found: false`; a line that cannot be a
token reports `error: invalid_key`. Neither command prints a token or a digest, and neither
writes. `deedkit admin key list` (docs/operations/production.md, Admin CLI) runs both.

### Extending a key

Since schema 36 `extend-key` sets one live key's expiry to now + `--days` (an integer 1–365,
Expand Down
11 changes: 11 additions & 0 deletions deploy/admin.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"policy": "/opt/deedkit/deploy/private/gateway-policy/policy.json",
"calls_registry": "/opt/deedkit/deploy/private/calls-agents.json",
"labels": "/opt/deedkit/deploy/private/admin/labels.json",
"paths": {
"/run/deedkit/keys": "/opt/deedkit/deploy/private/gateway-keys",
"/run/deedkit/voice": "/opt/deedkit/deploy/private/voice"
},
"messaging": ["docker", "compose", "--project-name", "<messaging compose project>", "--project-directory", "/opt/deedkit/deploy", "-f", "/opt/deedkit/deploy/compose.yaml", "-f", "/opt/deedkit/deploy/compose.operator.yaml", "--profile", "operator", "run", "--rm", "-T", "--no-deps", "--name", "{name}", "operator"],
"calendar": ["docker", "compose", "--project-name", "deedkit-calendar", "--env-file", "<calendar env file>", "-f", "/opt/deedkit/deploy/compose.calendar.yaml", "run", "--rm", "-T", "--no-deps", "--name", "{name}", "--volume", "<CALENDAR_STATE_DIR>/service:/data/service:ro", "calendar-api"]
}
33 changes: 33 additions & 0 deletions docs/operations/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,39 @@ reason and the names of unfinished and serving attempts. The tool reads receipts
After `LIVE`, `python3 scripts/feedback_sync.py follow` (dry run, then `--apply`) moves agent
feedback whose card that release delivers (ANYM-473, docs/operations/feedback-triage.md).

## Admin CLI

`python3 scripts/deedkit.py admin key list` (ANYM-494) shows every agent key in one view: one row
per gateway principal (label, enabled, gateway expiry) with the messaging, calendar and calls
credential bound to it (public ID, expiry, `live`, `revoked`, `expired`, `missing`, or `unknown`
when a backend command fails). It reads the gateway policy and the calls registry and runs the
messaging `list-keys --all` / `key-info` and calendar `list-agents` / `agent-info` operator
commands (ACCESS.md, `services/calendar/docs/SERVICE.md`); key files reach those commands on
stdin, never in argv, environment or output. It takes no lock and writes no store or file, so it
is safe during a call. The one-shot operator containers keep their stdout (public IDs only) in
Docker's container log until `run --rm` removes them; on a timeout or an interrupt the CLI stops its
own uniquely named container (`docker stop`, 30 s bound), and the calendar reads carry a 5 s
statement timeout. Filters: `--principal ID`, `--valid` (enabled, unexpired), `--expiring` (within
the gateway's 7-day warning window or expired), `--all` (default); `--json` for machine output;
`--strict` exits 1 when there are findings (exit 2 is an unreadable input).

Findings, for every gateway principal, disabled and expired ones included (re-enabling one would
expose them): `credential_missing`, `credential_revoked`, `credential_expired`,
`credential_pin_mismatch` (the key file does not match the policy's `key_sha256`, so the gateway
refuses it), `backend_expires_first` (the backend key ends before the gateway entry),
`shared_credential` (one backend key bound to two or more principals), `unbound_key` (a backend
key no principal holds, with its status) and `backend_unavailable` (a failed backend command, or
no calls registry configured while a principal holds a calls key). With `--principal`, the list
keeps that principal's findings and every `backend_unavailable` of a backend it uses.

The configuration is a private JSON file (`--config`, default
`/opt/deedkit/deploy/private/admin/config.json`; template `deploy/admin.example.json`): policy
and calls registry paths, container-to-host path mappings for key files, and the messaging and
calendar operator command prefixes. Every private file it reads must be a regular, owner-only
file (no group or other bits, no symlink). `admin key label ID TEXT` (1–80 printable characters,
an existing gateway principal) writes the label to `labels.json` beside it, mode 0600, atomically;
no service reads that file, so the enforced policy never changes.

## Record the production release

After a successful `apply`, the release owner records the release in Tiden from
Expand Down
7 changes: 7 additions & 0 deletions scripts/deedkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
from pathlib import Path
import subprocess
import sys
import tomllib

ROOT = Path(__file__).resolve().parents[1]
Expand Down Expand Up @@ -138,6 +139,12 @@ def validate_files(plan, root=ROOT):


def main(argv=None):
argv = sys.argv[1:] if argv is None else argv
if argv[:1] == ["admin"]:
# `deedkit admin ...` (ANYM-491): host administration, separate from module deployment.
from deedkit_admin import main as admin

sys.exit(admin(argv[1:]))
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=["resolve", "up", "status", "stop"])
parser.add_argument("--profile", required=True, type=Path)
Expand Down
Loading
Loading