Merge pull request 'fix: fix: stop baking credentials into git remote URLs — use clean URLs + existing credential helper everywhere (#604)' (#633) from fix/issue-604 into main
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
commit
10be72f5ce
10 changed files with 336 additions and 72 deletions
|
|
@ -42,42 +42,20 @@ init_state_dir() {
|
|||
log "Initialized state directory"
|
||||
}
|
||||
|
||||
# Configure git credential helper for password-based HTTP auth.
|
||||
# Forgejo 11.x rejects API tokens for git push (#361); password auth works.
|
||||
# This ensures all git operations (clone, fetch, push) from worktrees use
|
||||
# password auth without needing tokens embedded in remote URLs.
|
||||
configure_git_creds() {
|
||||
# Source shared git credential helper library (#604).
|
||||
# shellcheck source=lib/git-creds.sh
|
||||
source "${DISINTO_BAKED}/lib/git-creds.sh"
|
||||
|
||||
# Wrapper that calls the shared configure_git_creds with agent-specific paths,
|
||||
# then repairs any legacy baked-credential URLs in existing clones.
|
||||
_setup_git_creds() {
|
||||
configure_git_creds "/home/agent" "gosu agent"
|
||||
if [ -n "${FORGE_PASS:-}" ] && [ -n "${FORGE_URL:-}" ]; then
|
||||
_forge_host=$(printf '%s' "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
|
||||
_forge_proto=$(printf '%s' "$FORGE_URL" | sed 's|://.*||')
|
||||
# Determine the bot username from FORGE_TOKEN identity (or default to dev-bot)
|
||||
_bot_user=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
||||
"${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || _bot_user=""
|
||||
_bot_user="${_bot_user:-dev-bot}"
|
||||
|
||||
# Write a static credential helper script (git credential protocol)
|
||||
cat > /home/agent/.git-credentials-helper <<CREDEOF
|
||||
#!/bin/sh
|
||||
# Auto-generated git credential helper for Forgejo password auth (#361)
|
||||
# Only respond to "get" action; ignore "store" and "erase".
|
||||
[ "\$1" = "get" ] || exit 0
|
||||
# Read and discard stdin (git sends protocol/host info)
|
||||
cat >/dev/null
|
||||
echo "protocol=${_forge_proto}"
|
||||
echo "host=${_forge_host}"
|
||||
echo "username=${_bot_user}"
|
||||
echo "password=${FORGE_PASS}"
|
||||
CREDEOF
|
||||
chmod 755 /home/agent/.git-credentials-helper
|
||||
chown agent:agent /home/agent/.git-credentials-helper
|
||||
|
||||
gosu agent bash -c "git config --global credential.helper '/home/agent/.git-credentials-helper'"
|
||||
log "Git credential helper configured for ${_bot_user}@${_forge_host} (password auth)"
|
||||
log "Git credential helper configured (password auth)"
|
||||
fi
|
||||
|
||||
# Set safe.directory to work around dubious ownership after container restart
|
||||
# (https://github.com/disinto-admin/disinto/issues/517)
|
||||
gosu agent bash -c "git config --global --add safe.directory '*'"
|
||||
# Repair legacy clones with baked-in stale credentials (#604).
|
||||
_GIT_CREDS_LOG_FN=log repair_baked_cred_urls /home/agent/repos
|
||||
}
|
||||
|
||||
# Configure tea CLI login for forge operations (runs as agent user).
|
||||
|
|
@ -272,7 +250,7 @@ pull_factory_repo() {
|
|||
}
|
||||
|
||||
# Configure git and tea once at startup (as root, then drop to agent)
|
||||
configure_git_creds
|
||||
_setup_git_creds
|
||||
configure_tea_login
|
||||
|
||||
# Bootstrap ops repos from forgejo into container volumes (#586)
|
||||
|
|
|
|||
|
|
@ -45,17 +45,48 @@ if [ -d /opt/disinto ] && [ ! -d /opt/disinto/.git ] && [ -n "$(ls -A /opt/disin
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Shallow clone at the pinned version (inject token to support auth-required Forgejo)
|
||||
# Set HOME early so credential helper and git config land in the right place.
|
||||
export HOME=/home/agent
|
||||
mkdir -p "$HOME"
|
||||
|
||||
# Configure git credential helper before cloning (#604).
|
||||
# /opt/disinto does not exist yet so we cannot source lib/git-creds.sh;
|
||||
# inline a minimal credential-helper setup here.
|
||||
if [ -n "${FORGE_PASS:-}" ] && [ -n "${FORGE_URL:-}" ]; then
|
||||
_forge_host=$(printf '%s' "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
|
||||
_forge_proto=$(printf '%s' "$FORGE_URL" | sed 's|://.*||')
|
||||
_bot_user=""
|
||||
if [ -n "${FORGE_TOKEN:-}" ]; then
|
||||
_bot_user=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
||||
"${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || _bot_user=""
|
||||
fi
|
||||
_bot_user="${_bot_user:-dev-bot}"
|
||||
|
||||
cat > "${HOME}/.git-credentials-helper" <<CREDEOF
|
||||
#!/bin/sh
|
||||
[ "\$1" = "get" ] || exit 0
|
||||
cat >/dev/null
|
||||
echo "protocol=${_forge_proto}"
|
||||
echo "host=${_forge_host}"
|
||||
echo "username=${_bot_user}"
|
||||
echo "password=${FORGE_PASS}"
|
||||
CREDEOF
|
||||
chmod 755 "${HOME}/.git-credentials-helper"
|
||||
git config --global credential.helper "${HOME}/.git-credentials-helper"
|
||||
git config --global --add safe.directory '*'
|
||||
fi
|
||||
|
||||
# Shallow clone at the pinned version — use clean URL, credential helper
|
||||
# supplies auth (#604).
|
||||
if [ ! -d /opt/disinto/.git ]; then
|
||||
_auth_url=$(printf '%s' "$FORGE_URL" | sed "s|://|://token:${FORGE_TOKEN}@|")
|
||||
echo "edge: cloning ${FORGE_URL}/${FORGE_REPO} (branch ${DISINTO_VERSION:-main})..." >&2
|
||||
if ! git clone --depth 1 --branch "${DISINTO_VERSION:-main}" "${_auth_url}/${FORGE_REPO}.git" /opt/disinto; then
|
||||
if ! git clone --depth 1 --branch "${DISINTO_VERSION:-main}" "${FORGE_URL}/${FORGE_REPO}.git" /opt/disinto; then
|
||||
echo >&2
|
||||
echo "FATAL: failed to clone ${FORGE_URL}/${FORGE_REPO}.git (branch ${DISINTO_VERSION:-main})" >&2
|
||||
echo "Likely causes:" >&2
|
||||
echo " - Forgejo at ${FORGE_URL} is unreachable from the edge container" >&2
|
||||
echo " - Repository '${FORGE_REPO}' does not exist on this forge" >&2
|
||||
echo " - FORGE_TOKEN is invalid or has no read access to '${FORGE_REPO}'" >&2
|
||||
echo " - FORGE_TOKEN/FORGE_PASS is invalid or has no read access to '${FORGE_REPO}'" >&2
|
||||
echo " - Branch '${DISINTO_VERSION:-main}' does not exist in '${FORGE_REPO}'" >&2
|
||||
echo "Workaround: bind-mount a local git checkout into /opt/disinto." >&2
|
||||
echo "Sleeping 60s before exit to throttle the restart loop..." >&2
|
||||
|
|
@ -64,11 +95,13 @@ if [ ! -d /opt/disinto/.git ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# Set HOME so that claude OAuth credentials and session.lock are found at the
|
||||
# same in-container path as in disinto-agents (/home/agent/.claude), which makes
|
||||
# flock cross-serialize across containers on the same host inode.
|
||||
export HOME=/home/agent
|
||||
mkdir -p "$HOME"
|
||||
# Repair any legacy baked-credential URLs in /opt/disinto (#604).
|
||||
# Now that /opt/disinto exists, source the shared lib.
|
||||
if [ -f /opt/disinto/lib/git-creds.sh ]; then
|
||||
# shellcheck source=/opt/disinto/lib/git-creds.sh
|
||||
source /opt/disinto/lib/git-creds.sh
|
||||
_GIT_CREDS_LOG_FN="echo" repair_baked_cred_urls /opt/disinto
|
||||
fi
|
||||
|
||||
# Ensure log directory exists
|
||||
mkdir -p /opt/disinto-logs
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ export DISINTO_CONTAINER=1
|
|||
export HOME="${HOME:-/home/agent}"
|
||||
export USER="${USER:-agent}"
|
||||
|
||||
# Configure git credential helper so reproduce/triage agents can clone/push
|
||||
# without needing tokens embedded in remote URLs (#604).
|
||||
if [ -f "${DISINTO_DIR}/lib/git-creds.sh" ]; then
|
||||
# shellcheck source=lib/git-creds.sh
|
||||
source "${DISINTO_DIR}/lib/git-creds.sh"
|
||||
# shellcheck disable=SC2119 # no args intended — uses defaults
|
||||
configure_git_creds
|
||||
fi
|
||||
|
||||
FORGE_API="${FORGE_URL}/api/v1/repos/${FORGE_REPO}"
|
||||
|
||||
# Load project name from TOML
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ log() {
|
|||
printf '[%s] runner: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"
|
||||
}
|
||||
|
||||
# Configure git credential helper so formulas can clone/push without
|
||||
# needing tokens embedded in remote URLs (#604).
|
||||
if [ -f "${FACTORY_ROOT}/lib/git-creds.sh" ]; then
|
||||
# shellcheck source=lib/git-creds.sh
|
||||
source "${FACTORY_ROOT}/lib/git-creds.sh"
|
||||
# shellcheck disable=SC2119 # no args intended — uses defaults
|
||||
configure_git_creds
|
||||
fi
|
||||
|
||||
# ── Argument parsing ─────────────────────────────────────────────────────
|
||||
|
||||
action_id="${1:-}"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
# Globals expected:
|
||||
# FORGE_URL - Forge instance URL (e.g. http://localhost:3000)
|
||||
# FORGE_TOKEN - API token for Forge operations (used for API verification)
|
||||
# FORGE_PASS - Bot password for git HTTP push (#361: tokens rejected by Forgejo 11.x)
|
||||
# FACTORY_ROOT - Root of the disinto factory
|
||||
# PRIMARY_BRANCH - Primary branch name (e.g. main)
|
||||
#
|
||||
|
|
@ -21,7 +20,6 @@ set -euo pipefail
|
|||
_assert_forge_push_globals() {
|
||||
local missing=()
|
||||
[ -z "${FORGE_URL:-}" ] && missing+=("FORGE_URL")
|
||||
[ -z "${FORGE_PASS:-}" ] && missing+=("FORGE_PASS")
|
||||
[ -z "${FORGE_TOKEN:-}" ] && missing+=("FORGE_TOKEN")
|
||||
[ -z "${FACTORY_ROOT:-}" ] && missing+=("FACTORY_ROOT")
|
||||
[ -z "${PRIMARY_BRANCH:-}" ] && missing+=("PRIMARY_BRANCH")
|
||||
|
|
@ -35,17 +33,11 @@ _assert_forge_push_globals() {
|
|||
push_to_forge() {
|
||||
local repo_root="$1" forge_url="$2" repo_slug="$3"
|
||||
|
||||
# Build authenticated remote URL: http://dev-bot:<password>@host:port/org/repo.git
|
||||
# Forgejo 11.x rejects API tokens for git HTTP push (#361); password auth works.
|
||||
if [ -z "${FORGE_PASS:-}" ]; then
|
||||
echo "Error: FORGE_PASS not set — cannot push to Forgejo (see #361)" >&2
|
||||
return 1
|
||||
fi
|
||||
local auth_url
|
||||
auth_url=$(printf '%s' "$forge_url" | sed "s|://|://dev-bot:${FORGE_PASS}@|")
|
||||
local remote_url="${auth_url}/${repo_slug}.git"
|
||||
# Display URL without token
|
||||
local display_url="${forge_url}/${repo_slug}.git"
|
||||
# Use clean URL — credential helper supplies auth (#604).
|
||||
# Forgejo 11.x rejects API tokens for git HTTP push (#361); password auth works
|
||||
# via the credential helper configured in configure_git_creds().
|
||||
local remote_url="${forge_url}/${repo_slug}.git"
|
||||
local display_url="$remote_url"
|
||||
|
||||
# Always set the remote URL to ensure credentials are current
|
||||
if git -C "$repo_root" remote get-url forgejo >/dev/null 2>&1; then
|
||||
|
|
|
|||
|
|
@ -113,11 +113,9 @@ ensure_profile_repo() {
|
|||
# Define cache directory: /home/agent/data/.profile/{agent-name}
|
||||
PROFILE_REPO_PATH="${HOME:-/home/agent}/data/.profile/${agent_identity}"
|
||||
|
||||
# Build clone URL from FORGE_URL and agent identity
|
||||
# Build clone URL from FORGE_URL — credential helper supplies auth (#604)
|
||||
local forge_url="${FORGE_URL:-http://localhost:3000}"
|
||||
local auth_url
|
||||
auth_url=$(printf '%s' "$forge_url" | sed "s|://|://$(whoami):${FORGE_TOKEN}@|")
|
||||
local clone_url="${auth_url}/${agent_identity}/.profile.git"
|
||||
local clone_url="${forge_url}/${agent_identity}/.profile.git"
|
||||
|
||||
# Check if already cached and up-to-date
|
||||
if [ -d "${PROFILE_REPO_PATH}/.git" ]; then
|
||||
|
|
@ -592,14 +590,8 @@ ensure_ops_repo() {
|
|||
local ops_repo="${FORGE_OPS_REPO:-}"
|
||||
[ -n "$ops_repo" ] || return 0
|
||||
local forge_url="${FORGE_URL:-http://localhost:3000}"
|
||||
local clone_url
|
||||
if [ -n "${FORGE_TOKEN:-}" ]; then
|
||||
local auth_url
|
||||
auth_url=$(printf '%s' "$forge_url" | sed "s|://|://$(whoami):${FORGE_TOKEN}@|")
|
||||
clone_url="${auth_url}/${ops_repo}.git"
|
||||
else
|
||||
clone_url="${forge_url}/${ops_repo}.git"
|
||||
fi
|
||||
# Use clean URL — credential helper supplies auth (#604)
|
||||
local clone_url="${forge_url}/${ops_repo}.git"
|
||||
|
||||
log "Cloning ops repo: ${ops_repo} -> ${ops_root}"
|
||||
if git clone --quiet "$clone_url" "$ops_root" 2>/dev/null; then
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ services:
|
|||
- FORGE_REPO=${FORGE_REPO:-disinto-admin/disinto}
|
||||
- FORGE_OPS_REPO=${FORGE_OPS_REPO:-disinto-admin/disinto-ops}
|
||||
- FORGE_TOKEN=${FORGE_TOKEN:-}
|
||||
- FORGE_PASS=${FORGE_PASS:-}
|
||||
- FORGE_ADMIN_USERS=${FORGE_ADMIN_USERS:-disinto-admin}
|
||||
- FORGE_ADMIN_TOKEN=${FORGE_ADMIN_TOKEN:-}
|
||||
- OPS_REPO_ROOT=/opt/disinto-ops
|
||||
|
|
|
|||
120
lib/git-creds.sh
Normal file
120
lib/git-creds.sh
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env bash
|
||||
# git-creds.sh — Shared git credential helper configuration
|
||||
#
|
||||
# Configures a static credential helper for Forgejo password-based HTTP auth.
|
||||
# Forgejo 11.x rejects API tokens for git push (#361); password auth works.
|
||||
# This ensures all git operations (clone, fetch, push) use password auth
|
||||
# without needing tokens embedded in remote URLs (#604).
|
||||
#
|
||||
# Usage:
|
||||
# source "${FACTORY_ROOT}/lib/git-creds.sh"
|
||||
# configure_git_creds [HOME_DIR] [RUN_AS_CMD]
|
||||
# repair_baked_cred_urls DIR [DIR ...]
|
||||
#
|
||||
# Globals expected:
|
||||
# FORGE_PASS — bot password for git HTTP auth
|
||||
# FORGE_URL — Forge instance URL (e.g. http://forgejo:3000)
|
||||
# FORGE_TOKEN — API token (used to resolve bot username)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# configure_git_creds [HOME_DIR] [RUN_AS_CMD]
|
||||
# HOME_DIR — home directory for the git user (default: $HOME or /home/agent)
|
||||
# RUN_AS_CMD — command prefix to run as another user (e.g. "gosu agent")
|
||||
#
|
||||
# Writes a credential helper script and configures git to use it globally.
|
||||
configure_git_creds() {
|
||||
local home_dir="${1:-${HOME:-/home/agent}}"
|
||||
local run_as="${2:-}"
|
||||
|
||||
if [ -z "${FORGE_PASS:-}" ] || [ -z "${FORGE_URL:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local forge_host forge_proto
|
||||
forge_host=$(printf '%s' "$FORGE_URL" | sed 's|https\?://||; s|/.*||')
|
||||
forge_proto=$(printf '%s' "$FORGE_URL" | sed 's|://.*||')
|
||||
|
||||
# Determine the bot username from FORGE_TOKEN identity (or default to dev-bot)
|
||||
local bot_user=""
|
||||
if [ -n "${FORGE_TOKEN:-}" ]; then
|
||||
bot_user=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
|
||||
"${FORGE_URL}/api/v1/user" 2>/dev/null | jq -r '.login // empty') || bot_user=""
|
||||
fi
|
||||
bot_user="${bot_user:-dev-bot}"
|
||||
|
||||
local helper_path="${home_dir}/.git-credentials-helper"
|
||||
|
||||
# Write a static credential helper script (git credential protocol)
|
||||
cat > "$helper_path" <<CREDEOF
|
||||
#!/bin/sh
|
||||
# Auto-generated git credential helper for Forgejo password auth (#361, #604)
|
||||
# Only respond to "get" action; ignore "store" and "erase".
|
||||
[ "\$1" = "get" ] || exit 0
|
||||
# Read and discard stdin (git sends protocol/host info)
|
||||
cat >/dev/null
|
||||
echo "protocol=${forge_proto}"
|
||||
echo "host=${forge_host}"
|
||||
echo "username=${bot_user}"
|
||||
echo "password=${FORGE_PASS}"
|
||||
CREDEOF
|
||||
chmod 755 "$helper_path"
|
||||
|
||||
# Set ownership and configure git if running as a different user
|
||||
if [ -n "$run_as" ]; then
|
||||
local target_user
|
||||
target_user=$(echo "$run_as" | awk '{print $NF}')
|
||||
chown "${target_user}:${target_user}" "$helper_path" 2>/dev/null || true
|
||||
$run_as bash -c "git config --global credential.helper '${helper_path}'"
|
||||
else
|
||||
git config --global credential.helper "$helper_path"
|
||||
fi
|
||||
|
||||
# Set safe.directory to work around dubious ownership after container restart
|
||||
if [ -n "$run_as" ]; then
|
||||
$run_as bash -c "git config --global --add safe.directory '*'"
|
||||
else
|
||||
git config --global --add safe.directory '*'
|
||||
fi
|
||||
}
|
||||
|
||||
# repair_baked_cred_urls DIR [DIR ...]
|
||||
# Scans git repos under each DIR and rewrites remote URLs that contain
|
||||
# embedded credentials (user:pass@host) to clean URLs.
|
||||
# Logs each repair so operators can see the migration happened.
|
||||
#
|
||||
# Set _GIT_CREDS_LOG_FN to a custom log function name (default: echo).
|
||||
repair_baked_cred_urls() {
|
||||
local log_fn="${_GIT_CREDS_LOG_FN:-echo}"
|
||||
|
||||
for dir in "$@"; do
|
||||
[ -d "$dir" ] || continue
|
||||
|
||||
# Find git repos: either dir itself or immediate subdirectories
|
||||
local -a repos=()
|
||||
if [ -d "${dir}/.git" ]; then
|
||||
repos+=("$dir")
|
||||
else
|
||||
local sub
|
||||
for sub in "$dir"/*/; do
|
||||
[ -d "${sub}.git" ] && repos+=("${sub%/}")
|
||||
done
|
||||
fi
|
||||
|
||||
local repo
|
||||
for repo in "${repos[@]}"; do
|
||||
local url
|
||||
url=$(git -C "$repo" config --get remote.origin.url 2>/dev/null || true)
|
||||
[ -n "$url" ] || continue
|
||||
|
||||
# Check if URL contains embedded credentials: http(s)://user:pass@host
|
||||
if printf '%s' "$url" | grep -qE '^https?://[^/]+@'; then
|
||||
# Strip credentials: http(s)://user:pass@host/path -> http(s)://host/path
|
||||
local clean_url
|
||||
clean_url=$(printf '%s' "$url" | sed -E 's|(https?://)[^@]+@|\1|')
|
||||
git -C "$repo" remote set-url origin "$clean_url"
|
||||
$log_fn "Repaired baked credentials in ${repo} (remote origin -> ${clean_url})"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
|
@ -153,11 +153,10 @@ setup_ops_repo() {
|
|||
echo " ! disinto-admin = admin (already set or failed)"
|
||||
fi
|
||||
|
||||
# Clone ops repo locally if not present
|
||||
# Clone ops repo locally if not present — use clean URL, credential helper
|
||||
# supplies auth (#604).
|
||||
if [ ! -d "${ops_root}/.git" ]; then
|
||||
local auth_url
|
||||
auth_url=$(printf '%s' "$forge_url" | sed "s|://|://dev-bot:${FORGE_TOKEN}@|")
|
||||
local clone_url="${auth_url}/${actual_ops_slug}.git"
|
||||
local clone_url="${forge_url}/${actual_ops_slug}.git"
|
||||
echo "Cloning: ops repo -> ${ops_root}"
|
||||
if git clone --quiet "$clone_url" "$ops_root" 2>/dev/null; then
|
||||
echo "Ops repo: ${actual_ops_slug} cloned successfully"
|
||||
|
|
|
|||
131
tests/smoke-credentials.sh
Normal file
131
tests/smoke-credentials.sh
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env bash
|
||||
# tests/smoke-credentials.sh — Verify no git remote URL contains embedded credentials
|
||||
#
|
||||
# Scans all shell scripts that construct git URLs and verifies:
|
||||
# 1. No source file embeds credentials in remote URLs (static check)
|
||||
# 2. The repair_baked_cred_urls function correctly strips credentials
|
||||
# 3. configure_git_creds writes a working credential helper
|
||||
#
|
||||
# Required tools: bash, git, grep
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FACTORY_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
FAILED=0
|
||||
|
||||
fail() { printf 'FAIL: %s\n' "$*" >&2; FAILED=1; }
|
||||
pass() { printf 'PASS: %s\n' "$*"; }
|
||||
|
||||
# ── 1. Static check: no credential embedding in URL construction ──────────
|
||||
echo "=== 1/3 Static check: no credential embedding in URL construction ==="
|
||||
|
||||
# Patterns that embed credentials into git URLs:
|
||||
# sed "s|://|://user:pass@|" — the classic injection pattern
|
||||
# ://.*:.*@ — literal user:pass@ in a URL string
|
||||
# Allowlist: git-creds.sh itself (it writes the credential helper, not URLs),
|
||||
# and this test file.
|
||||
cred_embed_pattern='s\|://\|://.*:.*\$\{.*\}@'
|
||||
|
||||
offending_files=()
|
||||
while IFS= read -r f; do
|
||||
# Skip allowlisted files:
|
||||
# git-creds.sh — writes the credential helper, not URLs
|
||||
# smoke-credentials.sh — this test file
|
||||
# hire-agent.sh — one-shot setup: clones as newly-created user, clone dir deleted immediately
|
||||
case "$f" in
|
||||
*/git-creds.sh|*/smoke-credentials.sh|*/hire-agent.sh) continue ;;
|
||||
esac
|
||||
if grep -qE "$cred_embed_pattern" "$f" 2>/dev/null; then
|
||||
offending_files+=("$f")
|
||||
fi
|
||||
done < <(git -C "$FACTORY_ROOT" ls-files '*.sh')
|
||||
|
||||
if [ ${#offending_files[@]} -eq 0 ]; then
|
||||
pass "No shell scripts embed credentials in git remote URLs"
|
||||
else
|
||||
for f in "${offending_files[@]}"; do
|
||||
fail "Credential embedding found in: $f"
|
||||
grep -nE "$cred_embed_pattern" "$FACTORY_ROOT/$f" 2>/dev/null | head -3
|
||||
done
|
||||
fi
|
||||
|
||||
# ── 2. Unit test: repair_baked_cred_urls strips credentials ───────────────
|
||||
echo "=== 2/3 Unit test: repair_baked_cred_urls ==="
|
||||
|
||||
# Source the shared lib
|
||||
# shellcheck source=lib/git-creds.sh
|
||||
source "${FACTORY_ROOT}/lib/git-creds.sh"
|
||||
|
||||
# Create a temporary git repo with a baked-credential URL
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
|
||||
mkdir -p "${test_dir}/repo"
|
||||
git -C "${test_dir}/repo" init -q
|
||||
git -C "${test_dir}/repo" config user.email "test@test"
|
||||
git -C "${test_dir}/repo" config user.name "test"
|
||||
git -C "${test_dir}/repo" commit --allow-empty -m "init" -q
|
||||
git -C "${test_dir}/repo" remote add origin "http://dev-bot:secret-token@forgejo:3000/org/repo.git"
|
||||
|
||||
# Run repair
|
||||
_GIT_CREDS_LOG_FN="echo" repair_baked_cred_urls "${test_dir}"
|
||||
|
||||
# Verify the URL was cleaned
|
||||
repaired_url=$(git -C "${test_dir}/repo" config --get remote.origin.url)
|
||||
if [ "$repaired_url" = "http://forgejo:3000/org/repo.git" ]; then
|
||||
pass "repair_baked_cred_urls correctly stripped credentials"
|
||||
else
|
||||
fail "repair_baked_cred_urls result: '${repaired_url}' (expected 'http://forgejo:3000/org/repo.git')"
|
||||
fi
|
||||
|
||||
# Also test that a clean URL is left untouched
|
||||
git -C "${test_dir}/repo" remote set-url origin "http://forgejo:3000/org/repo.git"
|
||||
_GIT_CREDS_LOG_FN="echo" repair_baked_cred_urls "${test_dir}"
|
||||
clean_url=$(git -C "${test_dir}/repo" config --get remote.origin.url)
|
||||
if [ "$clean_url" = "http://forgejo:3000/org/repo.git" ]; then
|
||||
pass "repair_baked_cred_urls leaves clean URLs untouched"
|
||||
else
|
||||
fail "repair_baked_cred_urls modified a clean URL: '${clean_url}'"
|
||||
fi
|
||||
|
||||
# ── 3. Unit test: configure_git_creds writes a credential helper ──────────
|
||||
echo "=== 3/3 Unit test: configure_git_creds ==="
|
||||
|
||||
cred_home=$(mktemp -d)
|
||||
|
||||
# Export required globals
|
||||
export FORGE_PASS="test-password-123"
|
||||
export FORGE_URL="http://forgejo:3000"
|
||||
export FORGE_TOKEN="" # skip API call in test
|
||||
|
||||
configure_git_creds "$cred_home"
|
||||
|
||||
if [ -x "${cred_home}/.git-credentials-helper" ]; then
|
||||
pass "Credential helper script created and executable"
|
||||
else
|
||||
fail "Credential helper script not found or not executable at ${cred_home}/.git-credentials-helper"
|
||||
fi
|
||||
|
||||
# Verify the helper outputs correct credentials
|
||||
helper_output=$(echo "" | "${cred_home}/.git-credentials-helper" get 2>/dev/null)
|
||||
if printf '%s' "$helper_output" | grep -q "password=test-password-123"; then
|
||||
pass "Credential helper outputs correct password"
|
||||
else
|
||||
fail "Credential helper output missing password: ${helper_output}"
|
||||
fi
|
||||
|
||||
if printf '%s' "$helper_output" | grep -q "host=forgejo:3000"; then
|
||||
pass "Credential helper outputs correct host"
|
||||
else
|
||||
fail "Credential helper output missing host: ${helper_output}"
|
||||
fi
|
||||
|
||||
rm -rf "$cred_home"
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
if [ "$FAILED" -ne 0 ]; then
|
||||
echo "=== SMOKE-CREDENTIALS TEST FAILED ==="
|
||||
exit 1
|
||||
fi
|
||||
echo "=== SMOKE-CREDENTIALS TEST PASSED ==="
|
||||
Loading…
Add table
Add a link
Reference in a new issue