@@ -3,21 +3,20 @@ package va
33import (
44 "context"
55 "fmt"
6- "math/rand/v2"
76 "net/url"
87 "regexp"
98 "strings"
109 "sync"
1110 "time"
1211
1312 "github.com/miekg/dns"
13+ "google.golang.org/protobuf/proto"
1414
1515 "github.com/letsencrypt/boulder/bdns"
1616 "github.com/letsencrypt/boulder/core"
1717 corepb "github.com/letsencrypt/boulder/core/proto"
1818 berrors "github.com/letsencrypt/boulder/errors"
1919 "github.com/letsencrypt/boulder/features"
20- bgrpc "github.com/letsencrypt/boulder/grpc"
2120 "github.com/letsencrypt/boulder/identifier"
2221 "github.com/letsencrypt/boulder/probs"
2322 vapb "github.com/letsencrypt/boulder/va/proto"
@@ -80,14 +79,6 @@ func (va *ValidationAuthorityImpl) IsCAAValid(ctx context.Context, req *vapb.IsC
8079 va .log .AuditObject ("CAA check result" , logEvent )
8180 }()
8281
83- var remoteCAAResults chan * remoteVAResult
84- if features .Get ().EnforceMultiCAA {
85- if remoteVACount := len (va .remoteVAs ); remoteVACount > 0 {
86- remoteCAAResults = make (chan * remoteVAResult , remoteVACount )
87- go va .performRemoteCAACheck (ctx , req , remoteCAAResults )
88- }
89- }
90-
9182 internalErr = va .checkCAA (ctx , acmeID , params )
9283
9384 // Stop the clock for local check latency.
@@ -97,31 +88,22 @@ func (va *ValidationAuthorityImpl) IsCAAValid(ctx context.Context, req *vapb.IsC
9788 logEvent .InternalError = internalErr .Error ()
9889 prob = detailedError (internalErr )
9990 prob .Detail = fmt .Sprintf ("While processing CAA for %s: %s" , req .Domain , prob .Detail )
100- } else if remoteCAAResults != nil {
101- if ! features .Get ().EnforceMultiCAA && features .Get ().MultiCAAFullResults {
102- // If we're not going to enforce multi CAA but we are logging the
103- // differentials then collect and log the remote results in a separate go
104- // routine to avoid blocking the primary VA.
105- go func () {
106- _ = va .processRemoteCAAResults (
107- req .Domain ,
108- req .AccountURIID ,
109- string (challType ),
110- remoteCAAResults )
111- }()
112- } else if features .Get ().EnforceMultiCAA {
113- remoteProb := va .processRemoteCAAResults (
114- req .Domain ,
115- req .AccountURIID ,
116- string (challType ),
117- remoteCAAResults )
118-
119- // If the remote result was a non-nil problem then fail the CAA check
120- if remoteProb != nil {
121- prob = remoteProb
122- va .log .Infof ("CAA check failed due to remote failures: identifier=%v err=%s" ,
123- req .Domain , remoteProb )
91+ }
92+
93+ if features .Get ().EnforceMultiCAA {
94+ op := func (ctx context.Context , remoteva RemoteVA , req proto.Message ) (remoteResult , error ) {
95+ checkRequest , ok := req .(* vapb.IsCAAValidRequest )
96+ if ! ok {
97+ return nil , fmt .Errorf ("got type %T, want *vapb.IsCAAValidRequest" , req )
12498 }
99+ return remoteva .IsCAAValid (ctx , checkRequest )
100+ }
101+ remoteProb := va .performRemoteOperation (ctx , op , req )
102+ // If the remote result was a non-nil problem then fail the CAA check
103+ if remoteProb != nil {
104+ prob = remoteProb
105+ va .log .Infof ("CAA check failed due to remote failures: identifier=%v err=%s" ,
106+ req .Domain , remoteProb )
125107 }
126108 }
127109
@@ -139,138 +121,6 @@ func (va *ValidationAuthorityImpl) IsCAAValid(ctx context.Context, req *vapb.IsC
139121 }
140122}
141123
142- // processRemoteCAAResults evaluates a primary VA result, and a channel of
143- // remote VA problems to produce a single overall validation result based on
144- // configured feature flags. The overall result is calculated based on the VA's
145- // configured `maxRemoteFailures` value.
146- //
147- // If the `MultiCAAFullResults` feature is enabled then
148- // `processRemoteCAAResults` will expect to read a result from the
149- // `remoteResultsChan` channel for each VA and will not produce an overall
150- // result until all remote VAs have responded. In this case
151- // `logRemoteDifferentials` will also be called to describe the differential
152- // between the primary and all of the remote VAs.
153- //
154- // If the `MultiCAAFullResults` feature flag is not enabled then
155- // `processRemoteCAAResults` will potentially return before all remote VAs have
156- // had a chance to respond. This happens if the success or failure threshold is
157- // met. This doesn't allow for logging the differential between the primary and
158- // remote VAs but is more performant.
159- func (va * ValidationAuthorityImpl ) processRemoteCAAResults (
160- domain string ,
161- acctID int64 ,
162- challengeType string ,
163- remoteResultsChan <- chan * remoteVAResult ) * probs.ProblemDetails {
164-
165- required := len (va .remoteVAs ) - va .maxRemoteFailures
166- good := 0
167- bad := 0
168-
169- var remoteResults []* remoteVAResult
170- var firstProb * probs.ProblemDetails
171- // Due to channel behavior this could block indefinitely and we rely on gRPC
172- // honoring the context deadline used in client calls to prevent that from
173- // happening.
174- for result := range remoteResultsChan {
175- // Add the result to the slice
176- remoteResults = append (remoteResults , result )
177- if result .Problem == nil {
178- good ++
179- } else {
180- bad ++
181- // Store the first non-nil problem to return later (if `MultiCAAFullResults`
182- // is enabled).
183- if firstProb == nil {
184- firstProb = result .Problem
185- }
186- }
187-
188- // If MultiCAAFullResults isn't enabled then return early whenever the
189- // success or failure threshold is met.
190- if ! features .Get ().MultiCAAFullResults {
191- if good >= required {
192- return nil
193- } else if bad > va .maxRemoteFailures {
194- modifiedProblem := * result .Problem
195- modifiedProblem .Detail = "During secondary CAA checking: " + firstProb .Detail
196- return & modifiedProblem
197- }
198- }
199-
200- // If we haven't returned early because of MultiCAAFullResults being
201- // enabled we need to break the loop once all of the VAs have returned a
202- // result.
203- if len (remoteResults ) == len (va .remoteVAs ) {
204- break
205- }
206- }
207- // If we are using `features.MultiCAAFullResults` then we haven't returned
208- // early and can now log the differential between what the primary VA saw and
209- // what all of the remote VAs saw.
210- va .logRemoteResults (
211- domain ,
212- acctID ,
213- challengeType ,
214- remoteResults )
215-
216- // Based on the threshold of good/bad return nil or a problem.
217- if good >= required {
218- return nil
219- } else if bad > va .maxRemoteFailures {
220- modifiedProblem := * firstProb
221- modifiedProblem .Detail = "During secondary CAA checking: " + firstProb .Detail
222- va .metrics .prospectiveRemoteCAACheckFailures .Inc ()
223- return & modifiedProblem
224- }
225-
226- // This condition should not occur - it indicates the good/bad counts didn't
227- // meet either the required threshold or the maxRemoteFailures threshold.
228- return probs .ServerInternal ("Too few remote IsCAAValid RPC results" )
229- }
230-
231- // performRemoteCAACheck calls `isCAAValid` for each of the configured remoteVAs
232- // in a random order. The provided `results` chan should have an equal size to
233- // the number of remote VAs. The CAA checks will be performed in separate
234- // go-routines. If the result `error` from a remote `isCAAValid` RPC is nil or a
235- // nil `ProblemDetails` instance it is written directly to the `results` chan.
236- // If the err is a canceled error it is treated as a nil error. Otherwise the
237- // error/problem is written to the results channel as-is.
238- func (va * ValidationAuthorityImpl ) performRemoteCAACheck (
239- ctx context.Context ,
240- req * vapb.IsCAAValidRequest ,
241- results chan <- * remoteVAResult ) {
242- for _ , i := range rand .Perm (len (va .remoteVAs )) {
243- remoteVA := va .remoteVAs [i ]
244- go func (rva RemoteVA ) {
245- result := & remoteVAResult {
246- VAHostname : rva .Address ,
247- }
248- res , err := rva .IsCAAValid (ctx , req )
249- if err != nil {
250- if core .IsCanceled (err ) {
251- // Handle the cancellation error.
252- result .Problem = probs .ServerInternal ("Remote VA IsCAAValid RPC canceled" )
253- } else {
254- // Handle validation error.
255- va .log .Errf ("Remote VA %q.IsCAAValid failed: %s" , rva .Address , err )
256- result .Problem = probs .ServerInternal ("Remote VA IsCAAValid RPC failed" )
257- }
258- } else if res .Problem != nil {
259- prob , err := bgrpc .PBToProblemDetails (res .Problem )
260- if err != nil {
261- va .log .Infof ("Remote VA %q.IsCAAValid returned malformed problem: %s" , rva .Address , err )
262- result .Problem = probs .ServerInternal (
263- fmt .Sprintf ("Remote VA IsCAAValid RPC returned malformed result: %s" , err ))
264- } else {
265- va .log .Infof ("Remote VA %q.IsCAAValid returned problem: %s" , rva .Address , prob )
266- result .Problem = prob
267- }
268- }
269- results <- result
270- }(remoteVA )
271- }
272- }
273-
274124// checkCAA performs a CAA lookup & validation for the provided identifier. If
275125// the CAA lookup & validation fail a problem is returned.
276126func (va * ValidationAuthorityImpl ) checkCAA (
0 commit comments