diff options
| author | Mitch Riedstra <mitch@riedstra.us> | 2021-01-24 10:31:53 -0500 |
|---|---|---|
| committer | Mitch Riedstra <mitch@riedstra.us> | 2021-01-24 10:31:53 -0500 |
| commit | a4554be33914fd7cd77eea3326a747078bbe4c50 (patch) | |
| tree | 5e97506f384344662f9d3e55a6f83f88c76d6217 | |
| download | checkup-a4554be33914fd7cd77eea3326a747078bbe4c50.tar.gz checkup-a4554be33914fd7cd77eea3326a747078bbe4c50.tar.xz | |
initial
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | cert.go | 41 | ||||
| -rw-r--r-- | cmd/main/main.go | 113 | ||||
| -rw-r--r-- | go.mod | 8 | ||||
| -rw-r--r-- | go.sum | 6 | ||||
| -rw-r--r-- | http.go | 23 | ||||
| -rw-r--r-- | load.go | 1 | ||||
| -rw-r--r-- | sample-config.yml | 17 | ||||
| -rw-r--r-- | webhook.go | 52 |
9 files changed, 263 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db9a660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cmd/main/main +config.yml @@ -0,0 +1,41 @@ +package checkup + +import ( + "bytes" + "crypto/tls" + "fmt" + "time" +) + +func CertExpiresSoon(hostname, port string, window time.Duration) (*tls.Conn, error) { + conn, err := tls.Dial("tcp", hostname+":"+port, + &tls.Config{ServerName: hostname}) + + if err != nil { + return conn, err + } + + t := time.Now().Add(window) + + cert := conn.ConnectionState().PeerCertificates[0] + + if t.After(cert.NotAfter) { + return conn, fmt.Errorf("Cert expires soon: %s ( %d days )", + cert.NotAfter, (cert.NotAfter.Unix()-time.Now().Unix())/(60*60*24)) + } + + return conn, err +} + +func CertInfo(conn *tls.Conn) string { + buf := &bytes.Buffer{} + cs := conn.ConnectionState() + certs := cs.PeerCertificates + for _, cert := range certs { + fmt.Fprintf(buf, "Permitted: %v\n", cert.DNSNames) + fmt.Fprintf(buf, "Permitted: %v\n", cert.PermittedDNSDomains) + fmt.Fprintf(buf, "Not before: %v\n", cert.NotBefore) + fmt.Fprintf(buf, "Not after: %v\n", cert.NotAfter) + } + return string(buf.Bytes()) +} diff --git a/cmd/main/main.go b/cmd/main/main.go new file mode 100644 index 0000000..aa37a7f --- /dev/null +++ b/cmd/main/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "gopkg.in/yaml.v3" + "os" + "time" + + "riedstra.dev/go/checkup" +) + +type Config struct { + RocketChatURL string `yaml:"RocketChatURL"` + DiscordURL string `yaml:"DiscordURL"` + DefaultCertPort string `yaml:"DefaultCertPort"` + CertWindow int `yaml:"CertWindow"` + CheckCerts map[string]*string `yaml:"CheckCerts"` + ExpectedStatusCode int `yaml:"ExpectedStatusCode"` + StatusChecks map[string]*int `yaml:"StatusChecks"` +} + +func ReadConfig(fn string) (*Config, error) { + fh, err := os.Open(fn) + if err != nil { + return nil, err + } + + dec := yaml.NewDecoder(fh) + conf := &Config{} + err = dec.Decode(conf) + return conf, err +} + +func notify(conf *Config, b []byte) { + if len(b) >= 1 { + + if conf.RocketChatURL != "" { + err := checkup.SendRocketChatAlert(conf.RocketChatURL, string(b)) + if err != nil { + fmt.Fprintln(os.Stderr, string(b)) + fmt.Fprintf(os.Stderr, + "When sending webhook for rocketchat: %s\n", err) + } + + } + if conf.DiscordURL != "" { + err := checkup.SendDiscordAlert(conf.DiscordURL, string(b)) + if err != nil { + fmt.Fprintln(os.Stderr, string(b)) + fmt.Fprintf(os.Stderr, + "When sending webhook for discord: %s\n", err) + } + } + } +} + +func checkCerts(conf *Config) { + msg := &bytes.Buffer{} + for host, port := range conf.CheckCerts { + if port == nil { + port = &conf.DefaultCertPort + } + + conn, err := checkup.CertExpiresSoon( + host, + *port, + time.Duration(conf.CertWindow)*time.Hour*24) + if err != nil { + fmt.Fprintf(msg, "%s:%s --> %s\n", host, *port, err) + continue + } + conn.Close() + } + + notify(conf, msg.Bytes()) +} + +func checkStatus(conf *Config) { + msg := &bytes.Buffer{} + + for url, code := range conf.StatusChecks { + if code == nil { + code = &conf.ExpectedStatusCode + } + err := checkup.HttpStatusOK(url, *code) + if err != nil { + fmt.Fprintf(msg, "Checking: %s, %v\n", + url, err) + } + } + + notify(conf, msg.Bytes()) +} + +func main() { + fl := flag.NewFlagSet("checkup", flag.ExitOnError) + + confFn := fl.String("c", "config.yml", "Configuration file path") + + _ = fl.Parse(os.Args[1:]) + + conf, err := ReadConfig(*confFn) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + checkCerts(conf) + checkStatus(conf) + +} @@ -0,0 +1,8 @@ +module riedstra.dev/go/checkup + +go 1.15 + +require ( + golang.org/x/sys v0.0.0-20210123231150-1d476976d117 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) @@ -0,0 +1,6 @@ +golang.org/x/sys v0.0.0-20210123231150-1d476976d117 h1:M1sK0uTIn2x3HD5sySUPBg7ml5hmlQ/t7n7cIM6My9w= +golang.org/x/sys v0.0.0-20210123231150-1d476976d117/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -0,0 +1,23 @@ +package checkup + +import ( + "fmt" + "net/http" +) + +func HttpStatusOK(url string, status int) error { + resp, err := http.Get(url) + + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != status { + return fmt.Errorf("Bad status code, expected %d got: %d", + status, resp.StatusCode) + } + + return nil +} @@ -0,0 +1 @@ +package checkup diff --git a/sample-config.yml b/sample-config.yml new file mode 100644 index 0000000..dadbc0a --- /dev/null +++ b/sample-config.yml @@ -0,0 +1,17 @@ +--- +RocketChatURL: changeme +DiscordURL: changeme + +DefaultCertPort: '443' +# In Days +CertWindow: 15 +CheckCerts: + 'riedstra.dev': + 'git.riedstra.dev': + 'dispatch.riedstra.dev': + +ExpectedStatusCode: 200 +StatusChecks: + 'https://riedstra.dev': + 'https://git.riedstra.dev': + 'https://dispatch.riedstra.dev': diff --git a/webhook.go b/webhook.go new file mode 100644 index 0000000..897a249 --- /dev/null +++ b/webhook.go @@ -0,0 +1,52 @@ +package checkup + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +func SendDiscordAlert(hookURL string, message string) error { + return SendWebhook(hookURL, + struct { + Content string `json:"content"` + }{ + Content: message, + }, http.StatusNoContent) + +} + +func SendRocketChatAlert(hookURL string, message string) error { + return SendWebhook(hookURL, + struct { + Text string `json:"text"` + }{ + Text: message, + }, http.StatusOK) +} + +func SendWebhook(hookURL string, msg interface{}, code int) error { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + + err := enc.Encode(msg) + if err != nil { + return err + } + + resp, err := http.Post(hookURL, "application/json", buf) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != code { + bod, err := ioutil.ReadAll(resp.Body) + return fmt.Errorf("Bad status code: %d, expected %d : %s (Read errs: %s)", + resp.StatusCode, http.StatusOK, bod, err) + } + + return nil +} |
