2026-09-29 21:40:47 +02:00
spec : runbook-pack/v0.2
2026-09-29 00:25:18 +02:00
id : nk.ssh-certificates
title : Short-lived SSH credentials for admins, agents and automations
owner : net-kingdom
outcome : An actor obtains a short-lived CA-signed SSH certificate and uses it through an ops-bridge tunnel, with no static key doing the work.
engine : native
2026-09-29 21:40:47 +02:00
framing :
2026-09-29 00:25:18 +02:00
- Certificate files must be mode 600 and are never reused across reconnects.
- A non-zero cert_command exit is a failure and must trigger backoff.
- ops-warden never vends API keys or passwords; route them with warden route find.
2026-09-29 21:40:47 +02:00
actors :
- id : operator
role : Operator
description : Signs and inspects the certificate and brings the tunnel up, using their own identity.
privileges : [ sign-certificate, open-tunnel]
datapoints :
- id : actor_name
label : Actor name
type : string
2026-10-02 21:52:02 +02:00
default : agt-rehearsal
2026-09-29 21:40:47 +02:00
pattern : "(adm|agt|atm)-[a-z0-9-]+"
2026-10-02 21:52:02 +02:00
help : "Actor type prefix decides the maximum TTL: adm 48h, agt 24h, atm 8h. agt-rehearsal is an inert test actor (2h, a principal no host trusts); use your own actor for real access."
2026-09-29 21:40:47 +02:00
dimensions : {actor: operator, kind: identity, subsystem : ops-warden}
ask_in : ready
2026-10-02 22:01:44 +02:00
- id : warden_config
label : warden config file
type : path
default : ~/.config/warden/rehearsal/warden.yaml
help : The rehearsal config uses a throwaway local CA that nothing trusts and has no flex-auth gate, so it proves the signing mechanics, not the policy gate. For real access use your production config, ~/.config/warden/warden.yaml.
dimensions : {actor: operator, kind: location, subsystem : ops-warden}
ask_in : ready
2026-09-29 21:40:47 +02:00
- id : pubkey
label : Public key path
type : path
2026-10-02 21:52:02 +02:00
default : ~/.ssh/agt-rehearsal_ed25519.pub
2026-09-29 21:40:47 +02:00
dimensions : {actor: operator, kind: location, subsystem : ops-warden}
ask_in : ready
- id : tunnel
label : Tunnel name
type : string
default : k3s-api-railiance01
pattern : "[a-z0-9-]+"
dimensions : {kind: target, subsystem : ops-bridge}
ask_in : ready
2026-10-02 21:52:02 +02:00
- id : test_target
label : Is there a target that trusts your actor's principal?
type : enum
values : [ available, none-yet]
default : none-yet
help : With the inert rehearsal actor no host trusts the certificate, so the tunnel cannot be proven. Choose available only with a real actor and a tunnel that uses it.
dimensions : {kind: state, subsystem : ops-bridge}
ask_in : connect
2026-09-29 21:40:47 +02:00
stages :
- id : ready
title : Before you start
goal : You know what is in place and which certificates exist already.
steps :
- id : prerequisites
title : Confirm the prerequisites
actor : operator
owner : workstation
description : "In place before you start: the warden CLI is installed and your actor is in the principals inventory (ops-warden); the bridge CLI and a tunnel definition exist (ops-bridge); the target hosts trust the SSH CA and carry the actor principal (railiance-infra)."
idempotent : idempotent
verify :
done_when : All three prerequisites hold
expect : manual
- id : status-before
title : Look at current certificates
actor : operator
owner : ops-warden
requires : [ prerequisites]
2026-10-02 22:01:44 +02:00
uses : [ warden_config]
command : WARDEN_CONFIG={{warden_config}} warden status
2026-09-29 21:40:47 +02:00
idempotent : idempotent
verify :
done_when : You know which certificates are current and which are expired
expect : manual
- id : get-certificate
title : Get a certificate
goal : A short-lived certificate for the actor exists and its lifetime is within the limit for the actor type.
requires : [ ready]
steps :
- id : sign
title : Sign a public key for the actor
actor : operator
owner : ops-warden
2026-10-02 22:01:44 +02:00
uses : [ actor_name, pubkey, warden_config]
command : WARDEN_CONFIG={{warden_config}} warden sign {{actor_name}} --pubkey {{pubkey}} > /tmp/{{actor_name}}-cert.pub
2026-09-29 21:40:47 +02:00
idempotent : idempotent
idempotent_note : Each run issues a new certificate and overwrites the file; the old one expires on its own.
rollback : Delete /tmp/{{actor_name}}-cert.pub; the certificate expires on its own.
evidence : [ certificate_valid_before]
verify :
done_when : A certificate file exists and is readable as a certificate
command : ssh-keygen -L -f /tmp/{{actor_name}}-cert.pub
expect : exit-0
- id : inspect-ttl
title : Check the certificate lifetime
actor : operator
owner : ops-warden
requires : [ sign]
uses : [ actor_name]
command : ssh-keygen -L -f /tmp/{{actor_name}}-cert.pub | grep -E "Key ID|Principals|Valid"
idempotent : idempotent
verify :
done_when : "Valid before is within the actor-type TTL (adm 48h, agt 24h, atm 8h)"
expect : manual
gates :
- id : certificate-valid
title : The certificate is valid and short-lived
goal : short-lived-credentials
blocking : true
downstream_risk : A tunnel opened with a certificate that outlives its limit defeats the point of short-lived credentials.
checks :
- done_when : The lifetime is within the limit for the actor type
expect : manual
- done_when : The certificate file can be read
command : ssh-keygen -L -f /tmp/{{actor_name}}-cert.pub
expect : exit-0
- id : connect
title : Use it through a tunnel
goal : The tunnel that uses the certificate is up and the signing is on record.
requires : [ "gate:certificate-valid" ]
steps :
- id : tunnel-up
title : Bring up the tunnel that uses cert_command
actor : operator
owner : ops-bridge
2026-10-02 21:52:02 +02:00
uses : [ tunnel, test_target]
description : A tunnel only proves the certificate if a host trusts its principal. With the inert rehearsal actor none does, so record that no target exists yet; the stage is then not proven.
2026-09-29 21:40:47 +02:00
idempotent : idempotent
2026-10-02 21:52:02 +02:00
one_of :
- id : bring-up
title : Bring up the tunnel that uses the certificate
recommended_when : {test_target : [ available]}
command : bridge up {{tunnel}}
rollback : bridge down {{tunnel}}
verify :
done_when : The tunnel shows connected
command : bridge status
expect : output-contains
contains : "{{tunnel}}"
- id : no -target-yet
title : No target trusts this actor yet; tunnel not proven
recommended_when : {test_target : [ none-yet]}
verify :
done_when : You have recorded that the tunnel stage was not proven
expect : manual
2026-09-29 21:40:47 +02:00
- id : audit
title : Confirm the signing was audited
actor : operator
owner : ops-warden
requires : [ tunnel-up]
2026-10-02 22:01:44 +02:00
uses : [ warden_config]
command : WARDEN_CONFIG={{warden_config}} warden log | tail -5
2026-09-29 21:40:47 +02:00
idempotent : idempotent
verify :
done_when : Your signing appears in the history
expect : manual
gates :
- id : tunnel-connected
2026-10-02 21:52:02 +02:00
title : The tunnel is connected, or its absence is recorded
2026-09-29 21:40:47 +02:00
goal : private-access
blocking : true
2026-10-02 21:52:02 +02:00
downstream_risk : A pass here could be read as proof that private access works.
2026-09-29 21:40:47 +02:00
checks :
2026-10-02 21:52:02 +02:00
- done_when : bridge reports the tunnel as connected, or "tunnel not proven" is recorded because no target exists
expect : manual
2026-09-29 00:25:18 +02:00
ownership :
- {concern: Certificate issuance and TTL policy, owner : ops-warden}
- {concern: Tunnel lifecycle and refresh, owner : ops-bridge}
- {concern: Host CA trust and principals, owner : railiance-infra}