Why is CI/CD Security Important?
Your CI/CD pipeline has access to source code, credentials, production environments, and artifact repositories.
A compromised pipeline can be used to deploy malware, steal secrets, or gain access to production.
Pipeline security is a critical component of your DevSecOps strategy.
DevSecOps Pipeline Architecture
Security Gates in CI/CD
1. Pre-commit
Checks before code is committed
- Pre-commit hooks (secrets detection)
- Linting and formatting
- Local SAST scan
2. Build Stage
Checks during build
- SAST - static code analysis
- SCA - dependency analysis
- Secret scanning (GitLeaks)
- License compliance
3. Test Stage
Security tests
- Unit tests with security assertions
- Container image scanning (Trivy)
- IaC scanning (tfsec, Checkov)
4. Deploy Stage
Pre-deployment checks
- DAST - dynamic testing
- Approval gates for production
- Deployment verification
5. Post-deploy
Continuous monitoring
- Runtime security monitoring
- Vulnerability alerts
- Compliance checks
Secrets Management
Proper handling of secrets (API keys, passwords, certificates) is one of the most important aspects of CI/CD security.
NEVER do this
- Hardcoded secrets in code or pipeline configurations
- Secrets in environment variables without encryption
- Sharing secrets between environments (dev/prod)
- Long-lived static access keys
- Secrets in Docker images
Proper Secrets Handling
# GitLab CI - using protected variables
variables:
# Reference to secret stored in GitLab CI/CD settings
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
# Better - using OIDC (no static keys)
deploy:
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- |
export $(aws sts assume-role-with-web-identity \
--role-arn $AWS_ROLE_ARN \
--role-session-name "gitlab-$CI_JOB_ID" \
--web-identity-token $GITLAB_OIDC_TOKEN \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
--output text | xargs -n1 echo)
# Best - using external secrets manager
deploy:
before_script:
- |
# Retrieve secrets from AWS Secrets Manager
export DB_PASSWORD=$(aws secretsmanager get-secret-value \
--secret-id prod/db/password \
--query SecretString --output text)
Example: Secure DevSecOps Pipeline
# .gitlab-ci.yml - DevSecOps Pipeline
stages:
- test
- build
- scan
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# ===== TEST STAGE =====
# Secret detection
gitleaks:
stage: test
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --source . --report-path gitleaks-report.json
artifacts:
paths: [gitleaks-report.json]
when: always
allow_failure: true
# SAST - static analysis
semgrep:
stage: test
image: returntocorp/semgrep
script:
- semgrep ci --json --output semgrep-results.json
artifacts:
paths: [semgrep-results.json]
when: always
allow_failure: true
# SCA - dependency scanning
dependency_check:
stage: test
image: owasp/dependency-check:latest
script:
- /usr/share/dependency-check/bin/dependency-check.sh \
--project "MyApp" \
--scan . \
--format JSON \
--out dependency-check-report.json
artifacts:
paths: [dependency-check-report.json]
when: always
allow_failure: true
# ===== BUILD STAGE =====
build_image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
# ===== SCAN STAGE =====
# Container image scanning
trivy:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --exit-code 0 --severity HIGH,CRITICAL \
--format json --output trivy-results.json $DOCKER_IMAGE
artifacts:
paths: [trivy-results.json]
when: always
dependencies:
- build_image
# ===== DEPLOY STAGE =====
deploy_staging:
stage: deploy
environment:
name: staging
script:
- echo "Deploying to staging..."
only:
- main
deploy_production:
stage: deploy
environment:
name: production
script:
- echo "Deploying to production..."
when: manual # Requires manual approval
only:
- main
dependencies:
- deploy_staging
CI/CD Security Best Practices
Pipeline Configuration
- Pipeline as Code in Git repository
- Protected branches - no direct pushes to main
- Required code reviews
- Signed commits
- Pinned versions of tools and images
Secrets & Access
- OIDC/Workload Identity instead of static keys
- External secrets manager (Vault, AWS Secrets Manager)
- Secrets rotation
- Least privilege for pipeline service accounts
- Audit logging of all access
Security Gates
- Fail pipeline on critical findings
- Manual approval for production deploys
- Security review for infrastructure changes
- Rollback mechanisms
- Deployment windows
Security Risks in CI/CD
- Poisoned Pipeline Execution (PPE) - pipeline configuration manipulation
- Dependency Confusion - package manager exploitation
- Credential theft from pipeline logs
- Supply chain attacks via build tools
- Privilege escalation through self-hosted runners