Skip to main content

Binary Verification with an Agent Extension

Platform attestation proves where a workload is running, not what code it is running. This example builds an agent extension that checks the workload executable's hash against an approved list and fails attestation when the hash is unrecognized, so an unapproved binary never receives an identity.

This guide refuses an identity using the Agent Workload Attestation Extension, an executable that runs on every agent host. To refuse workloads from a central webhook instead, see Deny SVID Issuance. To compare all four extension points, see Which extension do you need?.

What you'll build

A Python executable that runs on each agent host, reads the workload's binary hash from the attestation request, and compares it against an allowlist. Approved workloads receive custom.binary_verified=true and custom.binary_hash, recording which binary was approved. Everything else is denied an SVID.

This extension example re-uses the already computed linux.binary.sha256 from the Linux attestor to demonstrate the types of logic available in an attestation extension.

warning

This example is illustrative only. Separate security analysis may be required for your specific implementation.

Prerequisites

  • A Kubernetes cluster registered with Defakto, with the Agent installed
  • Python 3.9 or later on the agent nodes
  • The Linux attestor enabled with discoverWorkloadPath: true, so requests carry linux.binary.sha256
  • An allowlist of approved binary hashes, and a process for updating it when workloads are rebuilt

discoverWorkloadPath is off by default and requires the CAP_SYS_PTRACE capability on the agent. Enable it in the WorkloadAttestation section:

section: WorkloadAttestation
schema: v1
spec:
linux:
enabled: true
discoverWorkloadPath: true

See Linux Workload Attestor for the capability grant on Kubernetes and on Linux hosts.

Write the extension

#!/usr/bin/env python3
"""Agent workload attestation extension: verify workload binary hashes."""

import json
import os
import sys

# Read from a mounted ConfigMap or a file managed by configuration management,
# so the allowlist can be updated without rebuilding the extension.
APPROVED_HASHES_PATH = os.environ.get(
"APPROVED_HASHES_PATH", "/etc/spirl/approved-hashes.txt"
)


def load_approved_hashes(path):
"""Read approved SHA256 hashes, one per line, ignoring blanks and comments."""
with open(path) as f:
return {
line.strip().lower()
for line in f
if line.strip() and not line.startswith("#")
}


def verify(req, approved):
binary = req.get("linux", {}).get("binary", {})
binary_hash = binary.get("sha256", "").lower()

if not binary_hash:
# Attestation reached the extension without the attribute, which means
# discoverWorkloadPath is disabled or the capability is missing.
return {"error": "linux.binary.sha256 not present in request"}

if binary_hash not in approved:
return {"error": f"unapproved binary hash: {binary_hash}"}

# Return the hash as well as the verdict, so the approved binary is
# recorded on the identity rather than only the fact that it passed.
return {"binary_verified": "true", "binary_hash": binary_hash}


def main():
try:
approved = load_approved_hashes(APPROVED_HASHES_PATH)
except OSError as e:
# Refuse to start rather than approving everything on a missing list.
print(f"failed to load approved hashes: {e}", file=sys.stderr)
sys.exit(1)

if not approved:
print("approved hash list is empty", file=sys.stderr)
sys.exit(1)

print(f"loaded {len(approved)} approved hashes", file=sys.stderr)

for line in sys.stdin:
try:
response = verify(json.loads(line), approved)
except json.JSONDecodeError as e:
response = {"error": f"invalid JSON input: {e}"}
except Exception as e: # never exit on a single bad request
print(f"unexpected error: {e}", file=sys.stderr)
response = {"error": "internal error"}

# stdout carries responses only. Flush so the agent is not left waiting.
print(json.dumps(response), flush=True)


if __name__ == "__main__":
main()

Two behaviors are deliberate. The extension exits at startup when the allowlist is missing or empty, rather than starting up and approving nothing, which makes the misconfiguration obvious immediately instead of at the first SVID request. Every request it cannot positively approve returns an error, which fails attestation. Confirm the allowlist is complete before enabling the extension, because an incomplete list denies legitimate workloads.

Install the script and record its checksum:

install -m 755 verify-binary.py /usr/local/bin/binary-verifier
sha256sum /usr/local/bin/binary-verifier

Write the allowlist to /etc/spirl/approved-hashes.txt, one hash per line:

# checkout-api v2.4.1
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
# payments-worker v1.9.0
b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

Produce those hashes from the same build that produces the workload images, so the list is generated rather than maintained by hand:

sha256sum /path/to/workload/binary

Wire it into Defakto

Add the extension to the spirl-system Helm values, using the checksum from the previous step:

agent:
extensionWorkloadAttestation:
cmd: "/usr/local/bin/binary-verifier"
timeout: "100ms"
checksum: "sha256:b2c3d4e5f6a7..."

The default 100ms timeout is sufficient, because the extension performs a set lookup rather than any file I/O. A timeout is treated as a failure and denies the workload, so leave headroom rather than tuning the value down.

Roll this out to a single node pool first. An incomplete allowlist denies SVIDs to every workload on every node that has the extension enabled.

Verify

Test the script directly before enabling it. Use a hash you know is on the allowlist:

echo '{"_meta":{"version":"1.0"},"linux":{"binary":{"path":"/usr/bin/myapp","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}},"pid":"1234"}' \
| /usr/local/bin/binary-verifier

An approved binary:

{"binary_verified": "true", "binary_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}

An unapproved binary:

{"error": "unapproved binary hash: 5f2b9c1d..."}

Then confirm the behavior end to end. A workload whose binary is on the list receives an SVID carrying custom.binary_verified and custom.binary_hash. A workload whose binary is not on the list fails attestation, and the agent logs the extension's error:

ERROR Extension failed {"error": "unapproved binary hash: 5f2b9c1d..."}

If every workload is denied with linux.binary.sha256 not present in request, the Linux attestor is not emitting the attribute. Confirm discoverWorkloadPath is enabled and the agent has CAP_SYS_PTRACE.

See the Agent Runbook for the response procedure when a custom attestor denies attestation in production.

Next steps