Skip to content

Commit 960b4e9

Browse files
LTSCommerceclaude
andcommitted
Plan 00054: make GitHub SSH verification + git wrappers port-agnostic for 443
Two more port-22 hardwires that only surfaced at runtime on a port-22-blocked network (the deploy chicken-and-egg the user hit): 1. 'Verify SSH access' probe targeted only the configured endpoint, so on a restricted network it tried github.com:22, got 'Connection refused', and the play died before 443 could help. Now it falls back to ssh.github.com:443 (same host keys/identity) when the primary endpoint does not authenticate — the same auto-fallback ccy uses. Task timeout 30 -> 45 for the second attempt. 2. Deployed per-account git wrappers (git()/git-<alias>()/clone-<alias>) forced 'ssh -F /dev/null -o HostName=github.com', isolating the key but also bypassing the ~/.ssh/config 443 override — so wrapper-driven git push stayed on port 22 even with 443 enabled. New shared _gh443_sshcmd helper builds the sshCommand from the GITHUB_SSH_443 runtime signal (ssh.github.com:443 when set, else github.com:22; unset = byte-identical to the old string). Key-isolation model untouched. Wrapper users need GITHUB_SSH_443 in-shell (automatic with always-on profile.d, or eval "$(github-ssh-443 env)"). Other play tasks (audit, override apply, key fetch) already use the GitHub HTTPS API/meta endpoint, so they work on a port-22-blocked network. Docs + plan notes updated. QA green; --syntax-check clean. Refs: CLAUDE/Plan/00054-github-ssh-443-host-level Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3d21355 commit 960b4e9

3 files changed

Lines changed: 69 additions & 8 deletions

File tree

CLAUDE/Plan/00054-github-ssh-443-host-level/PLAN.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,24 @@ signal. Always-on uses the profile.d export of the same var.
280280
self-default check (PCRE backreference, targets the `\1 | default` idiom only so
281281
it doesn't false-positive on `blockinfile` literals like nordvpn's
282282
`x: {{ x }}`). Documented in `CLAUDE/QA.md` and `CLAUDE/AgentNotes.md`.
283+
- **Restricted-network chicken-and-egg fixed (found on first HOST run, port 22
284+
blocked).** Two more port-22 hardwires that only fail at runtime on a blocked
285+
network:
286+
1. The "Verify SSH access" probe targeted only the configured endpoint
287+
(`github.com:22` when the toggle is off), so the play died with "Connection
288+
refused" before 443 could help. Fix: the probe now falls back to
289+
`ssh.github.com:443` (same host keys/identity) when the primary endpoint does
290+
not authenticate — the same auto-fallback pattern ccy uses. Verification is
291+
now port-agnostic; ansible task `timeout` 30→45 to fit two attempts.
292+
2. The deployed per-account git wrappers (`git()` auto-detect, `git-<alias>()`,
293+
`clone-<alias>`) forced `ssh -F /dev/null -o HostName=github.com`, which
294+
isolates the key but also bypasses the `~/.ssh/config` 443 override — so even
295+
with 443 enabled, wrapper-driven `git push` stayed on port 22. Fix: a shared
296+
`_gh443_sshcmd` helper in the deployed include builds the sshCommand from the
297+
`GITHUB_SSH_443` runtime signal (ssh.github.com:443 when set, else
298+
github.com:22; unset → byte-identical to the old string). Key-isolation model
299+
untouched. Note: wrapper users need `GITHUB_SSH_443` in the shell — automatic
300+
with always-on (profile.d) or `eval "$(github-ssh-443 env)"`; plain `git`
301+
with `git@github.com-<alias>:` remotes already routes via the override block.
302+
Everything else in the play (key audit, override apply, key fetch) already used
303+
the GitHub HTTPS API / meta endpoint, so it works on a port-22-blocked network.

