diff --git a/scripts/nym-node-setup/network-tunnel-manager.sh b/scripts/nym-node-setup/network-tunnel-manager.sh index 07be3eb723..581f9ec0e5 100644 --- a/scripts/nym-node-setup/network-tunnel-manager.sh +++ b/scripts/nym-node-setup/network-tunnel-manager.sh @@ -1,5 +1,5 @@ #!/bin/bash -# nym tunnel and wireguard exit policy manager +# nym host network firewall, tunnel and wireguard exit policy manager # run this script as root set -euo pipefail @@ -556,6 +556,34 @@ apply_smtps_465_rate_limit() { ############################################################################### NETWORK_FIREWALL_COMMENT="NYM-NETWORK-FW" +HOST_SSH_PORT="${HOST_SSH_PORT:-22}" +NETWORK_FIREWALL_TCP_PORTS=("$HOST_SSH_PORT" 80 443 1789 1790 8080 9000 9001 41264) +NETWORK_FIREWALL_UDP_PORTS=(4443 51822 51264) +NETWORK_FIREWALL_WG_TCP_PORT=51830 + +validate_port_value() { + local name="$1" + local value="$2" + + if ! [[ "$value" =~ ^[0-9]+$ ]] || (( 10#$value < 1 || 10#$value > 65535 )); then + error "invalid ${name}='${value}'. expected integer 1-65535" + exit 1 + fi +} + +validate_port_value "HOST_SSH_PORT" "$HOST_SSH_PORT" + +build_network_firewall_status_pattern() { + local patterns=("$NETWORK_FIREWALL_COMMENT") + local port + + for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}" "${NETWORK_FIREWALL_UDP_PORTS[@]}" "$NETWORK_FIREWALL_WG_TCP_PORT"; do + patterns+=("dpt:${port}") + done + + local IFS='|' + printf '%s' "${patterns[*]}" +} delete_managed_input_rules() { local cmd="$1" @@ -593,22 +621,19 @@ configure_network_firewall() { delete_managed_input_rules iptables delete_managed_input_rules ip6tables - local tcp_ports=(22 80 443 1789 1790 8080 9000 9001 41264) - local udp_ports=(4443 51822 51264) - local port - for port in "${tcp_ports[@]}"; do + for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}"; do add_input_port_rule iptables "$port" tcp add_input_port_rule ip6tables "$port" tcp done - for port in "${udp_ports[@]}"; do + for port in "${NETWORK_FIREWALL_UDP_PORTS[@]}"; do add_input_port_rule iptables "$port" udp add_input_port_rule ip6tables "$port" udp done - add_input_port_rule iptables 51830 tcp "$WG_INTERFACE" - add_input_port_rule ip6tables 51830 tcp "$WG_INTERFACE" + add_input_port_rule iptables "$NETWORK_FIREWALL_WG_TCP_PORT" tcp "$WG_INTERFACE" + add_input_port_rule ip6tables "$NETWORK_FIREWALL_WG_TCP_PORT" tcp "$WG_INTERFACE" save_iptables_rules ok "host network firewall configuration completed" @@ -616,23 +641,25 @@ configure_network_firewall() { show_network_firewall_status() { info "managed host network firewall rules" + local status_pattern + + status_pattern="$(build_network_firewall_status_pattern)" + echo info "ipv4 input rules:" - iptables -L INPUT -n -v --line-numbers | grep -E "($NETWORK_FIREWALL_COMMENT|dpt:22|dpt:80|dpt:443|dpt:1789|dpt:1790|dpt:8080|dpt:9000|dpt:9001|dpt:51822|dpt:51830|dpt:41264|dpt:51264)" || true + iptables -L INPUT -n -v --line-numbers | grep -E "(${status_pattern})" || true echo info "ipv6 input rules:" - ip6tables -L INPUT -n -v --line-numbers | grep -E "($NETWORK_FIREWALL_COMMENT|dpt:22|dpt:80|dpt:443|dpt:1789|dpt:1790|dpt:8080|dpt:9000|dpt:9001|dpt:51822|dpt:51830|dpt:41264|dpt:51264)" || true + ip6tables -L INPUT -n -v --line-numbers | grep -E "(${status_pattern})" || true } test_network_firewall_rules() { info "testing host network firewall rules" local failures=0 - local tcp_ports=(22 80 443 1789 1790 8080 9000 9001 41264) - local udp_ports=(4443 51822 51264) local port - for port in "${tcp_ports[@]}"; do + for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}"; do if iptables -C INPUT -p tcp --dport "$port" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then ok "ipv4 tcp port $port allowed" else @@ -648,7 +675,7 @@ test_network_firewall_rules() { fi done - for port in "${udp_ports[@]}"; do + for port in "${NETWORK_FIREWALL_UDP_PORTS[@]}"; do if iptables -C INPUT -p udp --dport "$port" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then ok "ipv4 udp port $port allowed" else @@ -664,17 +691,17 @@ test_network_firewall_rules() { fi done - if iptables -C INPUT -i "$WG_INTERFACE" -p tcp --dport 51830 -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then - ok "ipv4 tcp port 51830 allowed on $WG_INTERFACE" + if iptables -C INPUT -i "$WG_INTERFACE" -p tcp --dport "$NETWORK_FIREWALL_WG_TCP_PORT" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then + ok "ipv4 tcp port $NETWORK_FIREWALL_WG_TCP_PORT allowed on $WG_INTERFACE" else - error "ipv4 tcp port 51830 missing on $WG_INTERFACE" + error "ipv4 tcp port $NETWORK_FIREWALL_WG_TCP_PORT missing on $WG_INTERFACE" ((failures++)) fi - if ip6tables -C INPUT -i "$WG_INTERFACE" -p tcp --dport 51830 -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then - ok "ipv6 tcp port 51830 allowed on $WG_INTERFACE" + if ip6tables -C INPUT -i "$WG_INTERFACE" -p tcp --dport "$NETWORK_FIREWALL_WG_TCP_PORT" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then + ok "ipv6 tcp port $NETWORK_FIREWALL_WG_TCP_PORT allowed on $WG_INTERFACE" else - error "ipv6 tcp port 51830 missing on $WG_INTERFACE" + error "ipv6 tcp port $NETWORK_FIREWALL_WG_TCP_PORT missing on $WG_INTERFACE" ((failures++)) fi @@ -1695,6 +1722,7 @@ environment overrides: NETWORK_DEVICE_V6 Auto-detected IPv6 uplink (e.g., eth2). Optional; if unset, IPv6-specific setup is skipped. TUNNEL_INTERFACE Default: nymtun0. Requires root privileges (sudo) to manage. WG_INTERFACE Default: nymwg - Must match your WireGuard interface name. + HOST_SSH_PORT Default: 22. Set manually if you connect to your host's SSH daemon through another port. EOF status=0 diff --git a/scripts/nym-node-setup/nym-node-cli.py b/scripts/nym-node-setup/nym-node-cli.py index 9d99fc4ad0..cc56b99048 100755 --- a/scripts/nym-node-setup/nym-node-cli.py +++ b/scripts/nym-node-setup/nym-node-cli.py @@ -68,23 +68,60 @@ class NodeSetupCLI: print("Without confirming the points above, we cannot continue.") exit(1) + def _coerce_ssh_port(self, value) -> str: + sval = str(value).strip() if value is not None else "" + if not sval: + sval = "22" + if not sval.isdigit(): + print(f"Invalid SSH port: {sval!r}. Expected integer 1..65535.") + raise SystemExit(1) + port = int(sval, 10) + if not 1 <= port <= 65535: + print(f"Invalid SSH port: {port}. Expected integer 1..65535.") + raise SystemExit(1) + return str(port) + + def _resolve_field(args, existing, arg_name, env_key, prompt, *, default=None, validator=None): + cli_val = getattr(args, arg_name, None) + + if cli_val is not None: + value = str(cli_val).strip() + elif existing.get(env_key): + value = str(existing[env_key]).strip() + else: + entered = input(prompt).strip() + value = entered if entered else (default if default is not None else "") + + if validator: + value = validator(value) + + return value + def ensure_env_values(self, args): """Collect env vars from args or prompt interactively, then save to env.sh.""" env_file = Path("env.sh") fields = [ - ("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): "), - ("location", "LOCATION", "Enter node location (country code or name): "), - ("email", "EMAIL", "Enter your email: "), - ("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): "), - ("description", "DESCRIPTION", "Enter short node public description: "), + ("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): ", None, None), + ("location", "LOCATION", "Enter node location (country code or name): ", None, None), + ("email", "EMAIL", "Enter your email: ", None, None), + ("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): ", None, None), + ("description", "DESCRIPTION", "Enter short node public description: ", None, None), + ("host_ssh_port", "HOST_SSH_PORT", "Enter host SSH port (press enter for default port 22): ", "22", self._coerce_ssh_port), ] existing = self._read_env_file(env_file) updated = {} - for arg_name, key, prompt in fields: - cli_val = getattr(args, arg_name, None) - value = cli_val.strip() if cli_val else existing.get(key) or input(prompt).strip() + for arg_name, key, prompt, default, validator in fields: + value = self._resolve_field( + args, + existing, + arg_name, + key, + prompt, + default=default, + validator=validator, + ) updated[key] = value os.environ[key] = value @@ -639,6 +676,13 @@ class ArgParser: install_parser.add_argument("--moniker", help="Public moniker displayed in explorer & NymVPN app") install_parser.add_argument("--description", help="Short public description of the node") install_parser.add_argument("--public-ip", help="External IPv4 address (autodetected if omitted)") + + install_parser.add_argument( + "--host-ssh-port", + type=int, + help="Host SSH port to allow in the firewall (default: 22)", + ) + install_parser.add_argument("--nym-node-binary", help="URL for nym-node binary (autodetected if omitted)") install_parser.add_argument("--uplink-dev", help="Override uplink interface used for NAT/FORWARD (e.g., 'eth0'; autodetected if omitted)") diff --git a/scripts/nym-node-setup/setup-env-vars.sh b/scripts/nym-node-setup/setup-env-vars.sh index e119a61fd6..05dd9f6156 100644 --- a/scripts/nym-node-setup/setup-env-vars.sh +++ b/scripts/nym-node-setup/setup-env-vars.sh @@ -19,6 +19,8 @@ while true; do read -rp "Enter your email: " EMAIL read -rp "Enter node public moniker (visible in the explorer and NymVPN app): " MONIKER read -rp "Enter node public description: " DESCRIPTION + read -rp "Enter host SSH port (press enter for default port 22): " HOST_SSH_PORT + HOST_SSH_PORT="${HOST_SSH_PORT:-22}" # show summary table echo -e "\nPlease confirm the values you entered:" @@ -28,6 +30,7 @@ while true; do printf "%-20s %s\n" "EMAIL:" "$EMAIL" printf "%-20s %s\n" "MONIKER:" "$MONIKER" printf "%-20s %s\n" "DESCRIPTION:" "$DESCRIPTION" + printf "%-20s %s\n" "HOST_SSH_PORT:" "$HOST_SSH_PORT" echo "---------------------------------------" read -rp "Are these correct? (y/n): " CONFIRM @@ -52,6 +55,7 @@ PUBLIC_IP=${PUBLIC_IP:-""} echo "export MONIKER=\"${MONIKER}\"" echo "export DESCRIPTION=\"${DESCRIPTION}\"" echo "export PUBLIC_IP=\"${PUBLIC_IP}\"" + echo "export HOST_SSH_PORT=\"${HOST_SSH_PORT}\"" } > env.sh echo -e "\nVariables saved to ./env.sh"