feat(check): handlungsleitende Hinweise bei TLS- und Zertifikatsfehlern
Der bisherige Fehlertext gab den rohen x509-Fehler weiter, ohne zu sagen, was zu tun ist. check unterscheidet jetzt unbekannte CA, unpassenden Hostnamen, SHA-1-Signatur, abgelaufenes Zertifikat, zu alte TLS-Version und fehlende gemeinsame Cipher-Suite und nennt jeweils die passende Stellschraube. Der technische Originalfehler bleibt erhalten. Reine Netzwerkfehler bekommen bewusst keinen Hinweis, damit kein falscher Verdacht entsteht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
7237aee744
commit
fd88251e07
2 changed files with 116 additions and 0 deletions
|
|
@ -226,6 +226,51 @@ func (c *Checker) checkFirewall(ctx context.Context) (time.Time, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// directoryHint übersetzt typische TLS- und Zertifikatsfehler in einen
|
||||||
|
// handlungsleitenden Zusatz. Der technische Originalfehler bleibt erhalten;
|
||||||
|
// nur ein Hinweis kommt dazu. Passt nichts, bleibt der Zusatz leer — ein
|
||||||
|
// reiner Netzwerkfehler soll keinen falschen Verdacht wecken.
|
||||||
|
func directoryHint(err error) string {
|
||||||
|
msg := strings.ToLower(err.Error())
|
||||||
|
contains := func(needles ...string) bool {
|
||||||
|
for _, n := range needles {
|
||||||
|
if strings.Contains(msg, n) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case contains("insecure algorithm sha1", "sha1-rsa"):
|
||||||
|
return "Das Zertifikat des Domain Controllers ist mit SHA-1 signiert. " +
|
||||||
|
"Go akzeptiert das seit Version 1.24 nicht mehr und bietet auch keinen " +
|
||||||
|
"Schalter dafür — das Zertifikat muss mit SHA-256 neu ausgestellt werden."
|
||||||
|
case contains("certificate signed by unknown authority", "unknown authority", "unable to verify"):
|
||||||
|
return "Die ausstellende CA ist auf diesem Host nicht bekannt. Die interne " +
|
||||||
|
"Stammzertifizierungsstelle als PEM ablegen und in ad.ca_file eintragen " +
|
||||||
|
"(die Datei muss die gesamte Kette bis zur Wurzel enthalten). " +
|
||||||
|
"Ohne ad.ca_file wird nur der Trust-Store des Betriebssystems benutzt."
|
||||||
|
case contains("certificate is valid for", "certificate is not valid for any names", "x509: certificate relies on legacy"):
|
||||||
|
return "Das Zertifikat passt nicht zum angefragten Namen. In ad.servers muss " +
|
||||||
|
"genau der Name stehen, der im Zertifikat als Subject Alternative Name " +
|
||||||
|
"eingetragen ist — IP-Adressen sind deshalb nicht zulässig."
|
||||||
|
case contains("certificate has expired", "not yet valid"):
|
||||||
|
return "Das Zertifikat des Domain Controllers ist abgelaufen oder noch nicht " +
|
||||||
|
"gültig. Zertifikat erneuern und die Systemzeit beider Seiten prüfen."
|
||||||
|
case contains("protocol version not supported", "unsupported protocol version"):
|
||||||
|
return "Der Domain Controller spricht kein TLS 1.2. Für Altsysteme wie " +
|
||||||
|
"Windows Server 2012 R2 lässt sich ad.min_tls_version auf \"1.0\" setzen; " +
|
||||||
|
"besser ist, TLS 1.2 auf dem Domain Controller zu aktivieren."
|
||||||
|
case contains("no cipher suite supported by both", "handshake failure"):
|
||||||
|
return "Client und Domain Controller haben keine gemeinsame Cipher-Suite. " +
|
||||||
|
"Zuerst ad.min_tls_version prüfen. Braucht der Domain Controller zwingend " +
|
||||||
|
"RSA-Schlüsselaustausch, hilft nur GODEBUG=tlsrsakex=1 in der systemd-Unit."
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkDirectory bindet den Service-Account und löst die Gruppe auf.
|
// checkDirectory bindet den Service-Account und löst die Gruppe auf.
|
||||||
func (c *Checker) checkDirectory(ctx context.Context) (string, error) {
|
func (c *Checker) checkDirectory(ctx context.Context) (string, error) {
|
||||||
if c.Dir == nil {
|
if c.Dir == nil {
|
||||||
|
|
@ -233,6 +278,9 @@ func (c *Checker) checkDirectory(ctx context.Context) (string, error) {
|
||||||
}
|
}
|
||||||
dn, err := c.Dir.ResolveGroupDN(ctx)
|
dn, err := c.Dir.ResolveGroupDN(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if hint := directoryHint(err); hint != "" {
|
||||||
|
return "", fmt.Errorf("Verzeichnisdienst nicht nutzbar: %w\n\n%s", err, hint)
|
||||||
|
}
|
||||||
return "", fmt.Errorf("Verzeichnisdienst nicht nutzbar: %w", err)
|
return "", fmt.Errorf("Verzeichnisdienst nicht nutzbar: %w", err)
|
||||||
}
|
}
|
||||||
if dn == "" {
|
if dn == "" {
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,74 @@ func TestMissingAPIPermissionIsDistinguished(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDirectoryTLSErrorsGiveActionableHints(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
err string
|
||||||
|
want string // Textbaustein, der in der Ausgabe stehen muss
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"unbekannte CA",
|
||||||
|
`LDAPS-Verbindung zu dc01.firma.local:636: x509: certificate signed by unknown authority`,
|
||||||
|
"ad.ca_file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Hostname passt nicht",
|
||||||
|
`LDAPS-Verbindung zu dc01.firma.local:636: x509: certificate is valid for DC01, not dc01.firma.local`,
|
||||||
|
"ad.servers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SHA-1-Signatur",
|
||||||
|
`LDAPS-Verbindung zu dc01:636: x509: cannot verify signature: insecure algorithm SHA1-RSA`,
|
||||||
|
"SHA-256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Zertifikat abgelaufen",
|
||||||
|
`LDAPS-Verbindung zu dc01:636: x509: certificate has expired or is not yet valid`,
|
||||||
|
"abgelaufen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TLS-Version zu alt",
|
||||||
|
`LDAPS-Verbindung zu dc01:636: tls: protocol version not supported`,
|
||||||
|
"min_tls_version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keine gemeinsame Cipher-Suite",
|
||||||
|
`LDAPS-Verbindung zu dc01:636: tls: no cipher suite supported by both client and server`,
|
||||||
|
"tlsrsakex",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
c := newChecker(t, &fakePinger{at: checkNow}, &fakeDir{dnErr: errors.New(tc.err)})
|
||||||
|
rep := c.Run(context.Background(), "")
|
||||||
|
if rep.OK() {
|
||||||
|
t.Fatal("TLS-Fehler muss auffallen")
|
||||||
|
}
|
||||||
|
out := report(t, rep)
|
||||||
|
if !strings.Contains(out, tc.want) {
|
||||||
|
t.Errorf("Hinweis %q fehlt in der Ausgabe:\n%s", tc.want, out)
|
||||||
|
}
|
||||||
|
// Der technische Originalfehler muss erhalten bleiben.
|
||||||
|
if !strings.Contains(out, "dc01") {
|
||||||
|
t.Errorf("der Originalfehler darf nicht verloren gehen:\n%s", out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainNetworkErrorGetsNoMisleadingHint(t *testing.T) {
|
||||||
|
c := newChecker(t, &fakePinger{at: checkNow},
|
||||||
|
&fakeDir{dnErr: errors.New("dc01.firma.local: connection refused")})
|
||||||
|
rep := c.Run(context.Background(), "")
|
||||||
|
out := report(t, rep)
|
||||||
|
for _, unwanted := range []string{"ad.ca_file", "min_tls_version", "SHA-256"} {
|
||||||
|
if strings.Contains(out, unwanted) {
|
||||||
|
t.Errorf("ein reiner Netzwerkfehler darf keinen TLS-Hinweis erzeugen (%q):\n%s", unwanted, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnreachableDCIsNamed(t *testing.T) {
|
func TestUnreachableDCIsNamed(t *testing.T) {
|
||||||
c := newChecker(t, &fakePinger{at: checkNow},
|
c := newChecker(t, &fakePinger{at: checkNow},
|
||||||
&fakeDir{dnErr: errors.New("dc01.firma.local: connection refused")})
|
&fakeDir{dnErr: errors.New("dc01.firma.local: connection refused")})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue