diff --git a/cmd/boulder-va/main.go b/cmd/boulder-va/main.go index 032435fac49..60353424abd 100644 --- a/cmd/boulder-va/main.go +++ b/cmd/boulder-va/main.go @@ -115,7 +115,9 @@ func main() { scope, clk, logger, - c.VA.AccountURIPrefixes) + c.VA.AccountURIPrefixes, + va.PrimaryPerspective, + "") cmd.FailOnError(err, "Unable to create VA server") start, err := bgrpc.NewServer(c.VA.GRPC, logger).Add( diff --git a/cmd/remoteva/main.go b/cmd/remoteva/main.go index 9ea068fc086..49db5c17953 100644 --- a/cmd/remoteva/main.go +++ b/cmd/remoteva/main.go @@ -20,6 +20,29 @@ type Config struct { RVA struct { vaConfig.Common + // Perspective uniquely identifies the Network Perspective used to + // perform the validation, as specified in BRs Section 5.4.1, + // Requirement 2.7 ("Multi-Perspective Issuance Corroboration attempts + // from each Network Perspective"). It should uniquely identify a group + // of RVAs deployed in the same datacenter. + // + // TODO(#7615): Make mandatory. + Perspective string `validate:"omitempty"` + + // RIR indicates the Regional Internet Registry where this RVA is + // located. This field is used to identify the RIR region from which a + // given validation was performed, as specified in the "Phased + // Implementation Timeline" in BRs Section 3.2.2.9. It must be one of + // the following values: + // - ARIN + // - RIPE + // - APNIC + // - LACNIC + // - AfriNIC + // + // TODO(#7615): Make mandatory. + RIR string `validate:"omitempty,oneof=ARIN RIPE APNIC LACNIC AfriNIC"` + // SkipGRPCClientCertVerification, when disabled as it should typically // be, will cause the remoteva server (which receives gRPCs from a // boulder-va client) to use our default RequireAndVerifyClientCert @@ -118,7 +141,9 @@ func main() { scope, clk, logger, - c.RVA.AccountURIPrefixes) + c.RVA.AccountURIPrefixes, + c.RVA.Perspective, + c.RVA.RIR) cmd.FailOnError(err, "Unable to create Remote-VA server") start, err := bgrpc.NewServer(c.RVA.GRPC, logger).Add( diff --git a/core/objects.go b/core/objects.go index 443e1a693da..a4a5240df37 100644 --- a/core/objects.go +++ b/core/objects.go @@ -129,6 +129,7 @@ type ValidationRecord struct { Port string `json:"port,omitempty"` AddressesResolved []net.IP `json:"addressesResolved,omitempty"` AddressUsed net.IP `json:"addressUsed,omitempty"` + // AddressesTried contains a list of addresses tried before the `AddressUsed`. // Presently this will only ever be one IP from `AddressesResolved` since the // only retry is in the case of a v6 failure with one v4 fallback. E.g. if @@ -144,10 +145,29 @@ type ValidationRecord struct { // ... // } AddressesTried []net.IP `json:"addressesTried,omitempty"` + // ResolverAddrs is the host:port of the DNS resolver(s) that fulfilled the // lookup for AddressUsed. During recursive A and AAAA lookups, a record may // instead look like A:host:port or AAAA:host:port ResolverAddrs []string `json:"resolverAddrs,omitempty"` + + // Perspective uniquely identifies the Network Perspective used to perform + // the validation, as specified in BRs Section 5.4.1, Requirement 2.7 + // ("Multi-Perspective Issuance Corroboration attempts from each Network + // Perspective"). It should uniquely identify either the Primary Perspective + // (VA) or a group of RVAs deployed in the same datacenter. + Perspective string `json:"perspective,omitempty"` + + // RIR indicates the Regional Internet Registry where this RVA is located. + // This field is used to identify the RIR region from which a given + // validation was performed, as specified in the "Phased Implementation + // Timeline" in BRs Section 3.2.2.9. It must be one of the following values: + // - ARIN + // - RIPE + // - APNIC + // - LACNIC + // - AfriNIC + RIR string `json:"rir,omitempty"` } // Challenge is an aggregate of all data needed for any challenges. diff --git a/test/config-next/remoteva-a.json b/test/config-next/remoteva-a.json index 4085a6e140c..bf15c665067 100644 --- a/test/config-next/remoteva-a.json +++ b/test/config-next/remoteva-a.json @@ -36,7 +36,9 @@ "accountURIPrefixes": [ "http://boulder.service.consul:4000/acme/reg/", "http://boulder.service.consul:4001/acme/acct/" - ] + ], + "perspective": "development", + "rir": "ARIN" }, "syslog": { "stdoutlevel": 4, diff --git a/test/config-next/remoteva-b.json b/test/config-next/remoteva-b.json index 8e9a44e84fb..a3ca00b851c 100644 --- a/test/config-next/remoteva-b.json +++ b/test/config-next/remoteva-b.json @@ -36,7 +36,9 @@ "accountURIPrefixes": [ "http://boulder.service.consul:4000/acme/reg/", "http://boulder.service.consul:4001/acme/acct/" - ] + ], + "perspective": "development", + "rir": "RIPE" }, "syslog": { "stdoutlevel": 4, diff --git a/va/caa_test.go b/va/caa_test.go index 367088f3845..477d3b84b3d 100644 --- a/va/caa_test.go +++ b/va/caa_test.go @@ -589,7 +589,7 @@ func (b caaBrokenDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, s } func TestDisabledMultiCAARechecking(t *testing.T) { - brokenRVA := setupRemote(nil, "broken", caaBrokenDNS{}) + brokenRVA, _ := setupRemote(nil, "broken", caaBrokenDNS{}, "", "") remoteVAs := []RemoteVA{{brokenRVA, "broken"}} va, _ := setup(nil, 0, "local", remoteVAs, nil) @@ -663,10 +663,10 @@ func TestMultiCAARechecking(t *testing.T) { brokenUA = "broken" hijackedUA = "hijacked" ) - remoteVA := setupRemote(nil, remoteUA, nil) - brokenVA := setupRemote(nil, brokenUA, caaBrokenDNS{}) + remoteVA, _ := setupRemote(nil, remoteUA, nil, "", "") + brokenVA, _ := setupRemote(nil, brokenUA, caaBrokenDNS{}, "", "") // Returns incorrect results - hijackedVA := setupRemote(nil, hijackedUA, caaHijackedDNS{}) + hijackedVA, _ := setupRemote(nil, hijackedUA, caaHijackedDNS{}, "", "") testCases := []struct { name string diff --git a/va/proto/va.pb.go b/va/proto/va.pb.go index 2bcab1e3057..f73970cdee5 100644 --- a/va/proto/va.pb.go +++ b/va/proto/va.pb.go @@ -264,8 +264,10 @@ type ValidationResult struct { sizeCache protoimpl.SizeCache 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"` + 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"` + 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 *ValidationResult) Reset() { @@ -314,6 +316,20 @@ func (x *ValidationResult) GetProblems() *proto.ProblemDetails { return nil } +func (x *ValidationResult) GetPerspective() string { + if x != nil { + return x.Perspective + } + return "" +} + +func (x *ValidationResult) GetRir() string { + if x != nil { + return x.Rir + } + return "" +} + var File_va_proto protoreflect.FileDescriptor var file_va_proto_rawDesc = []byte{ @@ -347,27 +363,30 @@ var file_va_proto_rawDesc = []byte{ 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, 0x76, 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, 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, + 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, } var ( diff --git a/va/proto/va.proto b/va/proto/va.proto index d432e2676f5..3be26241330 100644 --- a/va/proto/va.proto +++ b/va/proto/va.proto @@ -40,4 +40,6 @@ message AuthzMeta { message ValidationResult { repeated core.ValidationRecord records = 1; core.ProblemDetails problems = 2; + string perspective = 3; + string rir = 4; } diff --git a/va/va.go b/va/va.go index 10ecfb2e00c..17c03cf6e01 100644 --- a/va/va.go +++ b/va/va.go @@ -31,6 +31,8 @@ import ( vapb "github.com/letsencrypt/boulder/va/proto" ) +const PrimaryPerspective = "Primary" + var ( // badTLSHeader contains the string 'HTTP /' which is returned when // we try to talk TLS to a server that only talks HTTP @@ -256,6 +258,8 @@ type ValidationAuthorityImpl struct { maxRemoteFailures int accountURIPrefixes []string singleDialTimeout time.Duration + perspective string + rir string metrics *vaMetrics } @@ -274,6 +278,8 @@ func NewValidationAuthorityImpl( clk clock.Clock, logger blog.Logger, accountURIPrefixes []string, + perspective string, + rir string, ) (*ValidationAuthorityImpl, error) { if len(accountURIPrefixes) == 0 { @@ -300,6 +306,8 @@ func NewValidationAuthorityImpl( // used for the DialContext operations that take place during an // HTTP-01 challenge validation. singleDialTimeout: 10 * time.Second, + perspective: perspective, + rir: rir, } return va, nil @@ -314,6 +322,8 @@ type verificationRequestEvent struct { ValidationLatency float64 Error string `json:",omitempty"` InternalError string `json:",omitempty"` + Perspective string `json:",omitempty"` + RIR string `json:",omitempty"` } // ipError is an error type used to pass though the IP address of the remote @@ -668,6 +678,18 @@ func (va *ValidationAuthorityImpl) PerformValidation(ctx context.Context, req *v logEvent.Challenge.Status = core.StatusValid } + if va.perspective != "" && va.perspective != PrimaryPerspective { + // This validation was performed by a remote VA. According to the + // requirements in section 5.4.1 (2) vii of the BRs we need to log + // the perspective used. Additionally, we'll log the RIR where this + // RVA is located. + // + // TODO(#7615): Make these fields mandatory for non-Primary + // perspectives and remove the (va.perspective != "") check. + logEvent.Perspective = va.perspective + logEvent.RIR = va.rir + } + va.metrics.localValidationTime.With(prometheus.Labels{ "type": string(logEvent.Challenge.Type), "result": string(logEvent.Challenge.Status), diff --git a/va/va_test.go b/va/va_test.go index 62b68f49861..705ca7d5372 100644 --- a/va/va_test.go +++ b/va/va_test.go @@ -123,6 +123,8 @@ func setup(srv *httptest.Server, maxRemoteFailures int, userAgent string, remote fc, logger, accountURIPrefixes, + PrimaryPerspective, + "", ) if mockDNSClientOverride != nil { @@ -146,10 +148,12 @@ func setup(srv *httptest.Server, maxRemoteFailures int, userAgent string, remote return va, logger } -func setupRemote(srv *httptest.Server, userAgent string, mockDNSClientOverride bdns.Client) RemoteClients { - rva, _ := setup(srv, 0, userAgent, nil, mockDNSClientOverride) +func setupRemote(srv *httptest.Server, userAgent string, mockDNSClientOverride bdns.Client, perspective, rir string) (RemoteClients, *blog.Mock) { + rva, log := setup(srv, 0, userAgent, nil, mockDNSClientOverride) + rva.perspective = perspective + rva.rir = rir - return RemoteClients{VAClient: &inMemVA{*rva}, CAAClient: &inMemVA{*rva}} + return RemoteClients{VAClient: &inMemVA{*rva}, CAAClient: &inMemVA{*rva}}, log } type multiSrv struct { @@ -369,8 +373,8 @@ func TestMultiVA(t *testing.T) { ms := httpMultiSrv(t, expectedToken, allowedUAs) defer ms.Close() - remoteVA1 := setupRemote(ms.Server, remoteUA1, nil) - remoteVA2 := setupRemote(ms.Server, remoteUA2, nil) + remoteVA1, _ := setupRemote(ms.Server, remoteUA1, nil, "", "") + remoteVA2, _ := setupRemote(ms.Server, remoteUA2, nil, "", "") remoteVAs := []RemoteVA{ {remoteVA1, remoteUA1}, {remoteVA2, remoteUA2}, @@ -507,8 +511,8 @@ func TestMultiVAEarlyReturn(t *testing.T) { ms := httpMultiSrv(t, expectedToken, allowedUAs) defer ms.Close() - remoteVA1 := setupRemote(ms.Server, remoteUA1, nil) - remoteVA2 := setupRemote(ms.Server, remoteUA2, nil) + remoteVA1, _ := setupRemote(ms.Server, remoteUA1, nil, "", "") + remoteVA2, _ := setupRemote(ms.Server, remoteUA2, nil, "", "") remoteVAs := []RemoteVA{ {remoteVA1, remoteUA1}, @@ -557,8 +561,8 @@ func TestMultiVAPolicy(t *testing.T) { ms := httpMultiSrv(t, expectedToken, allowedUAs) defer ms.Close() - remoteVA1 := setupRemote(ms.Server, remoteUA1, nil) - remoteVA2 := setupRemote(ms.Server, remoteUA2, nil) + remoteVA1, _ := setupRemote(ms.Server, remoteUA1, nil, "", "") + remoteVA2, _ := setupRemote(ms.Server, remoteUA2, nil, "", "") remoteVAs := []RemoteVA{ {remoteVA1, remoteUA1}, @@ -577,6 +581,40 @@ func TestMultiVAPolicy(t *testing.T) { } } +func TestMultiVALogging(t *testing.T) { + const ( + rva1UA = "remote 1" + rva2UA = "remote 2" + localUA = "local 1" + ) + + ms := httpMultiSrv(t, expectedToken, map[string]bool{localUA: true, rva1UA: true, rva2UA: true}) + defer ms.Close() + + rva1, rva1Log := setupRemote(ms.Server, rva1UA, nil, "dev-arin", "ARIN") + rva2, rva2Log := setupRemote(ms.Server, rva2UA, nil, "dev-ripe", "RIPE") + + remoteVAs := []RemoteVA{ + {rva1, rva1UA}, + {rva2, rva2UA}, + } + va, vaLog := setup(ms.Server, 0, localUA, remoteVAs, 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.AssertNotError(t, err, "performing validation") + + // We do not log perspective or RIR for the local VAs. + test.Assert(t, len(vaLog.GetAllMatching(`"Perspective"`)) == 0, "expected no logged perspective for primary") + test.Assert(t, len(vaLog.GetAllMatching(`"RIR"`)) == 0, "expected no logged RIR for primary") + + // We do log perspective and RIR for the remote VAs. + test.Assert(t, len(rva1Log.GetAllMatching(`"Perspective":"dev-arin"`)) == 1, "expected perspective of VA to be dev-arin") + test.Assert(t, len(rva1Log.GetAllMatching(`"RIR":"ARIN"`)) == 1, "expected perspective of VA to be ARIN") + test.Assert(t, len(rva2Log.GetAllMatching(`"Perspective":"dev-ripe"`)) == 1, "expected perspective of VA to be dev-ripe") + test.Assert(t, len(rva2Log.GetAllMatching(`"RIR":"RIPE"`)) == 1, "expected perspective of VA to be RIPE") +} + func TestDetailedError(t *testing.T) { cases := []struct { err error @@ -631,9 +669,9 @@ func TestDetailedError(t *testing.T) { func TestLogRemoteDifferentials(t *testing.T) { // Create some remote VAs - remoteVA1 := setupRemote(nil, "remote 1", nil) - remoteVA2 := setupRemote(nil, "remote 2", nil) - remoteVA3 := setupRemote(nil, "remote 3", nil) + remoteVA1, _ := setupRemote(nil, "remote 1", nil, "", "") + remoteVA2, _ := setupRemote(nil, "remote 2", nil, "", "") + remoteVA3, _ := setupRemote(nil, "remote 3", nil, "", "") remoteVAs := []RemoteVA{ {remoteVA1, "remote 1"}, {remoteVA2, "remote 2"},