1 package probers
2
3 import (
4 "fmt"
5 "net/url"
6 "strings"
7
8 "github.com/letsencrypt/boulder/observer/probers"
9 "github.com/letsencrypt/boulder/strictyaml"
10 "github.com/prometheus/client_golang/prometheus"
11 )
12
13 const (
14 notAfterName = "obs_tls_not_after"
15 notBeforeName = "obs_tls_not_before"
16 reasonName = "obs_tls_reason"
17 )
18
19
20 type TLSConf struct {
21 Hostname string `yaml:"hostname"`
22 RootOrg string `yaml:"rootOrg"`
23 RootCN string `yaml:"rootCN"`
24 Response string `yaml:"response"`
25 }
26
27
28 func (c TLSConf) Kind() string {
29 return "TLS"
30 }
31
32
33
34 func (c TLSConf) UnmarshalSettings(settings []byte) (probers.Configurer, error) {
35 var conf TLSConf
36 err := strictyaml.Unmarshal(settings, &conf)
37 if err != nil {
38 return nil, err
39 }
40
41 return conf, nil
42 }
43
44 func (c TLSConf) validateHostname() error {
45 url, err := url.Parse(c.Hostname)
46 if err != nil {
47 return fmt.Errorf(
48 "invalid 'hostname', got %q, expected a valid hostname: %s", c.Hostname, err)
49 }
50
51 if url.Scheme != "" {
52 return fmt.Errorf(
53 "invalid 'hostname', got: %q, should not include scheme", c.Hostname)
54 }
55
56 return nil
57 }
58
59 func (c TLSConf) validateResponse() error {
60 acceptable := []string{"valid", "expired", "revoked"}
61 for _, a := range acceptable {
62 if strings.ToLower(c.Response) == a {
63 return nil
64 }
65 }
66
67 return fmt.Errorf(
68 "invalid `response`, got %q. Must be one of %s", c.Response, acceptable)
69 }
70
71
72
73
74 func (c TLSConf) MakeProber(collectors map[string]prometheus.Collector) (probers.Prober, error) {
75
76 err := c.validateHostname()
77 if err != nil {
78 return nil, err
79 }
80
81
82 err = c.validateResponse()
83 if err != nil {
84 return nil, err
85 }
86
87
88 coll, ok := collectors[notAfterName]
89 if !ok {
90 return nil, fmt.Errorf("tls prober did not receive collector %q", notAfterName)
91 }
92
93 notAfterColl, ok := coll.(*prometheus.GaugeVec)
94 if !ok {
95 return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", notAfterName, coll)
96 }
97
98 coll, ok = collectors[notBeforeName]
99 if !ok {
100 return nil, fmt.Errorf("tls prober did not receive collector %q", notBeforeName)
101 }
102
103 notBeforeColl, ok := coll.(*prometheus.GaugeVec)
104 if !ok {
105 return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.GaugeVec", notBeforeName, coll)
106 }
107
108 coll, ok = collectors[reasonName]
109 if !ok {
110 return nil, fmt.Errorf("tls prober did not receive collector %q", reasonName)
111 }
112
113 reasonColl, ok := coll.(*prometheus.CounterVec)
114 if !ok {
115 return nil, fmt.Errorf("tls prober received collector %q of wrong type, got: %T, expected *prometheus.CounterVec", reasonName, coll)
116 }
117
118 return TLSProbe{c.Hostname, c.RootOrg, c.RootCN, strings.ToLower(c.Response), notAfterColl, notBeforeColl, reasonColl}, nil
119 }
120
121
122
123
124
125 func (c TLSConf) Instrument() map[string]prometheus.Collector {
126 notBefore := prometheus.Collector(prometheus.NewGaugeVec(
127 prometheus.GaugeOpts{
128 Name: notBeforeName,
129 Help: "Certificate notBefore value as a Unix timestamp in seconds",
130 }, []string{"hostname"},
131 ))
132 notAfter := prometheus.Collector(prometheus.NewGaugeVec(
133 prometheus.GaugeOpts{
134 Name: notAfterName,
135 Help: "Certificate notAfter value as a Unix timestamp in seconds",
136 }, []string{"hostname"},
137 ))
138 reason := prometheus.Collector(prometheus.NewCounterVec(
139 prometheus.CounterOpts{
140 Name: reasonName,
141 Help: fmt.Sprintf("Reason for TLS Prober check failure. Can be one of %s", getReasons()),
142 }, []string{"hostname", "reason"},
143 ))
144 return map[string]prometheus.Collector{
145 notAfterName: notAfter,
146 notBeforeName: notBefore,
147 reasonName: reason,
148 }
149 }
150
151
152
153 func init() {
154 probers.Register(TLSConf{})
155 }
156
View as plain text