Draft family workplan for review. Default reefs, rails, and rapps stay off the public internet until production-approved plus an explicit grant. Implementation is routed, not done here.
241 lines
8.4 KiB
Markdown
241 lines
8.4 KiB
Markdown
---
|
|
id: RMASTER-WP-0023
|
|
type: workplan
|
|
title: "Private-by-default exposure until production admission"
|
|
domain: financials
|
|
repo: railiance-master
|
|
status: proposed
|
|
owner: bernd
|
|
topic_slug: railiance
|
|
created: "2026-08-15"
|
|
updated: "2026-08-15"
|
|
related:
|
|
- RMASTER-WP-0017
|
|
- RMASTER-WP-0021
|
|
- RAIL-HO-WP-0009
|
|
related_repos:
|
|
- railiance-master
|
|
- rail-kubernetes
|
|
- railiance-cluster
|
|
- reef-railiance
|
|
- railiance-infra
|
|
- railiance-enablement
|
|
---
|
|
|
|
# RMASTER-WP-0023 — private-by-default exposure until production admission
|
|
|
|
## Goal
|
|
|
|
Make every new reef, rail, and rapp **unreachable from the public internet
|
|
until it is production-safe**, by one family rule that the implementation
|
|
repos can enforce.
|
|
|
|
"Production-safe" already has a name: ADR-0006 admission
|
|
(`declared` → `installed` → `verified` → `production-approved`). What is
|
|
missing is the **default-private exposure rule** and a field the schemas can
|
|
check. Topology and a working deploy must not imply a public listener.
|
|
|
|
## Why this is here
|
|
|
|
`railiance-master` owns framework contracts that span `reef-*`, `rail-*`,
|
|
and `rapp-*`. Shielding "whatever we spin up" is that kind of rule.
|
|
|
|
It does **not** belong as the system owner in:
|
|
|
|
- `railiance-infra` — host UFW. Necessary for a new reef with no 80/443;
|
|
useless for a rapp that shares `railiance01` ingress. RAIL-HO-WP-0009
|
|
closed the hand-opened k3s API; do not make S1 the admission desk for
|
|
rapps.
|
|
- `railiance-cluster` / `rail-kubernetes` — Ingress, NetworkPolicy, Service
|
|
types. This is where most packets will actually be blocked. They cannot
|
|
define what "production-safe" means for a rapp or for a non-k8s reef
|
|
(`reef-storage`).
|
|
- each `rapp-*` — must declare intended exposure; must not punch its own
|
|
public hole.
|
|
- `railiance-enablement` — should ship private-by-default templates; does
|
|
not own the rule.
|
|
- `reef-railiance` alone — too local; the next reef must inherit the same
|
|
rule.
|
|
|
|
## What already exists
|
|
|
|
- ADR-0006 and `docs/reef-production-readiness-contract.md` — admission
|
|
states; network isolation and extra gates for internet-facing or
|
|
high-value workloads.
|
|
- Family schemas (`schemas/{reef,rail,rapp}.schema.json`) — no
|
|
`exposure` / `network_posture` field.
|
|
- Readiness vocabularies are **deliberately not unified**
|
|
(`schemas/README.md`):
|
|
- rail: `declared | installed | verified | production-approved | deprecated`
|
|
- rapp: `draft | declared | deployed | verified | retired`
|
|
- reef: `lifecycle_state` `planned | active | retired`
|
|
- bindings: ADR-0006 states on the reef
|
|
- RAIL-HO-WP-0009 — operator access to a shielded k3s API is ops-bridge
|
|
(`k3s-api-railiance01` on `:16444`). That is the intended pattern for
|
|
anything not yet public.
|
|
- Live public exceptions already exist on `reef-railiance` (Forgejo,
|
|
Coulomb Social, Host Europe Nydus on 2224). They must be named as
|
|
grants, not silently grandfathered.
|
|
|
|
## Decision to ratify (T01)
|
|
|
|
Proposed default, for review:
|
|
|
|
| Posture | Meaning | Who may reach it |
|
|
| --- | --- | --- |
|
|
| `private` | Cluster / reef-internal only | in-cluster DNS, same-reef services |
|
|
| `operator` | Reachable via ops-bridge / SSH tunnel | operator and approved agents |
|
|
| `public` | Internet | anyone the Ingress / DNS / UFW surface allows |
|
|
|
|
Rules:
|
|
|
|
1. New reefs, rails, and rapps default to `private` or `operator`.
|
|
Prefer `operator` when a human must debug before admission.
|
|
2. `public` requires **both** `production-approved` (ADR-0006 binding, or
|
|
the equivalent rail/rapp state this workplan chooses) **and** an
|
|
explicit exposure grant in the declaration.
|
|
3. A working deploy, a `hosts_rail` / `binds_rapp` line, or an Ingress
|
|
object is not a grant.
|
|
4. Ops-bridge is the normal path to a shielded thing. That is the same
|
|
trade as ADR-005 in `railiance-infra`: convenience depends on the
|
|
tunnel; the Internet does not see the service.
|
|
|
|
Do not unify the three readiness enums in this workplan unless T01
|
|
explicitly decides to. Exposure can sit beside them.
|
|
|
|
## Boundaries
|
|
|
|
This workplan may:
|
|
|
|
- amend ADR-0006 or add a sibling ADR for exposure posture
|
|
- extend family schemas and `tools/validate-family-declarations.py`
|
|
- write the exposure contract and the exception register
|
|
- **route** implementation workplans to owning repos
|
|
|
|
It must not:
|
|
|
|
- implement NetworkPolicy, Ingress classes, or UFW rules here
|
|
- take down live public services as a side effect of the contract
|
|
- put booked cost, credentials, or customer numbers in declarations
|
|
|
|
## Tasks
|
|
|
|
### T01 — Ratify the exposure rule
|
|
|
|
```task
|
|
id: RMASTER-WP-0023-T01
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Review and accept or amend the decision table above. Record it as an ADR
|
|
(amend ADR-0006 or add ADR-0008). Name the default posture for a new
|
|
reef, a new rail, and a new rapp. Decide whether `public` keys off the
|
|
**binding** admission state (preferred) or off `rapp.yaml`
|
|
`readiness_state` (currently has no `production-approved`).
|
|
|
|
**Done when:** the ADR is accepted and the three readiness enums are
|
|
either left distinct on purpose or given a written unification plan.
|
|
|
|
### T02 — Write the exposure contract
|
|
|
|
```task
|
|
id: RMASTER-WP-0023-T02
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
One contract, `docs/exposure-posture-contract.md`, that says what each
|
|
posture means **per family**:
|
|
|
|
| Family | `private` | `operator` | `public` |
|
|
| --- | --- | --- | --- |
|
|
| `reef-*` | no extra host ports; no public DNS for new members | ops-bridge / SSH only | declared host/DNS surface |
|
|
| `rail-*` | no public Ingress class; default-deny NetworkPolicy | tunnel to the rail's API or admin path | rail may emit public Ingress |
|
|
| `rapp-*` | ClusterIP / in-cluster DNS only | named tunnel (local port) | named public hostname |
|
|
|
|
Include the fail-closed rule: missing field means `private`.
|
|
|
|
**Done when:** implementation repos can implement without inventing
|
|
vocabulary.
|
|
|
|
### T03 — Schema and validator
|
|
|
|
```task
|
|
id: RMASTER-WP-0023-T03
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Depends on T01.
|
|
|
|
Add `exposure` (or the T01 name) to `rapp.schema.json`, `rail.schema.json`,
|
|
and `reef.schema.json`. Validator must fail when:
|
|
|
|
- posture is `public` and the relevant admission/readiness state is not
|
|
production-approved
|
|
- posture is `public` and no grant object is present (hostname / port /
|
|
reason / approved_on)
|
|
- a live family declaration omits the field after the migration window
|
|
|
|
Keep the field additive first so existing declarations do not break the
|
|
day the schema lands; then require it.
|
|
|
|
**Done when:** `tools/validate-family-declarations.py` rejects a public
|
|
rapp that is not production-approved, and the testdata tree has good and
|
|
bad fixtures.
|
|
|
|
### T04 — Exception register for already-public surfaces
|
|
|
|
```task
|
|
id: RMASTER-WP-0023-T04
|
|
status: todo
|
|
priority: medium
|
|
```
|
|
|
|
List current public listeners on `reef-railiance` (and CoulombCore if in
|
|
scope) as explicit grants: Forgejo, Coulomb Social, Host Europe Nydus
|
|
2224, anything else found. Each grant: hostname or port, owning rapp or
|
|
provider, why it is public, residual risk owner.
|
|
|
|
Do not invent a shutdown plan here. The register exists so the contract
|
|
does not pretend the reef is already private.
|
|
|
|
**Done when:** every known public surface is either a grant or a routed
|
|
task to close it.
|
|
|
|
### T05 — Route enforcement workplans
|
|
|
|
```task
|
|
id: RMASTER-WP-0023-T05
|
|
status: wait
|
|
priority: high
|
|
```
|
|
|
|
Depends on T01 and T02.
|
|
|
|
Write the handoff workplan files (or precise intake notes) in the owning
|
|
repos. Do not implement there from this repo.
|
|
|
|
| Owner | What to enforce |
|
|
| --- | --- |
|
|
| `rail-kubernetes` (or `railiance-cluster` until the rail owns networking) | default-deny NetworkPolicy; no public Ingress unless the grant exists; ClusterIP as the paved Service |
|
|
| `reef-railiance` | first live admission: new binds stay `operator`; existing public binds appear in the T04 register |
|
|
| `railiance-infra` | new reefs do not get 80/443/6443 until the reef grant exists; not the rapp gate |
|
|
| `railiance-enablement` | templates emit private Services + tunnel docs, never a public Ingress by default |
|
|
|
|
**Done when:** each owning repo has a workplan or an accepted intake, with
|
|
a back-link to this ID.
|
|
|
|
## Suggested review order
|
|
|
|
1. T01 (you) — accept or change the table.
|
|
2. T02 + T04 can proceed in parallel after T01.
|
|
3. T03 after the field names are stable.
|
|
4. T05 last; it is the only task that creates work in other repos.
|
|
|
|
## Outcome
|
|
|
|
Pending review. This file is a draft (`status: proposed`). Register it
|
|
with `statehub fix-consistency` in `railiance-master` after you accept the
|
|
shape. Implementation stays out of this repo.
|