courses

DevSecOps Fundamentals

Security as everyone’s job, at every stage

DevSecOps integrates security practices into every phase of the DevOps pipeline — planning, coding, building, testing, releasing, deploying, operating, and monitoring. Rather than treating security as a gate at the end of development, DevSecOps embeds it continuously throughout the software delivery lifecycle.

ℹ️ DevSecOps is the connective tissue for much of this course: it’s where threat modeling, application-security testing, and vulnerability management actually run. The shift is as much cultural as technical — security becomes a shared responsibility, automated into the pipeline rather than owned by a separate team at the end.

What is DevSecOps?

DevSecOps (Development, Security, Operations) extends the DevOps philosophy of collaboration and automation to include security as a shared responsibility across every team and every stage of the pipeline. The core shift is cultural: security is not the sole responsibility of a security team — developers write secure code, operations enforces secure configuration, and automated tooling prevents regressions.

DevSecOps vs DevOps

Aspect DevOps DevSecOps
Primary goals Speed, reliability, collaboration Speed, reliability, collaboration, and security
Security timing End-of-pipeline scan or separate audit Embedded at every stage
Security ownership Security team Shared across Dev, Sec, Ops
Vulnerability discovery Late (pre-release or post-release) Early (commit, build, PR review)
Compliance Manual audit cycles Continuous compliance validation
Tools added CI/CD, monitoring, IaC + SAST, DAST, SCA, secrets scanning, policy-as-code

The OWASP DevSecOps Guideline and the NIST Secure Software Development Framework (SSDF, SP 800-218) both formalize these principles.

The Shift-Left principle

“Shift left” means moving security activities earlier in the development lifecycle:

Traditional:    Plan → Code → Build → Test → Release → [Security Audit]
DevSecOps:      Plan → Code → Build → Test → Release → Deploy → Monitor
                  ↓      ↓      ↓      ↓       ↓         ↓        ↓
                Threat SAST   SCA   DAST   Image   Policy  Runtime
                model  lint   deps  scan   scan    check   alerts

Finding a vulnerability during design costs orders of magnitude less than fixing it post-production. IBM’s Systems Science Institute estimated the relative costs as: design (1×), coding (10×), testing (15×), production (100×).

Security Principles

CIA Triad

The CIA Triad is the foundational model for information security. Every security control exists to protect one or more of these properties:

Property Definition Example controls
Confidentiality Information is accessible only to authorized parties Encryption, access control, MFA
Integrity Information is accurate and has not been tampered with Checksums, digital signatures, audit logs
Availability Systems and data are accessible when needed Redundancy, DDoS mitigation, backups

A ransomware attack violates all three: it denies availability, may exfiltrate data (confidentiality), and often corrupts backups (integrity).

import hashlib

def verify_file_integrity(filepath: str, expected_sha256: str) -> bool:
    """Verify a file's SHA-256 hash to ensure integrity."""
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(65536), b''):
            sha256.update(chunk)
    actual = sha256.hexdigest()
    if actual != expected_sha256:
        raise ValueError(f"Integrity check FAILED: expected {expected_sha256}, got {actual}")
    return True

Defense in Depth

Defense in depth (also called layered security) uses multiple independent security controls so that the failure of any single layer does not compromise the entire system.

Internet
    │
    ▼
[WAF / DDoS protection]        ← Layer 1: Edge
    │
    ▼
[Network firewall / ACLs]      ← Layer 2: Network
    │
    ▼
[Load balancer / TLS termination] ← Layer 3: Transport
    │
    ▼
[Application firewall rules]   ← Layer 4: Application
    │
    ▼
[Authentication / Authorization] ← Layer 5: Identity
    │
    ▼
[Input validation / SAST output] ← Layer 6: Code
    │
    ▼
[Encrypted database / secrets] ← Layer 7: Data

No single control is assumed to be perfect. An attacker who bypasses the WAF still faces network ACLs. An attacker who gains network access still needs valid credentials. An attacker with valid credentials still cannot read encrypted data without the key.

Zero Trust

Zero Trust is a security model that eliminates implicit trust based on network location. The traditional “castle and moat” model trusted everything inside the perimeter — Zero Trust does not.

Zero Trust principles (NIST SP 800-207):

  1. All resources are accessed via authenticated, authorized requests — regardless of network location
  2. Access is granted on a per-session basis with least privilege
  3. All traffic is inspected and logged
  4. The network is assumed to be hostile (even internal traffic)
# Example: enforce mTLS (mutual TLS) between services with Istio
# Both client and server present certificates; the network is untrusted

kubectl apply -f - <<'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT        # reject all non-mTLS traffic, even within the cluster
EOF

Least Privilege

Every user, process, and service should have only the minimum permissions needed to perform its function, and only for as long as needed.

# Bad: application running as root
docker run --user root myapp

