/
main.go
155 lines (127 loc) · 3.89 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package main
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/Luzifer/rconfig/v2"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
)
var (
cfg struct {
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
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"`
}
version = "dev"
probeMonitors = map[string]*probe{}
rootPool *x509.CertPool
redirectFoundError = errors.New("Found a redirect")
)
func init() {
rconfig.AutoEnv(true)
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse CLI parameters: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("promcertcheck %s\n", version)
os.Exit(0)
}
if logLevel, err := log.ParseLevel(cfg.LogLevel); err == nil {
log.SetLevel(logLevel)
} else {
log.Fatalf("Unable to parse log level: %s", err)
}
}
func main() {
// Configuration to receive redirects and TLS errors
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return redirectFoundError
}
http.DefaultClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
// Load valid CAs from system and specified folder
var err error
if rootPool, err = x509.SystemCertPool(); err != nil {
log.WithError(err).Fatal("Unable to load system RootCA pool")
}
if err = loadAdditionalRootCAPool(rootPool); err != nil {
log.WithError(err).Fatal("Could not load intermediate certificates")
}
registerProbes()
refreshCertificateStatus()
log.WithFields(log.Fields{
"version": version,
}).Info("PromCertcheck started to listen on 0.0.0.0:3000")
c := cron.New()
c.AddFunc("0 0 * * * *", refreshCertificateStatus)
c.Start()
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", htmlHandler)
http.HandleFunc("/httpStatus", httpStatusHandler)
http.HandleFunc("/results.json", jsonHandler)
http.ListenAndServe(cfg.Listen, nil)
}
func loadAdditionalRootCAPool(pool *x509.CertPool) error {
if cfg.RootsDir == "" {
// Nothing specified, not loading anything but sys certs
return nil
}
return filepath.Walk(cfg.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 := pool.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 _, 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")
}
}
func refreshCertificateStatus() {
for _, p := range probeMonitors {
go func(p *probe) {
logger := log.WithFields(log.Fields{
"host": p.url.Host,
})
if err := p.refresh(); err != nil {
logger.WithError(err).Error("Unable to refresh probe status")
return
}
logger.Debug("Probe refreshed")
}(p)
}
}