Operator decision on PR #81: no CI job may reach a privileged daemon. The act runner is untouched. - platform/buildkit (+ clusters/base/buildkit.yaml): moby/buildkit v0.34.1-rootless pinned by digest, uid 1000, not privileged; caps drop ALL + SETUID/SETGID, seccomp/AppArmor Unconfined, read-only rootfs (each measured under podman). cert-manager CA + server/client certs, ClusterIP :1234, default-deny with ingress only from the act-runner pod (DinD masquerades jobs to it). No Kyverno exception: every applied policy matches app-namespace=true only. - Each Containerfile names its shipped stage and ends in a `verify` stage holding every former `docker run` assertion (uid 65532, RUN --network=none, writable-path inventory standing in for the read-only rootfs). The old `&& echo` checks that could never fail are real assertions now. - images.sh drives buildkitd with a pinned buildx (sha256-checked) via bake; PRs build verify targets with agent-runner FROM the in-run toolbox (target: context); main verifies, then pushes. Fails loudly when the builder secrets are absent or buildkitd is unreachable. - infra/gitea/buildkit-client-secret.sh copies the client cert into Actions secrets. Co-Authored-By: Claude Opus 5.5 <[email protected]>
146 lines
9.2 KiB
Docker
146 lines
9.2 KiB
Docker
# Reproducible dev toolbox for the home-network-k3s platform (Phase 1+).
|
|
# Bakes the NON-Python prereqs (OpenTofu, age, sops, flux) plus ansible + kube tooling
|
|
# AND the pinned conformance-harness toolchain (kyverno CLI, kyverno-json, chainsaw,
|
|
# yamllint, check-jsonschema, yq, promtool) into a single pinned baseline image, so `make -C ci gate`
|
|
# runs identically here and in CI. This is the Proxmox (bpg/proxmox) toolbox + conformance
|
|
# harness: OpenTofu drives the Proxmox-hosted k3s cluster via the bpg provider (dev.sh opens
|
|
# an SSH tunnel to the kube-API), NOT a local libvirt/KVM host. The libvirt-client/qemu-img
|
|
# packages are retained as harmless disk-image utilities. Fedora base is pinned for
|
|
# reproducibility.
|
|
ARG FEDORA_VERSION=44
|
|
# Stage `toolbox` is the image that ships. Stage `verify` (end of file) is a TEST stage: it is
|
|
# built FROM the finished image and only runs assertions, and it is never pushed. Build with
|
|
# `--target toolbox` for the image and `--target verify` to prove it (build-image.sh does both).
|
|
FROM registry.fedoraproject.org/fedora:${FEDORA_VERSION} AS toolbox
|
|
|
|
ARG OPENTOFU_VERSION
|
|
ARG AGE_VERSION
|
|
ARG SOPS_VERSION
|
|
ARG FLUX_VERSION
|
|
ARG KUBECTL_VERSION
|
|
ARG HELM_VERSION
|
|
# Conformance test toolchain (pinned to match ci/versions.env).
|
|
ARG KYVERNO_VERSION
|
|
ARG KYVERNO_JSON_VERSION
|
|
ARG CHAINSAW_VERSION
|
|
ARG YAMLLINT_VERSION
|
|
ARG YQ_VERSION
|
|
ARG CHECK_JSONSCHEMA_VERSION
|
|
# promtool: the engine that evaluates the alert-rule UNIT TESTS (platform/**/*.promtool-test.yaml).
|
|
# Pinned to the Prometheus version prod actually runs — see versions.env for why that lockstep is
|
|
# the point, and for the live-cluster command that establishes it.
|
|
ARG PROMTOOL_VERSION
|
|
ARG TARGETARCH=amd64
|
|
|
|
# Distro packages: libvirt client libs (harmless disk-image utilities), ansible, ssh,
|
|
# disk-image + cloud-init seed tooling, python3-pip (for the pip-installed test tools),
|
|
# diffutils (provides `diff`, used by L1 golden render checks), and common utilities.
|
|
#
|
|
# wireguard-tools + qrencode serve the REMOTE-ACCESS VPN (platform/vpn-ingress):
|
|
# * wireguard-tools gives `wg genkey` / `wg pubkey`, needed to mint the server key and to derive
|
|
# every client profile's `[Peer] PublicKey` (infra/vpn-ingress/new-peer.sh).
|
|
# * qrencode renders a client profile as a QR code, which is how a PHONE gets enrolled.
|
|
# They are here rather than on the operator's host because this image is THE toolchain
|
|
# (CLAUDE.md §5; README "Don't install the toolchain on the host"). That rule was briefly broken
|
|
# by a runbook that told the operator to run `wg` on the host and `sops` in here, and then split
|
|
# every step across the boundary to work around it — a procedure nobody should have to follow, and
|
|
# one that cannot be reproduced in CI. Both tools in one image means `sops -d … | wg pubkey` is
|
|
# just a pipeline again.
|
|
# Unpinned on purpose, like every other dnf package here: the Fedora base is what is pinned
|
|
# (FEDORA_VERSION), and these are not gate tools whose version changes what green means.
|
|
RUN dnf -y install --setopt=install_weak_deps=False \
|
|
libvirt-client qemu-img cloud-utils genisoimage openssh-clients \
|
|
ansible-core python3-libvirt python3-pip diffutils \
|
|
wireguard-tools qrencode \
|
|
git make jq unzip tar gzip xz which ca-certificates curl \
|
|
&& dnf clean all
|
|
|
|
# Pinned Go binaries from official releases.
|
|
RUN set -eux; \
|
|
curl -fsSL "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/tofu_${OPENTOFU_VERSION}_linux_${TARGETARCH}.zip" -o /tmp/tofu.zip; \
|
|
unzip -d /usr/local/bin /tmp/tofu.zip tofu; chmod +x /usr/local/bin/tofu; \
|
|
curl -fsSL "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
|
install -m0755 /tmp/age/age /tmp/age/age-keygen /usr/local/bin/; \
|
|
curl -fsSL "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.${TARGETARCH}" -o /usr/local/bin/sops; chmod +x /usr/local/bin/sops; \
|
|
curl -fsSL "https://github.com/fluxcd/flux2/releases/download/v${FLUX_VERSION}/flux_${FLUX_VERSION}_linux_${TARGETARCH}.tar.gz" | tar -xz -C /usr/local/bin flux; \
|
|
curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl; chmod +x /usr/local/bin/kubectl; \
|
|
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
|
install -m0755 "/tmp/linux-${TARGETARCH}/helm" /usr/local/bin/helm; \
|
|
rm -rf /tmp/*
|
|
|
|
# Pinned conformance test toolchain (L0/L1 static gate + the L2/L3 Chainsaw runner).
|
|
# Arch strings differ per tool: yq/chainsaw/kyverno-json use ${TARGETARCH} (amd64), the
|
|
# kyverno CLI tarball uses x86_64 — map amd64 -> x86_64 for it only.
|
|
RUN set -eux; \
|
|
python3 -m pip install --no-cache-dir --break-system-packages \
|
|
"yamllint==${YAMLLINT_VERSION}" "check-jsonschema==${CHECK_JSONSCHEMA_VERSION}"; \
|
|
curl -fsSL "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_${TARGETARCH}" -o /usr/local/bin/yq; \
|
|
chmod +x /usr/local/bin/yq; \
|
|
curl -fsSL "https://github.com/kyverno/chainsaw/releases/download/v${CHAINSAW_VERSION}/chainsaw_linux_${TARGETARCH}.tar.gz" | tar -xz -C /usr/local/bin chainsaw; \
|
|
KYVERNO_CLI_ARCH="$([ "${TARGETARCH}" = amd64 ] && echo x86_64 || echo "${TARGETARCH}")"; \
|
|
curl -fsSL "https://github.com/kyverno/kyverno/releases/download/v${KYVERNO_VERSION}/kyverno-cli_v${KYVERNO_VERSION}_linux_${KYVERNO_CLI_ARCH}.tar.gz" | tar -xz -C /usr/local/bin kyverno; \
|
|
curl -fsSL "https://github.com/kyverno/kyverno-json/releases/download/v${KYVERNO_JSON_VERSION}/kyverno-json_linux_${TARGETARCH}.tar.gz" | tar -xz -C /usr/local/bin kyverno-json; \
|
|
PROM_TARBALL="prometheus-${PROMTOOL_VERSION}.linux-${TARGETARCH}"; \
|
|
curl -fsSL "https://github.com/prometheus/prometheus/releases/download/v${PROMTOOL_VERSION}/${PROM_TARBALL}.tar.gz" \
|
|
| tar -xz -C /usr/local/bin --strip-components=1 "${PROM_TARBALL}/promtool"; \
|
|
chmod +x /usr/local/bin/yq /usr/local/bin/chainsaw /usr/local/bin/kyverno /usr/local/bin/kyverno-json \
|
|
/usr/local/bin/promtool; \
|
|
rm -rf /tmp/*
|
|
|
|
# Record the baked toolchain for provenance.
|
|
RUN { echo "opentofu=${OPENTOFU_VERSION}"; echo "age=${AGE_VERSION}"; echo "sops=${SOPS_VERSION}"; \
|
|
echo "flux=${FLUX_VERSION}"; echo "kubectl=${KUBECTL_VERSION}"; echo "helm=${HELM_VERSION}"; \
|
|
echo "kyverno=${KYVERNO_VERSION}"; echo "kyverno-json=${KYVERNO_JSON_VERSION}"; \
|
|
echo "chainsaw=${CHAINSAW_VERSION}"; echo "yamllint=${YAMLLINT_VERSION}"; \
|
|
echo "yq=${YQ_VERSION}"; echo "check-jsonschema=${CHECK_JSONSCHEMA_VERSION}"; \
|
|
echo "promtool=${PROMTOOL_VERSION}"; \
|
|
} > /etc/dev-toolchain-versions
|
|
|
|
WORKDIR /work
|
|
CMD ["/bin/bash"]
|
|
|
|
# =============================================================================
|
|
# verify — a TEST stage, never shipped. It runs every assertion against the finished image AT
|
|
# BUILD TIME, so CI needs no container runtime to prove the image: the image pipeline builds
|
|
# this target on rootless BuildKit (platform/buildkit/), where `docker run` does not exist.
|
|
# `RUN --network=none` reproduces the old `run --network none` exactly, and the first check
|
|
# below proves it (it fails if any network is reachable).
|
|
# =============================================================================
|
|
FROM toolbox AS verify
|
|
ARG PROMTOOL_VERSION
|
|
ARG KUBECTL_VERSION
|
|
ARG CHAINSAW_VERSION
|
|
ARG KYVERNO_VERSION
|
|
ARG SOPS_VERSION
|
|
RUN --network=none /bin/bash -euo pipefail <<'VERIFY'
|
|
echo "--- no network (the build-time stand-in for run --network none) ---"
|
|
if getent hosts registry.fedoraproject.org >/dev/null 2>&1 || curl -sS --max-time 3 -o /dev/null https://1.1.1.1 2>/dev/null; then
|
|
echo "UNEXPECTED: network reachable in the verify stage"; exit 1
|
|
fi
|
|
echo " confirmed: no network"
|
|
echo "--- baked versions ---"; cat /etc/dev-toolchain-versions
|
|
echo "--- every tool is on PATH ---"
|
|
for t in tofu age age-keygen sops flux kubectl helm kyverno kyverno-json chainsaw yq \
|
|
yamllint check-jsonschema promtool ansible-playbook git make jq diff python3 \
|
|
wg qrencode; do
|
|
if command -v "$t" >/dev/null 2>&1; then printf " ok %s\n" "$t";
|
|
else printf " MISSING %s\n" "$t"; missing=1; fi
|
|
done
|
|
[ -z "${missing:-}" ] || { echo "toolbox is missing tools"; exit 1; }
|
|
echo "--- version-sensitive tools report their pins ---"
|
|
: "${PROMTOOL_VERSION:?}" "${KUBECTL_VERSION:?}" "${CHAINSAW_VERSION:?}" "${KYVERNO_VERSION:?}" "${SOPS_VERSION:?}"
|
|
check() { # name pin actual-output
|
|
if printf "%s" "$3" | grep -qF "$2"; then printf " ok %-10s %s\n" "$1" "$2";
|
|
else printf " DRIFT %-10s want %s, got: %s\n" "$1" "$2" "$(printf "%s" "$3" | head -1)"; drift=1; fi
|
|
}
|
|
check promtool "${PROMTOOL_VERSION}" "$(promtool --version 2>&1)"
|
|
check kubectl "${KUBECTL_VERSION}" "$(kubectl version --client 2>&1)"
|
|
check chainsaw "${CHAINSAW_VERSION}" "$(chainsaw version 2>&1)"
|
|
check kyverno "${KYVERNO_VERSION}" "$(kyverno version 2>&1)"
|
|
check sops "${SOPS_VERSION}" "$(sops --version --disable-version-check 2>&1 || sops --version 2>&1)"
|
|
[ -z "${drift:-}" ] || { echo "a tool does not report its pinned version"; exit 1; }
|
|
echo "--- python can import what the renderer and the gate need ---"
|
|
python3 -c "import yaml; print(' PyYAML', yaml.__version__)"
|
|
echo "TOOLBOX VERIFIED"
|
|
VERIFY
|