add Flatcar k3d security test host
Kube-bench CIS scan / Scan ephemeral K3s cluster (push) Successful in 1m37s
Kube-bench CIS scan / Scan ephemeral K3s cluster (push) Successful in 1m37s
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user