diff --git a/assets/orchestration-kestra-kv-store.png b/assets/orchestration-kestra-kv-store.png new file mode 100644 index 0000000..b195f16 Binary files /dev/null and b/assets/orchestration-kestra-kv-store.png differ diff --git a/docs/orchestration.md b/docs/orchestration.md index b9519ac..37f99a1 100644 --- a/docs/orchestration.md +++ b/docs/orchestration.md @@ -37,10 +37,24 @@ disk-cleanup: 0 app-update: 1 ``` -## Identity Split, Per Flow +## One Dedicated Identity, Not a Shared One > [!IMPORTANT] -> Most flows connect using one operator identity over SSH. One flow — the one that needs to run Ansible itself on the control node, not just reach out over SSH to a target — deliberately logs in as a separate, narrower service identity instead, since it needs local execution rights on the control node that the SSH-only identity doesn't have. Same underlying key material either way, just presented as two different accounts depending on what the flow actually needs to do locally, following the same "scope credentials to exactly what's needed" instinct as the least-privilege Git access model in [`docs/sso-and-identity.md`](sso-and-identity.md). Worth checking which one a given flow uses before assuming — it's not always the same answer, and assuming wrong is a fast way to get a confusing permission error mid-debugging. +> Every flow connects to automation using the exact same credential — a dedicated keypair that exists for this one purpose and nothing else, unrelated to the `ansible` automation identity used everywhere else in this repo. That separation is deliberate, not incidental: Kestra's SSH task plugin is a plain Java SSH client with no certificate support at all, so it can only ever authenticate with a static key — it has no way to participate in the certificate-based model the rest of the fleet's `ansible` identity uses (see [`docs/secrets-and-pki.md`](secrets-and-pki.md)). Giving Kestra its own key instead of reusing `ansible`'s means a leak of one credential can never expose the other — the blast radius of "Kestra's automation-host hop" and "the fleet-wide `ansible` identity" stay fully independent. This is the real config, not a paraphrase — every SSH task in every flow points at the same two KV entries: +> +> ``` +> $ curl -s .../flows/homelab/app-update?source=true | grep -A2 'type: io.kestra.plugin.fs.ssh.Command' +> type: io.kestra.plugin.fs.ssh.Command +> ... +> username: "{{ kv('kestra_local_user') }}" +> privateKey: "{{ kv('kestra_local_key') }}" +> ``` +> +> The SSH hop itself is only ever a means to escape Kestra's own container and reach the control node's local filesystem and tooling — it's never used to reach the wider fleet. `ansible-playbook`, once it's actually running (as a local process, invoked over that same SSH hop), makes its own outbound connections to every other fleet host, using the fleet-wide `ansible` identity and its own certificate. + +This is the actual KV Store, not a description of it — exactly two entries, nothing else: + +![Kestra's homelab-namespace KV Store: exactly two entries, kestra_local_key and kestra_local_user, the dedicated credential every flow's SSH task uses](../assets/orchestration-kestra-kv-store.png) ## A Real Special Case, Thought Through In Advance @@ -75,8 +89,8 @@ The automation host's own OS patches and Kestra's own container updates run via ``` $ systemctl list-timers automation-self-update.timer -NEXT LEFT LAST PASSED UNIT -Sat 2026-08-29 04:00:00 EEST ... Sat 2026-08-22 04:00:26 EEST - automation-self-update.timer +NEXT LEFT LAST PASSED UNIT +Sat 2026-09-12 04:00:00 EEST 6 days Sat 2026-09-05 04:00:32 EEST 4h 17min automation-self-update.timer ``` ---