Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions labs/lab7/k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: juice-shop
namespace: juice-shop
labels:
app: juice-shop
spec:
replicas: 1
selector:
matchLabels:
app: juice-shop
template:
metadata:
labels:
app: juice-shop
spec:
serviceAccountName: juice-shop-sa
automountServiceAccountToken: false

securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault

initContainers:
- name: seed-writable-dirs
image: bkimminich/juice-shop@sha256:fd58bdc9745416afce8184ee0666278a436574633ea7880365153a63bfd418b0
command:
- /nodejs/bin/node
- -e
- |
const fs = require('fs');
fs.cpSync('/juice-shop/data', '/data-vol', { recursive: true });
fs.cpSync('/juice-shop/ftp', '/ftp-vol', { recursive: true });
fs.cpSync('/juice-shop/frontend/dist', '/frontend-dist-vol', { recursive: true });
fs.cpSync('/juice-shop/.well-known', '/well-known-vol', { recursive: true });
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: app-data
mountPath: /data-vol
- name: app-ftp
mountPath: /ftp-vol
- name: frontend-dist
mountPath: /frontend-dist-vol
- name: well-known
mountPath: /well-known-vol

containers:
- name: juice-shop
# Image pinned by digest (Lab 1 digest)
image: bkimminich/juice-shop@sha256:fd58bdc9745416afce8184ee0666278a436574633ea7880365153a63bfd418b0
ports:
- containerPort: 3000
protocol: TCP

securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL

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

# Juice Shop writes to /tmp and /usr/src/app/logs at runtime.
# readOnlyRootFilesystem=true would crash without these emptyDir mounts.
volumeMounts:
- name: tmp
mountPath: /tmp
- name: app-logs
mountPath: /juice-shop/logs
- name: app-data
mountPath: /juice-shop/data
- name: app-ftp
mountPath: /juice-shop/ftp
- name: frontend-dist
mountPath: /juice-shop/frontend/dist
- name: well-known
mountPath: /juice-shop/.well-known

volumes:
- name: tmp
emptyDir: {}
- name: app-logs
emptyDir: {}
- name: app-data
emptyDir: {}
- name: app-ftp
emptyDir: {}
- name: frontend-dist
emptyDir: {}
- name: well-known
emptyDir: {}
8 changes: 8 additions & 0 deletions labs/lab7/k8s/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Namespace
metadata:
name: juice-shop
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
38 changes: 38 additions & 0 deletions labs/lab7/k8s/networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: juice-shop-netpol
namespace: juice-shop
spec:
podSelector:
matchLabels:
app: juice-shop
policyTypes:
- Ingress
- Egress

ingress:
# Allow traffic only from pods in the same namespace (e.g. port-forward / ingress controller)
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: juice-shop
ports:
- protocol: TCP
port: 3000

egress:
# Allow DNS resolution (kube-system CoreDNS)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow outbound HTTPS (challenge webhook, npm registry if needed)
- ports:
- protocol: TCP
port: 443
6 changes: 6 additions & 0 deletions labs/lab7/k8s/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: juice-shop-sa
namespace: juice-shop
automountServiceAccountToken: false
39 changes: 39 additions & 0 deletions labs/lab7/policies/pod-hardening.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import rego.v1

# Deny if pod-level securityContext.runAsNonRoot is not true
deny contains msg if {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot == true
msg := "pod securityContext.runAsNonRoot must be true"
}

# Deny if any container is missing readOnlyRootFilesystem: true
deny contains msg if {
input.kind == "Deployment"
some container in input.spec.template.spec.containers
not container.securityContext.readOnlyRootFilesystem == true
msg := sprintf("container '%s' must set securityContext.readOnlyRootFilesystem: true", [container.name])
}

# Deny if any container is missing allowPrivilegeEscalation: false
deny contains msg if {
input.kind == "Deployment"
some container in input.spec.template.spec.containers
not container.securityContext.allowPrivilegeEscalation == false
msg := sprintf("container '%s' must set securityContext.allowPrivilegeEscalation: false", [container.name])
}

# Deny if any container does not drop ALL capabilities
# Uses object.get with a default of [] so this rule still fires even when
# securityContext (or capabilities, or drop) is missing entirely from the
# container spec -- otherwise "ALL" in undefined is undefined and the rule
# silently never triggers.
deny contains msg if {
input.kind == "Deployment"
some container in input.spec.template.spec.containers
drop := object.get(container, ["securityContext", "capabilities", "drop"], [])
not "ALL" in drop
msg := sprintf("container '%s' must drop ALL capabilities", [container.name])
}
Loading
Loading