Lesson 20 Security & Best Practices
Theme:
Lesson 20 · Slide 1

Security & Best Practices

Hardening Helm charts with Pod Security Standards, least-privilege RBAC, supply chain verification, and policy enforcement.

Lesson 20 — Helm Charts: From Templates to Production Kubernetes

Lesson 20 · Slide 2

Pod Security Standards & securityContext

Kubernetes defines three Pod Security Standards. Target Restricted for all production workloads.

The Three Levels

  • Privileged — unrestricted, for system-level pods only
  • Baseline — blocks known privilege escalations
  • Restricted — hardened defaults, required for most compliance regimes

Pod-level vs Container-level

Pod-level applies to all containers: runAsNonRoot, fsGroup, seccompProfile.

Container-level overrides per container: readOnlyRootFilesystem, allowPrivilegeEscalation, capabilities.

# templates/deployment.yaml
spec:
  template:
    spec:
      securityContext:                          # Pod-level
        runAsNonRoot: {{ .Values.podSecurityContext.runAsNonRoot | default true }}
        fsGroup: {{ .Values.podSecurityContext.fsGroup | default 1000 }}
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: {{ .Chart.Name }}
          securityContext:                      # Container-level
            readOnlyRootFilesystem: {{ .Values.securityContext.readOnlyRootFilesystem | default true }}
            allowPrivilegeEscalation: {{ .Values.securityContext.allowPrivilegeEscalation | default false }}
            capabilities:
              drop: ["ALL"]
Lesson 20 · Slide 3

Image Pull Secrets & Private Registries

Authenticate to private container registries using imagePullSecrets. Two approaches:

Per-Pod imagePullSecrets

spec:
  template:
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
# values.yaml
imagePullSecrets:
  - name: registry-creds

ServiceAccount-level

apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "myapp.fullname" . }}
imagePullSecrets:
  - name: {{ .Values.registry.secretName }}

Attaching the secret to the ServiceAccount avoids repeating it on every pod spec.

Notes: Create the pull secret with: kubectl create secret docker-registry registry-creds --docker-server=... --docker-username=... --docker-password=...
Lesson 20 · Slide 4

Least-Privilege RBAC

Every application should run under its own ServiceAccount with only the permissions it needs — never use the default ServiceAccount.

# templates/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: {{ include "myapp.fullname" . }}
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "watch"]
    resourceNames: ["{{ include "myapp.fullname" . }}-config"]   # scope to specific resources
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
    resourceNames: ["{{ include "myapp.fullname" . }}-secret"]
# templates/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: {{ include "myapp.fullname" . }}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: {{ include "myapp.fullname" . }}
subjects:
  - kind: ServiceAccount
    name: {{ include "myapp.fullname" . }}
    namespace: {{ .Release.Namespace }}
Notes: Anti-pattern: wildcard RBAC (resources: ["*"], verbs: ["*"]). Always enumerate exact resources and verbs. Use resourceNames to restrict to specific objects when possible.
Lesson 20 · Slide 5

Supply Chain Security

Protect your deployment pipeline from tampered charts and images.

# values.yaml - pin by digest
image:
  repository: ghcr.io/myorg/myapp
  digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
  tag: ""    # ignored when digest is set

# templates/deployment.yaml
image: "{{ .Values.image.repository }}{{- if .Values.image.digest }}@{{ .Values.image.digest }}{{- else }}:{{ .Values.image.tag }}{{- end }}"
Lesson 20 · Slide 6

Resource Limits & Network Security

Always Set Requests/Limits

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

Add a LimitRange as a safety net for pods that forget:

apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
spec:
  limits:
    - default:
        cpu: "500m"
        memory: "256Mi"
      defaultRequest:
        cpu: "100m"
        memory: "128Mi"
      type: Container

Default-Deny NetworkPolicy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: {{ include "myapp.fullname" . }}
spec:
  podSelector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  policyTypes: ["Ingress", "Egress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - port: 5432
    - to:                         # Allow DNS
        - namespaceSelector: {}
      ports:
        - port: 53
          protocol: UDP
Lesson 20 · Slide 7

Secrets Hygiene & Policy Enforcement

Secrets Best Practices

  • Never commit secrets in values.yaml to Git
  • Use --set flags or external secret operators (External Secrets Operator, Sealed Secrets, Vault)
  • Mount secrets as files, not environment variables when possible
  • Rotate secrets regularly; use short-lived credentials
# Install with runtime secrets
helm install myapp ./mychart \
  --set secrets.dbPassword="$DB_PASS" \
  --set secrets.apiKey="$API_KEY"

Policy Enforcement Tools

  • kube-score — static analysis of manifests
  • Polaris — audit + admission controller
  • OPA/Gatekeeper — custom policy as code
# Audit with kube-score
helm template myapp ./mychart | \
  kube-score score -

# Audit with Polaris
polaris audit --helm-chart ./mychart

# Gatekeeper ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
Lesson 20 · Slide 8

Chart Audit Checklist

Review every chart against these criteria before deploying to production:

Notes: Common anti-patterns: running as root, no resource limits, wildcard RBAC, using :latest tag, hostNetwork: true, and storing secrets in version control.
1 / ?