Skip to content
Vladimir Chavkov
Go back

DevSecOps: Integrating Security Automation into CI/CD Pipelines

Edit page

DevSecOps: Integrating Security Automation into CI/CD Pipelines

Security can no longer be an afterthought in modern software development. DevSecOps embeds security practices throughout the development lifecycle, shifting security left and making it everyone’s responsibility. This guide shows you how to build secure CI/CD pipelines with automated security testing and compliance checks.

The DevSecOps Philosophy

Traditional Security vs. DevSecOps

Traditional Approach:

Dev → QA → Security Review → Production
(Weeks/Months delay)

DevSecOps Approach:

Dev + Security → Automated Tests → Production
(Continuous Integration of Security)

Core Principles

  1. Shift Left: Catch security issues early in development
  2. Automate Everything: Security checks in every pipeline run
  3. Continuous Monitoring: Security doesn’t end at deployment
  4. Shared Responsibility: Developers own application security
  5. Fail Fast: Block deployments with critical vulnerabilities

Security Testing Types

1. Static Application Security Testing (SAST)

Analyzes source code for security vulnerabilities without executing it.

Tools:

GitHub Actions Example:

.github/workflows/sast.yml
name: SAST Scanning
on:
push:
branches: [main, develop]
pull_request:
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@v3
- name: Run Semgrep
run: |
semgrep scan \
--config auto \
--sarif --output semgrep.sarif \
--severity ERROR \
--severity WARNING
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: semgrep.sarif
if: always()
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history for better analysis
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- name: Quality Gate Check
uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

2. Dynamic Application Security Testing (DAST)

Tests running applications for vulnerabilities.

Tools:

OWASP ZAP Integration:

.github/workflows/dast.yml
name: DAST Scanning
on:
schedule:
- cron: '0 2 * * *' # Run nightly
workflow_dispatch:
jobs:
zap_scan:
runs-on: ubuntu-latest
steps:
- name: Deploy to test environment
run: |
# Deploy application to staging
kubectl apply -f k8s/staging/
- name: Wait for deployment
run: sleep 60
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'https://staging.example.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.4.0
with:
target: 'https://staging.example.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-j'
- name: Upload ZAP Results
uses: actions/upload-artifact@v3
with:
name: zap-reports
path: |
report_html.html
report_json.json

3. Software Composition Analysis (SCA)

Scans dependencies for known vulnerabilities.

Tools:

Multi-Language SCA Pipeline:

.github/workflows/sca.yml
name: Dependency Scanning
on:
push:
schedule:
- cron: '0 0 * * 0' # Weekly scan
jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip-audit
run: pip install pip-audit
- name: Run pip-audit
run: pip-audit --requirement requirements.txt --format json --output pip-audit.json
- name: Safety Check
run: |
pip install safety
safety check --json --output safety.json
javascript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: npm audit
run: npm audit --json > npm-audit.json
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --json-file-output=snyk.json
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: checkov.sarif

4. Container Image Scanning

Scan Docker images for vulnerabilities and misconfigurations.

Tools:

Container Scanning Pipeline:

.github/workflows/container-scan.yml
name: Container Security
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-scan:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Run Grype scanner
uses: anchore/scan-action@v3
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
fail-build: true
severity-cutoff: high
- name: Scan with Snyk
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
args: --severity-threshold=high
- name: Push image if scans pass
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

5. Infrastructure as Code Security

Scan IaC templates for security misconfigurations.

Tools:

Terraform Security Pipeline:

.github/workflows/terraform-security.yml
name: Terraform Security
on:
pull_request:
paths:
- 'terraform/**'
jobs:
tfsec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform/
format: sarif
soft_fail: false
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: tfsec.sarif
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
quiet: false
soft_fail: false
output_format: sarif
download_external_modules: true
terrascan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Terrascan
uses: tenable/terrascan-action@main
with:
iac_type: 'terraform'
iac_dir: 'terraform/'
policy_type: 'aws'
only_warn: false
sarif_upload: true

Secret Detection and Management

1. Pre-Commit Secret Scanning

Prevent secrets from entering version control:

.pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.1
hooks:
- id: gitleaks
- repo: https://github.com/trufflesecurity/trufflehog
rev: v3.63.0
hooks:
- id: trufflehog
args: ['--regex', '--entropy=False']

Install pre-commit hooks:

Terminal window
pip install pre-commit
pre-commit install
pre-commit run --all-files

2. CI/CD Secret Scanning

.github/workflows/secret-scan.yml
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
trufflehog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD

3. Secrets Management Best Practices

