下面我将讨论 Debian 上默认安装的 OpenSSH 客户端。
我希望有多个(子)域名指向我的服务器(即每个服务器有多个),即 database.example.com、webserver.example.com 等。我想要这样做,因为我有多台服务器,有时我不知道哪个服务在哪个服务器上运行。我希望通过 DNS 来管理它,为每个服务设置一个子域名,然后我可以使用此子域名通过 SSH 进入正确的服务器,而不必记住服务在哪个服务器上运行。
问题是:指纹似乎包含域名。因此,如果 database.example.com 和 webserver.example.com 指向同一个 IP,并且我使用 database.example.com 并验证了指纹(因此它被添加到 known_hosts),那么如果我稍后通过 webserver.example.com SSH 进入服务器,我必须再次进行验证。
我不明白为什么这里使用的是域名而不是域名解析的 IP 地址。有没有办法在指纹中使用 IP 而不是域名?(也许是一些配置选项)。
如果这样,启用此功能是否会带来安全问题?
为什么一开始要以这种方式实现?另外,据我所知,指纹是从服务器公钥派生出来的,只有服务器才有匹配的私钥。那么为什么还需要包含域或 IP 之类的东西呢?这难道不是只能防止我意外连接到我在 known_hosts 中保存的其他服务器之一吗?
将用于连接到服务器的每个子域的密钥添加到known_hosts是我最不喜欢的选项,因为它需要做更多的工作,并且会导致忽略密钥检查,因为人们已经习惯于针对同一台服务器多次询问密钥是否正确。
我认为这个帖子是相关的,但它只是说它是这样处理的,而不是为什么以及是否有办法改变这种行为。
提前致谢
编辑:我还发现这个帖子现在建议禁用StrictHostKeyChecking
,但据我所知,这允许 ssh 添加所有指纹,据我所知,从安全角度来看,这是一个问题。我想要的是,即使我使用域连接到服务器,IP 也用于指纹检查(ssh 无论如何都会解析 ip 本身,您可以在使用标志时看到-vvv
)
答案1
您可以使用逗号分隔条目~/.ssh/known_hosts
你可以这样做:
database.example.com,webserver.example.com,database,webserver,10.1.2.3 ssh-rsa …
您可以以逗号分隔的格式添加主机的备用名称和 IP。
这使用真实的主机名,而不是Host
您可以在~/.ssh/config
文件中创建的别名。
查找重复条目known_hosts
我创建了一个小脚本,名为ssh-主机哈希可以查找known_hosts
文件中的重复条目。使用它和上述知识来折叠它们。
以下是 Github 密钥的输出(您可能注意到我将其折叠了一点,但还不够)。冲突按行号列出:
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTt
...JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
72: github.com,gist.github.com,192.30.252.128,192.30.252.129,192.30.252.130,192.30.252.131,192.30.252.140,192.30.252.141,192.30.252.142,192.30.252.143
93: 192.30.253.113
95: 192.30.253.118
96: 192.30.253.119
97: 192.30.253.112
109: 192.30.255.113
110: 192.30.255.112
155: 140.82.113.3
158: 140.82.114.4
161: 140.82.113.4
使用 ssh localhost 进行验证
我经常做的另一件事是确保我没有保存本地主机的指纹。这样,我就可以从保存并信任指纹的主机 ssh 到服务器,ssh localhost
从该服务器运行,并将该指纹与尚未保存指纹的客户端提供的指纹进行匹配。不过要注意不同的密钥类型(或指纹格式)。(这很有帮助,因为客户端提供的摘要ssh
与文件中存储的格式不同known_hosts
。)
答案2
阅读@AdamKatz 的回答让我想要对我的known_hosts文件进行排序,所以我编写了一个可以执行此操作的脚本,请在下面找到它(我很小心地编写它,但我只做了最少的测试,因此使用时请自担风险)。
至于原始问题,我建议在 .ssh/config 中使用 HostKeyAlias 配置条目,如这个 ServerFault 的答案是:“通过 SSH 进入一个经常更改 IP 的盒子”。
SSH:配置 ssh_config 以使用特定服务器指纹的特定密钥文件
# (python3)
# `organize_known_hosts.py`
# For all known hosts, sort them by algo+hashed value, grouping the known IPs and domain names into one comma-separted list
# Usage:
# Generated the sorted file:
# cat ~/.ssh/known_hosts | python organize_known_hosts.py > /tmp/known_hosts
# Print it for visual check
# cat /tmp/known_hosts
# mv /tmp/known_hosts ~/.ssh/
import sys
from collections import namedtuple
class Entry(namedtuple("Entry", ["domainString", "algorithm", "hash"])):
def __str__(self):
return f"{self.domainString} {self.algorithm} {self.hash}"
def rightHandSide(self):
"""algorithm+hash"""
return f"{self.algorithm}.{self.hash}"
entryMap = {}
for lineNumber, line in enumerate(sys.stdin, start=1):
content = line.strip().split()
if len(content) == 0:
continue # empty line -> skip
elif line[0].startswith("#") or len(content) != 3:
if not line[0].startswith("#"):
sys.stderr.write(f"could not parse a line; line moved to top|{lineNumber}: '{line}'\n")
sys.stdout.write(line)
continue # comment or erroneous line -> skip
entry = Entry(*content)
entryMap.setdefault(entry.rightHandSide(), []).append(entry)
compactEntryList = []
for k, entryList in sorted(entryMap.items()):
domainList = []
ipList = []
for entry in entryList:
for domainOrIp in entry.domainString.split(","):
if 'a' <= domainOrIp[0].lower() <= 'z':
domainList.append(domainOrIp)
else:
ipList.append(domainOrIp)
domainString = ",".join(sorted(domainList) + sorted(ipList))
compactEntryList.append(Entry(domainString, *k.split(".")))
sys.stdout.write("\n".join(map(str, compactEntryList)))