74 lines
3.4 KiB
Markdown
74 lines
3.4 KiB
Markdown
# netkingdom Publication-Scope Policy
|
|
|
|
**Owner:** netkingdom (security-infrastructure domain) · **Enforced by:** secrets-engine
|
|
**Data:** `policies/netkingdom-publication-scope.yaml` · **Code:** `src/secrets_engine/publication_policy.py`
|
|
|
|
## Idea
|
|
|
|
The scope of a publish token depends on the **maturity** of what is being
|
|
published. A repo's maturity translates into the maturity tag of the package it
|
|
ships, and each tag is restricted by the publication scope its token carries:
|
|
|
|
| Package maturity tag | Publication scope (when active) | Token env var | Why |
|
|
| --- | --- | --- | --- |
|
|
| `maturity-build` | **gitea-wide** | `NPM_AUTH_GITEA_TOKEN` | low-stakes build artifacts may use a broad, shared token |
|
|
| `maturity-test` | **org-wide** | `NPM_AUTH_COULOMB_TOKEN` | integration artifacts scoped to the org |
|
|
| `maturity-prod` | **repo-scoped** | `NPM_AUTH_TOKEN` (default) / `NPM_AUTH_WHYNOTDESIGN` (explicit) | high-stakes production publishes require tight, repo-scoped rights |
|
|
|
|
Scope **narrows as stakes rise** — the production token is the most tightly bound.
|
|
|
|
## The netkingdom maturity gate (why it's dormant today)
|
|
|
|
The graduated table above only takes effect once **netkingdom itself reaches
|
|
production grade** (`netkingdom_maturity: maturity-prod`). netkingdom is at
|
|
`maturity-build` today, so the policy is **dormant** and every lane **clamps to
|
|
the safest scope (`repo`)**, injected as `NPM_AUTH_TOKEN`.
|
|
|
|
This is fail-safe, not fail-open: until the policy machinery is itself
|
|
production-grade, we never hand out the broad (gitea/org-wide) tokens the
|
|
graduated table would otherwise allow.
|
|
|
|
```
|
|
$ secrets-engine policy publication whynot-design-npm-publish
|
|
netkingdom maturity: maturity-build (production_grade=False)
|
|
effective publication scope: repo (clamped from graduated table — fail-safe)
|
|
inject token as env var: NPM_AUTH_TOKEN
|
|
```
|
|
|
|
When netkingdom graduates, flip `netkingdom_maturity` to `maturity-prod` in the
|
|
policy file; the same lanes then resolve build→gitea, test→org, prod→repo
|
|
automatically. Nothing else changes.
|
|
|
|
## Token env-var naming convention
|
|
|
|
`NPM_AUTH[_<QUALIFIER>]_TOKEN`, where the qualifier signals the blast radius:
|
|
|
|
| Effective scope | Env var | Qualifier source |
|
|
| --- | --- | --- |
|
|
| repo (default) | `NPM_AUTH_TOKEN` | — (bare default) |
|
|
| repo (explicit) | `NPM_AUTH_WHYNOTDESIGN` | repo `whynot-design` |
|
|
| npm-scope / "project" | `NPM_AUTH_WHYNOT_TOKEN` | scope `@whynot` — **defined, not used by the policy** |
|
|
| org | `NPM_AUTH_COULOMB_TOKEN` | org `coulomb` |
|
|
| gitea-wide | `NPM_AUTH_GITEA_TOKEN` | — |
|
|
|
|
The name is a **signal**, not the boundary. The real boundary is how the Gitea
|
|
token is minted (a dedicated bot account per grant is usually needed — Gitea
|
|
package tokens are user-scoped, not natively scope/org-scoped) plus the OpenBao
|
|
lane path + consumer policy. The catalog records the *intended* grant via
|
|
`delivery_config.npm.maturity`; a reviewer sees the intended blast radius even
|
|
when the backend can only enforce a coarser one.
|
|
|
|
## Lane configuration
|
|
|
|
```yaml
|
|
delivery_config:
|
|
npm:
|
|
registry: "https://forgejo.coulomb.social/api/packages/coulomb/npm/"
|
|
scope: "@whynot"
|
|
package: "@whynot/design"
|
|
maturity: maturity-build # feeds the policy
|
|
# token_env: NPM_AUTH_WHYNOTDESIGN # optional explicit override
|
|
```
|
|
|
|
`secrets-engine exec` injects the resolved token under the resolved env-var name
|
|
into the npm child only; the parent shell never holds it.
|