Skip to main content

Exercises and Projects


1 - Hands-on exercises

Exercise 1: SAST Pipeline

Objective: Configure a pipeline with SAST scanning.

Solution
# .github/workflows/sast.yml
name: SAST Pipeline

on: [push, pull_request]

jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: GitLeaks Secrets Scan
uses: gitleaks/gitleaks-action@v2

- name: Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten

- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

Exercise 2: Container Security

Objective: Scan and secure a Docker image.

Solution
# Secure Dockerfile
FROM node:20-alpine@sha256:abc123...

RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production

COPY --chown=appuser:appgroup src/ ./src/

USER appuser
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:8080/health || exit 1

CMD ["node", "src/index.js"]
# Pipeline scan
- name: Build Image
run: docker build -t myapp:${{ github.sha }} .

- name: Trivy Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'

- name: Hadolint
uses: hadolint/hadolint-[email protected]
with:
dockerfile: Dockerfile

Exercise 3: Kyverno Policies

Objective: Create Kubernetes policies with Kyverno.

Solution
# require-security-context.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-security-context
spec:
validationFailureAction: enforce
rules:
- name: require-run-as-non-root
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Pods must run as non-root"
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: enforce
rules:
- name: require-limits
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Containers must have resource limits"
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"

Exercise 4: Secrets with Vault

Objective: Integrate HashiCorp Vault into Kubernetes.

Solution
# Vault installation
helm install vault hashicorp/vault \
--set "server.dev.enabled=true"

# Configuration
kubectl exec -it vault-0 -- vault secrets enable -path=secret kv-v2
kubectl exec -it vault-0 -- vault kv put secret/myapp/config \
api_key="sk-12345" \
db_password="secure-pass"

# Kubernetes Auth
kubectl exec -it vault-0 -- vault auth enable kubernetes
kubectl exec -it vault-0 -- vault write auth/kubernetes/config \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"

# Role
kubectl exec -it vault-0 -- vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=default \
policies=myapp-policy \
ttl=1h
# Pod with Vault injection
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-config: "secret/data/myapp/config"
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: myapp:latest

2 - Complete project: Secure Application Deployment

Architecture

Complete pipeline

# .github/workflows/secure-deploy.yml
name: Secure Deployment Pipeline

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
# Stage 1: Pre-commit checks
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Secrets Detection
uses: gitleaks/gitleaks-action@v2

- name: Commit Lint
uses: pre-commit/[email protected]

# Stage 2: SAST
sast:
runs-on: ubuntu-latest
needs: pre-commit
steps:
- uses: actions/checkout@v4

- name: Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit p/owasp-top-ten

- name: CodeQL Analysis
uses: github/codeql-action/analyze@v2

# Stage 3: SCA
sca:
runs-on: ubuntu-latest
needs: pre-commit
steps:
- uses: actions/checkout@v4

- name: Snyk SCA
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high

# Stage 4: Build & Container Scan
build:
runs-on: ubuntu-latest
needs: [sast, sca]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- name: Hadolint
uses: hadolint/hadolint-[email protected]

- name: Build Image
run: docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .

- name: Trivy Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
exit-code: '1'
severity: 'CRITICAL,HIGH'

- name: Generate SBOM
run: syft ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} -o cyclonedx-json > sbom.json

- name: Sign Image
run: cosign sign --key env://COSIGN_KEY ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
env:
COSIGN_KEY: ${{ secrets.COSIGN_KEY }}

- name: Push Image
run: docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

# Stage 5: IaC Scan
iac-scan:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4

- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: kubernetes/
framework: kubernetes

# Stage 6: Deploy
deploy:
runs-on: ubuntu-latest
needs: iac-scan
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4

- name: Deploy to Kubernetes
run: |
kubectl apply -f kubernetes/
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}

Kyverno Policies

# policies/security-policies.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: secure-deployment
spec:
validationFailureAction: enforce
rules:
# Require signed images
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/*"
attestors:
- entries:
- keyless:
subject: "*@github.com"
issuer: "https://token.actions.githubusercontent.com"

# Require security context
- name: require-security-context
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Security context required"
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- securityContext:
allowPrivilegeEscalation: false

3 - Review quiz

  1. What is "Shift Left" in security?

  2. What is the difference between SAST and DAST?

  3. What is an SBOM?

  4. How do you secure secrets in Kubernetes?

  5. What is Kyverno?

Answers
  1. Shift Left: Integrate security earlier in the development cycle to catch issues before production.

  2. SAST: Static analysis of source code (before execution). DAST: Dynamic analysis of the running application.

  3. SBOM (Software Bill of Materials): A complete list of an application's components, versions, and dependencies.

  4. Use an external Secrets Manager (Vault, AWS Secrets Manager) with External Secrets Operator or Vault Agent Injector.

  5. Kyverno: A Kubernetes-native policy engine to validate, mutate, and generate resources.


4 - Certifications

CertificationProviderFocus
Certified Kubernetes Security (CKS)CNCFK8s Security
AWS Security SpecialtyAWSAWS Security
GIAC Cloud Security (GCLD)SANSCloud Security
Certified DevSecOps Professional (CDP)Practical DevSecOpsDevSecOps
CompTIA Security+CompTIAFundamentals

Resources


Course summary

Congratulations! You have completed the DevSecOps course.

You now have a solid grasp of:

  • Shift Left principles
  • The SAST and DAST tools
  • Container security
  • Supply Chain Security
  • Secrets management
  • Compliance as Code
  • Incident Response

Next steps

  • Practice with labs
  • Take the CKS certification
  • Implement DevSecOps in your organization
  • Contribute to open source security projects

← Back to table of contents