1

In the following I'm talking about the OpenSSH Client installed by default on debian.

I'd like to have multiple (sub)domains pointing to my server(s) (so multiple per server), i.e. database.example.com, webserver.example.com etc. I want this because I have multiple servers and sometimes I don't know by heart which service is running on which server. I want to manage this by DNS by having a subdomain for every service and then I can use this subdomain to SSH into the right server without having to remember on which server the service is running.

The problem is: The fingerprint seems to include the domain name. So if database.example.com and webserver.example.com are pointing to the same IP and I used database.example.com and verified the fingerprint (so it's added to known_hosts) then I have to do the verification again if I SSH into the server via webserver.example.com later.

I don't understand why the domain and not the IP adress the domain resolves to is used here. Is there any way to use the IP instead of the domain in the fingerprint? (maybe some configuration option).

And if so can enabling this introduce a security problem?

And why is it even implemented this way in the first place? Also as far as I understand the fingerprint is derived from the servers public key for which only the server has the matching private key. So why is it even necessary to include anything like the domain or IP? Doesn't this only prevent me from accidently connecting to one of my other servers which I have saved in known_hosts?

Adding keys for every subdomain I use to connect to a server to known_hosts is my least preferred option since it's much more work and invites to ignore the key checking since one gets used to the fact that asks if the key is correct multiple times for the same server.

I think this post is related but it's just saying that it's handled that way and not why and if there's a way to change that behaviour.

Thanks in advance

Edit: I also found this post now suggesting to disable StrictHostKeyChecking but as far as I understand this allows to ssh-add all fingerprints which as far as I understand is a problem from a security standpoint. What I want is that, even if I use the domain to connect to the server, the IP is used for fingerprint checking (ssh resolves the ip itself anyway which you can see when using the -vvv flag)

J. Dietz
  • 11
  • 2
  • There is no way to change this behaviour. The fingerprint doesn't contain the dns name, the client just stores the fingerprint separately for every known host. foo.example.com is a different host than bar.example.com, even if they point to the same "physical" host. SSH doesn't care about that. – Gerald Schneider Mar 21 '20 at 17:54
  • You can use certificates instead of host keys to verify the server identity. You will however need a certificate that is valid for every hostname. – Gerald Schneider Mar 21 '20 at 17:58
  • Thank you. After researching for a few more hours I found this a few minutes ago too. But maybe there is an option to tell SSH "hey, please don't add the dns name but the ip you resolved the dns name to to the known_hosts file and check against the resolved ip instead of the dns name"? – J. Dietz Mar 21 '20 at 18:10
  • 1
    Not that I know of. And if there were, it would reduce the security of SSH. Who knows if someone spoofed the IP address? – Gerald Schneider Mar 21 '20 at 18:12
  • But as far as I understand it's the same thing the other way around: Who knows if the DNS resolved to another IP because someone spoofed it? AFAIK the IP is not checked if the connection is established with a domain name. – J. Dietz Mar 21 '20 at 18:32
  • There might be a way to accomplish this using the `CanonicalizeHostname` and `CanonicalizePermittedCNAMEs` and changing your DNS records to be CNAMEs but I find the documentation on these options confusing (hence this is a comment rather than answer). – Mark Wagner Mar 23 '20 at 18:42

2 Answers2

0

You can comma-delimit entries in ~/.ssh/known_hosts

You can do this:

database.example.com,webserver.example.com,database,webserver,10.1.2.3 ssh-rsa …

You can add alternate names and IPs for a host in that comma-delimited format.

This uses real host names, not the Host aliases you can create in your ~/.ssh/config file.

Find duplicate entries in known_hosts

I've created a tiny script called ssh-hosthashes that can find duplicate entries in a known_hosts file. Use it and the above knowledge to collapse them.

Here's its output for Github's key (which you may notice I've collapsed a bit yet not enough). Collisions are listed by line number:

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

Verify with ssh localhost

Another thing I do a lot is ensure I have not saved the fingerprint for localhost. That way, I can ssh into the server from a host that has the fingerprint saved and trusted, run ssh localhost from that server, and match that fingerprint to the one offered by a client that does not have it saved already. Watch for different key types (or fingerprint formats) though. (This is only helpful because the digest provided by the ssh client is different from the format stored in known_hosts files.)

Adam Katz
  • 869
  • 8
  • 16
0

Reading @AdamKatz's answer made me want to sort my known_hosts file, so I wrote a script which does that, find it below (I put care into writing it but I only did minimal testing so use at your own risks).

As for the original question, I'd recommend using a HostKeyAlias config entry in .ssh/config, as explained in this ServerFault answer: "SSH into a box with a frequently changed IP".

SSH: Configure ssh_config to use specific key file for a specific server fingerprint


# (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)))