diff --git a/.github/workflows/bash-syntax.yml b/.github/workflows/bash-syntax.yml index 222bc1b..b1f8133 100644 --- a/.github/workflows/bash-syntax.yml +++ b/.github/workflows/bash-syntax.yml @@ -25,3 +25,6 @@ jobs: - name: Test step 1 selections run: bash tests/test-step1-selection.sh + + - name: Test step 3 SSH configuration parsing + run: bash tests/test-step3-sshd-parsing.sh diff --git a/ochenstarik-server-user-3.sh b/ochenstarik-server-user-3.sh index ef1caa4..614a8f7 100644 --- a/ochenstarik-server-user-3.sh +++ b/ochenstarik-server-user-3.sh @@ -60,6 +60,13 @@ require_command() { command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1" } +read_sshd_setting() { + local setting="$1" config="$2" value + value="$(awk -v setting="$setting" '$1 == setting { print $2; exit }' <<< "$config")" + [[ -n "$value" ]] || die "SSH setting was not found in effective configuration: $setting" + printf '%s' "$value" +} + is_valid_port() { local port="$1" [[ "$port" =~ ^[0-9]{1,5}$ ]] || return 1 @@ -465,9 +472,14 @@ EOF chmod 644 "$SSHD_DROPIN" sshd -t || die "sshd syntax validation failed" -effective_port="$(sshd -T | awk '$1 == "port" { print $2; exit }')" -effective_root="$(sshd -T | awk '$1 == "permitrootlogin" { print $2; exit }')" -effective_password="$(sshd -T | awk '$1 == "passwordauthentication" { print $2; exit }')" +# Capture the complete output once. With pipefail enabled, piping `sshd -T` +# into an awk program that exits after the first match can terminate sshd with +# SIGPIPE (status 141) and abort the script even though the configuration is +# valid. +effective_sshd_config="$(sshd -T)" || die "Could not read effective SSH configuration" +effective_port="$(read_sshd_setting port "$effective_sshd_config")" +effective_root="$(read_sshd_setting permitrootlogin "$effective_sshd_config")" +effective_password="$(read_sshd_setting passwordauthentication "$effective_sshd_config")" [[ "$effective_port" == "$SSH_PORT" ]] || die "Effective SSH port is $effective_port, expected $SSH_PORT" [[ "$effective_root" == no ]] || die "Effective PermitRootLogin is $effective_root, expected no" [[ "$effective_password" == "$PASSWORD_AUTH" ]] || die "Effective PasswordAuthentication is $effective_password, expected $PASSWORD_AUTH" diff --git a/tests/test-step3-sshd-parsing.sh b/tests/test-step3-sshd-parsing.sh new file mode 100644 index 0000000..391ea08 --- /dev/null +++ b/tests/test-step3-sshd-parsing.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." +source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-user-3.sh) + +effective_config=$'port 20202\npermitrootlogin no\npasswordauthentication no\npubkeyauthentication yes' + +[[ "$(read_sshd_setting port "$effective_config")" == 20202 ]] +[[ "$(read_sshd_setting permitrootlogin "$effective_config")" == no ]] +[[ "$(read_sshd_setting passwordauthentication "$effective_config")" == no ]] + +if grep -E 'sshd[[:space:]]+-T[[:space:]]*\|' ochenstarik-server-user-3.sh; then + printf 'Unsafe sshd -T pipeline found.\n' >&2 + exit 1 +fi + +printf 'Step 3 SSH configuration parsing tests passed.\n'