From 4852ae36c0491eccdadd857aab44934f1017c6f5 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 8 Oct 2019 16:49:27 +0300 Subject: Initial commit --- certwatch.go | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 certwatch.go diff --git a/certwatch.go b/certwatch.go new file mode 100644 index 0000000..e75d09e --- /dev/null +++ b/certwatch.go @@ -0,0 +1,221 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "os" + "strings" + "fmt" + "time" + "flag" +) + +// Nagios status constants +const ( + StatusOK = iota + StatusWarning + StatusCritical + StatusUnknown +) + +var statusString = []string{StatusOK: `OK`, + StatusWarning: `WARNING`, + StatusCritical: `CRITICAL`, + StatusUnknown: `UNKNOWN`,} + +type CertResult struct { + Address string + Subject string + Status int + Ttl time.Duration + Error string +} + +type CertResultList struct { + Status int + Result []CertResult +} + +// The cnmap interface +type cnmap map[string]bool + +func (mp *cnmap) Set(value string) error { + if *mp == nil { + *mp = make(map[string]bool) + } + for _, cn := range strings.Split(value, ",") { + (*mp)[cn] = true + } + return nil +} + +func (mp *cnmap) String() string { + var a []string + for k := range *mp { + a = append(a, k) + } + return strings.Join(a, `,`) +} + +func (mp cnmap) Selected(cert *x509.Certificate) bool { + if mp == nil { + return true + } + if v, p := mp[cert.Subject.CommonName]; p { + return v + } + for _, name := range cert.DNSNames { + if v, p := mp[name]; p { + return v + } + } + return false +} + +// Command line options +var warnLimit time.Duration +var critLimit time.Duration +var verboseOption bool +var helpOption bool +var selectCN cnmap +var host string + +// Intitialize command line parser +func init() { + flag.DurationVar(&warnLimit, `w`, 0, `warning threshold`) + flag.DurationVar(&critLimit, `c`, 0, `critical threshold`) + flag.BoolVar(&verboseOption, `v`, false, `verbose mode`) + flag.BoolVar(&helpOption, `h`, false, `show help summary`) + flag.Var(&selectCN, `s`, `comma-separated list of allowed CNs`) + flag.StringVar(&host, `H`, ``, `host name`) + flag.Usage = func() { + if helpOption { + flag.CommandLine.SetOutput(os.Stdout) + } + fmt.Fprintf(flag.CommandLine.Output(), + "Usage: %s [OPTIONS] [HOST...]\n", + os.Args[0]) + fmt.Fprintln(flag.CommandLine.Output(), `OPTIONS are:`) + flag.PrintDefaults() + } +} + +func main() { + flag.Parse() + + if helpOption { + flag.Usage() + os.Exit(0) + } + + res := CertResultList{Status: StatusOK} + + if host != `` { + res.Check(host) + } + + for _, arg := range flag.Args() { + res.Check(arg) + } + res.Format() + os.Exit(res.Status) +} + +var conf = &tls.Config { + InsecureSkipVerify: true, +} + +func (res CertResult) FormatHR() { + if res.Status == StatusUnknown { + fmt.Printf("%s - %s;", res.Address, res.Error) + } else { + fmt.Printf("%s[%s] TTL %s;", + res.Address, res.Subject, res.Ttl.String()) + } +} + +func (res CertResult) FormatPerfData() { + fmt.Printf("%s=%d;%d;%d;;", + res.Subject, + int(res.Ttl.Seconds()), + int(warnLimit.Seconds()), + int(critLimit.Seconds())) +} + +func (rl CertResultList) Format() { + //'label'=value[UOM];[warn];[crit];[min];[max] + fmt.Printf("%s - ", statusString[rl.Status]) + rl.Result[0].FormatHR() + fmt.Printf(" | ") + rl.Result[0].FormatPerfData() + if len(rl.Result) > 1 { + for _, res := range rl.Result[1:] { + fmt.Println() + res.FormatHR() + } + fmt.Printf(`|`) + for _, res := range rl.Result[1:] { + res.FormatPerfData() + fmt.Println() + } + } else { + fmt.Println() + } +} + +func (rl *CertResultList) Append(res CertResult) { + rl.Result = append(rl.Result, res) + if res.Status > rl.Status { + rl.Status = res.Status + } +} + +func (rl *CertResultList) Check(addr string) { + a := strings.Split(addr, `:`) + switch (len(a)) { + case 1: + addr += `:443` + case 2: + break + default: + rl.Append(CertResult{Address: addr, + Status: StatusUnknown, + Error: `bad address`}) + return + } + + conn, err := tls.Dial("tcp", addr, conf) + if err != nil { + rl.Append(CertResult{Address: addr, Status: StatusUnknown, Error: err.Error()}) + return + } + defer conn.Close() + + state := conn.ConnectionState() + + for _, cert := range state.PeerCertificates { + if cert.IsCA { + continue + } + if !selectCN.Selected(cert) { + continue + } + if (verboseOption) { + fmt.Printf("Host: %s\n", addr) + fmt.Printf("CN: %s\n", cert.Subject.CommonName) + fmt.Printf("DNS: %s\n", strings.Join(cert.DNSNames, `,`)) + fmt.Printf("Expires: %s\n", cert.NotAfter.String()) + fmt.Println() + } + res := CertResult{Address: addr, + Subject: cert.Subject.CommonName, + Status: StatusOK} + res.Ttl = time.Until(cert.NotAfter) + if res.Ttl < critLimit { + res.Status = StatusCritical + } else if res.Ttl < warnLimit { + res.Status = StatusWarning + } + rl.Append(res) + } +} -- cgit v1.2.1