fix(framework/tools): wrapper hardening — TLS validation, cred-path fallback, no-CI fast-exit #551
@@ -16,7 +16,12 @@
|
|||||||
# After loading, service-specific env vars are exported.
|
# After loading, service-specific env vars are exported.
|
||||||
# Run `load_credentials --help` for details.
|
# Run `load_credentials --help` for details.
|
||||||
|
|
||||||
MOSAIC_CREDENTIALS_FILE="${MOSAIC_CREDENTIALS_FILE:-$HOME/src/jarvis-brain/credentials.json}"
|
if [[ -z "${MOSAIC_CREDENTIALS_FILE:-}" ]]; then
|
||||||
|
for _cand in "$HOME/.config/mosaic/credentials.json" "$HOME/src/jarvis-brain/credentials.json"; do
|
||||||
|
if [[ -f "$_cand" ]]; then MOSAIC_CREDENTIALS_FILE="$_cand"; break; fi
|
||||||
|
done
|
||||||
|
: "${MOSAIC_CREDENTIALS_FILE:=$HOME/src/jarvis-brain/credentials.json}"
|
||||||
|
fi
|
||||||
|
|
||||||
_mosaic_require_jq() {
|
_mosaic_require_jq() {
|
||||||
if ! command -v jq &>/dev/null; then
|
if ! command -v jq &>/dev/null; then
|
||||||
@@ -34,6 +39,19 @@ _mosaic_read_cred() {
|
|||||||
jq -r "$jq_path // empty" "$MOSAIC_CREDENTIALS_FILE"
|
jq -r "$jq_path // empty" "$MOSAIC_CREDENTIALS_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Decide curl TLS flag for a target URL: validate public hosts (MITM matters on
|
||||||
|
# WAN); allow self-signed only for private-network IP literals (trusted LAN) or an
|
||||||
|
# explicit $MOSAIC_INSECURE_TLS opt-in. Echoes "-k" or "" (empty).
|
||||||
|
_mosaic_tls_opt() {
|
||||||
|
local url="$1" host
|
||||||
|
[[ -n "${MOSAIC_INSECURE_TLS:-}" ]] && { echo "-k"; return; }
|
||||||
|
host=$(printf '%s' "$url" | sed -E 's#^[a-zA-Z]+://([^/:]+).*#\1#')
|
||||||
|
if [[ "$host" =~ ^(10\.|127\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.) ]]; then
|
||||||
|
echo "-k"; return
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Sync Woodpecker credentials to ~/.woodpecker/<instance>.env
|
# Sync Woodpecker credentials to ~/.woodpecker/<instance>.env
|
||||||
# Only writes when values differ to avoid unnecessary disk writes.
|
# Only writes when values differ to avoid unnecessary disk writes.
|
||||||
_mosaic_sync_woodpecker_env() {
|
_mosaic_sync_woodpecker_env() {
|
||||||
@@ -261,7 +279,8 @@ mosaic_http() {
|
|||||||
local base_url="${4:-}"
|
local base_url="${4:-}"
|
||||||
|
|
||||||
local response
|
local response
|
||||||
response=$(curl -sk -w "\n%{http_code}" -X "$method" \
|
local _tls; _tls=$(_mosaic_tls_opt "${base_url}${endpoint}")
|
||||||
|
response=$(curl -sS $_tls -w "\n%{http_code}" -X "$method" \
|
||||||
-H "$auth_header" \
|
-H "$auth_header" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"${base_url}${endpoint}")
|
"${base_url}${endpoint}")
|
||||||
@@ -279,7 +298,8 @@ mosaic_http_post() {
|
|||||||
local base_url="${4:-}"
|
local base_url="${4:-}"
|
||||||
|
|
||||||
local response
|
local response
|
||||||
response=$(curl -sk -w "\n%{http_code}" -X POST \
|
local _tls; _tls=$(_mosaic_tls_opt "${base_url}${endpoint}")
|
||||||
|
response=$(curl -sS $_tls -w "\n%{http_code}" -X POST \
|
||||||
-H "$auth_header" \
|
-H "$auth_header" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$data" \
|
-d "$data" \
|
||||||
@@ -297,7 +317,8 @@ mosaic_http_patch() {
|
|||||||
local base_url="${4:-}"
|
local base_url="${4:-}"
|
||||||
|
|
||||||
local response
|
local response
|
||||||
response=$(curl -sk -w "\n%{http_code}" -X PATCH \
|
local _tls; _tls=$(_mosaic_tls_opt "${base_url}${endpoint}")
|
||||||
|
response=$(curl -sS $_tls -w "\n%{http_code}" -X PATCH \
|
||||||
-H "$auth_header" \
|
-H "$auth_header" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$data" \
|
-d "$data" \
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ elif values and all(v == "success" for v in values):
|
|||||||
print("success")
|
print("success")
|
||||||
elif any(v in {"pending", "running", "queued", "waiting"} for v in values):
|
elif any(v in {"pending", "running", "queued", "waiting"} for v in values):
|
||||||
print("pending")
|
print("pending")
|
||||||
|
elif not values and not state:
|
||||||
|
# No pipeline/status of any kind reported for this commit. Distinct from
|
||||||
|
# "unknown" (an ambiguous/unrecognized status that should keep polling):
|
||||||
|
# this signals a repo/commit that simply has no CI configured.
|
||||||
|
print("no-status")
|
||||||
else:
|
else:
|
||||||
print("unknown")
|
print("unknown")
|
||||||
PY
|
PY
|
||||||
@@ -245,6 +250,13 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Count consecutive polls that find NO pipeline/status at all. A repo/commit with
|
||||||
|
# no CI configured (e.g. device-imaging class) would otherwise burn the full
|
||||||
|
# timeout in the pending/unknown branch. After NO_CI_MAX such polls, fast-exit 0
|
||||||
|
# with a clear "no CI configured" message — distinct from a real failure.
|
||||||
|
NO_CI_STREAK=0
|
||||||
|
NO_CI_MAX=3
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
NOW_TS=$(date +%s)
|
NOW_TS=$(date +%s)
|
||||||
if (( NOW_TS > DEADLINE_TS )); then
|
if (( NOW_TS > DEADLINE_TS )); then
|
||||||
@@ -272,11 +284,24 @@ while true; do
|
|||||||
echo "Error: CI reported ${STATE} for PR #$PR_NUMBER." >&2
|
echo "Error: CI reported ${STATE} for PR #$PR_NUMBER." >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
|
no-status)
|
||||||
|
NO_CI_STREAK=$((NO_CI_STREAK + 1))
|
||||||
|
if (( NO_CI_STREAK >= NO_CI_MAX )); then
|
||||||
|
echo "[INFO] no CI configured for this repo/commit (PR #$PR_NUMBER, ${NO_CI_STREAK} consecutive empty polls); treating as green."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep "$INTERVAL_SEC"
|
||||||
|
;;
|
||||||
pending|unknown)
|
pending|unknown)
|
||||||
|
# A pipeline exists but hasn't reached a terminal state (or is
|
||||||
|
# transiently ambiguous) — keep waiting, and reset the no-CI streak
|
||||||
|
# since this commit is not in the "no CI at all" condition.
|
||||||
|
NO_CI_STREAK=0
|
||||||
sleep "$INTERVAL_SEC"
|
sleep "$INTERVAL_SEC"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "[pr-ci-wait] Unrecognized state '${STATE}', continuing to poll..."
|
echo "[pr-ci-wait] Unrecognized state '${STATE}', continuing to poll..."
|
||||||
|
NO_CI_STREAK=0
|
||||||
sleep "$INTERVAL_SEC"
|
sleep "$INTERVAL_SEC"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ wp_resolve_repo_id() {
|
|||||||
local full_name="$1"
|
local full_name="$1"
|
||||||
local response http_code body repo_id
|
local response http_code body repo_id
|
||||||
|
|
||||||
response=$(curl -sk -w "\n%{http_code}" \
|
response=$(curl -sS -w "\n%{http_code}" \
|
||||||
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
||||||
"${WOODPECKER_URL}/api/repos/lookup/${full_name}")
|
"${WOODPECKER_URL}/api/repos/lookup/${full_name}")
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fi
|
|||||||
# Resolve owner/repo to numeric ID (Woodpecker v3 API)
|
# Resolve owner/repo to numeric ID (Woodpecker v3 API)
|
||||||
REPO_ID=$(wp_resolve_repo_id "$REPO") || exit 1
|
REPO_ID=$(wp_resolve_repo_id "$REPO") || exit 1
|
||||||
|
|
||||||
response=$(curl -sk -w "\n%{http_code}" \
|
response=$(curl -sS -w "\n%{http_code}" \
|
||||||
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
||||||
"${WOODPECKER_URL}/api/repos/${REPO_ID}/pipelines?perPage=${LIMIT}")
|
"${WOODPECKER_URL}/api/repos/${REPO_ID}/pipelines?perPage=${LIMIT}")
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ REPO_ID=$(wp_resolve_repo_id "$REPO") || exit 1
|
|||||||
_wp_fetch() {
|
_wp_fetch() {
|
||||||
local ep="$1"
|
local ep="$1"
|
||||||
local resp http_code body
|
local resp http_code body
|
||||||
resp=$(curl -sk -w "\n%{http_code}" \
|
resp=$(curl -sS -w "\n%{http_code}" \
|
||||||
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
||||||
"$ep")
|
"$ep")
|
||||||
http_code=$(echo "$resp" | tail -n1)
|
http_code=$(echo "$resp" | tail -n1)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ REPO_ID=$(wp_resolve_repo_id "$REPO") || exit 1
|
|||||||
|
|
||||||
echo "Triggering pipeline for $REPO on branch $BRANCH..."
|
echo "Triggering pipeline for $REPO on branch $BRANCH..."
|
||||||
|
|
||||||
response=$(curl -sk -w "\n%{http_code}" -X POST \
|
response=$(curl -sS -w "\n%{http_code}" -X POST \
|
||||||
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
-H "Authorization: Bearer $WOODPECKER_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$(jq -n --arg b "$BRANCH" '{branch: $b}')" \
|
-d "$(jq -n --arg b "$BRANCH" '{branch: $b}')" \
|
||||||
|
|||||||
Reference in New Issue
Block a user