- Eliminate the installation of Node.js for the checkout action in the CI workflow to streamline the process and reduce unnecessary dependencies.
468 lines
18 KiB
YAML
468 lines
18 KiB
YAML
---
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
|
|
jobs:
|
|
lint-and-test:
|
|
runs-on: ubuntu-latest
|
|
# Skip push events for non-master branches (they'll be covered by PR events)
|
|
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
|
|
container:
|
|
image: node:20-bullseye
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Lint markdown
|
|
run: npm run test:markdown
|
|
|
|
- name: Check markdown links
|
|
run: npm run test:links
|
|
continue-on-error: true
|
|
|
|
ansible-validation:
|
|
runs-on: ubuntu-latest
|
|
# Skip push events for non-master branches (they'll be covered by PR events)
|
|
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl git
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible and linting tools
|
|
run: pip3 install --no-cache-dir ansible ansible-lint yamllint
|
|
|
|
- name: Validate YAML syntax
|
|
run: |
|
|
echo "Checking YAML syntax..."
|
|
find . -name "*.yml" -o -name "*.yaml" | grep -v ".git" | while read file; do
|
|
python3 -c "import yaml; yaml.safe_load(open('$file'))" || exit 1
|
|
done
|
|
|
|
- name: Run ansible-lint
|
|
run: |
|
|
# Skip vault-encrypted files and playbooks that require vault passwords
|
|
ansible-lint --skip-list vault,internal-error || true
|
|
continue-on-error: true
|
|
|
|
secret-scanning:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: zricethezav/gitleaks:latest
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apk add --no-cache nodejs npm curl
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Scan for secrets
|
|
run: gitleaks detect --source . --no-banner --redact --exit-code 0
|
|
continue-on-error: true
|
|
|
|
dependency-scan:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: aquasec/trivy:latest
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apk add --no-cache nodejs npm curl
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Scan dependencies
|
|
run: trivy fs --scanners vuln,secret --exit-code 0 .
|
|
|
|
sast-scan:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Semgrep
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
pip3 install semgrep
|
|
|
|
- name: Run Semgrep scan
|
|
run: semgrep --config=auto --error
|
|
continue-on-error: true
|
|
|
|
license-check:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: node:20-bullseye
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install license-checker
|
|
run: npm install -g license-checker
|
|
|
|
- name: Check npm licenses
|
|
run: |
|
|
if [ -f "package.json" ]; then
|
|
license-checker --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause;ISC;BSD-2-Clause' || true
|
|
else
|
|
echo "No package.json found, skipping license check"
|
|
fi
|
|
continue-on-error: true
|
|
|
|
vault-check:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible
|
|
run: pip3 install --no-cache-dir ansible
|
|
|
|
- name: Validate vault files are encrypted
|
|
run: |
|
|
echo "Checking for Ansible Vault files..."
|
|
vault_files=$(find . -name "*vault*.yml" -o -name "*vault*.yaml" | grep -v ".git" | grep -v ".example" || true)
|
|
if [ -z "$vault_files" ]; then
|
|
echo "No vault files found"
|
|
exit 0
|
|
fi
|
|
failed=0
|
|
for vault_file in $vault_files; do
|
|
echo "Checking $vault_file..."
|
|
# Check if file starts with ANSIBLE_VAULT header (doesn't require password)
|
|
if head -n 1 "$vault_file" | grep -q "^\$ANSIBLE_VAULT"; then
|
|
echo "✓ $vault_file is properly encrypted (has vault header)"
|
|
else
|
|
echo "✗ ERROR: $vault_file does not have ANSIBLE_VAULT header - may be unencrypted!"
|
|
failed=1
|
|
fi
|
|
done
|
|
if [ $failed -eq 1 ]; then
|
|
echo "Some vault files are not encrypted. Please encrypt them with: ansible-vault encrypt <file>"
|
|
exit 1
|
|
fi
|
|
echo "All vault files are properly encrypted!"
|
|
|
|
playbook-test:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible
|
|
run: pip3 install --no-cache-dir ansible
|
|
|
|
- name: Dry-run playbooks
|
|
run: |
|
|
echo "Running dry-run tests on playbooks..."
|
|
failed=0
|
|
for playbook in playbooks/*.yml; do
|
|
if [ -f "$playbook" ]; then
|
|
echo "Testing $playbook..."
|
|
if ansible-playbook "$playbook" --syntax-check --list-tasks > /dev/null 2>&1; then
|
|
echo "✓ $playbook syntax is valid"
|
|
else
|
|
echo "✗ $playbook has syntax errors"
|
|
failed=1
|
|
fi
|
|
fi
|
|
done
|
|
if [ $failed -eq 1 ]; then
|
|
echo "❌ Some playbooks have syntax errors!"
|
|
echo "Note: This may be expected if playbooks require inventory/vault, but syntax errors should still be fixed."
|
|
exit 1
|
|
else
|
|
echo "✅ All playbooks passed syntax check"
|
|
fi
|
|
continue-on-error: true
|
|
|
|
container-scan:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Trivy
|
|
run: |
|
|
set -e
|
|
apt-get update && apt-get install -y wget curl tar
|
|
|
|
# Use a fixed, known-good Trivy version to avoid URL/redirect issues
|
|
TRIVY_VERSION="0.58.2"
|
|
TRIVY_URL="https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
|
|
|
|
echo "Installing Trivy version: ${TRIVY_VERSION}"
|
|
echo "Downloading from: ${TRIVY_URL}"
|
|
|
|
if ! wget --progress=bar:force "${TRIVY_URL}" -O /tmp/trivy.tar.gz 2>&1; then
|
|
echo "❌ Failed to download Trivy archive"
|
|
echo "Checking if file was partially downloaded:"
|
|
ls -lh /tmp/trivy.tar.gz 2>/dev/null || echo "No file found"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f /tmp/trivy.tar.gz ] || [ ! -s /tmp/trivy.tar.gz ]; then
|
|
echo "❌ Downloaded Trivy archive is missing or empty"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Download complete. File size: $(du -h /tmp/trivy.tar.gz | cut -f1)"
|
|
echo "Extracting Trivy..."
|
|
if ! tar -xzf /tmp/trivy.tar.gz -C /tmp/ trivy; then
|
|
echo "❌ Failed to extract Trivy binary from archive"
|
|
tar -tzf /tmp/trivy.tar.gz 2>&1 | head -20 || true
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f /tmp/trivy ]; then
|
|
echo "❌ Trivy binary not found after extraction"
|
|
ls -la /tmp/ | grep trivy || ls -la /tmp/ | head -20
|
|
exit 1
|
|
fi
|
|
|
|
mv /tmp/trivy /usr/local/bin/trivy
|
|
chmod +x /usr/local/bin/trivy
|
|
/usr/local/bin/trivy --version
|
|
trivy --version
|
|
|
|
- name: Scan for Dockerfiles and container configs
|
|
run: |
|
|
if [ -f "Dockerfile" ] || [ -f "docker-compose.yml" ] || find . -name "Dockerfile*" -o -name "*.dockerfile" 2>/dev/null | grep -v ".git" | head -1 > /dev/null; then
|
|
echo "Dockerfiles found. Scanning filesystem for container-related vulnerabilities..."
|
|
echo "Note: This scans filesystem, not built images."
|
|
echo "To scan actual images, build them first and use: trivy image <image:tag>"
|
|
trivy fs --scanners vuln --severity HIGH,CRITICAL --format table . || true
|
|
else
|
|
echo "No Dockerfiles found, skipping container image scan"
|
|
exit 0
|
|
fi
|
|
continue-on-error: true
|
|
|
|
sonar-analysis:
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
env:
|
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Java and SonarScanner
|
|
run: |
|
|
set -e
|
|
apt-get update && apt-get install -y wget curl unzip openjdk-21-jre
|
|
|
|
# Use a known working version to avoid download issues
|
|
SONAR_SCANNER_VERSION="5.0.1.3006"
|
|
SCANNER_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux.zip"
|
|
|
|
echo "Installing SonarScanner version: ${SONAR_SCANNER_VERSION}"
|
|
echo "Downloading from: ${SCANNER_URL}"
|
|
|
|
# Download with verbose error output
|
|
if ! wget --progress=bar:force "${SCANNER_URL}" -O /tmp/sonar-scanner.zip 2>&1; then
|
|
echo "❌ Failed to download SonarScanner"
|
|
echo "Checking if file was partially downloaded:"
|
|
ls -lh /tmp/sonar-scanner.zip 2>/dev/null || echo "No file found"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify download
|
|
if [ ! -f /tmp/sonar-scanner.zip ] || [ ! -s /tmp/sonar-scanner.zip ]; then
|
|
echo "❌ Downloaded file is missing or empty"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Download complete. File size: $(du -h /tmp/sonar-scanner.zip | cut -f1)"
|
|
|
|
echo "Extracting SonarScanner..."
|
|
if ! unzip -q /tmp/sonar-scanner.zip -d /tmp; then
|
|
echo "❌ Failed to extract SonarScanner"
|
|
echo "Archive info:"
|
|
file /tmp/sonar-scanner.zip || true
|
|
unzip -l /tmp/sonar-scanner.zip 2>&1 | head -20 || true
|
|
exit 1
|
|
fi
|
|
|
|
# Find the extracted directory (handle both naming conventions)
|
|
EXTRACTED_DIR=""
|
|
if [ -d "/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux" ]; then
|
|
EXTRACTED_DIR="/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux"
|
|
elif [ -d "/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux" ]; then
|
|
EXTRACTED_DIR="/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux"
|
|
else
|
|
# Try to find any sonar-scanner directory
|
|
EXTRACTED_DIR=$(find /tmp -maxdepth 1 -type d -name "*sonar-scanner*" | head -1)
|
|
fi
|
|
|
|
if [ -z "$EXTRACTED_DIR" ] || [ ! -d "$EXTRACTED_DIR" ]; then
|
|
echo "❌ SonarScanner directory not found after extraction"
|
|
echo "Contents of /tmp:"
|
|
ls -la /tmp/ | grep -E "(sonar|zip)" || ls -la /tmp/ | head -20
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found extracted directory: ${EXTRACTED_DIR}"
|
|
mv "${EXTRACTED_DIR}" /opt/sonar-scanner
|
|
|
|
# Create symlink
|
|
if [ -f /opt/sonar-scanner/bin/sonar-scanner ]; then
|
|
ln -sf /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner
|
|
chmod +x /opt/sonar-scanner/bin/sonar-scanner
|
|
chmod +x /usr/local/bin/sonar-scanner
|
|
else
|
|
echo "❌ sonar-scanner binary not found in /opt/sonar-scanner/bin/"
|
|
echo "Contents of /opt/sonar-scanner/bin/:"
|
|
ls -la /opt/sonar-scanner/bin/ || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "Verifying installation..."
|
|
if ! sonar-scanner --version; then
|
|
echo "❌ SonarScanner verification failed"
|
|
echo "PATH: $PATH"
|
|
which sonar-scanner || echo "sonar-scanner not in PATH"
|
|
exit 1
|
|
fi
|
|
echo "✓ SonarScanner installed successfully"
|
|
|
|
- name: Verify SonarQube connection
|
|
run: |
|
|
echo "Checking SonarQube connectivity..."
|
|
if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then
|
|
echo "❌ ERROR: SONAR_HOST_URL or SONAR_TOKEN secrets are not set!"
|
|
echo "Please configure them in: Repository Settings → Actions → Secrets"
|
|
exit 1
|
|
fi
|
|
echo "✓ Secrets are configured"
|
|
echo "SonarQube URL: ${SONAR_HOST_URL}"
|
|
echo "Testing connectivity to SonarQube server..."
|
|
if curl -f -s -o /dev/null -w "%{http_code}" "${SONAR_HOST_URL}/api/system/status" | grep -q "200"; then
|
|
echo "✓ SonarQube server is reachable"
|
|
else
|
|
echo "⚠️ Warning: Could not verify SonarQube server connectivity"
|
|
fi
|
|
|
|
- name: Run SonarScanner
|
|
run: |
|
|
echo "Starting SonarQube analysis..."
|
|
if ! sonar-scanner \
|
|
-Dsonar.projectKey=ansible \
|
|
-Dsonar.sources=. \
|
|
-Dsonar.host.url=${SONAR_HOST_URL} \
|
|
-Dsonar.token=${SONAR_TOKEN} \
|
|
-Dsonar.scm.disabled=true \
|
|
-Dsonar.python.version=3.10 \
|
|
-X; then
|
|
echo ""
|
|
echo "❌ SonarScanner analysis failed!"
|
|
echo ""
|
|
echo "Common issues:"
|
|
echo " 1. Project 'ansible' doesn't exist in SonarQube"
|
|
echo " → Create it manually in SonarQube UI"
|
|
echo " 2. Token doesn't have permission to analyze/create project"
|
|
echo " → Ensure token has 'Execute Analysis' permission"
|
|
echo " 3. Token doesn't have 'Create Projects' permission (if project doesn't exist)"
|
|
echo " → Grant this permission in SonarQube user settings"
|
|
echo ""
|
|
echo "Check SonarQube logs for more details."
|
|
exit 1
|
|
fi
|
|
continue-on-error: true
|
|
|
|
workflow-summary:
|
|
runs-on: ubuntu-latest
|
|
needs: [lint-and-test, ansible-validation, secret-scanning, dependency-scan, sast-scan, license-check, vault-check, playbook-test, container-scan, sonar-analysis]
|
|
if: always()
|
|
steps:
|
|
- name: Generate workflow summary
|
|
run: |
|
|
echo "## 🔍 CI Workflow Summary" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "### Job Results" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📝 Markdown Linting | ${{ needs.lint-and-test.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔧 Ansible Validation | ${{ needs.ansible-validation.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔐 Secret Scanning | ${{ needs.secret-scanning.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📦 Dependency Scan | ${{ needs.dependency-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔍 SAST Scan | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📄 License Check | ${{ needs.license-check.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔒 Vault Check | ${{ needs.vault-check.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📋 Playbook Test | ${{ needs.playbook-test.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🐳 Container Scan | ${{ needs.container-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔍 SonarQube Analysis | ${{ needs.sonar-analysis.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "### 📊 Summary" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "All security and validation checks have completed." >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "**Note:** Artifact uploads are not supported in Gitea Actions. Check individual job logs for detailed reports." >> $GITHUB_STEP_SUMMARY || true
|
|
continue-on-error: true
|