Securitatea containerelor si Kubernetes: Bune practici pentru productie
Securitatea containerelor este o provocare pe mai multe straturi care acopera intregul ciclu de viata al containerului - de la imagini de baza pana la runtime. Kubernetes adauga o dimensiune suplimentara cu propriul model de securitate pentru orchestrare.
Acest ghid acopera practici de securitate aplicabile in medii de productie cu containere.
Securitatea imaginilor de container
Imagini de baza securizate
# Foloseste imagini de baza distroless sau minimale
FROM gcr.io/distroless/nodejs20-debian12:nonroot
# Sau foloseste o versiune specifica Alpine cu actualizari de securitate
FROM node:20.10.0-alpine3.18
# Aplica actualizari de securitate
RUN apk update && apk upgrade --no-cache
# Creeaza un utilizator non-root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Seteaza directorul de lucru
WORKDIR /app
# Copiaza doar fisierele necesare
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production
COPY --chown=appuser:appgroup . .
# Sterge pachetele inutile
RUN rm -rf /var/cache/apk/* /root/.npm
# Ruleaza ca non-root
USER appuser
# Foloseste un UID specific
USER 1001
# Nu rula ca root chiar daca imaginea permite
CMD ["node", "server.js"]Build multi-stage pentru securitate
# Etapa de build
FROM node:20-alpine AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production
# Etapa de scanare de securitate
FROM aquasec/trivy:latest AS scanner
COPY --from=builder /build /scan
RUN trivy filesystem --exit-code 1 --severity HIGH,CRITICAL /scan
# Etapa de productie
FROM gcr.io/distroless/nodejs20-debian12:nonroot
WORKDIR /app
# Copiaza doar artefactele compilate
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
COPY --from=builder /build/package.json ./
USER nonroot
CMD ["dist/server.js"]Pipeline de scanare a imaginilor
# GitHub Actions workflow pentru scanarea imaginilor
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Grype vulnerability scan
uses: anchore/scan-action@v3
with:
image: 'app:${{ github.sha }}'
fail-build: true
severity-cutoff: high
- name: Dockle lint
uses: erzz/dockle-action@v1
with:
image: 'app:${{ github.sha }}'
exit-code: '1'
failure-threshold: high
- name: Upload results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'Securitatea pod-urilor Kubernetes
Standarde de securitate pentru pod-uri
# Standard de securitate restrictiv pentru pod-uri
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# Specificatie de pod securizat
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
# Context de securitate la nivel de pod
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
seccompProfile:
type: RuntimeDefault
# Service account
serviceAccountName: app-service-account
automountServiceAccountToken: false
containers:
- name: app
image: myregistry/app:v1.0.0@sha256:abc123...
imagePullPolicy: Always
# Context de securitate la nivel de container
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1001
capabilities:
drop:
- ALL
# Limite de resurse
resources:
limits:
cpu: "1"
memory: "512Mi"
ephemeral-storage: "1Gi"
requests:
cpu: "100m"
memory: "128Mi"
# Probe de sanatate
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# Montari de volume
volumeMounts:
- name: tmp
mountPath: /tmp
- name: secrets
mountPath: /secrets
readOnly: true
# Volume
volumes:
- name: tmp
emptyDir: {}
- name: secrets
secret:
secretName: app-secrets
defaultMode: 0400
# Afinitate de nod
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- production
# Toleratii
tolerations: []
# Politica DNS
dnsPolicy: ClusterFirstSecuritatea deployment-urilor
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
annotations:
# Activeaza scanarea de securitate
container.apparmor.security.beta.kubernetes.io/app: runtime/default
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry/app:v1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- containerPort: 8080
protocol: TCP
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
---
# Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: secure-app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: secure-appSecuritatea retelei
Politici de retea
# Blocheaza tot traficul implicit
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Permite ingress specific
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: secure-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
---
# Permite aplicatiei sa acceseze baza de date
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: secure-app
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- protocol: TCP
port: 5432
# Permite DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
# Blocheaza egress catre serviciul de metadata (AWS/GCP)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-metadata-access
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32 # AWS metadata
- 169.254.0.0/16 # Link-local
- 10.0.0.0/8 # Intern (ajusteaza dupa necesitati)Managementul secretelor
External Secrets Operator
# Configurare External Secrets
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
template:
type: Opaque
data:
db-password: "{{ .db_password }}"
api-key: "{{ .api_key }}"
data:
- secretKey: db_password
remoteRef:
key: production/app/database
property: password
- secretKey: api_key
remoteRef:
key: production/app/api
property: keySealed Secrets
# Sealed Secret (criptat, sigur pentru git)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: app-secrets
namespace: production
spec:
encryptedData:
db-password: AgBY8s9j3K...encrypted...
api-key: AgDJ2k4m8N...encrypted...
template:
metadata:
name: app-secrets
namespace: production
type: OpaqueSecuritatea RBAC
Service Account cu privilegii minime
# Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
automountServiceAccountToken: false
---
# Rol cu permisiuni minime
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-secrets"]
verbs: ["get"]
---
# Role Binding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-role-binding
namespace: production
subjects:
- kind: ServiceAccount
name: app-service-account
namespace: production
roleRef:
kind: Role
name: app-role
apiGroup: rbac.authorization.k8s.ioRestrictii RBAC la nivel de cluster
# Blocheaza escaladarea privilegiilor la nivel de cluster
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deny-privilege-escalation
rules:
- apiGroups: [""]
resources: ["pods/exec", "pods/attach"]
verbs: ["create"]
- apiGroups: [""]
resources: ["nodes/proxy", "pods/proxy", "services/proxy"]
verbs: ["*"]
---
# Rol de audit pentru echipa de securitate
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: security-auditor
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: [] # Fara acces la secreteSecuritate runtime
Reguli Falco
# Reguli Falco pentru detectia la runtime
- rule: Terminal shell in container
desc: Detecteaza executia unui shell intr-un container
condition: >
spawned_process and container
and shell_procs
and proc.tty != 0
and container_entrypoint
output: >
Shell pornit intr-un container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname)
priority: WARNING
tags: [container, shell]
- rule: Sensitive file access
desc: Detecteaza accesul la fisiere sensibile
condition: >
open_read and container
and sensitive_files
output: >
Fisier sensibil citit
(user=%user.name file=%fd.name container=%container.name)
priority: WARNING
tags: [container, filesystem]
- rule: Crypto mining detection
desc: Detecteaza potential crypto mining
condition: >
spawned_process and container
and (proc.name in (crypto_miners) or
proc.args contains "stratum" or
proc.args contains "xmr")
output: >
Crypto mining detectat
(user=%user.name command=%proc.cmdline container=%container.name)
priority: CRITICAL
tags: [container, crypto]
- rule: Outbound connection to unusual port
desc: Detecteaza conexiuni de iesire catre porturi neobisnuite
condition: >
outbound and container
and not fd.port in (allowed_outbound_ports)
output: >
Conexiune de iesire neobisnuita
(container=%container.name connection=%fd.name)
priority: WARNING
tags: [container, network]Politici Admission Controller (Kyverno)
# Necesita limite de resurse
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Enforce
background: true
rules:
- name: require-limits
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Limitele de resurse sunt obligatorii"
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
---
# Necesita non-root
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
spec:
validationFailureAction: Enforce
rules:
- name: check-non-root
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Containerele nu trebuie sa ruleze ca root"
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- securityContext:
runAsNonRoot: true
---
# Necesita registre aprobate
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-approved-registries
spec:
validationFailureAction: Enforce
rules:
- name: check-registry
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Imaginile trebuie sa provina din registre aprobate"
pattern:
spec:
containers:
- image: "gcr.io/myproject/* | myregistry.azurecr.io/*"Monitorizarea securitatii
Metrici de securitate Prometheus
# ServiceMonitor pentru metrici de securitate
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: security-metrics
namespace: monitoring
spec:
selector:
matchLabels:
app: security-metrics
endpoints:
- port: metrics
interval: 30s
path: /metrics
---
# PrometheusRule pentru alerte de securitate
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: security-alerts
namespace: monitoring
spec:
groups:
- name: container-security
rules:
- alert: PrivilegedContainerDetected
expr: sum(kube_pod_container_info{container_privileged="true"}) > 0
for: 5m
labels:
severity: critical
annotations:
summary: Container privilegiat detectat
- alert: ContainerRunningAsRoot
expr: sum(kube_pod_container_info{container_run_as_user="0"}) > 0
for: 5m
labels:
severity: warning
annotations:
summary: Container care ruleaza ca root
- alert: HighCVEVulnerabilities
expr: trivy_vulnerability_count{severity="CRITICAL"} > 0
for: 1m
labels:
severity: critical
annotations:
summary: CVE critic detectat in imagineConcluzie
Securitatea containerelor si Kubernetes necesita o abordare de aparare in profunzime de-a lungul intregului ciclu de viata. De la imagini de baza securizate pana la protectie runtime, fiecare strat contribuie la postura generala de securitate.
Concluzii principale:
- Imagini securizate - Foloseste baze minimale, scaneaza regulat
- Privilegii minime - Non-root, capabilitati minimale, filesystem read-only
- Segmentarea retelei - Blocheaza implicit, permite explicit
- Managementul secretelor - Secrete externe, criptare at rest
- Protectie runtime - Monitorizeaza, detecteaza, raspunde
La DeviDevs, ajutam organizatiile sa isi securizeze deployment-urile Kubernetes cu implementari de securitate complete. Contacteaza-ne pentru a discuta nevoile tale de securitate a containerelor.
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →