mirror of
https://github.com/Luzifer/promcertcheck.git
synced 2024-11-08 16:00:08 +00:00
Knut Ahlers
357693db38
which will respond with HTTP200 if everything is fine or HTTP500 if one or more certificates are broken Signed-off-by: Knut Ahlers <knut@ahlers.me>
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
package main // import "github.com/Luzifer/promcertcheck"
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Luzifer/rconfig"
|
|
"github.com/gorilla/mux"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/robfig/cron"
|
|
)
|
|
|
|
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"`
|
|
Probes []string `flag:"probe" default:"" description:"URLs to check for certificate issues"`
|
|
}{}
|
|
version = "dev"
|
|
probeMonitors = map[string]*probeMonitor{}
|
|
)
|
|
|
|
type probeMonitor struct {
|
|
IsValid prometheus.Gauge `json:"-"`
|
|
Expires prometheus.Gauge `json:"-"`
|
|
Status probeResult
|
|
Certificate *x509.Certificate
|
|
}
|
|
|
|
type redirectFoundError struct{}
|
|
|
|
func (r redirectFoundError) Error() string {
|
|
return "Found a redirect."
|
|
}
|
|
|
|
func init() {
|
|
if err := rconfig.Parse(&config); err != nil {
|
|
log.Fatalf("Unable to parse CLI parameters: %s", err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
return redirectFoundError{}
|
|
}
|
|
|
|
registerProbes()
|
|
refreshCertificateStatus()
|
|
|
|
fmt.Printf("PromCertcheck %s...\nStarting to listen on 0.0.0.0:3000\n", version)
|
|
|
|
c := cron.New()
|
|
c.AddFunc("0 0 * * * *", refreshCertificateStatus)
|
|
c.Start()
|
|
|
|
r := mux.NewRouter()
|
|
r.Handle("/metrics", prometheus.Handler())
|
|
r.HandleFunc("/", htmlHandler)
|
|
r.HandleFunc("/httpStatus", httpStatusHandler)
|
|
r.HandleFunc("/results.json", jsonHandler)
|
|
http.ListenAndServe(":3000", r)
|
|
}
|
|
|
|
func registerProbes() {
|
|
for _, probe := range config.Probes {
|
|
probeURL, _ := url.Parse(probe)
|
|
|
|
monitors := &probeMonitor{}
|
|
monitors.Expires = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "certcheck_expires",
|
|
Help: "Expiration date in unix timestamp (UTC)",
|
|
ConstLabels: prometheus.Labels{
|
|
"host": probeURL.Host,
|
|
},
|
|
})
|
|
monitors.IsValid = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "certcheck_valid",
|
|
Help: "Validity of the certificate (0/1)",
|
|
ConstLabels: prometheus.Labels{
|
|
"host": probeURL.Host,
|
|
},
|
|
})
|
|
|
|
prometheus.MustRegister(monitors.Expires)
|
|
prometheus.MustRegister(monitors.IsValid)
|
|
|
|
probeMonitors[probeURL.Host] = monitors
|
|
}
|
|
}
|
|
|
|
func refreshCertificateStatus() {
|
|
for _, probe := range config.Probes {
|
|
probeURL, _ := url.Parse(probe)
|
|
verificationResult, verifyCert := checkCertificate(probeURL)
|
|
|
|
if config.Debug {
|
|
fmt.Printf("---\nProbe: %s\nResult: %s\n",
|
|
probeURL.Host,
|
|
verificationResult,
|
|
)
|
|
if verifyCert != nil {
|
|
fmt.Printf("Version: %d\nSerial: %d\nSubject: %s\nExpires: %s\nIssuer: %s\nAlt Names: %s\n",
|
|
verifyCert.Version,
|
|
verifyCert.SerialNumber,
|
|
verifyCert.Subject.CommonName,
|
|
verifyCert.NotAfter,
|
|
verifyCert.Issuer.CommonName,
|
|
strings.Join(verifyCert.DNSNames, ", "),
|
|
)
|
|
}
|
|
}
|
|
|
|
if verifyCert != nil {
|
|
probeMonitors[probeURL.Host].Expires.Set(float64(verifyCert.NotAfter.UTC().Unix()))
|
|
}
|
|
|
|
switch verificationResult {
|
|
case certificateExpiresSoon, certificateOK:
|
|
probeMonitors[probeURL.Host].IsValid.Set(1)
|
|
case certificateInvalid, certificateNotFound:
|
|
probeMonitors[probeURL.Host].IsValid.Set(0)
|
|
default:
|
|
probeMonitors[probeURL.Host].IsValid.Set(0)
|
|
}
|
|
probeMonitors[probeURL.Host].Status = verificationResult
|
|
probeMonitors[probeURL.Host].Certificate = verifyCert
|
|
}
|
|
}
|
|
|
|
func inSlice(slice []string, needle string) bool {
|
|
for _, i := range slice {
|
|
if i == needle {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|