DevOps

DevSecOps Fundamentals

Understand DevSecOps principles: shift-left security, secure coding practices, and integrating security into CI/CD pipelines.

By TechCoder TeamLast updated: 2026-06-02
In a Nutshell

Understand DevSecOps principles: shift-left security, secure coding practices, and integrating security into CI/CD pipelines. This hands-on tutorial focuses on practical implementation of devsecops fundamentals concepts.

DevSecOps Fundamentals

DevSecOps integrates security practices into the DevOps workflow—shifting security left and making it everyone's responsibility.

What is DevSecOps?

Traditional:          DevSecOps:
Dev ──> Ops ──> Sec    Dev + Sec + Ops (simultaneous)
                    ┌─────────────────────┐
                    │  Security built-in  │
                    │  at every stage     │
                    └─────────────────────┘

Shift-Left Security

Requirements ──> Design ──> Code ──> Test ──> Deploy ──> Operate
    │               │          │         │         │         │
    ▼               ▼          ▼         ▼         ▼         ▼
 Threat        Security    SAST      DAST      Runtime  Incident
 Modeling       by        SonarQube  OWASP     Security   Response
               Design      Trivy     ZAP       WAF      Forensics

Security considerations moved earlier in the development lifecycle.

The Three Pillars of DevSecOps

  1. People: Culture of shared responsibility
  2. Process: Automated security checks in pipeline
  3. Technology: Security tools integrated into toolchain

Security in CI/CD Pipeline

Pipeline Security Stages

# .github/workflows/secure-pipeline.yml
name: Secure CI/CD

on: [push, pull_request]

jobs:
  # Stage 1: Secret Detection
  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Detect Secrets
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: main
          head: HEAD
          extra_args: --debug --only-verified

  # Stage 2: Software Composition Analysis (SCA)
  dependency-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Snyk
        uses: snyk/actions/node@master
        continue-on-error: true
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      - name: OWASP Dependency-Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'my-app'
          path: '.'
          format: 'ALL'

  # Stage 3: Static Application Security Testing (SAST)
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: SonarQube Scan
        uses: sonarqube-quality-gate/action@master
        with:
          host: ${{ secrets.SONAR_HOST }}
          token: ${{ secrets.SONAR_TOKEN }}
      - name: CodeQL Analysis
        uses: github/codeql-action/init@v2
        with:
          languages: javascript
      - uses: github/codeql-action/analyze@v2

  # Stage 4: Build and Image Scan
  build-scan:
    runs-on: ubuntu-latest
    needs: [secrets-scan, dependency-scan, sast]
    steps:
      - uses: actions/checkout@v4
      - name: Build Image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan Image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
      - name: Upload to Security Tab
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

  # Stage 5: Dynamic Application Security Testing (DAST)
  dast:
    runs-on: ubuntu-latest
    needs: build-scan
    steps:
      - name: Deploy to Staging
        run: ./deploy-staging.sh
      - name: OWASP ZAP Scan
        uses: zaproxy/action-full-scan@v0.7.0
        with:
          target: 'https://staging.example.com'
          rules_file_name: '.zap/rules.tsv'
          cmd_options: '-a'

Secure Coding Practices

OWASP Top 10 (2021)

RankRiskPrevention
A01Broken Access ControlLeast privilege, deny by default
A02Cryptographic FailuresEncrypt data, use strong algorithms
A03InjectionParameterized queries, input validation
A04Insecure DesignThreat modeling, secure patterns
A05Security MisconfigurationHardening, minimal features
A06Vulnerable ComponentsDependency scanning, updates
A07Auth FailuresMFA, strong passwords, session mgmt
A08Integrity FailuresSoftware supply chain security
A09Logging FailuresComprehensive logging, monitoring
A10SSRFURL validation, whitelist domains

Input Validation

# Bad
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)

# Good - Parameterized queries
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))

# Input validation
import re
from pydantic import BaseModel, validator

