129 lines
4.6 KiB
Go
129 lines
4.6 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// Rules in this file cover the optional bind-credentials workflow (driven
|
|
// by the bind_dn / bind_password / base_dn options) plus the cross-checker
|
|
// TLS-quality fold. Like the other rules, they read raw EndpointProbe
|
|
// fields and downstream observations directly.
|
|
|
|
// bindCredentialsRule: optional authenticated bind. Skipped when bind_dn
|
|
// isn't supplied, and reported as "not tested" when no encrypted endpoint
|
|
// was available to attempt it on.
|
|
type bindCredentialsRule struct{}
|
|
|
|
func (r *bindCredentialsRule) Name() string { return "ldap.bind_credentials" }
|
|
func (r *bindCredentialsRule) Description() string {
|
|
return "Verifies the supplied bind credentials are accepted by the directory (only runs when bind_dn is set)."
|
|
}
|
|
|
|
func (r *bindCredentialsRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
if optString(opts, "bind_dn") == "" {
|
|
return []sdk.CheckState{notTestedState("ldap.bind_credentials.skipped", "Authenticated bind not tested (no bind_dn supplied).")}
|
|
}
|
|
data, errSt := loadLDAPData(ctx, obs)
|
|
if errSt != nil {
|
|
return []sdk.CheckState{*errSt}
|
|
}
|
|
var states []sdk.CheckState
|
|
attempted := false
|
|
for _, ep := range data.Endpoints {
|
|
if !ep.BindAttempted {
|
|
continue
|
|
}
|
|
attempted = true
|
|
if ep.BindOK {
|
|
states = append(states, infoState(
|
|
CodeBindOK,
|
|
"Bind on "+ep.Address+" succeeded with the provided credentials.",
|
|
ep.Address,
|
|
"",
|
|
))
|
|
} else {
|
|
states = append(states, critState(
|
|
CodeBindFailed,
|
|
"Bind on "+ep.Address+" failed: "+ep.BindError,
|
|
ep.Address,
|
|
"Verify the bind DN exists and the password is current. On AD, check the account is not locked/expired.",
|
|
))
|
|
}
|
|
}
|
|
if !attempted {
|
|
return []sdk.CheckState{notTestedState("ldap.bind_credentials.skipped", "Bind not attempted on any endpoint (no encrypted endpoint reachable).")}
|
|
}
|
|
if len(states) == 0 {
|
|
return []sdk.CheckState{passState("ldap.bind_credentials.ok", "Bind succeeded with the provided credentials.")}
|
|
}
|
|
return states
|
|
}
|
|
|
|
// baseDNReadRule: optional baseObject read on the supplied base_dn.
|
|
type baseDNReadRule struct{}
|
|
|
|
func (r *baseDNReadRule) Name() string { return "ldap.base_dn_read" }
|
|
func (r *baseDNReadRule) Description() string {
|
|
return "Verifies the bound account can read the supplied base DN (only runs when base_dn is set and bind succeeded)."
|
|
}
|
|
|
|
func (r *baseDNReadRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
if optString(opts, "base_dn") == "" {
|
|
return []sdk.CheckState{notTestedState("ldap.base_dn_read.skipped", "Base DN read not tested (no base_dn supplied).")}
|
|
}
|
|
data, errSt := loadLDAPData(ctx, obs)
|
|
if errSt != nil {
|
|
return []sdk.CheckState{*errSt}
|
|
}
|
|
var states []sdk.CheckState
|
|
attempted := false
|
|
for _, ep := range data.Endpoints {
|
|
if !ep.BaseReadAttempted {
|
|
continue
|
|
}
|
|
attempted = true
|
|
if ep.BaseReadOK {
|
|
states = append(states, infoState(
|
|
CodeBaseReadOK,
|
|
"Base DN read succeeded on "+ep.Address+" (entries="+strconv.Itoa(ep.BaseReadEntries)+").",
|
|
ep.Address,
|
|
"",
|
|
))
|
|
} else {
|
|
states = append(states, critState(
|
|
CodeBaseReadFailed,
|
|
"Bind succeeded but baseObject read on "+data.BaseDN+" failed: "+ep.BaseReadError,
|
|
ep.Address,
|
|
"Verify the bind DN has read access to the base DN -- typically granted via an ACL entry such as `access to dn.subtree=\"<base>\" by dn.exact=\"<bind>\" read`.",
|
|
))
|
|
}
|
|
}
|
|
if !attempted {
|
|
return []sdk.CheckState{notTestedState("ldap.base_dn_read.skipped", "Base DN read not attempted (bind did not succeed on any endpoint).")}
|
|
}
|
|
return states
|
|
}
|
|
|
|
// tlsQualityRule: folds downstream TLS checker findings onto the LDAP
|
|
// service. Consumes a related observation (not LDAPData).
|
|
type tlsQualityRule struct{}
|
|
|
|
func (r *tlsQualityRule) Name() string { return "ldap.tls_quality" }
|
|
func (r *tlsQualityRule) Description() string {
|
|
return "Folds the downstream TLS checker findings (certificate chain, hostname match, expiry) onto the LDAP service."
|
|
}
|
|
|
|
func (r *tlsQualityRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
|
related, _ := obs.GetRelated(ctx, TLSRelatedKey)
|
|
if len(related) == 0 {
|
|
return []sdk.CheckState{notTestedState("ldap.tls_quality.skipped", "No related TLS observation available (no TLS checker downstream, or no probe yet).")}
|
|
}
|
|
states := tlsStatesFromRelated(related)
|
|
if len(states) == 0 {
|
|
return []sdk.CheckState{passState("ldap.tls_quality.ok", "Downstream TLS checker reports no issues on the LDAP endpoints.")}
|
|
}
|
|
return states
|
|
}
|