Skip to content

Commit 577a1e3

Browse files
authored
va: prepare to require minimum of 3 RVAs (#7815)
To prepare for the MPIC requirement of having a minimum of 3 perspectives, I added code to `NewValidationAuthorityImpl` to error if there aren't enough remote VAs configured _and_ the current VA is the primary perspective. Then I fixed all the tests, which involved adding some backends in the unittests, and spinning up `remoteva-c` in the integration tests. As a reminder, the `boulder va` command always considers itself the primary perspective, while `boulder remoteva` gives itself a perspective based on its config. I wound up backing out the code in `NewValidationAuthorityImpl` because right now our remote VAs are actually running the `boulder va` command, so they would error out in prod, even though our actual primary perspective does have enough backends. So this wound up as a test-only change.
1 parent a46c388 commit 577a1e3

File tree

10 files changed

+156
-27
lines changed

10 files changed

+156
-27
lines changed

test/config-next/remoteva-c.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"rva": {
3+
"userAgent": "remoteva-c",
4+
"dnsTries": 3,
5+
"dnsStaticResolvers": [
6+
"10.77.77.77:8343",
7+
"10.77.77.77:8443"
8+
],
9+
"dnsTimeout": "1s",
10+
"dnsAllowLoopbackAddresses": true,
11+
"issuerDomain": "happy-hacker-ca.invalid",
12+
"tls": {
13+
"caCertfile": "test/certs/ipki/minica.pem",
14+
"certFile": "test/certs/ipki/rva.boulder/cert.pem",
15+
"keyFile": "test/certs/ipki/rva.boulder/key.pem"
16+
},
17+
"skipGRPCClientCertVerification": true,
18+
"grpc": {
19+
"maxConnectionAge": "30s",
20+
"services": {
21+
"va.VA": {
22+
"clientNames": [
23+
"va.boulder"
24+
]
25+
},
26+
"grpc.health.v1.Health": {
27+
"clientNames": [
28+
"health-checker.boulder"
29+
]
30+
}
31+
}
32+
},
33+
"features": {
34+
"DOH": true
35+
},
36+
"accountURIPrefixes": [
37+
"http://boulder.service.consul:4000/acme/reg/",
38+
"http://boulder.service.consul:4001/acme/acct/"
39+
],
40+
"perspective": "development",
41+
"rir": "ARIN"
42+
},
43+
"syslog": {
44+
"stdoutlevel": 4,
45+
"sysloglevel": -1
46+
},
47+
"openTelemetry": {
48+
"endpoint": "bjaeger:4317",
49+
"sampleratio": 1
50+
}
51+
}

test/config-next/va.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@
5252
"serverAddress": "rva1.service.consul:9498",
5353
"timeout": "15s",
5454
"hostOverride": "rva1.boulder"
55+
},
56+
{
57+
"serverAddress": "rva1.service.consul:9499",
58+
"timeout": "15s",
59+
"hostOverride": "rva1.boulder"
5560
}
5661
],
5762
"accountURIPrefixes": [

test/config/remoteva-c.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"rva": {
3+
"userAgent": "remoteva-c",
4+
"debugAddr": ":8213",
5+
"dnsTries": 3,
6+
"dnsProvider": {
7+
"dnsAuthority": "consul.service.consul",
8+
"srvLookup": {
9+
"service": "dns",
10+
"domain": "service.consul"
11+
}
12+
},
13+
"dnsTimeout": "1s",
14+
"dnsAllowLoopbackAddresses": true,
15+
"issuerDomain": "happy-hacker-ca.invalid",
16+
"tls": {
17+
"caCertfile": "test/certs/ipki/minica.pem",
18+
"certFile": "test/certs/ipki/rva.boulder/cert.pem",
19+
"keyFile": "test/certs/ipki/rva.boulder/key.pem"
20+
},
21+
"grpc": {
22+
"maxConnectionAge": "30s",
23+
"address": ":9899",
24+
"services": {
25+
"va.VA": {
26+
"clientNames": [
27+
"va.boulder"
28+
]
29+
},
30+
"grpc.health.v1.Health": {
31+
"clientNames": [
32+
"health-checker.boulder"
33+
]
34+
}
35+
}
36+
},
37+
"features": {},
38+
"accountURIPrefixes": [
39+
"http://boulder.service.consul:4000/acme/reg/",
40+
"http://boulder.service.consul:4001/acme/acct/"
41+
]
42+
},
43+
"syslog": {
44+
"stdoutlevel": 4,
45+
"sysloglevel": 4
46+
}
47+
}

