From 3438b057d82576f93dbda642869f5259f5623ba9 Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Mon, 31 Mar 2025 11:10:22 -0500 Subject: [PATCH] Replace Python test_recheck_caa with Go TestCAARechecking (#8085) Replace a python integration test which relies on our "setup_twenty_days_ago" scaffolding with a Go test that uses direct database statements to avoid any need to do clock manipulation. The resulting test is much more verbose, but also (in my opinion) much clearer and significantly faster. --- test/integration/common_test.go | 17 ++++++ test/integration/validation_test.go | 95 +++++++++++++++++++++++++++++ test/v2_integration.py | 43 ------------- 3 files changed, 112 insertions(+), 43 deletions(-) create mode 100644 test/integration/validation_test.go diff --git a/test/integration/common_test.go b/test/integration/common_test.go index 377aa0650..fa9600a36 100644 --- a/test/integration/common_test.go +++ b/test/integration/common_test.go @@ -90,6 +90,23 @@ func delHTTP01Response(token string) error { return nil } +func addCAAIssueRecord(host string, issue string) error { + resp, err := http.Post("http://boulder.service.consul:8055/add-caa", "", + bytes.NewBufferString(fmt.Sprintf(`{ + "host": "%s", + "policies": [{"tag": "issue", "value": "%s"}] + }`, host, issue))) + if err != nil { + return fmt.Errorf("adding CAA record: %s", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("adding CAA record: status %d", resp.StatusCode) + } + return nil +} + func makeClientAndOrder(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string, certToReplace *x509.Certificate) (*client, *acme.Order, error) { var err error if c == nil { diff --git a/test/integration/validation_test.go b/test/integration/validation_test.go new file mode 100644 index 000000000..3740a2750 --- /dev/null +++ b/test/integration/validation_test.go @@ -0,0 +1,95 @@ +//go:build integration + +package integration + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "database/sql" + "strings" + "testing" + "time" + + "github.com/eggsampler/acme/v3" + "github.com/letsencrypt/boulder/test/vars" +) + +func TestCAARechecking(t *testing.T) { + t.Parallel() + + domain := randomDomain(t) + idents := []acme.Identifier{{Type: "dns", Value: domain}} + + // Create an order and authorization, and fulfill the associated challenge. + // This should put the authz into the "valid" state, since CAA checks passed. + client, err := makeClient() + if err != nil { + t.Fatalf("creating acme client: %s", err) + } + + order, err := client.Client.NewOrder(client.Account, idents) + 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 = addHTTP01Response(chal.Token, chal.KeyAuthorization) + if err != nil { + t.Fatalf("setting HTTP-01 challenge token: %s", err) + } + defer delHTTP01Response(chal.Token) + + chal, err = client.Client.UpdateChallenge(client.Account, chal) + if err != nil { + t.Fatalf("completing HTTP-01 validation: %s", err) + } + + // Manipulate the database so that it looks like the authz was validated + // more than 8 hours ago. + db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms) + if err != nil { + t.Fatalf("sql.Open: %s", err) + } + + _, err = db.Exec(`UPDATE authz2 SET attemptedAt = ? WHERE identifierValue = ?`, time.Now().Add(-24*time.Hour).Format(time.DateTime), domain) + if err != nil { + t.Fatalf("updating authz attemptedAt timestamp: %s", err) + } + + // Change the CAA record to now forbid issuance. + err = addCAAIssueRecord(domain, ";") + if err != nil { + t.Fatalf("updating CAA record: %s", err) + } + + // Try to finalize the order created above. Due to our db manipulation, this + // should trigger a CAA recheck. And due to our challtestsrv manipulation, + // that CAA recheck should fail. Therefore the whole finalize should fail. + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generating cert key: %s", err) + } + + csr, err := makeCSR(key, idents, false) + if err != nil { + t.Fatalf("generating finalize csr: %s", err) + } + + _, err = client.Client.FinalizeOrder(client.Account, order, csr) + if err == nil { + t.Errorf("expected finalize to fail, but got success") + } + if !strings.Contains(err.Error(), "CAA") { + t.Errorf("expected finalize to fail due to CAA, but got: %s", err) + } +} diff --git a/test/v2_integration.py b/test/v2_integration.py index ab76ca303..4789c8b95 100644 --- a/test/v2_integration.py +++ b/test/v2_integration.py @@ -1170,49 +1170,6 @@ def test_expiration_mailer(): if mailcount != 2: raise(Exception("\nExpiry mailer failed: expected 2 emails, got %d" % mailcount)) -caa_recheck_setup_data = {} -@register_twenty_days_ago -def caa_recheck_setup(): - client = chisel2.make_client() - # Issue a certificate with the clock set back, and save the authzs to check - # later that they are valid (200). They should however require rechecking for - # CAA purposes. - numNames = 10 - # Generate numNames subdomains of a random domain - base_domain = random_domain() - domains = [ "{0}.{1}".format(str(n),base_domain) for n in range(numNames) ] - order = chisel2.auth_and_issue(domains, client=client) - - global caa_recheck_setup_data - caa_recheck_setup_data = { - 'client': client, - 'authzs': order.authorizations, - } - -def test_recheck_caa(): - """Request issuance for a domain where we have a old cached authz from when CAA - was good. We'll set a new CAA record forbidding issuance; the CAA should - recheck CAA and reject the request. - """ - if 'authzs' not in caa_recheck_setup_data: - raise(Exception("CAA authzs not prepared for test_caa")) - domains = [] - for a in caa_recheck_setup_data['authzs']: - response = caa_recheck_setup_data['client']._post(a.uri, None) - if response.status_code != 200: - raise(Exception("Unexpected response for CAA authz: ", - response.status_code)) - domain = a.body.identifier.value - domains.append(domain) - - # Set a forbidding CAA record on just one domain - challSrv.add_caa_issue(domains[3], ";") - - # Request issuance for the previously-issued domain name, which should - # now be denied due to CAA. - chisel2.expect_problem("urn:ietf:params:acme:error:caa", - lambda: chisel2.auth_and_issue(domains, client=caa_recheck_setup_data['client'])) - def test_caa_good(): domain = random_domain() challSrv.add_caa_issue(domain, "happy-hacker-ca.invalid")