diff --git a/.devcontainer/tools/gluekube_ssh.sh b/.devcontainer/tools/gluekube_ssh.sh index a41c8bcef8..f46534c4d4 100755 --- a/.devcontainer/tools/gluekube_ssh.sh +++ b/.devcontainer/tools/gluekube_ssh.sh @@ -12,6 +12,15 @@ GUM_CHOOSE_HEADER="Select an option" # Ensure config directory exists mkdir -p "$CONFIG_DIR" +SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR) + +# ProxyCommand to reach a node through the bastion. Tunnels with -W and auths +# both hops from the local ssh-agent, so it works with AllowAgentForwarding no. +bastion_proxy() { + local bastion_ip="$1" + echo "ssh ${SSH_OPTS[*]} -W %h:%p cluster@$bastion_ip" +} + # API helper functions api_call() { local method="$1" @@ -658,18 +667,16 @@ kubectl_mode() { fi fi - # Start port forward in background. Route local:6443 -> bastion: - # -> master:6443 so a stale forward on the bastion's 6443 can't block us. - local mid_port=$(( 20000 + (RANDOM % 20000) )) - echo "Starting port forward: localhost:6443 -> $hostname:6443 (via bastion:$mid_port)" + # Tunnel to the master via the bastion; binds 6443 locally only. + echo "Starting port forward: localhost:6443 -> $hostname:6443 (via bastion)" # Create a temporary error log local error_log=$(mktemp) # Start port forward with error output captured - using proper backgrounding - (ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ExitOnForwardFailure=yes \ - -L "6443:localhost:${mid_port}" -t cluster@"$bastion_ip" \ - "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ExitOnForwardFailure=yes -N -L ${mid_port}:localhost:6443 cluster@$target_ip" \ + (ssh "${SSH_OPTS[@]}" -o ExitOnForwardFailure=yes \ + -o ProxyCommand="$(bastion_proxy "$bastion_ip")" \ + -N -L 6443:localhost:6443 cluster@"$target_ip" \ 2>"$error_log") & local ssh_pid=$! @@ -773,10 +780,8 @@ kubeconfig_mode() { echo "Fetching kubeconfig from $hostname..." - # Copy the file through bastion using double-hop SCP with agent forwarding and private IP - if ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -t cluster@"$bastion_ip" \ - "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR cluster@$target_ip \ - 'sudo cat /etc/kubernetes/admin.conf'" > ~/.kube/config 2>&1; then + if ssh "${SSH_OPTS[@]}" -o ProxyCommand="$(bastion_proxy "$bastion_ip")" cluster@"$target_ip" \ + 'sudo cat /etc/kubernetes/admin.conf' > ~/.kube/config 2>/dev/null; then # Update the server URL to localhost:6443 if kubectl config set-cluster "kubernetes" --server=https://127.0.0.1:6443 >/dev/null 2>&1; then echo "✓ Kubeconfig saved to ~/.kube/config" @@ -872,9 +877,8 @@ kubeconfig_and_port_forward() { kube_tmp=$(mktemp) echo "Fetching fresh kubeconfig from $hostname..." - if ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -t cluster@"$bastion_ip" \ - "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR cluster@$target_ip \ - 'sudo cat /etc/kubernetes/admin.conf'" > "$kube_tmp" 2>/dev/null && [[ -s "$kube_tmp" ]]; then + if ssh "${SSH_OPTS[@]}" -o ProxyCommand="$(bastion_proxy "$bastion_ip")" cluster@"$target_ip" \ + 'sudo cat /etc/kubernetes/admin.conf' > "$kube_tmp" 2>/dev/null && [[ -s "$kube_tmp" ]]; then mv "$kube_tmp" ~/.kube/config # Point the kubeconfig at the local port-forward if kubectl config set-cluster "kubernetes" --server=https://127.0.0.1:6443 >/dev/null 2>&1; then @@ -889,29 +893,19 @@ kubeconfig_and_port_forward() { return 1 fi - # Start a foreground port-forward. The path is: - # local:6443 -> bastion: -> master:6443 - # The middle hop deliberately uses a RANDOM high port on the bastion, NOT - # 6443. The old code bound 6443 on the bastion too, so a stale forward left - # on the bastion's 6443 (from an interrupted session or another engineer) - # made every new connect fail with "bind [127.0.0.1]:6443: Address already - # in use" - even when the LOCAL 6443 was free. A random mid port avoids it. - local mid_port=$(( 20000 + (RANDOM % 20000) )) - + # Foreground port-forward to the master via the bastion; binds 6443 locally only. echo "" - echo "Starting port forward: localhost:6443 -> $hostname:6443 (via bastion:$mid_port)" + echo "Starting port forward: localhost:6443 -> $hostname:6443 (via bastion)" echo "Leave this running; use kubectl from another terminal (e.g. kubectl get nodes)." echo "Press Ctrl+C to stop." echo "" - # ExitOnForwardFailure on BOTH hops so a bind clash fails fast instead of - # hanging. Capture exit status + elapsed time to distinguish a real bind - # failure (exits immediately) from a normal user Ctrl+C (ran for a while). + # Distinguish a fast bind failure from a normal user Ctrl+C via exit code + elapsed time. local fwd_start fwd_rc=0 fwd_start=$(date +%s) - ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ExitOnForwardFailure=yes \ - -L "6443:localhost:${mid_port}" -t cluster@"$bastion_ip" \ - "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ExitOnForwardFailure=yes -N -L ${mid_port}:localhost:6443 cluster@$target_ip" || fwd_rc=$? + ssh "${SSH_OPTS[@]}" -o ExitOnForwardFailure=yes \ + -o ProxyCommand="$(bastion_proxy "$bastion_ip")" \ + -N -L 6443:localhost:6443 cluster@"$target_ip" || fwd_rc=$? local fwd_elapsed=$(( $(date +%s) - fwd_start )) echo "" @@ -1458,9 +1452,7 @@ connect_ssh() { ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR cluster@"$bastion_ip" else - # Use agent forwarding with private IP address - ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -t cluster@"$bastion_ip" \ - "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR cluster@$target_ip" + ssh "${SSH_OPTS[@]}" -o ProxyCommand="$(bastion_proxy "$bastion_ip")" -t cluster@"$target_ip" fi }