Record verified production Clock custody and authority acceptance
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6
This commit is contained in:
parent
e70ef2f32a
commit
3bd3a2e87b
5 changed files with 102 additions and 8 deletions
|
|
@ -3,7 +3,7 @@ kind: credential-change-request
|
|||
schema_version: 1
|
||||
request_type: attended-host-key-delivery
|
||||
title: Railiance Clock authority signing key custody and host delivery
|
||||
status: approved
|
||||
status: verified
|
||||
created: '2026-09-15'
|
||||
updated: '2026-09-15'
|
||||
requester:
|
||||
|
|
@ -53,7 +53,7 @@ openbao:
|
|||
access_frontdoor:
|
||||
type: attended-host-file
|
||||
catalog_id: openbao-platform-admin-login
|
||||
readiness: approved-pending-apply
|
||||
readiness: ready
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: attended-host-file
|
||||
|
|
@ -93,7 +93,18 @@ verification:
|
|||
host key verification is required.
|
||||
- Fresh public key, artifact SHA256, readiness and signed sample acceptance are
|
||||
recorded.
|
||||
evidence: []
|
||||
evidence:
|
||||
- kind: attended_host_custody
|
||||
result: passed
|
||||
receipt: docs/evidence/2026-09-15-railiance-clock-host-custody.json
|
||||
details: OpenBao KV version 1 and matching public-key fingerprint on private host
|
||||
file; contained login, child operation and self-revocation succeeded.
|
||||
- kind: production_authority_acceptance
|
||||
result: passed
|
||||
receipt: docs/evidence/2026-09-15-railiance-clock-production.json
|
||||
details: Active/enabled private authority; pinned wheel; bounded host health;
|
||||
three signed workstation samples cross-checked through SSH, wrong key id refused.
|
||||
No standing workload read grant.
|
||||
lifecycle:
|
||||
deactivate: Stop authority and withdraw consumer trust. Retain custody and host
|
||||
key until owner retention decision.
|
||||
|
|
|
|||
|
|
@ -34,3 +34,23 @@ Rotation is a separate explicit operation: stop the authority, CAS against the
|
|||
known current custody version, replace the host key atomically, and distribute
|
||||
new public key/epoch trust before resuming clients. This initial-admission helper
|
||||
deliberately does not implement rotation or overwrite keys.
|
||||
|
||||
## Accepted production deployment
|
||||
|
||||
Version 1 custody and native workstation samples passed on 2026-09-15. See
|
||||
`docs/evidence/2026-09-15-railiance-clock-host-custody.json` and
|
||||
`docs/evidence/2026-09-15-railiance-clock-production.json`.
|
||||
Use the documented private OpenBao endpoint `http://127.0.0.1:18200` for the
|
||||
attended envelope: the configured public hostname presented an untrusted
|
||||
certificate during deployment. TLS verification was not disabled.
|
||||
|
||||
Authority endpoint on Railiance01: `http://127.0.0.1:8787/v1/time-samples`;
|
||||
readiness: `http://127.0.0.1:8787/readyz`. Workstations use an authenticated SSH
|
||||
loopback connection and separately admitted public key/epoch. Trust files last
|
||||
at most 15 minutes and do not survive a workstation boot. Optional integrations
|
||||
are not globally enabled by this deployment; operational trust refresh remains
|
||||
in RCLK-WP-0005-T06 and consumer adoption in RCLK-WP-0004.
|
||||
|
||||
The health export sandbox allows read-only adjtimex, which ProtectClock would
|
||||
block even with modes=0. Empty bounding/ambient capability sets deny clock writes.
|
||||
The authority keeps ProtectClock enabled.
|
||||
|
|
|
|||
15
docs/evidence/2026-09-15-railiance-clock-host-custody.json
Normal file
15
docs/evidence/2026-09-15-railiance-clock-host-custody.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"schema": "platform.railiance-clock-host-custody.v1",
|
||||
"status": "delivered_pending_authority_acceptance",
|
||||
"stage": "complete",
|
||||
"ccr": "CCR-2026-0028",
|
||||
"kv_version": 1,
|
||||
"kid": "railiance01-clock-20260915-v1",
|
||||
"public_key_pem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6dgMrD13yE5PKBxDSl7ledal2vwd\nDmPZEjdYW8UK48Abto6dfuJfjCHeC+Jl3w1D5VZLfbbzD114ymKTszE80A==\n-----END PUBLIC KEY-----\n",
|
||||
"public_key_sha256": "bd583446b5ed61d086806b2a0c5aaf33a875b751e45599e75335d1f415be609a",
|
||||
"host": {
|
||||
"public_key_sha256": "bd583446b5ed61d086806b2a0c5aaf33a875b751e45599e75335d1f415be609a",
|
||||
"owner": "railiance-clock",
|
||||
"mode": "0600"
|
||||
}
|
||||
}
|
||||
42
docs/evidence/2026-09-15-railiance-clock-production.json
Normal file
42
docs/evidence/2026-09-15-railiance-clock-production.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"scope": "production authority acceptance",
|
||||
"host": "railiance01",
|
||||
"client": "Linux/WSL via SSH loopback",
|
||||
"os_clocks_changed": false,
|
||||
"samples": [
|
||||
{
|
||||
"width_ms": 101.667802,
|
||||
"railiance_minus_local_midpoint_ms": 253.602519,
|
||||
"independent_host_cross_check": true
|
||||
},
|
||||
{
|
||||
"width_ms": 108.062875,
|
||||
"railiance_minus_local_midpoint_ms": 325.159405,
|
||||
"independent_host_cross_check": true
|
||||
},
|
||||
{
|
||||
"width_ms": 160.436282,
|
||||
"railiance_minus_local_midpoint_ms": 371.73663,
|
||||
"independent_host_cross_check": true
|
||||
}
|
||||
],
|
||||
"wrong_kid_refused": true,
|
||||
"epoch": "b1164ccb-a4c2-4cc8-adf8-1d5597de697b",
|
||||
"kid": "railiance01-clock-20260915-v1",
|
||||
"public_key_sha256": "bd583446b5ed61d086806b2a0c5aaf33a875b751e45599e75335d1f415be609a",
|
||||
"trust_lifetime_seconds": 900,
|
||||
"wheel_sha256": "89d1419f038eff485a27a4206a19101ce7ca2976092971bbc796a77188fc93ad",
|
||||
"listener": "127.0.0.1:8787",
|
||||
"service_active": true,
|
||||
"service_enabled": true,
|
||||
"authority_clock_write_capabilities": [],
|
||||
"health_clock_write_capabilities": [],
|
||||
"readiness": "usable",
|
||||
"key_custody": "CCR-2026-0028 version 1",
|
||||
"final_ansible_convergence": {
|
||||
"ok": 29,
|
||||
"changed": 0,
|
||||
"failed": 0,
|
||||
"unreachable": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -493,7 +493,7 @@ def render_summary(ccr: dict[str, Any], warnings: list[str]) -> str:
|
|||
f" {openbao['kv_path']}",
|
||||
f" {fields}",
|
||||
"Policy:",
|
||||
f" {openbao['policy_name']}",
|
||||
f" {openbao.get('policy_name', 'none (attended host delivery)')}",
|
||||
"Auth binding:",
|
||||
]
|
||||
if auth.get("method") == "token":
|
||||
|
|
@ -664,6 +664,8 @@ def auth_payload(ccr: dict[str, Any]) -> dict[str, Any]:
|
|||
|
||||
|
||||
def render_plan(ccr: dict[str, Any]) -> str:
|
||||
if ccr.get("request_type") == "attended-host-key-delivery":
|
||||
return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created."
|
||||
openbao = ccr["openbao"]
|
||||
auth = openbao["auth"]
|
||||
payload = auth_payload(ccr)
|
||||
|
|
@ -693,6 +695,8 @@ def render_plan(ccr: dict[str, Any]) -> str:
|
|||
|
||||
|
||||
def render_operator_commands(ccr: dict[str, Any]) -> str:
|
||||
if ccr.get("request_type") == "attended-host-key-delivery":
|
||||
return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created."
|
||||
openbao = ccr["openbao"]
|
||||
auth = openbao["auth"]
|
||||
auth_path = f"auth/{auth['mount']}/role/{auth['role']}"
|
||||
|
|
@ -898,7 +902,7 @@ def applier_dry_run_payload(ccr: dict[str, Any], warnings: list[str]) -> dict[st
|
|||
{
|
||||
"kind": "policy_write",
|
||||
"openbao_path": f"sys/policies/acl/{openbao['policy_name']}",
|
||||
"policy_name": openbao["policy_name"],
|
||||
"policy_name": openbao.get("policy_name"),
|
||||
"source": openbao["policy_file"],
|
||||
"body": generated_policy_hcl(ccr).rstrip(),
|
||||
},
|
||||
|
|
@ -998,6 +1002,8 @@ def render_applier_apply_plan(ccr: dict[str, Any], warnings: list[str]) -> str:
|
|||
|
||||
|
||||
def runbook_readiness_blockers(ccr: dict[str, Any]) -> list[str]:
|
||||
if ccr.get("request_type") == "attended-host-key-delivery":
|
||||
return ["use the attended owner procedure in docs/credential-lane-designs/railiance-clock-host-delivery.md"]
|
||||
blockers: list[str] = []
|
||||
status = ccr.get("status")
|
||||
if status not in RUNBOOK_ALLOWED_STATUSES:
|
||||
|
|
@ -1281,7 +1287,7 @@ def lifecycle_payload(ccr: dict[str, Any], action: str) -> dict[str, Any]:
|
|||
"openbao": {
|
||||
"secret_path": openbao["kv_path"],
|
||||
"fields": openbao["fields"],
|
||||
"policy_name": openbao["policy_name"],
|
||||
"policy_name": openbao.get("policy_name"),
|
||||
"auth_role_path": auth_role_path,
|
||||
"disable_commands": disable_commands,
|
||||
},
|
||||
|
|
@ -1627,7 +1633,7 @@ def status_payload(ccr: dict[str, Any], warnings: list[str]) -> dict[str, Any]:
|
|||
"mount": openbao["mount"],
|
||||
"kv_path": openbao["kv_path"],
|
||||
"fields": openbao["fields"],
|
||||
"policy_name": openbao["policy_name"],
|
||||
"policy_name": openbao.get("policy_name"),
|
||||
"auth_mount": auth.get("mount"),
|
||||
"auth_method": auth.get("method"),
|
||||
"auth_role": auth.get("role"),
|
||||
|
|
@ -1783,7 +1789,7 @@ def decision_template_context(ccr: dict[str, Any]) -> dict[str, str]:
|
|||
return {
|
||||
"id": ccr["id"],
|
||||
"kv_path": openbao["kv_path"],
|
||||
"policy_name": openbao["policy_name"],
|
||||
"policy_name": openbao.get("policy_name"),
|
||||
"auth_role_path": f"auth/{auth['mount']}/role/{auth['role']}",
|
||||
"decision_link": state_hub.get("decision_api_url")
|
||||
or state_hub.get("decision_dashboard_url")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue