diff --git a/consul_domain_fix.patch b/consul_domain_fix.patch new file mode 100644 index 00000000000..8d9d44404da --- /dev/null +++ b/consul_domain_fix.patch @@ -0,0 +1,62 @@ +diff --git a/vendor/github.com/letsencrypt/challtestsrv/dns.go b/vendor/github.com/letsencrypt/challtestsrv/dns.go +index 98be04251..818aa35f6 100644 +--- a/vendor/github.com/letsencrypt/challtestsrv/dns.go ++++ b/vendor/github.com/letsencrypt/challtestsrv/dns.go +@@ -5,6 +5,7 @@ import ( + "io" + "net" + "net/http" ++ "strings" + + "github.com/miekg/dns" + ) +@@ -53,7 +54,8 @@ func (s *ChallSrv) cnameAnswers(q dns.Question) []dns.RR { + } + + // No mock data - check if we should forward to real DNS +- if s.useRealDNS && s.realDNSForwarder != nil { ++ // Skip forwarding for internal domains like .consul ++ if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("CNAME query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { +@@ -92,7 +94,8 @@ func (s *ChallSrv) txtAnswers(q dns.Question) []dns.RR { + } + + // No mock data - check if we should forward to real DNS +- if s.useRealDNS && s.realDNSForwarder != nil { ++ // Skip forwarding for internal domains like .consul ++ if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("TXT query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { +@@ -142,7 +145,8 @@ func (s *ChallSrv) aAnswers(q dns.Question) []dns.RR { + } + + // No mock data - check if we should forward to real DNS +- if s.useRealDNS && s.realDNSForwarder != nil { ++ // Skip forwarding for internal domains like .consul ++ if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("A query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { +@@ -208,7 +212,8 @@ func (s *ChallSrv) aaaaAnswers(q dns.Question) []dns.RR { + } + + // No mock data - check if we should forward to real DNS +- if s.useRealDNS && s.realDNSForwarder != nil { ++ // Skip forwarding for internal domains like .consul ++ if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("AAAA query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { +@@ -269,7 +274,8 @@ func (s *ChallSrv) caaAnswers(q dns.Question) []dns.RR { + } + + // No mock data - check if we should forward to real DNS +- if s.useRealDNS && s.realDNSForwarder != nil { ++ // Skip forwarding for internal domains like .consul ++ if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("CAA query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { diff --git a/docker-compose.yml b/docker-compose.yml index 9bf8fd1840f..0ce96f01f5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,11 @@ services: # to the IP address where your ACME client's solver is listening. This is # pointing at the boulder service's "public" IP, where challtestsrv is. FAKE_DNS: 64.112.117.122 + # Set to "true" to forward DNS queries to real upstream DNS servers instead of fake responses + # When enabled, mock data from tests will still take precedence over real DNS + USE_REAL_DNS: "true" + # Comma-separated list of upstream DNS servers to use when USE_REAL_DNS is enabled + UPSTREAM_DNS_SERVERS: "8.8.8.8:53,1.1.1.1:53" BOULDER_CONFIG_DIR: test/config GOCACHE: /boulder/.gocache/go-build volumes: @@ -130,11 +135,11 @@ services: bconsul: image: hashicorp/consul:1.19.1 volumes: - - ./test/:/test/:cached + - ./test/:/test/:cached networks: bouldernet: ipv4_address: 10.77.77.10 - command: "consul agent -dev -config-format=hcl -config-file=/test/consul/config.hcl" + command: "consul agent -server -bootstrap-expect=1 -ui -config-format=hcl -config-file=/test/consul/config.hcl" bjaeger: image: jaegertracing/all-in-one:1.50 diff --git a/test/chall-test-srv/main.go b/test/chall-test-srv/main.go index 41241be523e..c3a86d7fdf1 100644 --- a/test/chall-test-srv/main.go +++ b/test/chall-test-srv/main.go @@ -72,6 +72,10 @@ func main() { "Default IPv4 address for mock DNS responses to A queries") defaultIPv6 := flag.String("defaultIPv6", "::1", "Default IPv6 address for mock DNS responses to AAAA queries") + useRealDNS := flag.Bool("use-real-dns", false, + "Forward DNS queries to real DNS servers instead of returning fake responses") + upstreamDNS := flag.String("upstream-dns", "8.8.8.8:53,1.1.1.1:53", + "Comma separated list of upstream DNS servers to use when use-real-dns is enabled") flag.Parse() @@ -89,16 +93,20 @@ func main() { logger := log.New(os.Stdout, "chall-test-srv - ", log.Ldate|log.Ltime) + upstreamServers := filterEmpty(strings.Split(*upstreamDNS, ",")) + // Create a new challenge server with the provided config srv, err := challtestsrv.New(challtestsrv.Config{ - HTTPOneAddrs: httpOneAddresses, - HTTPSOneAddrs: httpsOneAddresses, - DOHAddrs: dohAddresses, - DOHCert: *dohCert, - DOHCertKey: *dohCertKey, - DNSOneAddrs: dnsOneAddresses, - TLSALPNOneAddrs: tlsAlpnOneAddresses, - Log: logger, + HTTPOneAddrs: httpOneAddresses, + HTTPSOneAddrs: httpsOneAddresses, + DOHAddrs: dohAddresses, + DOHCert: *dohCert, + DOHCertKey: *dohCertKey, + DNSOneAddrs: dnsOneAddresses, + TLSALPNOneAddrs: tlsAlpnOneAddresses, + Log: logger, + UseRealDNS: *useRealDNS, + UpstreamDNSServers: upstreamServers, }) cmd.FailOnError(err, "Unable to construct challenge server") @@ -148,6 +156,9 @@ func main() { logger.Printf("Answering AAAA queries with %s by default", *defaultIPv6) } + + srv.SetDefaultDNSIPv4(*defaultIPv4) + srv.SetDefaultDNSIPv6(*defaultIPv6) } if *tlsAlpnOneBind != "" { http.HandleFunc("/add-tlsalpn01", oobSrv.addTLSALPN01) diff --git a/test/config/remoteva-a.json b/test/config/remoteva-a.json index 2ace42df439..7a99cc69bf7 100644 --- a/test/config/remoteva-a.json +++ b/test/config/remoteva-a.json @@ -1,55 +1,49 @@ { - "rva": { - "userAgent": "remoteva-a", - "debugAddr": ":8211", - "dnsTries": 3, - "dnsProvider": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "doh", - "domain": "service.consul" - } - }, - "dnsTimeout": "1s", - "issuerDomain": "happy-hacker-ca.invalid", - "tls": { - "caCertfile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/rva.boulder/cert.pem", - "keyFile": "test/certs/ipki/rva.boulder/key.pem" - }, - "grpc": { - "maxConnectionAge": "30s", - "address": ":9897", - "services": { - "va.VA": { - "clientNames": [ - "va.boulder" - ] - }, - "va.CAA": { - "clientNames": [ - "va.boulder" - ] - }, - "grpc.health.v1.Health": { - "clientNames": [ - "health-checker.boulder" - ] - } - } - }, - "features": { - "DOH": true - }, - "accountURIPrefixes": [ - "http://boulder.service.consul:4000/acme/reg/", - "http://boulder.service.consul:4001/acme/acct/" - ], - "perspective": "dadaist", - "rir": "ARIN" - }, - "syslog": { - "stdoutlevel": 4, - "sysloglevel": 4 - } + "rva": { + "userAgent": "remoteva-a", + "debugAddr": ":8211", + "dnsTries": 3, + "dnsProvider": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "doh", + "domain": "service.consul" + } + }, + "dnsTimeout": "1s", + "issuerDomain": "happy-hacker-ca.invalid", + "tls": { + "caCertfile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/rva.boulder/cert.pem", + "keyFile": "test/certs/ipki/rva.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "address": ":9897", + "services": { + "va.VA": { + "clientNames": ["va.boulder"] + }, + "va.CAA": { + "clientNames": ["va.boulder"] + }, + "grpc.health.v1.Health": { + "clientNames": ["health-checker.boulder"] + } + } + }, + "features": { + "DOH": true + }, + "accountURIPrefixes": [ + "http://boulder.service.consul:4000/acme/reg/", + "http://boulder.service.consul:4001/acme/acct/" + ], + "perspective": "dadaist", + "rir": "ARIN" + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/config/remoteva-b.json b/test/config/remoteva-b.json index 171b8534ad9..f0063c55308 100644 --- a/test/config/remoteva-b.json +++ b/test/config/remoteva-b.json @@ -1,55 +1,49 @@ { - "rva": { - "userAgent": "remoteva-b", - "debugAddr": ":8212", - "dnsTries": 3, - "dnsProvider": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "doh", - "domain": "service.consul" - } - }, - "dnsTimeout": "1s", - "issuerDomain": "happy-hacker-ca.invalid", - "tls": { - "caCertfile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/rva.boulder/cert.pem", - "keyFile": "test/certs/ipki/rva.boulder/key.pem" - }, - "grpc": { - "maxConnectionAge": "30s", - "address": ":9998", - "services": { - "va.VA": { - "clientNames": [ - "va.boulder" - ] - }, - "va.CAA": { - "clientNames": [ - "va.boulder" - ] - }, - "grpc.health.v1.Health": { - "clientNames": [ - "health-checker.boulder" - ] - } - } - }, - "features": { - "DOH": true - }, - "accountURIPrefixes": [ - "http://boulder.service.consul:4000/acme/reg/", - "http://boulder.service.consul:4001/acme/acct/" - ], - "perspective": "surrealist", - "rir": "RIPE" - }, - "syslog": { - "stdoutlevel": 4, - "sysloglevel": 4 - } + "rva": { + "userAgent": "remoteva-b", + "debugAddr": ":8212", + "dnsTries": 3, + "dnsProvider": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "doh", + "domain": "service.consul" + } + }, + "dnsTimeout": "1s", + "issuerDomain": "happy-hacker-ca.invalid", + "tls": { + "caCertfile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/rva.boulder/cert.pem", + "keyFile": "test/certs/ipki/rva.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "address": ":9998", + "services": { + "va.VA": { + "clientNames": ["va.boulder"] + }, + "va.CAA": { + "clientNames": ["va.boulder"] + }, + "grpc.health.v1.Health": { + "clientNames": ["health-checker.boulder"] + } + } + }, + "features": { + "DOH": true + }, + "accountURIPrefixes": [ + "http://boulder.service.consul:4000/acme/reg/", + "http://boulder.service.consul:4001/acme/acct/" + ], + "perspective": "surrealist", + "rir": "RIPE" + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/config/remoteva-c.json b/test/config/remoteva-c.json index 22c168b662c..9893c7215c7 100644 --- a/test/config/remoteva-c.json +++ b/test/config/remoteva-c.json @@ -1,55 +1,49 @@ { - "rva": { - "userAgent": "remoteva-c", - "debugAddr": ":8213", - "dnsTries": 3, - "dnsProvider": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "doh", - "domain": "service.consul" - } - }, - "dnsTimeout": "1s", - "issuerDomain": "happy-hacker-ca.invalid", - "tls": { - "caCertfile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/rva.boulder/cert.pem", - "keyFile": "test/certs/ipki/rva.boulder/key.pem" - }, - "grpc": { - "maxConnectionAge": "30s", - "address": ":9899", - "services": { - "va.VA": { - "clientNames": [ - "va.boulder" - ] - }, - "va.CAA": { - "clientNames": [ - "va.boulder" - ] - }, - "grpc.health.v1.Health": { - "clientNames": [ - "health-checker.boulder" - ] - } - } - }, - "features": { - "DOH": true - }, - "accountURIPrefixes": [ - "http://boulder.service.consul:4000/acme/reg/", - "http://boulder.service.consul:4001/acme/acct/" - ], - "perspective": "cubist", - "rir": "ARIN" - }, - "syslog": { - "stdoutlevel": 4, - "sysloglevel": 4 - } + "rva": { + "userAgent": "remoteva-c", + "debugAddr": ":8213", + "dnsTries": 3, + "dnsProvider": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "doh", + "domain": "service.consul" + } + }, + "dnsTimeout": "1s", + "issuerDomain": "happy-hacker-ca.invalid", + "tls": { + "caCertfile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/rva.boulder/cert.pem", + "keyFile": "test/certs/ipki/rva.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "address": ":9899", + "services": { + "va.VA": { + "clientNames": ["va.boulder"] + }, + "va.CAA": { + "clientNames": ["va.boulder"] + }, + "grpc.health.v1.Health": { + "clientNames": ["health-checker.boulder"] + } + } + }, + "features": { + "DOH": true + }, + "accountURIPrefixes": [ + "http://boulder.service.consul:4000/acme/reg/", + "http://boulder.service.consul:4001/acme/acct/" + ], + "perspective": "cubist", + "rir": "ARIN" + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/config/sa.json b/test/config/sa.json index c3fcde2cb3d..7e5dd46e18f 100644 --- a/test/config/sa.json +++ b/test/config/sa.json @@ -1,60 +1,53 @@ { - "sa": { - "db": { - "dbConnectFile": "test/secrets/sa_dburl", - "maxOpenConns": 100 - }, - "readOnlyDB": { - "dbConnectFile": "test/secrets/sa_ro_dburl", - "maxOpenConns": 100 - }, - "incidentsDB": { - "dbConnectFile": "test/secrets/incidents_dburl", - "maxOpenConns": 100 - }, - "ParallelismPerRPC": 20, - "debugAddr": ":8003", - "lagFactor": "200ms", - "tls": { - "caCertFile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/sa.boulder/cert.pem", - "keyFile": "test/certs/ipki/sa.boulder/key.pem" - }, - "grpc": { - "maxConnectionAge": "30s", - "address": ":9095", - "services": { - "sa.StorageAuthority": { - "clientNames": [ - "admin.boulder", - "ca.boulder", - "crl-updater.boulder", - "ra.boulder" - ] - }, - "sa.StorageAuthorityReadOnly": { - "clientNames": [ - "admin.boulder", - "wfe.boulder", - "sfe.boulder" - ] - }, - "grpc.health.v1.Health": { - "clientNames": [ - "health-checker.boulder", - "consul.boulder" - ] - } - } - }, - "features": { - "MultipleCertificateProfiles": true, - "InsertAuthzsIndividually": true, - "IgnoreAccountContacts": true - } - }, - "syslog": { - "stdoutlevel": 3, - "sysloglevel": 3 - } + "sa": { + "db": { + "dbConnectFile": "test/secrets/sa_dburl", + "maxOpenConns": 100 + }, + "readOnlyDB": { + "dbConnectFile": "test/secrets/sa_ro_dburl", + "maxOpenConns": 100 + }, + "incidentsDB": { + "dbConnectFile": "test/secrets/incidents_dburl", + "maxOpenConns": 100 + }, + "ParallelismPerRPC": 20, + "debugAddr": ":8003", + "lagFactor": "200ms", + "tls": { + "caCertFile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/sa.boulder/cert.pem", + "keyFile": "test/certs/ipki/sa.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "address": ":9095", + "services": { + "sa.StorageAuthority": { + "clientNames": [ + "admin.boulder", + "ca.boulder", + "crl-updater.boulder", + "ra.boulder" + ] + }, + "sa.StorageAuthorityReadOnly": { + "clientNames": ["admin.boulder", "wfe.boulder", "sfe.boulder"] + }, + "grpc.health.v1.Health": { + "clientNames": ["health-checker.boulder", "consul.boulder"] + } + } + }, + "features": { + "MultipleCertificateProfiles": true, + "InsertAuthzsIndividually": true, + "IgnoreAccountContacts": true + } + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/config/va.json b/test/config/va.json index 3823e3cba95..7eb238183da 100644 --- a/test/config/va.json +++ b/test/config/va.json @@ -1,75 +1,69 @@ { - "va": { - "userAgent": "boulder", - "debugAddr": ":8004", - "dnsTries": 3, - "dnsProvider": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "doh", - "domain": "service.consul" - } - }, - "dnsTimeout": "1s", - "issuerDomain": "happy-hacker-ca.invalid", - "tls": { - "caCertfile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/va.boulder/cert.pem", - "keyFile": "test/certs/ipki/va.boulder/key.pem" - }, - "grpc": { - "maxConnectionAge": "30s", - "services": { - "va.VA": { - "clientNames": [ - "ra.boulder" - ] - }, - "va.CAA": { - "clientNames": [ - "ra.boulder" - ] - }, - "grpc.health.v1.Health": { - "clientNames": [ - "health-checker.boulder" - ] - } - } - }, - "features": { - "DOH": true - }, - "remoteVAs": [ - { - "serverAddress": "rva1.service.consul:9397", - "timeout": "15s", - "hostOverride": "rva1.boulder", - "perspective": "dadaist", - "rir": "ARIN" - }, - { - "serverAddress": "rva1.service.consul:9498", - "timeout": "15s", - "hostOverride": "rva1.boulder", - "perspective": "surrealist", - "rir": "RIPE" - }, - { - "serverAddress": "rva1.service.consul:9499", - "timeout": "15s", - "hostOverride": "rva1.boulder", - "perspective": "cubist", - "rir": "ARIN" - } - ], - "accountURIPrefixes": [ - "http://boulder.service.consul:4000/acme/reg/", - "http://boulder.service.consul:4001/acme/acct/" - ] - }, - "syslog": { - "stdoutlevel": 3, - "sysloglevel": 3 - } + "va": { + "userAgent": "boulder", + "debugAddr": ":8004", + "dnsTries": 3, + "dnsProvider": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "doh", + "domain": "service.consul" + } + }, + "dnsTimeout": "1s", + "issuerDomain": "happy-hacker-ca.invalid", + "tls": { + "caCertfile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/va.boulder/cert.pem", + "keyFile": "test/certs/ipki/va.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "services": { + "va.VA": { + "clientNames": ["ra.boulder"] + }, + "va.CAA": { + "clientNames": ["ra.boulder"] + }, + "grpc.health.v1.Health": { + "clientNames": ["health-checker.boulder"] + } + } + }, + "features": { + "DOH": true + }, + "remoteVAs": [ + { + "serverAddress": "rva1.service.consul:9397", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "dadaist", + "rir": "ARIN" + }, + { + "serverAddress": "rva1.service.consul:9498", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "surrealist", + "rir": "RIPE" + }, + { + "serverAddress": "rva1.service.consul:9499", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "cubist", + "rir": "ARIN" + } + ], + "accountURIPrefixes": [ + "http://boulder.service.consul:4000/acme/reg/", + "http://boulder.service.consul:4001/acme/acct/" + ] + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/config/va.json.backup b/test/config/va.json.backup new file mode 100644 index 00000000000..f1f9e468d06 --- /dev/null +++ b/test/config/va.json.backup @@ -0,0 +1,75 @@ +{ + "va": { + "userAgent": "boulder", + "debugAddr": ":8004", + "dnsTries": 3, + "dnsProvider": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "dns", + "domain": "service.consul" + } + }, + "dnsTimeout": "5s", + "issuerDomain": "happy-hacker-ca.invalid", + "tls": { + "caCertfile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/va.boulder/cert.pem", + "keyFile": "test/certs/ipki/va.boulder/key.pem" + }, + "grpc": { + "maxConnectionAge": "30s", + "services": { + "va.VA": { + "clientNames": [ + "ra.boulder" + ] + }, + "va.CAA": { + "clientNames": [ + "ra.boulder" + ] + }, + "grpc.health.v1.Health": { + "clientNames": [ + "health-checker.boulder" + ] + } + } + }, + "features": { + "DOH": true + }, + "remoteVAs": [ + { + "serverAddress": "rva1.service.consul:9397", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "dadaist", + "rir": "ARIN" + }, + { + "serverAddress": "rva1.service.consul:9498", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "surrealist", + "rir": "RIPE" + }, + { + "serverAddress": "rva1.service.consul:9499", + "timeout": "15s", + "hostOverride": "rva1.boulder", + "perspective": "cubist", + "rir": "ARIN" + } + ], + "accountURIPrefixes": [ + "http://boulder.service.consul:4000/acme/reg/", + "http://boulder.service.consul:4001/acme/acct/" + ] + }, + "syslog": { + "stdoutlevel": 3, + "sysloglevel": 3 + } +} diff --git a/test/config/wfe2.json b/test/config/wfe2.json index 3948c158cb0..f9684d0d23c 100644 --- a/test/config/wfe2.json +++ b/test/config/wfe2.json @@ -1,149 +1,147 @@ { - "wfe": { - "timeout": "30s", - "listenAddress": "0.0.0.0:4001", - "TLSListenAddress": "0.0.0.0:4431", - "serverCertificatePath": "test/certs/ipki/boulder/cert.pem", - "serverKeyPath": "test/certs/ipki/boulder/key.pem", - "allowOrigins": [ - "*" - ], - "shutdownStopTimeout": "10s", - "subscriberAgreementURL": "https://boulder.service.consul:4431/terms/v7", - "debugAddr": ":8013", - "directoryCAAIdentity": "happy-hacker-ca.invalid", - "directoryWebsite": "https://github.com/letsencrypt/boulder", - "legacyKeyIDPrefix": "http://boulder.service.consul:4000/reg/", - "goodkey": {}, - "tls": { - "caCertFile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/wfe.boulder/cert.pem", - "keyFile": "test/certs/ipki/wfe.boulder/key.pem" - }, - "raService": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "ra", - "domain": "service.consul" - }, - "timeout": "15s", - "noWaitForReady": true, - "hostOverride": "ra.boulder" - }, - "saService": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "sa", - "domain": "service.consul" - }, - "timeout": "15s", - "noWaitForReady": true, - "hostOverride": "sa.boulder" - }, - "accountCache": { - "size": 9000, - "ttl": "5s" - }, - "getNonceService": { - "dnsAuthority": "consul.service.consul", - "srvLookup": { - "service": "nonce-taro", - "domain": "service.consul" - }, - "timeout": "15s", - "noWaitForReady": true, - "hostOverride": "nonce.boulder" - }, - "redeemNonceService": { - "dnsAuthority": "consul.service.consul", - "srvLookups": [ - { - "service": "nonce-taro", - "domain": "service.consul" - }, - { - "service": "nonce-zinc", - "domain": "service.consul" - } - ], - "srvResolver": "nonce-srv", - "timeout": "15s", - "noWaitForReady": true, - "hostOverride": "nonce.boulder" - }, - "nonceHMACKey": { - "keyFile": "test/secrets/nonce_prefix_key" - }, - "chains": [ - [ - "test/certs/webpki/int-rsa-a.cert.pem", - "test/certs/webpki/root-rsa.cert.pem" - ], - [ - "test/certs/webpki/int-rsa-b.cert.pem", - "test/certs/webpki/root-rsa.cert.pem" - ], - [ - "test/certs/webpki/int-ecdsa-a.cert.pem", - "test/certs/webpki/root-ecdsa.cert.pem" - ], - [ - "test/certs/webpki/int-ecdsa-b.cert.pem", - "test/certs/webpki/root-ecdsa.cert.pem" - ], - [ - "test/certs/webpki/int-ecdsa-a-cross.cert.pem", - "test/certs/webpki/root-rsa.cert.pem" - ], - [ - "test/certs/webpki/int-ecdsa-b-cross.cert.pem", - "test/certs/webpki/root-rsa.cert.pem" - ] - ], - "staleTimeout": "5m", - "limiter": { - "redis": { - "username": "boulder", - "passwordFile": "test/secrets/redis_password", - "lookups": [ - { - "Service": "redisratelimits", - "Domain": "service.consul" - } - ], - "lookupDNSAuthority": "consul.service.consul", - "readTimeout": "250ms", - "writeTimeout": "250ms", - "poolSize": 100, - "routeRandomly": true, - "tls": { - "caCertFile": "test/certs/ipki/minica.pem", - "certFile": "test/certs/ipki/wfe.boulder/cert.pem", - "keyFile": "test/certs/ipki/wfe.boulder/key.pem" - } - }, - "Defaults": "test/config/ratelimit-defaults.yml", - "Overrides": "test/config/ratelimit-overrides.yml" - }, - "features": { - "ServeRenewalInfo": true, - "CheckIdentifiersPaused": true - }, - "certProfiles": { - "legacy": "The normal profile you know and love", - "modern": "Profile 2: Electric Boogaloo", - "shortlived": "Like modern, but smaller" - }, - "unpause": { - "hmacKey": { - "keyFile": "test/secrets/sfe_unpause_key" - }, - "jwtLifetime": "336h", - "url": "https://boulder.service.consul:4003" - } - }, - "syslog": { - "stdoutlevel": 3, - "sysloglevel": 3 - } + "wfe": { + "timeout": "30s", + "listenAddress": "0.0.0.0:4001", + "TLSListenAddress": "0.0.0.0:4431", + "serverCertificatePath": "test/certs/ipki/boulder/cert.pem", + "serverKeyPath": "test/certs/ipki/boulder/key.pem", + "allowOrigins": ["*"], + "shutdownStopTimeout": "10s", + "subscriberAgreementURL": "https://boulder.service.consul:4431/terms/v7", + "debugAddr": ":8013", + "directoryCAAIdentity": "happy-hacker-ca.invalid", + "directoryWebsite": "https://github.com/letsencrypt/boulder", + "legacyKeyIDPrefix": "http://boulder.service.consul:4000/reg/", + "goodkey": {}, + "tls": { + "caCertFile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/wfe.boulder/cert.pem", + "keyFile": "test/certs/ipki/wfe.boulder/key.pem" + }, + "raService": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "ra", + "domain": "service.consul" + }, + "timeout": "15s", + "noWaitForReady": true, + "hostOverride": "ra.boulder" + }, + "saService": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "sa", + "domain": "service.consul" + }, + "timeout": "15s", + "noWaitForReady": true, + "hostOverride": "sa.boulder" + }, + "accountCache": { + "size": 9000, + "ttl": "5s" + }, + "getNonceService": { + "dnsAuthority": "consul.service.consul", + "srvLookup": { + "service": "nonce-taro", + "domain": "service.consul" + }, + "timeout": "15s", + "noWaitForReady": true, + "hostOverride": "nonce.boulder" + }, + "redeemNonceService": { + "dnsAuthority": "consul.service.consul", + "srvLookups": [ + { + "service": "nonce-taro", + "domain": "service.consul" + }, + { + "service": "nonce-zinc", + "domain": "service.consul" + } + ], + "srvResolver": "nonce-srv", + "timeout": "15s", + "noWaitForReady": true, + "hostOverride": "nonce.boulder" + }, + "nonceHMACKey": { + "keyFile": "test/secrets/nonce_prefix_key" + }, + "chains": [ + [ + "test/certs/webpki/int-rsa-a.cert.pem", + "test/certs/webpki/root-rsa.cert.pem" + ], + [ + "test/certs/webpki/int-rsa-b.cert.pem", + "test/certs/webpki/root-rsa.cert.pem" + ], + [ + "test/certs/webpki/int-ecdsa-a.cert.pem", + "test/certs/webpki/root-ecdsa.cert.pem" + ], + [ + "test/certs/webpki/int-ecdsa-b.cert.pem", + "test/certs/webpki/root-ecdsa.cert.pem" + ], + [ + "test/certs/webpki/int-ecdsa-a-cross.cert.pem", + "test/certs/webpki/root-rsa.cert.pem" + ], + [ + "test/certs/webpki/int-ecdsa-b-cross.cert.pem", + "test/certs/webpki/root-rsa.cert.pem" + ] + ], + "staleTimeout": "5m", + "limiter": { + "redis": { + "username": "boulder", + "passwordFile": "test/secrets/redis_password", + "lookups": [ + { + "Service": "redisratelimits", + "Domain": "service.consul" + } + ], + "lookupDNSAuthority": "consul.service.consul", + "readTimeout": "250ms", + "writeTimeout": "250ms", + "poolSize": 100, + "routeRandomly": true, + "tls": { + "caCertFile": "test/certs/ipki/minica.pem", + "certFile": "test/certs/ipki/wfe.boulder/cert.pem", + "keyFile": "test/certs/ipki/wfe.boulder/key.pem" + } + }, + "Defaults": "test/config/ratelimit-defaults.yml", + "Overrides": "test/config/ratelimit-overrides.yml" + }, + "features": { + "ServeRenewalInfo": true, + "CheckIdentifiersPaused": true + }, + "certProfiles": { + "legacy": "The normal profile you know and love", + "modern": "Profile 2: Electric Boogaloo", + "shortlived": "Like modern, but smaller" + }, + "unpause": { + "hmacKey": { + "keyFile": "test/secrets/sfe_unpause_key" + }, + "jwtLifetime": "336h", + "url": "https://boulder.service.consul:4003" + } + }, + "syslog": { + "stdoutlevel": 7, + "sysloglevel": 7 + } } diff --git a/test/consul/config-advanced-dns.hcl b/test/consul/config-advanced-dns.hcl new file mode 100644 index 00000000000..cc2fc3341b3 --- /dev/null +++ b/test/consul/config-advanced-dns.hcl @@ -0,0 +1,344 @@ +# Расширенная конфигурация Consul с продвинутыми настройками DNS +# Этот файл демонстрирует все возможности интеграции Consul с внешним DNS + +client_addr = "0.0.0.0" +bind_addr = "10.77.77.10" +log_level = "INFO" + +# ============================================================================ +# DNS RECURSORS - Внешние DNS серверы для запросов вне Consul +# ============================================================================ + +# Список внешних DNS серверов, на которые Consul будет перенаправлять запросы, +# которые он не может разрешить самостоятельно (не *.consul домены) +recursors = [ + "8.8.8.8", # Google Public DNS Primary + "8.8.4.4", # Google Public DNS Secondary + "1.1.1.1", # Cloudflare DNS Primary + "1.0.0.1" # Cloudflare DNS Secondary +] + +# ============================================================================ +# DNS CONFIGURATION - Детальная настройка DNS поведения +# ============================================================================ + +dns_config { + # ------------------------------------------------------------------------- + # КЭШИРОВАНИЕ И ПРОИЗВОДИТЕЛЬНОСТЬ + # ------------------------------------------------------------------------- + + # Разрешить использование устаревших (stale) данных из кэша. + # Это значительно повышает производительность и доступность DNS, + # позволяя отвечать на запросы даже если leader недоступен. + allow_stale = true + + # Максимальное время, в течение которого могут использоваться устаревшие данные + # 87600h = 10 лет (практически безлимитно для dev окружения) + # Для production рекомендуется: "5m" - "1h" + max_stale = "87600h" + + # ------------------------------------------------------------------------- + # TTL (TIME TO LIVE) НАСТРОЙКИ + # ------------------------------------------------------------------------- + + # TTL для записей узлов (node records) + # 0 = без кэширования, клиенты всегда будут запрашивать свежие данные + # Для production: "30s" - "5m" + node_ttl = "0s" + + # TTL для записей сервисов - можно настроить индивидуально для каждого сервиса + service_ttl = { + # Дефолтное значение для всех сервисов + "*" = "5s" + + # Критичные сервисы с частыми изменениями - короткий TTL + "sa" = "3s" + "ra" = "3s" + "va" = "3s" + + # Стабильные сервисы - более длинный TTL + "redis" = "30s" + "redisratelimits" = "30s" + + # Инфраструктурные сервисы + "dns" = "60s" + "consul" = "60s" + } + + # ------------------------------------------------------------------------- + # RECURSOR НАСТРОЙКИ + # ------------------------------------------------------------------------- + + # Таймаут для запросов к внешним DNS серверам (recursors) + # Если внешний DNS не отвечает за это время, Consul попробует следующий + recursor_timeout = "2s" + + # Стратегия выбора recursor из списка: + # - "sequential" - по порядку (fallback) + # - "random" - случайный выбор (load balancing) + recursor_strategy = "random" + + # ------------------------------------------------------------------------- + # ФИЛЬТРАЦИЯ И БЕЗОПАСНОСТЬ + # ------------------------------------------------------------------------- + + # Возвращать только сервисы с passing health checks + # false - вернет все сервисы, включая failing (полезно для debug) + # true - только healthy сервисы (для production) + only_passing = false + + # Включить DNS64 (IPv6 to IPv4 translation) - экспериментально + # enable_dns64 = false + + # ------------------------------------------------------------------------- + # ОПТИМИЗАЦИЯ РАЗМЕРА ОТВЕТОВ + # ------------------------------------------------------------------------- + + # Включить флаг TC (truncated) в DNS ответах, превышающих размер UDP пакета + # Клиент должен будет повторить запрос через TCP для получения полного ответа + enable_truncate = false + + # Максимальное количество записей в UDP ответе (для предотвращения truncation) + # 0 = без ограничений + # Для множества инстансов сервиса рекомендуется 3-5 + udp_answer_limit = 3 + + # Максимальное количество A/AAAA записей в ответе + # 0 = без ограничений + # Ограничение помогает избежать больших UDP пакетов + a_record_limit = 5 + + # Отключить DNS компрессию (обычно не требуется) + disable_compression = false + + # ------------------------------------------------------------------------- + # ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ + # ------------------------------------------------------------------------- + + # Использовать альтернативный домен вместо .consul + # domain = "consul" + + # Разрешить PTR запросы для обратного разрешения IP -> имя + # enable_ptr = true + + # Порядок предпочтения записей в DNS ответах + # - "name" - алфавитный порядок + # - "random" - случайный порядок (load balancing) + # node_meta_txt = ["*"] + + # Использовать RFC 2782 стиль для SRV записей + # soa { + # expire = 86400 + # min_ttl = 0 + # refresh = 3600 + # retry = 600 + # } +} + +# ============================================================================ +# PORTS CONFIGURATION +# ============================================================================ + +ports { + # DNS порт - стандартный порт 53 + # Можно использовать альтернативный порт, например 8600 (по умолчанию для Consul) + dns = 53 + + # HTTP API порт + http = 8500 + + # gRPC порт с TLS + grpc_tls = 8503 + + # Serf LAN порт (для communication между агентами) + # serf_lan = 8301 + + # Serf WAN порт (для communication между datacenters) + # serf_wan = 8302 + + # Server RPC порт + # server = 8300 +} + +# ============================================================================ +# TLS CONFIGURATION +# ============================================================================ + +# Использовать TLS для health checks (требуется для Boulder) +enable_agent_tls_for_checks = true + +tls { + defaults { + ca_file = "test/certs/ipki/minica.pem" + ca_path = "test/certs/ipki/minica-key.pem" + cert_file = "test/certs/ipki/consul.boulder/cert.pem" + key_file = "test/certs/ipki/consul.boulder/key.pem" + verify_incoming = false + verify_outgoing = true + + # Минимальная версия TLS + tls_min_version = "TLSv1_2" + + # Разрешенные cipher suites + # tls_cipher_suites = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" + } +} + +# ============================================================================ +# UI CONFIGURATION +# ============================================================================ + +ui_config { + enabled = true + + # Дополнительные метрики в UI + metrics_provider = "prometheus" + # metrics_proxy { + # base_url = "http://prometheus:9090" + # } +} + +# ============================================================================ +# PERFORMANCE TUNING +# ============================================================================ + +performance { + # Количество одновременных RPC запросов + # raft_multiplier = 1 + + # Настройки для DNS + # leave_drain_time = "5s" + # rpc_hold_timeout = "7s" +} + +# ============================================================================ +# TELEMETRY +# ============================================================================ + +telemetry { + # Отключить hostname prefix в метриках + disable_hostname = false + + # Prometheus метрики + prometheus_retention_time = "60s" + + # Dogstatsd + # dogstatsd_addr = "localhost:8125" + # dogstatsd_tags = ["environment:dev"] + + # Statsd + # statsd_address = "localhost:8125" +} + +# ============================================================================ +# LOGGING +# ============================================================================ + +# Уровень логирования: TRACE, DEBUG, INFO, WARN, ERROR +log_level = "INFO" + +# Формат логов: "standard" или "json" +# log_json = false + +# Файл для логов (опционально) +# log_file = "/var/log/consul/consul.log" + +# Ротация логов +# log_rotate_duration = "24h" +# log_rotate_max_files = 7 + +# ============================================================================ +# SERVICES CONFIGURATION +# ============================================================================ + +# Примеры сервисов с health checks и DNS-specific настройками + +services { + id = "sa-a" + name = "sa" + address = "10.77.77.77" + port = 9395 + tags = ["tcp", "primary", "datacenter-1"] + + # Метаданные для фильтрации и routing + meta = { + version = "v1" + region = "us-west" + } + + # Health checks с разными методами + checks = [ + { + id = "sa-a-grpc" + name = "sa-a-grpc" + grpc = "10.77.77.77:9395" + grpc_use_tls = true + tls_server_name = "sa.boulder" + tls_skip_verify = false + interval = "10s" + timeout = "5s" + }, + { + id = "sa-a-grpc-sa" + name = "sa-a-grpc-sa" + grpc = "10.77.77.77:9395/sa.StorageAuthority" + grpc_use_tls = true + tls_server_name = "sa.boulder" + tls_skip_verify = false + interval = "10s" + timeout = "5s" + } + ] + + # DNS-specific TTL для этого конкретного инстанса + # service_ttl = "5s" +} + +# ============================================================================ +# ДОПОЛНИТЕЛЬНЫЕ ОПЦИИ +# ============================================================================ + +# Datacenter имя (по умолчанию: dc1) +# datacenter = "dc1" + +# Node имя (по умолчанию: hostname) +# node_name = "consul-server-1" + +# Включить локальный script checks (security risk!) +# enable_local_script_checks = false + +# Включить удаленные script checks через HTTP API (security risk!) +# enable_script_checks = false + +# Автоматически покинуть кластер при остановке +# leave_on_terminate = true + +# Rejoin кластер после рестарта +# rejoin_after_leave = true + +# Disable update check +# disable_update_check = true + +# ============================================================================ +# ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ +# ============================================================================ + +# После применения этой конфигурации: +# +# 1. Запросы Consul сервисов (автоматическое разрешение): +# dig @10.77.77.10 sa.service.consul +# dig @10.77.77.10 -t SRV _sa._tcp.service.consul +# +# 2. Запросы внешних доменов (через recursors): +# dig @10.77.77.10 google.com +# dig @10.77.77.10 -t MX gmail.com +# +# 3. Фильтрация по тегам: +# dig @10.77.77.10 primary.sa.service.consul +# +# 4. Проверка конфигурации: +# consul validate /test/consul/config-advanced-dns.hcl +# curl http://10.77.77.10:8500/v1/agent/self | jq .DebugConfig.DNSRecursors +# +# 5. Мониторинг DNS метрик: +# curl http://10.77.77.10:8500/v1/agent/metrics?format=prometheus diff --git a/test/consul/config.hcl b/test/consul/config.hcl index a75263bdfea..d1fbeb2358b 100644 --- a/test/consul/config.hcl +++ b/test/consul/config.hcl @@ -2,7 +2,23 @@ client_addr = "0.0.0.0" bind_addr = "10.77.77.10" -log_level = "ERROR" +log_level = "DEBUG" + +# Data directory for Consul state storage +data_dir = "/tmp/consul" + +# External DNS servers for recursive queries that Consul cannot resolve +recursors = ["8.8.8.8", "8.8.4.4"] + +# DNS configuration +dns_config { + # Allow stale reads for better performance + allow_stale = true + # Maximum staleness time for cached data + max_stale = "87600h" + # Timeout for external DNS recursor queries + recursor_timeout = "5s" +} // When set, uses a subset of the agent's TLS configuration (key_file, // cert_file, ca_file, ca_path, and server_name) to set up the client for HTTP // or gRPC health checks. This allows services requiring 2-way TLS to be checked @@ -72,7 +88,7 @@ services { services { id = "dns-a" name = "dns" - address = "8.8.8.8" + address = "10.77.77.10" port = 53 tags = ["udp"] // Required for SRV RR support in VA RVA. } @@ -80,7 +96,7 @@ services { services { id = "dns-b" name = "dns" - address = "8.8.8.8" + address = "10.77.77.10" port = 53 tags = ["udp"] // Required for SRV RR support in VA RVA. } diff --git a/test/startservers.py b/test/startservers.py index cdfcad084b5..6400280f513 100644 --- a/test/startservers.py +++ b/test/startservers.py @@ -7,76 +7,302 @@ from helpers import config_dir, waithealth, waitport -Service = collections.namedtuple('Service', ('name', 'debug_port', 'grpc_port', 'host_override', 'cmd', 'deps')) +Service = collections.namedtuple( + "Service", ("name", "debug_port", "grpc_port", "host_override", "cmd", "deps") +) # Keep these ports in sync with consul/config.hcl SERVICES = ( - Service('remoteva-a', - 8011, 9397, 'rva.boulder', - ('./bin/boulder', 'remoteva', '--config', os.path.join(config_dir, 'remoteva-a.json'), '--addr', ':9397', '--debug-addr', ':8011'), - None), - Service('remoteva-b', - 8012, 9498, 'rva.boulder', - ('./bin/boulder', 'remoteva', '--config', os.path.join(config_dir, 'remoteva-b.json'), '--addr', ':9498', '--debug-addr', ':8012'), - None), - Service('remoteva-c', - 8023, 9499, 'rva.boulder', - ('./bin/boulder', 'remoteva', '--config', os.path.join(config_dir, 'remoteva-c.json'), '--addr', ':9499', '--debug-addr', ':8023'), - None), - Service('boulder-sa-1', - 8003, 9395, 'sa.boulder', - ('./bin/boulder', 'boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', ':9395', '--debug-addr', ':8003'), - None), - Service('boulder-sa-2', - 8103, 9495, 'sa.boulder', - ('./bin/boulder', 'boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', ':9495', '--debug-addr', ':8103'), - None), - Service('aia-test-srv', - 4502, None, None, - ('./bin/aia-test-srv', '--addr', ':4502', '--hierarchy', 'test/certs/webpki/'), None), - Service('ct-test-srv', - 4600, None, None, - ('./bin/ct-test-srv', '--config', 'test/ct-test-srv/ct-test-srv.json'), None), - Service('boulder-publisher-1', - 8009, 9391, 'publisher.boulder', - ('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', ':9391', '--debug-addr', ':8009'), - None), - Service('boulder-publisher-2', - 8109, 9491, 'publisher.boulder', - ('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', ':9491', '--debug-addr', ':8109'), - None), - Service('boulder-va-1', - 8004, 9392, 'va.boulder', - ('./bin/boulder', 'boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', ':9392', '--debug-addr', ':8004'), - ('remoteva-a', 'remoteva-b')), - Service('boulder-va-2', - 8104, 9492, 'va.boulder', - ('./bin/boulder', 'boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', ':9492', '--debug-addr', ':8104'), - ('remoteva-a', 'remoteva-b')), - Service('boulder-ca-1', - 8001, 9393, 'ca.boulder', - ('./bin/boulder', 'boulder-ca', '--config', os.path.join(config_dir, 'ca.json'), '--addr', ':9393', '--debug-addr', ':8001'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-ra-sct-provider-1', 'boulder-ra-sct-provider-2')), - Service('boulder-ca-2', - 8101, 9493, 'ca.boulder', - ('./bin/boulder', 'boulder-ca', '--config', os.path.join(config_dir, 'ca.json'), '--addr', ':9493', '--debug-addr', ':8101'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-ra-sct-provider-1', 'boulder-ra-sct-provider-2')), - Service('s3-test-srv', - 4501, None, None, - ('./bin/s3-test-srv', '--listen', ':4501'), - None), - Service('crl-storer', - 9667, None, None, - ('./bin/boulder', 'crl-storer', '--config', os.path.join(config_dir, 'crl-storer.json'), '--addr', ':9309', '--debug-addr', ':9667'), - ('s3-test-srv',)), - Service('boulder-ra-1', - 8002, 9394, 'ra.boulder', - ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9394', '--debug-addr', ':8002'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-ca-1', 'boulder-ca-2', 'boulder-va-1', 'boulder-va-2', 'boulder-publisher-1', 'boulder-publisher-2')), - Service('boulder-ra-2', - 8102, 9494, 'ra.boulder', - ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9494', '--debug-addr', ':8102'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-ca-1', 'boulder-ca-2', 'boulder-va-1', 'boulder-va-2', 'boulder-publisher-1', 'boulder-publisher-2')), + Service( + "remoteva-a", + 8011, + 9397, + "rva.boulder", + ( + "./bin/boulder", + "remoteva", + "--config", + os.path.join(config_dir, "remoteva-a.json"), + "--addr", + ":9397", + "--debug-addr", + ":8011", + ), + None, + ), + Service( + "remoteva-b", + 8012, + 9498, + "rva.boulder", + ( + "./bin/boulder", + "remoteva", + "--config", + os.path.join(config_dir, "remoteva-b.json"), + "--addr", + ":9498", + "--debug-addr", + ":8012", + ), + None, + ), + Service( + "remoteva-c", + 8023, + 9499, + "rva.boulder", + ( + "./bin/boulder", + "remoteva", + "--config", + os.path.join(config_dir, "remoteva-c.json"), + "--addr", + ":9499", + "--debug-addr", + ":8023", + ), + None, + ), + Service( + "boulder-sa-1", + 8003, + 9395, + "sa.boulder", + ( + "./bin/boulder", + "boulder-sa", + "--config", + os.path.join(config_dir, "sa.json"), + "--addr", + ":9395", + "--debug-addr", + ":8003", + ), + None, + ), + Service( + "boulder-sa-2", + 8103, + 9495, + "sa.boulder", + ( + "./bin/boulder", + "boulder-sa", + "--config", + os.path.join(config_dir, "sa.json"), + "--addr", + ":9495", + "--debug-addr", + ":8103", + ), + None, + ), + Service( + "aia-test-srv", + 4502, + None, + None, + ("./bin/aia-test-srv", "--addr", ":4502", "--hierarchy", "test/certs/webpki/"), + None, + ), + Service( + "ct-test-srv", + 4600, + None, + None, + ("./bin/ct-test-srv", "--config", "test/ct-test-srv/ct-test-srv.json"), + None, + ), + Service( + "boulder-publisher-1", + 8009, + 9391, + "publisher.boulder", + ( + "./bin/boulder", + "boulder-publisher", + "--config", + os.path.join(config_dir, "publisher.json"), + "--addr", + ":9391", + "--debug-addr", + ":8009", + ), + None, + ), + Service( + "boulder-publisher-2", + 8109, + 9491, + "publisher.boulder", + ( + "./bin/boulder", + "boulder-publisher", + "--config", + os.path.join(config_dir, "publisher.json"), + "--addr", + ":9491", + "--debug-addr", + ":8109", + ), + None, + ), + Service( + "boulder-va-1", + 8004, + 9392, + "va.boulder", + ( + "./bin/boulder", + "boulder-va", + "--config", + os.path.join(config_dir, "va.json"), + "--addr", + ":9392", + "--debug-addr", + ":8004", + ), + ("remoteva-a", "remoteva-b"), + ), + Service( + "boulder-va-2", + 8104, + 9492, + "va.boulder", + ( + "./bin/boulder", + "boulder-va", + "--config", + os.path.join(config_dir, "va.json"), + "--addr", + ":9492", + "--debug-addr", + ":8104", + ), + ("remoteva-a", "remoteva-b"), + ), + Service( + "boulder-ca-1", + 8001, + 9393, + "ca.boulder", + ( + "./bin/boulder", + "boulder-ca", + "--config", + os.path.join(config_dir, "ca.json"), + "--addr", + ":9393", + "--debug-addr", + ":8001", + ), + ( + "boulder-sa-1", + "boulder-sa-2", + "boulder-ra-sct-provider-1", + "boulder-ra-sct-provider-2", + ), + ), + Service( + "boulder-ca-2", + 8101, + 9493, + "ca.boulder", + ( + "./bin/boulder", + "boulder-ca", + "--config", + os.path.join(config_dir, "ca.json"), + "--addr", + ":9493", + "--debug-addr", + ":8101", + ), + ( + "boulder-sa-1", + "boulder-sa-2", + "boulder-ra-sct-provider-1", + "boulder-ra-sct-provider-2", + ), + ), + Service( + "s3-test-srv", + 4501, + None, + None, + ("./bin/s3-test-srv", "--listen", ":4501"), + None, + ), + Service( + "crl-storer", + 9667, + None, + None, + ( + "./bin/boulder", + "crl-storer", + "--config", + os.path.join(config_dir, "crl-storer.json"), + "--addr", + ":9309", + "--debug-addr", + ":9667", + ), + ("s3-test-srv",), + ), + Service( + "boulder-ra-1", + 8002, + 9394, + "ra.boulder", + ( + "./bin/boulder", + "boulder-ra", + "--config", + os.path.join(config_dir, "ra.json"), + "--addr", + ":9394", + "--debug-addr", + ":8002", + ), + ( + "boulder-sa-1", + "boulder-sa-2", + "boulder-ca-1", + "boulder-ca-2", + "boulder-va-1", + "boulder-va-2", + "boulder-publisher-1", + "boulder-publisher-2", + ), + ), + Service( + "boulder-ra-2", + 8102, + 9494, + "ra.boulder", + ( + "./bin/boulder", + "boulder-ra", + "--config", + os.path.join(config_dir, "ra.json"), + "--addr", + ":9494", + "--debug-addr", + ":8102", + ), + ( + "boulder-sa-1", + "boulder-sa-2", + "boulder-ca-1", + "boulder-ca-2", + "boulder-va-1", + "boulder-va-2", + "boulder-publisher-1", + "boulder-publisher-2", + ), + ), # We run a separate instance of the RA for use as the SCTProvider service called by the CA. # This solves a small problem of startup order: if a client (the CA in this case) starts # up before its backends, gRPC will try to connect immediately (due to health checks), @@ -84,18 +310,55 @@ # subsequent requests to fail. This issue only exists for the CA-RA pair because they # have a circular relationship - the RA calls CA.IssueCertificate, and the CA calls # SCTProvider.GetSCTs (offered by the RA). - Service('boulder-ra-sct-provider-1', - 8118, 9594, 'ra.boulder', - ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9594', '--debug-addr', ':8118'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-publisher-1', 'boulder-publisher-2')), - Service('boulder-ra-sct-provider-2', - 8119, 9694, 'ra.boulder', - ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9694', '--debug-addr', ':8119'), - ('boulder-sa-1', 'boulder-sa-2', 'boulder-publisher-1', 'boulder-publisher-2')), - Service('bad-key-revoker', - 8020, None, None, - ('./bin/boulder', 'bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json'), '--debug-addr', ':8020'), - ('boulder-ra-1', 'boulder-ra-2')), + Service( + "boulder-ra-sct-provider-1", + 8118, + 9594, + "ra.boulder", + ( + "./bin/boulder", + "boulder-ra", + "--config", + os.path.join(config_dir, "ra.json"), + "--addr", + ":9594", + "--debug-addr", + ":8118", + ), + ("boulder-sa-1", "boulder-sa-2", "boulder-publisher-1", "boulder-publisher-2"), + ), + Service( + "boulder-ra-sct-provider-2", + 8119, + 9694, + "ra.boulder", + ( + "./bin/boulder", + "boulder-ra", + "--config", + os.path.join(config_dir, "ra.json"), + "--addr", + ":9694", + "--debug-addr", + ":8119", + ), + ("boulder-sa-1", "boulder-sa-2", "boulder-publisher-1", "boulder-publisher-2"), + ), + Service( + "bad-key-revoker", + 8020, + None, + None, + ( + "./bin/boulder", + "bad-key-revoker", + "--config", + os.path.join(config_dir, "bad-key-revoker.json"), + "--debug-addr", + ":8020", + ), + ("boulder-ra-1", "boulder-ra-2"), + ), # Note: the nonce-service instances bind to specific interfaces, not all # interfaces, because they use their explicit host:port pair to calculate # the nonce prefix, which is used by WFEs when deciding where to redeem @@ -104,47 +367,168 @@ # these services, and potentially redeeem from either service (though in # practice it will only redeem from the one that is configured for getting # nonces). - Service('nonce-service-taro-1', - 8111, '10.77.77.77:9301', 'nonce.boulder', - ('./bin/boulder', 'nonce-service', '--config', os.path.join(config_dir, 'nonce-a.json'), '--addr', '10.77.77.77:9301', '--debug-addr', ':8111',), - None), - Service('nonce-service-taro-2', - 8113, '10.77.77.77:9501', 'nonce.boulder', - ('./bin/boulder', 'nonce-service', '--config', os.path.join(config_dir, 'nonce-a.json'), '--addr', '10.77.77.77:9501', '--debug-addr', ':8113',), - None), - Service('nonce-service-zinc-1', - 8112, '10.77.77.77:9401', 'nonce.boulder', - ('./bin/boulder', 'nonce-service', '--config', os.path.join(config_dir, 'nonce-b.json'), '--addr', '10.77.77.77:9401', '--debug-addr', ':8112',), - None), - Service('pardot-test-srv', + Service( + "nonce-service-taro-1", + 8111, + "10.77.77.77:9301", + "nonce.boulder", + ( + "./bin/boulder", + "nonce-service", + "--config", + os.path.join(config_dir, "nonce-a.json"), + "--addr", + "10.77.77.77:9301", + "--debug-addr", + ":8111", + ), + None, + ), + Service( + "nonce-service-taro-2", + 8113, + "10.77.77.77:9501", + "nonce.boulder", + ( + "./bin/boulder", + "nonce-service", + "--config", + os.path.join(config_dir, "nonce-a.json"), + "--addr", + "10.77.77.77:9501", + "--debug-addr", + ":8113", + ), + None, + ), + Service( + "nonce-service-zinc-1", + 8112, + "10.77.77.77:9401", + "nonce.boulder", + ( + "./bin/boulder", + "nonce-service", + "--config", + os.path.join(config_dir, "nonce-b.json"), + "--addr", + "10.77.77.77:9401", + "--debug-addr", + ":8112", + ), + None, + ), + Service( + "pardot-test-srv", # Uses port 9601 to mock Salesforce OAuth2 token API and 9602 to mock # the Pardot API. - 9601, None, None, - ('./bin/pardot-test-srv', '--config', os.path.join(config_dir, 'pardot-test-srv.json'),), - None), - Service('email-exporter', - 8114, None, None, - ('./bin/boulder', 'email-exporter', '--config', os.path.join(config_dir, 'email-exporter.json'), '--addr', ':9603', '--debug-addr', ':8114'), - ('pardot-test-srv',)), - Service('boulder-wfe2', - 4001, None, None, - ('./bin/boulder', 'boulder-wfe2', '--config', os.path.join(config_dir, 'wfe2.json'), '--addr', ':4001', '--tls-addr', ':4431', '--debug-addr', ':8013'), - ('boulder-ra-1', 'boulder-ra-2', 'boulder-sa-1', 'boulder-sa-2', 'nonce-service-taro-1', 'nonce-service-taro-2', 'nonce-service-zinc-1', 'email-exporter')), - Service('zendesk-test-srv', - 9701, None, None, - ('./bin/zendesk-test-srv', '--config', os.path.join(config_dir, 'zendesk-test-srv.json'),), - None), - Service('sfe', + 9601, + None, + None, + ( + "./bin/pardot-test-srv", + "--config", + os.path.join(config_dir, "pardot-test-srv.json"), + ), + None, + ), + Service( + "email-exporter", + 8114, + None, + None, + ( + "./bin/boulder", + "email-exporter", + "--config", + os.path.join(config_dir, "email-exporter.json"), + "--addr", + ":9603", + "--debug-addr", + ":8114", + ), + ("pardot-test-srv",), + ), + Service( + "boulder-wfe2", + 4001, + None, + None, + ( + "./bin/boulder", + "boulder-wfe2", + "--config", + os.path.join(config_dir, "wfe2.json"), + "--addr", + ":4001", + "--tls-addr", + ":4431", + "--debug-addr", + ":8013", + ), + ( + "boulder-ra-1", + "boulder-ra-2", + "boulder-sa-1", + "boulder-sa-2", + "nonce-service-taro-1", + "nonce-service-taro-2", + "nonce-service-zinc-1", + "email-exporter", + ), + ), + Service( + "zendesk-test-srv", + 9701, + None, + None, + ( + "./bin/zendesk-test-srv", + "--config", + os.path.join(config_dir, "zendesk-test-srv.json"), + ), + None, + ), + Service( + "sfe", # Uses port 4003 for HTTP. - 4003, None, None, - ('./bin/boulder', 'sfe', '--config', os.path.join(config_dir, 'sfe.json'), '--debug-addr', ':8015'), - ('boulder-ra-1', 'boulder-ra-2', 'boulder-sa-1', 'boulder-sa-2', 'zendesk-test-srv')), - Service('log-validator', - 8016, None, None, - ('./bin/boulder', 'log-validator', '--config', os.path.join(config_dir, 'log-validator.json'), '--debug-addr', ':8016'), - None), + 4003, + None, + None, + ( + "./bin/boulder", + "sfe", + "--config", + os.path.join(config_dir, "sfe.json"), + "--debug-addr", + ":8015", + ), + ( + "boulder-ra-1", + "boulder-ra-2", + "boulder-sa-1", + "boulder-sa-2", + "zendesk-test-srv", + ), + ), + Service( + "log-validator", + 8016, + None, + None, + ( + "./bin/boulder", + "log-validator", + "--config", + os.path.join(config_dir, "log-validator.json"), + "--debug-addr", + ":8016", + ), + None, + ), ) + def _service_toposort(services): """Yields Service objects in topologically sorted order. @@ -165,7 +549,8 @@ def _service_toposort(services): print("WARNING: services with unsatisfied dependencies:") for s in blocked: print(s.name, ":", s.deps) - raise(Exception("Unable to satisfy service dependencies")) + raise (Exception("Unable to satisfy service dependencies")) + processes = [] @@ -174,19 +559,21 @@ def _service_toposort(services): # to run the load-generator). challSrvProcess = None + def install(race_detection, coverage=False): # Pass empty BUILD_TIME and BUILD_ID flags to avoid constantly invalidating the # build cache with new BUILD_TIMEs, or invalidating it on merges with a new # BUILD_ID. - go_build_flags='' + go_build_flags = "" if race_detection: - go_build_flags += ' -race' + go_build_flags += " -race" if coverage: - go_build_flags += ' -cover' # https://go.dev/blog/integration-test-coverage + go_build_flags += " -cover" # https://go.dev/blog/integration-test-coverage return subprocess.call(["/usr/bin/make", "GO_BUILD_FLAGS=%s" % go_build_flags]) == 0 + def run(cmd, coverage_dir=None): e = os.environ.copy() e.setdefault("GORACE", "halt_on_error=1") @@ -198,6 +585,7 @@ def run(cmd, coverage_dir=None): p.cmd = cmd return p + def start(coverage_dir=None): """Return True if everything builds and starts. @@ -211,9 +599,12 @@ def start(coverage_dir=None): # Check that we can resolve the service names before we try to start any # services. This prevents a confusing error (timed out health check). try: - socket.getaddrinfo('publisher.service.consul', None) + socket.getaddrinfo("publisher.service.consul", None) except Exception as e: - print("Error querying DNS. Is consul running? `docker compose ps bconsul`. %s" % (e)) + print( + "Error querying DNS. Is consul running? `docker compose ps bconsul`. %s" + % (e) + ) return False # Start the chall-test-srv first so it can be used to resolve DNS for @@ -230,9 +621,11 @@ def start(coverage_dir=None): p = run(service.cmd, coverage_dir) processes.append(p) if service.grpc_port is not None: - waithealth(' '.join(p.args), service.grpc_port, service.host_override) + waithealth(" ".join(p.args), service.grpc_port, service.host_override) else: - if not waitport(service.debug_port, ' '.join(p.args), perTickCheck=check): + if not waitport( + service.debug_port, " ".join(p.args), perTickCheck=check + ): return False except Exception as e: print("Error starting service %s: %s" % (service.name, e)) @@ -241,6 +634,7 @@ def start(coverage_dir=None): print("All servers running. Hit ^C to kill.") return True + def check(): """Return true if all started processes are still alive. @@ -262,6 +656,7 @@ def check(): processes = stillok return not busted + def startChallSrv(): """ Start the chall-test-srv and wait for it to become available. See also @@ -269,29 +664,50 @@ def startChallSrv(): """ global challSrvProcess if challSrvProcess is not None: - raise(Exception("startChallSrv called more than once")) + raise (Exception("startChallSrv called more than once")) # NOTE(@cpu): We specify explicit bind addresses for -https01 and # --tlsalpn01 here to allow HTTPS HTTP-01 responses on 443 for on interface # and TLS-ALPN-01 responses on 443 for another interface. The choice of # which is used is controlled by mock DNS data added by the relevant # integration tests. - challSrvProcess = run([ - './bin/chall-test-srv', - '--defaultIPv4', os.environ.get("FAKE_DNS"), - '-defaultIPv6', '', - '--dns01', ':8053,:8054', - '--doh', ':8343,:8443', - '--doh-cert', 'test/certs/ipki/10.77.77.77/cert.pem', - '--doh-cert-key', 'test/certs/ipki/10.77.77.77/key.pem', - '--management', ':8055', - '--http01', '64.112.117.122:80', - '-https01', '64.112.117.122:443', - '--tlsalpn01', '64.112.117.134:443']) + challSrvCmd = [ + "./bin/chall-test-srv", + "--defaultIPv4", + os.environ.get("FAKE_DNS"), + "-defaultIPv6", + "", + "--dns01", + ":8053,:8054", + "--doh", + ":8343,:8443", + "--doh-cert", + "test/certs/ipki/10.77.77.77/cert.pem", + "--doh-cert-key", + "test/certs/ipki/10.77.77.77/key.pem", + "--management", + ":8055", + "--http01", + "64.112.117.122:80", + "-https01", + "64.112.117.122:443", + "--tlsalpn01", + "64.112.117.134:443", + ] + + # Add real DNS forwarding flags if enabled + if os.environ.get("USE_REAL_DNS", "").lower() in ("true", "1", "yes"): + challSrvCmd.extend(["--use-real-dns", "true"]) + upstreamDNS = os.environ.get("UPSTREAM_DNS_SERVERS", "8.8.8.8:53,1.1.1.1:53") + challSrvCmd.extend(["--upstream-dns", upstreamDNS]) + print("Real DNS forwarding enabled with upstream servers: %s" % upstreamDNS) + + challSrvProcess = run(challSrvCmd) # Wait for the chall-test-srv management port. - if not waitport(8055, ' '.join(challSrvProcess.args)): + if not waitport(8055, " ".join(challSrvProcess.args)): return False + def stopChallSrv(): """ Stop the running chall-test-srv (if any) and wait for it to terminate. @@ -305,6 +721,7 @@ def stopChallSrv(): challSrvProcess.wait() challSrvProcess = None + @atexit.register def stop(): # When we are about to exit, send SIGTERM to each subprocess and wait for diff --git a/vendor/github.com/letsencrypt/challtestsrv/challenge-servers.go b/vendor/github.com/letsencrypt/challtestsrv/challenge-servers.go index 85bb38fa206..a3f0d2c3aa1 100644 --- a/vendor/github.com/letsencrypt/challtestsrv/challenge-servers.go +++ b/vendor/github.com/letsencrypt/challtestsrv/challenge-servers.go @@ -60,6 +60,14 @@ type ChallSrv struct { // redirects is a map of paths to URLs. HTTP challenge servers respond to // requests for these paths with a 301 to the corresponding URL. redirects map[string]string + + // realDNSForwarder is used to forward DNS queries to real upstream DNS servers + // when useRealDNS is enabled + realDNSForwarder *RealDNSForwarder + + // useRealDNS indicates whether DNS queries should be forwarded to real DNS servers + // instead of returning mock responses + useRealDNS bool } // mockDNSData holds mock responses for DNS A, AAAA, and CAA lookups. @@ -107,6 +115,12 @@ type Config struct { DOHCert string // DOHCertKey is required if DOHAddrs is nonempty. DOHCertKey string + + // UseRealDNS enables forwarding DNS queries to real upstream DNS servers + UseRealDNS bool + // UpstreamDNSServers is a list of upstream DNS servers to forward queries to + // when UseRealDNS is enabled (e.g., "8.8.8.8:53", "1.1.1.1:53") + UpstreamDNSServers []string } // validate checks that a challenge server Config is valid. To be valid it must @@ -152,6 +166,13 @@ func New(config Config) (*ChallSrv, error) { cnameRecords: make(map[string]string), servFailRecords: make(map[string]bool), }, + useRealDNS: config.UseRealDNS, + } + + // Initialize real DNS forwarder if enabled + if config.UseRealDNS { + challSrv.realDNSForwarder = NewRealDNSForwarder(config.UpstreamDNSServers, challSrv.log) + challSrv.log.Printf("Real DNS forwarding enabled with upstream servers: %v", config.UpstreamDNSServers) } // If there are HTTP-01 addresses configured, create HTTP-01 servers with diff --git a/vendor/github.com/letsencrypt/challtestsrv/dns.go b/vendor/github.com/letsencrypt/challtestsrv/dns.go index 7c16fd048b9..818aa35f681 100644 --- a/vendor/github.com/letsencrypt/challtestsrv/dns.go +++ b/vendor/github.com/letsencrypt/challtestsrv/dns.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/http" + "strings" "github.com/miekg/dns" ) @@ -37,7 +38,9 @@ type dnsAnswerFunc func(question dns.Question) []dns.RR func (s *ChallSrv) cnameAnswers(q dns.Question) []dns.RR { var records []dns.RR + // Check for mock CNAME record first if value := s.GetDNSCNAMERecord(q.Name); value != "" { + s.log.Printf("CNAME query for %s: using mock data -> %s", q.Name, value) record := &dns.CNAME{ Hdr: dns.RR_Header{ Name: q.Name, @@ -46,8 +49,20 @@ func (s *ChallSrv) cnameAnswers(q dns.Question) []dns.RR { }, Target: value, } - records = append(records, record) + return records + } + + // No mock data - check if we should forward to real DNS + // Skip forwarding for internal domains like .consul + if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("CNAME query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { + s.log.Printf("CNAME query for %s: got %d answers from real DNS", q.Name, len(realAnswers)) + return realAnswers + } + s.log.Printf("CNAME query for %s: no answers from real DNS", q.Name) } return records @@ -58,18 +73,38 @@ func (s *ChallSrv) cnameAnswers(q dns.Question) []dns.RR { // given hostname in the question no RR's will be returned. func (s *ChallSrv) txtAnswers(q dns.Question) []dns.RR { var records []dns.RR + + // Check for DNS-01 challenge mock data first values := s.GetDNSOneChallenge(q.Name) - for _, resp := range values { - record := &dns.TXT{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - }, - Txt: []string{resp}, + if len(values) > 0 { + // We have mock challenge data, use it + s.log.Printf("TXT query for %s: using mock data (%d records)", q.Name, len(values)) + for _, resp := range values { + record := &dns.TXT{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + }, + Txt: []string{resp}, + } + records = append(records, record) } - records = append(records, record) + return records + } + + // No mock data - check if we should forward to real DNS + // Skip forwarding for internal domains like .consul + if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("TXT query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { + s.log.Printf("TXT query for %s: got %d answers from real DNS", q.Name, len(realAnswers)) + return realAnswers + } + s.log.Printf("TXT query for %s: no answers from real DNS", q.Name) } + return records } @@ -84,26 +119,64 @@ func (s *ChallSrv) aAnswers(q dns.Question) []dns.RR { if ip := net.ParseIP(q.Name); ip != nil { return records } + + // Check for mock A records first values := s.GetDNSARecord(q.Name) - if defaultIPv4 := s.GetDefaultDNSIPv4(); len(values) == 0 && defaultIPv4 != "" { - values = []string{defaultIPv4} + if len(values) > 0 { + // We have mock data, use it + s.log.Printf("A query for %s: using mock data (%d records)", q.Name, len(values)) + for _, resp := range values { + ipAddr := net.ParseIP(resp) + if ipAddr == nil || ipAddr.To4() == nil { + // If the mock data isn't a valid IPv4 address, don't use it. + continue + } + record := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: ipAddr, + } + records = append(records, record) + } + return records } - for _, resp := range values { - ipAddr := net.ParseIP(resp) - if ipAddr == nil || ipAddr.To4() == nil { - // If the mock data isn't a valid IPv4 address, don't use it. - continue + + // No mock data - check if we should forward to real DNS + // Skip forwarding for internal domains like .consul + if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("A query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { + s.log.Printf("A query for %s: got %d answers from real DNS", q.Name, len(realAnswers)) + return realAnswers } - record := &dns.A{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: ipAddr, + s.log.Printf("A query for %s: no answers from real DNS", q.Name) + } + + // Fall back to default IPv4 if set + if defaultIPv4 := s.GetDefaultDNSIPv4(); defaultIPv4 != "" { + s.log.Printf("A query for %s: using default IP %s", q.Name, defaultIPv4) + values = []string{defaultIPv4} + for _, resp := range values { + ipAddr := net.ParseIP(resp) + if ipAddr == nil || ipAddr.To4() == nil { + continue + } + record := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: ipAddr, + } + records = append(records, record) } - records = append(records, record) } + return records } @@ -113,26 +186,64 @@ func (s *ChallSrv) aAnswers(q dns.Question) []dns.RR { // used for the response. func (s *ChallSrv) aaaaAnswers(q dns.Question) []dns.RR { var records []dns.RR + + // Check for mock AAAA records first values := s.GetDNSAAAARecord(q.Name) - if defaultIPv6 := s.GetDefaultDNSIPv6(); len(values) == 0 && defaultIPv6 != "" { - values = []string{defaultIPv6} + if len(values) > 0 { + // We have mock data, use it + s.log.Printf("AAAA query for %s: using mock data (%d records)", q.Name, len(values)) + for _, resp := range values { + ipAddr := net.ParseIP(resp) + if ipAddr == nil { + // If the mock data isn't a valid IPv6 address, don't use it. + continue + } + record := &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + }, + AAAA: ipAddr, + } + records = append(records, record) + } + return records } - for _, resp := range values { - ipAddr := net.ParseIP(resp) - if ipAddr == nil || ipAddr.To4() != nil { - // If the mock data isn't a valid IPv6 address, don't use it. - continue + + // No mock data - check if we should forward to real DNS + // Skip forwarding for internal domains like .consul + if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("AAAA query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { + s.log.Printf("AAAA query for %s: got %d answers from real DNS", q.Name, len(realAnswers)) + return realAnswers } - record := &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - }, - AAAA: ipAddr, + s.log.Printf("AAAA query for %s: no answers from real DNS", q.Name) + } + + // Fall back to default IPv6 if set + if defaultIPv6 := s.GetDefaultDNSIPv6(); defaultIPv6 != "" { + s.log.Printf("AAAA query for %s: using default IP %s", q.Name, defaultIPv6) + values = []string{defaultIPv6} + for _, resp := range values { + ipAddr := net.ParseIP(resp) + if ipAddr == nil { + continue + } + record := &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + }, + AAAA: ipAddr, + } + records = append(records, record) } - records = append(records, record) } + return records } @@ -141,19 +252,39 @@ func (s *ChallSrv) aaaaAnswers(q dns.Question) []dns.RR { // added for the given hostname in the question no RRs will be returned. func (s *ChallSrv) caaAnswers(q dns.Question) []dns.RR { var records []dns.RR + + // Check for mock CAA records first values := s.GetDNSCAARecord(q.Name) - for _, resp := range values { - record := &dns.CAA{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeCAA, - Class: dns.ClassINET, - }, - Tag: resp.Tag, - Value: resp.Value, + if len(values) > 0 { + // We have mock data, use it + s.log.Printf("CAA query for %s: using mock data (%d records)", q.Name, len(values)) + for _, resp := range values { + record := &dns.CAA{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeCAA, + Class: dns.ClassINET, + }, + Tag: resp.Tag, + Value: resp.Value, + } + records = append(records, record) } - records = append(records, record) + return records } + + // No mock data - check if we should forward to real DNS + // Skip forwarding for internal domains like .consul + if s.useRealDNS && s.realDNSForwarder != nil && !strings.HasSuffix(strings.ToLower(q.Name), ".consul.") { + s.log.Printf("CAA query for %s: no mock data, forwarding to real DNS", q.Name) + realAnswers := s.realDNSForwarder.ForwardQuery(q) + if len(realAnswers) > 0 { + s.log.Printf("CAA query for %s: got %d answers from real DNS", q.Name, len(realAnswers)) + return realAnswers + } + s.log.Printf("CAA query for %s: no answers from real DNS", q.Name) + } + return records } diff --git a/vendor/github.com/letsencrypt/challtestsrv/real_dns_forwarder.go b/vendor/github.com/letsencrypt/challtestsrv/real_dns_forwarder.go new file mode 100644 index 00000000000..800e3893ea8 --- /dev/null +++ b/vendor/github.com/letsencrypt/challtestsrv/real_dns_forwarder.go @@ -0,0 +1,141 @@ +package challtestsrv + +import ( + "context" + "log" + "net" + "time" + + "github.com/miekg/dns" +) + +// RealDNSForwarder forwards DNS queries to real upstream DNS servers +type RealDNSForwarder struct { + upstreamServers []string + client *dns.Client + log *log.Logger +} + +// NewRealDNSForwarder creates a new forwarder that will use the provided upstream DNS servers +func NewRealDNSForwarder(upstreamServers []string, logger *log.Logger) *RealDNSForwarder { + if len(upstreamServers) == 0 { + // Default to Google and Cloudflare public DNS + upstreamServers = []string{"8.8.8.8:53", "1.1.1.1:53"} + } + + if logger != nil { + logger.Printf("Created RealDNSForwarder with upstream servers: %v", upstreamServers) + } + + return &RealDNSForwarder{ + upstreamServers: upstreamServers, + client: &dns.Client{ + Net: "udp", + Timeout: 5 * time.Second, + }, + log: logger, + } +} + +// ForwardQuery sends the DNS query to real upstream DNS servers +// Returns the answer records from the first successful response +func (f *RealDNSForwarder) ForwardQuery(q dns.Question) []dns.RR { + msg := new(dns.Msg) + msg.SetQuestion(q.Name, q.Qtype) + msg.RecursionDesired = true + + if f.log != nil { + f.log.Printf("Forwarding DNS query: %s %s to upstream servers", dns.TypeToString[q.Qtype], q.Name) + } + + // Try each upstream server until we get a response + for _, server := range f.upstreamServers { + resp, rtt, err := f.client.Exchange(msg, server) + if err != nil { + if f.log != nil { + f.log.Printf("DNS query to %s failed: %v", server, err) + } + continue + } + + if resp != nil && resp.Rcode == dns.RcodeSuccess { + if f.log != nil { + f.log.Printf("DNS query successful via %s (rtt: %v), got %d answers for %s", server, rtt, len(resp.Answer), q.Name) + } + return resp.Answer + } else if resp != nil { + if f.log != nil { + f.log.Printf("DNS query to %s returned rcode: %s", server, dns.RcodeToString[resp.Rcode]) + } + } + } + + // No successful response from any server + if f.log != nil { + f.log.Printf("No successful response from any upstream server for %s", q.Name) + } + return nil +} + +// ForwardQueryWithContext sends the DNS query with context support for timeout/cancellation +func (f *RealDNSForwarder) ForwardQueryWithContext(ctx context.Context, q dns.Question) []dns.RR { + msg := new(dns.Msg) + msg.SetQuestion(q.Name, q.Qtype) + msg.RecursionDesired = true + + if f.log != nil { + f.log.Printf("Forwarding DNS query with context: %s %s", dns.TypeToString[q.Qtype], q.Name) + } + + // Create a dialer that respects the context + dialer := &net.Dialer{} + + for _, server := range f.upstreamServers { + // Check if context is already cancelled + select { + case <-ctx.Done(): + if f.log != nil { + f.log.Printf("Context cancelled while querying %s", q.Name) + } + return nil + default: + } + + conn, err := dialer.DialContext(ctx, "udp", server) + if err != nil { + if f.log != nil { + f.log.Printf("Failed to dial %s: %v", server, err) + } + continue + } + + dnsConn := &dns.Conn{Conn: conn} + err = dnsConn.WriteMsg(msg) + if err != nil { + if f.log != nil { + f.log.Printf("Failed to write DNS message to %s: %v", server, err) + } + dnsConn.Close() + continue + } + + resp, err := dnsConn.ReadMsg() + dnsConn.Close() + + if err == nil && resp != nil && resp.Rcode == dns.RcodeSuccess { + if f.log != nil { + f.log.Printf("DNS query with context successful via %s, got %d answers for %s", server, len(resp.Answer), q.Name) + } + return resp.Answer + } else if err != nil { + if f.log != nil { + f.log.Printf("Failed to read DNS response from %s: %v", server, err) + } + } + } + + if f.log != nil { + f.log.Printf("No successful response with context from any upstream server for %s", q.Name) + } + return nil +}