标签

SSH

SSH(Secure Shell)是一种用于远程登录和其他网络服务的安全协议。它通过加密技术保障数据传输的安全性,防止信息在传输过程中被窃取或篡改。用户可以利用 SSH 在不安全的网络环境中安全地连接到远程服务器,执行命令、管理文件或进行系统维护。SSH 通常使用公钥和私钥进行身份验证,大大提高了安全性,避免了传统明文密码的风险。它已成为 Linux、Unix 等操作系统远程管理的标准工具,被广泛应用于开发、运维和数据传输等场景。SSH 还支持端口转发和安全的隧道功能,能够保护其他协议的数据传输。

SSH
服务端5月30日 21:21
SSH 协议是如何建立安全连接的?SSH 不是“把密码加密后发给服务器”这么简单。它先通过 TCP 连接到服务端 22 端口,双方确认协议版本和可用算法,再用密钥交换算法协商出临时会话密钥;后续真正传输命令、文件和端口转发数据时,主要靠对称加密保证速度,靠 MAC 或 AEAD 保证完整性。服务器会拿出自己的主机公钥证明“我就是你上次连过的那台机器”,客户端再用密码、公钥或键盘交互完成用户认证。 ## 追问 ### 为什么 SSH 既用非对称加密又用对称加密? 非对称加密适合身份验证和安全协商,但计算成本高,不适合持续传输大量数据。SSH 用它安全商量密钥,之后切到 AES、ChaCha20 这类对称算法。 ### known_hosts 文件有什么用? known_hosts 记录服务器主机公钥指纹,用来发现你是否连到了同一台服务器。生产环境指纹突然变化,要先确认是否重装、迁移或被中间人劫持。 ### 公钥登录比密码登录安全吗? 通常更安全,因为私钥不需要发到网络上,服务端只验证签名结果。边界是私钥文件必须有口令和合理权限。 ### SSH 能防住所有中间人攻击吗? 前提是客户端正确校验服务器主机指纹。第一次连接就接受了伪造指纹,SSH 也无法凭空知道对方是假的。 ## 写段配置 ```config PasswordAuthentication no PermitRootLogin no AllowUsers deploy PubkeyAuthentication yes ```
服务端5月29日 01:22
SSH 连接失败如何排查?常见原因有哪些?SSH 故障按报错分四类:Connection refused 说明目标端口未监听(sshd 未运行或防火墙阻断);Connection timeout 说明网络不可达(路由/防火墙丢弃包);Permission denied 是认证失败(密钥不匹配或权限错误);Host key verification failed 是 known_hosts 中记录的指纹与服务器当前密钥不一致。排查第一步始终是 ssh -vvv 看详细握手过程,日志会精确显示卡在哪个阶段。密钥权限是最常见的低级错误:私钥必须 600,.ssh 目录 700,authorized_keys 600,权限过宽 sshd 会拒绝认证。 ## 追问 **ssh -vvv 输出中各阶段分别对应什么?** 三个 v 递增详细度。-v 显示连接建立、密钥交换、认证尝试;-vv 增加配置解析和 IO 细节;-vvv 再增加包级调试。重点关注 debug1: Authentications that can continue 和 debug1: Offering public key 两行,前者看服务端支持哪些认证方式,后者看客户端尝试了哪些密钥。 **连接频繁断开怎么解决?** 通常是 NAT/防火墙空闲连接超时淘汰。客户端配置 ServerAliveInterval 60(每60秒发心跳),服务端配置 ClientAliveInterval 300。autossh 可自动重连断开的会话。 **known_hosts 冲突一定是重装系统吗?** 不一定。IP 被复用、服务器更新了主机密钥、或中间人攻击都会导致。确认安全后用 ssh-keygen -R host 删除旧记录,不可盲目忽略否则失去 MITM 保护。 **如何不用密码只允许密钥登录?** 服务端 /etc/ssh/sshd_config 设置 PasswordAuthentication no 和 PubkeyAuthentication yes,改完 sshd -t 验证语法再 systemctl restart sshd。 ## 写段代码 ```bash # 客户端 SSH 配置 ~/.ssh/config Host myserver HostName 10.0.0.1 User deploy Port 2222 IdentityFile ~/.ssh/deploy_key ServerAliveInterval 60 ```
服务端5月28日 06:59
SSH 安全加固怎么做?生产服务器必改的 8 项配置与面试追问SSH 安全加固是运维和后端面试中的高频考点,也是生产服务器上线前必须完成的配置。本文从实际生产场景出发,梳理 SSH 加固的核心配置项,每项给出"为什么做"和"怎么配",并在文末附上面试常见追问。 ## 修改默认端口:降低被扫描概率 SSH 默认监听 22 端口,这是所有自动化扫描工具的首要目标。改成非标准端口后,扫描流量会大幅下降,日志噪音也明显减少。 ```bash # /etc/ssh/sshd_config Port 2222 ``` 修改端口后记得同步更新防火墙规则和 Fail2Ban 配置,否则改了端口反而把自己锁在外面。另外,改端口属于"降低攻击面"而非"增强安全性",不要因此放松其他加固措施。 ## 禁用 root 登录:权限最小化 允许 root 直接 SSH 登录意味着一旦密钥或密码泄露,攻击者立即获得最高权限。正确的做法是用普通用户登录,再通过 sudo 提权。 ```bash # /etc/ssh/sshd_config PermitRootLogin no ``` 部署前要确认普通用户的 sudo 权限已配置好,否则禁用 root 后将无法执行管理操作。 ## 强制公钥认证:干掉暴力破解 密码认证最大的风险是暴力破解,即使设了复杂密码也难逃字典攻击。公钥认证用非对称加密,私钥不离开本地,攻击面极小。 ```bash # /etc/ssh/sshd_config PasswordAuthentication no PubkeyAuthentication yes ``` 切换顺序很重要:先部署公钥到服务器,测试公钥登录成功,再禁用密码认证。如果反着来,你可能会丢掉唯一能登录的方式。 ## 限制登录用户:白名单比黑名单可靠 不限制登录用户意味着系统上的所有账户(包括服务账户)都可以尝试 SSH 登录。用白名单方式只允许运维人员登录,是更安全的做法。 ```bash # /etc/ssh/sshd_config AllowUsers deploy admin@10.0.0.0/8 AllowGroups sshusers ``` SSH 处理顺序是 DenyUsers → AllowUsers → DenyGroups → AllowGroups,建议用 AllowUsers 或 AllowGroups 做白名单,比 DenyUsers 黑名单更可控。 ## 启用多因素认证:密钥 + 动态口令 密钥认证虽然安全,但如果私钥文件被盗,攻击者就能直接登录。多因素认证(MFA)要求同时持有密钥和动态口令,即使私钥泄露也无法单独使用。 ```bash # /etc/ssh/sshd_config AuthenticationMethods publickey,keyboard-interactive:pam # 安装 Google Authenticator PAM 模块 sudo apt-get install libpam-google-authenticator # 为用户生成 TOTP 密钥 google-authenticator # /etc/pam.d/sshd 添加 auth required pam_google_authenticator.so ``` 生产环境建议 MFA 仅对跳板机或入口机开启,内网机器可以只做密钥认证,平衡安全和效率。 ## 加密算法优化:淘汰弱算法 OpenSSH 支持多种加密算法,其中一些已被证明存在安全缺陷(如 3DES、CBC 模式、SHA1)。只保留安全的算法可以防止降级攻击。 ```bash # /etc/ssh/sshd_config Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512 MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com ``` 可以用 `ssh -Q cipher` 查看当前 OpenSSH 支持的算法列表,用 `sshd -T | grep -i cipher` 查看实际生效配置。推荐使用 ssh-audit 工具对服务器做算法安全审计。 ## 连接限制与超时:防暴力破解和会话劫持 暴力破解靠大量尝试碰运气,限制认证次数和连接速率能有效遏制。空闲会话不断开则可能被他人利用,设置超时是必要的。 ```bash # /etc/ssh/sshd_config MaxAuthTries 3 MaxSessions 2 MaxStartups 10:30:100 LoginGraceTime 60 ClientAliveInterval 300 ClientAliveCountMax 0 ``` `MaxStartups 10:30:100` 的含义:未完成认证的连接超过 10 个时,新连接有 30% 概率被拒绝;超过 100 个则全部拒绝。`ClientAliveCountMax 0` 表示连续 0 次无响应即断开,配合 `ClientAliveInterval 300` 实现 5 分钟无操作自动断开。 ## 网络层防护:防火墙 + Fail2Ban + TCP Wrapper SSH 加固不能只靠 sshd_config,网络层防护是第二道防线。三层配合使用效果最好。 **防火墙限制来源 IP**: ```bash # iptables: 仅允许内网段访问 iptables -A INPUT -p tcp --dport 2222 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 2222 -j DROP # ufw: 更简洁的写法 ufw allow from 10.0.0.0/8 to any port 2222 ``` **Fail2Ban 自动封禁**: ```bash # /etc/fail2ban/jail.local [sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600 findtime = 600 ``` **TCP Wrapper 兜底**: ```bash # /etc/hosts.allow sshd: 10.0.0.0/8 # /etc/hosts.deny sshd: ALL ``` 三者防护逻辑不同:防火墙在网络层过滤数据包,Fail2Ban 根据行为模式动态封禁,TCP Wrapper 在应用层做访问控制。不要只依赖其中一种。 ## 面试追问与回答 **Q: 只改端口能防住攻击吗?** 不能。改端口只是降低了被自动化工具发现的概率,端口扫描器仍然可以遍历所有端口找到 SSH 服务。改端口是辅助手段,核心安全还是要靠密钥认证、禁用 root、限制来源 IP 这些实质性加固。 **Q: 密钥认证就够安全了吗?什么场景还需要 MFA?** 密钥认证比密码安全得多,但私钥文件一旦泄露(比如开发机被入侵),攻击者就能直接登录服务器。以下场景必须上 MFA:面向公网暴露的跳板机、拥有高权限的核心服务器、合规要求(等保三级及以上)的场景。 **Q: ClientAliveInterval 和 LoginGraceTime 区别是什么?** 两者作用阶段完全不同。LoginGraceTime 控制的是"连接建立后多久没完成认证就断开",防的是半开连接占用资源;ClientAliveInterval 控制的是"认证成功后多久没操作就断开",防的是空闲会话被利用。生产环境建议前者设 60 秒,后者设 300 秒。 **Q: 怎么验证 SSH 加固配置是否生效?** 分三步验证:用 `sshd -T` 检查配置是否被正确加载;用 `ssh-audit` 工具扫描服务器,它会给出算法强度和配置问题的评分;用另一台机器尝试以 root 登录、密码登录、从非白名单 IP 登录,确认都被拒绝。修改配置前务必保留一个已登录的会话,防止配置错误把自己锁在外面。
服务端5月28日 02:10
如何进行 SSH 安全加固?有哪些最佳实践和安全配置建议?SSH 安全加固是运维和后端面试中的高频考点,也是生产环境必须落地的安全措施。一次配置不当的 SSH 服务,可能让整个服务器暴露在暴力破解和未授权访问的风险之下。本文从认证、网络、加密、密钥管理、监控五个层面系统讲解 SSH 安全加固方案。 ## 核心原则:最小权限 + 纵深防御 SSH 安全加固不是单一配置的修改,而是从多个层面构建防线。核心思路:即使某一层被突破,还有下一层阻拦。没有任何一项配置能单独保证安全,只有组合使用才能形成有效防御体系。 ## 认证层加固 ### 禁用密码认证,仅允许密钥登录 密码认证是暴力破解的最大突破口。密钥认证从根本上消除了密码被猜解的风险——密钥空间足够大,暴力破解在计算上不可行。 ```bash # /etc/ssh/sshd_config PasswordAuthentication no PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys ``` 密钥类型选择:优先 ED25519(更短、更快、更安全),RSA 至少 4096 位。 ```bash # 推荐:ED25519 ssh-keygen -t ed25519 -C "user@company" # 兼容场景:RSA 4096 ssh-keygen -t rsa -b 4096 -C "user@company" ``` 注意:ED25519 不支持 `-b` 参数指定密钥长度,它的密钥长度是固定的。原始文章中 `ssh-keygen -t ed25519 -b 4096` 的写法是错误的,`-b` 参数会被忽略。 ### 禁止 root 直接登录 生产环境中,root 直接登录是高危行为。应先以普通用户登录,再通过 sudo 提权,这样所有操作都有审计记录,便于事后追溯。 ```bash # /etc/ssh/sshd_config PermitRootLogin no ``` 如果必须允许 root 登录(极少数场景),至少使用 `PermitRootLogin prohibit-password`,仅允许密钥认证。`prohibit-password` 比 `without-password` 更明确,是 OpenSSH 7.0+ 的推荐写法。 ### 限制可登录的用户和组 不要让系统上所有用户都能 SSH 登录,明确指定允许登录的白名单。 ```bash # /etc/ssh/sshd_config AllowUsers admin deploy AllowGroups ssh-users ``` `AllowUsers` 和 `AllowGroups` 是白名单机制,不在名单中的用户即使有密钥也无法登录。两者同时配置时取交集,即用户必须同时满足用户和组的限制。搭配 `DenyUsers` 使用时,deny 优先于 allow。 ### 限制认证尝试次数和连接速率 暴力破解依赖大量快速尝试,限制速率可以直接阻断此类攻击。 ```bash # /etc/ssh/sshd_config MaxAuthTries 3 MaxStartups 10:30:100 LoginGraceTime 60 ``` `MaxStartups 10:30:100` 的含义:超过 10 个未完成连接时,以 30% 概率拒绝新连接;超过 100 个时全部拒绝。这个参数是防止连接耗尽型攻击的关键配置。 ## 网络层加固 ### 修改默认端口 改端口不是安全措施,而是降低噪声的手段。自动化扫描工具通常只扫 22 端口,改端口后日志中的扫描噪音会大幅减少,便于发现真正有威胁的连接。 ```bash # /etc/ssh/sshd_config Port 2222 ``` 修改后务必同步更新防火墙规则和安全组,否则会被自己挡在门外。建议在修改前先通过 console 或带外管理确认回退方案。 ### 防火墙限制来源 IP 最有效的访问控制是只允许已知 IP 连接。 ```bash # ufw 示例:仅允许办公网段 ufw allow from 10.0.1.0/24 to any port 2222 proto tcp ufw enable # iptables 示例 iptables -A INPUT -p tcp --dport 2222 -s 10.0.1.0/24 -j ACCEPT iptables -A INPUT -p tcp --dport 2222 -j DROP ``` 如果来源 IP 不固定,可以配合 VPN 或跳板机使用,将 SSH 访问收敛到单一入口。云上环境建议通过安全组实现,比 iptables 更不易出错。 ### 使用 fail2ban 自动封禁 fail2ban 通过分析日志自动封禁异常 IP,是对暴力破解的自动化防御。 ```bash # /etc/fail2ban/jail.local [sshd] enabled = true port = 2222 maxretry = 3 bantime = 3600 findtime = 600 ``` 注意事项:`maxretry` 不宜设太小,否则合法用户输错一次密码就可能被封。生产环境建议配合 `ignoreip` 将管理网段加入白名单,避免误封。`bantime` 建议设为较长时间(如 86400),频繁攻击的 IP 没必要短时间解封。 ### 配置连接超时 长时间空闲的 SSH 会话是安全隐患,终端可能被旁人操作。 ```bash # /etc/ssh/sshd_config ClientAliveInterval 300 ClientAliveCountMax 2 ``` 300 秒(5 分钟)无操作发送一次心跳探测,连续 2 次无响应则断开。实际超时时间 = ClientAliveInterval * ClientAliveCountMax = 600 秒。根据业务场景调整:高频操作环境可以设短一些,长时间编译部署的环境适当延长。 ## 加密层加固 ### 使用强加密算法 默认配置可能包含已淘汰的弱算法(如 3des、arcfour)。显式指定安全算法列表,确保通信强度。 ```bash # /etc/ssh/sshd_config KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com ``` 可以用 `ssh-audit` 工具检测当前配置的加密强度: ```bash # 安装 pip3 install ssh-audit # 检测 ssh-audit server_ip ``` `ssh-audit` 会输出当前使用的密钥交换、加密、MAC 算法的安全等级,标记出需要替换的弱算法。 ### 禁用不必要的功能 每多开一个功能就多一个攻击面。不需要的功能一律关闭。 ```bash # /etc/ssh/sshd_config X11Forwarding no GatewayPorts no PermitTunnel no ``` `AllowTcpForwarding` 需要根据业务判断:如果需要端口转发(如数据库调试),保留 `yes`;否则设为 `no`。注意设为 `no` 并不能完全阻止端口转发,用户仍可通过 `command=` 方式间接实现,真正需要禁止时应配合 `no-port-forwarding` 在 authorized_keys 中限制。 ## 密钥管理 ### 为私钥设置密码短语 即使私钥文件泄露,没有密码短语也无法使用。这是密钥安全的最后一道防线。 ```bash ssh-keygen -t ed25519 -C "user@company" # 在提示时输入强密码短语 ``` 已有密钥可以补设密码短语: ```bash ssh-keygen -p -f ~/.ssh/id_ed25519 ``` 配合 `ssh-agent` 使用,输入一次密码短语后,后续连接自动认证,不必每次都输入。 ### 限制密钥用途 在 `authorized_keys` 中可以为每个密钥设置限制条件,实现精细化的访问控制。 ```bash # ~/.ssh/authorized_keys # 限制只能执行特定命令 command="/usr/local/bin/backup.sh" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... # 限制来源 IP from="10.0.1.0/24" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... # 组合限制:来自特定 IP 且不能端口转发 from="10.0.1.0/24",no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ``` 这种机制特别适合 CI/CD 部署场景:为部署密钥绑定 `command`,即使密钥泄露,攻击者也只能执行指定命令。可用的限制选项还包括 `no-pty`(禁用交互式终端)、`no-agent-forwarding`(禁用 agent 转发)等。 ### 定期轮换密钥 密钥应该像密码一样定期更换,尤其在人员离职时必须及时清理。 ```bash # 生成新密钥 ssh-keygen -t ed25519 -f ~/.ssh/new_key -C "user@company" # 部署新公钥 ssh-copy-id -i ~/.ssh/new_key.pub user@server # 确认新密钥可用后再删除旧密钥 rm ~/.ssh/old_key ~/.ssh/old_key.pub ``` 建议在密钥的 `-C` 注释中包含创建日期,方便追踪轮换周期。 ## 多因素认证 ### SSH 证书替代传统密钥 大规模团队管理中,逐个分发密钥并维护 `authorized_keys` 效率极低。SSH 证书通过 CA 签发,自动过期,无需手动清理。 ```bash # 生成 CA 密钥 ssh-keygen -t ed25519 -f /etc/ssh/ca_key # 签发用户证书(有效期 52 周) ssh-keygen -s /etc/ssh/ca_key -I user_id -n username -V +52w ~/.ssh/user_key.pub # 服务器端信任 CA # /etc/ssh/sshd_config TrustedUserCAKeys /etc/ssh/ca_key.pub ``` 证书到期自动失效,人员离职只需不再签发新证书,无需逐台服务器删除公钥。这是 SSH 证书相比传统公钥的最大优势——集中化管理。Facebook(Meta)的基础设施就是大规模使用 SSH 证书的典型案例。 ### TOTP 双因素认证 在密钥认证基础上增加动态验证码,即使密钥泄露,没有验证码也无法登录。 ```bash # 安装 Google Authenticator PAM 模块 apt-get install libpam-google-authenticator # 每个用户独立配置 google-authenticator # /etc/pam.d/sshd 添加 auth required pam_google_authenticator.so # /etc/ssh/sshd_config KbdInteractiveAuthentication yes ``` 注意:OpenSSH 8.2+ 推荐使用 `KbdInteractiveAuthentication` 替代已弃用的 `ChallengeResponseAuthentication`。配置前确保有 console 访问作为回退手段,万一 PAM 配置出错不会锁死服务器。 ## 监控与应急 ### 日志监控 ```bash # /etc/ssh/sshd_config LogLevel VERBOSE SyslogFacility AUTHPRIV ``` VERBOSE 级别会记录密钥指纹,便于追踪是哪个密钥登录的。INFO 级别不够详细,DEBUG 级别日志量过大影响性能,VERBOSE 是生产环境的平衡选择。 查看登录活动: ```bash # 最近成功登录 last -n 20 # 失败登录尝试 lastb -n 20 # 实时监控 tail -f /var/log/auth.log | grep sshd ``` ### 登录告警 在关键服务器上配置登录通知,异常登录可以第一时间发现。 ```bash # /etc/profile 或 ~/.bashrc if [ -n "$SSH_CLIENT" ]; then echo "SSH login: $(whoami) from $(echo $SSH_CLIENT | awk '{print $1}') at $(date)" | mail -s "SSH Login Alert: $(hostname)" admin@company.com fi ``` 大规模环境建议接入集中化告警系统(如 Prometheus AlertManager),而不是依赖邮件通知。 ### 入侵应急响应 一旦发现异常登录迹象,按以下步骤处置: ```bash # 1. 查看异常连接 ss -tnp | grep :2222 # 2. 紧急封禁可疑 IP iptables -A INPUT -s 可疑IP -j DROP # 3. 如果确认被入侵,立即切断 SSH 服务 systemctl stop sshd # 4. 修改配置后重启(先验证语法) sshd -t && systemctl start sshd ``` `sshd -t` 在重启前检查配置语法,避免配置错误导致无法远程连接。养成任何配置修改后都先执行 `sshd -t` 的习惯。 ## 配置检查清单 每次加固后逐项确认: - 端口已改为非默认值,防火墙规则已同步更新 - 密码认证已禁用,仅允许密钥登录 - root 直接登录已禁止 - 可登录用户已限制为白名单 - 认证尝试次数已限制(MaxAuthTries <= 3) - 加密算法已更新为安全列表 - fail2ban 已启用且白名单已配置 - 空闲连接超时已设置 - 日志级别设为 VERBOSE - 配置修改后已执行 `sshd -t` 验证语法 - 已备份原配置文件(cp sshd_config sshd_config.bak) - 私钥已设置密码短语 - 旧密钥和离职人员密钥已清理 ## 常见误区 **误区一:改了端口就安全了。** 端口扫描工具可以遍历所有端口,改端口只是降低噪音,不能替代其他加固措施。 **误区二:密钥认证就不需要 fail2ban 了。** fail2ban 还能防御探测行为和异常连接模式,两者互补而非替代。 **误区三:加固一次就万事大吉。** SSH 软件需要定期更新,密钥需要轮换,日志需要审计。安全是持续过程,不是一次性操作。 **误区四:配置越严格越安全。** 过度加固可能导致运维不便甚至锁死自己。加固策略要根据实际场景权衡,始终确保有可恢复的手段(如 console 访问或带外管理)。 **误区五:AllowTcpForwarding no 就能禁止端口转发。** 用户仍可通过 `command=` 方式或 SSH 代理转发间接实现。真正需要禁止时应配合 authorized_keys 中的 `no-port-forwarding` 限制。
服务端5月28日 02:06
如何配置 SSH 密钥认证?密钥认证相比密码认证有哪些优势?SSH 密钥认证基于非对称加密,客户端持有私钥、服务器持有公钥,登录时通过加密挑战完成身份验证,无需传输密码。相比密码认证,密钥认证抗暴力破解、免输入密码、可精细控制权限,是服务器安全运维的基本要求。 ## 密钥类型怎么选 主流密钥算法对比: | 算法 | 密钥长度 | 安全性 | 性能 | 推荐度 | |------|---------|--------|------|--------| | ED25519 | 256 bit | 极高 | 最快 | 首选 | | ECDSA | 256 bit | 高 | 快 | 可用 | | RSA | 4096 bit | 高 | 慢 | 兼容性场景 | ED25519 基于 Curve25519 椭圆曲线,密钥短、签名快、抗侧信道攻击,是当前首选。RSA 4096 仅在需要兼容老旧系统时使用。 ## 生成密钥对 ```bash # 生成 ED25519 密钥(推荐) ssh-keygen -t ed25519 -C "user@example.com" # 生成 RSA 4096 密钥(兼容旧系统) ssh-keygen -t rsa -b 4096 -C "user@example.com" # 指定密钥文件名 ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "ci/deploy" # 生成带硬件绑定的 FIDO2 密钥(需要 YubiKey 等设备) ssh-keygen -t ed25519-sk -C "user@example.com" ``` 生成后会产生两个文件: - **私钥**(`~/.ssh/id_ed25519`):绝对不能泄露,等同于你的身份凭证 - **公钥**(`~/.ssh/id_ed25519.pub`):可以公开,上传到目标服务器 私钥建议设置 passphrase,即使私钥被盗,攻击者也无法直接使用。 ## 配置服务器端密钥登录 ### 第一步:复制公钥到服务器 ```bash # 方法一:ssh-copy-id(最简单) ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostname # 方法二:手动追加(目标机器没有 ssh-copy-id 时) cat ~/.ssh/id_ed25519.pub | ssh user@hostname \ "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" ``` ### 第二步:确认服务器 SSH 配置 编辑服务器上的 `/etc/ssh/sshd_config`: ```bash # 启用公钥认证 PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys # 确认密钥登录成功后,再禁用密码认证 PasswordAuthentication no ``` 修改后重启服务: ```bash # Ubuntu/Debian sudo systemctl restart sshd # CentOS/RHEL sudo systemctl restart sshd ``` **注意**:先测试密钥登录成功,再禁用密码认证,否则可能锁死自己。 ### 第三步:设置文件权限 权限不对是密钥登录失败最常见的原因: ```bash chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod 600 ~/.ssh/id_ed25519 # 私钥 chmod 644 ~/.ssh/id_ed25519.pub # 公钥 ``` ## 密钥认证比密码认证强在哪 **抗暴力破解**:密码可被字典攻击逐个尝试,而 256 bit 的 ED25519 私钥暴力破解概率在计算上不可能。一次暴力尝试的代价相差约 2^128 倍。 **零密码传输**:密码认证每次登录都要把密码发送到服务器,存在中间人截获风险。密钥认证只传输加密签名,私钥永不离开本地。 **免交互登录**:配合 ssh-agent 或无 passphrase 的密钥,可实现自动化部署、定时备份、CI/CD 流水线免密操作,密码认证做不到。 **细粒度权限控制**:在 `authorized_keys` 中可限制单个密钥的权限: ```bash # 限制只能执行 git 操作 command="/usr/bin/git-shell" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... # 限制来源 IP from="10.0.0.0/8" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... # 禁用端口转发和 X11 no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... # 组合限制:只允许从内网 SCP 文件 command="/usr/libexec/openssh/sftp-server",from="10.0.0.0/8",no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ``` **多因素叠加**:密钥本身是「你有的」,加上 passphrase 就是「你知道的」,再加 FIDO2 硬件密钥就是「你有的物理设备」,三因素认证也轻松实现。 ## ssh-agent 管理多个密钥 管理多台服务器或多个 Git 平台时,需要不同的密钥: ```bash # 启动 agent eval "$(ssh-agent -s)" # 添加密钥(会提示输入 passphrase) ssh-add ~/.ssh/id_ed25519 ssh-add ~/.ssh/deploy_key # 查看已加载的密钥 ssh-add -l # 删除所有密钥 ssh-add -D ``` 配置 `~/.ssh/config` 让不同主机使用不同密钥: ```bash Host github.com IdentityFile ~/.ssh/id_ed25519 User git Host production-server IdentityFile ~/.ssh/deploy_key User deploy Port 2222 Host jump-server IdentityFile ~/.ssh/id_ed25519 ProxyJump none Host internal-* ProxyJump jump-server IdentityFile ~/.ssh/id_ed25519 ``` ## 常见排错 密钥登录失败时,按以下顺序排查: **1. 用 verbose 模式看详细日志** ```bash ssh -vvv user@hostname ``` 重点看 `Offering public key` 和 `Server accepts key` 两行。 **2. 检查权限** ```bash # 客户端 ls -la ~/.ssh/ # 私钥必须是 600,目录必须是 700 # 服务器端 ls -la ~/.ssh/ ~/.ssh/authorized_keys # authorized_keys 必须 600,~/.ssh 必须 700 ``` **3. 检查服务器端日志** ```bash # 查看 SSH 服务日志 sudo journalctl -u sshd --since "10 minutes ago" # 或 sudo tail -f /var/log/auth.log ``` 常见错误原因: | 现象 | 原因 | 解决 | |------|------|------| | Permission denied | authorized_keys 权限 644 | chmod 600 | | Permission denied | .ssh 目录权限 755 | chmod 700 | | Permission denied | 家目录被组可写 | chmod 750 ~ | | Connection refused | sshd 未运行 | systemctl start sshd | | 密钥不被接受 | 公钥内容被截断/换行 | 重新复制 | | SELinux 阻止 | 文件安全上下文不对 | restorecon -Rv ~/.ssh | ## 安全加固清单 - 使用 ED25519 或 RSA 4096,不用 RSA 2048 及以下 - 私钥设置 passphrase,用 ssh-agent 避免反复输入 - 服务器端禁用密码认证:`PasswordAuthentication no` - 禁用 root 远程登录:`PermitRootLogin no` 或 `prohibit-password` - 限制 SSH 端口,不用默认 22 - 定期轮换密钥,移除 authorized_keys 中的旧公钥 - 敏感服务器使用 FIDO2 硬件密钥(`ed25519-sk`) - 配置 fail2ban 防止扫描探测 - 审计 SSH 登录日志,关注异常 IP
服务端5月28日 00:54
SSH 配置文件有哪些常用选项?如何通过配置文件简化连接管理?SSH 配置文件(~/.ssh/config 和 /etc/ssh/sshd_config)是管理和简化远程连接的核心工具。掌握常用选项后,可以将冗长的命令行参数转化为可复用的配置块,实现连接复用、跳板代理和端口转发等高级功能,大幅提升日常运维效率。 ## 客户端配置文件(~/.ssh/config) ### 配置文件位置与优先级 SSH 客户端从三个来源读取配置,优先级从高到低: 1. **命令行参数**:如 `ssh -p 2222 user@host`,优先级最高 2. **用户配置文件**:`~/.ssh/config`(最常用,日常管理的核心) 3. **全局配置文件**:`/etc/ssh/ssh_config`(系统管理员设置默认值) 三个来源的配置合并后生效,命令行参数覆盖用户配置,用户配置覆盖全局配置。注意:用户配置文件权限必须为 600,否则 SSH 会拒绝读取。 ### 基本连接配置 ```bash # ~/.ssh/config # 最基本的主机配置 Host server1 HostName 192.168.1.100 User admin Port 2222 IdentityFile ~/.ssh/id_ed25519 # 使用别名简化连接 Host prod HostName prod.example.com User deploy IdentityFile ~/.ssh/prod_key ``` 配置完成后,`ssh prod` 等价于 `ssh -i ~/.ssh/prod_key deploy@prod.example.com`,无需记忆每台服务器的连接参数。 ### 通配符与批量配置 Host 指令支持通配符 `*` 和 `?`,可以对一组主机应用相同配置: ```bash # 对所有 example.com 主机使用相同用户和密钥 Host *.example.com User webadmin IdentityFile ~/.ssh/web_key # 对所有连接启用连接保持 Host * ServerAliveInterval 60 ServerAliveCountMax 3 ``` 关键细节:SSH 按配置文件中的顺序逐段匹配 Host,第一个匹配段中出现的选项生效,不会与后续段合并。因此通配符配置应放在文件末尾,避免被提前匹配覆盖。 ### 连接复用(ControlMaster) 连接复用是 SSH 最实用的性能优化手段。首次连接建立 master 连接并创建 socket 文件,后续到同一主机的连接直接复用该通道,跳过认证和握手过程: ```bash Host * ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 600 ``` 各参数含义: | 参数 | 说明 | |------|------| | `ControlMaster auto` | 自动创建新连接或复用已有 master 连接 | | `ControlPath` | socket 文件路径,`%r` 远程用户名、`%h` 主机名、`%p` 端口 | | `ControlPersist 600` | 最后一个会话关闭后,master 连接保持 600 秒 | 使用前需创建 socket 目录:`mkdir -p ~/.ssh/sockets`。管理命令:`ssh -O exit hostname` 关闭复用连接,`ssh -O check hostname` 查看复用状态。 ### 跳板机配置 #### ProxyJump(推荐,OpenSSH 7.3+) ```bash Host jump HostName jump.example.com User jumper IdentityFile ~/.ssh/jump_key Host internal HostName 10.0.0.50 User root ProxyJump jump ``` ProxyJump 语法简洁,支持多跳串联:`ProxyJump jump1,jump2`。命令行等价写法:`ssh -J jump internal`。 #### ProxyCommand(兼容旧版本) ```bash Host internal HostName 10.0.0.50 User root ProxyCommand ssh -W %h:%p jump.example.com ``` 核心区别:ProxyJump 是 ProxyCommand 的高层封装,底层都是通过跳板机建立到目标的 TCP 通道。两者互斥,不能同时配置。OpenSSH 7.3 以下只能用 ProxyCommand。 ### 端口转发配置 SSH 支持三种端口转发,都可以写入配置文件持久化: ```bash # 本地端口转发:将远程 Redis 映射到本地 Host db-tunnel HostName db.example.com User admin LocalForward 16379 127.0.0.1:6379 # 远程端口转发:将本地服务暴露到远程 Host expose-local HostName remote.example.com User admin RemoteForward 8080 127.0.0.1:3000 # 动态端口转发:SOCKS5 代理 Host socks-proxy HostName tunnel.example.com User admin DynamicForward 1080 ``` 三种转发的区别: | 类型 | 配置项 | 数据流方向 | 典型场景 | |------|--------|-----------|----------| | 本地转发 | `LocalForward` | 本地 → 远程内网 | 访问远程数据库 | | 远程转发 | `RemoteForward` | 远程 → 本地 | 将本地服务暴露给远程 | | 动态转发 | `DynamicForward` | 本地 → 任意(SOCKS5) | 通过 SSH 安全代理上网 | ### Include 指令与配置拆分 OpenSSH 7.3+ 支持 Include 指令,可将配置按项目或环境拆分为多个文件: ```bash # ~/.ssh/config Include ~/.ssh/config.d/* # ~/.ssh/config.d/work Host prod-* User deploy IdentityFile ~/.ssh/work_key # ~/.ssh/config.d/personal Host github HostName github.com User git IdentityFile ~/.ssh/personal_key ``` 这种方式便于按项目或环境管理配置,也方便脚本自动化生成。Include 可以出现在配置文件的任何位置,被包含的文件内容在该位置展开。 ### Token 替换 配置文件中的部分选项支持 Token 动态替换: | Token | 含义 | 常见用途 | |-------|------|----------| | `%h` | 远程主机名 | ControlPath、ProxyCommand | | `%p` | 远程端口号 | ControlPath、ProxyCommand | | `%r` | 远程用户名 | ControlPath | | `%d` | 本地用户家目录 | IdentityFile 路径 | | `%u` | 本地用户名 | 日志路径 | ### 其他常用选项 | 选项 | 默认值 | 说明 | |------|--------|------| | `IdentitiesOnly yes` | no | 只使用配置的密钥,防止 ssh-agent 干扰 | | `Compression yes` | no | 启用压缩,低带宽环境有效 | | `StrictHostKeyChecking` | ask | 主机密钥验证策略:ask/yes/no | | `ForwardAgent yes` | no | 转发认证代理,用于跳板机链式认证 | | `ConnectTimeout 10` | 无 | 连接超时秒数 | | `ServerAliveInterval 60` | 0 | 客户端心跳间隔,防止连接被防火墙断开 | ## 服务器端配置文件(/etc/ssh/sshd_config) ### 认证与安全加固 ```bash # /etc/ssh/sshd_config # 认证方式 PasswordAuthentication no # 禁用密码认证,只允许密钥登录 PubkeyAuthentication yes # 启用公钥认证 PermitRootLogin prohibit-password # 允许 root 但禁止密码登录 MaxAuthTries 3 # 限制单次连接认证尝试次数 # 访问控制(白名单优先) AllowUsers admin deploy # 只允许这些用户登录 AllowGroups ssh-users # 只允许这些组登录 DenyUsers test guest # 拒绝这些用户登录 ``` `PermitRootLogin prohibit-password` 比 `no` 更灵活:允许 root 通过密钥登录但禁止密码,适合需要 root 权限的自动化脚本。 ### 连接与性能 ```bash MaxStartups 10:30:100 # 未认证连接速率限制:10个开始拒绝30%,100个全部拒绝 LoginGraceTime 60 # 认证超时时间(秒) ClientAliveInterval 300 # 服务器端心跳间隔(秒) ClientAliveCountMax 2 # 心跳无响应次数上限 ``` ### 功能开关 ```bash X11Forwarding no # 禁用 X11 转发 AllowTcpForwarding yes # 允许 TCP 转发 GatewayPorts no # 禁止远程转发绑定非本地地址 PermitTunnel no # 禁用 tun 设备 ``` ### Match 条件块 sshd_config 支持 Match 指令,按条件应用不同配置,实现精细化访问控制: ```bash # 内网允许密码认证 Match Address 192.168.1.0/24 PasswordAuthentication yes # 特定用户允许端口转发 Match User deploy AllowTcpForwarding yes GatewayPorts clientspecified # 特定组禁用端口转发 Match Group restricted AllowTcpForwarding no ``` Match 支持的条件:User、Group、Host、Address,可组合使用。 ## 常见面试追问 **ProxyJump 和 ProxyCommand 有什么区别?** ProxyJump 是 ProxyCommand 的高层封装(OpenSSH 7.3+),语法更简洁。底层原理相同:都是通过跳板机建立到目标主机的 TCP 通道。ProxyJump 支持逗号分隔的多跳串联(`ProxyJump j1,j2`),而 ProxyCommand 实现多跳需要嵌套。两者互斥,不能同时配置。 **ControlPath 中 %r %h %p 分别代表什么?** `%r` 是远程用户名,`%h` 是远程主机名,`%p` 是远程端口号。这三个 Token 组合可以确保到不同主机的连接使用不同的 socket 文件,避免复用冲突。 **StrictHostKeyChecking 三个值有什么区别?** `ask`(默认)首次连接时提示确认主机指纹;`yes` 只允许 known_hosts 中已有的主机连接,未知主机直接拒绝;`no` 自动接受新主机密钥存入 known_hosts。生产环境建议 `ask` 或 `yes`,`no` 存在中间人攻击风险。 **ClientAliveInterval 和 ServerAliveInterval 有什么区别?** 两者都是心跳机制,但发起方和用途不同。ServerAliveInterval 由客户端定期发心跳给服务器,防止客户端侧连接被防火墙或 NAT 设备断开。ClientAliveInterval 由服务器定期发心跳给客户端,用于检测客户端是否存活并主动断开僵死连接。
服务端5月28日 00:53
什么是 SSH 隧道和跳板机?如何配置多级跳板连接?SSH 隧道是通过 SSH 协议在客户端和服务器之间建立加密通道的技术,所有经过该通道的流量都受到加密保护。跳板机(Jump Host / Bastion Host)是部署在网络边界的唯一入口,外部用户必须先登录跳板机才能访问内网服务器。两者结合是企业运维中实现安全远程访问的标准方案——绝大多数生产环境都不允许直连内网服务器,必须经过跳板机中转。 ## SSH 隧道的三种转发模式 理解 SSH 隧道的核心在于区分三种端口转发方式,它们解决不同的问题,选错模式会导致连接不通或不必要的复杂性。 ### 本地端口转发(-L) 将本地某个端口的流量,通过 SSH 连接转发到远端服务器的指定端口。典型场景:从本地机器访问远端内网的数据库、Redis 等服务。 ```bash # 在本地 3306 端口访问内网 MySQL ssh -L 3306:db-server:3306 user@bastion -N # 连接后,本地执行 mysql -h 127.0.0.1 即可访问远程数据库 ``` `-L` 格式为 `本地端口:目标主机:目标端口`,其中"目标主机"是从 SSH 服务端(跳板机)的视角解析的,不是从本地解析——这是最常踩的坑。 ### 远程端口转发(-R) 将远端服务器的某个端口流量,通过 SSH 连接转发到本地指定端口。典型场景:让远端服务器访问你本地运行的服务,比如本地开发环境的 Web 服务需要被远端回调。 ```bash # 让远端服务器通过 8080 端口访问你本地的 Web 服务 ssh -R 8080:localhost:3000 user@remote-server -N # 远端服务器上访问 localhost:8080 即可到达你本地的 3000 端口 ``` 注意:远程转发需要 SSH 服务端开启 `GatewayPorts` 选项,否则默认只绑定到远端的 `localhost`。 ### 动态端口转发(-D) 在本地创建一个 SOCKS5 代理,所有通过该代理的流量都会经由 SSH 连接转发。典型场景:需要通过跳板机访问内网多个服务,不想为每个服务单独配一条 `-L` 规则。 ```bash # 在本地 1080 端口创建 SOCKS5 代理 ssh -D 1080 user@bastion -N # 配置浏览器或 curl 使用 socks5://127.0.0.1:1080 代理 curl --socks5 127.0.0.1:1080 http://internal-web:8080 ``` **选取原则**:访问单个固定服务用 `-L`,需要暴露本地服务给远端用 `-R`,需要灵活访问多个内网服务用 `-D`。`-N` 参数表示不打开远程 shell,仅做端口转发,生产环境推荐加上。 ## 跳板机配置方式 ### ProxyJump(推荐) OpenSSH 7.3+ 支持,是最简洁的配置方式。`-J` 参数直接指定跳板机,SSH 客户端自动处理中间的连接建立,流量端到端加密,中间跳板机无法解密。 ```bash # 命令行 ssh -J jump-user@jump-host:22 target-user@target-host # 配置文件(~/.ssh/config) Host jump-host HostName jump.example.com User jump-user Host target-host HostName target.example.com User target-user ProxyJump jump-host ``` 配置完成后,`ssh target-host` 即可自动经跳板机连接目标服务器,scp、rsync、sftp 等工具同样适用。 ### ProxyCommand(兼容旧版本) 适用于 OpenSSH 7.3 以下版本,通过 `ssh -W` 参数在跳板机上建立 TCP 转发通道。 ```bash # 命令行 ssh -o ProxyCommand="ssh -W %h:%p jump-user@jump-host" target-user@target-host # 配置文件 Host target-host HostName target.example.com User target-user ProxyCommand ssh -W %h:%p jump-user@jump-host ``` `%h` 和 `%p` 是占位符,分别代表最终目标的主机名和端口,SSH 客户端会自动替换。ProxyCommand 的优势是可以在命令中嵌入更复杂的逻辑,比如根据网络环境选择不同的跳板路径。 ### 多级跳板配置 当网络拓扑存在多层隔离(如 DMZ → 应用区 → 数据区)时,需要串联多个跳板机。ProxyJump 天然支持链式配置。 ```bash # 命令行:两级跳板,用逗号分隔 ssh -J jump1@host1,jump2@host2 target@final-host # 配置文件:逐层声明依赖关系 Host jump1 HostName host1.example.com User jump1 Host jump2 HostName host2.example.com User jump2 ProxyJump jump1 Host final HostName final.example.com User target ProxyJump jump2 ``` 关键在于配置文件中的"依赖链":`final → jump2 → jump1`。每层只需声明自己的上一跳,SSH 会递归建立连接。所有经过跳板机的流量都是端到端加密的,中间跳板机只能看到加密数据流。 ## 典型使用场景 ### 通过跳板机访问内网数据库 结合本地端口转发和 ProxyJump,将内网数据库映射到本地端口,用本地客户端工具直连。 ```bash ssh -L 3306:db-server:3306 -J bastion@bastion.example.com user@bastion.example.com -N ``` 连接后,本地 `mysql -h 127.0.0.1 -P 3306` 直接访问远程数据库,对本地客户端完全透明。 ### 通过跳板机传输文件 scp 和 rsync 原生支持 ProxyJump,无需额外配置。 ```bash # scp 传输 scp -o ProxyJump="bastion@bastion.example.com" local-file admin@internal-server:/path/ # rsync 同步 rsync -avz -e "ssh -J bastion@bastion.example.com" ./src/ admin@internal-server:/path/ ``` ### 用 SOCKS 代理浏览内网 Web 服务 当你需要通过浏览器访问内网多个 Web 应用时,动态转发比逐一配置本地端口更方便。 ```bash ssh -D 1080 -J bastion@bastion.example.com user@bastion.example.com -N ``` 在浏览器代理设置中配置 SOCKS5:`127.0.0.1:1080`,即可访问所有内网 Web 服务。Chrome 可配合 SwitchyOmega 插件实现按规则自动代理。 ### 用配置文件简化日常操作 将常用服务器和跳板关系写入 `~/.ssh/config`,日常操作只需 `ssh <别名>`。 ```bash Host bastion HostName bastion.example.com User bastion IdentityFile ~/.ssh/bastion_key Host web-server HostName 192.168.1.100 User webadmin ProxyJump bastion IdentityFile ~/.ssh/internal_key Host db-server HostName 192.168.1.200 User dbadmin ProxyJump bastion IdentityFile ~/.ssh/internal_key ``` 这样 `ssh web-server` 和 `ssh db-server` 都会自动经 bastion 跳转,VS Code Remote-SSH 也会自动读取该配置。 ## 密钥转发与安全要点 ### SSH Agent 转发 当跳板机不允许存放私钥时,可以通过 Agent 转发在跳板机上使用本地的 SSH 密钥,私钥始终留在本地。 ```bash ssh -A -J bastion@bastion.example.com target@internal-server ``` 安全提醒:Agent 转发存在风险——如果跳板机被入侵,攻击者可以利用转发的 Agent 连接你能访问的其他服务器。仅在可信跳板机上启用,用完及时关闭。 ### 跳板机加固配置 ```bash # /etc/ssh/sshd_config PasswordAuthentication no # 禁用密码登录 PermitRootLogin no # 禁止 root 登录 AllowUsers bastion # 限制可登录用户 MaxAuthTries 3 # 限制认证尝试次数 ClientAliveInterval 300 # 空闲连接超时检测 ``` 更完善的方案是部署堡垒机(如 Teleport、JumpServer),提供会话录制、访问审计和多因素认证。 ### 密钥管理实践 - 跳板机和目标服务器使用不同的密钥对,单密钥泄露不会影响全局 - 私钥必须设置密码短语(passphrase),用 `ssh-keygen -p` 添加 - 定期轮换密钥,使用 `ssh-keygen -R` 清理旧授权 - 用 `ssh-add -l` 检查 Agent 中加载的密钥,避免残留 ## 连接复用与性能优化 每次建立 SSH 连接都要完成 TCP 握手和密钥交换,频繁连接时开销明显。SSH 支持连接复用(Multiplexing),复用已有连接的通道建立新会话,后续连接几乎瞬时完成。 ```bash # ~/.ssh/config Host * ControlMaster auto ControlPath ~/.ssh/cm-%r@%h:%p ControlPersist 600 ``` `ControlPersist 600` 让最后一个会话关闭后连接保持 600 秒,避免频繁重建。其他优化手段:`-C` 启用压缩(低带宽高延迟网络有效),`ServerAliveInterval 60` 保持心跳防止连接断开。 ## 常见问题排查 连接失败时,`-v` / `-vv` / `-vvv` 是第一工具,逐级增加调试信息量。 ```bash ssh -vvv -J jump@jump-host target@target-host ``` **常见错误及对策**: - **`Permission denied (publickey)`**:密钥未正确配置。检查 `ssh-add -l` 是否加载了对应密钥,目标服务器的 `~/.ssh/authorized_keys` 是否包含公钥,文件权限是否正确(目录 700,authorized_keys 600) - **`Connection refused`**:目标 SSH 服务未运行或防火墙阻止。检查 `systemctl status sshd` 和端口开放情况 - **`open failed: connect failed`**:端口转发目标不可达。从跳板机上 `telnet 目标 端口` 确认能否连通 - **`Broken pipe`**:连接空闲超时断开。配置 `ServerAliveInterval 60` 和 `ServerAliveCountMax 3` 保持心跳 - **配置文件不生效**:`~/.ssh/config` 的缩进必须用空格不能用 Tab;`~/.ssh/` 目录权限 700;`config` 文件权限 644
服务端5月28日 00:52
如何使用 SSH 进行自动化运维?有哪些常用的自动化工具和脚本?SSH 自动化运维是后端工程师和运维人员的高频工作场景。无论是批量部署、定时巡检还是故障恢复,SSH 都是底层通道。下面从工具选型、实战脚本到生产注意事项,系统梳理 SSH 自动化运维的核心知识。 ## 核心答案:SSH 自动化运维的常用工具 SSH 自动化运维工具按复杂度可分为三层: - **轻量脚本层**:Shell 脚本 + SSH 原生命令,适合简单批量操作 - **编程封装层**:Fabric、Paramiko、Pexpect,适合需要逻辑判断的任务 - **配置管理层**:Ansible、SaltStack(SSH 模式),适合大规模集群管理 选型依据:服务器规模 < 10 台用 Shell 脚本即可;10-100 台用 Fabric;100+ 台上 Ansible。三者都基于 SSH 协议,无需在目标机器装代理。 ## Ansible:最主流的 SSH 自动化工具 Ansible 是当前使用最广泛的 SSH 自动化配置管理工具,核心优势是无代理(Agentless),通过 SSH 连接目标机器执行任务。 ### 安装与连接配置 ```bash # 安装 pip install ansible # 配置主机清单 /etc/ansible/hosts [webservers] web1.example.com web2.example.com [dbservers] db1.example.com [all:vars] ansible_user=admin ansible_ssh_private_key_file=~/.ssh/ansible_key ansible_ssh_common_args=-o StrictHostKeyChecking=no ``` 连接验证: ```bash ansible all -m ping ``` ### Playbook 实战:Nginx 部署 ```yaml # deploy_nginx.yml --- - hosts: webservers become: yes tasks: - name: Install nginx apt: name: nginx state: present update_cache: yes - name: Deploy config template: src: templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: reload nginx - name: Ensure nginx is running service: name: nginx state: started enabled: yes handlers: - name: reload nginx service: name: nginx state: reloaded ``` 执行:`ansible-playbook deploy_nginx.yml` ### Ansible 的关键概念 - **幂等性**:同一个 Playbook 执行多次,结果一致。`apt: state=present` 已安装则跳过,不会重复安装 - **Handler**:只在被 notify 触发时执行,且在所有 task 结束后统一执行,适合重启服务这类操作 - **变量与模板**:Jinja2 模板渲染配置文件,不同环境用不同变量文件(`group_vars/`、`host_vars/`) ## Fabric:Python 族的 SSH 自动化利器 Fabric 是基于 Paramiko 的 Python 库,用 Python 函数定义任务,适合需要条件判断、异常处理的自动化场景。 ### 安装与基本用法 ```bash pip install fabric ``` ```python # fabfile.py from fabric import Connection, task @task def deploy(c): """Deploy app to remote server""" conn = Connection('user@web1.example.com') conn.run('cd /var/www/myapp && git pull origin main') conn.run('pip install -r requirements.txt') conn.sudo('systemctl restart myapp') @task def check_disk(c, host): """Check remote disk usage""" conn = Connection(f'admin@{host}') result = conn.run('df -h / --output=pcent | tail -1', hide=True) usage = int(result.stdout.strip().replace('%', '')) if usage > 80: print(f"WARNING: {host} disk usage {usage}%") else: print(f"OK: {host} disk usage {usage}%") ``` 运行:`fab deploy` 或 `fab check_disk --host=web1.example.com` ### Fabric 的适用场景 Fabric 的优势在于你可以用 Python 写任意逻辑:根据返回值分支、调用 API、发送通知。这在 Shell 脚本里会很啰嗦。适合中小规模(几十台)的运维自动化,尤其是团队已经用 Python 的场景。 ## Shell 脚本:最直接的 SSH 批处理方式 不想引入额外依赖时,Shell 脚本 + SSH 是最简方案。 ### 并行批量执行 ```bash #!/bin/bash # parallel_ssh.sh SERVERS=( "admin@web1.example.com" "admin@web2.example.com" "admin@web3.example.com" ) SSH_OPTS="-i ~/.ssh/batch_key -o ConnectTimeout=10 -o StrictHostKeyChecking=no" for server in "${SERVERS[@]}"; do ssh $SSH_OPTS "$server" "$1" & done wait echo "All done." ``` 使用:`./parallel_ssh.sh "uptime"` 或 `./parallel_ssh.sh "systemctl restart nginx"` ### 带错误处理和日志的脚本 ```bash #!/bin/bash # batch_deploy.sh set -euo pipefail LOG="/var/log/batch_deploy.log" SERVERS_FILE="servers.txt" BRANCH="${1:-main}" log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; } failed=0 while read -r server; do log "Deploying to $server (branch=$BRANCH)..." if ssh -o ConnectTimeout=10 "$server" bash -s <<DEPLOY set -e cd /var/www/app git fetch origin && git reset --hard "origin/$BRANCH" npm ci && npm run build pm2 restart app DEPLOY then log "SUCCESS: $server" else log "FAILED: $server" ((failed++)) fi done < "$SERVERS_FILE" log "Finished. Failed: $failed" [ $failed -eq 0 ] || exit 1 ``` ### 用 SSH ControlMaster 加速连接 每次 SSH 都要建 TCP 连接和密钥交换,批量操作时开销很大。开启 ControlMaster 复用连接: ```bash # ~/.ssh/config Host * ControlMaster auto ControlPath ~/.ssh/sockets/%r@%h-%p ControlPersist 600 ``` ```bash mkdir -p ~/.ssh/sockets ``` 首次连接建立 socket,后续连接复用同一 socket,速度提升明显。 ## Pexpect:处理交互式 SSH 会话 有些 SSH 操作需要交互(输入密码、确认 yes/no),Pexpect 可以自动化这些交互。 ```python import pexpect def ssh_with_password(host, user, password, command): """Auto-handle password prompt in SSH session""" child = pexpect.spawn(f'ssh {user}@{host}', timeout=30) i = child.expect(['password:', 'Are you sure you want to continue connecting']) if i == 1: child.sendline('yes') child.expect('password:') child.sendline(password) child.expect(r'\$') child.sendline(command) child.expect(r'\$') print(child.before.decode().strip()) child.sendline('exit') child.close() ``` 注意:生产环境应优先使用密钥认证而非密码。Pexpect 更适合测试环境或遗留系统的临时自动化。 ## Paramiko:纯 Python SSH 库 Paramiko 是 Fabric 的底层依赖,也可以直接使用,适合需要精细控制 SSH 连接的场景。 ```python import paramiko def batch_check(servers, command): """Batch execute command and collect results""" results = {} for server in servers: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(server['host'], username=server['user'], key_filename=server.get('key')) stdin, stdout, stderr = ssh.exec_command(command) results[server['host']] = stdout.read().decode().strip() except Exception as e: results[server['host']] = f"ERROR: {e}" finally: ssh.close() return results servers = [ {'host': 'web1.example.com', 'user': 'admin', 'key': '~/.ssh/id_ed25519'}, {'host': 'web2.example.com', 'user': 'admin', 'key': '~/.ssh/id_ed25519'}, ] print(batch_check(servers, 'uptime')) ``` ## 生产环境的安全加固 SSH 自动化的安全是重中之重,以下是必须做的加固措施。 ### 密钥认证 + 最小权限 ```bash # 生成专用密钥(不要用默认 id_rsa) ssh-keygen -t ed25519 -f ~/.ssh/automation_key -C "automation@deploy" # 在目标机器限制密钥权限 # ~/.ssh/authorized_keys command="/usr/local/bin/automation-wrapper.sh",no-port-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3... ``` `command=` 限制该密钥只能执行指定脚本,即使密钥泄露也无法执行任意命令。 ### SSH 服务端配置 ```bash # /etc/ssh/sshd_config 关键配置 PermitRootLogin no # 禁止 root 登录 PasswordAuthentication no # 禁用密码认证 MaxAuthTries 3 # 限制尝试次数 AllowUsers admin deploy # 白名单用户 ClientAliveInterval 300 # 空闲超时 ClientAliveCountMax 2 # 超时次数 ``` ### 跳板机/堡垒机架构 生产环境不应让自动化脚本直连服务器,而应通过跳板机: ```bash # ~/.ssh/config 配置跳板机 Host bastion HostName jump.example.com User jump_user Host web-* ProxyJump bastion User admin ``` 这样所有连接都经过审计和管控。 ## 工具对比与选型 | 维度 | Shell 脚本 | Fabric | Ansible | |------|-----------|--------|---------| | 学习成本 | 低 | 中(需会 Python) | 中(需学 YAML + 模块) | | 适用规模 | <10 台 | 10-100 台 | 100+ 台 | | 幂等性 | 需手动保证 | 需手动保证 | 内置支持 | | 错误处理 | set -e 粗粒度 | Python try/except | 模块级失败检测 | | 配置管理 | 不支持 | 不支持 | 完整支持 | | 依赖 | 无 | Python + pip | Python + pip | | 生态 | 无 | 一般 | 丰富(Galaxy) | 选型建议:个人项目或临时任务用 Shell;Python 团队做部署自动化用 Fabric;企业级集群管理用 Ansible。三者可以混合使用,不是互斥关系。 ## 自动化运维实战场景 ### 批量部署 多台服务器同时部署应用是最常见的 SSH 自动化场景。用 Ansible 实现时,Playbook 天然支持批量执行和回滚: ```yaml # app_deploy.yml --- - hosts: webservers become: yes serial: 2 # 每次部署2台,降低风险 tasks: - name: Create backup archive: path: /var/www/app dest: "/backup/app_{{ ansible_date_time.iso8601 }}.tar.gz" - name: Pull latest code git: repo: https://github.com/user/app.git dest: /var/www/app version: "{{ deploy_version | default('main') }}" - name: Install dependencies command: npm ci args: chdir: /var/www/app - name: Build and restart shell: npm run build && pm2 restart app args: chdir: /var/www/app ``` `serial: 2` 实现滚动部署,避免所有服务器同时不可用。如果前两台部署失败,后续不会继续。 ### 定时巡检与告警 用 cron + Shell 脚本实现轻量巡检,异常时通过企业微信或钉钉推送告警: ```bash #!/bin/bash # health_check.sh WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" SERVERS=("web1" "web2" "db1") for server in "${SERVERS[@]}"; do # 检查 CPU 负载 load=$(ssh -o ConnectTimeout=5 "$server" "cat /proc/loadavg | awk '{print \$1}'") threshold=4.0 if (( $(echo "$load > $threshold" | bc -l) )); then curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' \ -d "{\"content\":\"ALERT: $server load=$load (> $threshold)\"}" fi # 检查磁盘 disk_pct=$(ssh -o ConnectTimeout=5 "$server" "df -h / | awk 'NR==2{print \$5}' | tr -d '%'") if [ "$disk_pct" -gt 85 ]; then curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' \ -d "{\"content\":\"ALERT: $server disk=${disk_pct}%\"}" fi done ``` 配合 cron 每分钟执行:`* * * * * /opt/scripts/health_check.sh` ### 自动备份与清理 ```bash #!/bin/bash # auto_backup.sh set -euo pipefail BACKUP_DIR="/backup" DATE=$(date +%Y%m%d) RETENTION=7 for server in db1 db2; do ssh admin@$server "mysqldump -u root --all-databases --single-transaction | gzip" \ > "$BACKUP_DIR/${server}_${DATE}.sql.gz" done # 保留最近7天备份 find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION -delete echo "[$(date)] Backup completed" >> /var/log/backup.log ``` `--single-transaction` 保证 InnoDB 备份一致性,不锁表。 ## 自动化运维的常见踩坑 **连接超时导致脚本卡死**:SSH 默认没有连接超时,批量操作时一台机器卡住整个脚本就挂了。解决:始终设置 `ConnectTimeout`,Shell 用 `timeout` 命令包裹,Python 用 `paramiko.SSHClient().connect(timeout=10)`。 **known_hosts 拒绝连接**:首次连接新机器时 SSH 会要求确认指纹,自动化脚本会卡住。解决:`StrictHostKeyChecking=no`(测试环境)或在跳板机上预置 known_hosts。 **并发连接数过多被拒**:目标机器的 `MaxStartups` 限制了并发 SSH 连接数。Ansible 默认 5 个 fork,可通过 `-f` 调整。大批量操作建议分批执行。 **密码写死在脚本里**:这是最常见的安全隐患。应该用密钥认证,密钥用 ssh-agent 管理,不写进代码。 **非幂等操作导致重复执行**:比如部署脚本每次都执行数据库迁移,可能导致重复操作。Ansible 的模块天生幂等,Shell/Fabric 需要自己判断状态再执行。 SSH 自动化运维从 Shell 脚本到 Ansible,复杂度递增但能力也递增。掌握工具选型、安全加固和常见陷阱,才能在生产环境用得稳、用得安全。
服务端5月28日 00:52
什么是 SSH 协议?它有哪些主要功能和工作原理?SSH(Secure Shell)是一种加密网络协议,用于在不安全的网络中安全地进行远程登录和其他网络服务。它替代了 Telnet、FTP 等明文传输协议,是目前 Linux/Unix 远程管理的标准工具。 ## 核心功能 - **远程登录**:通过加密通道登录远程服务器执行命令 - **文件传输**:SFTP 和 SCP 提供安全的文件收发 - **端口转发**:建立加密隧道,将本地或远程端口流量通过 SSH 隧道转发,实现安全代理 - **X11 转发**:在本地显示远程图形界面应用 ## 工作原理 SSH 采用客户端-服务器架构,一次完整的连接建立分为五个阶段: 1. **版本协商**:客户端连接服务器 22 端口后,双方交换版本号,协商使用 SSHv1 还是 SSHv2。当前生产环境应统一使用 SSHv2,SSHv1 已存在已知安全漏洞。 2. **算法协商**:双方交换各自支持的算法列表,按优先级选出共同支持的最强算法,包括密钥交换算法(ECDH、Diffie-Hellman)、对称加密算法(AES-256-GCM、ChaCha20-Poly1305)、公钥算法(RSA、ECDSA、Ed25519)和 HMAC 算法。 3. **密钥交换**:通过 Diffie-Hellman 或 ECDH 算法,双方在不直接传输密钥的情况下协商出相同的会话密钥(Session Key),后续所有通信都用该密钥加密。此阶段还会生成会话 ID,用于后续认证过程。 4. **身份认证**:客户端向服务器证明自己的身份,主要两种方式: - **密码认证**:直接输入用户名密码,简单但易被暴力破解 - **公钥认证**:客户端持有私钥,服务器持有对应公钥。客户端用私钥签名一段数据,服务器用公钥验证。安全性更高,推荐生产使用 5. **会话交互**:认证通过后客户端请求建立会话,服务器分配资源,双方开始加密通信。 ## 关键安全机制 - **主机密钥验证**:首次连接时服务器发送主机公钥,客户端将其存入 known_hosts 文件。后续连接时核对该公钥,若变化则发出警告,防止中间人攻击 - **前向保密**:SSHv2 使用临时密钥交换,即使长期私钥泄露,历史会话密钥也无法被推算 - **完整性校验**:每个数据包附带 HMAC,确保传输过程中数据未被篡改 ## 端口转发的三种类型 - **本地转发**(-L):将本地端口映射到远程服务器可达的某个地址,例如将本地 3306 端口安全访问内网数据库 - **远程转发**(-R):将远程端口映射回本地,常用于内网穿透 - **动态转发**(-D):创建 SOCKS 代理,按需转发流量 ## 常用命令 ```bash # 基本连接 ssh user@hostname # 指定端口 ssh -p 2222 user@hostname # 使用密钥认证 ssh -i ~/.ssh/id_ed25519 user@hostname # 本地端口转发 ssh -L 8080:localhost:80 user@remote # SCP 文件传输 scp file.txt user@hostname:/tmp/ ``` ## 生产环境安全加固 - 禁用密码登录,仅允许公钥认证(PasswordAuthentication no) - 禁用 root 直接登录(PermitRootLogin no) - 更换默认端口减少扫描 - 使用 Ed25519 替代 RSA 生成密钥对(更短更安全) - 配置 AllowUsers 限制可登录用户 SSH 的安全性建立在加密通信和密钥验证之上,理解其连接建立过程和认证机制是运维和后端面试的高频考点。
服务端5月28日 00:51
什么是 SSH 连接复用?如何配置和使用连接复用提高性能?SSH 连接复用(Connection Multiplexing)是指复用一条已建立的 SSH 连接来创建新的会话,省去重复的 TCP 握手和密钥交换环节。面试中常考的是三个配置参数:ControlMaster、ControlPath、ControlPersist,以及复用带来的性能收益和潜在风险。 ## 连接复用怎么工作 正常的 SSH 连接每次都要经历 TCP 三次握手、SSH 协议版本协商、Diffie-Hellman 密钥交换、用户认证四个阶段。在延迟较高的网络环境(如跨机房、通过跳板机)中,这个过程可能耗时 1-3 秒。 连接复用的做法是:第一次连接建立后,把这条连接作为"主连接"(master)保持在后台,后续对同一目标的连接直接通过 Unix 域套接字(ControlPath)复用主连接,跳过握手和认证,几乎瞬间完成。 ``` 首次连接: 客户端 --TCP握手--> --密钥交换--> --认证--> 建立主连接 复用连接: 客户端 --通过套接字--> 直接复用主连接(毫秒级) ``` 这带来的性能差异在脚本批量执行远程命令时尤为明显——10 次连接从 15 秒降到 1 秒以内是常见的。 ## 三个核心配置参数 ### ControlMaster 决定是否启用复用以及复用的行为: - `no`:禁用(默认值) - `auto`:如果已有主连接就复用,没有就创建新的——最常用的选项 - `yes`:强制创建主连接,如果已存在则失败 - `ask`:复用前询问用户确认 ### ControlPath 指定 Unix 域套接字文件的路径。支持的占位符: - `%r` — 远程用户名 - `%h` — 主机名 - `%p` — 端口号 - `%C` — 连接参数的 SHA1 哈希(推荐,避免路径过长) ```bash # 常见写法 ControlPath ~/.ssh/cm-%r@%h:%p # 使用 %C 避免路径超长(macOS 上常见问题) ControlPath ~/.ssh/cm-%C ``` 套接字文件路径有长度限制(通常 104-108 字节),路径太长会导致复用失败。使用 `%C` 可以规避这个问题。 ### ControlPersist 控制主连接在最后一个会话关闭后继续存活的时间: - `no`:最后一个会话断开后立即关闭主连接 - `yes`:永久保持,直到手动关闭或网络中断 - `600` / `10m`:保持 10 分钟 - `4h`:保持 4 小时 对于日常开发,`10m` 到 `1h` 是比较合理的范围。设成 `yes` 会导致主连接进程一直驻留,不推荐。 ## 最小可用配置 ```bash # ~/.ssh/config Host * ControlMaster auto ControlPath ~/.ssh/cm-%C ControlPersist 10m ``` 三行配置即可生效。建好配置后,第一次 `ssh user@server` 正常连接,第二次起就能感知到速度差异。 如果只想对特定主机启用: ```bash Host prod-* ControlMaster auto ControlPath ~/.ssh/cm-%C ControlPersist 30m ``` ## 管理复用连接 ```bash # 检查主连接是否存活 ssh -O check user@server # 主动关闭主连接(所有复用会话也会断开) ssh -O exit user@server # 停止接受新的复用请求(已有会话不受影响) ssh -O stop user@server # 查看控制套接字文件 ls -l ~/.ssh/cm-* ``` 网络中断后,残留的套接字文件会导致新连接报错 `Control socket connect: Connection refused`。直接删除即可: ```bash rm -f ~/.ssh/cm-* # 或只删除特定主机的 find ~/.ssh -name "cm-*" -type s -mtime +1 -delete ``` ## 哪些场景收益最大 **批量远程命令执行**——在脚本中循环 `ssh user@server "cmd"`,复用后只有第一次有连接延迟。 **Git over SSH**——`git push` / `git pull` / `git fetch` 走 SSH 时自动受益,频繁提交代码的开发者体感明显。 **跳板机 / ProxyJump**——通过跳板机连接目标机器时,到跳板机的连接可以复用。当 `~/.ssh/config` 中配置了 `ProxyJump` 时,ControlMaster 对跳板机连接同样生效: ```bash Host bastion HostName jump.example.com ControlMaster auto ControlPath ~/.ssh/cm-%C ControlPersist 10m Host internal-* ProxyJump bastion # internal-* 的连接也会触发 bastion 的复用 ``` **rsync / scp 文件传输**——底层走 SSH,同样能复用连接。 ## 需要注意的风险 **单点故障**:主连接断开时,所有复用它的会话同时失效。在长时运行的会话中(如远程编译、持续部署),这意味着一个网络抖动可能打掉所有窗口。 **资源泄漏**:ControlPersist 设为 `yes` 时,主连接的 ssh 进程会一直驻留。长时间运行后可能积累大量僵尸进程和套接字文件。建议设定具体时间。 **权限风险**:控制套接字文件如果权限不当,同一台机器上的其他用户可能劫持你的 SSH 会话。确保 `~/.ssh/` 目录权限为 `700`。 **与某些 SSH 功能冲突**:`-W`(netcat 模式)、`-J`(ProxyJump 的命令行形式)在特定版本下与 ControlMaster 存在兼容性问题,遇到问题时可以先 `ssh -O exit` 清除主连接再试。 ## 面试追问参考 **Q: ControlMaster 设为 auto 和 yes 有什么区别?** `auto` 在没有主连接时自动创建,有则复用;`yes` 强制要求自己成为主连接,如果已有主连接则连接失败。`yes` 适合脚本中明确需要"我是第一个连接"的场景。 **Q: 连接复用会带来安全风险吗?** 会。控制套接字本质上是一个 Unix 域套接字,本地有权限的用户理论上可以通过它建立 SSH 会话。所以必须保证套接字路径的目录权限正确(700),不要放在 `/tmp` 等公共目录。 **Q: 主连接断了怎么办?** 所有复用该主连接的会话都会立即断开。需要删除残留的套接字文件后重新建立连接。这也是为什么不建议在生产环境的关键操作中过度依赖复用。
服务端5月28日 00:49
什么是 SSH 证书认证?如何配置和管理 SSH 证书?SSH 证书认证用 CA(证书颁发机构)对用户或主机公钥进行签名,生成带有效期和身份信息的证书,服务器只需信任 CA 公钥即可验证所有由该 CA 签发的证书。相比手动分发 authorized_keys,证书方式在大规模环境下管理成本更低、安全性更强。 ## 为什么用证书而不是密钥? 传统 SSH 密钥认证的痛点在于:每台服务器都要维护 authorized_keys 文件,用户入职要在所有服务器上添加公钥,离职要逐台删除——服务器一多就是运维噩梦。证书认证从根本上解决了这个问题: - **服务器不存用户公钥**,只配置一条 `TrustedUserCAKeys` 指向 CA 公钥 - **证书自带有效期**,到期自动失效,不存在"永不过期的密钥" - **撤销只需更新列表**,不用逐台机器删 authorized_keys - **可限制权限**,比如只允许从特定 IP 连接、只能执行指定命令 Meta、Uber、Google 等公司内部都在用 SSH 证书方案管理数万台服务器的访问权限。 ## 证书认证的工作原理 核心流程只有三步: 1. **建立 CA**:生成一对 CA 密钥,私钥严格保管(建议离线存储或用 HSM),公钥分发到所有需要信任该 CA 的服务器 2. **签发证书**:用 CA 私钥对用户的公钥签名,生成包含身份标识(Key ID)、授权主体(Principals)、有效期等信息的证书文件 3. **验证连接**:用户 SSH 连接时出示证书,服务器用本地配置的 CA 公钥验证签名,检查有效期和 Principals 后放行 ## 搭建 CA 并签发用户证书 ### 生成 CA 密钥对 ```bash # 用户 CA(用于签发用户证书) ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "User CA" # 主机 CA(用于签发主机证书,可选) ssh-keygen -t ed25519 -f /etc/ssh/ca_host_key -C "Host CA" ``` CA 私钥权限必须设为 600,且只在签发证书时使用。生产环境建议将 CA 私钥存放在离线机器或 HSM 中。 ### 签发用户证书 ```bash ssh-keygen -s /etc/ssh/ca_user_key \ -I "user_zhangsan" \ -n "zhangsan" \ -V +52w \ -z 1 \ ~/.ssh/zhangsan_key.pub ``` 参数含义: - `-I`:证书的身份标识,用于日志审计,建议用 `user_用户名` 格式 - `-n`:Principals,允许登录的系统用户名,多个用逗号分隔 - `-V`:有效期,`+52w` 表示 52 周,也可写 `+365d`、`20240101-20250101` 等 - `-z`:证书序列号,用于撤销时定位,每次签发应递增 签发后生成 `zhangsan_key-cert.pub` 文件,用户连接时需同时持有私钥和此证书文件。 ### 签发主机证书 主机证书解决的是"首次连接时如何确认服务器身份"的问题,避免中间人攻击和 `known_hosts` 的手动维护。 ```bash ssh-keygen -s /etc/ssh/ca_host_key \ -I "host_web01" \ -h \ -n "web01.example.com,10.0.1.50" \ -V +52w \ /etc/ssh/ssh_host_ed25519_key.pub ``` `-h` 标志区分主机证书和用户证书。 ## 服务器端配置 ### 信任用户 CA 在 `/etc/ssh/sshd_config` 中添加: ```bash # 信任用户 CA,所有由该 CA 签发的用户证书均被接受 TrustedUserCAKeys /etc/ssh/ca_user_key.pub # 可选:限制每个用户可用的 Principals AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u # 确保公钥认证开启 PubkeyAuthentication yes ``` `AuthorizedPrincipalsFile` 的作用是限制哪些 Principal 可以映射到当前系统用户。比如 `/etc/ssh/auth_principals/root` 文件内容为 `admin ops`,那么只有证书中 Principals 包含 `admin` 或 `ops` 的才能以 root 登录。 ### 部署主机证书 ```bash # 确认主机证书文件在位 ls /etc/ssh/ssh_host_ed25519_key-cert.pub # 在 sshd_config 中指定主机证书 HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub # 重启 SSH 服务 systemctl restart sshd ``` **重要**:修改 sshd_config 后务必在另一个终端保留当前连接,先用新终端测试证书登录成功再关闭旧连接,防止配置错误锁死自己。 ## 客户端配置 ### 使用证书连接 证书文件和私钥放在同一目录,文件名遵循 OpenSSH 约定(私钥 `id_ed25519`,证书 `id_ed25519-cert.pub`)时无需额外配置: ```bash ssh zhangsan@web01.example.com ``` 如果证书文件名不是默认约定,可以手动指定: ```bash # 命令行指定 ssh -i ~/.ssh/zhangsan_key -o CertificateFile=~/.ssh/zhangsan_key-cert.pub zhangsan@web01 # 或写入 ~/.ssh/config Host web01 HostName web01.example.com User zhangsan IdentityFile ~/.ssh/zhangsan_key CertificateFile ~/.ssh/zhangsan_key-cert.pub ``` ### 信任主机 CA 在 `~/.ssh/known_hosts` 中添加一行: ``` @cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ``` 这表示所有 `*.example.com` 域名下由该 CA 签发的主机证书都被信任,不再需要逐台确认指纹。 ## 证书撤销 ### 撤销列表(RevokedKeys) SSH 证书的撤销不像 TLS 那样有 OCSP/CRL 协议,而是使用一个简单的密钥列表文件。 ```bash # 创建撤销列表文件,每行一个要撤销的公钥 cat > /etc/ssh/revoked_keys << 'EOF' ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... compromised_key ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... terminated_user EOF # 也可以直接从证书文件生成撤销条目 ssh-keygen -k -f /etc/ssh/revoked_keys ~/.ssh/zhangsan_key-cert.pub ``` 然后在 `sshd_config` 中配置: ```bash RevokedKeys /etc/ssh/revoked_keys ``` **注意**:`RevokedKeys` 文件中存放的是公钥或证书的原始内容,不是序列号。这是常见的误区——OpenSSH 的撤销列表不是 `serial:reason` 格式,而是标准的公钥列表格式。每次撤销后需重启或 reload sshd 才能生效。 ### 更实用的做法:缩短有效期 与其维护撤销列表,不如一开始就把用户证书有效期设短(如 24 小时),配合自动化签发服务(如 HashiCorp Vault),用户每次连接前自动获取新证书。证书天然过期,撤销列表的维护压力就小很多。 ## 高级权限控制 ### 限制证书能力 ```bash # 签发受限证书:只能执行部署脚本 ssh-keygen -s /etc/ssh/ca_user_key \ -I "deploy_ci" \ -n "deploy" \ -V +1d \ -O clear \ -O no-port-forwarding \ -O no-X11-forwarding \ -O force-command=/usr/local/bin/deploy.sh \ -O source-address=10.0.0.0/8 \ ~/.ssh/deploy_key.pub ``` 关键选项: - `-O clear`:清除所有默认权限(包括 pty、port-forwarding 等),之后再逐项添加需要的权限 - `-O force-command=...`:限制只能执行指定命令,适合 CI/CD 场景 - `-O source-address=...`:限制来源 IP 段 - `-O no-pty`:禁止分配终端,适合自动化脚本 ### 按角色签发不同证书 ```bash # 管理员:完整权限,有效期较长 ssh-keygen -s $CA_KEY -I "admin_lisi" -n "root,lisi" -V +4w \ -O permit-pty admin_key.pub # 只读巡检:无 pty,只能看 ssh-keygen -s $CA_KEY -I "readonly_wangwu" -n "readonly" -V +1w \ -O no-pty readonly_key.pub # CI/CD 部署:限定命令和 IP ssh-keygen -s $CA_KEY -I "cicd_gitlab" -n "deploy" -V +1h \ -O clear -O force-command=/usr/local/bin/deploy.sh \ -O source-address=10.1.0.0/16 cicd_key.pub ``` ## 证书查看与审计 ```bash # 查看证书详细信息 ssh-keygen -L -f ~/.ssh/zhangsan_key-cert.pub ``` 输出示例: ``` Type: ssh-ed25519-cert-v01@openssh.com user certificate Public key: ED25519-CERT SHA256:abc123... Signing CA: ED25519 SHA256:def456... (using ssh-ed25519) Key ID: "user_zhangsan" Serial: 1 Valid: from 2024-01-15T10:00:00 to 2025-01-13T10:00:00 Principals: zhangsan Critical Options: (none) Extensions: permit-X11-forwarding permit-agent-forwarding permit-port-forwarding permit-pty permit-user-rc ``` 通过 Key ID 和 Serial 可以追踪证书签发记录,配合日志系统实现访问审计。 ## 与 HashiCorp Vault 集成 手动签发证书在小型环境可行,但用户多了需要自动化。Vault 的 SSH Secrets Engine 可以按需签发短期证书: ```bash # 启用 SSH secrets engine vault secrets enable ssh # 配置 CA vault write ssh/config/ca generate_signing_key=true # 配置角色:开发环境,1 小时有效期 vault write ssh/roles/dev \ key_type=ca \ allowed_users="*" \ default_user="dev" \ ttl="1h" # 用户申请证书 vault write -field=signed_key ssh/sign/dev \ public_key=@$HOME/.ssh/id_ed25519.pub \ > $HOME/.ssh/id_ed25519-cert.pub ``` Vault 的优势:证书有效期短(通常 1 小时到 1 天),每次按需签发,自动记录审计日志,无需手动维护 CA 私钥的安全。 ## 常见问题排查 **连接时提示 `Permission denied (publickey)`** - 检查证书是否过期:`ssh-keygen -L -f cert.pub` 查看 Valid 字段 - 检查 Principals 是否匹配:证书中的 `-n` 值必须出现在目标用户的 AuthorizedPrincipalsFile 中 - 检查证书是否被撤销:查看服务器 `RevokedKeys` 文件 - 检查 sshd 是否加载了 CA 公钥:`sshd -T | grep trustedusercakeys` **首次连接仍提示确认指纹** - 客户端 `known_hosts` 中的 `@cert-authority` 行未正确配置,或域名通配符不匹配 - 主机证书未在 `sshd_config` 中用 `HostCertificate` 指定 **证书签发后立即失效** - `-V` 参数的时区问题:服务器时间与签发机器时间不一致 - 序列号冲突:同一序列号签发多张证书可能导致问题 ## 最佳实践总结 1. **CA 私钥离线保管**:只在签发时使用,日常不放在可达的网络中 2. **用户证书有效期不超过 1 天**:配合 Vault 等工具实现按需签发 3. **主机证书有效期可设 1 年**:主机证书变更频率低 4. **用 AuthorizedPrincipalsFile 做细粒度控制**:不同角色映射不同系统用户 5. **CI/CD 用 force-command 限定命令**:防止部署密钥被滥用 6. **保留密码登录作为兜底**:直到确认证书方案完全跑通再关闭密码认证 7. **证书签发流程自动化**:手动签发容易出错且不可审计
服务端5月28日 00:47
SSH 端口转发有哪些类型?本地转发、远程转发和动态转发怎么用?SSH 端口转发(Port Forwarding),也叫 SSH 隧道(SSH Tunneling),是通过 SSH 加密连接转发任意 TCP 流量的技术。它能让不安全的协议获得加密保护,也能穿透网络限制访问内网服务。SSH 端口转发有三种类型:本地转发(-L)、远程转发(-R)和动态转发(-D),三者数据流向和使用场景各不相同。 ## 本地端口转发(-L) 本地端口转发将本地某个端口的流量,经 SSH 隧道转发到远程服务器可达的目标地址。换句话说,你访问本机的一个端口,数据会自动通过 SSH 加密隧道到达远端目标。 **数据流向**:本机应用 → 本地端口 → SSH 隧道 → SSH 服务器 → 目标主机:目标端口 ```bash # 语法 ssh -L [本地地址:]本地端口:目标主机:目标端口 用户@SSH服务器 # 访问远程 MySQL ssh -L 3306:localhost:3306 user@remote-server # 现在连接 localhost:3306 等同于连接远程服务器的 MySQL # 通过跳板机访问内网服务 ssh -L 8080:192.168.1.50:80 user@jump-host # 本机访问 localhost:8080 → jump-host 转发 → 192.168.1.50:80 ``` 注意 `-L` 后面的「目标主机:目标端口」是从 SSH 服务器的视角解析的,所以 `localhost` 指的是 SSH 服务器自身。这是理解本地转发的关键——目标地址是远端网络中的地址,不是你本机的地址。 **典型场景**:远程数据库只有内网可访问,你在外网通过 SSH 跳板机建立本地转发,即可用本地客户端直连远程数据库。 ## 远程端口转发(-R) 远程端口转发与本地转发方向相反:把远程服务器上的某个端口流量,经 SSH 隧道转发回本机可达的目标地址。这在本地服务需要暴露给远程网络时使用。 **数据流向**:远程客户端 → 远程端口 → SSH 隧道 → 本机 → 目标主机:目标端口 ```bash # 语法 ssh -R [远程地址:]远程端口:目标主机:目标端口 用户@SSH服务器 # 让远程服务器能访问你本地的 Web 服务 ssh -R 8080:localhost:3000 user@public-server # 他人访问 public-server:8080 → SSH 隧道 → 你本机的 localhost:3000 # 绑定到远程服务器的所有网络接口 ssh -R 0.0.0.0:8080:localhost:3000 user@public-server ``` 远程转发默认只绑定到远程服务器的 127.0.0.1,外部无法连接。要让其他机器也能通过该端口访问,需要在远程服务器的 `/etc/ssh/sshd_config` 中设置 `GatewayPorts yes`,然后重启 sshd。 **典型场景**:本地开发了一个 Web 应用,需要临时让外部人员预览,但本机没有公网 IP。通过远程转发把本地服务映射到公网服务器的端口上,外部即可访问。 ## 动态端口转发(-D) 动态端口转发在本地创建一个 SOCKS 代理端口,根据应用层协议动态决定流量转发目标。与本地转发只能指定一个固定目标不同,动态转发支持任意目标。 **数据流向**:本机应用(SOCKS 客户端)→ 本地 SOCKS 端口 → SSH 隧道 → SSH 服务器 → 任意目标 ```bash # 语法 ssh -D [本地地址:]本地端口 用户@SSH服务器 # 创建 SOCKS5 代理 ssh -D 1080 user@proxy-server # 配置浏览器或系统代理为 socks5://127.0.0.1:1080 ``` 动态转发本质上把 SSH 服务器变成了一个代理服务器,所有通过 SOCKS5 协议发出的请求都由 SSH 服务器代为访问,再把结果加密返回。 **典型场景**:在不安全的网络环境中,通过 SSH 服务器代理所有流量,确保通信加密且无法被中间人窃听。 ## 三种转发的区别与选择 | 对比项 | 本地转发 -L | 远程转发 -R | 动态转发 -D | |--------|------------|------------|------------| | 数据方向 | 本机→远端 | 远端→本机 | 本机→远端(动态目标) | | 目标数量 | 固定一个 | 固定一个 | 任意多个 | | 协议支持 | 任意 TCP | 任意 TCP | SOCKS5 代理 | | 典型用途 | 访问内网服务 | 内网穿透 | 安全代理/翻墙 | 选择原则:访问特定内网服务用 -L,暴露本地服务用 -R,需要灵活代理多种目标用 -D。 ## SSH 配置文件简化操作 频繁使用端口转发时,可以在 `~/.ssh/config` 中预设,避免每次输入长命令: ``` Host db-tunnel HostName jump.example.com User deploy LocalForward 3306 db-server:3306 ServerAliveInterval 60 ServerAliveCountMax 3 Host dev-expose HostName public.example.com User deploy RemoteForward 8080 localhost:3000 ``` 使用时只需 `ssh db-tunnel` 或 `ssh dev-expose`,转发规则自动生效。 ## 常用参数组合 ```bash # -N: 不执行远程命令,只做端口转发 # -f: 后台运行 # -C: 启用压缩 # 后台运行本地转发 ssh -f -N -L 3306:db-server:3306 user@jump-host # 保持连接不断 ssh -o ServerAliveInterval=60 -N -L 3306:localhost:3306 user@remote-server # 使用 autossh 自动重连(适合持久化隧道) autossh -M 0 -o ServerAliveInterval=60 -N -L 3306:localhost:3306 user@remote-server ``` `-N` 和 `-f` 是端口转发最常用的两个参数:`-N` 避免 SSH 打开一个不需要的 shell,`-f` 让隧道在后台运行不占用终端。 ## 安全注意事项 1. **默认绑定 localhost**:-L 和 -D 默认只监听 127.0.0.1,不要随意改为 0.0.0.0,否则局域网内任何人都可能使用你的隧道 2. **GatewayPorts 慎开**:开启后远程转发的端口对公网可见,务必配合防火墙限制来源 IP 3. **禁用端口转发**:服务器可在 `sshd_config` 中设置 `AllowTcpForwarding no` 禁止所有端口转发,适用于只允许交互式登录的场景 4. **密钥认证优于密码**:端口转发往往配置为自动连接,使用密钥认证更安全且免输入密码 5. **审计活跃隧道**:定期检查服务器上的 SSH 转发连接,防止未授权的隧道 ## 故障排查 ```bash # 确认端口是否在监听 ss -tlnp | grep 3306 # 测试隧道是否通畅 curl -x socks5://127.0.0.1:1080 http://目标地址 # 动态转发 telnet localhost 3306 # 本地转发 # 查看 SSH 连接日志 ssh -v -L 3306:localhost:3306 user@remote-server # -v 参数会输出详细的连接过程,定位握手或认证问题 # 常见错误 # bind: Address already in use → 本地端口被占用,换一个端口 # Channel 3: open failed: connect failed → 目标地址从 SSH 服务器不可达 # Permission denied → SSH 认证失败,检查密钥或密码 ```