Heute habe ich versucht, mich von meiner neuen Linux Mint Installation aus mit einem meiner SSH-Server zu verbinden. Mein SSH-Client hat mich direkt mit der Frage begrüßt, ob ich dem neuen Hostkey vertrauen möchte oder nicht.

ssh username@hostname.kernel-error.org
The authenticity of host 'hostname.kernel-error.org (2a01:5a8:362:4416::32)' can't be established.
ED25519 key fingerprint is SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Für viele mag diese Meldung bekannt und vollkommen normal erscheinen. In der Regel antwortet man initial mit „yes“ und sieht sie nie wieder. Aber diese Meldung hat ihren Grund. Beim initialen Aufbau einer Verbindung zu einem SSH-Server wird einem der Fingerprint des HostKeys angezeigt. So hat man die Möglichkeit, den Fingerprint mit dem erwarteten Fingerprint abzugleichen, um sicherzustellen, dass man sich wirklich mit dem gewünschten SSH-Server verbindet und nicht etwa ein Angreifer Zugangsdaten abfischt. Wenn man eh immer nur „JA“ sagt, könnte man diesen Check auch direkt in seiner ~/.ssh/config mit folgendem Eintrag deaktivieren:

Host *
    StrictHostKeyChecking no

Warum erzähle ich das alles? Nun, weil es für mich eigentlich nicht normal ist, diese Meldung zu sehen. Denn es gibt die Möglichkeit, die Fingerprints der erwarteten HostKeys in seiner DNS-Zone zu hinterlegen und seinen SSH-Client mit der folgenden Konfiguration in seiner ~/.ssh/config anzuweisen, dies einfach selbst zu überprüfen, sofern der SSH-Client eine vertrauenswürdige Antwort vom DNS-Server erhält.

Host *
   VerifyHostKeyDNS yes

Vertrauenswürdige Antwort vom DNS-Server… Hier sind wir schon bei DNSSEC angekommen. Meine DNS-Server, einschließlich des lokalen Resolvers auf meinem Router, unterstützen alle DNSSEC. Meine SSH-Client-Konfiguration ist korrekt und dennoch erscheint die Meldung…. Also habe ich den Verbindungsaufbau mit etwas mehr Debugging-Output gestartet, was bei ssh einfach die zusätzliche Option -vvv bedeutet:

ssh usermane@hostname.kernel-error.org -vvv
[...]
debug1: SSH2_MSG_KEX_ECDH_REPLY received
debug1: Server host key: ssh-ed25519 SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu
debug3: verify_host_key_dns
debug1: found 2 insecure fingerprints in DNS
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 1
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 1
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 2
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 2
debug1: matching host key fingerprint found in DNS
[...]

Zu meiner Überraschung sehe ich:

debug1: found 2 insecure fingerprints in DNS

Hm… „insecure“… Er hat also die passenden Einträge in der DNS-Zone gefunden, kann diesen aber nicht vertrauen, weil… ja, warum? Die Antwort des DNS-Servers ist nicht vertrauenswürdig? OK, das lässt sich einfach mit dig und der Option +dnssec testen. Wir suchen einfach im Header nach „ad„:

dig +dnssec hostname.kernel-error.org @8.8.8.8

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> +dnssec hostname.kernel-error.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48645
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
[...]

@8.8.8.8 gibt an, dass direkt der öffentliche DNS-Server von Google gefragt wird. Dieser wird natürlich meine Server fragen usw., einfach um sicherzugehen, dass meine eigentlichen DNS-Server, die für die Zone zuständig sind, sauber konfiguriert sind. Ich sehe ein „ad„, also ist dort schon mal alles gut. Im Anschluss habe ich den Test noch mit meinem lokalen DNS-Resolver auf dem Router durchgeführt. Also einfach @192.168.0.1 oder was auch immer euer lokaler Router ist. Gleiches Ergebnis…. Aber warum will dann mein Linux Mint nicht? Sollte Linux Mint etwa kein DNSSEC können?

dig +dnssec hostname.kernel-error.org

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> +dnssec hostname.kernel-error.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1789
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
[...]

Öhhh ähhh… Ja, öhm, geht nicht… Aber warum? Was steht denn in meiner /etc/resolv.conf? 127.0.0.53? Ohhhhhhhh, stimmt! systemd-resolv! OK, ok… Ich könnte also in meiner /etc/systemd/resolved.conf nun einfach DNSSEC=yes setzen und mit einem systemctl restart systemd-resolved sollte dann… Nope, leider nicht. Nun geht überhaupt keine DNS-Auflösung mehr. Es scheint am eingesetzten Stub-Resolver zu liegen, den man ebenfalls noch ändern kann usw… Nennt mich etwas oldschool, aber für meine Zwecke reicht der klassische Weg über die vom NetworkManager gepflegte resolv.conf. Um also systemd-resolved zu deaktivieren und auf den NetworkManager zu wechseln, sind die folgenden Schritte nötig:

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf

