本页还没有译文
本页 32 段还没译成日本語,下面显示的是英文原文。Operate a Direct Hardened City from the gc CLI
还没翻译译自英文(docs.gascity.com)本页 32 段都还没译
This runbook is for operators standing up a direct, self-hosted, hardened
city that a gc client mutates over the HTTP+SSE control plane — no hosted
edge, no external broker. The capstone it enables is the one-liner:
gc --context prod rig add --git-url https://github.com/org/repo.git --name web \
&& gc --context prod sling <agent> <bead-id>rig add --git-url drives server-side provisioning (the server clones the
repo, inits beads, composes packs) and sling routes an existing bead into the
new rig — both authenticated by a fresh, request-bound grant your machine mints.
Read Prerequisites and threat posture first. A hardened city exposes a fully unauthenticated read plane; the network front is not optional.
1. Prerequisites and threat posture
- One controller replica only. The grant replay guard and the rig-create in-flight index are process-local. A second controller against the same city reopens the grant replay window and races double-clones. Nothing in code detects a second replica — this is an operator rule.
- The read plane is FULLY UNAUTHENTICATED. Write-auth gates mutations only. Anyone who can reach the port can read every bead payload, all mail, session peeks and transcripts, and the entire event stream — including the 202 rig-provisioning progress. A network/TLS front (reverse proxy, private network, or firewall) is REQUIRED, not optional. In-band read auth is later work.
- TLS.
gcrefuses a plain-httpnon-loopback URL at context validation. Terminate TLS in front of the city and, if the cert is private, pass its CA to the context with--ca-file.
2. Mint the city keypair
The server holds only the public key; the private key stays on the operator
machine (0600). gc-write-mint accepts a PEM PKCS#8, or a raw / hex / base64
32-byte ed25519 seed.
mkdir -p ~/.gc/keys && chmod 700 ~/.gc/keys
# Generate a PKCS#8 private key.
openssl genpkey -algorithm ed25519 -out ~/.gc/keys/city.ed25519
chmod 600 ~/.gc/keys/city.ed25519
# Extract the raw 32-byte public key and base64 it for the server config.
openssl pkey -in ~/.gc/keys/city.ed25519 -pubout -outform DER \
| tail -c 32 | base64
# -> <base64 ed25519 pubkey>The verify key is configured as kid:base64pub (comma-separable for rotation),
e.g. k1:<base64 ed25519 pubkey>.
3. Configure and boot the hardened city
city.toml:
[api]
port = 9443 # behind the TLS front
bind = "0.0.0.0" # non-loopback => read-only unless allow_mutations
allow_mutations = true
write_auth_verify_key = "k1:<base64 ed25519 pubkey>"Exactly one key source. The GC_CITY_WRITE_PUBKEY env overrides the
config key — set one, never both. GC_CITY_WRITE_EPOCH_FLOOR is env-only.
Boot behavior matrix:
| Bind + config | Result |
|---|---|
| verify key present | boots hardened; prints the loud read-plane warning (below) |
| no key, no ack | refuses to boot (G10 fail-closed) |
no key + write_auth_allow_unverified = true (or GC_CITY_WRITE_ALLOW_UNVERIFIED=1) | boots with an unauthenticated write plane — only ever behind a trusted network front |
On any non-loopback bind that allows mutations, boot now emits a loud warning naming the unauthenticated read surface — this is expected:
WARNING: 0.0.0.0 is a non-loopback bind with mutations enabled — the READ plane is UNAUTHENTICATED.
Anyone who can reach this port can read, with no credential:
- beads (work items and their payloads) and mail
- session peeks and full transcripts
- the event stream, including 202 rig-provisioning progress
Write-auth gates MUTATIONS only (posture: grant-gated — every mutation requires a signed X-GC-City-Write grant).
A network/TLS front (reverse proxy, private network, or firewall) is REQUIRED, not optional.With the ack knob instead of a key, the posture line reads UNVERIFIED — no write-auth verify key is set; mutations are gated ONLY by the network front.
Supervisor-managed variant. A gc supervisor deployment must list the
public hostname in [supervisor] allowed_hosts or every request dies 421.
The standalone gc controller allows any host (the network front is the
boundary), so it needs no allowed_hosts.
4. Configure the client context
gc context add prod \
--url https://city.example.com:9443 \
--city example-city \
--grant-command "gc-write-mint --kid k1 --key ~/.gc/keys/city.ed25519 --city example-city" \
--ca-file ~/.gc/keys/city-front-ca.pem # only if the TLS front uses a private CA
gc context current # dry-run: prints the winning tier and what it shadowedThe context is stored 0600 in $GC_HOME/contexts.toml. Pin --city on the
minter — gc-write-mint refuses to sign a request for any other city. The
grant command re-validates the audience and city and recomputes the request
digest before signing; the private key never enters gc.
5. The one-liner
gc --context prod rig add --git-url https://github.com/org/repo.git --name web \
&& gc --context prod sling <agent> <bead-id>Expected transcript:
# stderr — the resolved target echo
target: example-city @ https://city.example.com:9443 (context: prod, cred: grant:gc-write-mint …, source: flag --context)
# stdout — streamed provisioning progress, then the terminal line
Cloning rig working tree from git
Adding rig 'web'...
Prefix: web
Default branch: main
Initialized beads database
Generated routes.jsonl for cross-rig routing
Rig added.
provisioned → web (prefix web, branch main)
# stdout — the sling result
slung → <agent> (<bead-id>)Watch the provision live from a second terminal:
gc --context prod events --follow --type rig.provision.progress \
--payload-match request_id=<id>Remote sling contract. A remote sling is the 2-arg explicit-target,
existing-bead shape only: gc --context prod sling <agent> <bead-id>. Inline
text, --stdin, and the 1-arg target-inference form are refused (a remote city
cannot see your local rig config or create a local bead).
6. Failure and resume recipes
The CLI prints these recipes itself; here is what each means.
-
Lost stream / deadline (
rig_stream_lost,rig_stream_deadline): the provision continues server-side. Resume idempotently by re-running the exact printed command — same--request-id, same digest-affecting flags (--name/--prefix/--default-branch/--git-url). An omitted flag must stay omitted, or the digest mismatches and the server returns 409. Or watch passively:gc --context prod events --watch --type request.result.rig.create \ --payload-match request_id=<id> -
rig_provision_failed(e.g.clone_failed,blocked_host): the server rolled back to no-rig. Retry the same--request-idto re-clone cleanly — the rolled-back record is re-executable. -
rig_name_conflictwith an in-flight id: another request is already provisioning that name. Watch its stream (the printed recipe); never re-POST your body under its id (that 409s). -
SPA 401 by design. The dashboard loads fine but 401s on every mutation — it mints no grant. Operate writes through
gcwith the grant. -
401 on
gcmutations. The context lacks agrant_command, the kid/key mismatches the server, or the epoch floor moved. Check the server audit log; the client-facing body is deliberately generic (no verification oracle).
7. Validate (the manual real-clone proof)
The automated tests stub the git-fetch boundary, so this is the honest home for the real-git / real-DNS / real-TLS proof. Run it once per release against a hardened city fronted by TLS, with a real public repo URL:
-
Boot warning present. Restart the city; confirm the unauthenticated read-plane warning from §3 prints.
-
Grant-less mutation 401s. A raw
curlPOST with only the CSRF header is refused:curl -sS -o /dev/null -w '%{http_code}\n' -X POST \ -H 'X-GC-Request: true' \ https://city.example.com:9443/v0/city/example-city/rigs # -> 401 -
/svcmutation 403s. A workspace-service mutation is refused on a hardened bind (G11):curl -sS -o /dev/null -w '%{http_code}\n' -X POST \ -H 'X-GC-Request: true' \ https://city.example.com:9443/v0/city/example-city/svc/whatever # -> 403 -
The one-liner succeeds against a real repo (the transcript in §5).
-
Replay is idempotent. Re-run the rig-add with the same
--request-id:exists → web (idempotent replay)
8. Accepted residual risks
These are known and accepted for the direct-hardened deployment. Treat them as operating constraints, not bugs.
- Single-replica only. The replay guard (grant
jti) and the rig-create in-flight index are process-local. A second controller against the same city reopens grant replay (bounded by the ≤2 m TTL + 30 s skew) and races double-clones. Run exactly one controller per hardened city. - Unauthenticated read plane. Everything readable is readable by anyone who reaches the port. The mitigation is the network/TLS front — a mandatory part of the deployment, not an add-on.
- Same-user grant trust. Anyone who can exec as the operator user can run
grant_commandand mint valid grants.0600oncontexts.tomland the key file is the boundary; treat helper-exec access as write access. (A credential embedded in a--git-urlis also argv-visible to a same-userpsduring the server-side clone — an accepted residual.) - Repo-content trust (single-tenant). The clone hardening blocks transport
abuse (
ext::/file:/// SSRF / hooks / submodules), but the cloned content then runs inside pipeline agents. Only add repos you would run locally. - DNS-rebinding TOCTOU. The strict SSRF fence resolves once, fail-closed; git re-resolves at fetch. Redirects are refused and rebind-to-SERVFAIL is blocked, but a fast A-record flip between fence and fetch remains theoretically open — the host's egress firewall is the backstop.