test: Add integration tests for MPIC validation (#8102)

- Update the chall-test-srv-client to make DNS events and DNS01 methods
more convenient
- Add an integration test that counts DCV and CAA checks for each
validation method

Part of #7963
This commit is contained in:
Samantha Frank 2025-04-09 12:53:07 -04:00 committed by GitHub
parent 5cc8a77ce3
commit bc39780908
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 235 additions and 5 deletions

View File

@ -2,6 +2,8 @@ package challtestsrvclient
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -354,13 +356,17 @@ func (c *Client) RemoveServfailResponse(host string) ([]byte, error) {
} }
// AddDNS01Response adds an ACME DNS-01 challenge response for the provided host // AddDNS01Response adds an ACME DNS-01 challenge response for the provided host
// to the challenge test server's DNS interfaces. The provided value will be // to the challenge test server's DNS interfaces. The value is hashed and
// served for TXT queries for _acme-challenge.<host>. Any failure returns an // base64-encoded using RawURLEncoding, and served for TXT queries to
// error that includes both the relevant operation and the payload. // _acme-challenge.<host>. Any failure returns an error that includes both the
// relevant operation and the payload.
func (c *Client) AddDNS01Response(host, value string) ([]byte, error) { func (c *Client) AddDNS01Response(host, value string) ([]byte, error) {
host = "_acme-challenge." + host
if !strings.HasSuffix(host, ".") { if !strings.HasSuffix(host, ".") {
host += "." host += "."
} }
h := sha256.Sum256([]byte(value))
value = base64.RawURLEncoding.EncodeToString(h[:])
payload := map[string]string{"host": host, "value": value} payload := map[string]string{"host": host, "value": value}
resp, err := c.postURL(addTXT, payload) resp, err := c.postURL(addTXT, payload)
if err != nil { if err != nil {
@ -376,6 +382,12 @@ func (c *Client) AddDNS01Response(host, value string) ([]byte, error) {
// provided host from the challenge test server's DNS interfaces. Any failure // provided host from the challenge test server's DNS interfaces. Any failure
// returns an error that includes both the relevant operation and the payload. // returns an error that includes both the relevant operation and the payload.
func (c *Client) RemoveDNS01Response(host string) ([]byte, error) { func (c *Client) RemoveDNS01Response(host string) ([]byte, error) {
if !strings.HasPrefix(host, "_acme-challenge.") {
host = "_acme-challenge." + host
}
if !strings.HasSuffix(host, ".") {
host += "."
}
payload := map[string]string{"host": host} payload := map[string]string{"host": host}
resp, err := c.postURL(delTXT, payload) resp, err := c.postURL(delTXT, payload)
if err != nil { if err != nil {
@ -391,8 +403,8 @@ func (c *Client) RemoveDNS01Response(host string) ([]byte, error) {
type DNSRequest struct { type DNSRequest struct {
Question struct { Question struct {
Name string `json:"Name"` Name string `json:"Name"`
Qtype int `json:"Qtype"` Qtype uint16 `json:"Qtype"`
Qclass int `json:"Qclass"` Qclass uint16 `json:"Qclass"`
} `json:"Question"` } `json:"Question"`
} }

View File

@ -13,8 +13,226 @@ import (
"github.com/eggsampler/acme/v3" "github.com/eggsampler/acme/v3"
"github.com/letsencrypt/boulder/test/vars" "github.com/letsencrypt/boulder/test/vars"
"github.com/miekg/dns"
) )
func TestMPICTLSALPN01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeTLSALPN01]
if !ok {
t.Fatalf("no TLS-ALPN-01 challenge found in %#v", authz)
}
_, err = testSrvClient.AddARecord(domain, []string{"10.88.88.88"})
if err != nil {
t.Fatalf("adding A record: %s", err)
}
defer func() {
testSrvClient.RemoveARecord(domain)
}()
_, err = testSrvClient.AddTLSALPN01Response(domain, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveTLSALPN01Response(domain)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing TLS-ALPN-01 validation: %s", err)
}
validationEvents, err := testSrvClient.TLSALPN01RequestHistory(domain)
if err != nil {
t.Fatal(err)
}
if len(validationEvents) != 4 {
t.Errorf("expected 4 validation events (VA=1 RVAs=3), got %d", len(validationEvents))
}
dnsEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
caaCount := 0
for _, event := range dnsEvents {
if event.Question.Qtype == dns.TypeCAA {
caaCount++
}
}
if caaCount != 4 {
t.Fatalf("expected 4 CAA validation events (VA=1 RVAs=3), got %d", caaCount)
}
}
func TestMPICDNS01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeDNS01]
if !ok {
t.Fatalf("no DNS challenge found in %#v", authz)
}
_, err = testSrvClient.AddDNS01Response(domain, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveDNS01Response(domain)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing DNS-01 validation: %s", err)
}
challDomainDNSEvents, err := testSrvClient.DNSRequestHistory("_acme-challenge." + domain)
if err != nil {
t.Fatal(err)
}
validationCount := 0
for _, event := range challDomainDNSEvents {
if event.Question.Qtype == dns.TypeTXT && event.Question.Name == "_acme-challenge."+domain+"." {
validationCount++
}
}
if validationCount != 4 {
t.Errorf("expected 4 validation events (VA=1 RVAs=3), got %d", validationCount)
}
domainDNSEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
caaCount := 0
for _, event := range domainDNSEvents {
if event.Question.Qtype == dns.TypeCAA {
caaCount++
}
}
if caaCount != 4 {
t.Fatalf("expected 4 CAA validation events (VA=1 RVAs=3), got %d", caaCount)
}
}
func TestMPICHTTP01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeHTTP01]
if !ok {
t.Fatalf("no HTTP challenge found in %#v", authz)
}
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing HTTP-01 validation: %s", err)
}
validationEvents, err := testSrvClient.HTTPRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
validationCount := 0
for _, event := range validationEvents {
if event.URL == "/.well-known/acme-challenge/"+chal.Token {
validationCount++
}
}
if validationCount != 4 {
t.Errorf("expected 4 validation events (VA=1 RVAs=3), got %d", validationCount)
}
dnsEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
caaCount := 0
for _, event := range dnsEvents {
if event.Question.Qtype == dns.TypeCAA {
caaCount++
}
}
if caaCount != 4 {
t.Fatalf("expected 4 CAA validation events (VA=1 RVAs=3), got %d", caaCount)
}
}
func TestCAARechecking(t *testing.T) { func TestCAARechecking(t *testing.T) {
t.Parallel() t.Parallel()