295 lines
11 KiB
Bash
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'
|