From 9c3a161ccd56c25f46665fd34ca9a154cbf15770 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Wed, 1 Jan 2025 14:51:58 -0500 Subject: [PATCH] AXFRDDNS: Enable automated testing (#3290) --- .github/workflows/pr_integration_tests.yml | 14 +- integrationTest/integration_test.go | 154 +++++++++++++-------- integrationTest/providers.json | 9 ++ providers/axfrddns/auditrecords.go | 15 +- providers/axfrddns/axfrddnsProvider.go | 1 + 5 files changed, 133 insertions(+), 60 deletions(-) diff --git a/.github/workflows/pr_integration_tests.yml b/.github/workflows/pr_integration_tests.yml index 733f731de..8af0f7d14 100644 --- a/.github/workflows/pr_integration_tests.yml +++ b/.github/workflows/pr_integration_tests.yml @@ -52,7 +52,7 @@ jobs: Write-Host "Integration test providers: $Providers" echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT env: - PROVIDERS: "['AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']" + PROVIDERS: "['AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']" ENV_CONTEXT: ${{ toJson(env) }} VARS_CONTEXT: ${{ toJson(vars) }} SECRETS_CONTEXT: ${{ toJson(secrets) }} @@ -73,6 +73,8 @@ jobs: # PROVIDER DOMAIN LIST # These providers will be tested if the env variable is set. # Set it to the domain name to use during the test. + AXFRDDNS_DOMAIN: ${{ vars.AXFRDDNS_DOMAIN }} + AXFRDDNS_DNSSEC_DOMAIN: ${{ vars.AXFRDDNS_DNSSEC_DOMAIN }} AZURE_DNS_DOMAIN: ${{ vars.AZURE_DNS_DOMAIN }} BIND_DOMAIN: ${{ vars.BIND_DOMAIN }} BUNNY_DNS_DOMAIN: ${{ vars.BUNNY_DNS_DOMAIN }} @@ -98,6 +100,16 @@ jobs: # The above providers have additional env variables they # need for credentials and such. # + AXFRDDNS_MASTER: ${{ secrets.AXFRDDNS_MASTER }} + AXFRDDNS_NAMESERVERS: ${{ secrets.AXFRDDNS_NAMESERVERS }} + AXFRDDNS_TRANSFER_KEY: ${{ secrets.AXFRDDNS_TRANSFER_KEY }} + AXFRDDNS_UPDATE_KEY: ${{ secrets.AXFRDDNS_UPDATE_KEY }} + # + AXFRDDNS_DNSSEC_MASTER: ${{ secrets.AXFRDDNS_DNSSEC_MASTER }} + AXFRDDNS_DNSSEC_NAMESERVERS: ${{ secrets.AXFRDDNS_DNSSEC_NAMESERVERS }} + AXFRDDNS_DNSSEC_TRANSFER_KEY: ${{ secrets.AXFRDDNS_DNSSEC_TRANSFER_KEY }} + AXFRDDNS_DNSSEC_UPDATE_KEY: ${{ secrets.AXFRDDNS_DNSSEC_UPDATE_KEY }} + # AZURE_DNS_CLIENT_ID: ${{ secrets.AZURE_DNS_CLIENT_ID }} AZURE_DNS_CLIENT_SECRET: ${{ secrets.AZURE_DNS_CLIENT_SECRET }} AZURE_DNS_RESOURCE_GROUP: ${{ secrets.AZURE_DNS_RESOURCE_GROUP }} diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 4b7039c99..462e44d0b 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -20,7 +20,8 @@ import ( "github.com/miekg/dns/dnsutil" ) -var providerToRun = flag.String("provider", "", "Provider to run") +var providerFlag = flag.String("provider", "", "Provider to run (if empty, deduced from -profile)") +var profileFlag = flag.String("profile", "", "Entry in providers.json to use (if empty, copied from -provider)") var startIdx = flag.Int("start", -1, "Test number to begin with") var endIdx = flag.Int("end", -1, "Test index to stop after") var verbose = flag.Bool("verbose", false, "Print corrections as you run them") @@ -47,60 +48,96 @@ func CfCProxyFull() *TestCase { return tc("cproxyf", cfProxyCNAME("cproxy", "exa // --- func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[string]string) { - if *providerToRun == "" { - t.Log("No provider specified with -provider") + if *providerFlag == "" && *profileFlag == "" { + t.Log("No -provider or -profile specified") return nil, "", nil } + + // Load the profile values + jsons, err := credsfile.LoadProviderConfigs("providers.json") if err != nil { t.Fatalf("Error loading provider configs: %s", err) } - for name, cfg := range jsons { - if *providerToRun != name { - continue - } - var metadata json.RawMessage - // CLOUDFLAREAPI tests related to CLOUDFLAREAPI_SINGLE_REDIRECT/CF_REDIRECT/CF_TEMP_REDIRECT - // requires metadata to enable this feature. - // In hindsight, I have no idea why this metadata flag is required to - // use this feature. Maybe because we didn't have the capabilities - // feature at the time? - if name == "CLOUDFLAREAPI" { - items := []string{} - if *enableCFWorkers { - items = append(items, `"manage_workers": true`) - } - switch *enableCFRedirectMode { - case "": - items = append(items, `"manage_redirects": true`) - case "c": - items = append(items, `"manage_redirects": true`) - items = append(items, `"manage_single_redirects": true`) - case "n": - items = append(items, `"manage_single_redirects": true`) - case "o": - } - metadata = []byte(`{ ` + strings.Join(items, `, `) + ` }`) - } - - provider, err := providers.CreateDNSProvider(name, cfg, metadata) - if err != nil { - t.Fatal(err) - } - - if name == "CLOUDFLAREAPI" && *enableCFWorkers { - // Cloudflare only. Will do nothing if provider != *cloudflareProvider. - if err := cloudflare.PrepareCloudflareTestWorkers(provider); err != nil { - t.Fatal(err) - } - } - - return provider, cfg["domain"], cfg + // Which profile are we using? Use the profile but default to the provider. + targetProfile := *profileFlag + if targetProfile == "" { + targetProfile = *providerFlag } - t.Fatalf("Provider %s not found", *providerToRun) - return nil, "", nil + var profileName, profileType string + var cfg map[string]string + + // Find the profile we want to use. + for p, c := range jsons { + if p == targetProfile { + cfg = c + profileName = p + profileType = cfg["TYPE"] + if profileType == "" { + t.Fatalf("providers.json profile %q does not have a TYPE field", *profileFlag) + } + break + } + } + if profileName == "" { + t.Fatalf("Profile not found: -profile=%q -provider=%q", *profileFlag, *providerFlag) + return nil, "", nil + } + + // Fill in -profile if blank. + if *profileFlag == "" { + *profileFlag = profileName + } + + // Fix legacy use of -provider. + if *providerFlag != profileType { + fmt.Printf("WARNING: -provider=%q does not match profile TYPE=%q. Using profile TYPE.\n", *providerFlag, profileType) + *providerFlag = profileType + } + + //fmt.Printf("DEBUG flag=%q Profile=%q TYPE=%q\n", *providerFlag, profileName, profileType) + fmt.Printf("Testing Profile=%q TYPE=%q\n", profileName, profileType) + + var metadata json.RawMessage + + // CLOUDFLAREAPI tests related to CLOUDFLAREAPI_SINGLE_REDIRECT/CF_REDIRECT/CF_TEMP_REDIRECT + // requires metadata to enable this feature. + // In hindsight, I have no idea why this metadata flag is required to + // use this feature. Maybe because we didn't have the capabilities + // feature at the time? + if profileType == "CLOUDFLAREAPI" { + items := []string{} + if *enableCFWorkers { + items = append(items, `"manage_workers": true`) + } + switch *enableCFRedirectMode { + case "": + items = append(items, `"manage_redirects": true`) + case "c": + items = append(items, `"manage_redirects": true`) + items = append(items, `"manage_single_redirects": true`) + case "n": + items = append(items, `"manage_single_redirects": true`) + case "o": + } + metadata = []byte(`{ ` + strings.Join(items, `, `) + ` }`) + } + + provider, err := providers.CreateDNSProvider(profileType, cfg, metadata) + if err != nil { + t.Fatal(err) + } + + if profileType == "CLOUDFLAREAPI" && *enableCFWorkers { + // Cloudflare only. Will do nothing if provider != *cloudflareProvider. + if err := cloudflare.PrepareCloudflareTestWorkers(provider); err != nil { + t.Fatal(err) + } + } + + return provider, cfg["domain"], cfg } func TestDNSProviders(t *testing.T) { @@ -155,7 +192,7 @@ func testPermitted(p string, f TestGroup) error { // If there are any required capabilities, make sure they all exist. if len(f.required) != 0 { for _, c := range f.required { - if !providers.ProviderHasCapability(*providerToRun, c) { + if !providers.ProviderHasCapability(*providerFlag, c) { return fmt.Errorf("%s not supported", c) } } @@ -211,18 +248,18 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma //} dom.Records = append(dom.Records, &rc) } - if *providerToRun == "AXFRDDNS" { - // Bind will refuse a DDNS update when the resulting zone - // contains a NS record without an associated address - // records (A or AAAA) - dom.Records = append(dom.Records, a("ns."+domainName+".", "9.8.7.6")) - } + //if *providerToRun == "AXFRDDNS" { + // Bind will refuse a DDNS update when the resulting zone + // contains a NS record without an associated address + // records (A or AAAA) + //dom.Records = append(dom.Records, a("ns."+domainName+".", "9.8.7.6")) + //} dom.Unmanaged = tst.Unmanaged dom.UnmanagedUnsafe = tst.UnmanagedUnsafe models.PostProcessRecords(dom.Records) dom2, _ := dom.Copy() - if err := providers.AuditRecords(*providerToRun, dom.Records); err != nil { + if err := providers.AuditRecords(*providerFlag, dom.Records); err != nil { t.Skipf("***SKIPPED(PROVIDER DOES NOT SUPPORT '%s' ::%q)", err, desc) return } @@ -299,7 +336,8 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string, } // Abide by filter - if err := testPermitted(*providerToRun, *group); err != nil { + fmt.Printf("DEBUG testPermitted: prov=%q profile=%q\n", *providerFlag, *profileFlag) + if err := testPermitted(*profileFlag, *group); err != nil { //t.Logf("%s: ***SKIPPED(%v)***", group.Desc, err) makeChanges(t, prv, dc, tc("Empty"), fmt.Sprintf("%02d:%s ***SKIPPED(%v)***", gIdx, group.Desc, err), false, origConfig) continue @@ -345,7 +383,7 @@ func TestDualProviders(t *testing.T) { t.Fatal("NO DOMAIN SET! Exiting!") } dc := getDomainConfigWithNameservers(t, p, domain) - if !providers.ProviderHasCapability(*providerToRun, providers.DocDualHost) { + if !providers.ProviderHasCapability(*providerFlag, providers.DocDualHost) { t.Skip("Skipping. DocDualHost == Cannot") return } @@ -423,7 +461,7 @@ func TestNameserverDots(t *testing.T) { t.Fatal("NO DOMAIN SET! Exiting!") } dc := getDomainConfigWithNameservers(t, p, domain) - if !providers.ProviderHasCapability(*providerToRun, providers.DocDualHost) { + if !providers.ProviderHasCapability(*providerFlag, providers.DocDualHost) { t.Skip("Skipping. DocDualHost == Cannot") return } @@ -945,6 +983,7 @@ func makeTests() []*TestGroup { // CNAME testgroup("CNAME", + not("AXFRDDNS_DNSSEC"), tc("Create a CNAME", cname("testcname", "www.google.com.")), tc("Change CNAME target", cname("testcname", "www.yahoo.com.")), ), @@ -1077,6 +1116,7 @@ func makeTests() []*TestGroup { // changes properly for you. Let's verify that we got it right! testgroup("TypeChangeHard", + not("AXFRDDNS_DNSSEC"), tc("Create a CNAME", cname("foo", "google.com.")), tc("Change to A record", a("foo", "1.2.3.4")), tc("Change back to CNAME", cname("foo", "google2.com.")), diff --git a/integrationTest/providers.json b/integrationTest/providers.json index 61a133055..b25d2cf1e 100644 --- a/integrationTest/providers.json +++ b/integrationTest/providers.json @@ -25,6 +25,15 @@ "transfer-key": "$AXFRDDNS_TRANSFER_KEY", "update-key": "$AXFRDDNS_UPDATE_KEY" }, + "AXFRDDNS_DNSSEC": { + "TYPE": "AXFRDDNS", + "buggy-cname": "$AXFRDDNS_DNSSEC_BUGGY_CNAME", + "domain": "$AXFRDDNS_DNSSEC_DOMAIN", + "master": "$AXFRDDNS_DNSSEC_MASTER", + "nameservers": "ns.example.com", + "transfer-key": "$AXFRDDNS_DNSSEC_TRANSFER_KEY", + "update-key": "$AXFRDDNS_DNSSEC_UPDATE_KEY" + }, "AZURE_DNS": { "ClientID": "$AZURE_DNS_CLIENT_ID", "ClientSecret": "$AZURE_DNS_CLIENT_SECRET", diff --git a/providers/axfrddns/auditrecords.go b/providers/axfrddns/auditrecords.go index 9dd7f90b1..3c03ed6d5 100644 --- a/providers/axfrddns/auditrecords.go +++ b/providers/axfrddns/auditrecords.go @@ -1,10 +1,21 @@ package axfrddns -import "github.com/StackExchange/dnscontrol/v4/models" +import ( + "github.com/StackExchange/dnscontrol/v4/models" + "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" +) // AuditRecords returns a list of errors corresponding to the records // that aren't supported by this provider. If all records are // supported, an empty list is returned. func AuditRecords(records []*models.RecordConfig) []error { - return nil + a := rejectif.Auditor{} + + a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-01-01 + + a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2025-01-01 + + a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2025-01-01 + + return a.Audit(records) } diff --git a/providers/axfrddns/axfrddnsProvider.go b/providers/axfrddns/axfrddnsProvider.go index 942b4355f..25ecd2e32 100644 --- a/providers/axfrddns/axfrddnsProvider.go +++ b/providers/axfrddns/axfrddnsProvider.go @@ -161,6 +161,7 @@ func initAxfrDdns(config map[string]string, providermeta json.RawMessage) (provi "transfer-server", "update-mode", "transfer-mode", + "buggy-cname", "domain", "TYPE": continue