checker-tls/checker/rules_protocol.go

105 lines
2.8 KiB
Go

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
}