Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
110 lines
3.1 KiB
Go
110 lines
3.1 KiB
Go
package checks
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/store"
|
|
)
|
|
|
|
// Params are the per-check settings kept as JSON in checks.params. Fields that
|
|
// do not apply to a kind are ignored.
|
|
type Params struct {
|
|
// TimeoutS is the deadline of one run in seconds (default: the runner's, never more than the interval).
|
|
TimeoutS int `json:"timeout_s,omitempty"`
|
|
// ExpectStatus is the HTTP status that counts as success (default: any 2xx).
|
|
ExpectStatus int `json:"expect_status,omitempty"`
|
|
// BodyContains must appear in the first 1 MiB of an HTTP response body.
|
|
BodyContains string `json:"body_contains,omitempty"`
|
|
// Server is the DNS server to ask, "ip" or "ip:port" ("[v6]:port").
|
|
Server string `json:"server,omitempty"`
|
|
// Expect is an address that must be among the DNS answers.
|
|
Expect string `json:"expect,omitempty"`
|
|
// RecordType is "A", "AAAA" or empty for both.
|
|
RecordType string `json:"record_type,omitempty"`
|
|
// WarnDays fails a TLS check when the certificate has fewer days left (default 14).
|
|
WarnDays int `json:"warn_days,omitempty"`
|
|
// Insecure skips chain and name verification in a TLS check (expiry is still measured).
|
|
Insecure bool `json:"insecure,omitempty"`
|
|
}
|
|
|
|
// Defaults.
|
|
const (
|
|
DefaultIntervalS = 60
|
|
DefaultWarnDays = 14
|
|
)
|
|
|
|
// ParseParams decodes checks.params; empty means all defaults. Unknown fields are an error.
|
|
func ParseParams(raw string) (Params, error) {
|
|
var p Params
|
|
if strings.TrimSpace(raw) == "" {
|
|
return p, nil
|
|
}
|
|
dec := json.NewDecoder(strings.NewReader(raw))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&p); err != nil {
|
|
return Params{}, fmt.Errorf("check params: %w", err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Validate reports why c cannot run, for the API to reject a bad definition.
|
|
func Validate(c store.Check) error {
|
|
p, err := ParseParams(c.Params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if p.TimeoutS < 0 || p.WarnDays < 0 || p.ExpectStatus < 0 || p.ExpectStatus > 599 {
|
|
return errors.New("check params: negative or out-of-range value")
|
|
}
|
|
switch c.Kind {
|
|
case store.CheckPing:
|
|
return nil
|
|
case store.CheckHTTP:
|
|
return validateHTTP(c.Target)
|
|
case store.CheckTLS:
|
|
_, _, err := tlsTarget(c.Target)
|
|
return err
|
|
case store.CheckDNS:
|
|
return validateDNS(p)
|
|
}
|
|
return fmt.Errorf("unknown check kind %q", c.Kind)
|
|
}
|
|
|
|
func validateHTTP(target string) error {
|
|
u, err := url.Parse(target)
|
|
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Hostname() == "" {
|
|
return errors.New("http check target must be an http(s) URL")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateDNS(p Params) error {
|
|
if _, err := dnsServer(p.Server); err != nil {
|
|
return err
|
|
}
|
|
if p.Expect != "" {
|
|
if _, err := netip.ParseAddr(p.Expect); err != nil {
|
|
return errors.New("dns check expect must be an IP address")
|
|
}
|
|
}
|
|
_, err := recordTypes(p.RecordType)
|
|
return err
|
|
}
|
|
|
|
func (p Params) timeout(def time.Duration, interval time.Duration) time.Duration {
|
|
d := def
|
|
if p.TimeoutS > 0 {
|
|
d = time.Duration(p.TimeoutS) * time.Second
|
|
}
|
|
if interval > 0 && d > interval {
|
|
d = interval
|
|
}
|
|
return d
|
|
}
|