feat: security hardening + rewrite README for portfolio #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI Pipeline | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| push: | |
| branches: | |
| - main | |
| jobs: | |
| # ============================================================================ | |
| # STAGE 1: Fast Validation (< 2 min) | |
| # ============================================================================ | |
| validation: | |
| name: Code Validation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Terraform validation | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| - name: Terraform Format Check | |
| run: terraform fmt -check -recursive terraform/ | |
| continue-on-error: false | |
| - name: Terraform Init | |
| run: | | |
| cd terraform | |
| terraform init -backend=false | |
| - name: Terraform Validate | |
| run: | | |
| cd terraform | |
| terraform validate -no-color | |
| # YAML validation | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install yamllint | |
| run: pip install yamllint | |
| - name: YAML Lint | |
| run: | | |
| yamllint -f parsable \ | |
| docker/docker-compose.yml \ | |
| terraform/cloud-init.yaml \ | |
| .github/workflows/*.yml | |
| # Docker Compose validation | |
| - name: Validate docker-compose.yml | |
| run: | | |
| cd docker | |
| docker compose config --quiet | |
| # Markdown validation | |
| - name: Markdown Lint | |
| run: | | |
| FILES=$(find . -name "*.md" -not -path "*/\.terraform/*" -not -path "*/node_modules/*" -not -path "*/.git/*") | |
| if [ -n "$FILES" ]; then | |
| echo "$FILES" | xargs npx markdownlint-cli2 | |
| fi | |
| # ShellCheck | |
| - name: ShellCheck | |
| uses: ludeeus/action-shellcheck@master | |
| with: | |
| scandir: './scripts' | |
| severity: warning | |
| ignore_paths: '.git .terraform' | |
| # ============================================================================ | |
| # STAGE 2: Security Scans (runs after validation) | |
| # ============================================================================ | |
| security: | |
| name: Security Scanning | |
| runs-on: ubuntu-latest | |
| needs: validation | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| # Terraform security | |
| - name: Run tfsec | |
| uses: aquasecurity/tfsec-action@v1.0.3 | |
| with: | |
| working_directory: terraform | |
| soft_fail: false | |
| additional_args: --minimum-severity MEDIUM | |
| # IaC security scan | |
| - name: Run Trivy IaC scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| scan-type: 'config' | |
| scan-ref: '.' | |
| format: 'table' | |
| severity: 'MEDIUM,HIGH,CRITICAL' | |
| exit-code: '1' | |
| skip-dirs: '.git,.terraform,node_modules' | |
| # Secret detection | |
| - name: Run Gitleaks | |
| uses: gitleaks/gitleaks-action@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ============================================================================ | |
| # STAGE 3: Docker Checks (runs after validation, parallel with security) | |
| # ============================================================================ | |
| docker: | |
| name: Docker Checks | |
| runs-on: ubuntu-latest | |
| needs: validation | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| # Docker Compose security | |
| - name: Trivy Docker Compose scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| scan-type: 'config' | |
| scan-ref: 'docker/docker-compose.yml' | |
| format: 'table' | |
| severity: 'MEDIUM,HIGH,CRITICAL' | |
| exit-code: '1' | |
| # Custom Docker security checks | |
| - name: Check for privileged containers | |
| run: | | |
| if grep -q "privileged.*true" docker/docker-compose.yml; then | |
| echo "❌ ERROR: Privileged containers found!" | |
| exit 1 | |
| fi | |
| echo "✅ No privileged containers" | |
| - name: Check Docker socket permissions | |
| run: | | |
| if grep -q "/var/run/docker.sock.*:rw" docker/docker-compose.yml; then | |
| echo "❌ ERROR: Docker socket mounted as read-write!" | |
| exit 1 | |
| fi | |
| echo "✅ Docker socket permissions OK" | |
| - name: Check for hardcoded secrets | |
| run: | | |
| PATTERNS="password:|secret:|api_key:|token:" | |
| if grep -iE "$PATTERNS" docker/docker-compose.yml | grep -v "GITHUB_TOKEN\|secrets\."; then | |
| echo "❌ ERROR: Potential hardcoded secrets found!" | |
| exit 1 | |
| fi | |
| echo "✅ No hardcoded secrets detected" | |
| # ============================================================================ | |
| # STAGE 4: PR Automation (parallel with security/docker) | |
| # ============================================================================ | |
| pr-automation: | |
| name: PR Automation | |
| runs-on: ubuntu-latest | |
| needs: validation | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # PR Info | |
| - name: PR Information | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| console.log('PR Information:'); | |
| console.log(`- Title: ${pr.title}`); | |
| console.log(`- Author: ${pr.user.login}`); | |
| console.log(`- Base: ${pr.base.ref}`); | |
| console.log(`- Head: ${pr.head.ref}`); | |
| console.log(`- Files changed: ${pr.changed_files}`); | |
| console.log(`- Additions: +${pr.additions}`); | |
| console.log(`- Deletions: -${pr.deletions}`); | |
| # Auto-labeling | |
| - name: Auto Label PR | |
| uses: actions/labeler@v5 | |
| with: | |
| repo-token: "${{ secrets.GITHUB_TOKEN }}" | |
| configuration-path: .github/labeler.yml | |
| # Size labeling | |
| - name: Add Size Label | |
| uses: codelytv/pr-size-labeler@v1 | |
| with: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| xs_label: 'size/XS' | |
| xs_max_size: 10 | |
| s_label: 'size/S' | |
| s_max_size: 100 | |
| m_label: 'size/M' | |
| m_max_size: 500 | |
| l_label: 'size/L' | |
| l_max_size: 1000 | |
| xl_label: 'size/XL' | |
| # Conventional commits (informational only) | |
| - name: Validate Conventional Commits | |
| uses: wagoid/commitlint-github-action@v6 | |
| continue-on-error: true | |
| with: | |
| configFile: .github/commitlint.config.mjs | |
| failOnWarnings: false | |
| failOnErrors: false | |
| helpURL: 'https://www.conventionalcommits.org' | |
| # ============================================================================ | |
| # STAGE 5: Summary (runs after all checks) | |
| # ============================================================================ | |
| summary: | |
| name: CI Summary | |
| runs-on: ubuntu-latest | |
| needs: [validation, security, docker, pr-automation] | |
| if: always() | |
| steps: | |
| - name: Check Results | |
| run: | | |
| echo "## CI Pipeline Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.validation.result }}" == "success" ] && \ | |
| [ "${{ needs.security.result }}" == "success" ] && \ | |
| [ "${{ needs.docker.result }}" == "success" ]; then | |
| echo "✅ **All checks passed!**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Code Validation" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Security Scanning" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Docker Checks" >> $GITHUB_STEP_SUMMARY | |
| exit 0 | |
| else | |
| echo "❌ **Some checks failed**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- ${{ needs.validation.result == 'success' && '✅' || '❌' }} Code Validation" >> $GITHUB_STEP_SUMMARY | |
| echo "- ${{ needs.security.result == 'success' && '✅' || '❌' }} Security Scanning" >> $GITHUB_STEP_SUMMARY | |
| echo "- ${{ needs.docker.result == 'success' && '✅' || '❌' }} Docker Checks" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi |