summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2019-10-08 13:49:27 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2019-10-08 13:49:27 (GMT)
commit4852ae36c0491eccdadd857aab44934f1017c6f5 (patch) (side-by-side diff)
tree5b14ebf3a7c3f31e4f734d6664bfd19211fdc4d4
downloadcertmon-4852ae36c0491eccdadd857aab44934f1017c6f5.tar.gz
certmon-4852ae36c0491eccdadd857aab44934f1017c6f5.tar.bz2
Initial commit
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--certwatch.go221
1 files changed, 221 insertions, 0 deletions
diff --git a/certwatch.go b/certwatch.go
new file mode 100644
index 0000000..e75d09e
--- a/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)
+ }
+}

Return to:

Send suggestions and report system problems to the System administrator.