railiance-master/workplans/RMASTER-WP-0023-private-by-default-exposure.md
codex 48dba891b5 Propose RMASTER-WP-0023: private-by-default exposure until admission
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.
2026-08-15 19:19:07 +02:00

8.4 KiB

id type title domain repo status owner topic_slug created updated related related_repos
RMASTER-WP-0023 workplan Private-by-default exposure until production admission financials railiance-master proposed bernd railiance 2026-08-15 2026-08-15
RMASTER-WP-0017
RMASTER-WP-0021
RAIL-HO-WP-0009
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 (declaredinstalledverifiedproduction-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

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

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

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

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

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.