What a customer hits
A customer has a working sandbox workload on the general-purpose runtime class. They need one extra capability, so they add the obvious thing:
securityContext:
capabilities:
add: [NET_ADMIN]
They get NET_ADMIN — and silently lose the ten capabilities the class was granting them, including CHOWN, SETGID, SETUID, and NET_BIND_SERVICE. A stock nginx or Postgres image then fails at runtime: its entrypoint chowns data directories and its workers drop privileges. The failure surfaces far from the one-line YAML change that caused it, and the field they edited is named add, which in Kubernetes is additive.
It gets worse: per datum-labs/kata-provider#18 the resulting crash-loop still reports Available=True, so the customer gets no signal from the platform at all.
Found while validating #310.
Evidence
Verified against the staging datum-cloud project control plane with kubectl apply --dry-run=server (real webhooks, nothing created).
Container with no securityContext — admission stamps:
capabilities:
add: [CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID]
drop: [ALL]
allowPrivilegeEscalation: false
seccompProfile: {type: RuntimeDefault}
Same container stating capabilities: {add: [NET_ADMIN]} — admission stores:
capabilities:
add: [NET_ADMIN]
drop: [ALL]
All ten defaults are gone. A second probe confirms there is no merge anywhere: input add: [NET_ADMIN, CHOWN] stores exactly those two.
Reproduction
Against a control plane with the RuntimeClasses feature gate enabled and the general-purpose class published:
apiVersion: compute.datumapis.com/v1alpha
kind: Workload
metadata:
name: cap-repro
spec:
template:
spec:
runtime:
class: general-purpose
resources:
instanceType: datumcloud/d1-standard-2
sandbox:
containers:
- name: app
image: docker.io/library/nginx:latest
# probe 2: uncomment
# securityContext:
# capabilities:
# add: [NET_ADMIN]
networkInterfaces:
- network:
name: default
placements:
- name: us
locations:
- name: gcp-us-south1-a
scaleSettings:
minReplicas: 1
kubectl apply --dry-run=server -f workload.yaml -o json
Run once as written, once with the securityContext block uncommented, and compare the stored securityContext on the container.
The design decision this has to overturn
This behaviour is deliberate today. The reasoning we published was that a customer statement is the whole answer for the capabilities it covers: merging the class default into a stated set would put back the invisible injection the runtime class surface was meant to replace, so a container that states capabilities keeps exactly those.
That rationale no longer holds. The concern it protects against is the platform adding capabilities invisibly — but admission now stamps the resolved result onto the stored workload, so a merged set would be fully visible to the customer in their own spec, alongside the drop: [ALL] that is already appended to stated sets for exactly this legibility reason. Stamping already solves invisibility. Merging costs nothing in legibility, and replacement costs the customer a working deployment.
Expected
A stated capability should be additive to the class default, or the platform must make the replacement explicit to the customer.
What a customer hits
A customer has a working sandbox workload on the
general-purposeruntime class. They need one extra capability, so they add the obvious thing:They get
NET_ADMIN— and silently lose the ten capabilities the class was granting them, includingCHOWN,SETGID,SETUID, andNET_BIND_SERVICE. A stock nginx or Postgres image then fails at runtime: its entrypoint chowns data directories and its workers drop privileges. The failure surfaces far from the one-line YAML change that caused it, and the field they edited is namedadd, which in Kubernetes is additive.It gets worse: per datum-labs/kata-provider#18 the resulting crash-loop still reports
Available=True, so the customer gets no signal from the platform at all.Found while validating #310.
Evidence
Verified against the staging
datum-cloudproject control plane withkubectl apply --dry-run=server(real webhooks, nothing created).Container with no
securityContext— admission stamps:Same container stating
capabilities: {add: [NET_ADMIN]}— admission stores:All ten defaults are gone. A second probe confirms there is no merge anywhere: input
add: [NET_ADMIN, CHOWN]stores exactly those two.Reproduction
Against a control plane with the RuntimeClasses feature gate enabled and the
general-purposeclass published:Run once as written, once with the
securityContextblock uncommented, and compare the storedsecurityContexton the container.The design decision this has to overturn
This behaviour is deliberate today. The reasoning we published was that a customer statement is the whole answer for the capabilities it covers: merging the class default into a stated set would put back the invisible injection the runtime class surface was meant to replace, so a container that states capabilities keeps exactly those.
That rationale no longer holds. The concern it protects against is the platform adding capabilities invisibly — but admission now stamps the resolved result onto the stored workload, so a merged set would be fully visible to the customer in their own spec, alongside the
drop: [ALL]that is already appended to stated sets for exactly this legibility reason. Stamping already solves invisibility. Merging costs nothing in legibility, and replacement costs the customer a working deployment.Expected
A stated capability should be additive to the class default, or the platform must make the replacement explicit to the customer.