/
tls_conf.go
155 lines (132 loc) · 4.41 KB
/
tls_conf.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 probers
import (
"fmt"
"net/url"
"strings"
"github.com/letsencrypt/boulder/observer/probers"
"github.com/letsencrypt/boulder/strictyaml"
"github.com/prometheus/client_golang/prometheus"
)
const (
notAfterName = "obs_tls_not_after"
notBeforeName = "obs_tls_not_before"
reasonName = "obs_tls_reason"
)
// TLSConf is exported to receive YAML configuration.
type TLSConf struct {
Hostname string `yaml:"hostname"`
RootOrg string `yaml:"rootOrg"`
RootCN string `yaml:"rootCN"`
Response string `yaml:"response"`
}
// Kind returns a name that uniquely identifies the `Kind` of `Configurer`.
func (c TLSConf) Kind() string {
return "TLS"
}
// UnmarshalSettings takes YAML as bytes and unmarshals it to the to an TLSConf
// object.
func (c TLSConf) UnmarshalSettings(settings []byte) (probers.Configurer, error) {
var conf TLSConf
err := strictyaml.Unmarshal(settings, &conf)
if err != nil {
return nil, err
}
return conf, nil
}
func (c TLSConf) validateHostname() error {
url, err := url.Parse(c.Hostname)
if err != nil {
return fmt.Errorf(
"invalid 'hostname', got %q, expected a valid hostname: %s", c.Hostname, err)
}
if url.Scheme != "" {
return fmt.Errorf(
"invalid 'hostname', got: %q, should not include scheme", c.Hostname)
}
return nil
}
func (c TLSConf) validateResponse() error {
acceptable := []string{"valid", "expired", "revoked"}
for _, a := range acceptable {
if strings.ToLower(c.Response) == a {
return nil
}
}
return fmt.Errorf(
"invalid `response`, got %q. Must be one of %s", c.Response, acceptable)
}
// MakeProber constructs a `TLSProbe` object from the contents of the bound
// `TLSConf` object. If the `TLSConf` cannot be validated, an error appropriate
// for end-user consumption is returned instead.
func (c TLSConf) MakeProber(collectors map[string]prometheus.Collector) (probers.Prober, error) {
// Validate `hostname`
err := c.validateHostname()
if err != nil {
return nil, err
}
// Valid `response`
err = c.validateResponse()
if err != nil {
return nil, err
}
// Validate the Prometheus collectors that were passed in
coll, ok := collectors[notAfterName]
if !ok {
return nil, fmt.Errorf("tls prober did not receive collector %q", notAfterName)
}
notAfterColl, ok := coll.(*prometheus.GaugeVec)
if !ok {
return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", notAfterName, coll)
}
coll, ok = collectors[notBeforeName]
if !ok {
return nil, fmt.Errorf("tls prober did not receive collector %q", notBeforeName)
}
notBeforeColl, ok := coll.(*prometheus.GaugeVec)
if !ok {
return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", notBeforeName, coll)
}
coll, ok = collectors[reasonName]
if !ok {
return nil, fmt.Errorf("tls prober did not receive collector %q", reasonName)
}
reasonColl, ok := coll.(*prometheus.CounterVec)
if !ok {
return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.CounterVec", reasonName, coll)
}
return TLSProbe{c.Hostname, c.RootOrg, c.RootCN, strings.ToLower(c.Response), notAfterColl, notBeforeColl, reasonColl}, nil
}
// Instrument constructs any `prometheus.Collector` objects the `TLSProbe` will
// need to report its own metrics. A map is returned containing the constructed
// objects, indexed by the name of the Promtheus metric. If no objects were
// constructed, nil is returned.
func (c TLSConf) Instrument() map[string]prometheus.Collector {
notBefore := prometheus.Collector(prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: notBeforeName,
Help: "Certificate notBefore value as a Unix timestamp in seconds",
}, []string{"hostname"},
))
notAfter := prometheus.Collector(prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: notAfterName,
Help: "Certificate notAfter value as a Unix timestamp in seconds",
}, []string{"hostname"},
))
reason := prometheus.Collector(prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: reasonName,
Help: fmt.Sprintf("Reason for TLS Prober check failure. Can be one of %s", getReasons()),
}, []string{"hostname", "reason"},
))
return map[string]prometheus.Collector{
notAfterName: notAfter,
notBeforeName: notBefore,
reasonName: reason,
}
}
// init is called at runtime and registers `TLSConf`, a `Prober` `Configurer`
// type, as "TLS".
func init() {
probers.Register(TLSConf{})
}