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:
@@ -125,6 +125,32 @@ func clientIP(r *http.Request, trustedProxies []*net.IPNet) string {
|
||||
return host
|
||||
}
|
||||
|
||||
// SeenAddress is how the panel sees a request's address, for Health: the
|
||||
// address it counts sign-ins against (Client), the transport peer (Peer),
|
||||
// and whether Client came from X-Forwarded-For (FromHeader) or the header
|
||||
// was there and left unread because the peer is not a trusted proxy
|
||||
// (HeaderIgnored) - the case where every visitor shares the proxy's address
|
||||
// and one sign-in limit.
|
||||
type SeenAddress struct {
|
||||
Client string
|
||||
Peer string
|
||||
FromHeader bool
|
||||
HeaderIgnored bool
|
||||
}
|
||||
|
||||
// Seen reads a request's address as clientIP does.
|
||||
func (m *Module) Seen(r *http.Request) SeenAddress {
|
||||
peer, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
peer = r.RemoteAddr
|
||||
}
|
||||
s := SeenAddress{Client: clientIP(r, m.trustedProxies), Peer: peer}
|
||||
hasHeader := r.Header.Get("X-Forwarded-For") != ""
|
||||
s.FromHeader = hasHeader && s.Client != peer
|
||||
s.HeaderIgnored = hasHeader && !s.FromHeader
|
||||
return s
|
||||
}
|
||||
|
||||
func ipInAny(ip net.IP, nets []*net.IPNet) bool {
|
||||
for _, n := range nets {
|
||||
if n.Contains(ip) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Health says where the panel sees a request from: through a trusted proxy,
|
||||
// the client's own address from X-Forwarded-For; through a proxy it does not
|
||||
// trust, the proxy's, with the header left unread - every visitor then
|
||||
// shares one sign-in limit; and with no header, the connection's own.
|
||||
func TestSeenAddress(t *testing.T) {
|
||||
_, proxyNet, _ := net.ParseCIDR("172.18.0.0/16")
|
||||
m := &Module{trustedProxies: []*net.IPNet{proxyNet}}
|
||||
untrusting := &Module{}
|
||||
|
||||
r := httptest.NewRequest("GET", "/server/health", nil)
|
||||
r.RemoteAddr = "172.18.0.1:40112"
|
||||
r.Header.Set("X-Forwarded-For", "198.51.100.7")
|
||||
if s := m.Seen(r); s != (SeenAddress{Client: "198.51.100.7", Peer: "172.18.0.1", FromHeader: true}) {
|
||||
t.Errorf("through a trusted proxy: %+v", s)
|
||||
}
|
||||
if s := untrusting.Seen(r); s != (SeenAddress{Client: "172.18.0.1", Peer: "172.18.0.1", HeaderIgnored: true}) {
|
||||
t.Errorf("through a proxy not trusted: %+v", s)
|
||||
}
|
||||
|
||||
r.Header.Del("X-Forwarded-For")
|
||||
r.RemoteAddr = "203.0.113.9:5000"
|
||||
if s := m.Seen(r); s != (SeenAddress{Client: "203.0.113.9", Peer: "203.0.113.9"}) {
|
||||
t.Errorf("direct: %+v", s)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user