feat: declare proposed fixed KeyCape issuer ESO source contract
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
parent
0a9617a62d
commit
104ed90f42
4 changed files with 313 additions and 0 deletions
68
docs/keycape-issuer-eso-contract.md
Normal file
68
docs/keycape-issuer-eso-contract.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Proposed fixed issuer ESO contract — RPF-1.3-T4 / NK48-R5-F2
|
||||
|
||||
Status: proposed source; not admitted, deployed, or owner-acknowledged. This is a
|
||||
new Platform declaration for review, not an existing Forgejo grant discovery.
|
||||
|
||||
The only substitution in `manifests/templates/keycape-issuer-eso.yaml`
|
||||
(sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743) is the admitted `client_id`. The proposed issuer
|
||||
store is **ClusterSecretStore `openbao-keycape-{client_id}-issuer`**, cluster
|
||||
scoped (no metadata namespace), restricted by conditions to ExternalSecrets in
|
||||
`sso`. For Forgejo it is `openbao-keycape-forgejo-issuer`. The suffix makes the
|
||||
issuer purpose explicit and keeps type-admitted IDs ending in `-` representable.
|
||||
The store's DNS subdomain name can exceed 63 characters; the existing adapter's
|
||||
63-character target Secret reference bound is preserved (client ID at most 48).
|
||||
No type parameter is added or changed.
|
||||
|
||||
The provider is `http://openbao.openbao.svc:8200`, KV v2 mount `platform`,
|
||||
Kubernetes auth mount `kubernetes`, role `external-secrets-keycape-{client_id}`,
|
||||
serviceAccountRef `external-secrets/external-secrets`, audience `openbao`.
|
||||
CCR-2026-0041 already records that Forgejo role/identity/audience, not this new
|
||||
store name or an unrestricted client family grant. Other clients still require
|
||||
their own existing owner-controlled role/case admission.
|
||||
|
||||
The ExternalSecret is `sso/keycape-{client_id}-client`, references precisely that
|
||||
ClusterSecretStore, refreshes every 5m and targets the same name with Owner
|
||||
creation and Retain remote deletion. Its only data entry maps `client-secret`
|
||||
from remoteRef `workloads/keycape/{client_id}`, property `CLIENT_SECRET`. The
|
||||
provider mount plus mount-relative remoteRef equals custody reference
|
||||
`platform/workloads/keycape/{client_id}`; do not double-prefix `platform/`.
|
||||
This is distinct from the Apps-owned consumer namespace SecretStore
|
||||
`openbao-keycape-{client_id}-consumer` and its dedicated consumer identity.
|
||||
|
||||
Store provisioning belongs to Platform's separate owner-controlled path. The
|
||||
proposed `platform-secret-delivery` lane retains ONLY create/get/watch/delete on
|
||||
the exact ExternalSecret in `sso`; this source does not delegate store creation
|
||||
or Secret read/list. Never apply the multi-document template through that lane.
|
||||
The pure `scripts/keycape_issuer_eso.py` module exposes only the ExternalSecret
|
||||
document through `external_secret()` and validates fixed public observations
|
||||
through `require_fixed_store()` and `require_ready_delivery()`. It performs no
|
||||
API call and issues no evidence/receipt. It is not a real capability or admission
|
||||
boundary; a caller-supplied observation alone establishes no authenticity.
|
||||
|
||||
The existing RPF-1.3-T4 delivery binding must validate the case/client binding,
|
||||
verify this exact preexisting store's public identity/spec/Ready state, apply or
|
||||
compare the exact ExternalSecret, and wait for matching spec plus Ready=True.
|
||||
The proposed helper accepts extra controller metadata but deliberately refuses
|
||||
spec drift. Actual API defaulting/normalization and installed ESO schema support
|
||||
must be checked in that implementation; these fixtures establish no native
|
||||
compatibility or credential isolation. ESO's shared controller remains trusted.
|
||||
No Secret bodies, values, arbitrary remoteRefs, or raw errors may enter receipts.
|
||||
|
||||
Existing owner requirements remain: closed `netkingdom.deliver-secret.v1`
|
||||
metadata receipt with authenticated before/after UID/resourceVersion provenance,
|
||||
unchanged objects reported NO_CHANGE, attempt-session revocation, and created-only
|
||||
rollback guarded by exact UID/resourceVersion (drift or uncertain ownership goes
|
||||
to owner reconciliation). The helper neither deletes a target Secret nor creates
|
||||
an OpenBao role/policy nor changes CCR-0041. These capabilities and catalog routing
|
||||
remain unimplemented/unaccepted under RPF-1.3-T4. Independent Platform source
|
||||
review of this declaration precedes adopting the conditional NetKingdom type
|
||||
text; no descriptor acceptance, acknowledgment, pin, grant, catalog or native
|
||||
permission is inferred.
|
||||
|
||||
Grounding: CCR-2026-0041 exact role/identity; the approved historical
|
||||
`docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml` issuer
|
||||
ClusterSecretStore and target pattern; `scripts/catalog_lane_scaffold.py`
|
||||
ClusterSecretStore/namespace restriction and mount-relative remoteRef model;
|
||||
RPF-1.3-C1 and RPF-1.3-T4 in the frozen owner review. The scaffold's historical
|
||||
audience field placement differs from the issuer draft; this candidate uses
|
||||
the issuer draft's serviceAccountRef.audiences, not a claim of installed support.
|
||||
46
manifests/templates/keycape-issuer-eso.yaml
Normal file
46
manifests/templates/keycape-issuer-eso.yaml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# PROPOSED SOURCE ONLY — RPF-1.3-T4 / NK48-R5-F2; not admitted or applied.
|
||||
# Platform controls this template. Only admitted client_id is substituted.
|
||||
# Provision the store through Platform's separate reviewed owner path; the
|
||||
# platform-secret-delivery lane applies ONLY the ExternalSecret document.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: openbao-keycape-{client_id}-issuer
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: http://openbao.openbao.svc:8200
|
||||
path: platform
|
||||
version: v2
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: kubernetes
|
||||
role: external-secrets-keycape-{client_id}
|
||||
serviceAccountRef:
|
||||
name: external-secrets
|
||||
namespace: external-secrets
|
||||
audiences:
|
||||
- openbao
|
||||
conditions:
|
||||
- namespaces:
|
||||
- sso
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycape-{client_id}-client
|
||||
namespace: sso
|
||||
spec:
|
||||
refreshInterval: 5m
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: openbao-keycape-{client_id}-issuer
|
||||
target:
|
||||
name: keycape-{client_id}-client
|
||||
creationPolicy: Owner
|
||||
deletionPolicy: Retain
|
||||
data:
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: workloads/keycape/{client_id}
|
||||
property: CLIENT_SECRET
|
||||
70
scripts/keycape_issuer_eso.py
Normal file
70
scripts/keycape_issuer_eso.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""Proposed fixed issuer ESO source contract for RPF-1.3-T4 / NK48-R5-F2.
|
||||
|
||||
Pure public metadata only. No API/credential/Secret access or apply path. This
|
||||
new declaration requires independent Platform review before type integration.
|
||||
The delivery lane may use only external_secret(); store provisioning is a
|
||||
separate owner action and is never delegated by this module.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TEMPLATE = ROOT / 'manifests/templates/keycape-issuer-eso.yaml'
|
||||
|
||||
|
||||
class Refused(ValueError):
|
||||
"""Fixed issuer metadata differs; never return an owner delivery receipt."""
|
||||
|
||||
|
||||
def documents(client_id: str) -> tuple[dict, dict]:
|
||||
# Match the frozen type client_id pattern and existing adapter's derived bound.
|
||||
# No caller can supply a store name/kind/namespace or an OpenBao path.
|
||||
if not isinstance(client_id, str) or not re.fullmatch(r'[a-z0-9][a-z0-9-]{1,62}', client_id):
|
||||
raise Refused('bounded admitted client_id required')
|
||||
if len('keycape-' + client_id + '-client') > 63:
|
||||
raise Refused('derived issuer reference exceeds existing adapter bound')
|
||||
store, external = yaml.safe_load_all(TEMPLATE.read_text().format(client_id=client_id))
|
||||
return store, external
|
||||
|
||||
|
||||
def external_secret(client_id: str) -> dict:
|
||||
"""The ONLY document the proposed delivery capability may apply."""
|
||||
return deepcopy(documents(client_id)[1])
|
||||
|
||||
|
||||
def _matching_public_resource(observed: dict, expected: dict) -> None:
|
||||
if not isinstance(observed, dict):
|
||||
raise Refused('public resource observation required')
|
||||
metadata = observed.get('metadata')
|
||||
if not isinstance(metadata, dict):
|
||||
raise Refused('resource metadata required')
|
||||
expected_namespace = expected['metadata'].get('namespace')
|
||||
namespace_matches = (metadata.get('namespace') == expected_namespace
|
||||
if expected_namespace else metadata.get('namespace') in (None, ''))
|
||||
if (observed.get('apiVersion') != expected['apiVersion']
|
||||
or observed.get('kind') != expected['kind']
|
||||
or metadata.get('name') != expected['metadata']['name']
|
||||
or not namespace_matches
|
||||
or observed.get('spec') != expected['spec']):
|
||||
raise Refused('fixed issuer resource differs')
|
||||
status = observed.get('status')
|
||||
conditions = status.get('conditions', []) if isinstance(status, dict) else []
|
||||
if not isinstance(conditions, list) or not any(
|
||||
isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True'
|
||||
for c in conditions):
|
||||
raise Refused('fixed issuer resource not ready')
|
||||
|
||||
|
||||
def require_fixed_store(observed: dict, client_id: str) -> None:
|
||||
"""Refuse a missing/drifted/not-ready store; never create it through delivery."""
|
||||
_matching_public_resource(observed, documents(client_id)[0])
|
||||
|
||||
|
||||
def require_ready_delivery(observed: dict, client_id: str) -> None:
|
||||
"""Spec equality and Ready precede a created/NO_CHANGE owner receipt."""
|
||||
_matching_public_resource(observed, documents(client_id)[1])
|
||||
129
tests/test_keycape_issuer_eso.py
Normal file
129
tests/test_keycape_issuer_eso.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"""Public metadata fixtures only: no API, credentials, or native calls."""
|
||||
import copy
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
import keycape_issuer_eso as contract
|
||||
|
||||
|
||||
class FixedIssuerContract(unittest.TestCase):
|
||||
def observations(self):
|
||||
resources = contract.documents("forgejo")
|
||||
for resource in resources:
|
||||
resource["metadata"].update(uid="public-fixture-uid", resourceVersion="7")
|
||||
resource["status"] = {"conditions": [{"type": "Ready", "status": "True"}]}
|
||||
return resources
|
||||
|
||||
def test_independent_forgejo_binding(self):
|
||||
# Independent owner-source example: CCR0041 role/SA/audience and the
|
||||
# existing issuer ESO draft's provider/target/relative key convention.
|
||||
store, external = contract.documents("forgejo")
|
||||
self.assertEqual(store["kind"], "ClusterSecretStore")
|
||||
self.assertEqual(store["metadata"], {"name": "openbao-keycape-forgejo-issuer"})
|
||||
self.assertEqual(store["spec"], {
|
||||
"provider": {"vault": {
|
||||
"server": "http://openbao.openbao.svc:8200", "path": "platform", "version": "v2",
|
||||
"auth": {"kubernetes": {"mountPath": "kubernetes",
|
||||
"role": "external-secrets-keycape-forgejo",
|
||||
"serviceAccountRef": {"name": "external-secrets", "namespace": "external-secrets",
|
||||
"audiences": ["openbao"]}}}}},
|
||||
"conditions": [{"namespaces": ["sso"]}]})
|
||||
self.assertEqual(external["metadata"], {"name": "keycape-forgejo-client", "namespace": "sso"})
|
||||
self.assertEqual(external["spec"], {
|
||||
"refreshInterval": "5m",
|
||||
"secretStoreRef": {"kind": "ClusterSecretStore", "name": "openbao-keycape-forgejo-issuer"},
|
||||
"target": {"name": "keycape-forgejo-client", "creationPolicy": "Owner", "deletionPolicy": "Retain"},
|
||||
"data": [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]})
|
||||
|
||||
def test_valid_ready_metadata_allows_extra_controller_metadata(self):
|
||||
store, external = self.observations()
|
||||
contract.require_fixed_store(store, "forgejo")
|
||||
contract.require_ready_delivery(external, "forgejo")
|
||||
|
||||
def test_store_drift_refuses(self):
|
||||
changes = [
|
||||
("kind", "SecretStore"),
|
||||
("metadata.namespace", "sso"),
|
||||
("metadata.name", "caller-store"),
|
||||
("spec.provider.vault.server", "https://wrong.invalid"),
|
||||
("spec.provider.vault.path", "tenant"),
|
||||
("spec.provider.vault.auth.kubernetes.role", "external-secrets-keycape-sibling"),
|
||||
("spec.provider.vault.auth.kubernetes.serviceAccountRef.audiences", ["wrong"]),
|
||||
("spec.provider.vault.auth.kubernetes.serviceAccountRef.namespace", "sso"),
|
||||
("spec.conditions", [{"namespaces": ["sso", "other"]}]),
|
||||
]
|
||||
for path, value in changes:
|
||||
with self.subTest(path=path):
|
||||
store = self.observations()[0]
|
||||
mutate(store, path, value)
|
||||
with self.assertRaises(contract.Refused):
|
||||
contract.require_fixed_store(store, "forgejo")
|
||||
|
||||
def test_external_drift_refuses(self):
|
||||
changes = [
|
||||
("metadata.namespace", ""), ("metadata.namespace", None), ("metadata.namespace", "other"),
|
||||
("metadata.name", "keycape-sibling-client"),
|
||||
("spec.secretStoreRef.kind", "SecretStore"),
|
||||
("spec.secretStoreRef.name", "openbao-keycape-forgejo-consumer"),
|
||||
("spec.target.name", "other"), ("spec.target.creationPolicy", "Merge"),
|
||||
("spec.target.deletionPolicy", "Delete"),
|
||||
("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "platform/workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
("spec.data", [{"secretKey": "wrong", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "wrong"}}]),
|
||||
]
|
||||
for path, value in changes:
|
||||
with self.subTest(path=path, value=value):
|
||||
external = self.observations()[1]
|
||||
mutate(external, path, value)
|
||||
with self.assertRaises(contract.Refused):
|
||||
contract.require_ready_delivery(external, "forgejo")
|
||||
|
||||
def test_missing_or_unready_refuses(self):
|
||||
for check, index in [(contract.require_fixed_store, 0), (contract.require_ready_delivery, 1)]:
|
||||
for status in [None, {}, [], "bad", {"conditions": None},
|
||||
{"conditions": [{"type": "Ready", "status": "False"}]}]:
|
||||
with self.subTest(check=check.__name__, status=status):
|
||||
observed = self.observations()[index]
|
||||
observed["status"] = status
|
||||
with self.assertRaises(contract.Refused):
|
||||
check(observed, "forgejo")
|
||||
for malformed in [None, [], {}, {"metadata": None}]:
|
||||
with self.assertRaises(contract.Refused):
|
||||
check(malformed, "forgejo")
|
||||
|
||||
def test_no_caller_store_or_path_override(self):
|
||||
for invalid in ["", "f", "../forgejo", "forgejo/sibling", "Forgejo", "forgejo\n", "-forgejo", "f" * 49, None]:
|
||||
with self.subTest(invalid=invalid), self.assertRaises(contract.Refused):
|
||||
contract.documents(invalid)
|
||||
with self.assertRaises(TypeError):
|
||||
contract.external_secret("forgejo", store_name="caller-store")
|
||||
|
||||
def test_type_admitted_trailing_dash_is_preserved(self):
|
||||
self.assertEqual(contract.external_secret("forgejo-")["metadata"]["name"],
|
||||
"keycape-forgejo--client")
|
||||
self.assertEqual(contract.documents("forgejo-")[0]["metadata"]["name"],
|
||||
"openbao-keycape-forgejo--issuer")
|
||||
|
||||
def test_delivery_document_is_only_external_and_detached(self):
|
||||
external = contract.external_secret("forgejo")
|
||||
self.assertEqual(external["kind"], "ExternalSecret")
|
||||
external["spec"]["secretStoreRef"]["name"] = "tampered"
|
||||
self.assertEqual(contract.external_secret("forgejo")["spec"]["secretStoreRef"]["name"],
|
||||
"openbao-keycape-forgejo-issuer")
|
||||
|
||||
|
||||
def mutate(resource, path, value):
|
||||
parts = path.split(".")
|
||||
for key in parts[:-1]:
|
||||
resource = resource[key]
|
||||
resource[parts[-1]] = copy.deepcopy(value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
Loading…
Add table
Add a link
Reference in a new issue