diff --git a/checker/discover.go b/checker/discover.go deleted file mode 100644 index b7b8a5d..0000000 --- a/checker/discover.go +++ /dev/null @@ -1,120 +0,0 @@ -package checker - -import ( - "fmt" - - sdk "git.happydns.org/checker-sdk-go/checker" -) - -// directTLSServices enumerates SRV service names (the "service" part of -// _service._proto.domain) that by convention mean "direct TLS on connect", -// as opposed to STARTTLS or plaintext. -// -// Matching on the service name is more authoritative than matching on the -// port: port 636 could carry anything, but _ldaps._tcp unambiguously -// designates LDAP over TLS — even on a non-standard port. -var directTLSServices = map[string]bool{ - "https": true, - "ftps": true, // FTPS implicit - "smtps": true, // SMTP over TLS (legacy port 465 semantics) - "submissions": true, // RFC 8314: SMTP submission over TLS - "imaps": true, - "pop3s": true, - "nntps": true, - "ircs": true, - "telnets": true, - "ldaps": true, - "sips": true, - "ipps": true, // IPP over TLS (printing) - "xmpps-client": true, // XMPP client over direct TLS - "xmpps-server": true, // XMPP server-to-server over direct TLS - "mqtts": true, - "coaps": true, - "stuns": true, - "turns": true, -} - -// starttlsSpec describes how to surface a STARTTLS-capable SRV service as -// a DiscoveredEndpoint: the endpoint type (which carries the protocol -// family in its suffix) and whether the protocol historically treats -// STARTTLS as mandatory or opportunistic. -type starttlsSpec struct { - Type string - Opportunistic bool -} - -// starttlsServices enumerates SRV service names that speak plaintext on -// connect and then negotiate TLS via a protocol-specific STARTTLS handshake. -// -// The Type follows the "starttls-" convention agreed with the -// future TLS checker (the consumer); the SDK itself has no opinion on -// these values. The suffix mirrors the SRV service-name vocabulary so -// producers and consumers stay naturally aligned. -// -// The Opportunistic flag is exposed to consumers as Meta["starttls"] = -// "required" | "opportunistic" so rules can pick an appropriate severity -// when the server does not advertise STARTTLS. -var starttlsServices = map[string]starttlsSpec{ - "submission": {"starttls-smtp", false}, // RFC 8314: STARTTLS required - "smtp": {"starttls-smtp", true}, // port 25: opportunistic - "imap": {"starttls-imap", false}, - "pop3": {"starttls-pop3", false}, - "xmpp-client": {"starttls-xmpp-client", false}, // RFC 7590 - "xmpp-server": {"starttls-xmpp-server", true}, // s2s: opportunistic - "ldap": {"starttls-ldap", true}, - "nntp": {"starttls-nntp", true}, - "ftp": {"starttls-ftp", true}, - "sieve": {"starttls-sieve", false}, - "postgresql": {"starttls-postgres", true}, -} - -// DiscoverEndpoints is invoked right after Collect. It declares (host, port) -// pairs worth testing by other checkers: -// -// - direct-TLS endpoints (Type="tls") whose SRV service name is listed in -// directTLSServices (e.g. _ldaps, _sips, _https), -// - STARTTLS-capable endpoints (Type="starttls-") whose SRV service -// name is listed in starttlsServices (e.g. _submission, _imap, _xmpp-client). -// -// Unknown service names produce no endpoints: we lean on the SRV naming -// convention rather than guessing from the port, since a port alone -// conveys no protocol semantics. -func (p *srvProvider) DiscoverEndpoints(data any) ([]sdk.DiscoveredEndpoint, error) { - d, ok := data.(*SRVData) - if !ok { - return nil, fmt.Errorf("unexpected data type %T", data) - } - var out []sdk.DiscoveredEndpoint - for _, r := range d.Records { - if r.IsNullTarget || r.Target == "" { - continue - } - - if directTLSServices[r.Service] { - out = append(out, sdk.DiscoveredEndpoint{ - Type: "tls", - Host: r.Target, - Port: r.Port, - SNI: r.Target, - }) - continue - } - - if spec, ok := starttlsServices[r.Service]; ok { - policy := "required" - if spec.Opportunistic { - policy = "opportunistic" - } - out = append(out, sdk.DiscoveredEndpoint{ - Type: spec.Type, - Host: r.Target, - Port: r.Port, - SNI: r.Target, - Meta: map[string]any{ - "starttls": policy, - }, - }) - } - } - return out, nil -} diff --git a/checker/provider.go b/checker/provider.go index da4df90..8fed05f 100644 --- a/checker/provider.go +++ b/checker/provider.go @@ -1,6 +1,8 @@ package checker import ( + "fmt" + sdk "git.happydns.org/checker-sdk-go/checker" ) @@ -17,3 +19,54 @@ func (p *srvProvider) Key() sdk.ObservationKey { func (p *srvProvider) Definition() *sdk.CheckerDefinition { return Definition() } + +// directTLSPorts lists TCP ports where clients speak TLS immediately upon +// connection (as opposed to STARTTLS upgrades). A dedicated TLS checker +// consumes these endpoints to validate certificates. +var directTLSPorts = map[uint16]bool{ + 443: true, // HTTPS + 465: true, // SMTPS + 563: true, // NNTPS + 636: true, // LDAPS + 853: true, // DoT + 989: true, // FTPS data + 990: true, // FTPS control + 992: true, // Telnet/TLS + 993: true, // IMAPS + 995: true, // POP3S + 5061: true, // SIPS + 5223: true, // XMPP client TLS + 5349: true, // STUN/TURN/TLS + 6697: true, // IRCS + 8443: true, // HTTPS alt +} + +// DiscoverEndpoints is invoked by the host right after Collect. It declares +// (host, port) pairs worth testing by other checkers — here: TLS endpoints +// whose SRV target points at a well-known direct-TLS port. +// +// STARTTLS SRVs (e.g. _xmpp-server._tcp on 5269, _sips._tcp notwithstanding) +// are intentionally not emitted yet: a dedicated "smtp-starttls" / "xmpp-starttls" +// endpoint type will be defined when the TLS checker grows that capability. +func (p *srvProvider) DiscoverEndpoints(data any) ([]sdk.DiscoveredEndpoint, error) { + d, ok := data.(*SRVData) + if !ok { + return nil, fmt.Errorf("unexpected data type %T", data) + } + var out []sdk.DiscoveredEndpoint + for _, r := range d.Records { + if r.IsNullTarget || r.Target == "" { + continue + } + if !directTLSPorts[r.Port] { + continue + } + out = append(out, sdk.DiscoveredEndpoint{ + Type: "tls", + Host: r.Target, + Port: r.Port, + SNI: r.Target, + }) + } + return out, nil +}