Dann in die Konfigurationsdatei vom NetworkManager /etc/NetworkManager/NetworkManager.conf in der [main]-Sektion die folgende Option setzen:

dns=default

Nun nur noch den NetworkManager neu starten und schon sollte die /etc/resolv.conf mit den DNS-Informationen gefüttert werden:

sudo systemctl restart NetworkManager
cat /etc/resolv.conf
# Generated by NetworkManager
search kernel-error.local
nameserver 10.10.88.1
nameserver fd00:424e:6eff:f525:454e:6eff:f525:4241

Perfekt! Also los, noch ein Versuch mit dem SSH-Client und… nichts… DNS-Auflösung funktioniert, aber es ist noch immer „insecure„. Stimmt! Es fehlt etwas in meiner resolv.conf. Wir brauchen bestimmt noch die folgende Option:

options edns0

Jetzt aber! HA, dig ist schon mal glücklich, ich sehe ein „ad“. Ähm, aber der SSH-Client noch immer nicht?! Was zum… OK, OK… Irgendwas um SSH muss ich vergessen haben. Aber was? Wie macht SSH das überhaupt? Vielleicht gibt mir das eine Idee. Also, mal kurz in den Code geschaut, C bekomme ich gerade noch hin:

        /* Check for authenticated data */
        if (ldns_pkt_ad(pkt)) {
                rrset->rri_flags |= RRSET_VALIDATED;
        } else { /* AD is not set, try autonomous validation */
                ldns_rr_list * trusted_keys = ldns_rr_list_new();

                debug2("ldns: trying to validate RRset");
                /* Get eventual sigs */
                rrsigs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_RRSIG,
                    LDNS_SECTION_ANSWER);

                rrset->rri_nsigs = ldns_rr_list_rr_count(rrsigs);
                debug2("ldns: got %u signature(s) (RRTYPE %u) from DNS",
                       rrset->rri_nsigs, LDNS_RR_TYPE_RRSIG);

                if ((err = ldns_verify_trusted(ldns_res, rrdata, rrsigs,
                     trusted_keys)) == LDNS_STATUS_OK) {
                        rrset->rri_flags |= RRSET_VALIDATED;
                        debug2("ldns: RRset is signed with a valid key");
                } else {
                        debug2("ldns: RRset validation failed: %s",
                            ldns_get_errorstr_by_id(err));
                }

                ldns_rr_list_deep_free(trusted_keys);
        }

Aaaaaaaaaaaaaaaaaaaaaaa… ldns… woher bekommt er wohl die Trust Keys? Genau… woher? Da fehlt also NOCH etwas in meiner resolv.conf:

options edns0 trust-ad

Und? genau… geht 😀

ssh usermane@hostname.kernel-error.org -vvv
[...]
debug1: SSH2_MSG_KEX_ECDH_REPLY received
debug1: Server host key: ssh-ed25519 SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu
debug3: verify_host_key_dns
debug1: found 2 secure fingerprints in DNS
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 1
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 1
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 2
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 2
debug1: matching host key fingerprint found in DNS
[...]

Pfff… nun habe ich natürlich die Optionen von Hand in meine resolv.conf eingetragen, der NetworkManager wird diese Option also spätestens beim nächsten Boot rauswerfen. Also muss ich noch meinem NetworkManager beibringen, dass er bitte diese Option ebenfalls in meine resolv.conf schreibt, wenn das jeweilige Netzwerkprofil aktiviert ist. Dazu gibt es aber leider keinen Menüpunkt in der GUI vom NetworkManager, also muss das per CLI gemacht werden. Dieses für IPv4 und IPv6 gleichermaßen, sonst greift es leider nicht!

nmcli conn modify DEINE-PROFIL-UUID ipv4.dns-options edns0,trust-ad
nmcli conn modify DEINE-PROFIL-UUID ipv6.dns-options edns0,trust-ad

Oh, ein nmcli conn show listet die bestehenden und vor allem das aktive Profil inkl. der UUID auf. Davon abgesehen, klappt es nun so und ist rebootfest.

So und nun ihr! Ich bin mit meinem FreeBSD-Wissen an das Thema herangegangen. Wie macht man das als Hardcore-Linux-User und mit systemd-resolved richtig und funktionierend?