Skip to content
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

**What are your needs or what are you planning to do in this PR?**

>
>
148 changes: 142 additions & 6 deletions .github/workflows/gitleaks-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ name: gitleaks
on:
workflow_call:

env:
# Pinned gitleaks release.
GITLEAKS_VERSION: "8.30.1"

jobs:
secret-scan:
name: Run Gitleaks
Expand All @@ -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/<default>
# 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:-<full history>}"
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