Files
home-network-k3s-impl/schemas
jakman85andClaude Opus 5 1f20506829 feat(renderer): inClusterClients — authorable direct east-west ingress (schema 1.5.0)
A platform controller that dials a service's ClusterIP directly had NO authorable
form in the ServiceDefinition contract:

  * ingressRoutes renders the north-south allow only — gateway-ingress admits
    `fromEntities: [ingress]`, the Gateway's Envoy identity, and nothing else;
  * dependencies compiles edges only BETWEEN CATALOG SERVICES, from the source
    entry's own declaration.

Under the default-deny baseline (R-NET-01) such a client is therefore DROPPED, and
the only way to unblock it was a live `kubectl apply` of a hand-written CNP — a
cluster object outside git, one `kubectl delete` or one rebuild from coming back.

`inClusterClients: [{namespace, port, protocol?, name?}]` closes that. Each entry
renders ONE least-privilege ingress CiliumNetworkPolicy: this service's
endpointSelector, `fromEndpoints` matching ONLY `k8s:io.kubernetes.pod.namespace`,
`toPorts` carrying ONLY the declared port/protocol — never `fromEntities: all`.
Additive: the namespace default-deny keeps denying everything undeclared (R-NET-02),
and the entry is NOT a dependency edge, so it creates no wave ordering.

Shape notes:
  * selected by NAMESPACE, not pod labels: an upstream controller's labels belong to
    its chart, not to this platform's contract. The cost is explicit — every pod in
    that namespace is admitted on that port, so the field is for platform-controller
    namespaces (flux-system, monitoring), never a shared one. The schema description
    says so.
  * optional `name` (default: the namespace) only names the rendered policy,
    `<service>-allow-<name>-ingress`. That name is operator-facing (kubectl get cnp,
    a Hubble drop), and pinning it lets an allowance that already had to be applied
    live be authored under its EXISTING name, so Flux adopts the live object in place
    instead of creating a near-duplicate and leaving the original unmanaged.
  * one field, not a policy DSL: no arbitrary selectors, entities or CIDRs.

Covered by render.py --selftest (4c): name/namespace/selector/port shape, no
fromEntities widening, default-deny still `ingress: []`, the gateway allow not
displaced, `name` defaulting to the namespace, and nothing rendered when the field is
absent. No golden changes here — no catalog entry uses the field yet.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-12 22:46:57 -05:00
..

schemas — the ServiceDefinition contract

This directory holds the ServiceDefinition interface — the highest-traffic contract in the platform (spec agents/coordination-protocol.md §2). Onboarding a service is adding/editing one ServiceDefinition under ../catalog/; the platform compiles it into concrete resources. Operators declare what; the platform provides the how.

Source of truth: the read-only spec docs/13-service-catalog.md. This schema matches docs/13 exactly. Do not diverge from it here — escalate spec problems, don't fork the contract.

Files

File Purpose
servicedefinition.schema.json Formal JSON Schema (draft 2020-12) for a ServiceDefinition. Enforces enums, required fields, patterns, and cross-field invariants.
renderer-contract.md Authoritative field → rendered-resource mapping, with requirement IDs. The contract other workstreams consume.

Schema version: 1.0.0 (in the schema description). Treat any change as an interface change: propose on the task board, update dependent renderers/tests in the same change, never silently break a consumer (coordination-protocol §2).

Required vs. defaulted fields

  • Required: name, source (with source.image for kind: container, source.vmTemplate for kind: vm).
  • Everything else is optional and carries a default matching docs/13's "default posture" (kind: container, exposure: internal, egress: internet, auth.mode: forward-auth, update.channel: stable, update.autoRollbackOnFailedHealth: true, observability.metrics: true, dependencies: [] ⇒ DNS-only). A renderer MUST materialize defaults before rendering — see renderer-contract.md §"Default posture".

Cross-field rules the schema enforces

  • kind: vm ⇒ source.vmTemplate required; otherwise source.image required.
  • auth.mode: oidc | forward-auth ⇒ auth.groups present and non-empty (R-IDP-06 — access is "in an authorized group," not merely authenticated).
  • ingressRoutes[].host must match <label>.home.keithjackson.dev.
  • storage[].class ∈ {longhorn, bulk}; exposure ∈ {internal, public}; egress ∈ {internet, vpn}; auth.mode ∈ {oidc, forward-auth, native, none}.
  • additionalProperties: false everywhere — unknown keys are rejected (catches typos in review/CI).

Validate examples against the schema — L1, no cluster (R-TEST-02)

Reproducible, single command, no cluster required. Canonical tool: check-jsonschema (YAML-native, pins the schema file, pin the tool version for determinism):

# one-time (pin the version for reproducibility)
pip install check-jsonschema==0.38.0

# validate every catalog entry against the contract
check-jsonschema --schemafile schemas/servicedefinition.schema.json catalog/*.yaml
# -> "ok -- validation done"

Fallback with no extra tool (Python jsonschema + pyyaml only):

python3 - <<'PY'
import json, yaml, glob, sys
from jsonschema import Draft202012Validator
schema = json.load(open('schemas/servicedefinition.schema.json'))
Draft202012Validator.check_schema(schema)
v = Draft202012Validator(schema); bad = False
for f in sorted(glob.glob('catalog/*.yaml')):
    errs = sorted(v.iter_errors(yaml.safe_load(open(f))), key=lambda e: list(e.path))
    print(('PASS ' if not errs else 'FAIL ') + f)
    for e in errs: print('   ', list(e.path), e.message); bad = True
sys.exit(1 if bad else 0)
PY

Both are pure manifest/schema checks (L0/L1 in spec docs/14, table row "L1 Rendering") — they run in CI with no cluster and are wired into the .gitea/ schema-validation gate by WS-TEST.

See also

  • renderer-contract.md — what each field renders into (Deployment/VM, PVC, CiliumNetworkPolicy, egress steering, HTTPRoute/Certificate/DNS, ServiceMonitor/alerts, ImagePolicy, Keycloak/oauth2-proxy).
  • Example ServiceDefinitions: ../catalog/pihole.yaml, ../catalog/keycloak.yaml.