Update README.md #47
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
| # ============================================================================== | |
| # GitHub Actions CI Pipeline for Apotropaios - Firewall Manager | |
| # Description: Multi-stage CI with syntax check, lint, security scan, unit tests, | |
| # integration tests, security tests, and multi-distro matrix. | |
| # Version: 1.1.5 | |
| # ============================================================================== | |
| name: CI | |
| on: | |
| push: | |
| branches: [main, develop, 'feature/**', 'fix/**'] | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_dispatch: | |
| inputs: | |
| debug_enabled: | |
| description: 'Enable debug logging' | |
| required: false | |
| default: 'false' | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| permissions: | |
| contents: read | |
| security-events: write | |
| jobs: | |
| # =========================================================================== | |
| # Stage 1: Syntax Check (fastest gate) | |
| # =========================================================================== | |
| syntax: | |
| name: Syntax Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Bash syntax check | |
| run: make syntax-check | |
| # =========================================================================== | |
| # Stage 2: ShellCheck Lint | |
| # =========================================================================== | |
| lint: | |
| name: ShellCheck Lint | |
| needs: syntax | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install ShellCheck | |
| run: sudo apt-get install -y shellcheck | |
| - name: Run ShellCheck | |
| run: make lint | |
| # =========================================================================== | |
| # Stage 3: Security Scan (static analysis + security tests) | |
| # =========================================================================== | |
| security: | |
| name: Security Scan | |
| needs: lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install BATS | |
| run: | | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| sudo /tmp/bats/install.sh /usr/local | |
| - name: Install ShellCheck | |
| run: sudo apt-get install -y shellcheck | |
| - name: Security-focused ShellCheck (severity=warning) | |
| run: | | |
| echo "==> Security-focused static analysis..." | |
| find . -name '*.sh' -not -path './dist/*' -not -path './.git/*' -not -path './data/*' | while read -r f; do | |
| shellcheck -S warning -x "$f" 2>/dev/null || true | |
| done | |
| - name: Check for dangerous patterns | |
| run: | | |
| echo "==> Scanning for dangerous patterns..." | |
| found=0 | |
| # Check for eval with user-supplied data (exclude framework-internal eval) | |
| # Known-safe: security_scrub_vars (var scrubbing), _CLEANUP_STACK (cleanup), | |
| # error_with_fallback (primary/fallback), util_parallel_exec (framework cmd) | |
| matches=$(grep -rn 'eval.*\$' lib/ apotropaios.sh --include='*.sh' \ | |
| | grep -v 'eval "exec' \ | |
| | grep -v '^\s*#' \ | |
| | grep -v 'security_scrub_vars\|_CLEANUP_STACK\|cleanup\|scrub\|error_with_fallback\|util_parallel' \ | |
| || true) | |
| if [ -n "$matches" ]; then | |
| echo "$matches" | |
| echo "::warning::Found eval with variable expansion outside known-safe patterns (review required)" | |
| found=1 | |
| fi | |
| # Check for unquoted command substitution in dangerous contexts | |
| # Exclude: variable assignments (local/readonly/export), arithmetic, | |
| # log messages, printf arguments, and string interpolation in quotes | |
| matches=$(grep -rn '\$(' lib/ apotropaios.sh --include='*.sh' \ | |
| | grep -v '^\s*#' \ | |
| | grep -v 'local \|readonly \|export \|="\$(\|="$(' \ | |
| | grep -v 'log_\|printf\|echo' \ | |
| | grep -v '\$((\|util_\|basename\|dirname\|date\|wc\|grep\|cat\|head\|cut' \ | |
| || true) | |
| if [ -n "$matches" ]; then | |
| echo "$matches" | |
| echo "::notice::Found potentially unquoted command substitution (review recommended)" | |
| found=1 | |
| fi | |
| # Check for /tmp without mktemp | |
| matches=$(grep -rn '/tmp/' lib/ apotropaios.sh --include='*.sh' \ | |
| | grep -v '#\|mktemp\|test\|TMPDIR\|printf\|help\|echo' \ | |
| || true) | |
| if [ -n "$matches" ]; then | |
| echo "$matches" | |
| echo "::warning::Found hardcoded /tmp paths — use mktemp instead" | |
| found=1 | |
| fi | |
| if [ "$found" -eq 0 ]; then | |
| echo " ✓ No dangerous patterns found" | |
| fi | |
| echo "==> Pattern scan complete" | |
| - name: Run security tests | |
| run: | | |
| mkdir -p test-results | |
| bats tests/security/ --tap > test-results/security.tap 2>&1 || { | |
| echo "::error::Security tests failed" | |
| cat test-results/security.tap | |
| exit 1 | |
| } | |
| echo "Security tests passed: $(grep -c '^ok ' test-results/security.tap)" | |
| - name: Upload security results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: security-scan-results | |
| path: test-results/ | |
| retention-days: 30 | |
| # =========================================================================== | |
| # Stage 4a: Unit Tests | |
| # =========================================================================== | |
| unit-tests: | |
| name: Unit Tests (${{ matrix.os }}) | |
| needs: lint | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-22.04 | |
| - os: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install BATS | |
| run: | | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| sudo /tmp/bats/install.sh /usr/local | |
| - name: Run unit tests | |
| run: | | |
| mkdir -p test-results | |
| bats tests/unit/ --tap > test-results/unit.tap 2>&1 || { | |
| cat test-results/unit.tap; exit 1 | |
| } | |
| echo "Unit: $(grep -c '^ok ' test-results/unit.tap) passed" | |
| - name: Upload results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: unit-results-${{ matrix.os }} | |
| path: test-results/ | |
| retention-days: 14 | |
| # =========================================================================== | |
| # Stage 4b: Integration Tests | |
| # =========================================================================== | |
| integration-tests: | |
| name: Integration Tests (${{ matrix.os }}) | |
| needs: lint | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-22.04 | |
| - os: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install BATS | |
| run: | | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| sudo /tmp/bats/install.sh /usr/local | |
| - name: Run integration tests | |
| run: | | |
| mkdir -p test-results | |
| bats tests/integration/ --tap > test-results/integration.tap 2>&1 || { | |
| cat test-results/integration.tap; exit 1 | |
| } | |
| echo "Integration: $(grep -c '^ok ' test-results/integration.tap) passed" | |
| - name: Upload results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: integration-results-${{ matrix.os }} | |
| path: test-results/ | |
| retention-days: 14 | |
| # =========================================================================== | |
| # Stage 5: Multi-Distro Container Tests | |
| # =========================================================================== | |
| distro-tests: | |
| name: Distro (${{ matrix.name }}) | |
| needs: lint | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Debian-12 | |
| container: 'debian:12' | |
| pkg_manager: apt | |
| - name: Kali-Linux | |
| container: 'kalilinux/kali-rolling' | |
| pkg_manager: apt | |
| - name: Rocky-9 | |
| container: 'rockylinux:9' | |
| pkg_manager: dnf | |
| - name: Alma-9 | |
| container: 'almalinux:9' | |
| pkg_manager: dnf | |
| - name: Arch | |
| container: 'archlinux:latest' | |
| pkg_manager: pacman | |
| container: | |
| image: ${{ matrix.container }} | |
| steps: | |
| - name: Install prerequisites (apt) | |
| if: matrix.pkg_manager == 'apt' | |
| run: | | |
| apt-get update -qq | |
| apt-get install -y bash git curl make coreutils procps util-linux | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| cd /tmp/bats && ./install.sh /usr/local | |
| - name: Install prerequisites (dnf) | |
| if: matrix.pkg_manager == 'dnf' | |
| run: | | |
| dnf install -y --allowerasing bash git curl make coreutils procps-ng util-linux | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| cd /tmp/bats && ./install.sh /usr/local | |
| - name: Install prerequisites (pacman) | |
| if: matrix.pkg_manager == 'pacman' | |
| run: | | |
| pacman -Sy --noconfirm bash git curl make coreutils procps-ng util-linux | |
| git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats | |
| cd /tmp/bats && ./install.sh /usr/local | |
| - uses: actions/checkout@v6 | |
| - name: Run all tests | |
| run: | | |
| mkdir -p test-results | |
| bats tests/unit/ --tap > test-results/unit.tap 2>&1 || { cat test-results/unit.tap; exit 1; } | |
| bats tests/integration/ --tap > test-results/integration.tap 2>&1 || { cat test-results/integration.tap; exit 1; } | |
| bats tests/security/ --tap > test-results/security.tap 2>&1 || { cat test-results/security.tap; exit 1; } | |
| u=$(grep -c '^ok ' test-results/unit.tap) | |
| i=$(grep -c '^ok ' test-results/integration.tap) | |
| s=$(grep -c '^ok ' test-results/security.tap) | |
| echo "${{ matrix.name }}: ${u} unit + ${i} integration + ${s} security = $((u+i+s)) total" | |
| - name: Upload results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: distro-results-${{ matrix.name }} | |
| path: test-results/ | |
| retention-days: 14 | |
| # =========================================================================== | |
| # Stage 6: Test Summary | |
| # =========================================================================== | |
| test-summary: | |
| name: Test Summary | |
| needs: [unit-tests, integration-tests, security, distro-tests] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Download all results | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: all-results/ | |
| - name: Generate summary | |
| run: | | |
| echo "## Test Results Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stage | Result |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Unit Tests | ${{ needs.unit-tests.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Integration Tests | ${{ needs.integration-tests.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Security Scan | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Distro Tests | ${{ needs.distro-tests.result }} |" >> $GITHUB_STEP_SUMMARY | |
| - name: Check all jobs | |
| run: | | |
| if [ "${{ needs.unit-tests.result }}" != "success" ]; then | |
| echo "::error::Unit tests failed"; exit 1 | |
| fi | |
| if [ "${{ needs.integration-tests.result }}" != "success" ]; then | |
| echo "::error::Integration tests failed"; exit 1 | |
| fi | |
| if [ "${{ needs.security.result }}" != "success" ]; then | |
| echo "::error::Security scan failed"; exit 1 | |
| fi | |
| if [ "${{ needs.distro-tests.result }}" != "success" ]; then | |
| echo "::error::Distro tests failed"; exit 1 | |
| fi | |
| echo "All CI stages passed" |