class UserInput(BaseModel):
    email: str
    age: int
    
    @validator('email')
    def validate_email(cls, v):
        if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', v):
            raise ValueError('Invalid email')
        return v
    
    @validator('age')
    def validate_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('Invalid age')
        return v

Secrets Management

# Bad - Never do this!
api_key: "sk-live-1234567890abcdef"

# Good - Use environment variables
api_key: ${API_KEY}

# Better - Use secret management service
# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id prod/api/key

# HashiCorp Vault
vault kv get secret/api/key

# Kubernetes Secrets
kubectl create secret generic api-key --from-literal=key=secretvalue

# Docker Secrets (Swarm)
echo "secretvalue" | docker secret create api_key -

Container Security

Dockerfile Best Practices

# Use minimal base images
FROM alpine:latest
# Or even better - distroless
FROM gcr.io/distroless/nodejs18-debian11

# Run as non-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# Scan for vulnerabilities
# docker scan myimage:latest
# or trivy image myimage:latest

# Don't leak secrets
# Bad
ENV API_KEY=sk-1234567890

# Good
ENV API_KEY=${API_KEY}
ARG API_KEY
RUN echo $API_KEY > /app/config && ...

# Multi-stage to reduce attack surface
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]

Container Security Scanning

# Trivy - comprehensive scanner
trivy image myimage:latest
trivy fs --security-checks vuln,config,secret ./
trivy k8s --report summary cluster

# Grype
grype myimage:latest

# Docker Scout
docker scout cves myimage:latest

# In CI/CD
trivy image --exit-code 1 --severity HIGH,CRITICAL myimage:latest

Infrastructure Security

Terraform Security (tfsec)

# Install tfsec
brew install tfsec

# Scan Terraform
tfsec .
tfsec . --format sarif --out tfsec.sarif

# Fix issues automatically
tfsec . --fix
# Bad - Unencrypted S3 bucket
resource "aws_s3_bucket" "bad" {
  bucket = "my-bucket"
}

# Good - Encrypted with private ACL
resource "aws_s3_bucket" "good" {
  bucket = "my-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "good" {
  bucket = aws_s3_bucket.good.bucket
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
      kms_master_key_id = aws_kms_key.mykey.arn
    }
  }
}

resource "aws_s3_bucket_public_access_block" "good" {
  bucket = aws_s3_bucket.good.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Kubernetes Security

# Pod Security Standards
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
    resources:
      limits:
        memory: "256Mi"
        cpu: "500m"
      requests:
        memory: "128Mi"
        cpu: "250m"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /cache
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}
# kube-bench - CIS benchmark scanning
docker run --pid=host aquasec/kube-bench:latest

# kube-hunter - penetration testing
kube-hunter --remote some.node.com

# kubectl audit
kubectl get pods --all-namespaces -o json | kubesec scan -

Compliance as Code

Policy as Code (Open Policy Agent)

# policy.rego - Deny public S3 buckets
package aws.s3

deny[msg] {
    bucket := input.resource.aws_s3_bucket[name]
    bucket.acl == "public-read"
    msg := sprintf("S3 bucket %s is publicly readable", [name])
}

deny[msg] {
    bucket := input.resource.aws_s3_bucket[name]
    not bucket.server_side_encryption_configuration
    msg := sprintf("S3 bucket %s is not encrypted", [name])
}
# Run OPA
opa eval --data policy.rego --input terraform.tfstate.json "data.aws.s3.deny"

Security Metrics

MetricTargetTool
Vulnerability Scan Coverage100% of imagesTrivy, Snyk
Mean Time to Patch (MTTP)< 24 hoursDependency bot
Security Test Pass Rate> 95%CI/CD pipeline
Secrets in Code0GitLeaks, TruffleHog
IAM Policy Violations0IAM Analyzer

Quiz

Quiz

Question 1 of 5

What does 'shift-left security' mean?

Moving security checks to production
Integrating security earlier in the development lifecycle
Using left-hand authentication
Security only for legacy systems

Next Steps

Now let's explore security tools and secrets management in detail.