SSH:为什么从另一个域连接到同一台服务器时必须重新验证指纹?

SSH:为什么从另一个域连接到同一台服务器时必须重新验证指纹?

下面我将讨论 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)))

相关内容