add Flatcar k3d security test host
Kube-bench CIS scan / Scan ephemeral K3s cluster (push) Successful in 1m37s

This commit is contained in:
2026-08-14 21:47:10 -04:00
parent e79045712d
commit 2bd22db276
3 changed files with 259 additions and 0 deletions
+7
View File
@@ -45,3 +45,10 @@ The image URL and optional SHA256 value can be overridden through environment
variables. The default URL tracks the Flatcar Stable current amd64 image. Pin
`FLATCAR_IMAGE_URL` and set `FLATCAR_IMAGE_SHA256` for a reproducible change
controlled deployment.
## Dedicated k3d test host
Use [k3d-test-host.md](k3d-test-host.md) and
`create-flatcar-k3d-test-host.sh` for a separate Flatcar VM used for Linux
k3d, Kyverno, Falco, and kube-bench testing. This VM has no Gitea runner token
and should remain separate from the CI runner trust boundary.
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env bash
# Create a Flatcar VM for disposable Linux k3d security testing.
# Run this on the Proxmox host as root.
set -Eeuo pipefail
usage() {
cat <<'EOF'
Usage:
VM_ID=9101 \
SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)" \
./create-flatcar-k3d-test-host.sh
Required environment variables:
VM_ID Proxmox VM ID that does not already exist
SSH_PUBLIC_KEY SSH public key for the Flatcar core user
Optional environment variables:
VM_NAME VM name, default flatcar-k3d-test
CORES CPU cores, default 4
MEMORY_MB Memory in MiB, default 16384
DISK_SIZE Final disk size, default 100G
BRIDGE Proxmox bridge, default vmbr0
DISK_STORAGE Storage for the VM disk, default local-lvm
CLOUDINIT_STORAGE Storage for the Cloud-Init drive, default local-lvm
SNIPPET_STORAGE Proxmox storage containing snippets, default local
SNIPPET_DIR Host snippet directory, default /var/lib/vz/snippets
IMAGE_CACHE_DIR Host image cache, default /var/lib/vz/template/cache
FLATCAR_IMAGE_URL Flatcar Stable amd64 Proxmox image URL
FLATCAR_IMAGE_SHA256 Optional expected SHA256 for the image
K3D_VERSION k3d version installed on first boot, default v5.9.0
KUBECTL_VERSION kubectl version installed on first boot, default v1.35.5
The VM uses DHCP and is intended to be a dedicated disposable Linux test host.
It is not a Gitea runner and does not receive a registration token.
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
[[ "${1:-}" != "--help" && "${1:-}" != "-h" ]] || { usage; exit 0; }
[[ "$(id -u)" -eq 0 ]] || die "run this script as root on the Proxmox host"
for command_name in qm pvesm curl sha256sum jq; do
require_command "$command_name"
done
: "${VM_ID:?VM_ID is required}"
: "${SSH_PUBLIC_KEY:?SSH_PUBLIC_KEY is required}"
VM_NAME="${VM_NAME:-flatcar-k3d-test}"
CORES="${CORES:-4}"
MEMORY_MB="${MEMORY_MB:-16384}"
DISK_SIZE="${DISK_SIZE:-100G}"
BRIDGE="${BRIDGE:-vmbr0}"
DISK_STORAGE="${DISK_STORAGE:-local-lvm}"
CLOUDINIT_STORAGE="${CLOUDINIT_STORAGE:-local-lvm}"
SNIPPET_STORAGE="${SNIPPET_STORAGE:-local}"
SNIPPET_DIR="${SNIPPET_DIR:-/var/lib/vz/snippets}"
IMAGE_CACHE_DIR="${IMAGE_CACHE_DIR:-/var/lib/vz/template/cache}"
FLATCAR_IMAGE_URL="${FLATCAR_IMAGE_URL:-https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_proxmoxve_image.img}"
K3D_VERSION="${K3D_VERSION:-v5.9.0}"
KUBECTL_VERSION="${KUBECTL_VERSION:-v1.35.5}"
[[ "$VM_ID" =~ ^[0-9]+$ ]] || die "VM_ID must be numeric"
[[ "$CORES" =~ ^[0-9]+$ ]] || die "CORES must be numeric"
[[ "$MEMORY_MB" =~ ^[0-9]+$ ]] || die "MEMORY_MB must be numeric"
if qm status "$VM_ID" >/dev/null 2>&1; then
die "VM $VM_ID already exists; choose another VM_ID"
fi
snippet_storages="$(pvesm status --content snippets 2>/dev/null || true)"
awk -v storage="$SNIPPET_STORAGE" '$1 == storage {found = 1} END {exit !found}' <<<"$snippet_storages" || die "storage $SNIPPET_STORAGE is not configured for snippets"
mkdir -p "$SNIPPET_DIR" "$IMAGE_CACHE_DIR"
chmod 0750 "$SNIPPET_DIR"
work_dir="$(mktemp -d /tmp/flatcar-k3d.XXXXXX)"
cleanup() { rm -rf "$work_dir"; }
trap cleanup EXIT
image_path="$IMAGE_CACHE_DIR/$(basename "$FLATCAR_IMAGE_URL")"
snippet_name="flatcar-k3d-test-${VM_ID}-user-data"
snippet_path="$SNIPPET_DIR/$snippet_name"
if [[ -s "$image_path" ]]; then
echo "Using cached Flatcar image: $image_path"
else
echo "Downloading Flatcar image: $FLATCAR_IMAGE_URL"
curl --fail --location --retry 3 --output "$image_path.partial" "$FLATCAR_IMAGE_URL"
mv "$image_path.partial" "$image_path"
fi
if [[ -n "${FLATCAR_IMAGE_SHA256:-}" ]]; then
printf '%s %s\n' "$FLATCAR_IMAGE_SHA256" "$image_path" | sha256sum --check --strict -
fi
bootstrap="$work_dir/bootstrap-k3d-test-host.sh"
cat >"$bootstrap" <<EOF
#!/bin/bash
set -Eeuo pipefail
mkdir -p /opt/k3d-test/bin /opt/k3d-test/workspace
chmod 0755 /opt/k3d-test /opt/k3d-test/bin /opt/k3d-test/workspace
mkdir -p /home/core/bin
chown core:core /opt/k3d-test/workspace
curl --fail --location --retry 3 \\
--output /opt/k3d-test/bin/k3d \\
https://github.com/k3d-io/k3d/releases/download/${K3D_VERSION}/k3d-linux-amd64
chmod 0755 /opt/k3d-test/bin/k3d
curl --fail --location --retry 3 \\
--output /opt/k3d-test/bin/kubectl \\
https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
chmod 0755 /opt/k3d-test/bin/kubectl
ln -sfn /opt/k3d-test/bin/k3d /home/core/bin/k3d
ln -sfn /opt/k3d-test/bin/kubectl /home/core/bin/kubectl
cat >/home/core/.profile <<'PROFILE'
export PATH="/home/core/bin:/opt/k3d-test/bin:$PATH"
PROFILE
chown -R core:core /home/core/bin /home/core/.profile
touch /opt/k3d-test/.tools-installed
EOF
bootstrap_unit="$work_dir/k3d-test-tools.service"
cat >"$bootstrap_unit" <<'EOF'
[Unit]
Description=Install k3d test host tooling
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
ConditionPathExists=!/opt/k3d-test/.tools-installed
[Service]
Type=oneshot
ExecStart=/opt/k3d-test/bootstrap-k3d-test-host.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
jq -n \
--arg ssh_key "$SSH_PUBLIC_KEY" \
--rawfile bootstrap "$bootstrap" \
--rawfile bootstrap_unit "$bootstrap_unit" \
'{
ignition: {version: "3.3.0"},
storage: {
directories: [
{path: "/opt/k3d-test", mode: 493},
{path: "/opt/k3d-test/bin", mode: 493},
{path: "/opt/k3d-test/workspace", mode: 493}
],
files: [
{path: "/opt/k3d-test/bootstrap-k3d-test-host.sh", mode: 365, contents: {source: ("data:text/plain;charset=utf-8," + ($bootstrap | @uri))}}
]
},
systemd: {
units: [
{name: "docker.service", enabled: true},
{name: "k3d-test-tools.service", enabled: true, contents: $bootstrap_unit}
]
},
passwd: {users: [{name: "core", sshAuthorizedKeys: [$ssh_key]}]}
}' >"$snippet_path"
chmod 0600 "$snippet_path"
echo "Creating Proxmox VM $VM_ID"
qm create "$VM_ID" \
--name "$VM_NAME" \
--ostype l26 \
--cores "$CORES" \
--memory "$MEMORY_MB" \
--net0 "virtio,bridge=$BRIDGE" \
--ipconfig0 ip=dhcp \
--agent enabled=1 \
--onboot 1 \
--scsihw virtio-scsi-single
qm disk import "$VM_ID" "$image_path" "$DISK_STORAGE"
qm set "$VM_ID" --scsi0 "$DISK_STORAGE:vm-${VM_ID}-disk-0"
qm resize "$VM_ID" scsi0 "$DISK_SIZE"
qm set "$VM_ID" --boot order=scsi0
qm set "$VM_ID" --ide2 "$CLOUDINIT_STORAGE:cloudinit"
qm set "$VM_ID" --cicustom "user=${SNIPPET_STORAGE}:snippets/${snippet_name}"
qm start "$VM_ID"
cat <<EOF
Flatcar k3d test host created.
VM ID: $VM_ID
VM name: $VM_NAME
Snippet: $snippet_path
After DHCP assigns an address, verify:
ssh core@<vm-ip> 'systemctl status docker k3d-test-tools --no-pager'
ssh core@<vm-ip> 'k3d version && kubectl version --client'
The test host has no Gitea token and is separate from the CI runner.
EOF
+40
View File
@@ -0,0 +1,40 @@
# Flatcar k3d test host
`create-flatcar-k3d-test-host.sh` creates a dedicated Flatcar VM for Linux
k3d testing. It is separate from the Gitea runner and does not receive a
runner registration token or a repository credential.
The default profile is 4 CPU cores, 16 GiB memory, and a 100 GiB disk. The
Ignition configuration enables Docker, creates `/opt/k3d-test/workspace`, and
installs pinned k3d and kubectl binaries on first boot.
Enable snippets on the Proxmox storage first:
```bash
pvesm set local --content iso,vztmpl,backup,snippets
```
Create the VM:
```bash
VM_ID=9101 \
SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)" \
DISK_STORAGE=pve1_nvme \
SNIPPET_STORAGE=local \
bash create-flatcar-k3d-test-host.sh
```
After the VM receives a DHCP address:
```bash
ssh core@<vm-ip> 'systemctl status docker k3d-test-tools --no-pager'
ssh core@<vm-ip> 'k3d version && kubectl version --client'
```
Clone the repository into the test workspace, then run the project tests from
the Flatcar host. The Docker socket remains local to this VM, so the MacBook
only acts as the SSH operator workstation.
The VM is intentionally dedicated to disposable security testing. Do not run
untrusted workloads or unrelated services on it. k3d creates K3s nodes as
Docker containers, so the host still has broad control over the test cluster.