Initial commit
This commit is contained in:
commit
d96ebc4d0e
19 changed files with 2537 additions and 0 deletions
219
checker/tls_related_test.go
Normal file
219
checker/tls_related_test.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// stubObsGetter is a minimal ObservationGetter that returns canned XMPPData
|
||||
// and a canned list of related observations.
|
||||
type stubObsGetter struct {
|
||||
xmpp XMPPData
|
||||
related []sdk.RelatedObservation
|
||||
relErr error
|
||||
}
|
||||
|
||||
func (s *stubObsGetter) Get(_ context.Context, key sdk.ObservationKey, dest any) error {
|
||||
if key != ObservationKeyXMPP {
|
||||
return nil
|
||||
}
|
||||
b, _ := json.Marshal(s.xmpp)
|
||||
return json.Unmarshal(b, dest)
|
||||
}
|
||||
|
||||
func (s *stubObsGetter) GetRelated(_ context.Context, _ sdk.ObservationKey) ([]sdk.RelatedObservation, error) {
|
||||
return s.related, s.relErr
|
||||
}
|
||||
|
||||
func mkTLSObs(t *testing.T, payload any) sdk.RelatedObservation {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal tls payload: %v", err)
|
||||
}
|
||||
return sdk.RelatedObservation{
|
||||
CheckerID: "tls",
|
||||
Key: TLSRelatedKey,
|
||||
Data: b,
|
||||
CollectedAt: time.Now(),
|
||||
Ref: "ep-1",
|
||||
}
|
||||
}
|
||||
|
||||
func TestRule_FoldsTLSCritIntoAggregate(t *testing.T) {
|
||||
obs := &stubObsGetter{
|
||||
xmpp: healthyXMPPData(),
|
||||
related: []sdk.RelatedObservation{
|
||||
mkTLSObs(t, map[string]any{
|
||||
"host": "xmpp.example.com",
|
||||
"port": 5222,
|
||||
"chain_valid": false,
|
||||
"hostname_match": true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
states := (&xmppRule{}).Evaluate(context.Background(), obs, sdk.CheckerOptions{"domain": "example.com", "mode": "both"})
|
||||
state := states[0]
|
||||
if state.Status != sdk.StatusCrit {
|
||||
t.Fatalf("expected StatusCrit due to TLS chain invalid, got %s (%s)", state.Status, state.Message)
|
||||
}
|
||||
if !strings.Contains(state.Message, "xmpp.example.com:5222") && !strings.Contains(state.Message, "Invalid certificate") {
|
||||
t.Fatalf("expected TLS message in state, got %q", state.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRule_IgnoresUnrelatedTLSObs(t *testing.T) {
|
||||
obs := &stubObsGetter{
|
||||
xmpp: healthyXMPPData(),
|
||||
related: nil,
|
||||
}
|
||||
states := (&xmppRule{}).Evaluate(context.Background(), obs, sdk.CheckerOptions{"domain": "example.com", "mode": "both"})
|
||||
state := states[0]
|
||||
if state.Status != sdk.StatusOK {
|
||||
t.Fatalf("expected StatusOK without related TLS issues, got %s (%s)", state.Status, state.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLReportCtx_IncludesTLSPosture(t *testing.T) {
|
||||
data := healthyXMPPData()
|
||||
p := &xmppProvider{}
|
||||
related := []sdk.RelatedObservation{
|
||||
mkTLSObs(t, map[string]any{
|
||||
"host": "xmpp.example.com",
|
||||
"port": 5222,
|
||||
"chain_valid": true,
|
||||
"hostname_match": true,
|
||||
"not_after": time.Now().Add(60 * 24 * time.Hour).Format(time.RFC3339),
|
||||
"tls_version": "TLS 1.3",
|
||||
}),
|
||||
}
|
||||
rctx := &stubReportCtx{data: mustJSON(t, data), related: related}
|
||||
html, err := p.GetHTMLReport(rctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHTMLReport: %v", err)
|
||||
}
|
||||
if !strings.Contains(html, "chain valid") {
|
||||
t.Fatal("expected 'chain valid' in HTML, not found")
|
||||
}
|
||||
if !strings.Contains(html, "hostname match") {
|
||||
t.Fatal("expected 'hostname match' in HTML, not found")
|
||||
}
|
||||
if !strings.Contains(html, "TLS checker") {
|
||||
t.Fatal("expected TLS checker footer mention, not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLReport_BackCompatNoRelated(t *testing.T) {
|
||||
data := healthyXMPPData()
|
||||
p := &xmppProvider{}
|
||||
// StaticReportContext mimics the host-side "no related observations" path
|
||||
// (e.g. /report HTTP handler on the remote checker).
|
||||
html, err := p.GetHTMLReport(sdk.StaticReportContext(mustJSON(t, data)))
|
||||
if err != nil {
|
||||
t.Fatalf("GetHTMLReport: %v", err)
|
||||
}
|
||||
// Renderer must still produce a valid document and must not include TLS
|
||||
// posture rows when no related observations were passed.
|
||||
if !strings.Contains(html, "<title>XMPP Report") {
|
||||
t.Fatal("expected report title in HTML")
|
||||
}
|
||||
if strings.Contains(html, "TLS cert") {
|
||||
t.Fatal("did not expect 'TLS cert' row without related observations")
|
||||
}
|
||||
}
|
||||
|
||||
type stubReportCtx struct {
|
||||
data json.RawMessage
|
||||
related []sdk.RelatedObservation
|
||||
}
|
||||
|
||||
func (s *stubReportCtx) Data() json.RawMessage { return s.data }
|
||||
func (s *stubReportCtx) Related(_ sdk.ObservationKey) []sdk.RelatedObservation {
|
||||
return s.related
|
||||
}
|
||||
|
||||
func mustJSON(t *testing.T, v any) json.RawMessage {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func healthyXMPPData() XMPPData {
|
||||
return XMPPData{
|
||||
Domain: "example.com",
|
||||
SRV: SRVLookup{
|
||||
Client: []SRVRecord{{Target: "xmpp.example.com", Port: 5222}},
|
||||
Server: []SRVRecord{{Target: "xmpp.example.com", Port: 5269}},
|
||||
},
|
||||
Endpoints: []EndpointProbe{
|
||||
{
|
||||
Mode: ModeClient, Target: "xmpp.example.com", Port: 5222,
|
||||
Address: "xmpp.example.com:5222",
|
||||
TCPConnected: true,
|
||||
StreamOpened: true,
|
||||
STARTTLSOffered: true,
|
||||
STARTTLSRequired: true,
|
||||
STARTTLSUpgraded: true,
|
||||
FeaturesRead: true,
|
||||
SASLMechanisms: []string{"SCRAM-SHA-256", "SCRAM-SHA-256-PLUS"},
|
||||
},
|
||||
{
|
||||
Mode: ModeServer, Target: "xmpp.example.com", Port: 5269,
|
||||
Address: "xmpp.example.com:5269",
|
||||
TCPConnected: true,
|
||||
StreamOpened: true,
|
||||
STARTTLSOffered: true,
|
||||
STARTTLSRequired: true,
|
||||
STARTTLSUpgraded: true,
|
||||
FeaturesRead: true,
|
||||
DialbackOffered: true,
|
||||
},
|
||||
},
|
||||
Coverage: ReachabilitySpan{HasIPv4: true, WorkingC2S: true, WorkingS2S: true},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSIssuesFromRelated_StructuredIssues(t *testing.T) {
|
||||
related := []sdk.RelatedObservation{
|
||||
mkTLSObs(t, map[string]any{
|
||||
"host": "xmpp.example.com",
|
||||
"port": 5222,
|
||||
"issues": []map[string]any{
|
||||
{"code": "tls.self_signed", "severity": "crit", "message": "self-signed cert"},
|
||||
{"code": "tls.weak_cipher", "severity": "warn", "message": "weak cipher"},
|
||||
},
|
||||
}),
|
||||
}
|
||||
out := tlsIssuesFromRelated(related)
|
||||
if len(out) != 2 {
|
||||
t.Fatalf("expected 2 issues, got %d", len(out))
|
||||
}
|
||||
if out[0].Code != "xmpp.tls.tls.self_signed" || out[0].Severity != SeverityCrit {
|
||||
t.Fatalf("unexpected first issue: %+v", out[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSIssuesFromRelated_FlagsOnly(t *testing.T) {
|
||||
related := []sdk.RelatedObservation{
|
||||
mkTLSObs(t, map[string]any{
|
||||
"host": "xmpp.example.com",
|
||||
"port": 5222,
|
||||
"hostname_match": false,
|
||||
}),
|
||||
}
|
||||
out := tlsIssuesFromRelated(related)
|
||||
if len(out) != 1 {
|
||||
t.Fatalf("expected 1 synthesized issue, got %d", len(out))
|
||||
}
|
||||
if out[0].Severity != SeverityCrit || !strings.Contains(out[0].Message, "does not cover") {
|
||||
t.Fatalf("unexpected synthesized issue: %+v", out[0])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue