github.com/letsencrypt/boulder@v0.20251208.0/observer/probers/crl/crl.go (about) 1 package probers 2 3 import ( 4 "crypto/x509" 5 "io" 6 "net/http" 7 "slices" 8 "time" 9 10 "github.com/prometheus/client_golang/prometheus" 11 12 "github.com/letsencrypt/boulder/crl/idp" 13 ) 14 15 // CRLProbe is the exported 'Prober' object for monitors configured to 16 // monitor CRL availability & characteristics. 17 type CRLProbe struct { 18 url string 19 partitioned bool 20 cNextUpdate *prometheus.GaugeVec 21 cThisUpdate *prometheus.GaugeVec 22 cCertCount *prometheus.GaugeVec 23 } 24 25 // Name returns a string that uniquely identifies the monitor. 26 func (p CRLProbe) Name() string { 27 return p.url 28 } 29 30 // Kind returns a name that uniquely identifies the `Kind` of `Prober`. 31 func (p CRLProbe) Kind() string { 32 return "CRL" 33 } 34 35 // Probe requests the configured CRL and publishes metrics about it if found. 36 func (p CRLProbe) Probe(timeout time.Duration) (bool, time.Duration) { 37 start := time.Now() 38 resp, err := http.Get(p.url) 39 if err != nil { 40 return false, time.Since(start) 41 } 42 43 body, err := io.ReadAll(resp.Body) 44 if err != nil { 45 return false, time.Since(start) 46 } 47 dur := time.Since(start) 48 49 crl, err := x509.ParseRevocationList(body) 50 if err != nil { 51 return false, dur 52 } 53 54 // Partitioned CRLs MUST contain an issuingDistributionPoint extension, which 55 // MUST contain the URL from which they were fetched, to prevent substitution 56 // attacks. 57 if p.partitioned { 58 idps, err := idp.GetIDPURIs(crl.Extensions) 59 if err != nil { 60 return false, dur 61 } 62 if !slices.Contains(idps, p.url) { 63 return false, dur 64 } 65 } 66 67 // Report metrics for this CRL 68 p.cThisUpdate.WithLabelValues(p.url).Set(float64(crl.ThisUpdate.Unix())) 69 p.cNextUpdate.WithLabelValues(p.url).Set(float64(crl.NextUpdate.Unix())) 70 p.cCertCount.WithLabelValues(p.url).Set(float64(len(crl.RevokedCertificateEntries))) 71 72 return true, dur 73 }