github.com/letsencrypt/boulder@v0.20251208.0/test/integration/dns_account_01_test.go (about)

     1  //go:build integration
     2  
     3  package integration
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/eggsampler/acme/v3"
    12  )
    13  
    14  func TestDNSAccount01HappyPath(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
    18  		t.Skip("Test requires dns-account-01 to be enabled")
    19  	}
    20  
    21  	domain := random_domain()
    22  	c, err := makeClient()
    23  	if err != nil {
    24  		t.Fatalf("creating client: %s", err)
    25  	}
    26  
    27  	idents := []acme.Identifier{{Type: "dns", Value: domain}}
    28  
    29  	order, err := c.Client.NewOrder(c.Account, idents)
    30  	if err != nil {
    31  		t.Fatalf("creating new order: %s", err)
    32  	}
    33  
    34  	authzURL := order.Authorizations[0]
    35  	auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
    36  	if err != nil {
    37  		t.Fatalf("fetching authorization: %s", err)
    38  	}
    39  
    40  	chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
    41  	if !ok {
    42  		t.Fatal("dns-account-01 challenge not offered by server")
    43  	}
    44  
    45  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, chal.KeyAuthorization)
    46  	if err != nil {
    47  		t.Fatalf("adding DNS response: %s", err)
    48  	}
    49  	t.Cleanup(func() {
    50  		_, _ = testSrvClient.RemoveDNSAccount01Response(c.Account.URL, domain)
    51  	})
    52  
    53  	chal, err = c.Client.UpdateChallenge(c.Account, chal)
    54  	if err != nil {
    55  		t.Fatalf("updating challenge: %s", err)
    56  	}
    57  
    58  	// Check that the authorization status has changed
    59  	auth, err = c.Client.FetchAuthorization(c.Account, authzURL)
    60  	if err != nil {
    61  		t.Fatalf("fetching authorization after challenge update: %s", err)
    62  	}
    63  
    64  	if auth.Status != "valid" {
    65  		t.Fatalf("expected authorization status to be 'valid', got '%s'", auth.Status)
    66  	}
    67  }
    68  
    69  func TestDNSAccount01WrongTXTRecord(t *testing.T) {
    70  	t.Parallel()
    71  
    72  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
    73  		t.Skip("Test requires dns-account-01 to be enabled")
    74  	}
    75  
    76  	domain := random_domain()
    77  	c, err := makeClient()
    78  	if err != nil {
    79  		t.Fatalf("creating client: %s", err)
    80  	}
    81  
    82  	idents := []acme.Identifier{{Type: "dns", Value: domain}}
    83  
    84  	order, err := c.Client.NewOrder(c.Account, idents)
    85  	if err != nil {
    86  		t.Fatalf("creating new order: %s", err)
    87  	}
    88  
    89  	authzURL := order.Authorizations[0]
    90  	auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
    91  	if err != nil {
    92  		t.Fatalf("fetching authorization: %s", err)
    93  	}
    94  
    95  	chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
    96  	if !ok {
    97  		t.Fatal("dns-account-01 challenge not offered by server")
    98  	}
    99  
   100  	// Add a wrong TXT record
   101  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, "wrong-digest")
   102  	if err != nil {
   103  		t.Fatalf("adding DNS response: %s", err)
   104  	}
   105  	t.Cleanup(func() {
   106  		_, _ = testSrvClient.RemoveDNSAccount01Response(c.Account.URL, domain)
   107  	})
   108  
   109  	_, err = c.Client.UpdateChallenge(c.Account, chal)
   110  	if err == nil {
   111  		t.Fatalf("updating challenge: expected error, got nil")
   112  	}
   113  	prob, ok := err.(acme.Problem)
   114  	if !ok {
   115  		t.Fatalf("updating challenge: expected acme.Problem error, got %T", err)
   116  	}
   117  	if prob.Type != "urn:ietf:params:acme:error:unauthorized" {
   118  		t.Fatalf("updating challenge: expected unauthorized error, got %s", prob.Type)
   119  	}
   120  	if !strings.Contains(prob.Detail, "Incorrect TXT record") {
   121  		t.Fatalf("updating challenge: expected Incorrect TXT record error, got %s", prob.Detail)
   122  	}
   123  }
   124  
   125  func TestDNSAccount01NoTXTRecord(t *testing.T) {
   126  	t.Parallel()
   127  
   128  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
   129  		t.Skip("Test requires dns-account-01 to be enabled")
   130  	}
   131  
   132  	domain := random_domain()
   133  	c, err := makeClient()
   134  	if err != nil {
   135  		t.Fatalf("creating client: %s", err)
   136  	}
   137  
   138  	idents := []acme.Identifier{{Type: "dns", Value: domain}}
   139  
   140  	order, err := c.Client.NewOrder(c.Account, idents)
   141  	if err != nil {
   142  		t.Fatalf("creating new order: %s", err)
   143  	}
   144  
   145  	authzURL := order.Authorizations[0]
   146  	auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
   147  	if err != nil {
   148  		t.Fatalf("fetching authorization: %s", err)
   149  	}
   150  
   151  	chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
   152  	if !ok {
   153  		t.Fatal("dns-account-01 challenge not offered by server")
   154  	}
   155  
   156  	// Skip adding a TXT record
   157  
   158  	_, err = c.Client.UpdateChallenge(c.Account, chal)
   159  	if err == nil {
   160  		t.Fatalf("updating challenge: expected error, got nil")
   161  	}
   162  	prob, ok := err.(acme.Problem)
   163  	if !ok {
   164  		t.Fatalf("updating challenge: expected acme.Problem error, got %T", err)
   165  	}
   166  	if prob.Type != "urn:ietf:params:acme:error:unauthorized" {
   167  		t.Fatalf("updating challenge: expected unauthorized error, got %s", prob.Type)
   168  	}
   169  	if !strings.Contains(prob.Detail, "No TXT record found") {
   170  		t.Fatalf("updating challenge: expected No TXT record found error, got %s", prob.Detail)
   171  	}
   172  }
   173  
   174  func TestDNSAccount01MultipleTXTRecordsNoneMatch(t *testing.T) {
   175  	t.Parallel()
   176  
   177  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
   178  		t.Skip("Test requires dns-account-01 to be enabled")
   179  	}
   180  
   181  	domain := random_domain()
   182  	c, err := makeClient()
   183  	if err != nil {
   184  		t.Fatalf("creating client: %s", err)
   185  	}
   186  
   187  	idents := []acme.Identifier{{Type: "dns", Value: domain}}
   188  
   189  	order, err := c.Client.NewOrder(c.Account, idents)
   190  	if err != nil {
   191  		t.Fatalf("creating new order: %s", err)
   192  	}
   193  
   194  	authzURL := order.Authorizations[0]
   195  	auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
   196  	if err != nil {
   197  		t.Fatalf("fetching authorization: %s", err)
   198  	}
   199  
   200  	chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
   201  	if !ok {
   202  		t.Fatal("dns-account-01 challenge not offered by server")
   203  	}
   204  
   205  	// Add multiple wrong TXT records
   206  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, "wrong-digest-1")
   207  	if err != nil {
   208  		t.Fatalf("adding DNS response: %s", err)
   209  	}
   210  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, "wrong-digest-2")
   211  	if err != nil {
   212  		t.Fatalf("adding DNS response: %s", err)
   213  	}
   214  	t.Cleanup(func() {
   215  		_, _ = testSrvClient.RemoveDNSAccount01Response(c.Account.URL, domain)
   216  	})
   217  
   218  	_, err = c.Client.UpdateChallenge(c.Account, chal)
   219  	if err == nil {
   220  		t.Fatalf("updating challenge: expected error, got nil")
   221  	}
   222  	prob, ok := err.(acme.Problem)
   223  	if !ok {
   224  		t.Fatalf("updating challenge: expected acme.Problem error, got %T", err)
   225  	}
   226  	if prob.Type != "urn:ietf:params:acme:error:unauthorized" {
   227  		t.Fatalf("updating challenge: expected unauthorized error, got %s", prob.Type)
   228  	}
   229  	if !strings.Contains(prob.Detail, "Incorrect TXT record") {
   230  		t.Fatalf("updating challenge: expected Incorrect TXT record error, got %s", prob.Detail)
   231  	}
   232  }
   233  
   234  func TestDNSAccount01MultipleTXTRecordsOneMatches(t *testing.T) {
   235  	t.Parallel()
   236  
   237  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
   238  		t.Skip("Test requires dns-account-01 to be enabled")
   239  	}
   240  
   241  	domain := random_domain()
   242  	c, err := makeClient()
   243  	if err != nil {
   244  		t.Fatalf("creating client: %s", err)
   245  	}
   246  
   247  	idents := []acme.Identifier{{Type: "dns", Value: domain}}
   248  
   249  	order, err := c.Client.NewOrder(c.Account, idents)
   250  	if err != nil {
   251  		t.Fatalf("creating new order: %s", err)
   252  	}
   253  
   254  	authzURL := order.Authorizations[0]
   255  	auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
   256  	if err != nil {
   257  		t.Fatalf("fetching authorization: %s", err)
   258  	}
   259  
   260  	chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
   261  	if !ok {
   262  		t.Fatal("dns-account-01 challenge not offered by server")
   263  	}
   264  
   265  	// Add multiple TXT records, one of which is correct
   266  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, "wrong-digest-1")
   267  	if err != nil {
   268  		t.Fatalf("adding DNS response: %s", err)
   269  	}
   270  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, chal.KeyAuthorization)
   271  	if err != nil {
   272  		t.Fatalf("adding DNS response: %s", err)
   273  	}
   274  	_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, "wrong-digest-2")
   275  	if err != nil {
   276  		t.Fatalf("adding DNS response: %s", err)
   277  	}
   278  	t.Cleanup(func() {
   279  		_, _ = testSrvClient.RemoveDNSAccount01Response(c.Account.URL, domain)
   280  	})
   281  
   282  	chal, err = c.Client.UpdateChallenge(c.Account, chal)
   283  	if err != nil {
   284  		t.Fatalf("updating challenge: expected no error, got %s", err)
   285  	}
   286  
   287  	// Check that the authorization status has changed
   288  	auth, err = c.Client.FetchAuthorization(c.Account, authzURL)
   289  	if err != nil {
   290  		t.Fatalf("fetching authorization after challenge update: %s", err)
   291  	}
   292  
   293  	if auth.Status != "valid" {
   294  		t.Fatalf("expected authorization status to be 'valid', got '%s'", auth.Status)
   295  	}
   296  }
   297  
   298  func TestDNSAccount01WildcardDomain(t *testing.T) {
   299  	t.Parallel()
   300  
   301  	if os.Getenv("BOULDER_CONFIG_DIR") == "test/config" {
   302  		t.Skip("Test requires dns-account-01 to be enabled")
   303  	}
   304  
   305  	hostDomain := randomDomain(t)
   306  	wildcardDomain := fmt.Sprintf("*.%s", randomDomain(t))
   307  
   308  	c, err := makeClient()
   309  	if err != nil {
   310  		t.Fatalf("creating client: %s", err)
   311  	}
   312  
   313  	idents := []acme.Identifier{
   314  		{Type: "dns", Value: hostDomain},
   315  		{Type: "dns", Value: wildcardDomain},
   316  	}
   317  
   318  	order, err := c.Client.NewOrder(c.Account, idents)
   319  	if err != nil {
   320  		t.Fatalf("creating new order: %s", err)
   321  	}
   322  
   323  	for _, authzURL := range order.Authorizations {
   324  		auth, err := c.Client.FetchAuthorization(c.Account, authzURL)
   325  		if err != nil {
   326  			t.Fatalf("fetching authorization: %s", err)
   327  		}
   328  
   329  		isWildcard := strings.HasPrefix(auth.Identifier.Value, "*.")
   330  		domain := auth.Identifier.Value
   331  		if isWildcard {
   332  			domain = strings.TrimPrefix(domain, "*.")
   333  		}
   334  
   335  		chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNSAccount01]
   336  		if !ok {
   337  			t.Fatal("dns-account-01 challenge not offered by server")
   338  		}
   339  
   340  		_, err = testSrvClient.AddDNSAccount01Response(c.Account.URL, domain, chal.KeyAuthorization)
   341  		if err != nil {
   342  			t.Fatalf("adding DNS response: %s", err)
   343  		}
   344  		t.Cleanup(func() {
   345  			_, _ = testSrvClient.RemoveDNSAccount01Response(c.Account.URL, domain)
   346  		})
   347  
   348  		chal, err = c.Client.UpdateChallenge(c.Account, chal)
   349  		if err != nil {
   350  			t.Fatalf("updating challenge: %s", err)
   351  		}
   352  
   353  		// Check that the authorization status has changed
   354  		auth, err = c.Client.FetchAuthorization(c.Account, authzURL)
   355  		if err != nil {
   356  			t.Fatalf("fetching authorization after challenge update: %s", err)
   357  		}
   358  
   359  		if auth.Status != "valid" {
   360  			t.Fatalf("expected authorization status to be 'valid', got '%s'", auth.Status)
   361  		}
   362  	}
   363  }