From ea5524949abb6615a9c1765d38eab6ec9ae5fdfc Mon Sep 17 00:00:00 2001 From: S Date: Thu, 26 Feb 2026 15:30:38 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20add=20Phase=209=20=E2=80=94=20Security?= =?UTF-8?q?=20Scanning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- phase9_post_check.sh | 68 +++++++++++++++++++++++ phase9_security.sh | 125 +++++++++++++++++++++++++++++++++++++++++++ phase9_teardown.sh | 71 ++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100755 phase9_post_check.sh create mode 100755 phase9_security.sh create mode 100755 phase9_teardown.sh diff --git a/phase9_post_check.sh b/phase9_post_check.sh new file mode 100755 index 0000000..387c0aa --- /dev/null +++ b/phase9_post_check.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# phase9_post_check.sh — Verify Phase 9 (Security Scanning) succeeded +# Checks for each repo: +# 1. security-scan.yml exists in .gitea/workflows/ +# 2. If SECURITY_FAIL_ON_ERROR=true, branch protection includes security checks +# Exits 0 only if ALL checks pass. +# ============================================================================= + +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 \ + REPO_1_NAME REPO_2_NAME REPO_3_NAME \ + SECURITY_FAIL_ON_ERROR PROTECTED_BRANCH + +log_info "=== Phase 9 Post-Check ===" + +REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") +PASS=0 +FAIL=0 + +run_check() { + local description="$1"; shift + if "$@" 2>/dev/null; then + log_success "$description" + PASS=$((PASS + 1)) + else + log_error "FAIL: $description" + FAIL=$((FAIL + 1)) + fi +} + +for repo in "${REPOS[@]}"; do + log_info "--- Checking repo: ${repo} ---" + + # Check 1: security-scan.yml exists + run_check "security-scan.yml exists in ${repo}" \ + gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows/security-scan.yml" -o /dev/null + + # Check 2: Branch protection includes security checks (if required) + if [[ "$SECURITY_FAIL_ON_ERROR" == "true" ]]; then + check_status_checks() { + local protection + protection=$(gitea_api GET "/repos/${GITEA_ORG_NAME}/$1/branch_protections/${PROTECTED_BRANCH}") + local contexts + contexts=$(printf '%s' "$protection" | jq -r '.status_check_contexts // [] | join(",")') + # Verify all three security checks are present + [[ "$contexts" == *"semgrep"* ]] && [[ "$contexts" == *"trivy"* ]] && [[ "$contexts" == *"gitleaks"* ]] + } + run_check "Branch protection includes security checks for ${repo}" check_status_checks "$repo" + fi +done + +# Summary +printf '\n' +log_info "Results: ${PASS} passed, ${FAIL} failed" + +if [[ $FAIL -gt 0 ]]; then + log_error "Phase 9 post-check FAILED" + exit 1 +else + log_success "Phase 9 post-check PASSED — security scanning active" + exit 0 +fi diff --git a/phase9_security.sh b/phase9_security.sh new file mode 100755 index 0000000..96f2720 --- /dev/null +++ b/phase9_security.sh @@ -0,0 +1,125 @@ +#!/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" + +cleanup() { + rm -rf "$TEMP_BASE" +} +trap cleanup EXIT + +SUCCESS=0 +SKIPPED=0 +FAILED=0 + +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" + + CLONE_URL=$(echo "${GITEA_INTERNAL_URL}" | sed "s|://|://${GITEA_ADMIN_USER}:${GITEA_ADMIN_TOKEN}@|") + log_info "Cloning ${repo}..." + git clone -q "${CLONE_URL}/${GITEA_ORG_NAME}/${repo}.git" "$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" + + # ------------------------------------------------------------------------- + # 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 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" diff --git a/phase9_teardown.sh b/phase9_teardown.sh new file mode 100755 index 0000000..d350cb0 --- /dev/null +++ b/phase9_teardown.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# phase9_teardown.sh — Remove security scanning workflows from all repos +# Also removes security status checks from branch protection if they were added. +# Clones each repo, removes security-scan.yml, commits + pushes. +# ============================================================================= + +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 \ + PROTECTED_BRANCH + +log_warn "=== Phase 9 Teardown: Security Scanning ===" + +REPOS=("$REPO_1_NAME" "$REPO_2_NAME" "$REPO_3_NAME") +TEMP_BASE="/tmp/gitea-migration-security-teardown" + +cleanup() { + rm -rf "$TEMP_BASE" +} +trap cleanup EXIT + +printf 'This will remove security-scan.yml from all repos. Continue? [y/N] ' +read -r confirm +if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + log_info "Teardown cancelled" + exit 0 +fi + +for repo in "${REPOS[@]}"; do + log_info "--- Processing: ${repo} ---" + + # Check if security-scan.yml exists before cloning + if ! gitea_api GET "/repos/${GITEA_ORG_NAME}/${repo}/contents/.gitea/workflows/security-scan.yml" >/dev/null 2>&1; then + log_info "No security-scan.yml in ${repo} — already clean" + continue + fi + + # Clone, remove, commit, push + CLONE_DIR="${TEMP_BASE}/${repo}" + rm -rf "$CLONE_DIR" + + CLONE_URL=$(echo "${GITEA_INTERNAL_URL}" | sed "s|://|://${GITEA_ADMIN_USER}:${GITEA_ADMIN_TOKEN}@|") + git clone -q "${CLONE_URL}/${GITEA_ORG_NAME}/${repo}.git" "$CLONE_DIR" + + if [[ -f "${CLONE_DIR}/.gitea/workflows/security-scan.yml" ]]; then + rm -f "${CLONE_DIR}/.gitea/workflows/security-scan.yml" + cd "$CLONE_DIR" + git config user.name "Gitea Migration" + git config user.email "migration@gitea.local" + git add -A + git commit -q -m "Remove security scanning workflow (teardown)" + git push -q origin HEAD + cd "$SCRIPT_DIR" + log_success "Removed security-scan.yml from ${repo}" + fi + + # Remove security status checks from branch protection + # Reset to empty status check contexts + PATCH_PAYLOAD=$(jq -n '{status_check_contexts: []}') + gitea_api PATCH "/repos/${GITEA_ORG_NAME}/${repo}/branch_protections/${PROTECTED_BRANCH}" "$PATCH_PAYLOAD" >/dev/null 2>&1 || true + log_info "Cleared security status checks from branch protection for ${repo}" +done + +log_success "Phase 9 teardown complete"