test/config/va.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
"serverAddress": "rva1.service.consul:9498",
5050
"timeout": "15s",
5151
"hostOverride": "rva1.boulder"
52+
},
53+
{
54+
"serverAddress": "rva1.service.consul:9499",
55+
"timeout": "15s",
56+
"hostOverride": "rva1.boulder"
5257
}
5358
],
5459
"maxRemoteValidationFailures": 1,

test/consul/config.hcl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ services {
176176
tags = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
177177
}
178178

179+
services {
180+
id = "rva1-c"
181+
name = "rva1"
182+
address = "10.77.77.77"
183+
port = 9499
184+
tags = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
185+
}
186+
179187
# TODO(#5294) Remove rva2-a/b in favor of rva1-a/b
180188
services {
181189
id = "rva2-a"

test/startservers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
8012, 9498, 'rva.boulder',
2525
('./bin/boulder', 'remoteva', '--config', os.path.join(config_dir, 'remoteva-b.json'), '--addr', ':9498', '--debug-addr', ':8012'),
2626
None),
27+
Service('remoteva-c',
28+
8023, 9499, 'rva.boulder',
29+
('./bin/boulder', 'remoteva', '--config', os.path.join(config_dir, 'remoteva-c.json'), '--addr', ':9499', '--debug-addr', ':8023'),
30+
None),
2731
Service('boulder-sa-1',
2832
8003, 9395, 'sa.boulder',
2933
('./bin/boulder', 'boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', ':9395', '--debug-addr', ':8003'),

test/v2_integration.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,8 @@ def test_http_multiva_threshold_pass():
10051005

10061006
# Configure a guestlist that will pass the multiVA threshold test by
10071007
# allowing the primary VA at some, but not all, remotes.
1008-
guestlist = {"boulder": 1, "boulder-remoteva-a": 1, "boulder-remoteva-b": 1, "remoteva-a": 1}
1008+
# In particular, remoteva-c is missing.
1009+
guestlist = {"boulder": 1, "remoteva-a": 1, "remoteva-b": 1}
10091010

10101011
hostname, cleanup = multiva_setup(client, guestlist)
10111012

@@ -1021,7 +1022,7 @@ def test_http_multiva_primary_fail_remote_pass():
10211022

10221023
# Configure a guestlist that will fail the primary VA check but allow all of
10231024
# the remote VAs.
1024-
guestlist = {"boulder": 0, "boulder-remoteva-a": 1, "boulder-remoteva-b": 1, "remoteva-a": 1, "remoteva-b": 1}
1025+
guestlist = {"boulder": 0, "remoteva-a": 1, "remoteva-b": 1}
10251026

10261027
hostname, cleanup = multiva_setup(client, guestlist)
10271028

va/caa_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ func TestMultiCAARechecking(t *testing.T) {
734734
},
735735
},
736736
{
737-
name: "functional localVA, 2 broken RVAs, no CAA records",
737+
name: "functional localVA, 2 broken RVA, no CAA records",
738738
domains: "present-dns-only.com",
739739
expectedProbSubstring: "During secondary CAA checking: While processing CAA",
740740
expectedProbType: probs.DNSProblem,
@@ -938,7 +938,7 @@ func TestMultiCAARechecking(t *testing.T) {
938938
},
939939
},
940940
{
941-
name: "1 hijacked RVA, CAA issuewild type present",
941+
name: "1 hijacked RVA, CAA issuewild type present, 1 failure allowed",
942942
domains: "satisfiable-wildcard.com",
943943
expectedDiffLogSubstring: `RemoteSuccesses":2,"RemoteFailures":[{"VAHostname":"hijacked","Problem":{"type":"caa","detail":"While processing CAA for`,
944944
localDNSClient: caaMockDNS{},
@@ -949,7 +949,7 @@ func TestMultiCAARechecking(t *testing.T) {
949949
},
950950
},
951951
{
952-
name: "2 hijacked RVAs, CAA issuewild type present",
952+
name: "2 hijacked RVAs, CAA issuewild type present, 1 failure allowed",
953953
domains: "satisfiable-wildcard.com",
954954
expectedProbSubstring: "During secondary CAA checking: While processing CAA",
955955
expectedProbType: probs.CAAProblem,
@@ -962,7 +962,7 @@ func TestMultiCAARechecking(t *testing.T) {
962962
},
963963
},
964964
{
965-
name: "3 hijacked RVAs, CAA issuewild type present",
965+
name: "3 hijacked RVAs, CAA issuewild type present, 1 failure allowed",
966966
domains: "satisfiable-wildcard.com",
967967
expectedProbSubstring: "During secondary CAA checking: While processing CAA",
968968
expectedProbType: probs.CAAProblem,

va/dns_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestDNSValidationEmpty(t *testing.T) {
3030

3131
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
3232
"operation": opChallAndCAA,
33-
"perspective": PrimaryPerspective,
33+
"perspective": va.perspective,
3434
"challenge_type": string(core.ChallengeTypeDNS01),
3535
"problem_type": string(probs.UnauthorizedProblem),
3636
"result": fail,

va/va_test.go

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ func createValidationRequest(domain string, challengeType core.AcmeChallenge) *v
103103

104104
// setup returns an in-memory VA and a mock logger. The default resolver client
105105
// is MockClient{}, but can be overridden.
106+
//
107+
// If remoteVAs is nil, this builds a VA that acts like a remote (and does not
108+
// perform multi-perspective validation). Otherwise it acts like a primary.
106109
func setup(srv *httptest.Server, userAgent string, remoteVAs []RemoteVA, mockDNSClientOverride bdns.Client) (*ValidationAuthorityImpl, *blog.Mock) {
107110
features.Reset()
108111
fc := clock.NewFake()
@@ -113,6 +116,13 @@ func setup(srv *httptest.Server, userAgent string, remoteVAs []RemoteVA, mockDNS
113116
userAgent = "user agent 1.0"
114117
}
115118

119+
perspective := PrimaryPerspective
120+
if len(remoteVAs) == 0 {
121+
// We're being set up as a remote. Use a distinct perspective from other remotes
122+
// to better simulate what prod will be like.
123+
perspective = "example perspective " + core.RandomString(4)
124+
}
125+
116126
va, err := NewValidationAuthorityImpl(
117127
&bdns.MockClient{Log: logger},
118128
remoteVAs,
@@ -122,9 +132,12 @@ func setup(srv *httptest.Server, userAgent string, remoteVAs []RemoteVA, mockDNS
122132
fc,
123133
logger,
124134
accountURIPrefixes,
125-
PrimaryPerspective,
135+
perspective,
126136
"",
127137
)
138+
if err != nil {
139+
panic(fmt.Sprintf("Failed to create validation authority: %v", err))
140+
}
128141

129142
if mockDNSClientOverride != nil {
130143
va.dnsClient = mockDNSClientOverride
@@ -138,9 +151,6 @@ func setup(srv *httptest.Server, userAgent string, remoteVAs []RemoteVA, mockDNS
138151
va.tlsPort = port
139152
}
140153

141-
if err != nil {
142-
panic(fmt.Sprintf("Failed to create validation authority: %v", err))
143-
}
144154
return va, logger
145155
}
146156

@@ -255,7 +265,7 @@ func TestPerformValidationInvalid(t *testing.T) {
255265
test.Assert(t, res.Problems != nil, "validation succeeded")
256266
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
257267
"operation": opChallAndCAA,
258-
"perspective": PrimaryPerspective,
268+
"perspective": va.perspective,
259269
"challenge_type": string(core.ChallengeTypeDNS01),
260270
"problem_type": string(probs.UnauthorizedProblem),
261271
"result": fail,
@@ -285,7 +295,7 @@ func TestPerformValidationValid(t *testing.T) {
285295

286296
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
287297
"operation": opChallAndCAA,
288-
"perspective": PrimaryPerspective,
298+
"perspective": va.perspective,
289299
"challenge_type": string(core.ChallengeTypeDNS01),
290300
"problem_type": "",
291301
"result": pass,
@@ -312,7 +322,7 @@ func TestPerformValidationWildcard(t *testing.T) {
312322

313323
test.AssertMetricWithLabelsEquals(t, va.metrics.validationLatency, prometheus.Labels{
314324
"operation": opChallAndCAA,
315-
"perspective": PrimaryPerspective,
325+
"perspective": va.perspective,
316326
"challenge_type": string(core.ChallengeTypeDNS01),
317327
"problem_type": "",
318328
"result": pass,
@@ -422,15 +432,6 @@ func TestMultiVA(t *testing.T) {
422432
AllowedUAs: map[string]bool{remoteUA1: true, remoteUA2: true},
423433
ExpectedProb: unauthorized,
424434
},
425-
{
426-
// If one out of two remote VAs fail with an internal err it should succeed
427-
Name: "Local VA ok, 1/2 remote VA internal err",
428-
RemoteVAs: []RemoteVA{
429-
{remoteVA1, remoteUA1},
430-
{brokenVA, "broken"},
431-
},
432-
AllowedUAs: allowedUAs,
433-
},
434435
{
435436
// If one out of three remote VAs fails with an internal err it should succeed
436437
Name: "Local VA ok, 1/3 remote VA internal err",
@@ -530,12 +531,12 @@ func TestMultiVA(t *testing.T) {
530531
AllowedUAs: allowedUAs,
531532
},
532533
{
533-
// If two remote VA cancels, it should fail
534-
Name: "Local VA OK, two cancelled remote VAs",
534+
// If all remote VAs cancel, it should fail
535+
Name: "Local VA OK, three cancelled remote VAs",
535536
RemoteVAs: []RemoteVA{
536-
{remoteVA1, remoteUA1},
537537
{cancelledVA, remoteUA1},
538538
{cancelledVA, remoteUA2},
539+
{cancelledVA, remoteUA3},
539540
},
540541
AllowedUAs: allowedUAs,
541542
ExpectedProb: probs.ServerInternal("During secondary validation: Remote PerformValidation RPC canceled"),
@@ -645,24 +646,28 @@ func TestMultiVAPolicy(t *testing.T) {
645646
const (
646647
remoteUA1 = "remote 1"
647648
remoteUA2 = "remote 2"
649+
remoteUA3 = "remote 3"
648650
localUA = "local 1"
649651
)
650-
// Forbid both remote UAs to ensure that multi-va fails
652+
// Forbid all remote UAs to ensure that multi-va fails
651653
allowedUAs := map[string]bool{
652654
localUA: true,
653655
remoteUA1: false,
654656
remoteUA2: false,
657+
remoteUA3: false,
655658
}
656659

657660
ms := httpMultiSrv(t, expectedToken, allowedUAs)
658661
defer ms.Close()
659662

660663
remoteVA1 := setupRemote(ms.Server, remoteUA1, nil, "", "")
661664
remoteVA2 := setupRemote(ms.Server, remoteUA2, nil, "", "")
665+
remoteVA3 := setupRemote(ms.Server, remoteUA3, nil, "", "")
662666

663667
remoteVAs := []RemoteVA{
664668
{remoteVA1, remoteUA1},
665669
{remoteVA2, remoteUA2},
670+
{remoteVA3, remoteUA3},
666671
}
667672

668673
// Create a local test VA with the two remote VAs
@@ -681,6 +686,7 @@ func TestMultiVALogging(t *testing.T) {
681686
const (
682687
rva1UA = "remote 1"
683688
rva2UA = "remote 2"
689+
rva3UA = "remote 3"
684690
localUA = "local 1"
685691
)
686692

@@ -689,10 +695,12 @@ func TestMultiVALogging(t *testing.T) {
689695

690696
rva1 := setupRemote(ms.Server, rva1UA, nil, "dev-arin", "ARIN")
691697
rva2 := setupRemote(ms.Server, rva2UA, nil, "dev-ripe", "RIPE")
698+
rva3 := setupRemote(ms.Server, rva3UA, nil, "dev-ripe", "RIPE")
692699

693700
remoteVAs := []RemoteVA{
694701
{rva1, rva1UA},
695702
{rva2, rva2UA},
703+
{rva3, rva3UA},
696704
}
697705
va, _ := setup(ms.Server, localUA, remoteVAs, nil)
698706
req := createValidationRequest("letsencrypt.org", core.ChallengeTypeHTTP01)

0 commit comments

Comments
 (0)