VA: Make performRemoteValidation more generic (#7847)

- Make performRemoteValidation a more generic function that returns a
new remoteResult interface
- Modify the return value of IsCAAValid and PerformValidation to satisfy
the remoteResult interface
- Include compile time checks and tests that pass an arbitrary operation
This commit is contained in:
Samantha Frank 2024-11-27 15:29:33 -05:00 committed by GitHub
parent ded2e5e610
commit 27a77142ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 215 additions and 100 deletions

View File

@ -188,13 +188,13 @@ func ValidationResultToPB(records []core.ValidationRecord, prob *probs.ProblemDe
return nil, err
}
}
marshalledProbs, err := ProblemDetailsToPB(prob)
marshalledProb, err := ProblemDetailsToPB(prob)
if err != nil {
return nil, err
}
return &vapb.ValidationResult{
Records: recordAry,
Problems: marshalledProbs,
Problem: marshalledProb,
Perspective: perspective,
Rir: rir,
}, nil
@ -212,7 +212,7 @@ func pbToValidationResult(in *vapb.ValidationResult) ([]core.ValidationRecord, *
return nil, nil, err
}
}
prob, err := PBToProblemDetails(in.Problems)
prob, err := PBToProblemDetails(in.Problem)
if err != nil {
return nil, nil, err
}

View File

@ -1762,7 +1762,7 @@ func (ra *RegistrationAuthorityImpl) recordValidation(ctx context.Context, authI
Attempted: string(challenge.Type),
AttemptedAt: validated,
ValidationRecords: vr.Records,
ValidationError: vr.Problems,
ValidationError: vr.Problem,
})
return err
}
@ -1929,8 +1929,8 @@ func (ra *RegistrationAuthorityImpl) PerformValidation(
prob = probs.ServerInternal("Could not communicate with VA")
ra.log.AuditErrf("Could not communicate with VA: %s", err)
} else {
if res.Problems != nil {
prob, err = bgrpc.PBToProblemDetails(res.Problems)
if res.Problem != nil {
prob, err = bgrpc.PBToProblemDetails(res.Problem)
if err != nil {
prob = probs.ServerInternal("Could not communicate with VA")
ra.log.AuditErrf("Could not communicate with VA: %s", err)

View File

@ -728,7 +728,7 @@ func TestPerformValidationAlreadyValid(t *testing.T) {
Url: "http://example.com/",
},
},
Problems: nil,
Problem: nil,
}
// A subsequent call to perform validation should return nil due
@ -758,7 +758,7 @@ func TestPerformValidationSuccess(t *testing.T) {
ResolverAddrs: []string{"rebound"},
},
},
Problems: nil,
Problem: nil,
}
now := fc.Now()
@ -901,7 +901,7 @@ func TestPerformValidation_FailedValidationsTriggerPauseIdentifiersRatelimit(t *
ResolverAddrs: []string{"rebound"},
},
},
Problems: &corepb.ProblemDetails{
Problem: &corepb.ProblemDetails{
Detail: fmt.Sprintf("CAA invalid for %s", domain),
},
}
@ -954,7 +954,7 @@ func TestPerformValidation_FailedValidationsTriggerPauseIdentifiersRatelimit(t *
ResolverAddrs: []string{"rebound"},
},
},
Problems: &corepb.ProblemDetails{
Problem: &corepb.ProblemDetails{
Detail: fmt.Sprintf("CAA invalid for %s", domain),
},
}
@ -1034,7 +1034,7 @@ func TestPerformValidation_FailedThenSuccessfulValidationResetsPauseIdentifiersR
ResolverAddrs: []string{"rebound"},
},
},
Problems: &corepb.ProblemDetails{
Problem: &corepb.ProblemDetails{
Detail: fmt.Sprintf("CAA invalid for %s", domain),
},
}
@ -1092,7 +1092,7 @@ func TestPerformValidation_FailedThenSuccessfulValidationResetsPauseIdentifiersR
ResolverAddrs: []string{"rebound"},
},
},
Problems: nil,
Problem: nil,
}
challIdx = dnsChallIdx(t, authzPB.Challenges)

View File

