diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml new file mode 100644 index 0000000..3169f83 --- /dev/null +++ b/.gitea/workflows/security.yml @@ -0,0 +1,177 @@ +name: Security Scan and Upload + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '0 0 * * 1' + workflow_dispatch: + +jobs: + security-audit: + name: Security & DefectDojo Upload + runs-on: ubuntu-latest + continue-on-error: true + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # --- AUTO-SETUP PROJECT --- + - name: Ensure DefectDojo Product Exists + env: + DD_URL: ${{ secrets.DD_URL }} + DD_TOKEN: ${{ secrets.DD_TOKEN }} + PRODUCT_NAME: ${{ github.repository }} + PRODUCT_TYPE_ID: 1 + run: | + sudo apt-get install jq -y > /dev/null + + echo "Checking connection to $DD_URL..." + + # Check if product exists - capture HTTP code to debug connection issues + RESPONSE=$(curl --write-out "%{http_code}" --silent --output /tmp/response.json \ + -H "Authorization: Token $DD_TOKEN" \ + "$DD_URL/api/v2/products/?name=$PRODUCT_NAME") + + # If response is not 200, print error + if [ "$RESPONSE" != "200" ]; then + echo "::error::Failed to query DefectDojo. HTTP Code: $RESPONSE" + cat /tmp/response.json + exit 1 + fi + + COUNT=$(cat /tmp/response.json | jq -r '.count') + + if [ "$COUNT" = "0" ]; then + echo "Creating product '$PRODUCT_NAME'..." + curl -s -X POST "$DD_URL/api/v2/products/" \ + -H "Authorization: Token $DD_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "name": "'"$PRODUCT_NAME"'", "description": "Auto-created by Gitea Actions", "prod_type": '$PRODUCT_TYPE_ID' }' + else + echo "Product '$PRODUCT_NAME' already exists." + fi + + # --- 1. TRIVY (Dependencies & Misconfig) --- + - name: Install Trivy + run: | + sudo apt-get install wget apt-transport-https gnupg lsb-release -y + wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - + echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list + sudo apt-get update && sudo apt-get install trivy -y + + - name: Run Trivy (FS Scan) + run: | + trivy fs . --scanners vuln,misconfig --format json --output trivy-results.json --exit-code 0 + + - name: Upload Trivy to DefectDojo + env: + DD_URL: ${{ secrets.DD_URL }} + DD_TOKEN: ${{ secrets.DD_TOKEN }} + run: | + echo "Uploading Trivy results..." + # Generate today's date in YYYY-MM-DD format + TODAY=$(date +%Y-%m-%d) + + HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \ + -H "Authorization: Token $DD_TOKEN" \ + -F "active=true" \ + -F "verified=true" \ + -F "scan_type=Trivy Scan" \ + -F "engagement_name=CI/CD Pipeline" \ + -F "product_name=${{ github.repository }}" \ + -F "scan_date=$TODAY" \ + -F "auto_create_context=true" \ + -F "file=@trivy-results.json") + + if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then + echo "::error::Upload Failed with HTTP $HTTP_CODE" + echo "--- SERVER RESPONSE ---" + cat response.txt + echo "-----------------------" + exit 1 + else + echo "Upload Success!" + fi + + # --- 2. GITLEAKS (Secrets) --- + - name: Install Gitleaks + run: | + wget -qO gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz + tar -xzf gitleaks.tar.gz + sudo mv gitleaks /usr/local/bin/ && chmod +x /usr/local/bin/gitleaks + + - name: Run Gitleaks + run: gitleaks detect --source . -v --report-path gitleaks-results.json --report-format json --no-git || true + + - name: Upload Gitleaks to DefectDojo + env: + DD_URL: ${{ secrets.DD_URL }} + DD_TOKEN: ${{ secrets.DD_TOKEN }} + run: | + echo "Uploading Gitleaks results..." + TODAY=$(date +%Y-%m-%d) + + HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \ + -H "Authorization: Token $DD_TOKEN" \ + -F "active=true" \ + -F "verified=true" \ + -F "scan_type=Gitleaks Scan" \ + -F "engagement_name=CI/CD Pipeline" \ + -F "product_name=${{ github.repository }}" \ + -F "scan_date=$TODAY" \ + -F "auto_create_context=true" \ + -F "file=@gitleaks-results.json") + + if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then + echo "::error::Upload Failed with HTTP $HTTP_CODE" + echo "--- SERVER RESPONSE ---" + cat response.txt + echo "-----------------------" + exit 1 + else + echo "Upload Success!" + fi + + # --- 3. SEMGREP (SAST) --- + - name: Install Semgrep (via pipx) + run: | + sudo apt-get install pipx -y + pipx install semgrep + # Add pipx binary path to GITHUB_PATH so next steps can see 'semgrep' + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Run Semgrep + run: semgrep scan --config=p/security-audit --config=p/owasp-top-ten --json --output semgrep-results.json . || true + + - name: Upload Semgrep to DefectDojo + env: + DD_URL: ${{ secrets.DD_URL }} + DD_TOKEN: ${{ secrets.DD_TOKEN }} + run: | + echo "Uploading Semgrep results..." + TODAY=$(date +%Y-%m-%d) + + HTTP_CODE=$(curl --write-out "%{http_code}" --output response.txt --silent -X POST "$DD_URL/api/v2/import-scan/" \ + -H "Authorization: Token $DD_TOKEN" \ + -F "active=true" \ + -F "verified=true" \ + -F "scan_type=Semgrep JSON Report" \ + -F "engagement_name=CI/CD Pipeline" \ + -F "product_name=${{ github.repository }}" \ + -F "scan_date=$TODAY" \ + -F "auto_create_context=true" \ + -F "file=@semgrep-results.json") + + if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then + echo "::error::Upload Failed with HTTP $HTTP_CODE" + echo "--- SERVER RESPONSE ---" + cat response.txt + echo "-----------------------" + exit 1 + else + echo "Upload Success!" + fi \ No newline at end of file