Files
mxaccessgw/scripts/ci/run-windev-ci.sh
T
Joseph Doherty 6803bab79a
ci / portable (push) Successful in 7m28s
ci / java (push) Successful in 1m59s
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m6s
fix(TST-25,SEC): stop CI SSH key leaking in cleartext CI logs
The windows-x86 acceptance check 'no key material in logs' failed: run #37's
job log printed the full WINDEV_SSH_KEY PEM in the step env echo. Gitea's
secret masker is line-oriented, so a multiline PEM rendered as one line with
literal \n escapes never matches the real-newline secret value and is not
redacted.

Fix: store WINDEV_SSH_KEY base64-encoded (single line) so the masker redacts
it to ***; run-windev-ci.sh auto-decodes a base64 PEM (still accepts a raw PEM
for local hand-testing). Drop the redundant WINDEV_SSH_KNOWN_HOSTS from the job
env (host keys are public and come from the committed windev.known_hosts pin),
removing another cleartext env line. Document the base64 requirement in the
bring-up README.

Operationally: the previously-exposed CI key has been rotated on windev
(old pubkey revoked from administrators_authorized_keys, new key installed) and
the Gitea WINDEV_SSH_KEY secret replaced with the new key's base64.
2026-07-13 10:45:16 -04:00

106 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# TST-25: Linux side of the Windows/x86 Worker CI tier.
#
# Runs in the `windows-x86` / nightly Gitea jobs on a Linux runner (which always schedules,
# unlike a broken Windows act_runner). SSHes to windev (10.100.0.48), where it fetches +
# checks out the SHA under test in the isolated CI clone C:\build\mxaccessgw-ci and runs
# scripts/ci/windev-worker-ci.ps1 there. The remote exit code propagates through
# ssh -> this script -> the CI step, so a Worker regression turns the job red.
#
# Usage: run-windev-ci.sh <build|test|live>
#
# Environment:
# CI_SHA commit to test (default: `git rev-parse HEAD` in this checkout)
# WINDEV_SSH_KEY private key (Gitea secret). Store it BASE64-ENCODED (one line):
# Gitea's secret masker is line-oriented, so a raw multiline PEM in
# the step `env:` echo is NOT redacted and leaks in cleartext, whereas
# the single-line base64 is masked to *** (TST-25 acceptance: no key
# material in logs). A raw PEM is still accepted for local hand-testing
# (auto-detected). If empty, falls back to the runner's default identity.
# WINDEV_SSH_KNOWN_HOSTS pinned host keys (Gitea secret). If empty, uses the committed
# scripts/ci/windev.known_hosts.
# WINDEV_SSH_USER ssh user on windev (default: ci)
# WINDEV_SSH_HOST windev host/IP (default: 10.100.0.48)
#
# The private key is never echoed (written 0600 to a temp file, removed on exit). git on
# windev writes progress to stderr, so the remote bootstrap does NOT use -ErrorActionPreference
# Stop around git — it checks $LASTEXITCODE instead (known windev gotcha).
set -euo pipefail
MODE="${1:-}"
case "$MODE" in
build|test|live) ;;
*) echo "usage: $0 <build|test|live>" >&2; exit 2 ;;
esac
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CI_SHA="${CI_SHA:-$(git -C "$SCRIPT_DIR" rev-parse HEAD)}"
WINDEV_SSH_USER="${WINDEV_SSH_USER:-ci}"
WINDEV_SSH_HOST="${WINDEV_SSH_HOST:-10.100.0.48}"
REMOTE_CLONE='C:\build\mxaccessgw-ci'
REMOTE_SCRIPT='scripts\ci\windev-worker-ci.ps1'
# Validate the SHA shape before it reaches a remote shell command.
if ! printf '%s' "$CI_SHA" | grep -Eq '^[0-9a-fA-F]{7,40}$'; then
echo "run-windev-ci: refusing to run — CI_SHA '$CI_SHA' is not a hex commit id" >&2
exit 2
fi
WORK="$(mktemp -d)"
cleanup() { rm -rf "$WORK"; }
trap cleanup EXIT
SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=yes -o ConnectTimeout=15)
# known_hosts: secret if provided, else the committed pins.
KNOWN_HOSTS="$WORK/known_hosts"
if [ -n "${WINDEV_SSH_KNOWN_HOSTS:-}" ]; then
printf '%s\n' "$WINDEV_SSH_KNOWN_HOSTS" > "$KNOWN_HOSTS"
else
cp "$SCRIPT_DIR/windev.known_hosts" "$KNOWN_HOSTS"
fi
SSH_OPTS+=(-o "UserKnownHostsFile=$KNOWN_HOSTS")
# Private key: secret if provided, else fall back to the default identity/agent.
# CI stores it base64-encoded (single line) so Gitea's line-oriented secret masker redacts it
# in the step env echo — a raw multiline PEM leaks in cleartext. Decode when it is valid base64
# wrapping a PEM; otherwise treat the value as a raw PEM (local hand-testing convenience).
if [ -n "${WINDEV_SSH_KEY:-}" ]; then
KEY="$WORK/id_ci"
if printf '%s' "$WINDEV_SSH_KEY" | base64 -d 2>/dev/null | grep -q 'PRIVATE KEY'; then
( umask 077; printf '%s' "$WINDEV_SSH_KEY" | base64 -d > "$KEY" )
else
( umask 077; printf '%s\n' "$WINDEV_SSH_KEY" > "$KEY" )
fi
SSH_OPTS+=(-o IdentitiesOnly=yes -i "$KEY")
fi
echo "run-windev-ci: mode=$MODE sha=$CI_SHA target=${WINDEV_SSH_USER}@${WINDEV_SSH_HOST}"
# Remote PowerShell bootstrap: fetch + checkout enough to load the versioned CI script, then
# hand off to it. Not Stop around git (stderr progress). windev-worker-ci.ps1 re-fetches and
# re-checks-out under a lock, so this checkout only needs to load the right script version.
read -r -d '' BOOTSTRAP <<PS || true
\$ErrorActionPreference = 'Continue'
Set-Location '$REMOTE_CLONE'
git fetch --prune origin
git checkout --force --detach $CI_SHA
if (\$LASTEXITCODE -ne 0) { Write-Error "bootstrap checkout failed"; exit \$LASTEXITCODE }
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '$REMOTE_SCRIPT' -Sha '$CI_SHA' -Mode '$MODE'
exit \$LASTEXITCODE
PS
# PowerShell -EncodedCommand wants UTF-16LE base64 on one line (avoids all ssh/quoting hazards).
ENCODED="$(printf '%s' "$BOOTSTRAP" | iconv -t UTF-16LE | base64 | tr -d '\n')"
set +e
ssh "${SSH_OPTS[@]}" "${WINDEV_SSH_USER}@${WINDEV_SSH_HOST}" \
"powershell -NoProfile -NonInteractive -EncodedCommand $ENCODED"
RC=$?
set -e
echo "run-windev-ci: remote exit code $RC"
exit $RC