Skip to content

Commit 5247a57

Browse files
committed
Add test-ssh-public-key-auth.sh script
1 parent 9bb906a commit 5247a57

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/bin/bash
2+
3+
# SSH Public Key Authentication Matrix Test
4+
#
5+
# This script tests that older OpenSSH client versions can still connect to an
6+
# XCP-ng server using public key authentication
7+
#
8+
# Usage:
9+
# test-ssh-public-key-auth.sh <host>
10+
#
11+
# Context:
12+
# Due to the removal of the ssh-rsa signature algorithm in OpenSSH 9.8, older
13+
# clients (version < 7.2) are no longer able to use public key authentication
14+
# with ssh-rsa keys. Howerver, newer OpenSSH versions (7.2 and above) should
15+
# still be able to authenticate using ssh-rsa keys, as well as ed25519 keys.
16+
#
17+
# This script can be used to verify this assumption by running a matrix of tests
18+
# with different OpenSSH client versions and key types against a specified host.
19+
# In practice, it should be run manually against a test XCP-ng server whenever
20+
# the OpenSSH RPM package is updated, to ensure that we maintain compatibility
21+
# with older clients.
22+
#
23+
# What does each test do?
24+
# 1. Pull the specified OpenSSH client Docker image.
25+
# 2. Generate a new SSH key pair using the OpenSSH client in the Docker container.
26+
# 3. Copy the public key to the target host using ssh-copy-id with password
27+
# authentication.
28+
# 4. Attempt to SSH into the host using the private key and run a simple command
29+
# to verify that authentication works.
30+
# 5. Clean up by removing the public key from the host's authorized_keys file
31+
# and deleting the generated key pair.
32+
#
33+
# It requires Docker or Podman to be installed.
34+
35+
# -e: Exit immediately if a command exits with a non-zero status.
36+
# -u: Treat unset variables as an error and exit immediately.
37+
set -eu
38+
39+
# Constants and defaults
40+
USER="root"
41+
PASSWORD=""
42+
VERSIONS=("7.2_p2-r5" "7.9_p1-r6" "8.8_p1-r1" "9.9_p2-r0" "10.2_p1-r0")
43+
KEY_TYPES=("rsa" "ed25519")
44+
SSH_OPTIONS="-o StrictHostKeyChecking=no"
45+
IMAGE_BASE_NAME="sig9/alpine-openssh-client"
46+
47+
print_help() {
48+
cat <<'EOF'
49+
Usage:
50+
test-ssh-public-key-auth.sh <host>
51+
52+
Description:
53+
Runs an SSH public key authentication matrix test with:
54+
- OpenSSH client versions: 7.2_p2-r5, 7.9_p1-r6, 8.8_p1-r1, 9.9_p2-r0, 10.2_p1-r0
55+
- Key types: rsa, ed25519
56+
Requires Docker or Podman to be installed.
57+
EOF
58+
}
59+
60+
silent_unless_fail() {
61+
local err
62+
# Run the command passed as arguments, capture stderr, discard stdout
63+
if ! err=$("$@" 2>/dev/stdout >/dev/null); then
64+
echo -e "\b\b\b: ❌\n$err" >&2
65+
return 1
66+
fi
67+
}
68+
69+
status() {
70+
# Rewrite a single terminal status line for step-by-step progress.
71+
printf "\r\033[K$CURRENT_PREFIX %s" "$1"
72+
}
73+
74+
build_prefix() {
75+
local host="$1"
76+
local version="$2"
77+
local key_type="$3"
78+
local version_fixed
79+
local key_type_fixed
80+
81+
C_RESET=$'\e[0m'
82+
C_DIM=$'\e[2m'
83+
C_HOST_LABEL=$'\e[36m'
84+
C_HOST_VALUE=$'\e[1;96m'
85+
C_VER_LABEL=$'\e[33m'
86+
C_VER_VALUE=$'\e[1;93m'
87+
C_KEY_LABEL=$'\e[32m'
88+
C_KEY_VALUE=$'\e[1;92m'
89+
90+
printf -v version_fixed "%-10.10s" "$version"
91+
printf -v key_type_fixed "%-7.7s" "$key_type"
92+
93+
local prefix="${C_DIM}[${C_RESET}"
94+
prefix+="${C_HOST_LABEL}host${C_RESET}:${C_HOST_VALUE}${host}${C_RESET} "
95+
prefix+="${C_DIM}|${C_RESET} "
96+
prefix+="${C_VER_LABEL}ver${C_RESET}:${C_VER_VALUE}${version_fixed}${C_RESET} "
97+
prefix+="${C_DIM}|${C_RESET} "
98+
prefix+="${C_KEY_LABEL}key${C_RESET}:${C_KEY_VALUE}${key_type_fixed}${C_RESET}"
99+
prefix+="${C_DIM}]${C_RESET}"
100+
printf "%s" "$prefix"
101+
}
102+
103+
run_case() {
104+
local version="$1"
105+
local key_type="$2"
106+
107+
local image="$IMAGE_BASE_NAME:$version"
108+
local key_name="regression_testing_${version}_${key_type}"
109+
local comment="$(uuidgen)@testing-${version}-${key_type}"
110+
local cleanup="sed -i /$comment\$/d ~/.ssh/authorized_keys"
111+
112+
CURRENT_PREFIX="$(build_prefix "$HOST" "$version" "$key_type")"
113+
114+
status "Pulling Docker image $image..."
115+
if ! silent_unless_fail $CONTAINER_ENGINE pull "$image"; then
116+
return 1
117+
fi
118+
119+
status "Testing SSH connection to $USER@$HOST using $image"
120+
rm -f "$key_name" "$key_name.pub"
121+
122+
status "Generating SSH key pair..."
123+
if ! silent_unless_fail $CONTAINER_ENGINE run --rm -v "$(pwd):/data" "$image" -c "cd /data && ssh-keygen -t $key_type -f $key_name -C '$comment' -N ''"; then
124+
rm -f "$key_name" "$key_name.pub"
125+
return 1
126+
fi
127+
128+
status "Copying public key to $USER@$HOST..."
129+
if ! silent_unless_fail $CONTAINER_ENGINE run --rm -v "$(pwd):/data" "$image" -c "mkdir -p /root/.ssh && sshpass -p '$PASSWORD' ssh-copy-id -i /data/$key_name.pub $SSH_OPTIONS '$USER@$HOST'"; then
130+
rm -f "$key_name" "$key_name.pub"
131+
return 1
132+
fi
133+
134+
status "Verifying SSH connection..."
135+
if ! silent_unless_fail $CONTAINER_ENGINE run --rm -v "$(pwd):/data" "$image" -c "ssh $SSH_OPTIONS -i /data/$key_name '$USER@$HOST' 'echo hello'"; then
136+
rm -f "$key_name" "$key_name.pub"
137+
return 1
138+
fi
139+
140+
status "SSH connection successful, cleaning up..."
141+
if ! silent_unless_fail $CONTAINER_ENGINE run --rm -v "$(pwd):/data" "$image" -c "ssh $SSH_OPTIONS -i /data/$key_name '$USER@$HOST' '$cleanup'"; then
142+
rm -f "$key_name" "$key_name.pub"
143+
return 1
144+
fi
145+
rm -f "$key_name" "$key_name.pub"
146+
147+
status "PASS ✅"
148+
printf "\n"
149+
return 0
150+
}
151+
152+
153+
# Parse arguments
154+
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
155+
print_help
156+
exit 0
157+
fi
158+
159+
if [ "$#" -ne 1 ]; then
160+
echo "Expected exactly one argument: <host>" >&2
161+
print_help
162+
exit 1
163+
fi
164+
165+
HOST="$1"
166+
167+
168+
# Detect the container engine
169+
if command -v podman &> /dev/null; then
170+
CONTAINER_ENGINE="podman"
171+
elif command -v docker &> /dev/null; then
172+
CONTAINER_ENGINE="docker"
173+
else
174+
echo "Error: Neither podman nor docker found."
175+
exit 1
176+
fi
177+
178+
179+
180+
# Prompt for password
181+
if [ -z "$PASSWORD" ]; then
182+
read -s -p $'Enter \e[1m'"$USER@$HOST"$'\e[0m password: ' PASSWORD
183+
echo ""
184+
fi
185+
186+
187+
# Run the matrix of tests
188+
TOTAL=0
189+
PASSED=0
190+
FAILED=0
191+
192+
for VERSION in "${VERSIONS[@]}"; do
193+
for KEY_TYPE in "${KEY_TYPES[@]}"; do
194+
TOTAL=$((TOTAL + 1))
195+
if run_case "$VERSION" "$KEY_TYPE"; then
196+
PASSED=$((PASSED + 1))
197+
else
198+
FAILED=$((FAILED + 1))
199+
fi
200+
done
201+
done
202+
203+
printf "\e[1mMatrix run complete\e[0m total: %d \e[32mpassed: %d\e[0m \e[31mfailed: %d\e[0m\n" "$TOTAL" "$PASSED" "$FAILED"
204+
205+
if [ "$FAILED" -gt 0 ]; then
206+
exit 1
207+
fi

0 commit comments

Comments
 (0)