health-setup-checks: the certificate's name, Postfix's greeting, the proxy
Decided by the owner on 2026-10-13 (in place of preflight), taken up on 2026-10-16; mockupsb2dcefc,cc33890,0feaa55(his: the greeting on a row of its own, the name check a tag beside Names). - The certificate: health.Certificate keeps its names (DNS names, or the CN without any) and ForHost holds it to SELFPOST_HOSTNAME as a TLS client does - VerifyHostname over the DNS names, the CN only without any, as OpenSSL's X509_check_host and so a sending Postfix still take it (src/tls/tls_client.c:439, 513). A certificate for another name fails; the tag beside Names says matches / not for the hostname. - The greeting: postconf -x -h smtpd_banner, read once at the panel's start like the served certificate's path (main.cf is not rewritten after), is what smtpd writes after "220 " (src/smtpd/smtpd.c:5979). A first word other than the hostname warns, with what to set. A fact may now take the whole row (Fact.Wide, sp-wide) and carry a tag beside its label (Fact.LabelTag); both on the kit page. - The proxy: auth.Module.Seen says the address the panel counts sign-ins against, the peer, and whether X-Forwarded-For was read or left unread for an untrusted peer; the Configuration card shows "Your address" and, in the second case, warns that every visitor shares one sign-in limit and names the /32 (or /128) to put in TRUSTED_PROXY_CIDR. Tests: TestCertificateForHost (CN-only, DNS names, a wildcard, the CN ignored beside DNS names), TestSeenAddress, TestHealthSaysWhereItSeesTheViewer, TestHealthGreetingAndCertificateName; e2e reads the stand's right state on Health (matches, "220 mail.e2e.test ESMTP", a direct client). go vet + go test (26 packages) and make e2e on the test server for this tree. Docs: help (word for word with the mockup), guide § Overview and Health, CHANGELOG [Unreleased] § Added, roadmap row done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -286,3 +286,48 @@ func TestIntervalText(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A certificate for another name is no certificate for this server: a client
|
||||
// that checks the name refuses it however valid it is. Its DNS names are
|
||||
// matched as a TLS client matches them, a wildcard for one label; with none,
|
||||
// its common name, as OpenSSL - and so a sending Postfix - still does.
|
||||
func TestCertificateForHost(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cnOnly := filepath.Join(dir, "cn.pem")
|
||||
writeCert(t, cnOnly, "mail.example.org", 90*24*time.Hour)
|
||||
if c := CheckCertificate(cnOnly).ForHost("MAIL.example.org"); c.Status != StatusOK || c.MatchesHost == nil || !*c.MatchesHost {
|
||||
t.Errorf("a CN-only certificate for the hostname: %+v", c)
|
||||
}
|
||||
if c := CheckCertificate(cnOnly).ForHost("other.example.org"); c.Status != StatusError || *c.MatchesHost ||
|
||||
!strings.Contains(c.Detail, "None of the certificate's names is other.example.org") {
|
||||
t.Errorf("a certificate for another name: %+v", c)
|
||||
}
|
||||
|
||||
san := filepath.Join(dir, "san.pem")
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpl := &x509.Certificate{SerialNumber: big.NewInt(2), Subject: pkix.Name{CommonName: "mail.example.org"},
|
||||
DNSNames: []string{"*.example.net", "relay.example.org"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(90 * 24 * time.Hour)}
|
||||
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(san, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := CheckCertificate(san)
|
||||
if strings.Join(c.Names, " ") != "*.example.net relay.example.org" {
|
||||
t.Errorf("names = %q, want the DNS names", c.Names)
|
||||
}
|
||||
for host, want := range map[string]bool{"mail.example.net": true, "relay.example.org": true, "a.b.example.net": false,
|
||||
"mail.example.org": false /* the CN does not count beside DNS names */} {
|
||||
if got := c.ForHost(host); *got.MatchesHost != want {
|
||||
t.Errorf("ForHost(%s) matches = %v, want %v", host, *got.MatchesHost, want)
|
||||
}
|
||||
}
|
||||
if c := (Certificate{Status: StatusError}).ForHost("mail.example.org"); c.MatchesHost != nil {
|
||||
t.Error("a certificate that was not read was matched")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user