1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package redis
16
17 import (
18 "context"
19 "errors"
20 "time"
21
22 "github.com/jmhodges/clock"
23 "github.com/letsencrypt/boulder/core"
24 blog "github.com/letsencrypt/boulder/log"
25 "github.com/letsencrypt/boulder/ocsp/responder"
26 "github.com/letsencrypt/boulder/rocsp"
27 "github.com/prometheus/client_golang/prometheus"
28 "golang.org/x/crypto/ocsp"
29
30 berrors "github.com/letsencrypt/boulder/errors"
31 )
32
33 type rocspClient interface {
34 GetResponse(ctx context.Context, serial string) ([]byte, error)
35 StoreResponse(ctx context.Context, resp *ocsp.Response) error
36 }
37
38 type redisSource struct {
39 client rocspClient
40 signer responder.Source
41 counter *prometheus.CounterVec
42 signAndSaveCounter *prometheus.CounterVec
43 cachedResponseAges prometheus.Histogram
44 clk clock.Clock
45 liveSigningPeriod time.Duration
46
47
48 logSampleRate int
49
50
51 log blog.Logger
52 }
53
54
55
56 func NewRedisSource(
57 client *rocsp.RWClient,
58 signer responder.Source,
59 liveSigningPeriod time.Duration,
60 clk clock.Clock,
61 stats prometheus.Registerer,
62 log blog.Logger,
63 logSampleRate int,
64 ) (*redisSource, error) {
65 counter := prometheus.NewCounterVec(prometheus.CounterOpts{
66 Name: "ocsp_redis_responses",
67 Help: "Count of OCSP requests/responses by action taken by the redisSource",
68 }, []string{"result"})
69 stats.MustRegister(counter)
70
71 signAndSaveCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
72 Name: "ocsp_redis_sign_and_save",
73 Help: "Count of OCSP sign and save requests",
74 }, []string{"cause", "result"})
75 stats.MustRegister(signAndSaveCounter)
76
77
78 buckets := make([]float64, 14)
79 for i := range buckets {
80 buckets[i] = 43200 * float64(i)
81 }
82
83 cachedResponseAges := prometheus.NewHistogram(prometheus.HistogramOpts{
84 Name: "ocsp_redis_cached_response_ages",
85 Help: "How old are the cached OCSP responses when we successfully retrieve them.",
86 Buckets: buckets,
87 })
88 stats.MustRegister(cachedResponseAges)
89
90 var rocspReader rocspClient
91 if client != nil {
92 rocspReader = client
93 }
94 return &redisSource{
95 client: rocspReader,
96 signer: signer,
97 counter: counter,
98 signAndSaveCounter: signAndSaveCounter,
99 cachedResponseAges: cachedResponseAges,
100 liveSigningPeriod: liveSigningPeriod,
101 clk: clk,
102 log: log,
103 }, nil
104 }
105
106
107
108 func (src *redisSource) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
109 serialString := core.SerialToString(req.SerialNumber)
110
111 respBytes, err := src.client.GetResponse(ctx, serialString)
112 if err != nil {
113 if errors.Is(err, rocsp.ErrRedisNotFound) {
114 src.counter.WithLabelValues("not_found").Inc()
115 } else {
116 src.counter.WithLabelValues("lookup_error").Inc()
117 responder.SampledError(src.log, src.logSampleRate, "looking for cached response: %s", err)
118
119
120 }
121 return src.signAndSave(ctx, req, causeNotFound)
122 }
123
124 resp, err := ocsp.ParseResponse(respBytes, nil)
125 if err != nil {
126 src.counter.WithLabelValues("parse_error").Inc()
127 return nil, err
128 }
129
130 if src.isStale(resp) {
131 src.counter.WithLabelValues("stale").Inc()
132 freshResp, err := src.signAndSave(ctx, req, causeStale)
133
134
135
136
137
138
139 if err != nil {
140 return nil, err
141 }
142 return freshResp, nil
143 }
144
145 src.counter.WithLabelValues("success").Inc()
146 return &responder.Response{Response: resp, Raw: respBytes}, nil
147 }
148
149 func (src *redisSource) isStale(resp *ocsp.Response) bool {
150 age := src.clk.Since(resp.ThisUpdate)
151 src.cachedResponseAges.Observe(age.Seconds())
152 return age > src.liveSigningPeriod
153 }
154
155 type signAndSaveCause string
156
157 const (
158 causeStale signAndSaveCause = "stale"
159 causeNotFound signAndSaveCause = "not_found"
160 causeMismatch signAndSaveCause = "mismatch"
161 )
162
163 func (src *redisSource) signAndSave(ctx context.Context, req *ocsp.Request, cause signAndSaveCause) (*responder.Response, error) {
164 resp, err := src.signer.Response(ctx, req)
165 if errors.Is(err, responder.ErrNotFound) {
166 src.signAndSaveCounter.WithLabelValues(string(cause), "certificate_not_found").Inc()
167 return nil, responder.ErrNotFound
168 } else if errors.Is(err, berrors.UnknownSerial) {
169
170
171
172
173
174 src.signAndSaveCounter.WithLabelValues(string(cause), "unknown_serial").Inc()
175 responder.SampledError(src.log, src.logSampleRate, "unknown serial: %s", core.SerialToString(req.SerialNumber))
176 return nil, responder.ErrNotFound
177 } else if err != nil {
178 src.signAndSaveCounter.WithLabelValues(string(cause), "signing_error").Inc()
179 return nil, err
180 }
181 src.signAndSaveCounter.WithLabelValues(string(cause), "signing_success").Inc()
182 go func() {
183
184
185 _ = src.client.StoreResponse(context.Background(), resp.Response)
186 }()
187 return resp, nil
188 }
189
View as plain text