#!/usr/bin/env bash set -euo pipefail # ============================================================================= # phase9_security.sh — Deploy security scanning workflows to all repos # Depends on: Phase 5 complete (repos have .gitea/workflows/) # For each repo: # 1. Clone from Gitea to temp directory # 2. Render security-scan.yml.tpl → .gitea/workflows/security-scan.yml # 3. Commit and push # 4. If SECURITY_FAIL_ON_ERROR=true, update branch protection to require # security status checks (semgrep, trivy, gitleaks) # The security workflow runs Semgrep (SAST), Trivy (container/dependency scan), # and Gitleaks (secret detection) on every pull request. # Idempotent: skips repos that already have security-scan.yml. # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "${SCRIPT_DIR}/lib/common.sh" load_env require_vars GITEA_ADMIN_TOKEN GITEA_INTERNAL_URL GITEA_ORG_NAME \ GITEA_ADMIN_USER \ REPO_1_NAME REPO_2_NAME REPO_3_NAME \ SEMGREP_VERSION TRIVY_VERSION GITLEAKS_VERSION \ SECURITY_FAIL_ON_ERROR PROTECTED_BRANCH phase_header 9 "Security Scanning" REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") TEMP_BASE="/tmp/gitea-migration-security" GITEA_BASE_URL="${GITEA_INTERNAL_URL%/}" ASKPASS_SCRIPT="" # Clean up cloned repos and ephemeral auth script on exit (even on failure). cleanup() { rm -rf "$TEMP_BASE" if [[ -n "$ASKPASS_SCRIPT" ]]; then rm -f "$ASKPASS_SCRIPT" fi } trap cleanup EXIT # Create an ephemeral GIT_ASKPASS script so tokens are never embedded in # git remote URLs. Git calls this script with "Username for ..." or # "Password for ..." and the script responds from environment variables. setup_git_auth() { ASKPASS_SCRIPT=$(mktemp) cat > "$ASKPASS_SCRIPT" <<'EOF' #!/usr/bin/env sh case "$1" in *sername*) printf '%s\n' "$GITEA_GIT_USERNAME" ;; *assword*) printf '%s\n' "$GITEA_GIT_TOKEN" ;; *) printf '\n' ;; esac EOF chmod 700 "$ASKPASS_SCRIPT" } git_with_auth() { GIT_TERMINAL_PROMPT=0 \ GIT_ASKPASS="$ASKPASS_SCRIPT" \ GITEA_GIT_USERNAME="$GITEA_ADMIN_USER" \ GITEA_GIT_TOKEN="$GITEA_ADMIN_TOKEN" \ "$@" } SUCCESS=0 SKIPPED=0 FAILED=0 setup_git_auth for repo in "${REPOS[@]}"; do log_info "--- Processing repo: ${repo} ---" # ------------------------------------------------------------------------- # Idempotency: check if security-scan.yml already exists via API # ------------------------------------------------------------------------- if gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows/security-scan.yml" >/dev/null 2>&1; then log_info "security-scan.yml already exists in ${repo} — skipping" SKIPPED=$((SKIPPED + 1)) continue fi # ------------------------------------------------------------------------- # Step 1: Clone the repo # ------------------------------------------------------------------------- CLONE_DIR="${TEMP_BASE}/${repo}" rm -rf "$CLONE_DIR" mkdir -p "$CLONE_DIR" REPO_URL="${GITEA_BASE_URL}/${GITEA_ORG_NAME}/${repo}.git" log_info "Cloning ${repo}..." git_with_auth git clone -q "$REPO_URL" "$CLONE_DIR" # ------------------------------------------------------------------------- # Step 2: Render security workflow template # The template references tool versions from .env so they're pinned # and reproducible across all repos. # ------------------------------------------------------------------------- log_info "Rendering security-scan.yml..." mkdir -p "${CLONE_DIR}/.gitea/workflows" export SEMGREP_VERSION TRIVY_VERSION GITLEAKS_VERSION PROTECTED_BRANCH render_template "${SCRIPT_DIR}/templates/workflows/security-scan.yml.tpl" \ "${CLONE_DIR}/.gitea/workflows/security-scan.yml" \ "\${PROTECTED_BRANCH} \${SEMGREP_VERSION} \${TRIVY_VERSION} \${GITLEAKS_VERSION}" # ------------------------------------------------------------------------- # Step 3: Commit and push # ------------------------------------------------------------------------- cd "$CLONE_DIR" git config user.name "Gitea Migration" git config user.email "migration@gitea.local" git add .gitea/workflows/security-scan.yml git commit -q -m "Add security scanning workflow (Semgrep + Trivy + Gitleaks)" git_with_auth git push -q origin HEAD cd "$SCRIPT_DIR" log_success "Security workflow deployed to ${repo}" # ------------------------------------------------------------------------- # Step 4: Update branch protection to require security status checks # Only if SECURITY_FAIL_ON_ERROR=true — this means PRs cannot be merged # unless all three security scans pass. # ------------------------------------------------------------------------- if [[ "$SECURITY_FAIL_ON_ERROR" == "true" ]]; then log_info "Updating branch protection to require security checks..." # PATCH the existing branch protection to add status check contexts PATCH_PAYLOAD=$(jq -n '{ enable_status_check: true, status_check_contexts: ["semgrep", "trivy", "gitleaks"] }') if gitea_api PATCH "/repos/${GITEA_ORG_NAME}/${repo}/branch_protections/${PROTECTED_BRANCH}" "$PATCH_PAYLOAD" >/dev/null 2>&1; then log_success "Branch protection updated with security status checks" else log_warn "Could not update branch protection — may need to be set manually" fi fi SUCCESS=$((SUCCESS + 1)) done # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- printf '\n' TOTAL=${#REPOS[@]} log_info "Results: ${SUCCESS} deployed, ${SKIPPED} skipped, ${FAILED} failed (out of ${TOTAL})" if [[ $FAILED -gt 0 ]]; then log_error "Some repos failed — check logs above" exit 1 fi log_success "Phase 9 complete — security scanning deployed"