使用ssh隧道,让公网服务器访问本地服务 本文共有10118个字,关键词: 你想要实现的是通过 SSH 隧道,让公网服务器 abc.com 能够访问你本机上的 1001-1005 端口,在服务器上创建仅能密钥认证、无法执行命令的 tunnel_user 用户,并编写可注册为系统服务的脚本完成隧道配置。 #### 1. 首先在 abc.com 服务器上执行以下操作: ``` # 1. 创建普通用户 tunnel_user(无登录shell,无家目录) sudo useradd -m -s /usr/sbin/nologin tunnel_user #已经创建 #sudo usermod -s /usr/sbin/nologin tunnel_user #改为正常登录 #sudo usermod -s /bin/bash tunnel_user # 2. 创建 .ssh 目录并设置权限 sudo mkdir -p /home/tunnel_user/.ssh sudo chown tunnel_user:tunnel_user /home/tunnel_user/.ssh sudo chmod 700 /home/tunnel_user/.ssh # 3. 生成密钥对(本地机器执行,生成后将公钥上传到服务器) # 本地机器执行此命令(不要设置密码) ssh-keygen -t ed25519 -f ~/.ssh/tunnel_key -N "" # 4. 将本地公钥复制到服务器 tunnel_user 的 authorized_keys # 本地执行(替换为你的服务器地址) cat ~/.ssh/tunnel_key.pub | ssh root@abc.com "sudo tee -a /home/tunnel_user/.ssh/authorized_keys" # 5. 设置 authorized_keys 权限(服务器执行) sudo chown tunnel_user:tunnel_user /home/tunnel_user/.ssh/authorized_keys sudo chmod 600 /home/tunnel_user/.ssh/authorized_keys # 6. 限制 tunnel_user 仅能做端口转发,禁止执行命令 # 编辑服务器的 authorized_keys 文件,在公钥前添加限制 sudo vi /home/tunnel_user/.ssh/authorized_keys # 在公钥开头添加:no-pty,no-agent-forwarding,no-X11-forwarding,command="echo 'This account is for port forwarding only'" # 最终格式示例: # no-pty,no-agent-forwarding,no-X11-forwarding,command="echo 'This account is for port forwarding only'" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxx tunnel_key # 7. 重启 sshd 服务 sudo systemctl restart sshd # 重新加载 sudo systemctl reload ssh ``` #### 2. 关键配置说明 no-pty:禁止分配伪终端,无法执行交互式命令 no-agent-forwarding:禁止代理转发,提升安全性 no-X11-forwarding:禁止 X11 转发 command="...":强制执行指定命令,覆盖用户的任何命令输入 #### 3. 可选配置 编辑SSH配置 ``` sudo tee -a /etc/ssh/sshd_config << 'EOF' # 仅允许tunnel_user使用密钥登录,并进行端口转发 Match User tunnel_user # 认证方式 AuthenticationMethods publickey PubkeyAuthentication yes PasswordAuthentication no ChallengeResponseAuthentication no KbdInteractiveAuthentication no # 端口转发设置 AllowTcpForwarding yes # 允许转发到任何地址 PermitOpen any # 允许绑定到0.0.0.0(远程转发) GatewayPorts yes # 安全限制 # 禁止分配TTY(不能执行命令) PermitTTY no # 禁用X11转发 X11Forwarding no # 禁用代理转发 AllowAgentForwarding no # 禁用VPN式隧道 PermitTunnel no # 禁止执行~/.ssh/rc文件 PermitUserRC no # 会话限制(可选) # 服务器每300秒向客户端发心跳 # ClientAliveInterval 300 # 10次心跳失败则断开 # ClientAliveCountMax 2 # 增加会话数限制 MaxSessions 10 EOF # 3. 重新加载SSH(不重启) sudo systemctl reload ssh ``` #### 4. 客户端脚本 ``` #!/bin/bash # SSH隧道服务脚本(自定义端口映射版 + 测试命令) # 配置项 TUNNEL_USER="tunnel_user" SERVER="abc.com" SERVER_SSH_PORT="22" LOCAL_IP="127.0.0.1" # 本地回环地址 # 核心:自定义端口映射表(格式:本地端口:服务器端口),可自由增删修改 PORT_MAPPINGS=( "1001:3301" # 本地1001 → 服务器3301 "1002:3302" # 本地1002 → 服务器3302 "1003:3303" # 本地1003 → 服务器3303 "1004:3304" # 本地1004 → 服务器3304 "1005:3305" # 本地1005 → 服务器3305 # 可添加更多映射,例如 "1006:3306" ) KEY_FILE="$HOME/.ssh/tunnel_key" LOG_FILE="/var/log/ssh_tunnel.log" PID_FILE="/var/run/ssh_tunnel.pid" # 日志函数 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE } # 检查密钥文件 check_key() { if [ ! -f "$KEY_FILE" ]; then log "错误:密钥文件 $KEY_FILE 不存在" echo "错误:密钥文件 $KEY_FILE 不存在" exit 1 fi } # 验证端口映射格式并构建转发参数 build_forward_args() { local args="" for mapping in "${PORT_MAPPINGS[@]}"; do # 拆分本地端口和服务器端口 IFS=':' read -r local_port remote_port <<< "$mapping" # 验证端口格式(数字且1-65535) if ! [[ "$local_port" =~ ^[0-9]+$ && "$remote_port" =~ ^[0-9]+$ && "$local_port" -ge 1 && "$local_port" -le 65535 && "$remote_port" -ge 1 && "$remote_port" -le 65535 ]]; then log "错误:端口映射 $mapping 格式无效,必须是 数字:数字(1-65535)" echo "错误:端口映射 $mapping 格式无效!" exit 1 fi # 构建-R参数(远程端口转发:服务器端口 → 本地端口) args+=" -R $remote_port:$LOCAL_IP:$local_port" done echo "$args" } # 构建完整的测试命令(test功能核心) build_full_command() { check_key local forward_args=$(build_forward_args) # 拼接完整的SSH命令(去掉后台运行&,方便手动测试) local full_cmd="ssh -i \"$KEY_FILE\" \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=no \ -o PasswordAuthentication=no \ -N $forward_args \ -p $SERVER_SSH_PORT \ $TUNNEL_USER@$SERVER" echo "===== 完整的SSH隧道执行命令 =====" echo $full_cmd echo -e "\n===== 端口映射清单 =====" for mapping in "${PORT_MAPPINGS[@]}"; do IFS=':' read -r local_port remote_port <<< "$mapping" echo " 服务器端口 $remote_port → 本地 $LOCAL_IP:$local_port" done echo -e "\n===== 服务器端端口检测命令 =====" echo "# 登录服务器后执行,检查端口是否监听:" for mapping in "${PORT_MAPPINGS[@]}"; do IFS=':' read -r local_port remote_port <<< "$mapping" echo "ss -tulpn | grep $remote_port" done echo -e "\n===== 本地端进程检测命令 =====" echo "# 本地执行,检查隧道进程是否运行:" echo "ps aux | grep ssh | grep $TUNNEL_USER" } # 启动隧道 start() { if [ -f "$PID_FILE" ]; then local pid=$(cat "$PID_FILE") if ps -p $pid > /dev/null; then log "隧道已在运行(PID: $pid)" echo "隧道已在运行(PID: $pid)" return 0 else rm -f "$PID_FILE" log "清理无效的PID文件" fi fi check_key local forward_args=$(build_forward_args) log "启动SSH隧道,转发参数:$forward_args" echo "启动SSH隧道..." # 启动隧道(后台运行,禁用密码认证,设置心跳保持连接) ssh -i "$KEY_FILE" \ -o ServerAliveInterval=30 \ -o ServerAliveCountMax=3 \ -o StrictHostKeyChecking=no \ -o PasswordAuthentication=no \ -N $forward_args \ -p $SERVER_SSH_PORT \ $TUNNEL_USER@$SERVER \ > $LOG_FILE 2>&1 & local pid=$! echo $pid > $PID_FILE # 延迟1秒检测进程是否真的启动 sleep 1 if ps -p $pid > /dev/null; then log "隧道启动成功,PID: $pid" echo "隧道启动成功,PID: $pid" else rm -f $PID_FILE log "隧道启动失败!请执行 $0 test 查看完整命令手动测试" echo "错误:隧道启动失败!请执行 $0 test 查看完整命令手动测试" fi } # 停止隧道 stop() { if [ -f "$PID_FILE" ]; then local pid=$(cat "$PID_FILE") if ps -p $pid > /dev/null; then log "停止隧道(PID: $pid)" echo "停止隧道(PID: $pid)..." kill $pid sleep 2 # 确保进程终止 if ps -p $pid > /dev/null; then kill -9 $pid log "强制终止隧道进程(PID: $pid)" fi rm -f "$PID_FILE" log "隧道已停止" echo "隧道已停止" else rm -f "$PID_FILE" log "PID文件存在,但进程未运行,清理PID文件" echo "隧道未运行" fi else log "PID文件不存在,隧道未运行" echo "隧道未运行" fi } # 重启隧道 restart() { stop sleep 2 start } # 查看状态(显示完整端口映射+端口检测) status() { echo "===== 隧道状态 =====" if [ -f "$PID_FILE" ]; then local pid=$(cat "$PID_FILE") if ps -p $pid > /dev/null; then log "查看状态:隧道运行中(PID: $pid)" echo "✅ 隧道运行中(PID: $pid)" echo -e "\n===== 端口映射 =====" for mapping in "${PORT_MAPPINGS[@]}"; do IFS=':' read -r local_port remote_port <<< "$mapping" echo " 服务器端口 $remote_port → 本地 $LOCAL_IP:$local_port" done echo -e "\n===== 建议 =====" echo "请在服务器执行以下命令检测端口是否监听:" for mapping in "${PORT_MAPPINGS[@]}"; do IFS=':' read -r local_port remote_port <<< "$mapping" echo " ss -tulpn | grep $remote_port" done else log "查看状态:PID文件存在,但进程未运行" echo "❌ 隧道已停止(无效PID)" fi else log "查看状态:隧道未运行" echo "❌ 隧道未运行" fi } # 注册为系统服务 install_service() { # 获取脚本绝对路径(避免路径问题) SCRIPT_ABS_PATH=$(realpath "$0") # 创建systemd服务文件 local service_file="/etc/systemd/system/ssh-tunnel.service" sudo cat > $service_file << EOF [Unit] Description=SSH Tunnel Service (Custom Port Mapping) After=network.target [Service] Type=forking User=$USER Group=$USER ExecStart=$SCRIPT_ABS_PATH start ExecStop=$SCRIPT_ABS_PATH stop ExecRestart=$SCRIPT_ABS_PATH restart PIDFile=$PID_FILE Restart=always RestartSec=10 StandardOutput=append:$LOG_FILE StandardError=append:$LOG_FILE [Install] WantedBy=multi-user.target EOF # 确保日志文件存在且权限正确 sudo touch $LOG_FILE sudo chown $USER:$USER $LOG_FILE # 重新加载systemd配置 sudo systemctl daemon-reload # 设置开机自启 sudo systemctl enable ssh-tunnel log "系统服务已注册,服务名:ssh-tunnel" echo "✅ 系统服务已注册成功!" echo "常用命令:" echo " 启动服务:sudo systemctl start ssh-tunnel" echo " 停止服务:sudo systemctl stop ssh-tunnel" echo " 查看状态:sudo systemctl status ssh-tunnel" echo " 查看日志:tail -f $LOG_FILE" } # 卸载系统服务 uninstall_service() { sudo systemctl stop ssh-tunnel sudo systemctl disable ssh-tunnel sudo rm -f /etc/systemd/system/ssh-tunnel.service sudo systemctl daemon-reload log "系统服务已卸载" echo "✅ 系统服务已卸载成功!" } # 主逻辑 case "$1" in start) start ;; stop) stop ;; restart) restart ;; status) status ;; test) build_full_command ;; install) install_service ;; uninstall) uninstall_service ;; *) echo "用法:$0 {start|stop|restart|status|test|install|uninstall}" exit 1 ;; esac exit 0 ``` #### 5. 用法 ``` # 1. 将脚本保存为 ssh_tunnel_service.sh # 2. 添加执行权限 chmod +x ssh_tunnel_service.sh # 3. 测试启动隧道(先手动测试) ./ssh_tunnel_service.sh start # 4. 查看隧道状态 ./ssh_tunnel_service.sh status # 5. 注册为系统服务(永久生效) ./ssh_tunnel_service.sh install # 6. 启动系统服务 sudo systemctl start ssh-tunnel # 7. 设置开机自启(install命令已包含,可验证) sudo systemctl enable ssh-tunnel # 8. 测试 ./ssh_tunnel_service.sh test ``` 「一键投喂 软糖/蛋糕/布丁/牛奶/冰阔乐!」 赞赏 × 梦白沙 (๑>ڡ<)☆谢谢老板~ 1元 2元 5元 10元 50元 任意金额 2元 使用微信扫描二维码完成支付 版权声明:本文为作者原创,如需转载须联系作者本人同意,未经作者本人同意不得擅自转载。 随手发现 2026-01-28 评论 7 次浏览