1 package ctpolicy
2
3 import (
4 "context"
5 "errors"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/jmhodges/clock"
11 "github.com/letsencrypt/boulder/core"
12 "github.com/letsencrypt/boulder/ctpolicy/loglist"
13 berrors "github.com/letsencrypt/boulder/errors"
14 blog "github.com/letsencrypt/boulder/log"
15 "github.com/letsencrypt/boulder/metrics"
16 pubpb "github.com/letsencrypt/boulder/publisher/proto"
17 "github.com/letsencrypt/boulder/test"
18 "github.com/prometheus/client_golang/prometheus"
19 "google.golang.org/grpc"
20 )
21
22 type mockPub struct{}
23
24 func (mp *mockPub) SubmitToSingleCTWithResult(_ context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
25 return &pubpb.Result{Sct: []byte{0}}, nil
26 }
27
28 type mockFailPub struct{}
29
30 func (mp *mockFailPub) SubmitToSingleCTWithResult(_ context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
31 return nil, errors.New("BAD")
32 }
33
34 type mockSlowPub struct{}
35
36 func (mp *mockSlowPub) SubmitToSingleCTWithResult(ctx context.Context, _ *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
37 <-ctx.Done()
38 return nil, errors.New("timed out")
39 }
40
41 func TestGetSCTs(t *testing.T) {
42 expired, cancel := context.WithDeadline(context.Background(), time.Now())
43 defer cancel()
44 missingSCTErr := berrors.MissingSCTs
45 testCases := []struct {
46 name string
47 mock pubpb.PublisherClient
48 groups loglist.List
49 ctx context.Context
50 result core.SCTDERs
51 expectErr string
52 berrorType *berrors.ErrorType
53 }{
54 {
55 name: "basic success case",
56 mock: &mockPub{},
57 groups: loglist.List{
58 "OperA": {
59 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
60 "LogA2": {Url: "UrlA2", Key: "KeyA2"},
61 },
62 "OperB": {
63 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
64 },
65 "OperC": {
66 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
67 },
68 },
69 ctx: context.Background(),
70 result: core.SCTDERs{[]byte{0}, []byte{0}},
71 },
72 {
73 name: "basic failure case",
74 mock: &mockFailPub{},
75 groups: loglist.List{
76 "OperA": {
77 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
78 "LogA2": {Url: "UrlA2", Key: "KeyA2"},
79 },
80 "OperB": {
81 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
82 },
83 "OperC": {
84 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
85 },
86 },
87 ctx: context.Background(),
88 expectErr: "failed to get 2 SCTs, got 3 error(s)",
89 berrorType: &missingSCTErr,
90 },
91 {
92 name: "parent context timeout failure case",
93 mock: &mockSlowPub{},
94 groups: loglist.List{
95 "OperA": {
96 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
97 "LogA2": {Url: "UrlA2", Key: "KeyA2"},
98 },
99 "OperB": {
100 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
101 },
102 "OperC": {
103 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
104 },
105 },
106 ctx: expired,
107 expectErr: "failed to get 2 SCTs before ctx finished",
108 berrorType: &missingSCTErr,
109 },
110 }
111
112 for _, tc := range testCases {
113 t.Run(tc.name, func(t *testing.T) {
114 ctp := New(tc.mock, tc.groups, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
115 ret, err := ctp.GetSCTs(tc.ctx, []byte{0}, time.Time{})
116 if tc.result != nil {
117 test.AssertDeepEquals(t, ret, tc.result)
118 } else if tc.expectErr != "" {
119 if !strings.Contains(err.Error(), tc.expectErr) {
120 t.Errorf("Error %q did not match expected %q", err, tc.expectErr)
121 }
122 if tc.berrorType != nil {
123 test.AssertErrorIs(t, err, *tc.berrorType)
124 }
125 }
126 })
127 }
128 }
129
130 type mockFailOnePub struct {
131 badURL string
132 }
133
134 func (mp *mockFailOnePub) SubmitToSingleCTWithResult(_ context.Context, req *pubpb.Request, _ ...grpc.CallOption) (*pubpb.Result, error) {
135 if req.LogURL == mp.badURL {
136 return nil, errors.New("BAD")
137 }
138 return &pubpb.Result{Sct: []byte{0}}, nil
139 }
140
141 func TestGetSCTsMetrics(t *testing.T) {
142 ctp := New(&mockFailOnePub{badURL: "UrlA1"}, loglist.List{
143 "OperA": {
144 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
145 },
146 "OperB": {
147 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
148 },
149 "OperC": {
150 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
151 },
152 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
153 _, err := ctp.GetSCTs(context.Background(), []byte{0}, time.Time{})
154 test.AssertNotError(t, err, "GetSCTs failed")
155 test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlB1", "result": succeeded}, 1)
156 test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlC1", "result": succeeded}, 1)
157 }
158
159 func TestGetSCTsFailMetrics(t *testing.T) {
160
161 ctp := New(&mockFailOnePub{badURL: "UrlA1"}, loglist.List{
162 "OperA": {
163 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
164 },
165 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
166 _, err := ctp.GetSCTs(context.Background(), []byte{0}, time.Time{})
167 test.AssertError(t, err, "GetSCTs should have failed")
168 test.AssertErrorIs(t, err, berrors.MissingSCTs)
169 test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlA1", "result": failed}, 1)
170
171
172 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
173 defer cancel()
174
175 ctp = New(&mockSlowPub{}, loglist.List{
176 "OperA": {
177 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
178 },
179 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
180 _, err = ctp.GetSCTs(ctx, []byte{0}, time.Time{})
181 test.AssertError(t, err, "GetSCTs should have timed out")
182 test.AssertErrorIs(t, err, berrors.MissingSCTs)
183 test.AssertContains(t, err.Error(), context.DeadlineExceeded.Error())
184 test.AssertMetricWithLabelsEquals(t, ctp.winnerCounter, prometheus.Labels{"url": "UrlA1", "result": failed}, 1)
185 }
186
187 func TestLogListMetrics(t *testing.T) {
188
189 ctp := New(&mockPub{}, loglist.List{
190 "OperA": {
191 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
192 "LogA2": {Url: "UrlA2", Key: "KeyA2"},
193 },
194 "OperB": {
195 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
196 },
197 "OperC": {
198 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
199 },
200 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
201 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 2)
202 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 1)
203 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "sctLogs"}, 1)
204
205
206 ctp = New(&mockPub{}, loglist.List{
207 "OperA": {
208 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
209 "LogA2": {Url: "UrlA2", Key: "KeyA2"},
210 },
211 "OperB": {
212 "LogB1": {Url: "UrlB1", Key: "KeyB1"},
213 },
214 "OperC": {},
215 }, nil, loglist.List{
216 "OperA": {
217 "LogA1": {Url: "UrlA1", Key: "KeyA1"},
218 },
219 "OperB": {},
220 "OperC": {
221 "LogC1": {Url: "UrlC1", Key: "KeyC1"},
222 },
223 }, 0, blog.NewMock(), metrics.NoopRegisterer)
224 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 2)
225 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 1)
226 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "sctLogs"}, 0)
227 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "finalLogs"}, 1)
228 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "finalLogs"}, 0)
229 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperC", "source": "finalLogs"}, 1)
230
231
232 ctp = New(&mockPub{}, loglist.List{
233 "OperA": {},
234 "OperB": {},
235 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
236 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "sctLogs"}, 0)
237 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperB", "source": "sctLogs"}, 0)
238
239
240 ctp = New(&mockPub{}, loglist.List{
241 "OperA": {},
242 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
243 test.AssertMetricWithLabelsEquals(t, ctp.operatorGroupsGauge, prometheus.Labels{"operator": "OperA", "source": "allLogs"}, 0)
244
245 fc := clock.NewFake()
246 Tomorrow := fc.Now().Add(24 * time.Hour)
247 NextWeek := fc.Now().Add(7 * 24 * time.Hour)
248
249
250 ctp = New(&mockPub{}, loglist.List{
251 "OperA": {
252 "LogA1": {Url: "UrlA1", Key: "KeyA1", Name: "LogA1", EndExclusive: Tomorrow},
253 "LogA2": {Url: "UrlA2", Key: "KeyA2", Name: "LogA2", EndExclusive: NextWeek},
254 },
255 "OperB": {
256 "LogB1": {Url: "UrlB1", Key: "KeyB1", Name: "LogB1", EndExclusive: Tomorrow},
257 },
258 }, nil, nil, 0, blog.NewMock(), metrics.NoopRegisterer)
259 test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperA", "logID": "LogA1"}, 86400)
260 test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperA", "logID": "LogA2"}, 604800)
261 test.AssertMetricWithLabelsEquals(t, ctp.shardExpiryGauge, prometheus.Labels{"operator": "OperB", "logID": "LogB1"}, 86400)
262 }
263
View as plain text