2018-06-04 09:49:36 +00:00
|
|
|
package main
|
2015-09-04 13:36:49 +00:00
|
|
|
|
|
|
|
import (
|
2017-11-05 16:00:16 +00:00
|
|
|
"crypto/tls"
|
2015-09-04 13:36:49 +00:00
|
|
|
"crypto/x509"
|
2017-11-05 16:00:16 +00:00
|
|
|
"errors"
|
2015-09-04 13:36:49 +00:00
|
|
|
"fmt"
|
2017-11-05 16:31:06 +00:00
|
|
|
"io/ioutil"
|
2015-09-04 13:36:49 +00:00
|
|
|
"net/http"
|
2017-11-05 16:31:06 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2015-09-04 13:36:49 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2021-02-19 18:11:42 +00:00
|
|
|
"github.com/Luzifer/rconfig/v2"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2015-09-04 13:36:49 +00:00
|
|
|
"github.com/robfig/cron"
|
2017-11-05 15:03:26 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2015-09-04 13:36:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2017-11-05 16:38:33 +00:00
|
|
|
cfg struct {
|
2018-06-04 10:18:55 +00:00
|
|
|
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
|
2017-11-05 16:38:33 +00:00
|
|
|
ExpireWarning time.Duration `flag:"expire-warning" default:"744h" description:"When to warn about a soon expiring certificate"`
|
|
|
|
RootsDir string `flag:"roots-dir" default:"" description:"Directory to load custom RootCA certs from to be trusted (*.pem)"`
|
|
|
|
LogLevel string `flag:"log-level" default:"info" description:"Verbosity of logs to use (debug, info, warning, error, ...)"`
|
|
|
|
Probes []string `flag:"probe" default:"" description:"URLs to check for certificate issues"`
|
|
|
|
VersionAndExit bool `flag:"version" default:"false" description:"Print program version and exit"`
|
|
|
|
}
|
2017-11-05 16:31:06 +00:00
|
|
|
|
|
|
|
version = "dev"
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
probeMonitors = map[string]*probe{}
|
2017-11-05 16:31:06 +00:00
|
|
|
rootPool *x509.CertPool
|
2017-11-05 16:00:16 +00:00
|
|
|
|
|
|
|
redirectFoundError = errors.New("Found a redirect")
|
2015-09-04 13:36:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2018-09-28 21:49:40 +00:00
|
|
|
rconfig.AutoEnv(true)
|
|
|
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
2017-06-26 13:04:28 +00:00
|
|
|
log.Fatalf("Unable to parse CLI parameters: %s", err)
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
2017-11-05 15:03:26 +00:00
|
|
|
|
2017-11-05 16:38:33 +00:00
|
|
|
if cfg.VersionAndExit {
|
|
|
|
fmt.Printf("promcertcheck %s\n", version)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
if logLevel, err := log.ParseLevel(cfg.LogLevel); err == nil {
|
2017-11-05 15:03:26 +00:00
|
|
|
log.SetLevel(logLevel)
|
|
|
|
} else {
|
|
|
|
log.Fatalf("Unable to parse log level: %s", err)
|
|
|
|
}
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2018-06-04 10:18:55 +00:00
|
|
|
// Configuration to receive redirects and TLS errors
|
2015-09-04 13:36:49 +00:00
|
|
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
2017-11-05 16:00:16 +00:00
|
|
|
return redirectFoundError
|
|
|
|
}
|
|
|
|
http.DefaultClient.Transport = &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
// Load valid CAs from system and specified folder
|
2017-11-05 16:31:06 +00:00
|
|
|
var err error
|
|
|
|
if rootPool, err = x509.SystemCertPool(); err != nil {
|
|
|
|
log.WithError(err).Fatal("Unable to load system RootCA pool")
|
|
|
|
}
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
if err = loadAdditionalRootCAPool(rootPool); err != nil {
|
2017-11-05 16:31:06 +00:00
|
|
|
log.WithError(err).Fatal("Could not load intermediate certificates")
|
|
|
|
}
|
|
|
|
|
2015-09-04 13:36:49 +00:00
|
|
|
registerProbes()
|
|
|
|
refreshCertificateStatus()
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"version": version,
|
|
|
|
}).Info("PromCertcheck started to listen on 0.0.0.0:3000")
|
2015-09-04 13:46:43 +00:00
|
|
|
|
2015-09-04 13:36:49 +00:00
|
|
|
c := cron.New()
|
|
|
|
c.AddFunc("0 0 * * * *", refreshCertificateStatus)
|
|
|
|
c.Start()
|
|
|
|
|
2021-02-19 18:11:42 +00:00
|
|
|
http.Handle("/metrics", promhttp.Handler())
|
2018-06-04 10:18:55 +00:00
|
|
|
http.HandleFunc("/", htmlHandler)
|
|
|
|
http.HandleFunc("/httpStatus", httpStatusHandler)
|
|
|
|
http.HandleFunc("/results.json", jsonHandler)
|
|
|
|
http.ListenAndServe(cfg.Listen, nil)
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
func loadAdditionalRootCAPool(pool *x509.CertPool) error {
|
2017-11-05 16:38:33 +00:00
|
|
|
if cfg.RootsDir == "" {
|
2017-11-05 16:31:06 +00:00
|
|
|
// Nothing specified, not loading anything but sys certs
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-11-05 16:38:33 +00:00
|
|
|
return filepath.Walk(cfg.RootsDir, func(path string, info os.FileInfo, err error) error {
|
2017-11-05 16:31:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasSuffix(path, ".pem") || info.IsDir() {
|
|
|
|
// Likely not a certificate, ignore
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
pem, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
if ok := pool.AppendCertsFromPEM(pem); !ok {
|
2017-11-05 16:31:06 +00:00
|
|
|
return fmt.Errorf("Failed to load certificate %q", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.WithFields(log.Fields{"path": path}).Debug("Loaded RootCA certificate")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-09-04 13:36:49 +00:00
|
|
|
func registerProbes() {
|
2018-06-04 10:18:55 +00:00
|
|
|
for _, probeURL := range cfg.Probes {
|
|
|
|
p, err := probeFromURL(probeURL)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Unable to create probe")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
probeMonitors[p.url.Host] = p
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"host": p.url.Host,
|
|
|
|
}).Info("Probe registered")
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func refreshCertificateStatus() {
|
2018-06-04 10:18:55 +00:00
|
|
|
for _, p := range probeMonitors {
|
|
|
|
go func(p *probe) {
|
|
|
|
logger := log.WithFields(log.Fields{
|
|
|
|
"host": p.url.Host,
|
2017-11-05 16:38:33 +00:00
|
|
|
})
|
2015-09-04 13:36:49 +00:00
|
|
|
|
2018-06-04 10:18:55 +00:00
|
|
|
if err := p.refresh(); err != nil {
|
|
|
|
logger.WithError(err).Error("Unable to refresh probe status")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Debug("Probe refreshed")
|
|
|
|
}(p)
|
2015-09-04 13:36:49 +00:00
|
|
|
}
|
|
|
|
}
|