Compare commits
No commits in common. "7eb0dbddc740b57c7b83327cf63749d555a27716" and "6e59eb545dd5ec61283b9f858c83b499ffdf0bbc" have entirely different histories.
7eb0dbddc7
...
6e59eb545d
22 changed files with 61 additions and 61 deletions
30
README.md
30
README.md
|
|
@ -38,27 +38,27 @@ view; the default is a JSON metrics dump.
|
|||
|
||||
Both checkers accept the same options:
|
||||
|
||||
- `domain_name` (auto-filled) — required
|
||||
- `username`, `password` — optional Basic credentials; unlock authenticated
|
||||
- `domain_name` (auto-filled): required
|
||||
- `username`, `password`: optional Basic credentials; unlock authenticated
|
||||
checks (principal, home-set, collections, REPORT probe)
|
||||
- `context_url` — optional explicit override, bypasses `/.well-known` + SRV
|
||||
- `timeout_seconds` — per-request HTTP timeout, default 10
|
||||
- `context_url`: optional explicit override, bypasses `/.well-known` + SRV
|
||||
- `timeout_seconds`: per-request HTTP timeout, default 10
|
||||
|
||||
## What is checked
|
||||
|
||||
1. **Discovery** — `/.well-known/{caldav,carddav}` (must 3xx, not 200),
|
||||
1. **Discovery**: `/.well-known/{caldav,carddav}` (must 3xx, not 200),
|
||||
`_caldavs._tcp` / `_carddavs._tcp` SRV, TXT `path=` hint.
|
||||
2. **Transport** — HTTPS reachable. TLS certificate validation is
|
||||
deliberately out of scope — a dedicated TLS checker covers that.
|
||||
3. **OPTIONS** — `DAV:` advertises `calendar-access` or `addressbook`; Allow
|
||||
2. **Transport**: HTTPS reachable. TLS certificate validation is
|
||||
deliberately out of scope; a dedicated TLS checker covers that.
|
||||
3. **OPTIONS**: `DAV:` advertises `calendar-access` or `addressbook`; Allow
|
||||
includes `PROPFIND` and `REPORT`; auth schemes captured for info.
|
||||
4. **Principal** — PROPFIND `current-user-principal` (auth required).
|
||||
5. **Home-set** — `calendar-home-set` / `addressbook-home-set`.
|
||||
6. **Collections** — enumerate, record properties (`supported-calendar-component-set`,
|
||||
4. **Principal**: PROPFIND `current-user-principal` (auth required).
|
||||
5. **Home-set**: `calendar-home-set` / `addressbook-home-set`.
|
||||
6. **Collections**: enumerate, record properties (`supported-calendar-component-set`,
|
||||
`supported-address-data`, display name, description, max size).
|
||||
7. **REPORT probe** — issue a minimal `calendar-query` / `addressbook-query`
|
||||
7. **REPORT probe**: issue a minimal `calendar-query` / `addressbook-query`
|
||||
against the first collection.
|
||||
8. **Scheduling** (CalDAV only) — if `calendar-schedule` is advertised,
|
||||
8. **Scheduling** (CalDAV only): if `calendar-schedule` is advertised,
|
||||
verify `schedule-inbox-URL` and `schedule-outbox-URL` on the principal.
|
||||
|
||||
The HTML report surfaces the most common failures at the top as callouts:
|
||||
|
|
@ -71,5 +71,5 @@ The HTML report surfaces the most common failures at the top as callouts:
|
|||
|
||||
## Dependencies
|
||||
|
||||
- [`github.com/emersion/go-webdav`](https://github.com/emersion/go-webdav) — CalDAV/CardDAV client
|
||||
- [`git.happydns.org/checker-sdk-go`](https://git.happydns.org/happyDomain/checker-sdk-go) — checker SDK
|
||||
- [`github.com/emersion/go-webdav`](https://github.com/emersion/go-webdav): CalDAV/CardDAV client
|
||||
- [`git.happydns.org/checker-sdk-go`](https://git.happydns.org/happyDomain/checker-sdk-go): checker SDK
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ func (p *caldavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (
|
|||
|
||||
anonClient := dav.NewHTTPClient(timeout)
|
||||
|
||||
// Phase 1 — Discovery
|
||||
// Phase 1: Discovery
|
||||
obs.Discovery = dav.Discover(ctx, anonClient, dav.KindCalDAV, domain, explicit)
|
||||
if obs.Discovery.ContextURL == "" {
|
||||
return obs, nil
|
||||
}
|
||||
|
||||
// Phase 2 — Transport + OPTIONS (no auth required)
|
||||
// Phase 2: Transport + OPTIONS (no auth required)
|
||||
optsRes, err := dav.ProbeOptions(ctx, anonClient, obs.Discovery.ContextURL)
|
||||
obs.Options = optsRes
|
||||
if err != nil {
|
||||
|
|
@ -54,7 +54,7 @@ func (p *caldavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (
|
|||
obs.Transport = dav.TransportResult{Reached: true}
|
||||
obs.Scheduling.Advertised = optsRes.HasCapability("calendar-schedule")
|
||||
|
||||
// Phase 3 — Authenticated probes
|
||||
// Phase 3: Authenticated probes
|
||||
if !obs.HasCredentials {
|
||||
obs.Principal.Skipped = true
|
||||
obs.HomeSet.Skipped = true
|
||||
|
|
@ -110,7 +110,7 @@ func (p *caldavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (
|
|||
}
|
||||
}
|
||||
|
||||
// Report probe — empty calendar-query against the first calendar.
|
||||
// Report probe: empty calendar-query against the first calendar.
|
||||
if len(obs.Collections.Items) > 0 {
|
||||
first := obs.Collections.Items[0].Path
|
||||
obs.Report.ProbePath = first
|
||||
|
|
@ -131,7 +131,7 @@ func (p *caldavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (
|
|||
obs.Report.Skipped = true
|
||||
}
|
||||
|
||||
// Scheduling inbox/outbox — only probe if advertised.
|
||||
// Scheduling inbox/outbox: only probe if advertised.
|
||||
if obs.Scheduling.Advertised {
|
||||
inbox, outbox, err := dav.FindScheduleURLs(ctx, authClient, principal)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ func Definition() *sdk.CheckerDefinition {
|
|||
// always offered at domain scope.
|
||||
ApplyToDomain: true,
|
||||
|
||||
// Also offered at service scope so alerts — including the TLS
|
||||
// alerts derived from the endpoints we publish — surface on a
|
||||
// Also offered at service scope so alerts, including the TLS
|
||||
// alerts derived from the endpoints we publish, surface on a
|
||||
// dedicated "CalDAV" service page rather than on the domain
|
||||
// page. The abstract.CalDAV service type does not exist in the
|
||||
// happyDomain service catalog yet; until it does, this has no
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
//
|
||||
// Downstream TLS probes published for the endpoints we discovered are read
|
||||
// via ctx.Related(dav.TLSRelatedKey) and folded into the report (callouts +
|
||||
// dedicated TLS phase) — per
|
||||
// dedicated TLS phase), per
|
||||
// happydomain3/docs/checker-discovery-endpoint.md.
|
||||
func (p *caldavProvider) GetHTMLReport(ctx sdk.ReportContext) (string, error) {
|
||||
var d dav.Observation
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ func (p *carddavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions)
|
|||
|
||||
anonClient := dav.NewHTTPClient(timeout)
|
||||
|
||||
// Phase 1 — Discovery
|
||||
// Phase 1: Discovery
|
||||
obs.Discovery = dav.Discover(ctx, anonClient, dav.KindCardDAV, domain, explicit)
|
||||
if obs.Discovery.ContextURL == "" {
|
||||
return obs, nil
|
||||
}
|
||||
|
||||
// Phase 2 — OPTIONS
|
||||
// Phase 2: OPTIONS
|
||||
optsRes, err := dav.ProbeOptions(ctx, anonClient, obs.Discovery.ContextURL)
|
||||
obs.Options = optsRes
|
||||
if err != nil {
|
||||
|
|
@ -47,7 +47,7 @@ func (p *carddavProvider) Collect(ctx context.Context, opts sdk.CheckerOptions)
|
|||
}
|
||||
obs.Transport = dav.TransportResult{Reached: true}
|
||||
|
||||
// Phase 3 — Authenticated
|
||||
// Phase 3: Authenticated
|
||||
if !obs.HasCredentials {
|
||||
obs.Principal.Skipped = true
|
||||
obs.HomeSet.Skipped = true
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// DiscoverEntries implements sdk.DiscoveryPublisher. See the CalDAV sibling
|
||||
// for the rationale — the shared helper produces the TLS discovery entries.
|
||||
// for the rationale; the shared helper produces the TLS discovery entries.
|
||||
func (p *carddavProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) {
|
||||
obs, ok := data.(*dav.Observation)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
// GetHTMLReport folds downstream TLS probes (published on our discovered
|
||||
// endpoints) into the CardDAV report via ctx.Related — see the CalDAV
|
||||
// endpoints) into the CardDAV report via ctx.Related; see the CalDAV
|
||||
// sibling for the rationale.
|
||||
func (p *carddavProvider) GetHTMLReport(ctx sdk.ReportContext) (string, error) {
|
||||
var d dav.Observation
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"log"
|
||||
|
||||
"git.happydns.org/checker-dav/caldav"
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
"git.happydns.org/checker-sdk-go/checker/server"
|
||||
)
|
||||
|
||||
// Version is injected at link time via -ldflags "-X main.Version=...".
|
||||
|
|
@ -16,8 +16,8 @@ var listenAddr = flag.String("listen", ":8080", "HTTP listen address")
|
|||
func main() {
|
||||
flag.Parse()
|
||||
caldav.Version = Version
|
||||
server := sdk.NewServer(caldav.Provider())
|
||||
if err := server.ListenAndServe(*listenAddr); err != nil {
|
||||
srv := server.New(caldav.Provider())
|
||||
if err := srv.ListenAndServe(*listenAddr); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"log"
|
||||
|
||||
"git.happydns.org/checker-dav/carddav"
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
"git.happydns.org/checker-sdk-go/checker/server"
|
||||
)
|
||||
|
||||
// Version is injected at link time via -ldflags "-X main.Version=...".
|
||||
|
|
@ -16,8 +16,8 @@ var listenAddr = flag.String("listen", ":8080", "HTTP listen address")
|
|||
func main() {
|
||||
flag.Parse()
|
||||
carddav.Version = Version
|
||||
server := sdk.NewServer(carddav.Provider())
|
||||
if err := server.ListenAndServe(*listenAddr); err != nil {
|
||||
srv := server.New(carddav.Provider())
|
||||
if err := srv.ListenAndServe(*listenAddr); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -3,7 +3,7 @@ module git.happydns.org/checker-dav
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.happydns.org/checker-sdk-go v1.2.0
|
||||
git.happydns.org/checker-sdk-go v1.3.0
|
||||
git.happydns.org/checker-tls v0.2.0
|
||||
)
|
||||
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -1,5 +1,5 @@
|
|||
git.happydns.org/checker-sdk-go v1.2.0 h1:v4MpKAz0W3PwP+bxx3pya8w893sVH5xTD1of1cc0TV8=
|
||||
git.happydns.org/checker-sdk-go v1.2.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
||||
git.happydns.org/checker-sdk-go v1.3.0 h1:FG2kIhlJCzI0m35EhxSgn4UWc9M4ha6aZTeoChu4l7A=
|
||||
git.happydns.org/checker-sdk-go v1.3.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
||||
git.happydns.org/checker-tls v0.2.0 h1:2dYpcePBylUc3le76fFlLbxraiLpGESmOhx4NfD7REM=
|
||||
git.happydns.org/checker-tls v0.2.0/go.mod h1:0ZSG0CTP007SHBPE7qInESVIOcW+xgucHUhHgj6MeZ8=
|
||||
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 h1:kHoSgklT8weIDl6R6xFpBJ5IioRdBU1v2X2aCZRVCcM=
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
// NewHTTPClient returns an http.Client with a sane default transport for
|
||||
// probing DAV servers. TLS certificate validation uses Go's default rules —
|
||||
// probing DAV servers. TLS certificate validation uses Go's default rules;
|
||||
// dedicated TLS correctness belongs in a separate checker.
|
||||
func NewHTTPClient(timeout time.Duration) *http.Client {
|
||||
return &http.Client{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
// then SRV/TXT. An explicit override shortcuts everything.
|
||||
//
|
||||
// The returned Observation.Discovery is fully populated with whatever was
|
||||
// learned along the way, even if every step fails — the report leans on the
|
||||
// learned along the way, even if every step fails; the report leans on the
|
||||
// captured evidence to tell the user which leg of the discovery broke.
|
||||
func Discover(ctx context.Context, client *http.Client, kind Kind, domain, explicitURL string) DiscoveryResult {
|
||||
res := DiscoveryResult{}
|
||||
|
|
@ -26,7 +26,7 @@ func Discover(ctx context.Context, client *http.Client, kind Kind, domain, expli
|
|||
return res
|
||||
}
|
||||
|
||||
// 1. /.well-known — this is the #1 misconfig hotspot, so we always probe
|
||||
// 1. /.well-known: this is the #1 misconfig hotspot, so we always probe
|
||||
// it even if SRV below might have worked, to surface the mistake.
|
||||
wellKnown := "https://" + domain + kind.WellKnownPath()
|
||||
res.WellKnownURL = wellKnown
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ import (
|
|||
// A CalDAV/CardDAV context URL always implies a direct-TLS HTTPS endpoint,
|
||||
// so we emit a single tls.endpoint.v1 entry for the resolved context URL's
|
||||
// host:port. If the endpoint was reached via SRV, we also surface each SRV
|
||||
// target as its own entry — those are the names operators actually need
|
||||
// target as its own entry; those are the names operators actually need
|
||||
// certificates on, and they may differ from the queried domain.
|
||||
//
|
||||
// SNI is always populated (equal to Host for CalDAV/CardDAV, since — unlike
|
||||
// XMPP (RFC 6120 §13.7.2.1) — there is no mandated source-domain-vs-target
|
||||
// SNI is always populated (equal to Host for CalDAV/CardDAV, since, unlike
|
||||
// XMPP (RFC 6120 §13.7.2.1), there is no mandated source-domain-vs-target
|
||||
// split: clients negotiate TLS for the hostname they connect to). We fill
|
||||
// the field unconditionally so consumers can rely on it being set.
|
||||
func DiscoverEntries(obs *Observation) []sdk.DiscoveryEntry {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func TestDiscoverEntries_contextURLOnly(t *testing.T) {
|
|||
if got[0].Host != "dav.example.com" || got[0].Port != 443 {
|
||||
t.Errorf("unexpected endpoint: %+v", got[0])
|
||||
}
|
||||
// Direct TLS — no STARTTLS upgrade.
|
||||
// Direct TLS; no STARTTLS upgrade.
|
||||
if got[0].STARTTLS != "" {
|
||||
t.Errorf("STARTTLS = %q, want empty (direct TLS)", got[0].STARTTLS)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// ProbeOptions issues an HTTP OPTIONS against url and reports the parsed DAV
|
||||
// headers. A missing DAV: header, or one that does not contain the kind's
|
||||
// required capability, is not treated as a transport error here — the caller
|
||||
// required capability, is not treated as a transport error here; the caller
|
||||
// rule decides severity from the parsed values.
|
||||
func ProbeOptions(ctx context.Context, client *http.Client, url string) (OptionsResult, error) {
|
||||
res := OptionsResult{}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func UserOptions() []sdk.CheckerOptionDocumentation {
|
|||
Id: "context_url",
|
||||
Type: "string",
|
||||
Label: "Explicit context URL",
|
||||
Description: "Optional. Bypasses /.well-known and SRV discovery — use for servers with a non-standard layout.",
|
||||
Description: "Optional. Bypasses /.well-known and SRV discovery. Use for servers with a non-standard layout.",
|
||||
Placeholder: "https://dav.example.com/caldav/",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func FindScheduleURLs(ctx context.Context, client *http.Client, principalURL str
|
|||
// ── raw PROPFIND ─────────────────────────────────────────────────────────────
|
||||
|
||||
// multistatus is the subset of the DAV:multistatus XML schema we need to read
|
||||
// principal URLs and scheduling hrefs. It is intentionally permissive — extra
|
||||
// principal URLs and scheduling hrefs. It is intentionally permissive, extra
|
||||
// elements are ignored, which makes us tolerant of server-specific extensions.
|
||||
type multistatus struct {
|
||||
XMLName xml.Name `xml:"DAV: multistatus"`
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ func buildCallouts(o *Observation) []calloutData {
|
|||
out = append(out, calloutData{
|
||||
Severity: "crit",
|
||||
Title: fmt.Sprintf("Server does not advertise %q", o.Kind.RequiredCapability()),
|
||||
Body: fmt.Sprintf("The DAV: response header is %q — this endpoint is not a %s server, or a reverse proxy is stripping headers.", strings.Join(o.Options.DAVClasses, ", "), o.Kind),
|
||||
Body: fmt.Sprintf("The DAV: response header is %q. This endpoint is not a %s server, or a reverse proxy is stripping headers.", strings.Join(o.Options.DAVClasses, ", "), o.Kind),
|
||||
})
|
||||
}
|
||||
if !o.HasCredentials && o.Discovery.ContextURL != "" && o.Options.HasCapability(o.Kind.RequiredCapability()) {
|
||||
|
|
@ -171,7 +171,7 @@ func exampleContextURL(k Kind) string {
|
|||
func buildPhases(o *Observation) []phaseData {
|
||||
var phases []phaseData
|
||||
|
||||
// Phase 1 — Discovery
|
||||
// Phase 1: Discovery
|
||||
discovery := phaseData{Title: "Discovery"}
|
||||
discovery.Items = append(discovery.Items, itemFor(
|
||||
"/.well-known redirect",
|
||||
|
|
@ -205,7 +205,7 @@ func buildPhases(o *Observation) []phaseData {
|
|||
discovery.Open = hasItemSeverity(discovery.Items, "warn", "fail")
|
||||
phases = append(phases, discovery)
|
||||
|
||||
// Phase 2 — Transport + OPTIONS
|
||||
// Phase 2: Transport + OPTIONS
|
||||
transport := phaseData{Title: "Transport & OPTIONS"}
|
||||
transport.Items = append(transport.Items,
|
||||
itemFor("HTTPS reached", boolStatus(o.Transport.Reached, "crit"), o.Transport.Error, ""),
|
||||
|
|
@ -221,7 +221,7 @@ func buildPhases(o *Observation) []phaseData {
|
|||
transport.Open = hasItemSeverity(transport.Items, "warn", "fail")
|
||||
phases = append(phases, transport)
|
||||
|
||||
// Phase 3 — Authenticated
|
||||
// Phase 3: Authenticated
|
||||
auth := phaseData{Title: "Authenticated probes"}
|
||||
auth.Items = append(auth.Items,
|
||||
authItemFor("Principal", o.Principal.URL, o.Principal.Skipped, o.Principal.Error),
|
||||
|
|
@ -232,7 +232,7 @@ func buildPhases(o *Observation) []phaseData {
|
|||
auth.Open = hasItemSeverity(auth.Items, "warn", "fail")
|
||||
phases = append(phases, auth)
|
||||
|
||||
// Phase 4 — Scheduling (CalDAV only)
|
||||
// Phase 4: Scheduling (CalDAV only)
|
||||
if o.Kind == KindCalDAV && o.Scheduling != nil {
|
||||
sched := phaseData{Title: "Scheduling (CalDAV)"}
|
||||
if !o.Scheduling.Advertised {
|
||||
|
|
@ -259,7 +259,7 @@ func buildTLSPhase(summaries []TLSSummary) phaseData {
|
|||
for _, s := range summaries {
|
||||
label := s.Address
|
||||
if s.TLSVersion != "" {
|
||||
label = fmt.Sprintf("%s — %s", s.Address, s.TLSVersion)
|
||||
label = fmt.Sprintf("%s (%s)", s.Address, s.TLSVersion)
|
||||
}
|
||||
p.Items = append(p.Items, phaseItem{
|
||||
Label: label,
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func (r *discoveryRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter,
|
|||
Message: "could not resolve a context URL (no /.well-known redirect and no SRV record)",
|
||||
}}
|
||||
}
|
||||
// /.well-known returning 200 is legal per RFC but strongly discouraged —
|
||||
// /.well-known returning 200 is legal per RFC but strongly discouraged;
|
||||
// many clients won't follow it. Warn, don't crit.
|
||||
if disc.WellKnownCode == 200 && disc.Source != "explicit" {
|
||||
return []sdk.CheckState{{
|
||||
|
|
@ -250,7 +250,7 @@ func (r *collectionsRule) Evaluate(ctx context.Context, obs sdk.ObservationGette
|
|||
return []sdk.CheckState{{
|
||||
Status: sdk.StatusWarn,
|
||||
Code: "collections_empty",
|
||||
Message: "home-set is empty — the account has no calendars/addressbooks",
|
||||
Message: "home-set is empty; the account has no calendars/addressbooks",
|
||||
}}
|
||||
}
|
||||
out := make([]sdk.CheckState, 0, len(c.Items))
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
const TLSRelatedKey sdk.ObservationKey = "tls_probes"
|
||||
|
||||
// tlsProbeView is a permissive decode of a TLS probe payload. We intentionally
|
||||
// only read the fields we need and tolerate missing ones — the TLS checker's
|
||||
// only read the fields we need and tolerate missing ones; the TLS checker's
|
||||
// full schema is owned by that checker.
|
||||
type tlsProbeView struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
|
|
@ -104,11 +104,11 @@ func (v *tlsProbeView) chainOK() (bool, bool) {
|
|||
//
|
||||
// Two payload shapes are accepted:
|
||||
//
|
||||
// 1. {"probes": {"<ref>": <probe>, …}} — the current convention used by
|
||||
// checker-tls. Each consumer picks its own probe via r.Ref — the value
|
||||
// 1. {"probes": {"<ref>": <probe>, …}}: the current convention used by
|
||||
// checker-tls. Each consumer picks its own probe via r.Ref; the value
|
||||
// is the DiscoveryEntry.Ref that the producer originally emitted,
|
||||
// preserved by the host along the lineage chain.
|
||||
// 2. <probe> — a single top-level probe object, kept for back-compat with
|
||||
// 2. <probe>: a single top-level probe object, kept for back-compat with
|
||||
// callers that pre-date the keyed map and with unit-test fixtures.
|
||||
func parseTLSRelated(r sdk.RelatedObservation) *tlsProbeView {
|
||||
var keyed struct {
|
||||
|
|
@ -248,7 +248,7 @@ func buildTLSCallouts(v *tlsProbeView, addr string) []tlsCallout {
|
|||
out = append(out, tlsCallout{
|
||||
Severity: "crit",
|
||||
Title: fmt.Sprintf("Certificate on %s has expired", addr),
|
||||
Body: fmt.Sprintf("Renew it — clients will refuse to connect. Expired %d day(s) ago (valid until %s).", -days, t.Format(time.RFC3339)),
|
||||
Body: fmt.Sprintf("Renew it. Clients will refuse to connect. Expired %d day(s) ago (valid until %s).", -days, t.Format(time.RFC3339)),
|
||||
})
|
||||
case days < 14:
|
||||
out = append(out, tlsCallout{
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func TestFoldTLSRelated_explicitIssueWinsOverFlags(t *testing.T) {
|
|||
{"code": "weak_cipher", "severity": "warn", "message": "TLS 1.0 offered", "fix": "disable TLS <1.2"},
|
||||
},
|
||||
})})
|
||||
// When explicit issues exist, we do not also emit synthesized callouts —
|
||||
// When explicit issues exist, we do not also emit synthesized callouts;
|
||||
// the TLS checker is the source of truth for severity and wording.
|
||||
if len(callouts) != 1 || callouts[0].Severity != "warn" {
|
||||
t.Fatalf("want single warn callout, got %+v", callouts)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue