diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1d3b0b0..88fb7e6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,4 +2,4 @@ **What are your needs or what are you planning to do in this PR?** -> +> diff --git a/.github/workflows/gitleaks-analysis.yml b/.github/workflows/gitleaks-analysis.yml index f5f83de..ed349ae 100644 --- a/.github/workflows/gitleaks-analysis.yml +++ b/.github/workflows/gitleaks-analysis.yml @@ -3,6 +3,10 @@ name: gitleaks on: workflow_call: +env: + # Pinned gitleaks release. + GITLEAKS_VERSION: "8.30.1" + jobs: secret-scan: name: Run Gitleaks @@ -14,11 +18,143 @@ jobs: with: fetch-depth: 0 + - name: Install Gitleaks + run: | + ASSET="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + BASE_URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}" + + workdir="$(mktemp -d)" + cd "$workdir" + + curl -sSfL -o "${ASSET}" "${BASE_URL}/${ASSET}" + curl -sSfL -o checksums.txt "${BASE_URL}/gitleaks_${GITLEAKS_VERSION}_checksums.txt" + + # Verify integrity + grep " ${ASSET}\$" checksums.txt | sha256sum -c - + + mkdir -p "$HOME/.local/bin" + tar -xzf "${ASSET}" -C "$HOME/.local/bin" gitleaks + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + "$HOME/.local/bin/gitleaks" version + - name: Run secret scan - uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITLEAKS_LICENSE: "Centreon" - GITLEAKS_ENABLE_COMMENTS: false - GITLEAKS_ENABLE_UPLOAD_ARTIFACT: false - GITLEAKS_ENABLE_SUMMARY: false + EVENT_NAME: ${{ github.event_name }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PUSH_BEFORE: ${{ github.event.before }} + PUSH_AFTER: ${{ github.event.after }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -u + + # checkout pins the PR merge ref at trigger time, so a mid-run push can + # leave the head commit out of the clone. Resolving the range in a loop, + # re-fetching between attempts, and scan only once it is valid. + LOG_OPTS="" + HEAD_SHA="${PR_HEAD_SHA}" + + # Sets LOG_OPTS (empty => full scan); returns non-zero if the range is + # unresolvable. Validated with gitleaks' exact flags. + compute_range() { + case "${EVENT_NAME}" in + pull_request) + git cat-file -e "${HEAD_SHA}^{commit}" 2>/dev/null || return 1 + base="$(git merge-base "${PR_BASE_SHA}" "${HEAD_SHA}" 2>/dev/null)" || return 1 + LOG_OPTS="--no-merges --first-parent ${base}..${HEAD_SHA}" + ;; + push) + if [ -n "${PUSH_BEFORE}" ] \ + && [ "${PUSH_BEFORE}" != "0000000000000000000000000000000000000000" ] \ + && git cat-file -e "${PUSH_BEFORE}^{commit}" 2>/dev/null; then + # Existing branch: scan exactly the commits this push added. + LOG_OPTS="--no-merges ${PUSH_BEFORE}..${PUSH_AFTER}" + elif base="$(git merge-base "refs/remotes/origin/${DEFAULT_BRANCH}" "${PUSH_AFTER}" 2>/dev/null)" \ + && [ -n "${base}" ]; then + # New branch (before is the zero SHA or empty) or rewritten + # history: scan only the commits unique to this branch, i.e. + # since it diverged from the default branch. The shared base + # history is already covered by the default branch's own scans, + # so a full scan there would just re-report pre-existing, + # unrelated findings. (fetch-depth: 0 makes origin/ + # available in the clone.) + LOG_OPTS="--no-merges ${base}..${PUSH_AFTER}" + else + # Default branch not resolvable (unrelated history, or the ref + # is missing): fall back to a full scan rather than risk + # missing anything. Over-scan is the safe direction. + LOG_OPTS="" + fi + ;; + *) + # workflow_dispatch, etc: full scan. + LOG_OPTS="" + ;; + esac + + if [ -n "${LOG_OPTS}" ]; then + git log ${LOG_OPTS} >/dev/null 2>&1 || return 1 + fi + return 0 + } + + # Fetch the refs the range needs (actions/checkout can't be re-run in a + # loop). Falls back to the live PR tip if the head SHA was force-pushed away. + refetch() { + if [ "${EVENT_NAME}" = "pull_request" ] && [ -n "${PR_NUMBER}" ]; then + git fetch --no-tags --force origin \ + "+refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr/${PR_NUMBER}" || true + if ! git cat-file -e "${HEAD_SHA}^{commit}" 2>/dev/null; then + HEAD_SHA="$(git rev-parse "refs/remotes/origin/pr/${PR_NUMBER}" 2>/dev/null || echo "${HEAD_SHA}")" + fi + else + git fetch --no-tags --force origin "+refs/heads/*:refs/remotes/origin/*" || true + fi + } + + attempt=1 + max_attempts=5 + while : ; do + if compute_range; then + echo "::notice::Commit range resolved on attempt ${attempt}: ${LOG_OPTS:-}" + break + fi + + if [ "${attempt}" -ge "${max_attempts}" ]; then + echo "::error::Could not resolve a valid commit range after ${max_attempts} attempts. Skipping" + echo "::error::The scan target is missing from the checkout (likely a force-push race); re-run this job. Skipping" + exit 1 + fi + + echo "Commit range not resolvable yet; re-fetching refs (attempt ${attempt})." + refetch + attempt=$((attempt + 1)) + done + + EXIT_CODE=0 + if [ -n "${LOG_OPTS}" ]; then + gitleaks detect \ + --source . \ + --log-opts="${LOG_OPTS}" \ + --exit-code=2 \ + --redact \ + --verbose || EXIT_CODE=$? + else + gitleaks detect \ + --source . \ + --exit-code=2 \ + --redact \ + --verbose || EXIT_CODE=$? + fi + + if [ "${EXIT_CODE}" -eq 0 ]; then + echo "No secrets found." + elif [ "${EXIT_CODE}" -eq 2 ]; then + echo "::error::Gitleaks has detected secrets in this branch!" + exit 1 + else + echo "::error::Gitleaks encountered an unexpected error (exit code: ${EXIT_CODE})." + exit 0 + fi