Verifying Kubernetes Platform Binaries
The last competency in Cluster Setup is the one people skip: check that the binary you are about to run is the one that was published. This guide verifies a release download, breaks it by one byte to watch the check fail, verifies the binary already installed on the node - and is honest about what a checksum does not tell you.
Cluster Setup Guide 8 of 40 Beginner
- Kubernetesapiserver v1.36.4, kubelet v1.36.3
- Runtimecontainerd 2.2.6
- CNICilium 1.18.1 - tunnel/VXLAN, with Hubble relay and UI
- Host OSUbuntu 26.04 LTS, kernel 7.0.0-29
- Built withkubeadm v1.36.3 - podSubnet 10.244.0.0/16, serviceSubnet 10.96.0.0/12
- TimeAbout 14 min
- Reviewed25 August 2026
Written against the versions above. Kubernetes publishes a `.sha256` beside every release binary at dl.k8s.io, and signatures separately via sigstore. `cosign` is not installed on this node, which the guide states rather than working around.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA8001 | 192.168.0.56 | Ubuntu 26.04 LTS | Single Node (control plane, untainted) | 2 Core | 4 GB | 50 GB |
Before you start
- guide 5 - the other half of node trust.
-
Fetch a release binary and the digest published beside it
The version this node is already running.
bash Example session cd /tmp && rm -rf verify && mkdir verify && cd verify && V=$(kubectl version --client -o json | tr -d ' "' | grep -o 'gitVersion:[^,}]*' | cut -d: -f2 | head -1); echo "version on this node: $V"; curl -sLO https://dl.k8s.io/release/$V/bin/linux/amd64/kubectl && curl -sLO https://dl.k8s.io/release/$V/bin/linux/amd64/kubectl.sha256 && ls -l kubectl kubectl.sha256 | awk '{print $5, $9}'; echo "--- the binary and a 64-character checksum, served from the same place. That is the weakness this step has to be honest about"version on this node: v1.36.359556002 kubectl64 kubectl.sha256--- the binary and a 64-character checksum, served from the same place. That is the weakness this step has to be honest aboutcd /tmp/verify && cat kubectl.sha256; echo; echo "--- the published digest. Everything below is a comparison against this string"ebbd080e7c2e275093b55915722043257eb24004363e20acb3c4d71919f88336--- the published digest. Everything below is a comparison against this stringExpected result
version on this node: v1.36.3, a 59MBkubectland a 64-bytekubectl.sha256, then the digest itself -ebbd080e7c2e275093b55915722043257eb24004363e20acb3c4d71919f88336.Success conditionYou have the artefact and the claim about it.
-
Verify it, then break it and verify again
The check, and the same check on a file with one extra byte.
bash Example session cd /tmp/verify && echo "$(cat kubectl.sha256) kubectl" | sha256sum -c -; echo "--- OK means the bytes you have are the bytes that were published"kubectl: OK--- OK means the bytes you have are the bytes that were publishedcd /tmp/verify && cp kubectl kubectl.tampered && printf 'x' >> kubectl.tampered && echo "$(cat kubectl.sha256) kubectl.tampered" | sha256sum -c - 2>&1 | tail -2; echo "--- ONE byte appended to a 60MB binary, and the check fails. That is the whole point of a digest"kubectl.tampered: FAILEDsha256sum: WARNING: 1 computed checksum did NOT match--- ONE byte appended to a 60MB binary, and the check fails. That is the whole point of a digestExpected result
kubectl: OK, thenkubectl.tampered: FAILEDandsha256sum: WARNING: 1 computed checksum did NOT match.Success conditionYou have seen the check succeed and fail on purpose.
-
The binary that is already running
The same question, asked about the machine rather than the download.
bash Example session cd /tmp/verify && echo -n "installed: "; sha256sum $(which kubectl) | cut -d' ' -f1; echo -n "published: "; cat kubectl.sha256; echo; if [ "$(sha256sum $(which kubectl) | cut -d' ' -f1)" = "$(cat kubectl.sha256)" ]; then echo "MATCH - the binary in PATH is the published release"; else echo "DIFFERENT - the binary in PATH is not the file published at that URL"; fiinstalled: ebbd080e7c2e275093b55915722043257eb24004363e20acb3c4d71919f88336published: ebbd080e7c2e275093b55915722043257eb24004363e20acb3c4d71919f88336MATCH - the binary in PATH is the published releaseExpected resultThe installed digest and the published digest identical, and
MATCH - the binary in PATH is the published release.Success conditionYou can confirm the provenance of what is installed, not just what you downloaded.
-
What a checksum does not prove
The tools that are here, and the one that is not.
bash Example session which cosign sha256sum gpg 2>&1 | tr '\n' ' '; echo; echo "--- sha256sum and gpg are here and cosign is not. A digest proves INTEGRITY against the file that was published; it cannot prove the publisher was Kubernetes, because an attacker who serves you the binary can serve you the digest beside it"cosign not found /usr/bin/sha256sum /usr/bin/gpg--- sha256sum and gpg are here and cosign is not. A digest proves INTEGRITY against the file that was published; it cannot prove the publisher was Kubernetes, because an attacker who serves you the binary can serve you the digest beside itExpected result
cosign not found, with/usr/bin/sha256sumand/usr/bin/gpgpresent.Success conditionYou can state the limit of the control you just applied.
Troubleshooting
sha256sum -creports no properly formatted lines.Why: The .sha256 file contains only the digest, with no filename.
Fix:
echo "$(cat f.sha256) f" | sha256sum -c -supplies the filename.The verification always passes and nothing is being checked.
Why: An empty or missing digest file compares against nothing.
Fix:Break a copy on purpose and confirm it FAILS.
The installed binary does not match the published digest.
Why: It came from a distribution package rather than the upstream release.
Fix:Verify against the packager's signature instead, and record which you trust.
Treating a matching checksum as proof of authenticity.
Why: The digest is served by the same host as the binary.
Fix:Verify a signature for authenticity. A digest is an integrity check.