From c5ebb2e044adf791250dacdb8e67364fa6fbb88a Mon Sep 17 00:00:00 2001 From: kacerr Date: Thu, 10 Sep 2026 18:53:04 +0000 Subject: [PATCH] Worked example and make target for the kvm-ssh Secret docs/kvm-provider.md walks through key generation, ssh-copy-id, ssh-keyscan under the address the operator dials, and the Secret from the private key plus ~/.ssh/kvm_known_hosts. make kvm-ssh-secret runs the same as an apply with KVM_SSH_KEY / KVM_KNOWN_HOSTS overrides and refuses a .pub, the classic mistake. README mirrors the short form. Co-Authored-By: Claude Fable 5.1 --- Makefile | 14 ++++++++++++++ README.md | 10 +++++++--- docs/kvm-provider.md | 44 +++++++++++++++++++++++++++++++++----------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index b621dac..b7ab1ce 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,20 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified @out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \ if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -; else echo "No CRDs to delete; skipping."; fi +KVM_SSH_KEY ?= $(HOME)/.ssh/proxy-operator_ed25519 +KVM_KNOWN_HOSTS ?= $(HOME)/.ssh/kvm_known_hosts +KVM_SSH_NAMESPACE ?= egress-proxies-operator-system + +.PHONY: kvm-ssh-secret +kvm-ssh-secret: ## Create or update the kvm-ssh Secret from KVM_SSH_KEY (private key) and KVM_KNOWN_HOSTS. + @test -f "$(KVM_SSH_KEY)" || { echo "KVM_SSH_KEY=$(KVM_SSH_KEY) not found"; exit 1; } + @test -f "$(KVM_KNOWN_HOSTS)" || { echo "KVM_KNOWN_HOSTS=$(KVM_KNOWN_HOSTS) not found"; exit 1; } + @grep -q 'PRIVATE KEY' "$(KVM_SSH_KEY)" || { echo "KVM_SSH_KEY=$(KVM_SSH_KEY) is not a private key (did you pass the .pub?)"; exit 1; } + "$(KUBECTL)" -n $(KVM_SSH_NAMESPACE) create secret generic kvm-ssh \ + --from-file=id_ed25519="$(KVM_SSH_KEY)" \ + --from-file=known_hosts="$(KVM_KNOWN_HOSTS)" \ + --dry-run=client -o yaml | "$(KUBECTL)" apply -f - + .PHONY: deploy deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG} diff --git a/README.md b/README.md index 8dc4df5..c8a7dbe 100644 --- a/README.md +++ b/README.md @@ -139,12 +139,16 @@ Managed proxies as VMs on libvirt/KVM hosts you own. Full guide: 1. Each host needs `libvirtd`, an SSH user in the `libvirt` group, a bridge to a network with DHCP, and a storage pool. -2. Create the `kvm-ssh` Secret (private key + `known_hosts`); the manager - mounts it at `/etc/proxy-operator/kvm`: +2. Create the `kvm-ssh` Secret from the operator's **private** key and a + `known_hosts` with each host's key; the manager mounts it at + `/etc/proxy-operator/kvm`: ```sh + ssh-keyscan -H >> ~/.ssh/kvm_known_hosts kubectl -n egress-proxies-operator-system create secret generic kvm-ssh \ - --from-file=id_ed25519= --from-file=known_hosts= + --from-file=id_ed25519=$HOME/.ssh/proxy-operator_ed25519 \ + --from-file=known_hosts=$HOME/.ssh/kvm_known_hosts + # or: make kvm-ssh-secret (same paths by default; KVM_SSH_KEY / KVM_KNOWN_HOSTS to override) ``` 3. Add a `kvm` entry to the providers config with `images`, diff --git a/docs/kvm-provider.md b/docs/kvm-provider.md index 718d71f..322d573 100644 --- a/docs/kvm-provider.md +++ b/docs/kvm-provider.md @@ -43,20 +43,42 @@ ssh virt@nas1.home.lan ip link show br0 ## Cluster-side setup -1. Create the SSH Secret. `known_hosts` must contain every host's key - (`ssh-keyscan -H nas1.home.lan >> known_hosts`); without it, set +1. Create the SSH Secret from a key pair dedicated to the operator and a + `known_hosts` file holding every host's key. The worked example below + uses `~/.ssh/proxy-operator_ed25519` and `~/.ssh/kvm_known_hosts`, + the same paths the integration test and `make kvm-ssh-secret` default + to. + + ```sh + # once: a key pair for the operator; the .pub goes on every host + ssh-keygen -t ed25519 -f ~/.ssh/proxy-operator_ed25519 -N '' -C proxy-operator + ssh-copy-id -i ~/.ssh/proxy-operator_ed25519.pub virt@nas1.home.lan + + # once per host: its host key, under the address the *operator* will + # dial (hosts[].address), which may differ from what your laptop uses + ssh-keyscan -H nas1.home.lan >> ~/.ssh/kvm_known_hosts + + # the Secret: the PRIVATE key (not the .pub) plus known_hosts + kubectl -n egress-proxies-operator-system create secret generic kvm-ssh \ + --from-file=id_ed25519=$HOME/.ssh/proxy-operator_ed25519 \ + --from-file=known_hosts=$HOME/.ssh/kvm_known_hosts + ``` + + `make kvm-ssh-secret` runs that last command as an apply, so it also + updates an existing Secret; override the paths with + `KVM_SSH_KEY=... KVM_KNOWN_HOSTS=...`. Putting the `.pub` in the + Secret is the classic mistake: the operator then fails at startup + with a "not a private key" error. Without `known_hosts`, set `insecureIgnoreHostKey: true` and accept that a MITM could hand you a fake hypervisor. - ```sh - kubectl -n egress-proxies-operator-system create secret generic kvm-ssh \ - --from-file=id_ed25519=$HOME/.ssh/proxy-operator_ed25519 \ - --from-file=known_hosts=./known_hosts - ``` - - `config/manager/manager.yaml` mounts it (optional) at - `/etc/proxy-operator/kvm`; the deployment starts fine without it as - long as no `kvm` provider is configured. + `config/manager/manager.yaml` mounts the Secret (optional) at + `/etc/proxy-operator/kvm` as two files, `id_ed25519` and + `known_hosts`, which is what `sshKeyFile` and `knownHostsFile` in the + provider config point at. The deployment starts fine without the + Secret as long as no `kvm` provider is configured. Rotating the key: + re-run the command (or the make target) and restart the manager; it + reads both files once at startup. 2. Add a provider entry to the providers config (`config/manager/providers_config.yaml` in-cluster; full sample in