From 09c2b28afae4258378b1d4f32b5cfe7e6c55d263 Mon Sep 17 00:00:00 2001 From: Afonso Oliveira Date: Wed, 23 Sep 2026 11:45:50 +0100 Subject: [PATCH 1/4] Remove standard SSH allow rules when disabling SSH Reported-by: Sean Huber --- bin/omarchy-remove-security-sshd | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bin/omarchy-remove-security-sshd b/bin/omarchy-remove-security-sshd index 3106c89e..0c18b353 100755 --- a/bin/omarchy-remove-security-sshd +++ b/bin/omarchy-remove-security-sshd @@ -1,6 +1,6 @@ #!/bin/bash -# omarchy:summary=Disable the OpenSSH server, close the firewall port, and optionally remove authorized keys +# omarchy:summary=Disable the OpenSSH server, remove standard firewall rules, and optionally remove authorized keys # omarchy:requires-sudo=true set -e @@ -13,9 +13,12 @@ echo "Stopping and disabling the OpenSSH server..." sudo systemctl disable --now sshd.service 2>/dev/null || true if omarchy-cmd-present ufw; then - echo "Closing the SSH port in the firewall..." - sudo ufw --force delete limit 22/tcp >/dev/null 2>&1 || true + echo "Removing standard SSH firewall rules..." + sudo ufw --force delete limit 22/tcp >/dev/null + sudo ufw --force delete allow 22/tcp >/dev/null + sudo ufw --force delete allow ssh >/dev/null sudo ufw reload >/dev/null + echo "Standard SSH firewall rules removed. Review custom firewall rules separately." fi if [[ -s $AUTHORIZED_KEYS ]]; then @@ -28,5 +31,5 @@ if [[ -s $AUTHORIZED_KEYS ]]; then fi fi -echo -e "\e[32m\nThe SSH server has been disabled and its firewall port closed.\e[0m" +echo -e "\e[32m\nThe SSH server has been disabled.\e[0m" echo "The openssh package remains installed since it also provides the ssh client." From 1d0165aeb3c2ca94569ea13fe3924c4504313b78 Mon Sep 17 00:00:00 2001 From: Afonso Oliveira Date: Wed, 23 Sep 2026 11:49:42 +0100 Subject: [PATCH 2/4] Test SSH firewall rule removal and failures --- .../remove-security-sshd-firewall-test.sh | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 test/shell.d/remove-security-sshd-firewall-test.sh diff --git a/test/shell.d/remove-security-sshd-firewall-test.sh b/test/shell.d/remove-security-sshd-firewall-test.sh new file mode 100755 index 00000000..e6a5a681 --- /dev/null +++ b/test/shell.d/remove-security-sshd-firewall-test.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +remove="${REMOVE_SECURITY_SSHD_UNDER_TEST:-$ROOT/bin/omarchy-remove-security-sshd}" +test_dir=$(mktemp -d) +stub_bin="$test_dir/bin" +mkdir -p "$stub_bin" +trap 'rm -rf "$test_dir"' EXIT + +cat >"$stub_bin/omarchy-cmd-present" <<'STUB' +#!/bin/bash +[[ $1 == "ufw" && ${UFW_PRESENT:-1} == 1 ]] +STUB + +cat >"$stub_bin/sudo" <<'STUB' +#!/bin/bash +set -euo pipefail + +printf '%s\n' "$*" >>"${CALL_LOG:?}" +[[ $1 == "systemctl" ]] && exit 0 +[[ $1 == "ufw" ]] || exit 97 +shift + +case "$*" in + "--force delete limit 22/tcp") failure=delete-limit; rule='limit 22/tcp' ;; + "--force delete allow 22/tcp") failure=delete-port; rule='allow 22/tcp' ;; + "--force delete allow ssh") failure=delete-service; rule='allow ssh' ;; + reload) failure=reload ;; + *) exit 97 ;; +esac + +if [[ ${FAIL_UFW:-} == "$failure" ]]; then + printf 'injected ufw failure: %s\n' "$failure" >&2 + exit 1 +fi +[[ $failure == "reload" ]] && exit 0 + +next="${UFW_STATE:?}.next" +: >"$next" +while IFS= read -r current || [[ -n $current ]]; do + [[ $current == "$rule" ]] || printf '%s\n' "$current" >>"$next" +done <"$UFW_STATE" +mv "$next" "$UFW_STATE" +STUB + +cat >"$stub_bin/gum" <<'STUB' +#!/bin/bash +exit 1 +STUB + +chmod +x "$stub_bin"/* + +prepare_case() { + local name="$1" + case_dir="$test_dir/$name" + home="$case_dir/home" + calls="$case_dir/calls" + state="$case_dir/ufw-state" + mkdir -p "$home/.ssh" + printf 'ssh-ed25519 fake-key test@example\n' >"$home/.ssh/authorized_keys" + : >"$calls" + printf '%s\n' 'limit 22/tcp' 'allow 22/tcp' 'allow ssh' 'allow 443/tcp' >"$state" +} + +run_remove() { + local failure="${1:-}" + set +e + output=$(env HOME="$home" PATH="$stub_bin:$ROOT/bin:/usr/bin:/bin" \ + CALL_LOG="$calls" UFW_STATE="$state" UFW_PRESENT="${UFW_PRESENT:-1}" FAIL_UFW="$failure" \ + bash "$remove" 2>&1) + status=$? + set -e +} + +assert_no_success_claim() { + if grep -Eiq 'firewall[^[:cntrl:]]*(closed|removed)|((closed|removed)[^[:cntrl:]]*firewall)' <<<"$output"; then + fail "output does not claim firewall success" "$output" + fi +} + +prepare_case standard +run_remove +(( status == 0 )) || fail "removal succeeds with standard SSH rules" "$output" +[[ $(<"$state") == 'allow 443/tcp' ]] || fail "removal deletes only standard SSH rules" "$(cat "$state")" +[[ -s $home/.ssh/authorized_keys ]] || fail "declined key removal preserves authorized keys" + +run_remove +(( status == 0 )) || fail "repeated removal accepts absent rules" "$output" +[[ $(<"$state") == 'allow 443/tcp' ]] || fail "repeated removal preserves unrelated rules" +pass "removal deletes all standard SSH rules, preserves HTTPS, and is idempotent" + +for failure in delete-limit delete-port delete-service reload; do + prepare_case "$failure" + run_remove "$failure" + (( status != 0 )) || fail "$failure failure makes removal fail" "$output" + grep -Fq "injected ufw failure: $failure" <<<"$output" || + fail "$failure stderr reaches the caller" "$output" + assert_no_success_claim + [[ -s $home/.ssh/authorized_keys ]] || fail "$failure preserves authorized keys" +done +pass "each UFW deletion and reload failure is returned without false success" + +prepare_case no-ufw +UFW_PRESENT=0 run_remove +(( status == 0 )) || fail "removal succeeds without UFW" "$output" +! grep -q '^ufw ' "$calls" || fail "no-UFW removal does not invoke UFW" "$(cat "$calls")" +assert_no_success_claim +[[ -s $home/.ssh/authorized_keys ]] || fail "no-UFW removal preserves authorized keys" +pass "no-UFW removal does not claim firewall success" From 27e1f73e3582fc35419491171e78df0ee8326cec Mon Sep 17 00:00:00 2001 From: Afonso Oliveira Date: Thu, 24 Sep 2026 10:18:44 +0100 Subject: [PATCH 3/4] Remove SSH service limits and application firewall rules --- bin/omarchy-remove-security-sshd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/omarchy-remove-security-sshd b/bin/omarchy-remove-security-sshd index 0c18b353..4bfcc8b0 100755 --- a/bin/omarchy-remove-security-sshd +++ b/bin/omarchy-remove-security-sshd @@ -17,6 +17,9 @@ if omarchy-cmd-present ufw; then sudo ufw --force delete limit 22/tcp >/dev/null sudo ufw --force delete allow 22/tcp >/dev/null sudo ufw --force delete allow ssh >/dev/null + sudo ufw --force delete limit ssh >/dev/null + sudo ufw --force delete allow to any app SSH >/dev/null + sudo ufw --force delete limit to any app SSH >/dev/null sudo ufw reload >/dev/null echo "Standard SSH firewall rules removed. Review custom firewall rules separately." fi From f8ead5cf5f3daa6efa1229418afe445245b9685a Mon Sep 17 00:00:00 2001 From: Afonso Oliveira Date: Thu, 24 Sep 2026 10:18:44 +0100 Subject: [PATCH 4/4] Test SSH service and application firewall cleanup --- test/shell.d/remove-security-sshd-firewall-test.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/shell.d/remove-security-sshd-firewall-test.sh b/test/shell.d/remove-security-sshd-firewall-test.sh index e6a5a681..31a85bd7 100755 --- a/test/shell.d/remove-security-sshd-firewall-test.sh +++ b/test/shell.d/remove-security-sshd-firewall-test.sh @@ -28,6 +28,9 @@ case "$*" in "--force delete limit 22/tcp") failure=delete-limit; rule='limit 22/tcp' ;; "--force delete allow 22/tcp") failure=delete-port; rule='allow 22/tcp' ;; "--force delete allow ssh") failure=delete-service; rule='allow ssh' ;; + "--force delete limit ssh") failure=delete-service-limit; rule='limit ssh' ;; + "--force delete allow to any app SSH") failure=delete-app-allow; rule='allow app SSH' ;; + "--force delete limit to any app SSH") failure=delete-app-limit; rule='limit app SSH' ;; reload) failure=reload ;; *) exit 97 ;; esac @@ -62,7 +65,7 @@ prepare_case() { mkdir -p "$home/.ssh" printf 'ssh-ed25519 fake-key test@example\n' >"$home/.ssh/authorized_keys" : >"$calls" - printf '%s\n' 'limit 22/tcp' 'allow 22/tcp' 'allow ssh' 'allow 443/tcp' >"$state" + printf '%s\n' 'limit 22/tcp' 'allow 22/tcp' 'allow ssh' 'limit ssh' 'allow app SSH' 'limit app SSH' 'allow 443/tcp' >"$state" } run_remove() { @@ -92,7 +95,7 @@ run_remove [[ $(<"$state") == 'allow 443/tcp' ]] || fail "repeated removal preserves unrelated rules" pass "removal deletes all standard SSH rules, preserves HTTPS, and is idempotent" -for failure in delete-limit delete-port delete-service reload; do +for failure in delete-limit delete-port delete-service delete-service-limit delete-app-allow delete-app-limit reload; do prepare_case "$failure" run_remove "$failure" (( status != 0 )) || fail "$failure failure makes removal fail" "$output"