Skip to main content

DevOps Security

Master DevOps security practices including container security and secrets management. Learn to secure your infrastructure, applications, and CI/CD pipelines.

Security in DevOps (DevSecOps)

DevSecOps integrates security practices into the DevOps workflow, making security everyone's responsibility from development through deployment.

Core Principles

Shift Left

  • Security testing early in development
  • Find vulnerabilities before production
  • Automated security scanning in CI/CD
  • Developer security training

Continuous Security

  • Security as code
  • Automated compliance checks
  • Continuous monitoring
  • Regular security audits

Least Privilege

  • Minimum necessary permissions
  • Time-limited access
  • Regular access reviews
  • Just-in-time (JIT) access

Container Security

Security Layers

┌─────────────────────────────────┐
│ 1. Image Security │
│ - Scan for vulnerabilities │
│ - Use trusted base images │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 2. Runtime Security │
│ - Non-root users │
│ - Resource limits │
│ - Read-only filesystems │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 3. Host Security │
│ - Hardened OS │
│ - Updated kernel │
│ - Minimal attack surface │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 4. Network Security │
│ - Network policies │
│ - Service mesh │
│ - Encrypted communication │
└─────────────────────────────────┘

Container Image Security

Use Official and Minimal Base Images

# ❌ BAD - Large attack surface
FROM ubuntu:latest

# ✅ GOOD - Minimal, updated base image
FROM alpine:3.18

# ✅ BETTER - Distroless (minimal runtime)
FROM gcr.io/distroless/static-debian11

# ✅ BEST - Specific version, minimal
FROM alpine:3.18.4

Multi-Stage Builds

# Build stage - includes build tools
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# Runtime stage - minimal, no build tools
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .

# Non-root user
RUN adduser -D appuser
USER appuser

CMD ["./myapp"]

Don't Run as Root

# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Or in Alpine
RUN addgroup -S appuser && adduser -S appuser -G appuser

# Set ownership
COPY --chown=appuser:appuser . /app

# Switch to non-root user
USER appuser

# ✅ Container runs as appuser, not root

Read-Only Root Filesystem

# Dockerfile
FROM alpine:3.18
RUN adduser -D appuser
USER appuser
WORKDIR /app
COPY app .
CMD ["./app"]

Docker run:

docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
myapp:latest

Kubernetes:

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: myapp
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

Vulnerability Scanning

Trivy (Comprehensive Scanner)

# Install Trivy
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install trivy

# Scan Docker image
trivy image nginx:latest

# Scan with severity filter
trivy image --severity HIGH,CRITICAL myapp:1.0.0

# Scan filesystem
trivy fs /path/to/project

# Generate report
trivy image --format json --output results.json myapp:1.0.0

Clair (Google Container Analysis)

# Run Clair
docker run -d --name clair \
-p 6060:6060 \
quay.io/coreos/clair:latest

# Scan with clairctl
clairctl analyze myapp:latest

Snyk

# Install Snyk
npm install -g snyk

# Authenticate
snyk auth

# Scan Docker image
snyk container test myapp:latest

# Monitor image
snyk container monitor myapp:latest

Integrate in CI/CD

GitHub Actions:

name: Security Scan

on: [push]

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Build image
run: docker build -t myapp:${{ github.sha }} .

- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1' # Fail build on vulnerabilities

Container Runtime Security

Security Context (Kubernetes)

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault

containers:
- name: myapp
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

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

Pod Security Standards

Restricted Policy (Most Secure):

apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

AppArmor Profile

apiVersion: v1
kind: Pod
metadata:
name: secured-pod
annotations:
container.apparmor.security.beta.kubernetes.io/myapp: runtime/default
spec:
containers:
- name: myapp
image: myapp:latest

Secrets Management

Never Hard-Code Secrets

# ❌ NEVER DO THIS
ENV API_KEY="abc123secret"
ENV DB_PASSWORD="SuperSecret123!"

# ❌ NEVER COMMIT THIS
# config.yml
database:
password: "hardcoded_password"

# ✅ Use environment variables or secret managers

Kubernetes Secrets

Create Secret:

# From literal values
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=SuperSecret123!

# From file
kubectl create secret generic api-key \
--from-file=api-key.txt

# From YAML (base64 encoded)
echo -n 'SuperSecret123!' | base64

secrets.yaml:

apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64 encoded 'admin'
password: U3VwZXJTZWNyZXQxMjMh # base64 encoded 'SuperSecret123!'

Use Secrets in Pod:

apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp
image: myapp:latest
env:
# Single secret as environment variable
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password

# All secrets as environment variables
envFrom:
- secretRef:
name: db-credentials

# Mount secrets as files
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true

volumes:
- name: secret-volume
secret:
secretName: db-credentials

HashiCorp Vault

Install Vault

# Download and install
wget https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip
unzip vault_1.15.0_linux_amd64.zip
sudo mv vault /usr/local/bin/

# Start Vault dev server (NOT for production)
vault server -dev

Store and Retrieve Secrets

