diff --git a/Dockerfile b/Dockerfile index b1b400b..9bbea0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,5 +12,7 @@ RUN set -ex \ EXPOSE 3000 +VOLUME ["/data/certs"] + ENTRYPOINT ["/go/bin/promcertcheck"] CMD ["--probe=https://www.google.com/", "--probe=https://www.facebook.com/"] diff --git a/cert.go b/cert.go index c969292..46b699a 100644 --- a/cert.go +++ b/cert.go @@ -55,8 +55,10 @@ func checkCertificate(probeURL *url.URL) (probeResult, *x509.Certificate) { } resp.Body.Close() - intermediatePool := x509.NewCertPool() - var verifyCert *x509.Certificate + var ( + intermediatePool = x509.NewCertPool() + verifyCert *x509.Certificate + ) hostPort := strings.Split(probeURL.Host, ":") host := hostPort[0] @@ -79,6 +81,7 @@ func checkCertificate(probeURL *url.URL) (probeResult, *x509.Certificate) { verificationResult := false if _, err := verifyCert.Verify(x509.VerifyOptions{ Intermediates: intermediatePool, + Roots: rootPool, }); err == nil { verificationResult = true } diff --git a/main.go b/main.go index 9977860..f6723ac 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,11 @@ import ( "crypto/x509" "errors" "fmt" + "io/ioutil" "net/http" "net/url" + "os" + "path/filepath" "strings" "time" @@ -21,11 +24,15 @@ var ( config = struct { Debug bool `flag:"debug" default:"false" description:"Output debugging data"` 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"` }{} - version = "dev" + + version = "dev" + probeMonitors = map[string]*probeMonitor{} + rootPool *x509.CertPool redirectFoundError = errors.New("Found a redirect") ) @@ -57,6 +64,15 @@ func main() { TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } + var err error + if rootPool, err = x509.SystemCertPool(); err != nil { + log.WithError(err).Fatal("Unable to load system RootCA pool") + } + + if err = loadAdditionalRootCAPool(); err != nil { + log.WithError(err).Fatal("Could not load intermediate certificates") + } + registerProbes() refreshCertificateStatus() @@ -74,6 +90,37 @@ func main() { http.ListenAndServe(":3000", r) } +func loadAdditionalRootCAPool() error { + if config.RootsDir == "" { + // Nothing specified, not loading anything but sys certs + return nil + } + + return filepath.Walk(config.RootsDir, func(path string, info os.FileInfo, err error) error { + 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 + } + + if ok := rootPool.AppendCertsFromPEM(pem); !ok { + return fmt.Errorf("Failed to load certificate %q", path) + } + + log.WithFields(log.Fields{"path": path}).Debug("Loaded RootCA certificate") + + return nil + }) +} + func registerProbes() { for _, probe := range config.Probes { probeURL, _ := url.Parse(probe)