docs/github-multi-account.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,15 @@ to `github.com`, pass their globs so they are rerouted too:
260260
github-ssh-443 on --aliases 'myorg_deploy_*,clientco_*'
261261
```
262262

263+
> **Run `eval "$(github-ssh-443 env)"` too.** The per-account git wrappers
264+
> (`git`, `git-<alias>`, `clone-<alias>`) force an isolated `ssh -F /dev/null`
265+
> command that bypasses `~/.ssh/config`, so they follow the `GITHUB_SSH_443`
266+
> **environment** signal, not the config block. Without the `eval`, plain `git`
267+
> with a `git@github.com-<alias>:` remote still routes over 443 (via the config
268+
> override), but the wrappers would stay on port 22. Always-on mode sets the env
269+
> var for every shell via `/etc/profile.d`, so this only matters for the temporary
270+
> CLI path.
271+
263272
### Desktop host — always-on (persisted)
264273

265274
Set the variable in `environment/localhost/host_vars/localhost.yml` and re-run the

playbooks/imports/play-github-cli-multi.yml

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,13 +539,26 @@
539539
# specified key. Without these, ssh also offers every key in the
540540
# running ssh-agent — GitHub rate-limits after ~5 failed attempts
541541
# and ssh hangs until the outer timeout fires (rc=124, no output).
542-
timeout 15 ssh -F /dev/null -o IdentityAgent=none -o IdentitiesOnly=yes \
543-
-o BatchMode=yes -o ConnectTimeout=10 \
544-
-o StrictHostKeyChecking=accept-new -i "$tmp_key" \
545-
-p {{ github_ssh_port }} -T git@{{ github_ssh_hostname }} 2>&1
542+
out=$(timeout 15 ssh -F /dev/null -o IdentityAgent=none -o IdentitiesOnly=yes \
543+
-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
544+
-i "$tmp_key" -p {{ github_ssh_port }} -T git@{{ github_ssh_hostname }} 2>&1)
545+
# Port-agnostic fallback: if the primary endpoint did not authenticate
546+
# (e.g. a network that firewalls port 22), retry over ssh.github.com:443,
547+
# which serves the SAME host keys and identity. This keeps verification
548+
# working on restricted networks and removes the chicken-and-egg where
549+
# the play cannot pass on a port-22-blocked network before 443 is on.
550+
if ! printf '%s' "$out" | grep -q "successfully authenticated"; then
551+
alt=$(timeout 15 ssh -F /dev/null -o IdentityAgent=none -o IdentitiesOnly=yes \
552+
-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new \
553+
-i "$tmp_key" -p 443 -T git@ssh.github.com 2>&1)
554+
if printf '%s' "$alt" | grep -q "successfully authenticated"; then
555+
out="$alt"
556+
fi
557+
fi
558+
printf '%s\n' "$out"
546559
args:
547560
executable: /bin/bash
548-
timeout: 30
561+
timeout: 45
549562
register: ssh_test
550563
changed_when: false
551564
failed_when: ssh_test.rc not in [0, 1, 124, 255]
@@ -656,6 +669,24 @@
656669
block: |
657670
# GitHub CLI Multi-Account Aliases and Functions
658671
672+
# GitHub SSH-over-443 awareness for the per-account git wrappers.
673+
# Those wrappers force `ssh -F /dev/null -o HostName=github.com` to
674+
# isolate to a single key (ignoring ~/.ssh/config), which also
675+
# bypasses the 443 override block. So they build their sshCommand
676+
# through this helper, which consults the GITHUB_SSH_443 runtime
677+
# signal and swaps the endpoint to ssh.github.com:443 when set
678+
# (host export, /etc/profile.d, or `eval "$(github-ssh-443 env)"`),
679+
# else github.com:22. Unset → identical to the previous behaviour.
680+
_gh443_sshcmd() {
681+
local key="$1"
682+
local host="github.com" port="22"
683+
if [ "${GITHUB_SSH_443:-0}" = "1" ]; then
684+
host="ssh.github.com"
685+
port="443"
686+
fi
687+
printf 'ssh -F /dev/null -o HostName=%s -p %s -i %s -o IdentitiesOnly=yes -o AddKeysToAgent=yes' "$host" "$port" "$key"
688+
}
689+
659690
# Quick status check for all GitHub accounts
660691
function gh-status() {
661692
echo "GitHub CLI Account Status:"
@@ -792,7 +823,7 @@
792823
793824
# Switch to the right account and clone
794825
gh auth switch --hostname github.com --user "{{ username }}" 2>/dev/null
795-
gh repo clone "$repo_path" -- --config core.sshCommand="ssh -F /dev/null -o HostName=github.com -i ~/.ssh/github_{{ alias }} -o IdentitiesOnly=yes -o AddKeysToAgent=yes"
826+
gh repo clone "$repo_path" -- --config core.sshCommand="$(_gh443_sshcmd ~/.ssh/github_{{ alias }})"
796827
local exit_code=$?
797828
798829
# Set the remote to use the specific SSH key
@@ -1034,7 +1065,7 @@
10341065
# would try to resolve the literal host "github.com-<alias>"
10351066
# and fail ("Could not resolve hostname"). Forcing HostName
10361067
# restores the real target while keeping the isolation.
1037-
command git -c "core.sshCommand=ssh -F /dev/null -o HostName=github.com -i ~/.ssh/github_${detected_alias} -o IdentitiesOnly=yes -o AddKeysToAgent=yes" "$@"
1068+
command git -c "core.sshCommand=$(_gh443_sshcmd ~/.ssh/github_${detected_alias})" "$@"
10381069
else
10391070
# No account detected, use standard git
10401071
command git "$@"
@@ -1051,7 +1082,7 @@
10511082
# "github.com-<alias>" used by clone-<alias>/remote-<alias> remotes.
10521083
{% for alias in github_accounts.keys() %}
10531084
function git-{{ alias }}() {
1054-
command git -c "core.sshCommand=ssh -F /dev/null -o HostName=github.com -i ~/.ssh/github_{{ alias }} -o IdentitiesOnly=yes -o AddKeysToAgent=yes" "$@"
1085+
command git -c "core.sshCommand=$(_gh443_sshcmd ~/.ssh/github_{{ alias }})" "$@"
10551086
}
10561087
{% endfor %}
10571088

0 commit comments

Comments
 (0)