# Set VAULT_ADDR
export VAULT_ADDR='http://127.0.0.1:8200'

# Store secret
vault kv put secret/myapp/db password=SuperSecret123! username=admin

# Retrieve secret
vault kv get secret/myapp/db

# Get specific field
vault kv get -field=password secret/myapp/db

Vault in Kubernetes

Install Vault using Helm:

helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault

Inject Secrets into Pods:

apiVersion: v1
kind: Pod
metadata:
name: myapp
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-database: "secret/data/myapp/db"
vault.hashicorp.com/agent-inject-template-database: |
{{- with secret "secret/data/myapp/db" -}}
export DB_PASSWORD="{{ .Data.data.password }}"
export DB_USERNAME="{{ .Data.data.username }}"
{{- end }}
spec:
serviceAccountName: myapp
containers:
- name: myapp
image: myapp:latest
command: ["/bin/sh"]
args:
- -c
- source /vault/secrets/database && ./app

AWS Secrets Manager

Store Secrets

# Create secret
aws secretsmanager create-secret \
--name prod/db/credentials \
--secret-string '{"username":"admin","password":"SuperSecret123!"}'

# Retrieve secret
aws secretsmanager get-secret-value \
--secret-id prod/db/credentials

# Update secret
aws secretsmanager update-secret \
--secret-id prod/db/credentials \
--secret-string '{"username":"admin","password":"NewPassword456!"}'

Use in Application

Python:

import boto3
import json

def get_secret():
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId='prod/db/credentials')
secret = json.loads(response['SecretString'])
return secret

# Use secret
secret = get_secret()
db_password = secret['password']

In Kubernetes with External Secrets Operator:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: myapp

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/db/credentials
property: password

Secret Scanning in Git

git-secrets (Pre-commit Hook)

# Install git-secrets
git clone https://github.com/awslabs/git-secrets
cd git-secrets
sudo make install

# Initialize in repo
cd /path/to/your/repo
git secrets --install
git secrets --register-aws

# Scan all history
git secrets --scan-history

Gitleaks

# Install gitleaks
brew install gitleaks

# Scan repository
gitleaks detect --source . --verbose

# Scan in CI/CD
gitleaks detect --source . --report-format json --report-path gitleaks-report.json

GitHub Actions:

name: Secret Scanning

on: [push, pull_request]

jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2

Network Security

Kubernetes Network Policies

Deny All Ingress:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress

Allow Specific Traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

Service Mesh (Istio)

# Mutual TLS (mTLS) between services
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT

# Authorization policy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: frontend-to-backend
namespace: production
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/frontend"]

CI/CD Security

Secure CI/CD Pipeline

┌─────────────────────────────────────┐
│ 1. Code Commit │
│ - Branch protection │
│ - Signed commits │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 2. Secret Scanning │
│ - Gitleaks, git-secrets │
│ - Block if secrets found │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 3. Code Analysis │
│ - SAST (static analysis) │
│ - Dependency scanning │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 4. Build │
│ - Reproducible builds │
│ - Sign artifacts │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 5. Container Scan │
│ - Trivy, Clair, Snyk │
│ - Block on critical vulns │
└──────────────┬──────────────────────┘

┌─────────────────────────────────────┐
│ 6. Deploy │
│ - Least privilege credentials │
│ - Audit logging │
└─────────────────────────────────────┘

GitHub Actions Security

name: Secure Pipeline

on:
push:
branches: [main]

permissions:
contents: read # Least privilege

jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Secret scanning
uses: gitleaks/gitleaks-action@v2

- name: Dependency scanning
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

- name: SAST
uses: github/codeql-action/init@v2

- name: Build
run: docker build -t myapp:${{ github.sha }} .

- name: Container scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'

Best Practices Checklist

Container Security

  • ✅ Use minimal base images
  • ✅ Run as non-root user
  • ✅ Read-only root filesystem
  • ✅ Scan for vulnerabilities
  • ✅ Sign images
  • ✅ Use specific image tags (not latest)
  • ✅ Remove unnecessary packages
  • ✅ Use multi-stage builds

Secrets Management

  • ✅ Never hard-code secrets
  • ✅ Use secret managers (Vault, AWS Secrets Manager)
  • ✅ Rotate secrets regularly
  • ✅ Scan for secrets in Git
  • ✅ Encrypt secrets at rest
  • ✅ Use short-lived credentials
  • ✅ Audit secret access
  • ✅ Minimum necessary permissions

Network Security

  • ✅ Implement network policies
  • ✅ Use mTLS for service-to-service communication
  • ✅ Encrypt traffic (TLS)
  • ✅ Segment networks
  • ✅ Monitor network traffic
  • ✅ Use service mesh for advanced security

CI/CD Security

  • ✅ Scan code for secrets
  • ✅ Static code analysis (SAST)
  • ✅ Dependency scanning
  • ✅ Container vulnerability scanning
  • ✅ Least privilege for CI/CD
  • ✅ Sign artifacts
  • ✅ Audit logging

Resources

Tools

Documentation


Secure your infrastructure and applications with DevSecOps best practices!