Skip to content

Capability and seccomp rejections do not tell the customer how to fix the manifest #314

Description

@scotwells

When a customer gets a capability request wrong, the error usually does not tell them how to fix it. A typo reads as if the platform is withholding a real capability. A lowercase name returns a regex. A single mistake can produce two errors where the second contradicts the first. And an unsupported seccomp profile surfaces as a decoder error that never mentions seccomp. In every one of these cases the customer is one small edit away from a working manifest, and the message does not say which edit.

This is the validation surface introduced in #310.

The model to follow

One message already does this right. Input capabilities: {add: [CAP_NET_ADMIN]}:

Invalid value: "CAP_NET_ADMIN": must omit the CAP_ prefix, for example "NET_ADMIN"

It names the mistake and states the fix. The cases below should read like this.

The cases

1. A typo is reported as a policy denial. Input capabilities: {add: [NOT_A_CAPABILITY]}:

Forbidden: capability NOT_A_CAPABILITY is not granted by the "general-purpose" runtime class, which grants AUDIT_CONTROL, AUDIT_READ, ... WAKE_ALARM

This implies the capability exists and is being withheld — a customer reasonably goes looking for a different runtime class or files a support request. It does not exist. The platform never distinguishes an unknown capability name from a real one, so there is no "unknown capability" message and no "did you mean".

2. A lowercase name leaks the raw validator. Input capabilities: {add: [net_admin]}:

Invalid value: "net_admin": spec.template.spec.runtime.sandbox.containers[0].securityContext.capabilities.add[0] in body should match '^[A-Z_]+$'

A regex is not product copy. The message never says capability names are uppercase, and the customer is one character-case away from correct.

3. One mistake produces two errors, and the second is often wrong or contradictory. Input capabilities: {add: [ALL]}:

* Forbidden: ALL may not be added; list each capability the container needs
* Forbidden: capability ALL is not granted by the "general-purpose" runtime class, which grants AUDIT_CONTROL, ... WAKE_ALARM

The second is noise — ALL is not a capability the class could ever grant. The platform rejects ALL and then goes on to check the same entry against the class anyway.

Worse on a class that supports no capability requests at all. Input capabilities: {add: [ALL]} with class: unikernel:

* Forbidden: ALL may not be added; list each capability the container needs
* Forbidden: container capability requests are not supported by the "unikernel" runtime class

The customer is told to list the capabilities they need, immediately before being told the class accepts none. Following the first instruction still fails.

4. An unsupported seccomp profile surfaces as a decoder error. Input seccompProfile: {type: Localhost, localhostProfile: foo.json}:

Error from server (BadRequest): ... strict decoding error: unknown field "spec.template.spec.runtime.sandbox.containers[0].securityContext.seccompProfile.localhostProfile"

localhostProfile is not part of the published API at all, so the request is rejected before any customer-facing validation runs and the message never says Localhost profiles are unsupported. For contrast, type: Localhost with the field omitted gives a good message: Unsupported value: "Localhost": supported values: "RuntimeDefault", "Unconfined".

5. The grantable-capability list is repeated inline in every rejection. The general-purpose class grants 41 capabilities, and the full list is inlined in each capability error — twice when two errors fire. A customer fixing one typo reads roughly 500 characters of allowlist, twice.

Reproduction

Against a control plane with the RuntimeClasses gate on and the general-purpose class published, kubectl apply --dry-run=server a sandbox Workload, varying only containers[0].securityContext.

apiVersion: compute.datumapis.com/v1alpha
kind: Workload
metadata:
  name: cap-errors
spec:
  template:
    spec:
      runtime:
        class: general-purpose
        sandbox:
          containers:
            - name: app
              image: docker.io/library/nginx:latest
              securityContext:
                capabilities:
                  add: [NET_BIND_SERVICE]
  placements:
    - name: default
      cityCodes: [DFW]
      scaleSettings:
        minReplicas: 1

Inputs tested:

securityContext Case
capabilities: {add: [NOT_A_CAPABILITY]} 1
capabilities: {add: [net_admin]} 2
capabilities: {add: [ALL]} 3, 5
capabilities: {add: [ALL]} with class: unikernel 3
seccompProfile: {type: Localhost, localhostProfile: foo.json} 4
seccompProfile: {type: Localhost} good message, contrast
capabilities: {add: [CAP_NET_ADMIN]} the model to follow

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions