lightweight-server/tests/test-firewall-panel-security.sh
ochenstarik-ui 9f06756cef
Some checks failed
Bash syntax / syntax (push) Failing after 11s
Bash syntax / syntax (pull_request) Failing after 2s
fix: close installer exposure and pin verified payloads
2026-10-23 12:33:19 +07:00

295 lines
11 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
bash <<'STEP2_TEST'
set -Eeuo pipefail
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-2.sh)
SSH_PORT=20202
SSH_CONNECTION='198.51.100.9 41000 192.0.2.1 22'
MANAGED_RULES=()
declare -A RULE_SEEN=()
add_default_rules
[[ " ${MANAGED_RULES[*]} " == *' 20202/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " == *' 22/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " == *' 443/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 80/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 2096/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 40000/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 63636/tcp '* ]]
MANAGED_RULES=(22/tcp 20202/tcp 80/tcp 2096/tcp 443/tcp 40000/tcp 63636/tcp)
PREVIOUS_MANAGED_RULES=("${MANAGED_RULES[@]}")
RULE_SEEN=()
confirm_legacy_rules <<< 'n'
[[ " ${MANAGED_RULES[*]} " == *' 22/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " == *' 443/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 80/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 2096/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 40000/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " != *' 63636/tcp '* ]]
legacy_delete_log="$(mktemp)"
ufw() {
if [[ "${1:-}" == status ]]; then
printf '%s\n' "${UFW_TEST_STATUS:-$'Status: active\nTo Action From'}"
return 0
fi
printf '%s\n' "$*" >> "$legacy_delete_log"
return 1
}
UFW_TEST_STATUS=$'Status: active\nTo Action From'
delete_managed_ufw_rules
for legacy_rule in 80/tcp 2096/tcp 40000/tcp 63636/tcp; do
grep -Fq -- "--force delete allow ${legacy_rule}" "$legacy_delete_log"
done
UFW_TEST_STATUS=$'Status: active\nTo Action From\n80/tcp (v6) ALLOW IN Anywhere (v6)'
if (delete_managed_ufw_rules) >/dev/null 2>&1; then
printf 'Step 2 accepted a remaining IPv6 legacy UFW allow rule\n' >&2
exit 1
fi
rm -f -- "$legacy_delete_log"
MANAGED_RULES=(80/tcp 443/tcp)
PREVIOUS_MANAGED_RULES=("${MANAGED_RULES[@]}")
RULE_SEEN=()
confirm_legacy_rules <<< 'y'
[[ " ${MANAGED_RULES[*]} " == *' 80/tcp '* ]]
[[ " ${MANAGED_RULES[*]} " == *' 443/tcp '* ]]
SSH_CONNECTION='198.51.100.9 41000 192.0.2.1 20202'
MANAGED_RULES=(20202/tcp 443/tcp)
RULE_SEEN=()
confirm_legacy_rules <<< 'n'
add_default_rules
[[ " ${MANAGED_RULES[*]} " != *' 22/tcp '* ]]
# sudo commonly clears SSH_CONNECTION. A first run should still retain the
# conventional SSH port if no sshd socket can be discovered, while a later
# run must not resurrect 22 after the managed list records its retirement.
unset SSH_CONNECTION
ss() { :; }
ssh_probe_dir="$(mktemp -d)"
trap 'rm -rf -- "$ssh_probe_dir"' EXIT
mapfile -t current_ssh_ports < <(discover_current_ssh_ports "$ssh_probe_dir/missing-managed-file")
[[ " ${current_ssh_ports[*]} " == *' 22 '* ]]
touch "$ssh_probe_dir/retired-managed-file"
mapfile -t current_ssh_ports < <(discover_current_ssh_ports "$ssh_probe_dir/retired-managed-file")
[[ " ${current_ssh_ports[*]} " != *' 22 '* ]]
STEP2_TEST
bash <<'PANEL_TEST'
set -Eeuo pipefail
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-panel-warp-6.sh)
OPENSSL_FAKE_VALUE=0123456789abcdef0123456789abcdef0123456789abcdef
openssl() { printf '%s' "$OPENSSL_FAKE_VALUE"; }
XUI_EXISTING_SUB_PATH=''
choose_public_access <<< ''
[[ "$XUI_PUBLIC_ACCESS" == no && "$XUI_WEB_LISTEN" == 127.0.0.1 ]]
choose_subscription_access <<< ''
[[ "$XUI_SUBSCRIPTION_ACCESS" == private && "$XUI_SUB_LISTEN" == 127.0.0.1 ]]
choose_subscription_access <<< '2'
[[ "$XUI_SUBSCRIPTION_ACCESS" == disabled ]]
choose_subscription_access <<< '3'
[[ "$XUI_SUBSCRIPTION_ACCESS" == public ]]
[[ "$XUI_SUB_LISTEN" == '' ]]
[[ "$XUI_SUB_PATH" =~ ^/[0-9a-f]{48}/$ ]]
subscription_db_probe="$(mktemp)"
SQLITE_TEST_SUB_PATH='/operator-custom/path/'
sqlite3() { printf '%s' "$SQLITE_TEST_SUB_PATH"; }
load_existing_subscription_path "$subscription_db_probe"
[[ "$XUI_EXISTING_SUB_PATH" == '/operator-custom/path/' ]]
choose_subscription_access <<< '1'
[[ "$XUI_SUB_PATH" == "$XUI_EXISTING_SUB_PATH" ]]
choose_subscription_access <<< '2'
[[ "$XUI_SUB_PATH" == "$XUI_EXISTING_SUB_PATH" ]]
XUI_EXISTING_SUB_PATH='/0123456789abcdef0123456789abcdef0123456789abcdef/'
choose_subscription_access <<< $'3\n'
[[ "$XUI_SUBSCRIPTION_ACCESS" == public && "$XUI_SUB_PATH" == "$XUI_EXISTING_SUB_PATH" ]]
OPENSSL_FAKE_VALUE=abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef
choose_subscription_access <<< $'3\ny'
[[ "$XUI_SUBSCRIPTION_ACCESS" == public && "$XUI_SUB_PATH" == '/abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef/' ]]
XUI_EXISTING_SUB_PATH='/operator-custom/path/'
choose_subscription_access <<< $'3\n1'
[[ "$XUI_SUBSCRIPTION_ACCESS" == private && "$XUI_SUB_PATH" == "$XUI_EXISTING_SUB_PATH" ]]
OPENSSL_FAKE_VALUE=abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef
choose_subscription_access <<< $'3\ny'
[[ "$XUI_SUBSCRIPTION_ACCESS" == public && "$XUI_SUB_PATH" == '/abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef/' ]]
rm -f -- "$subscription_db_probe"
XUI_EXISTING_SUB_PATH=''
choose_public_access <<< '1'
choose_subscription_access <<< '1'
set_xui_desired_listeners
[[ "$XUI_DESIRED_WEB_LISTEN" == 127.0.0.1 ]]
[[ "$XUI_DESIRED_SUB_LISTEN" == 127.0.0.1 ]]
[[ "$XUI_DESIRED_SUB_ENABLED" == true ]]
choose_public_access <<< '2'
choose_subscription_access <<< '3'
set_xui_desired_listeners
[[ "$XUI_DESIRED_WEB_LISTEN" == '' ]]
[[ "$XUI_DESIRED_SUB_LISTEN" == '' ]]
[[ "$XUI_DESIRED_SUB_ENABLED" == true ]]
choose_subscription_access <<< '2'
set_xui_desired_listeners
[[ "$XUI_DESIRED_SUB_LISTEN" == 127.0.0.1 ]]
[[ "$XUI_DESIRED_SUB_ENABLED" == false ]]
ss() {
case "${SS_MODE:-private}" in
private) printf 'LISTEN 0 4096 127.0.0.1:%s 0.0.0.0:*\n' "$SS_PORT" ;;
delayed)
local calls
calls="$(cat "$SS_CALLS_FILE")"
calls=$((calls + 1))
printf '%s\n' "$calls" > "$SS_CALLS_FILE"
((calls > 2)) && printf 'LISTEN 0 4096 127.0.0.1:%s 0.0.0.0:*\n' "$SS_PORT"
;;
wildcard) printf 'LISTEN 0 4096 0.0.0.0:%s 0.0.0.0:*\n' "$SS_PORT" ;;
ipv6) printf 'LISTEN 0 4096 [::]:%s [::]:*\n' "$SS_PORT" ;;
esac
}
SS_PORT=2053
SS_MODE=delayed
SS_CALLS_FILE="$(mktemp)"
printf '0\n' > "$SS_CALLS_FILE"
sleep() { :; }
wait_for_tcp_listener 2053 panel
[[ "$(cat "$SS_CALLS_FILE")" -eq 3 ]]
verify_private_listener 2053 panel
rm -f -- "$SS_CALLS_FILE"
SS_MODE=private
for SS_MODE in wildcard ipv6; do
if (verify_private_listener 2053 panel) >/dev/null 2>&1; then
printf 'Private listener check accepted %s bind\n' "$SS_MODE" >&2
exit 1
fi
done
guard_test_dir="$(mktemp -d)"
trap 'rm -rf -- "$guard_test_dir"' EXIT
cat > "$guard_test_dir/fake-x-ui" <<'FAKE_XUI'
#!/usr/bin/env bash
printf 'bind:%s\n' "$*" >> "$GUARD_LOG"
[[ "${XUI_FAKE_FAIL:-no}" != yes ]]
FAKE_XUI
cat > "$guard_test_dir/real-systemctl" <<'REAL_SYSTEMCTL'
#!/usr/bin/env bash
printf 'systemctl:%s\n' "$*" >> "$GUARD_LOG"
REAL_SYSTEMCTL
cat > "$guard_test_dir/real-rc-service" <<'REAL_RC_SERVICE'
#!/usr/bin/env bash
printf 'rc-service:%s\n' "$*" >> "$GUARD_LOG"
REAL_RC_SERVICE
cat > "$guard_test_dir/fake-sqlite3" <<'FAKE_SQLITE3'
#!/usr/bin/env bash
if [[ "$#" == 1 ]]; then
cat >/dev/null
printf 'sqlite-write\n' >> "$GUARD_LOG"
[[ "${SQLITE_FAKE_FAIL:-no}" != yes ]]
else
printf 'sqlite-query\n' >> "$GUARD_LOG"
printf '127.0.0.1|false\n'
fi
FAKE_SQLITE3
touch "$guard_test_dir/fake.db"
chmod 700 "$guard_test_dir/fake-x-ui" "$guard_test_dir/real-systemctl" \
"$guard_test_dir/real-rc-service" "$guard_test_dir/fake-sqlite3"
export GUARD_LOG="$guard_test_dir/calls.log"
create_xui_start_guard "$guard_test_dir/guard" "$guard_test_dir/released" \
"$guard_test_dir/fake-x-ui" "$guard_test_dir/fake.db" \
"$guard_test_dir/real-systemctl" "$guard_test_dir/real-rc-service" "$guard_test_dir/fake-sqlite3"
export PATH="${XUI_GUARD_DIR}:${PATH}"
systemctl start x-ui.service
[[ "$(sed -n '1p' "$GUARD_LOG")" == 'bind:setting -listenIP 127.0.0.1' ]]
[[ "$(sed -n '2p' "$GUARD_LOG")" == 'sqlite-write' ]]
[[ "$(tail -n1 "$GUARD_LOG")" == 'systemctl:start x-ui.service' ]]
: > "$GUARD_LOG"
export XUI_FAKE_FAIL=yes
if systemctl start x-ui; then
printf 'Guard allowed an x-ui start after bind failure\n' >&2
exit 1
fi
! grep -q '^systemctl:' "$GUARD_LOG"
unset XUI_FAKE_FAIL
: > "$GUARD_LOG"
export SQLITE_FAKE_FAIL=yes
if systemctl start x-ui; then
printf 'Guard allowed an x-ui start after safe subscription settings failed\n' >&2
exit 1
fi
! grep -q '^systemctl:' "$GUARD_LOG"
: > "$GUARD_LOG"
unset SQLITE_FAKE_FAIL
: > "$GUARD_LOG"
touch "$guard_test_dir/released"
systemctl start x-ui
[[ "$(wc -l < "$GUARD_LOG")" -eq 1 ]]
[[ "$(sed -n '1p' "$GUARD_LOG")" == 'systemctl:start x-ui' ]]
PANEL_TEST
bash <<'STEP3_TEST'
set -Eeuo pipefail
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-user-3.sh)
tmp_dir="$(mktemp -d)"
trap 'rm -rf -- "$tmp_dir"' EXIT
MANAGE_UFW=yes
SSH_PORT=20202
UFW_TEST_STATUS='Status: active'
ufw() {
if [[ "${1:-}" == status ]]; then
printf '%s\n' "$UFW_TEST_STATUS"
return 0
fi
printf '%s\n' "$*" >> "$tmp_dir/ufw-calls"
return 0
}
require_command() { :; }
chown() { :; }
printf '22/tcp\n20202/tcp\n443/tcp\n' > "$tmp_dir/managed"
retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'n'
grep -Fqx '22/tcp' "$tmp_dir/managed"
retire_temporary_ssh_port_22 "$tmp_dir/managed" </dev/null
grep -Fqx '22/tcp' "$tmp_dir/managed"
retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'y'
! grep -Fqx '22/tcp' "$tmp_dir/managed"
grep -Fqx '20202/tcp' "$tmp_dir/managed"
grep -Fq 'from 0.0.0.0/0 to any port 22 proto tcp' "$tmp_dir/ufw-calls"
grep -Fq 'from ::/0 to any port 22 proto tcp' "$tmp_dir/ufw-calls"
printf '22/tcp\n' > "$tmp_dir/managed"
MANAGE_UFW=no
retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'y'
grep -Fqx '22/tcp' "$tmp_dir/managed"
MANAGE_UFW=yes
SSH_PORT=22
retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'y'
grep -Fqx '22/tcp' "$tmp_dir/managed"
UFW_TEST_STATUS=$'Status: active\nTo Action From\n22/tcp ALLOW IN Anywhere'
SSH_PORT=20202
if (retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'y') >/dev/null 2>&1; then
printf 'Step 3 reported success while UFW still allows port 22\n' >&2
exit 1
fi
grep -Fqx '22/tcp' "$tmp_dir/managed"
UFW_TEST_STATUS=$'Status: active\nTo Action From\n22/tcp (v6) ALLOW IN Anywhere (v6)'
if (retire_temporary_ssh_port_22 "$tmp_dir/managed" <<< 'y') >/dev/null 2>&1; then
printf 'Step 3 reported success while UFW still allows IPv6 port 22\n' >&2
exit 1
fi
grep -Fqx '22/tcp' "$tmp_dir/managed"
STEP3_TEST
if grep -q 'allow_ufw_rule "63636/tcp"' ochenstarik-server-user-3.sh; then
printf 'Step 3 must not open an unsolicited 63636/tcp rule\n' >&2
exit 1
fi
if grep -q 'allow_ufw_port 80 ' ochenstarik-server-panel-warp-6.sh; then
printf 'Step 6 must not open an unsolicited 80/tcp rule\n' >&2
exit 1
fi
grep -q 'XUI_PUBLIC_ACCESS=no' ochenstarik-server-panel-warp-6.sh
grep -q 'Приватный режим' ochenstarik-server-panel-warp-6.sh
grep -q 'WARP_ANSWER=no' ochenstarik-server-panel-warp-6.sh
printf '[+] Firewall and panel security behavior passed\n'