Skip to content

security(k8s): three CKV_K8S_40 sites have no UID constraint β€” raise them or demonstrate oneΒ #2901

Description

@devantler

πŸ€– Generated by the Agentic Engineer

Evidence

While dispositioning CKV_K8S_40 in #2898, three of the eleven sites turned out to have no
constraint on their UID at all
. Each runs below UID 10000 by convention, and nothing in the
manifest requires it:

Site UID What it actually touches
k8s/bases/apps/umami/cron-job.yaml (CronJob umami-provision-tenants) 1001 No volumes and no volumeMounts. Talks to http://umami-umami.umami.svc.cluster.local over HTTP, with readOnlyRootFilesystem: false.
k8s/providers/docker/.../minio/job.yaml (Job minio-create-bucket) 1000 mc against http://minio.minio.svc.cluster.local:9000. Its only volume is a /tmp/.mc emptyDir with no fsGroup, so any UID can write it.
k8s/providers/docker/.../minio/deployment.yaml (Deployment minio) 1000 A /data emptyDir under fsGroup: 1000, which governs group ownership β€” so the UID itself is not what makes the volume writable.

The first draft of #2898 suppressed all three with reasons asserting shared file ownership. That was
wrong on inspection β€” none of them shares a volume with anything β€” so the suppressions were removed
rather than shipped with an invented justification.

Why this is worth doing rather than suppressing

Two of these three run on the local Docker provider, which CI brings up on every run, and the
third only calls an HTTP API. So unlike the rest of the CKV_K8S_40 backlog, this is the subset
where raising the UID is both plausible and verifiable in CI rather than requiring a production
change nobody can exercise.

Smallest useful change

Raise the mc Job first β€” it has no fsGroup, no shared volume, and the shortest feedback loop.
Then the umami CronJob (no volumes at all), then the MinIO server, which is the only one where the
image's own expectations about /data need checking.

Success signal

CKV_K8S_40 drops from 5 to 2 with the local Docker provider still coming up green in CI, and the
MinIO bucket-creation Job still completing. If a workload turns out to genuinely need its UID, that
is a real answer too β€” record it as a scoped skip under #2898's convention with the evidence.

Acceptance criteria

  • Each of the three either runs above UID 10000, or carries a scoped skip whose reason is
    demonstrated rather than assumed.
  • The docker-provider CI run is green, and minio-create-bucket still completes.
  • .mega-linter.yml's recorded checkov count is refreshed.

Rough size: small. Part of #2787.

