Security Defaults
| Field | Value |
|---|---|
| Type | Agent Reference |
| Source | ~/.copilot/agents/_refs/platform-sre-kubernetes/security-defaults.md |
| Description | Not specified |
Source Content
Security Defaults
Applied on every workload by default — Pod Security Standard restricted baseline, plus NetworkPolicy least-privilege. Templates ship hardened; relaxing requires an explicit values.yaml flag and a comment explaining why.
Pod & container security context
spec: template: spec: automountServiceAccountToken: false # opt in only when needed securityContext: runAsNonRoot: true runAsUser: 65532 # distroless nonroot UID runAsGroup: 65532 fsGroup: 65532 seccompProfile: type: RuntimeDefault containers: - name: app image: ghcr.io/<org>/<svc>@sha256:... imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: ["ALL"] resources: requests: { cpu: 100m, memory: 128Mi } limits: { cpu: 500m, memory: 512Mi }If the app needs to write at runtime (cache, tmp), mount an emptyDir to that path — never disable readOnlyRootFilesystem.
NetworkPolicy — default deny + explicit allow
Every namespace starts with a default-deny policy:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: { name: default-deny, namespace: <svc> }spec: podSelector: {} policyTypes: [Ingress, Egress]Then per-workload allow rules:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: { name: <svc>-allow, namespace: <svc> }spec: podSelector: matchLabels: { app.kubernetes.io/name: <svc> } policyTypes: [Ingress, Egress] ingress: - from: - namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: traefik } } podSelector: { matchLabels: { app.kubernetes.io/name: traefik } } ports: [{ port: http, protocol: TCP }] egress: - to: [{ namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: cnpg-system } } }] ports: [{ port: 5432, protocol: TCP }] - to: [{ namespaceSelector: {}, podSelector: { matchLabels: { k8s-app: kube-dns } } }] ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]Egress to public internet only when the service genuinely needs it — and then named, not 0.0.0.0/0.
Image standards
- Base: distroless (
gcr.io/distroless/static:nonrootfor Go,gcr.io/distroless/nodejs20-debian12:nonrootfor Node). - Tags: digest-pinned in production. Floating tags allowed only in
test. - Signing: cosign keyless via OIDC; ArgoCD verifies signatures on sync (Sigstore policy controller).
- Vulnerability scan: Trivy in CI; fail on
CRITICAL. Daily rescan via Trivy Operator publishes to Prometheus.
RBAC
- One
ServiceAccountper workload. Never reusedefault. Role/RoleBinding(namespace-scoped) overClusterRole/ClusterRoleBinding.- Verbs limited to those actually called — start at zero, add as the app fails.
Policy enforcement
Cluster admission policies via Kyverno (preferred — pure YAML) or OPA Gatekeeper. Mandatory policies:
| Policy | Action |
|---|---|
disallow-latest-tag | block on :latest |
require-non-root | block missing runAsNonRoot |
require-resource-limits | block missing CPU/memory limits |
require-readonly-rootfs | block missing readOnlyRootFilesystem |
require-drop-all-caps | block missing capabilities.drop: [ALL] |
disallow-host-namespaces | block hostNetwork/hostPID/hostIPC |
verify-image-signatures | reject unsigned images in prod |
Run kyverno test against the policy library in CI. New charts are expected to pass without exceptions.