R-TLS-01/docs/08 govern externally reachable TLS and ADR-0006 puts workload mTLS out of scope, but tls-lifecycle-automated required ACME for every Certificate, which platform/buildkit's internal CA cannot satisfy. Clause (5b) accepts the tls-scope=workload-mtls annotation only when the certificate proves it: no Gateway serves it, internal names only, namespaced selfSigned/ca issuer backed by an exempt CA certificate. Also fixes yamllint comma spacing in the buildkitd mounts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
143 lines
6.4 KiB
YAML
143 lines
6.4 KiB
YAML
---
|
|
# buildkitd, ROOTLESS. The builder behind .gitea/workflows/images.yaml; see README.md.
|
|
#
|
|
# THE SECURITY CONTEXT IS THE BOUNDARY, AND EVERY LINE OF IT WAS MEASURED (2026-10-29, the pinned
|
|
# image under podman with the same restrictions; decisions/2026-10-29-image-pipeline.md):
|
|
# * privileged: false — never. This is the whole point of the operator's decision on PR #81:
|
|
# the alternative was handing CI the privileged DinD daemon, i.e. root on the node.
|
|
# * runAsUser/Group 1000 — the image's `user`; rootlesskit maps it to uid 0 INSIDE a user
|
|
# namespace only. The nodes allow that (measured: kernel.unprivileged_userns_clone = 1,
|
|
# user.max_user_namespaces = 127206 on all three Debian 13 nodes).
|
|
# * capabilities: drop ALL, add SETUID + SETGID — the image's newuidmap/newgidmap are NOT
|
|
# setuid; they carry FILE capabilities, which need SETUID/SETGID in the bounding set.
|
|
# Measured: with drop ALL and nothing added, rootlesskit fails "newuidmap ... operation not
|
|
# permitted" and the daemon never starts.
|
|
# * allowPrivilegeEscalation: true — required for the same reason: no_new_privs (what `false`
|
|
# sets) stops a file capability from ever taking effect. It does NOT grant privileges the
|
|
# bounding set above does not contain.
|
|
# * seccompProfile: Unconfined — measured: with the default seccomp profile the daemon starts
|
|
# but every RUN step fails (the runtime cannot create the build step's namespaces/mounts).
|
|
# * appArmorProfile: Unconfined — upstream's documented requirement (buildkit
|
|
# examples/kubernetes, *.rootless.yaml); the nodes run AppArmor (enabled=Y) and containerd's
|
|
# default profile denies mount. NOT measured here: the test host runs SELinux, not AppArmor.
|
|
# * readOnlyRootFilesystem: true — measured to work with the five emptyDirs below.
|
|
#
|
|
# --oci-worker-no-process-sandbox: build steps share the daemon's PID namespace instead of getting
|
|
# their own (creating one needs an unmasked /proc, i.e. more privilege). Consequence, stated by
|
|
# buildkitd itself at startup: a build step can signal or ptrace the daemon and read what the
|
|
# daemon can read — including this pod's TLS key and the build cache. So a build step is trusted
|
|
# at the level of "may impersonate this builder", which is the level anyone holding the client
|
|
# certificate already has. It can NOT reach the node: everything still runs as uid 1000 in a user
|
|
# namespace, unprivileged, with no host mounts.
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: buildkitd
|
|
namespace: buildkit
|
|
labels:
|
|
app.kubernetes.io/name: buildkitd
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate # one builder, one cache; never two daemons fighting over a node's disk
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: buildkitd
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: buildkitd
|
|
spec:
|
|
automountServiceAccountToken: false # the builder needs no Kubernetes API at all
|
|
enableServiceLinks: false
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
fsGroup: 1000 # makes the 0440 TLS Secret below readable by uid 1000, and nobody else
|
|
containers:
|
|
- name: buildkitd
|
|
# Pinned by digest (the multi-arch index). The kubernetes manager in renovate.json5
|
|
# tracks platform/**, so Renovate proposes bumps; the CI client (buildx, pinned in
|
|
# versions.env) talks to any recent daemon.
|
|
image: docker.io/moby/buildkit:v0.34.1-rootless@sha256:f68752a5a94acfcdad2d07867b7075601a421638e17fc5f28efc1c814d0c4f89
|
|
args:
|
|
- --addr
|
|
- unix:///run/user/1000/buildkit/buildkitd.sock # local only: the probes below
|
|
- --addr
|
|
- tcp://0.0.0.0:1234 # CI, mTLS only
|
|
- --tlscacert
|
|
- /certs/ca.crt # client certs are VERIFIED against this CA: no cert, no build
|
|
- --tlscert
|
|
- /certs/tls.crt
|
|
- --tlskey
|
|
- /certs/tls.key
|
|
- --config
|
|
- /etc/buildkit/buildkitd.toml
|
|
- --oci-worker-no-process-sandbox
|
|
ports:
|
|
- name: buildkit
|
|
containerPort: 1234
|
|
protocol: TCP
|
|
readinessProbe:
|
|
exec:
|
|
command: ["buildctl", "debug", "workers"]
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 30
|
|
livenessProbe:
|
|
exec:
|
|
command: ["buildctl", "debug", "workers"]
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 60
|
|
timeoutSeconds: 10
|
|
securityContext:
|
|
privileged: false
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
allowPrivilegeEscalation: true
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
add: ["SETUID", "SETGID"]
|
|
seccompProfile:
|
|
type: Unconfined
|
|
appArmorProfile:
|
|
type: Unconfined
|
|
resources:
|
|
# Build steps (dnf, npm, pip) run INSIDE this container's cgroup, so the limit covers
|
|
# the daemon and the build together.
|
|
requests:
|
|
cpu: 250m
|
|
memory: 512Mi
|
|
ephemeral-storage: 2Gi
|
|
limits:
|
|
memory: 4Gi
|
|
volumeMounts:
|
|
- { name: certs, mountPath: /certs, readOnly: true }
|
|
- { name: config, mountPath: /etc/buildkit, readOnly: true }
|
|
- { name: cache, mountPath: /home/user/.local/share/buildkit }
|
|
- { name: run, mountPath: /run/user }
|
|
- { name: tmpdir, mountPath: /home/user/.local/tmp } # the image's TMPDIR
|
|
- { name: tmp, mountPath: /tmp }
|
|
- { name: clicfg, mountPath: /home/user/.docker } # buildctl (the probes) writes here
|
|
volumes:
|
|
- name: certs
|
|
secret:
|
|
secretName: buildkitd-tls # cert-manager: ca.crt + tls.crt + tls.key
|
|
defaultMode: 0440
|
|
- name: config
|
|
configMap:
|
|
name: buildkitd-config
|
|
- name: cache
|
|
emptyDir:
|
|
sizeLimit: 6Gi # GC caps use at 4GB (configmap.yaml); this is the backstop
|
|
- name: run
|
|
emptyDir: {}
|
|
- name: tmpdir
|
|
emptyDir: {}
|
|
- name: tmp
|
|
emptyDir: {}
|
|
- name: clicfg
|
|
emptyDir: {}
|