# Upstream containernetworking/plugins binaries, baked in at build time. # # WHY THIS IMAGE EXISTS # The multus cni-plugins-installer DaemonSet used to fetch this tarball from # GitHub on the node itself, at node-provisioning time, and extract it straight # into /opt/cni/bin. Two problems with that: # # 1. Security. The download had NO integrity verification — wget, then tar, # into a directory whose contents are executed as root in the CNI path on # every node. A tampered or MITM'd artifact would have got privileged # execution cluster-wide, with nothing to detect it. # 2. Node rebuild. It put a live dependency on github.com (and previously the # Alpine package index too) on the critical path of bringing a node up, so # a GitHub outage or slow transfer stalled or broke CNI installation # exactly when a node was being rebuilt. # # Baking the binaries in fixes both: the fetch happens once, here, where the # checksum IS verified, and the installer becomes a local file copy with no # network at all. # # The version and digest are pinned literals, so bumping them is a reviewable # commit rather than a silent change in what runs on every node. The digest was # cross-checked against upstream's own published # cni-plugins-linux-amd64-.tgz.sha256 asset rather than merely recorded # from a local download — a download can be tampered with; the point of the # check is to compare against what upstream states. # # amd64 only, matching the ARCH the installer already hardcoded and the cluster's # node architecture. Add a second stage if a different arch is ever needed. FROM alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 AS fetch ARG CNI_PLUGINS_VERSION=v1.9.1 ARG CNI_PLUGINS_SHA256=b98f74a0f8522f0a83867178729c1aa70f2158f90c45a2ca8fa791db1c76b303 # busybox wget and tar are already in the base; nothing is installed here # either. sha256sum -c fails the BUILD if the artifact does not match, which is # the whole point — a bad download must never reach a node. RUN set -eu; \ F="cni-plugins-linux-amd64-${CNI_PLUGINS_VERSION}.tgz"; \ wget -q -O "/tmp/${F}" \ "https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/${F}"; \ echo "${CNI_PLUGINS_SHA256} /tmp/${F}" | sha256sum -c -; \ mkdir -p /plugins; \ tar -xzf "/tmp/${F}" -C /plugins; \ rm -f "/tmp/${F}"; \ # Drop the non-executable payload: the tarball ships LICENSE and README.md, # which the old installer extracted into /opt/cni/bin alongside the binaries. rm -f /plugins/LICENSE /plugins/README.md; \ # Fail loudly here rather than shipping an image that silently installs # nothing, if upstream ever restructures the archive. # # This list must cover every plugin the repo actually depends on, not just # the ones the installer happens to log. `dhcp` is included because the # sibling cni-dhcp-daemon DaemonSet runs that binary and documents that it # ships from here (see kubernetes/infrastructure/multus/cni-dhcp-daemon.yaml) # — without it in the check, upstream dropping `dhcp` while keeping the # others would build green and only surface later as a broken DHCP daemon on # whichever node rolled first. for p in macvlan static host-local dhcp; do \ [ -x "/plugins/$p" ] || { echo "FATAL: /plugins/$p missing or not executable" >&2; exit 1; }; \ done FROM alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # Alpine rather than scratch: the installer needs a shell to copy the binaries # onto the host. busybox covers cp/mv/chown/ls, so nothing is added. COPY --from=fetch /plugins/ /opt/cni-plugins/