@ -1083,8 +1083,8 @@ def test_http_multiva_threshold_fail():
raise(Exception("no HTTP-01 challenge in failed authz"))
if httpChall.error.typ != "urn:ietf:params:acme:error:unauthorized":
raise(Exception("expected unauthorized prob, found {0}".format(httpChall.error.typ)))
if not httpChall.error.detail.startswith("During secondary domain validation: "):
raise(Exception("expected 'During secondary domain validation' problem detail, found {0}".format(httpChall.error.detail)))
if not httpChall.error.detail.startswith("During secondary validation: "):
raise(Exception("expected 'During secondary validation' problem detail, found {0}".format(httpChall.error.detail)))
class FakeH2ServerHandler(socketserver.BaseRequestHandler):
"""

View File

@ -25,8 +25,8 @@ func TestDNSValidationEmpty(t *testing.T) {
// metrics checked below are incremented.
req := createValidationRequest("empty-txts.com", core.ChallengeTypeDNS01)
res, _ := va.PerformValidation(context.Background(), req)
test.AssertEquals(t, res.Problems.ProblemType, "unauthorized")
test.AssertEquals(t, res.Problems.Detail, "No TXT record found at _acme-challenge.empty-txts.com")
test.AssertEquals(t, res.Problem.ProblemType, "unauthorized")
test.AssertEquals(t, res.Problem.Detail, "No TXT record found at _acme-challenge.empty-txts.com")
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
"operation": opChallAndCAA,

View File

@ -91,7 +91,9 @@ type IsCAAValidResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Problem *proto.ProblemDetails `protobuf:"bytes,1,opt,name=problem,proto3" json:"problem,omitempty"`
Problem *proto.ProblemDetails `protobuf:"bytes,1,opt,name=problem,proto3" json:"problem,omitempty"`
Perspective string `protobuf:"bytes,3,opt,name=perspective,proto3" json:"perspective,omitempty"`
Rir string `protobuf:"bytes,4,opt,name=rir,proto3" json:"rir,omitempty"`
}
func (x *IsCAAValidResponse) Reset() {
@ -133,6 +135,20 @@ func (x *IsCAAValidResponse) GetProblem() *proto.ProblemDetails {
return nil
}
func (x *IsCAAValidResponse) GetPerspective() string {
if x != nil {
return x.Perspective
}
return ""
}
func (x *IsCAAValidResponse) GetRir() string {
if x != nil {
return x.Rir
}
return ""
}
type PerformValidationRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -265,7 +281,7 @@ type ValidationResult struct {
unknownFields protoimpl.UnknownFields
Records []*proto.ValidationRecord `protobuf:"bytes,1,rep,name=records,proto3" json:"records,omitempty"`
Problems *proto.ProblemDetails `protobuf:"bytes,2,opt,name=problems,proto3" json:"problems,omitempty"`
Problem *proto.ProblemDetails `protobuf:"bytes,2,opt,name=problem,proto3" json:"problem,omitempty"`
Perspective string `protobuf:"bytes,3,opt,name=perspective,proto3" json:"perspective,omitempty"`
Rir string `protobuf:"bytes,4,opt,name=rir,proto3" json:"rir,omitempty"`
}
@ -309,9 +325,9 @@ func (x *ValidationResult) GetRecords() []*proto.ValidationRecord {
return nil
}
func (x *ValidationResult) GetProblems() *proto.ProblemDetails {
func (x *ValidationResult) GetProblem() *proto.ProblemDetails {
if x != nil {
return x.Problems
return x.Problem
}
return nil
}
@ -343,50 +359,53 @@ var file_va_proto_rawDesc = []byte{
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x22,
0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x55, 0x52, 0x49, 0x49, 0x44, 0x18, 0x03,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x55, 0x52, 0x49,
0x49, 0x44, 0x22, 0x44, 0x0a, 0x12, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64,
0x49, 0x44, 0x22, 0x78, 0x0a, 0x12, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x62,
0x6c, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52,
0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x22, 0xc4, 0x01, 0x0a, 0x18, 0x50, 0x65, 0x72,
0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12,
0x2d, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65,
0x6e, 0x67, 0x65, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x23,
0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e,
0x76, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x61, 0x75,
0x74, 0x68, 0x7a, 0x12, 0x3a, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4b,
0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4b,
0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22,
0x31, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05,
0x72, 0x65, 0x67, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x65, 0x67,
0x49, 0x44, 0x22, 0xaa, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x30, 0x0a, 0x08, 0x70, 0x72, 0x6f,
0x62, 0x6c, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x73, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70,
0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a,
0x03, 0x72, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x32,
0x4f, 0x0a, 0x02, 0x56, 0x41, 0x12, 0x49, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x76, 0x61, 0x2e,
0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x61, 0x2e, 0x56, 0x61,
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00,
0x32, 0x44, 0x0a, 0x03, 0x43, 0x41, 0x41, 0x12, 0x3d, 0x0a, 0x0a, 0x49, 0x73, 0x43, 0x41, 0x41,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73, 0x43, 0x41, 0x41,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76,
0x61, 0x2e, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x73,
0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x69,
0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x69, 0x72, 0x22, 0xc4, 0x01, 0x0a,
0x18, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6e, 0x73,
0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e,
0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68,
0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,
0x67, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x61, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61,
0x52, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x3a, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63,
0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x65, 0x78, 0x70, 0x65, 0x63,
0x74, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x31, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x4d, 0x65, 0x74, 0x61,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x72,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x0a,
0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74,
0x61, 0x69, 0x6c, 0x73, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x12, 0x20, 0x0a,
0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12,
0x10, 0x0a, 0x03, 0x72, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x69,
0x72, 0x32, 0x4f, 0x0a, 0x02, 0x56, 0x41, 0x12, 0x49, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f,
0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x76,
0x61, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x61, 0x2e,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x22, 0x00, 0x32, 0x44, 0x0a, 0x03, 0x43, 0x41, 0x41, 0x12, 0x3d, 0x0a, 0x0a, 0x49, 0x73, 0x43,
0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73, 0x43,
0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
0x2e, 0x76, 0x61, 0x2e, 0x49, 0x73, 0x43, 0x41, 0x41, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x61, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -417,7 +436,7 @@ var file_va_proto_depIdxs = []int32{
6, // 1: va.PerformValidationRequest.challenge:type_name -> core.Challenge
3, // 2: va.PerformValidationRequest.authz:type_name -> va.AuthzMeta
7, // 3: va.ValidationResult.records:type_name -> core.ValidationRecord
5, // 4: va.ValidationResult.problems:type_name -> core.ProblemDetails
5, // 4: va.ValidationResult.problem:type_name -> core.ProblemDetails
2, // 5: va.VA.PerformValidation:input_type -> va.PerformValidationRequest
0, // 6: va.CAA.IsCAAValid:input_type -> va.IsCAAValidRequest
4, // 7: va.VA.PerformValidation:output_type -> va.ValidationResult

View File

@ -23,6 +23,8 @@ message IsCAAValidRequest {
// If CAA is valid for the requested domain, the problem will be empty
message IsCAAValidResponse {
core.ProblemDetails problem = 1;
string perspective = 3;
string rir = 4;
}
message PerformValidationRequest {
@ -39,7 +41,7 @@ message AuthzMeta {
message ValidationResult {
repeated core.ValidationRecord records = 1;
core.ProblemDetails problems = 2;
core.ProblemDetails problem = 2;
string perspective = 3;
string rir = 4;
}

View File

@ -18,9 +18,11 @@ import (
"github.com/jmhodges/clock"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/protobuf/proto"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
berrors "github.com/letsencrypt/boulder/errors"
bgrpc "github.com/letsencrypt/boulder/grpc"
"github.com/letsencrypt/boulder/identifier"
@ -452,15 +454,35 @@ func (va *ValidationAuthorityImpl) observeLatency(op, perspective, challType, pr
va.metrics.validationLatency.With(labels).Observe(latency.Seconds())
}
// performRemoteValidation coordinates the whole process of kicking off and
// collecting results from calls to remote VAs' PerformValidation function. It
// returns a problem if too many remote perspectives failed to corroborate
// domain control, or nil if enough succeeded to surpass our corroboration
// threshold.
func (va *ValidationAuthorityImpl) performRemoteValidation(
ctx context.Context,
req *vapb.PerformValidationRequest,
) *probs.ProblemDetails {
// remoteOperation is a func type that encapsulates the operation and request
// passed to va.performRemoteOperation. The operation must be a method on
// vapb.VAClient or vapb.CAAClient, and the request must be the corresponding
// proto.Message passed to that method.
type remoteOperation = func(context.Context, RemoteVA, proto.Message) (remoteResult, error)
// remoteResult is an interface that must be implemented by the results of a
// remoteOperation, such as *vapb.ValidationResult and *vapb.IsCAAValidResponse.
// It provides methods to access problem details, the associated perspective,
// and the RIR.
type remoteResult interface {
proto.Message
GetProblem() *corepb.ProblemDetails
GetPerspective() string
GetRir() string
}
var _ remoteResult = (*vapb.ValidationResult)(nil)
var _ remoteResult = (*vapb.IsCAAValidResponse)(nil)
// performRemoteOperation concurrently calls the provided operation with `req` and a
// RemoteVA once for each configured RemoteVA. It cancels remaining operations and returns
// early if either the required number of successful results is obtained or the number of
// failures exceeds va.maxRemoteFailures.
//
// Internal logic errors are logged. If the number of operation failures exceeds
// va.maxRemoteFailures, the first encountered problem is returned as a
// *probs.ProblemDetails.
func (va *ValidationAuthorityImpl) performRemoteOperation(ctx context.Context, op remoteOperation, req proto.Message) *probs.ProblemDetails {
remoteVACount := len(va.remoteVAs)
if remoteVACount == 0 {
return nil
@ -470,7 +492,7 @@ func (va *ValidationAuthorityImpl) performRemoteValidation(
addr string
perspective string
rir string
result *vapb.ValidationResult
result remoteResult
err error
}
@ -480,7 +502,7 @@ func (va *ValidationAuthorityImpl) performRemoteValidation(
responses := make(chan *response, remoteVACount)
for _, i := range rand.Perm(remoteVACount) {
go func(rva RemoteVA) {
res, err := rva.PerformValidation(subCtx, req)
res, err := op(subCtx, rva, req)
responses <- &response{rva.Address, rva.Perspective, rva.RIR, res, err}
}(va.remoteVAs[i])
}
@ -498,20 +520,20 @@ func (va *ValidationAuthorityImpl) performRemoteValidation(
failed = append(failed, resp.perspective)
if core.IsCanceled(resp.err) {
currProb = probs.ServerInternal("Secondary domain validation RPC canceled")
currProb = probs.ServerInternal("Secondary validation RPC canceled")
} else {
va.log.Errf("Remote VA %q.PerformValidation failed: %s", resp.addr, resp.err)
currProb = probs.ServerInternal("Secondary domain validation RPC failed")
va.log.Errf("Operation on remote VA (%s) failed: %s", resp.addr, resp.err)
currProb = probs.ServerInternal("Secondary validation RPC failed")
}
} else if resp.result.Problems != nil {
} else if resp.result.GetProblem() != nil {
// The remote VA returned a problem.
failed = append(failed, resp.perspective)
var err error
currProb, err = bgrpc.PBToProblemDetails(resp.result.Problems)
currProb, err = bgrpc.PBToProblemDetails(resp.result.GetProblem())
if err != nil {
va.log.Errf("Remote VA %q.PerformValidation returned malformed problem: %s", resp.addr, err)
currProb = probs.ServerInternal("Secondary domain validation RPC returned malformed result")
va.log.Errf("Operation on Remote VA (%s) returned malformed problem: %s", resp.addr, err)
currProb = probs.ServerInternal("Secondary validation RPC returned malformed result")
}
} else {
// The remote VA returned a successful result.
@ -542,12 +564,12 @@ func (va *ValidationAuthorityImpl) performRemoteValidation(
if len(passed) >= required {
return nil
} else if len(failed) > va.maxRemoteFailures {
firstProb.Detail = fmt.Sprintf("During secondary domain validation: %s", firstProb.Detail)
firstProb.Detail = fmt.Sprintf("During secondary validation: %s", firstProb.Detail)
return firstProb
} else {
// This condition should not occur - it indicates the passed/failed counts
// neither met the required threshold nor the maxRemoteFailures threshold.
return probs.ServerInternal("Too few remote PerformValidation RPC results")
return probs.ServerInternal("Too few remote RPC results")
}
}
@ -734,6 +756,13 @@ func (va *ValidationAuthorityImpl) PerformValidation(ctx context.Context, req *v
// singular problem, because the remote VAs have already audit-logged their
// own validation records, and it's not helpful to present multiple large
// errors to the end user.
prob = va.performRemoteValidation(ctx, req)
op := func(ctx context.Context, remoteva RemoteVA, req proto.Message) (remoteResult, error) {
validationRequest, ok := req.(*vapb.PerformValidationRequest)
if !ok {
return nil, fmt.Errorf("got type %T, want *vapb.PerformValidationRequest", req)
}
return remoteva.PerformValidation(ctx, validationRequest)
}
prob = va.performRemoteOperation(ctx, op, req)
return bgrpc.ValidationResultToPB(records, filterProblemDetails(prob), va.perspective, va.rir)
}

View File

@ -21,6 +21,7 @@ import (
"github.com/jmhodges/clock"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/core"
@ -346,7 +347,7 @@ func TestPerformValidationInvalid(t *testing.T) {
req := createValidationRequest("foo.com", core.ChallengeTypeDNS01)
res, _ := va.PerformValidation(context.Background(), req)
test.Assert(t, res.Problems != nil, "validation succeeded")
test.Assert(t, res.Problem != nil, "validation succeeded")
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
"operation": opChallAndCAA,
"perspective": va.perspective,
@ -375,7 +376,7 @@ func TestPerformValidationValid(t *testing.T) {
// create a challenge with well known token
req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
res, _ := va.PerformValidation(context.Background(), req)
test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed: %#v", res.Problem))
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
"operation": opChallAndCAA,
@ -402,7 +403,7 @@ func TestPerformValidationWildcard(t *testing.T) {
req := createValidationRequest("*.good-dns01.com", core.ChallengeTypeDNS01)
// perform a validation for a wildcard name
res, _ := va.PerformValidation(context.Background(), req)
test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed: %#v", res.Problem))
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
"operation": opChallAndCAA,
@ -435,7 +436,7 @@ func TestDCVAndCAASequencing(t *testing.T) {
req := createValidationRequest("good-dns01.com", core.ChallengeTypeDNS01)
res, err := va.PerformValidation(context.Background(), req)
test.AssertNotError(t, err, "performing validation")
test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed: %#v", res.Problems))
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed: %#v", res.Problem))
caaLog := mockLog.GetAllMatching(`Checked CAA records for`)
test.AssertEquals(t, len(caaLog), 1)
@ -444,11 +445,75 @@ func TestDCVAndCAASequencing(t *testing.T) {
req = createValidationRequest("bad-dns01.com", core.ChallengeTypeDNS01)
res, err = va.PerformValidation(context.Background(), req)
test.AssertNotError(t, err, "performing validation")
test.Assert(t, res.Problems != nil, "validation succeeded")
test.Assert(t, res.Problem != nil, "validation succeeded")
caaLog = mockLog.GetAllMatching(`Checked CAA records for`)
test.AssertEquals(t, len(caaLog), 0)
}
func TestPerformRemoteOperation(t *testing.T) {
va, _ := setupWithRemotes(nil, "", []remoteConf{
{ua: pass, rir: arin},
{ua: pass, rir: ripe},
{ua: pass, rir: apnic},
}, nil)
testCases := []struct {
name string
req proto.Message
expectedType string
expectedDetail string
op func(ctx context.Context, rva RemoteVA, req proto.Message) (remoteResult, error)
}{
{
name: "ValidationResult",
req: &vapb.PerformValidationRequest{},
expectedType: string(probs.BadNonceProblem),
expectedDetail: "quite surprising",
op: func(ctx context.Context, rva RemoteVA, req proto.Message) (remoteResult, error) {
prob := &corepb.ProblemDetails{
ProblemType: string(probs.BadNonceProblem),
Detail: "quite surprising",
}
return &vapb.ValidationResult{Problem: prob, Perspective: rva.Perspective, Rir: rva.RIR}, nil
},
},
{
name: "IsCAAValidResponse",
req: &vapb.IsCAAValidRequest{},
expectedType: string(probs.PausedProblem),
expectedDetail: "quite surprising, indeed",
op: func(ctx context.Context, rva RemoteVA, req proto.Message) (remoteResult, error) {
prob := &corepb.ProblemDetails{
ProblemType: string(probs.PausedProblem),
Detail: "quite surprising, indeed",
}
return &vapb.IsCAAValidResponse{Problem: prob, Perspective: rva.Perspective, Rir: rva.RIR}, nil
},
},
{
name: "IsCAAValidRequestWithValidationResult",
req: &vapb.IsCAAValidRequest{},
expectedType: string(probs.BadPublicKeyProblem),
expectedDetail: "a shocking result",
op: func(ctx context.Context, rva RemoteVA, req proto.Message) (remoteResult, error) {
prob := &corepb.ProblemDetails{
ProblemType: string(probs.BadPublicKeyProblem),
Detail: "a shocking result",
}
return &vapb.ValidationResult{Problem: prob, Perspective: rva.Perspective, Rir: rva.RIR}, nil
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
prob := va.performRemoteOperation(context.Background(), tc.op, tc.req)
test.AssertEquals(t, string(prob.Type), tc.expectedType)
test.AssertContains(t, prob.Detail, tc.expectedDetail)
})
}
}
func TestMultiVA(t *testing.T) {
t.Parallel()
@ -581,7 +646,7 @@ func TestMultiVA(t *testing.T) {
},
PrimaryUA: pass,
ExpectedProbType: string(probs.UnauthorizedProblem),
ExpectedLogContains: "During secondary domain validation: The key authorization file from the server",
ExpectedLogContains: "During secondary validation: The key authorization file from the server",
},
{
// If one remote VA cancels, it should succeed
@ -603,7 +668,7 @@ func TestMultiVA(t *testing.T) {
},
PrimaryUA: pass,
ExpectedProbType: string(probs.ServerInternalProblem),
ExpectedLogContains: "During secondary domain validation: Secondary domain validation RPC canceled",
ExpectedLogContains: "During secondary validation: Secondary validation RPC canceled",
},
{
// With the local and remote VAs seeing diff problems, we expect a problem.
@ -615,7 +680,7 @@ func TestMultiVA(t *testing.T) {
},
PrimaryUA: pass,
ExpectedProbType: string(probs.UnauthorizedProblem),
ExpectedLogContains: "During secondary domain validation: The key authorization file from the server",
ExpectedLogContains: "During secondary validation: The key authorization file from the server",
},
}
@ -632,13 +697,13 @@ func TestMultiVA(t *testing.T) {
// Perform all validations
res, _ := localVA.PerformValidation(ctx, req)
if res.Problems == nil && tc.ExpectedProbType != "" {
if res.Problem == nil && tc.ExpectedProbType != "" {
t.Errorf("expected prob %v, got nil", tc.ExpectedProbType)
} else if res.Problems != nil && tc.ExpectedProbType == "" {
t.Errorf("expected no prob, got %v", res.Problems)
} else if res.Problems != nil && tc.ExpectedProbType != "" {
} else if res.Problem != nil && tc.ExpectedProbType == "" {
t.Errorf("expected no prob, got %v", res.Problem)
} else if res.Problem != nil && tc.ExpectedProbType != "" {
// That result should match expected.
test.AssertEquals(t, res.Problems.ProblemType, tc.ExpectedProbType)
test.AssertEquals(t, res.Problem.ProblemType, tc.ExpectedProbType)
}
if tc.ExpectedLogContains != "" {
@ -701,7 +766,7 @@ func TestMultiVAEarlyReturn(t *testing.T) {
res, _ := localVA.PerformValidation(ctx, req)
// It should always fail
if res.Problems == nil {
if res.Problem == nil {
t.Error("expected prob from PerformValidation, got nil")
}
@ -739,7 +804,7 @@ func TestMultiVAPolicy(t *testing.T) {
req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)
res, _ := localVA.PerformValidation(ctx, req)
// It should fail
if res.Problems == nil {
if res.Problem == nil {
t.Error("expected prob from PerformValidation, got nil")
}
}
@ -758,7 +823,7 @@ func TestMultiVALogging(t *testing.T) {
va, _ := setupWithRemotes(ms.Server, pass, remoteConfs, nil)
req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)
res, err := va.PerformValidation(ctx, req)
test.Assert(t, res.Problems == nil, fmt.Sprintf("validation failed with: %#v", res.Problems))
test.Assert(t, res.Problem == nil, fmt.Sprintf("validation failed with: %#v", res.Problem))
test.AssertNotError(t, err, "performing validation")
}