CI/CD pipeline là mục tiêu có giá trị cao cho những kẻ tấn công vì chúng có quyền truy cập vào mã nguồn, thông tin xác thực triển khai và hệ thống production của bạn. CI/CD pipeline bị xâm phạm có thể dẫn đến các cuộc tấn công chuỗi cung ứng, đánh cắp bí mật hoặc mã độc được triển khai tới production. Cổ phần rất cao và bảo mật không thể là một suy nghĩ lại.
Nhiều nhà phát triển tập trung vào việc làm cho quy trình làm việc hoạt động hiệu quả mà không xem xét các tác động về bảo mật. Cách tiếp cận này có hiệu quả cho đến khi bạn gặp phải sự cố bảo mật đầu tiên, kiểm tra tuân thủ hoặc triển khai doanh nghiệp trong đó các yêu cầu bảo mật là không thể thương lượng. Hiểu các phương pháp bảo mật tốt nhất ngay từ đầu sẽ ngăn ngừa việc tái cấu trúc gây khó khăn sau này và bảo vệ ứng dụng cũng như người dùng của bạn.
Bề mặt tấn công CI/CD
CI/CD pipeline của bạn tạo ra một số vectơ tấn công tiềm ẩn không tồn tại trong quy trình phát triển truyền thống. Hiểu được những mối đe dọa này sẽ giúp bạn thiết kế các biện pháp phòng vệ thích hợp.
Source Code Insert: Kẻ tấn công có thể gửi pull request kèm theo các thay đổi quy trình làm việc độc hại được thực thi khi PR được xây dựng. Điều này đặc biệt nguy hiểm vì các tệp quy trình công việc có quyền truy cập vào các bí mật và có thể sửa đổi quy trình xây dựng.
Tiết lộ bí mật: Quy trình làm việc CI/CD cần quyền truy cập vào thông tin xác thực triển khai, khóa API và thông tin nhạy cảm khác. Việc quản lý bí mật kém có thể làm lộ các thông tin xác thực này trong nhật ký, artifact hoặc tệp quy trình làm việc.
Tấn công chuỗi cung ứng: Các phần phụ thuộc được tải xuống trong quá trình xây dựng có thể chứa mã độc. Cơ quan đăng ký gói bị xâm phạm hoặc các cuộc tấn công nhầm lẫn phụ thuộc có thể đưa các gói độc hại vào quá trình xây dựng của bạn.
Tăng cường đặc quyền: Các quy trình làm việc chạy với nhiều quyền có thể bị lợi dụng để truy cập các tài nguyên vượt quá mức cần thiết cho quá trình xây dựng và triển khai.
Bảo mật kích hoạt quy trình làm việc
Việc cân nhắc bảo mật quan trọng nhất là kiểm soát thời điểm chạy quy trình làm việc và mã nào chúng thực thi. Những kẻ độc hại thường cố gắng khai thác các trình kích hoạt quy trình làm việc để chạy mã trái phép.
name: Secure Workflow Triggers
# Be explicit about which events trigger workflows
on:
push:
branches: [main, develop]
# Never trigger on all branches - limit scope
pull_request:
branches: [main]
# Use pull_request_target carefully - it has access to secrets
# For workflows that use pull_request_target, add safety checks
pull_request_target:
types: [opened, synchronize]
branches: [main]
jobs:
# Security gate for external contributions
security-check:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_target'
steps:
# Only checkout the base branch initially, not the PR code
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
# Verify the PR is from a trusted source
- name: Check PR source
run: |
# Check if PR is from a fork
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "PR is from external fork: ${{ github.event.pull_request.head.repo.full_name }}"
# Check if author is a collaborator
AUTHOR="${{ github.event.pull_request.user.login }}"
if gh api repos/${{ github.repository }}/collaborators/$AUTHOR >/dev/null 2>&1; then
echo "✅ Author is a repository collaborator"
else
echo "❌ Author is not a collaborator, manual review required"
exit 1
fi
else
echo "✅ PR is from the same repository"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Only after verification, checkout the PR code
- name: Checkout PR code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Scan for suspicious changes in workflow files
- name: Scan workflow changes
run: |
# Check if .github/workflows was modified
if git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | grep -q "^\.github/workflows/"; then
echo "⚠️ Workflow files modified in this PR"
echo "Modified workflow files:"
git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} | grep "^\.github/workflows/"
# Require manual approval for workflow changes from forks
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "❌ External contributors cannot modify workflows without manual review"
exit 1
fi
fi
Mẫu này cung cấp nhiều lớp bảo vệ chống lại các sửa đổi quy trình làm việc độc hại trong khi vẫn cho phép đóng góp hợp pháp.
Quản lý và bảo vệ bí mật
Quản lý bí mật thích hợp là rất quan trọng đối với bảo mật CI/CD. Bí mật không bao giờ được xuất hiện trong nhật ký, tệp quy trình công việc hoặc artifact và quyền truy cập phải được giới hạn ở những quy trình công việc thực sự cần chúng.
name: Secure Secret Management
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Use environment protection
steps:
- uses: actions/checkout@v4
- name: Configure deployment credentials
env:
# Use secrets for sensitive information
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
# Configure AWS credentials without logging them
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
aws configure set region us-east-1
# Set up SSH key for deployment
mkdir -p ~/.ssh
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
# Verify configuration without exposing secrets
echo "✅ AWS configuration completed"
echo "✅ SSH key configuration completed"
- name: Validate secret availability
run: |
# Check that secrets are available without exposing them
if [ -z "${{ secrets.AWS_ACCESS_KEY_ID }}" ]; then
echo "❌ AWS_ACCESS_KEY_ID secret not configured"
exit 1
fi
if [ -z "${{ secrets.AWS_SECRET_ACCESS_KEY }}" ]; then
echo "❌ AWS_SECRET_ACCESS_KEY secret not configured"
exit 1
fi
if [ -z "${{ secrets.DEPLOY_KEY }}" ]; then
echo "❌ DEPLOY_KEY secret not configured"
exit 1
fi
echo "✅ All required secrets are configured"
# Use secrets safely in deployment commands
- name: Deploy to production
run: |
# Deploy using configured credentials
# Secrets are already configured and won't appear in logs
aws s3 sync dist/ s3://production-bucket/ --delete
# Use SSH key for server deployment
rsync -avz --delete -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \
dist/ deploy@production-server:/var/www/app/
# Clean up sensitive files
- name: Cleanup sensitive data
if: always()
run: |
# Remove temporary files containing secrets
rm -f ~/.ssh/deploy_key
rm -f ~/.aws/credentials
# Clear environment variables (bash history)
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset DEPLOY_KEY
Triển khai quyền truy cập đặc quyền tối thiểu
Quy trình làm việc phải có các quyền tối thiểu cần thiết để hoàn thành nhiệm vụ của mình. GitHub cung cấp các biện pháp kiểm soát quyền chi tiết mà bạn nên sử dụng để hạn chế thiệt hại tiềm ẩn do quy trình công việc bị xâm phạm.
name: Least Privilege Workflow
on:
push:
branches: [main]
pull_request:
branches: [main]
# Explicitly set minimal permissions
permissions:
contents: read # Read repository contents
actions: read # Read workflow status
checks: write # Write check status
pull-requests: write # Comment on PRs
jobs:
test:
runs-on: ubuntu-latest
# This job only needs to read code and write test results
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
echo "Running tests..."
# Test commands here
- name: Report test results
uses: actions/github-script@v7
with:
script: |
// This action can create check runs because we have checks: write permission
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Test Results',
head_sha: context.sha,
status: 'completed',
conclusion: 'success',
output: {
title: 'All tests passed',
summary: 'Test suite completed successfully'
}
});
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
environment: production
# Deployment needs different permissions
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- name: Create deployment
uses: actions/github-script@v7
with:
script: |
// Create deployment record
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'production',
auto_merge: false
});
// Store deployment ID for status updates
core.setOutput('deployment-id', deployment.data.id);
- name: Deploy application
run: |
echo "Deploying application..."
# Deployment commands here
- name: Update deployment status
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: '${{ steps.deploy.outputs.deployment-id }}',
state: 'success',
environment: 'production',
environment_url: 'https://myapp.com'
});
An ninh chuỗi cung ứng
Việc bảo vệ khỏi các cuộc tấn công chuỗi cung ứng đòi hỏi phải kiểm soát và xác thực các yếu tố phụ thuộc cũng như hành động mà quy trình công việc của bạn sử dụng.
name: Supply Chain Security
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Pin actions to specific SHA hashes for maximum security
- name: Setup Node.js (pinned)
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v4.0.3
with:
node-version: '18'
# Audit dependencies for known vulnerabilities
- name: Audit dependencies
run: |
echo "=== Dependency Security Audit ==="
# Check for known vulnerabilities
npm audit --audit-level=moderate
# Check for outdated packages with security updates
npm outdated --depth=0 || true
# Generate dependency report
npm list --all > dependency-report.txt
echo "📊 Dependency report generated"
# Scan for secrets accidentally committed to repository
- name: Secret scanning
run: |
echo "=== Secret Scanning ==="
# Look for common secret patterns
if grep -r -E "(password|passwd|pwd|secret|key|token|api_key)" . \
--exclude-dir=node_modules \
--exclude-dir=.git \
--exclude="*.md" \
--exclude="*.yml" \
--exclude="*.yaml"; then
echo "⚠️ Potential secrets found in code"
echo "Review the above matches to ensure no real secrets are committed"
else
echo "✅ No obvious secret patterns found"
fi
# Static code analysis for security issues
- name: Static security analysis
run: |
echo "=== Static Security Analysis ==="
# Install and run ESLint security plugin
npm install --no-save eslint-plugin-security
# Create temporary ESLint config for security scanning
cat > .eslintrc.security.js << 'EOF'
module.exports = {
plugins: ['security'],
extends: ['plugin:security/recommended'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
}
};
EOF
# Run security-focused linting
npx eslint --config .eslintrc.security.js src/ || {
echo "⚠️ Security issues found in code"
echo "Review and fix security-related ESLint errors"
}
# Check for dependency confusion attacks
- name: Check package integrity
run: |
echo "=== Package Integrity Check ==="
# Verify package-lock.json exists and is up to date
if [ ! -f "package-lock.json" ]; then
echo "❌ package-lock.json missing - dependency confusion risk"
exit 1
fi
# Check for suspicious package names that might be typosquatting
SUSPICIOUS_PATTERNS="^(react-|vue-|angular-|lodas|request|express)"
if npm list --json | jq -r '.dependencies | keys[]' | grep -E "$SUSPICIOUS_PATTERNS"; then
echo "⚠️ Suspicious package names found - verify legitimacy"
fi
echo "✅ Package integrity checks completed"
# Generate security report
- name: Generate security report
if: always()
run: |
cat > security-report.md << 'EOF'
# Security Scan Report
**Repository**: ${{ github.repository }}
**Commit**: ${{ github.sha }}
**Branch**: ${{ github.ref_name }}
**Scan Date**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
## Scans Performed
- ✅ Dependency vulnerability audit
- ✅ Secret pattern scanning
- ✅ Static code security analysis
- ✅ Package integrity verification
## Recommendations
1. Regularly update dependencies to patch security vulnerabilities
2. Use dependabot or similar tools for automated dependency updates
3. Implement pre-commit hooks to prevent secret commits
4. Consider using SAST tools in your development workflow
EOF
echo "Security report generated"
- name: Upload security report
uses: actions/upload-artifact@v4
if: always()
with:
name: security-report
path: security-report.md
retention-days: 30
Cổng phê duyệt và bảo vệ môi trường
Để triển khai production, hãy triển khai nhiều lớp bảo vệ để ngăn chặn việc triển khai trái phép hoặc vô tình.
name: Protected Production Deployment
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
build-version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Build application
run: |
echo "Building application..."
# Build steps here
- name: Generate build version
id: version
run: |
VERSION="v$(date +%Y%m%d-%H%M%S)-${GITHUB_SHA:0:7}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Build version: $VERSION"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ steps.version.outputs.version }}
path: dist/
# Staging deployment with automatic approval
deploy-staging:
runs-on: ubuntu-latest
needs: build
environment: staging
steps:
- name: Deploy to staging
run: |
echo "Deploying ${{ needs.build.outputs.build-version }} to staging"
# Staging deployment steps
- name: Run smoke tests
run: |
echo "Running smoke tests on staging..."
# Smoke test commands
# Production deployment with manual approval and time delays
deploy-production:
runs-on: ubuntu-latest
needs: [build, deploy-staging]
environment:
name: production
url: https://myapp.com
steps:
# Additional security checks before production deployment
- name: Pre-deployment security verification
run: |
echo "=== Pre-deployment Security Checks ==="
# Verify staging deployment is healthy
STAGING_HEALTH=$(curl -sf https://staging.myapp.com/health || echo "unhealthy")
if [ "$STAGING_HEALTH" != "healthy" ]; then
echo "❌ Staging environment is not healthy, aborting production deployment"
exit 1
fi
# Check for any active security incidents
echo "✅ Staging health check passed"
echo "✅ No active security incidents"
# Verify build artifacts integrity
echo "✅ Build artifact integrity verified"
- name: Deploy to production
run: |
echo "Deploying ${{ needs.build.outputs.build-version }} to production"
# Production deployment steps
# Create deployment record for audit trail
echo "Deployment completed at $(date -u)"
echo "Deployed by: ${{ github.actor }}"
echo "Build version: ${{ needs.build.outputs.build-version }}"
echo "Commit: ${{ github.sha }}"
- name: Post-deployment verification
run: |
echo "Running post-deployment verification..."
# Wait for deployment to stabilize
sleep 30
# Verify production health
for i in {1..5}; do
if curl -sf https://myapp.com/health; then
echo "✅ Production health check passed"
break
else
echo "⚠️ Health check attempt $i failed, retrying..."
sleep 30
fi
if [ $i -eq 5 ]; then
echo "❌ Production health checks failed"
exit 1
fi
done
- name: Send deployment notification
if: always()
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
if [ "${{ job.status }}" == "success" ]; then
STATUS="✅ successful"
COLOR="good"
else
STATUS="❌ failed"
COLOR="danger"
fi
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
--data "{
\"text\": \"Production deployment $STATUS\",
\"attachments\": [{
\"color\": \"$COLOR\",
\"fields\": [
{\"title\": \"Version\", \"value\": \"${{ needs.build.outputs.build-version }}\", \"short\": true},
{\"title\": \"Deployed by\", \"value\": \"${{ github.actor }}\", \"short\": true},
{\"title\": \"Commit\", \"value\": \"${{ github.sha }}\", \"short\": true}
]
}]
}"
Đường mòn tuân thủ và kiểm toán
Môi trường doanh nghiệp thường yêu cầu các quy trình kiểm tra chi tiết và tuân thủ các tiêu chuẩn bảo mật. Dưới đây là cách triển khai quy trình làm việc thân thiện với việc kiểm tra:
name: Compliance-Ready Workflow
on:
push:
branches: [main]
jobs:
audit-and-deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
# Create detailed audit log
- name: Create audit log entry
run: |
mkdir -p audit-logs
cat > audit-logs/deployment-$(date +%Y%m%d-%H%M%S).json << EOF
{
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"event_type": "deployment_started",
"repository": "${{ github.repository }}",
"branch": "${{ github.ref_name }}",
"commit_sha": "${{ github.sha }}",
"commit_message": $(echo '${{ github.event.head_commit.message }}' | jq -Rs .),
"author": "${{ github.event.head_commit.author.name }}",
"actor": "${{ github.actor }}",
"workflow": "${{ github.workflow }}",
"run_id": "${{ github.run_id }}",
"run_number": "${{ github.run_number }}",
"environment": "production",
"runner_os": "${{ runner.os }}",
"runner_arch": "${{ runner.arch }}"
}
EOF
# Verify compliance requirements
- name: Compliance checks
run: |
echo "=== Compliance Verification ==="
# Check that deployment is from approved branch
if [ "${{ github.ref_name }}" != "main" ]; then
echo "❌ Production deployments must be from main branch"
exit 1
fi
# Verify required approvals (in real scenario, this would check PR approvals)
echo "✅ Deployment from approved branch"
# Check that secrets are properly configured
if [ -z "${{ secrets.PROD_DEPLOY_KEY }}" ]; then
echo "❌ Production deployment key not configured"
exit 1
fi
echo "✅ Required secrets configured"
# Verify environment protection is active
echo "✅ Environment protection verified"
- name: Deploy with audit trail
run: |
echo "=== Production Deployment ==="
# Log deployment start
echo "Deployment started at $(date -u)"
echo "Commit: ${{ github.sha }}"
echo "Author: ${{ github.event.head_commit.author.name }}"
echo "Deployed by: ${{ github.actor }}"
# Actual deployment commands would go here
echo "Deploying application..."
# Log deployment completion
echo "Deployment completed at $(date -u)"
# Store audit logs
- name: Store audit logs
uses: actions/upload-artifact@v4
if: always()
with:
name: audit-logs-${{ github.run_id }}
path: audit-logs/
retention-days: 2555 # ~7 years for compliance
# Send audit notification
- name: Send audit notification
if: always()
run: |
# In a real scenario, this would send to your audit/compliance system
echo "Audit trail created for deployment ${{ github.run_id }}"
echo "Status: ${{ job.status }}"
echo "Logs stored in artifacts for compliance retention"
Bảo mật không chỉ là ngăn chặn các cuộc tấn công - mà còn là xây dựng niềm tin với người dùng, đáp ứng các yêu cầu tuân thủ và bảo vệ doanh nghiệp của bạn. Các mẫu này cung cấp nền tảng vững chắc cho CI/CD an toàn, nhưng bảo mật là một quá trình liên tục đòi hỏi phải xem xét và cập nhật thường xuyên khi các mối đe dọa phát triển.
Trong phần cuối cùng, chúng ta sẽ khám phá các ví dụ thực tế và thảo luận cách tiếp tục hành trình tự động hóa của bạn ngoài những kiến thức cơ bản được đề cập trong hướng dẫn này.