Activity

  1. coderabbitai commented on Aug 2, 2026

    @coderabbitai
    Contributor
    πŸ”— Related PRs

    #2344 - fix(security-context): pair pod-level runAsUser with injected runAsGroup (#2292 regression) [merged]
    #2437 - fix(cilium): run the cilium-operator as non-root [merged]
    #2455 - fix(security): harden containers with read-only root filesystems (Kubescape C-0017) [merged]
    #2857 - test(headlamp): measure the user-namespace mapping in prod [merged]
    #2860 - security(k8s): record an explicit service-account-token verdict per workload [merged]


    πŸ“ Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    πŸ§ͺ Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    πŸ’¬ Have feedback or questions? Drop into our discord!

  2. moved this to 🫴 Ready in 🌊 Project Boardon Aug 2, 2026
  3. devantler commented on Aug 2, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    Two more sites join this bucket β€” both from review, both measured

    #2900's review round found two further CKV_K8S_40 sites whose stated constraint did not hold.
    Both are now here rather than suppressed, bringing this issue to five sites.

    k8s/bases/infrastructure/vault-config/job.yaml (Job vault-config, UID 100)

    It does not mount the vault-snapshots PVC. Its volume list is entirely emptyDir and
    secret; the snapshots volume is an emptyDir that the fetch-snapshot init container fills
    from the R2/MinIO mirror β€” and two comments already in that file say exactly this ("read it
    WITHOUT mounting the RWO vault-snapshots PVC").

    Nor is 100 an image requirement: the pinned quay.io/openbao/openbao:2.5.3 config reads
    User: "<unset>". What is true, read live from the cluster, is that the OpenBao StatefulSet
    runs runAsUser: 100, runAsGroup: 1000, fsGroup: 1000 β€” which is why the two vault-backup jobs,
    which do mount the claim, keep their disposition and this one does not.

    Open question for whoever picks it up: vault-init and store-keys write to emptyDirs under
    fsGroup: 1000, so a higher UID is plausible β€” but OpenBao's docker-entrypoint.sh may expect
    paths owned by 100. That needs exercising, not reasoning.

    k8s/providers/docker/.../coredns/deployment.yaml (Deployment coredns)

    CoreDNS does not run as root. The pinned image's own config,
    registry.k8s.io/coredns/coredns:v1.14.3@sha256:884b72dd…, reads:

    {"User": "nonroot:nonroot", "Entrypoint": ["/coredns"]}

    That is the distroless nonroot identity β€” 65532, already above the 10000 threshold. The manifest
    simply sets no runAsUser, so checkov cannot see it. An explicit runAsUser: 65532 would match
    what the container already does and clear the check outright.

    ⚠️ The catch, and why this one is not "just do it". This is the local Docker provider's cluster
    DNS
    , and this repo runs no full-cluster system test on PRs β€” only static validation and a
    Kubescape scan. If the assumption is wrong, the symptom is a cluster with no DNS, not a red check.
    The container already grants NET_BIND_SERVICE and the distroless binary carries the capability on
    the file, so it should hold β€” but "should" is the word that makes this need a cluster.

    #2900 also corrected the false comment in that file, which had asserted the opposite for as long
    as it existed.

    Revised scope

    Five sites, in ascending order of how much evidence is still needed:

    Site UID What is still unknown
    minio-create-bucket 1000 nothing obvious β€” no fsGroup, no shared volume, HTTP only
    umami-provision-tenants 1001 nothing obvious β€” no volumes at all, HTTP only
    minio (server) 1000 whether the image expects /data at its own UID
    coredns unset (image: 65532) whether :53 still binds; needs a cluster
    vault-config 100 whether OpenBao's entrypoint expects paths owned by 100
  4. self-assigned this
    on Aug 2, 2026
  5. moved this from 🫴 Ready to πŸ‘€ In Review in 🌊 Project Boardon Aug 2, 2026
  6. devantler commented on Aug 2, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    One premise of this issue is false β€” acceptance criterion 2 cannot be met as written

    This issue argues the MinIO pair is the verifiable subset:

    Two of these three run on the local Docker provider, which CI brings up on every run […] this
    is the subset where raising the UID is both plausible and verifiable in CI.

    MinIO is not brought up by CI. It is an opt-in controller, commented out in the docker
    provider's own kustomizations:

    • k8s/providers/docker/infrastructure/controllers/kustomization.yaml lists # - minio/ under
      "Opt-in controllers (uncomment what you want to try locally)", alongside velero/.
    • k8s/providers/docker/infrastructure/kustomization.yaml likewise keeps vault-backup/ commented
      out, noting it "needs controllers/velero + minio".

    So acceptance criterion 2 β€” "the docker-provider CI run is green, and minio-create-bucket still
    completes"
    β€” cannot be satisfied: that Job never runs in CI. The criterion is unmet rather than
    skipped, and #2911 says so instead of implying a live exercise.

    What replaced it

    Static evidence read from the images themselves rather than reasoned about, which is what the
    withdrawn suppressions got wrong:

    Site Image User /etc/passwd entry for the UID it ran under Disposition
    minio/job.yaml (mc) none (would run as root) none raised to 65532
    minio/deployment.yaml none (would run as root) none raised to 65532
    umami/cron-job.yaml nextjs nextjs:x:1001:65533 β€” exact match scoped skip

    Both MinIO images are therefore already running under a UID their own image does not know about,
    which is the same class of change as raising it. MinIO's entrypoint only switches user when
    MINIO_USERNAME and MINIO_GROUPNAME are set; neither is set here, so it takes the plain
    exec branch and never looks the UID up.

    The umami CronJob went the other way: 1001 turned out to be its image's baked user, so raising it
    would break the match the image asks for. That is the one demonstrated constraint of the three, and
    it is recorded as a scoped skip rather than a change.

    Measured, with the repo's own scripts/megalinter-scan-counts.sh: checkov 37 β†’ 34, CKV_K8S_40
    11 β†’ 8. trivy also moved 624 β†’ 620, and that delta is attributable rather than drift β€” exactly two
    targets changed (both MinIO files, 4 β†’ 2 failures each), the other 457 kubernetes targets are
    unchanged, and both runs ran the same 118 tests per target, so the rule set did not move.

    Residual risk, stated plainly: because CI does not deploy MinIO, a UID mistake here would surface
    the first time someone opts the stack in locally, not in this pull request. The blast radius is
    bounded to that opt-in local/CI stand-in for R2 β€” it is not a production workload.

  7. moved this from πŸ‘€ In Review to βœ… Done in 🌊 Project Boardon Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Fields

No fields configured for Security.

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions