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=\"\" by dn.exact=\"\" 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 }