ReviewOS

also looking at this

stacks/ts-cloud

docs(cloud): say what attaching does to a credential's blast radius

#171
Merged glennmichael123 wants to merge docs/attach-credential-reach into main
4 files +358 -1
docs/config.mdmodified+48-0
Changes to docs/config.md
@@ -105,6 +105,54 @@ Every field is optional, and each resolves through the same chain:
105105
106106`infrastructure.compute.image` overrides `hetzner.image` when set: it is the provider-agnostic way to pin an image, and it is what a golden-image bake sets.
107107
108## Attaching to another project's server
109
110Set `cloud.attachTo` to an owner project's `project.slug` to deploy this project's
111sites onto a box that project already runs, instead of provisioning one:
112
113```typescript
114export default {
115 project: { name: 'LogHQ', slug: 'loghq', region: 'us-east-1' },
116 cloud: { provider: 'hetzner', attachTo: 'statushq' },
117 sites: { app: { root: 'dist', start: 'bun run server.ts', port: 3022 } },
118} satisfies Partial<CloudConfig>
119```
120
121The deploy targets the owner's `<slug>-<environment>-app` server, ships only this
122project's sites, and adds its own additive rpx `sites.d/<slug>.json` fragment plus
123DNS. It never touches the owner's box lifecycle, firewall, or other tenants: the
124owner provisions and manages the shared box, attachers only deploy onto it.
125
126### It widens what your CI credential can reach
127
128Attaching finds the owner's box by **listing** the provider's servers with this
129project's own token. That is the whole mechanism, and it has a consequence worth
130stating plainly before you adopt it: the owner's box must be visible to the
131attacher's credential, so both projects have to live in the same provider project.
132
133On Hetzner there is no narrower option. A Cloud API token is scoped to a project
134with Read or Read & Write, with no per-resource scoping, and a deploy needs write.
135So once `attachTo` is set, this project's CI token can modify and delete every
136server in that provider project:
137
138| Before | After |
139|---|---|
140| `loghq` CI reaches the `loghq` box | `loghq` CI reaches `statushq`, `bughq`, `stacks`, `localtunnels` |
141| `bughq` CI reaches the `bughq` box | `bughq` CI reaches all of the above |
142
143Three pipelines that each had blast radius over one box now each have it over the
144whole fleet. A compromised CI run or a mistargeted teardown reaches production
145systems belonging to unrelated apps.
146
147**Per-project isolation and attaching are mutually exclusive.** An app kept in its
148own provider project cannot be attached at all, because its token cannot see the
149owner's box. Co-hosting trades credential isolation for a shared box; that trade
150is often worth making, but it should be a decision rather than a surprise.
151
152`describeCredentialReach()` and `formatCredentialReach()` report what a given
153token actually reaches, separating the owner's boxes from the ones no one asked
154for, so the radius can be shown before an attach is approved.
155
108156## Two app models
109157
110158ts-cloud deploys apps two ways; pick per environment: