package checker import ( "context" "crypto/tls" "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) // tlsVersionRule flags endpoints negotiating a protocol version below the // recommended TLS 1.2 floor. type tlsVersionRule struct{} func (r *tlsVersionRule) Name() string { return "tls.version" } func (r *tlsVersionRule) Description() string { return "Flags endpoints negotiating a TLS version below the recommended TLS 1.2." } func (r *tlsVersionRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } if len(data.Probes) == 0 { return []sdk.CheckState{emptyCaseState("tls.version.no_endpoints")} } var out []sdk.CheckState any := false for _, ref := range sortedRefs(data) { p := data.Probes[ref] if p.TLSVersionNum == 0 { continue } any = true if p.TLSVersionNum >= tls.VersionTLS12 { continue } out = append(out, sdk.CheckState{ Status: sdk.StatusWarn, Code: "tls.version.weak", Subject: subjectOf(p), Message: fmt.Sprintf("Negotiated TLS version %s is below the recommended TLS 1.2.", p.TLSVersion), Meta: metaOf(p), }) } if !any { return []sdk.CheckState{unknownState( "tls.version.skipped", "No endpoint completed a TLS handshake.", )} } if len(out) == 0 { return []sdk.CheckState{passState( "tls.version.ok", "Every endpoint negotiates TLS 1.2 or higher.", )} } return out } // cipherSuiteRule reports the negotiated cipher suite for visibility. // It does not currently classify suites as weak/strong: go's crypto/tls // refuses to negotiate the known-weak suites anyway. The rule exists so the // UI can expose the suite in the passing-list rather than leaving it buried // in the raw observation. type cipherSuiteRule struct{} func (r *cipherSuiteRule) Name() string { return "tls.cipher_suite" } func (r *cipherSuiteRule) Description() string { return "Reports the cipher suite negotiated on each endpoint." } func (r *cipherSuiteRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } if len(data.Probes) == 0 { return []sdk.CheckState{emptyCaseState("tls.cipher_suite.no_endpoints")} } var out []sdk.CheckState for _, ref := range sortedRefs(data) { p := data.Probes[ref] if p.CipherSuite == "" { continue } out = append(out, sdk.CheckState{ Status: sdk.StatusInfo, Code: "tls.cipher_suite.negotiated", Subject: subjectOf(p), Message: fmt.Sprintf("Cipher suite %s negotiated.", p.CipherSuite), Meta: metaOf(p), }) } if len(out) == 0 { return []sdk.CheckState{unknownState( "tls.cipher_suite.skipped", "No endpoint completed a TLS handshake.", )} } return out }