No description
  • Go 99.1%
  • Makefile 0.5%
  • Dockerfile 0.4%
Find a file
Pierre-Olivier Mercier c99c13a7e0 fix: Implement CheckerDefinitionProvider on tlsProvider
Lets the SDK seed OptionEndpoints from the primary's DiscoverEntries
output when checker-tls runs as a sibling.
2026-04-26 00:36:44 +07:00
checker fix: Implement CheckerDefinitionProvider on tlsProvider 2026-04-26 00:36:44 +07:00
contract Split monolithic rule into per-test rules, collect gathers facts only 2026-04-25 23:14:42 +07:00
plugin fix: Implement CheckerDefinitionProvider on tlsProvider 2026-04-26 00:36:44 +07:00
.gitignore Initial commit 2026-04-24 12:13:57 +07:00
Dockerfile Migrate to checker-sdk-go v1.3.0 with standalone build tag 2026-04-24 14:04:55 +07:00
go.mod Bump SDK to 1.4.0 2026-04-24 17:43:36 +07:00
go.sum Bump SDK to 1.4.0 2026-04-24 17:43:36 +07:00
LICENSE Initial commit 2026-04-24 12:13:57 +07:00
main.go Migrate to checker-sdk-go v1.3.0 with standalone build tag 2026-04-24 14:04:55 +07:00
Makefile Migrate to checker-sdk-go v1.3.0 with standalone build tag 2026-04-24 14:04:55 +07:00
NOTICE Initial commit 2026-04-24 12:13:57 +07:00
README.md Split monolithic rule into per-test rules, collect gathers facts only 2026-04-25 23:14:42 +07:00

checker-tls

TLS posture checker for happyDomain.

Consumes DiscoveryEntry records of type tls.endpoint.v1 published by service checkers (xmpp, srv, caldav, carddav, …), performs a real TCP dial, optional protocol-specific STARTTLS upgrade, and TLS handshake on each, and exports per-endpoint posture under the observation key tls_probes.

For producers: the contract package

Service checkers that want TLS probing on the endpoints they discover should depend on git.happydns.org/checker-tls/contract and emit DiscoveryEntry records through it. The contract is a tiny producer↔consumer package; it has no dependency on checker-tls itself beyond the SDK.

import (
    sdk "git.happydns.org/checker-sdk-go/checker"
    tlsct "git.happydns.org/checker-tls/contract"
)

// DiscoverEntries is the sdk.DiscoveryPublisher hook on your provider.
func (p *xmppProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) {
    d := data.(*XMPPData)
    var out []sdk.DiscoveryEntry
    for _, srv := range d.Client {
        e, err := tlsct.NewEntry(tlsct.TLSEndpoint{
            Host:            srv.Target,
            Port:            srv.Port,
            SNI:             d.Domain, // only when it differs from Host
            STARTTLS:        "xmpp-client",
            RequireSTARTTLS: true,
        })
        if err != nil {
            return nil, err
        }
        out = append(out, e)
    }
    return out, nil
}

TLSEndpoint fields

Field Meaning
Host Target hostname (trailing dot tolerated).
Port TCP port.
SNI TLS SNI; leave empty when equal to Host.
STARTTLS SRV service name of the upgrade protocol; empty for direct TLS.
RequireSTARTTLS true when absence of STARTTLS must be reported crit, not opportunistic.

Helpers

Symbol Role
contract.Type The DiscoveryEntry.Type string ("tls.endpoint.v1").
contract.NewEntry(ep) Builds a DiscoveryEntry with a deterministic Ref and marshaled payload.
contract.Ref(ep) Exposes the Ref derivation so producers can reference it ahead of time.
contract.ParseEntry(e) Decodes a single entry; errors on wrong Type or malformed payload.
contract.ParseEntries(s) Filters a slice to this contract, decodes each, returns warnings for the malformed.

The Ref is a deterministic 16-hex-digit hash of (Host, Port, effective SNI, STARTTLS proto, RequireSTARTTLS). It appears as the key in tls_probes.probes and as the value consumers will see in any future RelatedObservation.Ref field.

Supported STARTTLS protocols

TLSEndpoint.STARTTLS Upgrade performed
(empty) Direct TLS on connect
smtp ESMTP EHLO + STARTTLS (RFC 3207)
submission Same as smtp
imap IMAP CAPABILITY + STARTTLS (RFC 3501)
pop3 POP3 CAPA + STLS (RFC 2595)
xmpp-client XMPP c2s stream + <starttls/> (RFC 6120)
xmpp-server XMPP s2s stream + <starttls/> (RFC 6120)
any other value Probe reports a handshake_failed issue

The protocol table lives in checker/starttls.go; new entries are added there, not in this repo's consumers.

Versioning

DiscoveryEntry.Type ends in .v1. If a future schema adds a field the consumer can't ignore safely, this package will introduce tls.endpoint.v2 alongside a new TLSEndpoint type, and old consumers will silently skip v2 entries (they do not match the expected Type).

Observation payload

Observation data written under tls_probes:

{
  "probes": {
    "<ref>": {
      "host": "example.net",
      "port": 5222,
      "endpoint": "example.net:5222",
      "type": "starttls-xmpp-client",
      "sni": "example.net",
      "tls_version": "TLS1.3",
      "cipher_suite": "TLS_AES_128_GCM_SHA256",
      "hostname_match": true,
      "chain_valid": true,
      "not_after": "2026-07-01T00:00:00Z",
      "issuer": "Let's Encrypt",
      "issuer_dn": "CN=R3,O=Let's Encrypt,C=US",
      "issuer_aki": "142EB317B75856CBAE500940E61FAF9D8B14C2C6",
      "issues": []
    }
  },
  "collected_at": "2026-04-21T12:34:56Z"
}

The map is keyed by contract.Ref(ep), the same value the host exposes on the lineage side so that a consumer knows which probe corresponds to which entry it originally published.

The type field inside each probe preserves the human-readable "tls" / "starttls-<proto>" shape for backward compatibility with existing downstream parsers.

Issues reported

  • tcp_unreachable, dial failed.
  • handshake_failed, TLS handshake or STARTTLS upgrade failed.
  • starttls_not_offered, server didn't advertise STARTTLS. Severity is crit when TLSEndpoint.RequireSTARTTLS is true, warn otherwise.
  • chain_invalid, leaf does not chain to a system-trusted root.
  • hostname_mismatch, cert SANs don't cover the SNI.
  • expired / expiring_soon, cert expiry posture.
  • weak_tls_version, negotiated TLS < 1.2.

Options

Id Type Default Description
probeTimeoutMs number 10000 Per-endpoint dial + handshake timeout in ms.

Running

# Plugin (loaded by happyDomain at startup)
make plugin

# Standalone HTTP server
make && ./checker-tls -listen :8080