package checker import ( "encoding/json" "fmt" "html/template" "sort" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) // ── HTML report ─────────────────────────────────────────────────────────────── type matrixCertData struct { SubjectCommonName string IssuerCommonName string SHA256Fingerprint string DNSNames []string } type matrixConnectionData struct { Address string TLSVersion string CipherSuite string Certs []matrixCertData AllChecksOK bool CheckDetails []matrixCheckItem Errors []string Open bool } type matrixCheckItem struct { Label string OK bool } type matrixConnErrData struct { Address string Message string } type matrixSRVRecord struct { Target string Port uint16 Priority uint16 Weight uint16 } type matrixHostData struct { Name string CName string Addrs []string } type matrixTemplateData struct { FederationOK bool Version string VersionError string WellKnownServer string WellKnownResult string SRVSkipped bool SRVCName string SRVRecords []matrixSRVRecord SRVError string Hosts []matrixHostData Addrs []string Connections []matrixConnectionData ConnectionErrors []matrixConnErrData } var matrixHTMLTemplate = template.Must( template.New("matrix").Parse(` Matrix Federation Report

Matrix Federation

{{if .FederationOK}} Federation OK {{- else}} Federation FAIL {{- end}} {{if .Version}}
Server: {{.Version}}{{if .VersionError}} — {{.VersionError}}{{end}}
{{end}}
{{if .Connections}}

Connections ({{len .Connections}})

{{range .Connections}} {{.Address}} {{if .AllChecksOK}}All checks OK{{else}}Checks failed{{end}}
{{if or .TLSVersion .CipherSuite}}

TLS

{{.TLSVersion}}{{if and .TLSVersion .CipherSuite}} — {{end}}{{.CipherSuite}}

{{end}} {{if .Certs}}

Certificates

{{range .Certs}} {{end}}
SubjectIssuerDNS NamesFingerprint (SHA-256)
{{.SubjectCommonName}} {{.IssuerCommonName}} {{range .DNSNames}}{{.}} {{end}} {{.SHA256Fingerprint}}
{{end}} {{if .CheckDetails}}

Checks

{{range .CheckDetails}} {{end}}
{{if .OK}}{{else}}{{end}} {{.Label}}
{{end}} {{range .Errors}}

⚠ {{.}}

{{end}}
{{end}}
{{end}} {{if .ConnectionErrors}}

Connection Errors ({{len .ConnectionErrors}})

{{range .ConnectionErrors}}

{{.Address}}
{{.Message}}

{{end}}
{{end}}

Well-Known

{{if .WellKnownServer}}

Server: {{.WellKnownServer}}

{{else if .WellKnownResult}}

{{.WellKnownResult}}

{{else}}

Not found.

{{end}}

DNS Resolution

{{if .SRVSkipped}}

SRV lookup skipped{{if .SRVCName}} (CNAME: {{.SRVCName}}){{end}}

{{else if .SRVError}}

SRV error: {{.SRVError}}

{{else if .SRVRecords}}

SRV Records

{{range .SRVRecords}} {{end}}
TargetPortPriorityWeight
{{.Target}} {{.Port}} {{.Priority}} {{.Weight}}
{{else}}

No SRV records found.

{{end}} {{if .Hosts}}

Resolved Hosts

{{range .Hosts}}

{{.Name}} {{if .CName}} → {{.CName}}{{end}} {{if .Addrs}}: {{range .Addrs}}{{.}} {{end}}{{end}}

{{end}} {{else if .Addrs}}

Addresses

{{end}}
`), ) // GetHTMLReport implements sdk.CheckerHTMLReporter. func (p *matrixProvider) GetHTMLReport(ctx sdk.ReportContext) (string, error) { var r MatrixFederationData if err := json.Unmarshal(ctx.Data(), &r); err != nil { return "", fmt.Errorf("failed to unmarshal matrix report: %w", err) } data := matrixTemplateData{ FederationOK: r.FederationOK, WellKnownServer: r.WellKnownResult.Server, WellKnownResult: r.WellKnownResult.Result, SRVSkipped: r.DNSResult.SRVSkipped, SRVCName: r.DNSResult.SRVCName, Addrs: r.DNSResult.Addrs, } // Version if r.Version.Name != "" || r.Version.Version != "" { data.Version = strings.TrimSpace(r.Version.Name + " " + r.Version.Version) } data.VersionError = r.Version.Error // SRV records for _, s := range r.DNSResult.SRVRecords { data.SRVRecords = append(data.SRVRecords, matrixSRVRecord{ Target: s.Target, Port: s.Port, Priority: s.Priority, Weight: s.Weight, }) } // SRV error if r.DNSResult.SRVError != nil { data.SRVError = r.DNSResult.SRVError.Message } // Hosts hostNames := make([]string, 0, len(r.DNSResult.Hosts)) for name := range r.DNSResult.Hosts { hostNames = append(hostNames, name) } sort.Strings(hostNames) for _, name := range hostNames { h := r.DNSResult.Hosts[name] data.Hosts = append(data.Hosts, matrixHostData{ Name: name, CName: h.CName, Addrs: h.Addrs, }) } // Successful connections connAddrs := make([]string, 0, len(r.ConnectionReports)) for addr := range r.ConnectionReports { connAddrs = append(connAddrs, addr) } sort.Strings(connAddrs) for _, addr := range connAddrs { cr := r.ConnectionReports[addr] conn := matrixConnectionData{ Address: addr, TLSVersion: cr.Cipher.Version, CipherSuite: cr.Cipher.CipherSuite, AllChecksOK: cr.Checks.AllChecksOK, Errors: cr.Errors, Open: !cr.Checks.AllChecksOK, } for _, cert := range cr.Certificates { conn.Certs = append(conn.Certs, matrixCertData{ SubjectCommonName: cert.SubjectCommonName, IssuerCommonName: cert.IssuerCommonName, SHA256Fingerprint: cert.SHA256Fingerprint, DNSNames: cert.DNSNames, }) } conn.CheckDetails = []matrixCheckItem{ {"Matching server name", cr.Checks.MatchingServerName}, {"Certificate valid until future", cr.Checks.FutureValidUntilTS}, {"Valid certificates", cr.Checks.ValidCertificates}, {"Has Ed25519 key", cr.Checks.HasEd25519Key}, {"All Ed25519 checks OK", cr.Checks.AllEd25519ChecksOK}, } data.Connections = append(data.Connections, conn) } // Failed connections errAddrs := make([]string, 0, len(r.ConnectionErrors)) for addr := range r.ConnectionErrors { errAddrs = append(errAddrs, addr) } sort.Strings(errAddrs) for _, addr := range errAddrs { data.ConnectionErrors = append(data.ConnectionErrors, matrixConnErrData{ Address: addr, Message: r.ConnectionErrors[addr].Message, }) } var buf strings.Builder if err := matrixHTMLTemplate.Execute(&buf, data); err != nil { return "", fmt.Errorf("failed to render matrix HTML report: %w", err) } return buf.String(), nil }