# Good: application running as unprivileged user (UID 1000)
docker run --user 1000:1000 myapp

# Bad: service account with cluster-admin
kubectl create clusterrolebinding myapp-admin \
  --clusterrole=cluster-admin --serviceaccount=default:myapp

# Good: service account with only what the app needs
kubectl create role myapp-role \
  --verb=get,list --resource=configmaps --namespace=myapp
kubectl create rolebinding myapp-binding \
  --role=myapp-role --serviceaccount=myapp:myapp
# Python: request only the IAM permissions needed
import boto3

# Bad: use a profile with AdministratorAccess
s3 = boto3.client('s3')

# Good: use an IAM role scoped to the specific bucket and operation
# (configured in AWS via least-privilege policy, attached to instance/ECS task role)
# The policy grants: s3:GetObject on arn:aws:s3:::my-bucket/*
s3 = boto3.client('s3', region_name='us-east-1')
obj = s3.get_object(Bucket='my-bucket', Key='report.pdf')

DevSecOps Lifecycle Integration

Security in Each Pipeline Stage

Plan:

Code:

Build:

Test:

Release:

Deploy:

Operate:

Monitor:

A minimal DevSecOps GitHub Actions pipeline

# .github/workflows/devsecops.yml
name: DevSecOps Pipeline

on: [push, pull_request]

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

      # Python SAST with Bandit
      - name: Run Bandit
        run: |
          pip install bandit
          bandit -r src/ -f json -o bandit-report.json -ll
        continue-on-error: false

      # Semgrep multi-language SAST
      # The old returntocorp/semgrep-action is retired — run the CLI directly.
      - name: Run Semgrep
        run: |
          pip install semgrep
          semgrep scan --config p/owasp-top-ten --error

  secrets:
    name: Secrets Detection
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # full history for gitleaks
      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2

  sca:
    name: Dependency Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Trivy filesystem scan
        # Pin to a full commit SHA, not a tag: trivy-action tags were
        # force-pushed during a March 2026 supply-chain compromise, so a
        # moving ref like @master (or even a tag) is not trustworthy.
        uses: aquasecurity/trivy-action@<commit-sha>   # SHA for a known-good release
        with:
          scan-type: fs
          scan-ref: .
          severity: CRITICAL,HIGH
          exit-code: 1

  container:
    name: Container Scan
    runs-on: ubuntu-latest
    needs: [sast, secrets, sca]
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:$ .
      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@<commit-sha>   # pin to a SHA (see note above)
        with:
          image-ref: myapp:$
          severity: CRITICAL,HIGH
          exit-code: 1

Scripting for DevSecOps

Python for security automation

Python is the primary language for DevSecOps automation. Key security libraries:

Library Purpose Install
bandit Python SAST scanner pip install bandit
safety Check Python deps for CVEs pip install safety
semgrep Multi-language SAST pip install semgrep
cryptography Crypto primitives pip install cryptography
paramiko SSH client/server pip install paramiko
requests HTTP client (with TLS) pip install requests
boto3 AWS SDK pip install boto3
hvac Vault/OpenBao API client pip install hvac
python-nmap Nmap Python wrapper pip install python-nmap
# Scan Python code with Bandit
pip install bandit
bandit -r myapp/ -ll                    # report medium+ severity issues
bandit -r myapp/ -f json -o report.json # machine-readable output

# Check dependencies for known CVEs
# `safety check` was retired (unsupported past June 2024); use `safety scan`,
# which auto-discovers requirements/Poetry/Pipenv files in the project.
pip install safety
safety scan                             # scan the project directory
safety scan --output json               # JSON output for CI
# Safety 3.x requires auth: run `safety auth` once, or set SAFETY_API_KEY in CI.

Bash for security scripting

# Harden a new Linux host: disable root login and empty passwords
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^PermitEmptyPasswords.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config
systemctl reload sshd

# Find world-writable files (potential privilege escalation vectors)
find / -xdev -type f -perm -0002 -ls 2>/dev/null

# Find SUID/SGID binaries (review for unexpected entries)
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -ls 2>/dev/null

# Check for failed SSH login attempts
journalctl -u sshd --since "1 hour ago" | grep "Failed password" | \
    awk '{print $11}' | sort | uniq -c | sort -rn | head -20

# Verify file checksums against a known-good manifest
sha256sum --check /etc/checksums.sha256

Key takeaways

References


Related course pages: Threat Modeling · Vulnerability Management · Containerization

🛠️ Maintenance note: third-party GitHub Actions move and get re-tagged — re-verify the uses: references each term, and replace the <commit-sha> placeholders on trivy-action with a current known-good commit SHA (its tags were force-pushed in a March 2026 supply-chain compromise, so prefer SHA pins). Also re-check the IBM cost-of-defect figures and the SSDF revision each term.