# Use secrets management tools
# GitHub Actions with AWS Secrets Manager
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::ACCOUNT:role/GitHubActionsRole
aws-region: us-east-1
- name: Retrieve secrets
uses: aws-actions/aws-secretsmanager-get-secrets@v1
with:
secret-ids: |
prod/database/credentials
prod/api/keys
parse-json-secrets: true
- name: Use secrets
env:
DB_USERNAME: ${{ env.PROD_DATABASE_CREDENTIALS_USERNAME }}
DB_PASSWORD: ${{ env.PROD_DATABASE_CREDENTIALS_PASSWORD }}
API_KEY: ${{ env.PROD_API_KEYS_API_KEY }}
run: |
# Secrets available as environment variables
./deploy.sh

Security Policy as Code

1. Open Policy Agent (OPA) for Kubernetes

policy/deployment-security.rego
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Deployment"
container := input.request.object.spec.template.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container %v must run as non-root", [container.name])
}
deny[msg] {
input.request.kind.kind == "Deployment"
container := input.request.object.spec.template.spec.containers[_]
container.securityContext.privileged
msg := sprintf("Container %v cannot run as privileged", [container.name])
}
deny[msg] {
input.request.kind.kind == "Deployment"
not input.request.object.spec.template.spec.securityContext.runAsUser
msg := "Deployment must specify runAsUser"
}

Test policies in CI:

.github/workflows/policy-test.yml
name: OPA Policy Testing
on: [push, pull_request]
jobs:
opa-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
with:
version: latest
- name: Run OPA Tests
run: |
opa test policy/ -v
- name: Validate Kubernetes manifests
run: |
for manifest in k8s/*.yaml; do
opa eval \
--data policy/ \
--input "$manifest" \
--format pretty \
"data.kubernetes.admission.deny"
done

Complete DevSecOps Pipeline

.github/workflows/devsecops.yml
name: DevSecOps Pipeline
on:
push:
branches: [main, develop]
pull_request:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 1. Secret Scanning
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 2. SAST
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: returntocorp/semgrep-action@v1
with:
config: auto
# 3. SCA - Dependency Scanning
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# 4. Build and Test
build:
runs-on: ubuntu-latest
needs: [secret-scan, sast, sca]
steps:
- uses: actions/checkout@v3
- name: Run tests
run: npm test
- name: Build Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Save image
run: |
docker save ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} > image.tar
- uses: actions/upload-artifact@v3
with:
name: docker-image
path: image.tar
# 5. Container Scanning
container-scan:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@v3
with:
name: docker-image
- name: Load image
run: docker load < image.tar
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
# 6. Infrastructure Security
iac-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: k8s/
framework: kubernetes
# 7. Deploy to Staging
deploy-staging:
runs-on: ubuntu-latest
needs: [container-scan, iac-scan]
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
kubectl apply -f k8s/staging/
# 8. DAST on Staging
dast:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: ZAP Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'https://staging.example.com'
# 9. Deploy to Production
deploy-production:
runs-on: ubuntu-latest
needs: [dast]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
kubectl apply -f k8s/production/
- name: Verify deployment
run: |
kubectl rollout status deployment/app -n production

Compliance and Reporting

1. Generate Security Reports

.github/workflows/security-report.yml
name: Weekly Security Report
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 8 AM
jobs:
generate-report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run all security scans
run: |
# Run Trivy, Snyk, Checkov, etc.
# Aggregate results
- name: Generate SARIF report
run: |
python scripts/aggregate-security-results.py
- name: Upload to Security Dashboard
run: |
curl -X POST https://security-dashboard.company.com/api/reports \
-H "Authorization: Bearer ${{ secrets.SECURITY_TOKEN }}" \
-F "report=@security-report.json"
- name: Send Slack notification
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "Weekly Security Report Available",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "View the latest security scan results"
}
}
]
}

Best Practices Checklist

Code Security

Dependency Management

Container Security

Infrastructure Security

Pipeline Security

Compliance

Conclusion

DevSecOps is not a destination but a continuous journey. By integrating security automation into your CI/CD pipelines, you shift security left, catch vulnerabilities early, and build a culture where security is everyone’s responsibility.

Start small—add one security scan to your pipeline this week. Gradually expand coverage until security is woven into every stage of your development lifecycle. The investment in security automation pays dividends in reduced risk, faster remediation, and increased confidence in your deployments.


Need help implementing DevSecOps? Our security automation training programs cover everything from basic scanning to advanced security orchestration with hands-on labs. Explore DevSecOps training or contact us to build your security-first culture.


Edit page
Share this post on:

Previous Post
Terraform Best Practices: Production-Ready Infrastructure as Code
Next Post
GitOps and Continuous Delivery: ArgoCD vs Flux for Kubernetes