diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 936a99ec4b..dd217c7170 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2428,9 +2428,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "RFC2136_NAMESERVER": Network address in the form "host" or "host:port"`) - ew.writeln(` - "RFC2136_TSIG_ALGORITHM": TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the 'RFC2136_TSIG*' variables unset.`) - ew.writeln(` - "RFC2136_TSIG_KEY": Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the 'RFC2136_TSIG*' variables unset.`) - ew.writeln(` - "RFC2136_TSIG_SECRET": Secret key payload. To disable TSIG authentication, leave the' RFC2136_TSIG*' variables unset.`) + ew.writeln(` - "RFC2136_TSIG_ALGORITHM": TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the 'RFC2136_TSIG_KEY' or 'RFC2136_TSIG_SECRET' variables unset.`) + ew.writeln(` - "RFC2136_TSIG_KEY": Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the 'RFC2136_TSIG_KEY' variable unset.`) + ew.writeln(` - "RFC2136_TSIG_SECRET": Secret key payload. To disable TSIG authentication, leave the 'RFC2136_TSIG_SECRET' variable unset.`) ew.writeln() ew.writeln(`Additional Configuration:`) @@ -2438,6 +2438,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "RFC2136_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "RFC2136_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) ew.writeln(` - "RFC2136_SEQUENCE_INTERVAL": Time between sequential requests`) + ew.writeln(` - "RFC2136_TSIG_FILE": Path to a key file generated by tsig-keygen`) ew.writeln(` - "RFC2136_TTL": The TTL of the TXT record used for the DNS challenge`) ew.writeln() diff --git a/docs/content/dns/zz_gen_rfc2136.md b/docs/content/dns/zz_gen_rfc2136.md index d203992658..ad52005d4e 100644 --- a/docs/content/dns/zz_gen_rfc2136.md +++ b/docs/content/dns/zz_gen_rfc2136.md @@ -27,20 +27,18 @@ Here is an example bash command using the RFC2136 provider: ```bash RFC2136_NAMESERVER=127.0.0.1 \ -RFC2136_TSIG_KEY=lego \ +RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ## --- -keyname=lego; keyfile=lego.key; tsig-keygen $keyname > $keyfile +keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ -RFC2136_TSIG_KEY="$keyname" \ -RFC2136_TSIG_ALGORITHM="$( awk -F'[ ";]' '/algorithm/ { print $2 }' $keyfile )." \ -RFC2136_TSIG_SECRET="$( awk -F'[ ";]' '/secret/ { print $3 }' $keyfile )" \ -lego --email you@example.com --dns rfc2136 d "*.example.com" -d example.com run +RFC2136_TSIG_FILE="$keyfile" \ +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ``` @@ -51,9 +49,9 @@ lego --email you@example.com --dns rfc2136 d "*.example.com" -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| | `RFC2136_NAMESERVER` | Network address in the form "host" or "host:port" | -| `RFC2136_TSIG_ALGORITHM` | TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset. | -| `RFC2136_TSIG_KEY` | Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset. | -| `RFC2136_TSIG_SECRET` | Secret key payload. To disable TSIG authentication, leave the` RFC2136_TSIG*` variables unset. | +| `RFC2136_TSIG_ALGORITHM` | TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` or `RFC2136_TSIG_SECRET` variables unset. | +| `RFC2136_TSIG_KEY` | Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` variable unset. | +| `RFC2136_TSIG_SECRET` | Secret key payload. To disable TSIG authentication, leave the `RFC2136_TSIG_SECRET` variable unset. | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). @@ -67,6 +65,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `RFC2136_POLLING_INTERVAL` | Time between DNS propagation check | | `RFC2136_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | | `RFC2136_SEQUENCE_INTERVAL` | Time between sequential requests | +| `RFC2136_TSIG_FILE` | Path to a key file generated by tsig-keygen | | `RFC2136_TTL` | The TTL of the TXT record used for the DNS challenge | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/rfc2136/internal/fixtures/invalid_field.conf b/providers/dns/rfc2136/internal/fixtures/invalid_field.conf new file mode 100644 index 0000000000..07c6a7be24 --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/invalid_field.conf @@ -0,0 +1,4 @@ +key "example.com" { + algorithm; + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/invalid_key.conf b/providers/dns/rfc2136/internal/fixtures/invalid_key.conf new file mode 100644 index 0000000000..965888eaeb --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/invalid_key.conf @@ -0,0 +1,4 @@ +key { + algorithm hmac-sha256; + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/mising_algo.conf b/providers/dns/rfc2136/internal/fixtures/mising_algo.conf new file mode 100644 index 0000000000..530323172d --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/mising_algo.conf @@ -0,0 +1,3 @@ +key "example.com" { + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/missing_secret.conf b/providers/dns/rfc2136/internal/fixtures/missing_secret.conf new file mode 100644 index 0000000000..f45eeac300 --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/missing_secret.conf @@ -0,0 +1,3 @@ +key "example.com" { + algorithm hmac-sha256; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/sample.conf b/providers/dns/rfc2136/internal/fixtures/sample.conf new file mode 100644 index 0000000000..6e249e8a58 --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/sample.conf @@ -0,0 +1,4 @@ +key "example.com" { + algorithm hmac-sha256; + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/text_after.conf b/providers/dns/rfc2136/internal/fixtures/text_after.conf new file mode 100644 index 0000000000..9b1cf8e587 --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/text_after.conf @@ -0,0 +1,9 @@ +key "example.com" { + algorithm hmac-sha256; + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; + +key "example.org" { + algorithm hmac-sha512; + secret "v6CkK3gop6HXj4+dcWiLXLGSYKVY5J1cTMjDsdl/Ah9B8aWfTgjwFBoHHyiHWSyvwWPDuEIRs2Pqm8nedca4+g=="; +}; diff --git a/providers/dns/rfc2136/internal/fixtures/text_before.conf b/providers/dns/rfc2136/internal/fixtures/text_before.conf new file mode 100644 index 0000000000..0a8415b21e --- /dev/null +++ b/providers/dns/rfc2136/internal/fixtures/text_before.conf @@ -0,0 +1,8 @@ +foo { + bar example; +}; + +key "example.com" { + algorithm hmac-sha256; + secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="; +}; diff --git a/providers/dns/rfc2136/internal/readme.md b/providers/dns/rfc2136/internal/readme.md new file mode 100644 index 0000000000..d0ecae7f49 --- /dev/null +++ b/providers/dns/rfc2136/internal/readme.md @@ -0,0 +1,10 @@ +# TSIG Key File + +How to generate example: + +```console +$ docker run --rm -it -v $(pwd):/app -w /app alpine sh +/app # apk add bind +/app # tsig-keygen example.com > sample1.conf +/app # tsig-keygen -a hmac-sha512 example.com > sample2.conf +``` diff --git a/providers/dns/rfc2136/internal/tsigkey.go b/providers/dns/rfc2136/internal/tsigkey.go new file mode 100644 index 0000000000..b4672f44dd --- /dev/null +++ b/providers/dns/rfc2136/internal/tsigkey.go @@ -0,0 +1,89 @@ +package internal + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +type Key struct { + Name string + Algorithm string + Secret string +} + +// ReadTSIGFile reads TSIG key file generated with `tsig-keygen`. +func ReadTSIGFile(filename string) (*Key, error) { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("open file: %w", err) + } + + defer func() { _ = file.Close() }() + + key := &Key{} + + var read bool + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(strings.TrimSuffix(scanner.Text(), ";")) + + if line == "" { + continue + } + + if read && line == "}" { + break + } + + fields := strings.Fields(line) + + switch { + case fields[0] == "key": + read = true + + if len(fields) != 3 { + return nil, fmt.Errorf("invalid key line: %s", line) + } + + key.Name = safeUnquote(fields[1]) + + case !read: + continue + + default: + if len(fields) != 2 { + continue + } + + v := safeUnquote(fields[1]) + + switch safeUnquote(fields[0]) { + case "algorithm": + key.Algorithm = v + case "secret": + key.Secret = v + default: + continue + } + } + } + + return key, nil +} + +func safeUnquote(v string) string { + if len(v) < 2 { + // empty or single character string + return v + } + + if v[0] == '"' && v[len(v)-1] == '"' { + // string wrapped in quotes + return v[1 : len(v)-1] + } + + return v +} diff --git a/providers/dns/rfc2136/internal/tsigkey_test.go b/providers/dns/rfc2136/internal/tsigkey_test.go new file mode 100644 index 0000000000..4ed7f66168 --- /dev/null +++ b/providers/dns/rfc2136/internal/tsigkey_test.go @@ -0,0 +1,95 @@ +package internal + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReadTSIGFile(t *testing.T) { + testCases := []struct { + desc string + filename string + expected *Key + }{ + { + desc: "basic", + filename: "sample.conf", + expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="}, + }, + { + desc: "data before the key", + filename: "text_before.conf", + expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="}, + }, + { + desc: "data after the key", + filename: "text_after.conf", + expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="}, + }, + { + desc: "ignore missing secret", + filename: "missing_secret.conf", + expected: &Key{Name: "example.com", Algorithm: "hmac-sha256"}, + }, + { + desc: "ignore missing algorithm", + filename: "mising_algo.conf", + expected: &Key{Name: "example.com", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="}, + }, + { + desc: "ignore invalid field format", + filename: "invalid_field.conf", + expected: &Key{Name: "example.com", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + key, err := ReadTSIGFile(filepath.Join("fixtures", test.filename)) + require.NoError(t, err) + + assert.Equal(t, test.expected, key) + }) + } +} + +func TestReadTSIGFile_error(t *testing.T) { + if runtime.GOOS != "linux" { + // Because error messages are different on Windows. + t.Skip("only for UNIX systems") + } + + testCases := []struct { + desc string + filename string + expected string + }{ + { + desc: "missing file", + filename: "missing.conf", + expected: "open file: open fixtures/missing.conf: no such file or directory", + }, + { + desc: "invalid key format", + filename: "invalid_key.conf", + expected: "invalid key line: key {", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + _, err := ReadTSIGFile(filepath.Join("fixtures", test.filename)) + require.Error(t, err) + + require.EqualError(t, err, test.expected) + }) + } +} diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 8a7dedc803..bd1d58a0cf 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/rfc2136/internal" "github.com/miekg/dns" ) @@ -17,11 +18,14 @@ import ( const ( envNamespace = "RFC2136_" + EnvTSIGFile = envNamespace + "TSIG_FILE" + EnvTSIGKey = envNamespace + "TSIG_KEY" EnvTSIGSecret = envNamespace + "TSIG_SECRET" EnvTSIGAlgorithm = envNamespace + "TSIG_ALGORITHM" - EnvNameserver = envNamespace + "NAMESERVER" - EnvDNSTimeout = envNamespace + "DNS_TIMEOUT" + + EnvNameserver = envNamespace + "NAMESERVER" + EnvDNSTimeout = envNamespace + "DNS_TIMEOUT" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -31,10 +35,14 @@ const ( // Config is used to configure the creation of the DNSProvider. type Config struct { - Nameserver string - TSIGAlgorithm string - TSIGKey string - TSIGSecret string + Nameserver string + + TSIGFile string + + TSIGAlgorithm string + TSIGKey string + TSIGSecret string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -76,6 +84,9 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Nameserver = values[EnvNameserver] + + config.TSIGFile = env.GetOrDefaultString(EnvTSIGFile, "") + config.TSIGKey = env.GetOrFile(EnvTSIGKey) config.TSIGSecret = env.GetOrFile(EnvTSIGSecret) @@ -92,8 +103,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("rfc2136: nameserver missing") } - if config.TSIGAlgorithm == "" { - config.TSIGAlgorithm = dns.HmacSHA1 + if config.TSIGFile != "" { + key, err := internal.ReadTSIGFile(config.TSIGFile) + if err != nil { + return nil, fmt.Errorf("rfc2136: read TSIG file %s: %w", config.TSIGFile, err) + } + + config.TSIGAlgorithm = key.Algorithm + config.TSIGKey = key.Name + config.TSIGSecret = key.Secret } // Append the default DNS port if none is specified. @@ -108,6 +126,23 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.TSIGKey == "" || config.TSIGSecret == "" { config.TSIGKey = "" config.TSIGSecret = "" + } else { + // zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) + config.TSIGKey = strings.ToLower(dns.Fqdn(config.TSIGKey)) + } + + if config.TSIGAlgorithm == "" { + config.TSIGAlgorithm = dns.HmacSHA1 + } else { + // To be compatible with https://github.com/miekg/dns/blob/master/tsig.go + config.TSIGAlgorithm = dns.Fqdn(config.TSIGAlgorithm) + } + + switch config.TSIGAlgorithm { + case dns.HmacSHA1, dns.HmacSHA224, dns.HmacSHA256, dns.HmacSHA384, dns.HmacSHA512: + // valid algorithm + default: + return nil, fmt.Errorf("rfc2136: unsupported TSIG algorithm: %s", config.TSIGAlgorithm) } return &DNSProvider{config: config}, nil @@ -179,13 +214,10 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { // TSIG authentication / msg signing if d.config.TSIGKey != "" && d.config.TSIGSecret != "" { - key := strings.ToLower(dns.Fqdn(d.config.TSIGKey)) - alg := dns.Fqdn(d.config.TSIGAlgorithm) - m.SetTsig(key, alg, 300, time.Now().Unix()) + m.SetTsig(d.config.TSIGKey, d.config.TSIGAlgorithm, 300, time.Now().Unix()) - // secret(s) for Tsig map[], - // zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) - c.TsigSecret = map[string]string{key: d.config.TSIGSecret} + // Secret(s) for TSIG map[]. + c.TsigSecret = map[string]string{d.config.TSIGKey: d.config.TSIGSecret} } // Send the query diff --git a/providers/dns/rfc2136/rfc2136.toml b/providers/dns/rfc2136/rfc2136.toml index 621acc8026..df313fde77 100644 --- a/providers/dns/rfc2136/rfc2136.toml +++ b/providers/dns/rfc2136/rfc2136.toml @@ -6,29 +6,28 @@ Since = "v0.3.0" Example = ''' RFC2136_NAMESERVER=127.0.0.1 \ -RFC2136_TSIG_KEY=lego \ +RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ## --- -keyname=lego; keyfile=lego.key; tsig-keygen $keyname > $keyfile +keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ -RFC2136_TSIG_KEY="$keyname" \ -RFC2136_TSIG_ALGORITHM="$( awk -F'[ ";]' '/algorithm/ { print $2 }' $keyfile )." \ -RFC2136_TSIG_SECRET="$( awk -F'[ ";]' '/secret/ { print $3 }' $keyfile )" \ -lego --email you@example.com --dns rfc2136 d "*.example.com" -d example.com run +RFC2136_TSIG_FILE="$keyfile" \ +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ''' [Configuration] [Configuration.Credentials] - RFC2136_TSIG_KEY = "Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset." - RFC2136_TSIG_SECRET = "Secret key payload. To disable TSIG authentication, leave the` RFC2136_TSIG*` variables unset." - RFC2136_TSIG_ALGORITHM = "TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset." + RFC2136_TSIG_KEY = "Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` variable unset." + RFC2136_TSIG_SECRET = "Secret key payload. To disable TSIG authentication, leave the `RFC2136_TSIG_SECRET` variable unset." + RFC2136_TSIG_ALGORITHM = "TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` or `RFC2136_TSIG_SECRET` variables unset." RFC2136_NAMESERVER = 'Network address in the form "host" or "host:port"' [Configuration.Additional] + RFC2136_TSIG_FILE = "Path to a key file generated by tsig-keygen" RFC2136_POLLING_INTERVAL = "Time between DNS propagation check" RFC2136_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" RFC2136_TTL = "The TTL of the TXT record used for the DNS challenge" diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index 235ce4e4e5..80fdc69cbd 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/tester" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,6 +27,142 @@ const ( fakeTsigSecret = "IwBTJx9wrDp4Y1RyC3H0gA==" ) +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvTSIGFile, + EnvTSIGKey, + EnvTSIGSecret, + EnvTSIGAlgorithm, + EnvNameserver, + EnvDNSTimeout, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvNameserver: "example.com", + }, + }, + { + desc: "missing nameserver", + envVars: map[string]string{ + EnvNameserver: "", + }, + expected: "rfc2136: some credentials information are missing: RFC2136_NAMESERVER", + }, + { + desc: "invalid algorithm", + envVars: map[string]string{ + EnvNameserver: "example.com", + EnvTSIGKey: "", + EnvTSIGSecret: "", + EnvTSIGAlgorithm: "foo", + }, + expected: "rfc2136: unsupported TSIG algorithm: foo.", + }, + { + desc: "valid TSIG file", + envVars: map[string]string{ + EnvNameserver: "example.com", + EnvTSIGFile: "./internal/fixtures/sample.conf", + }, + }, + { + desc: "invalid TSIG file", + envVars: map[string]string{ + EnvNameserver: "example.com", + EnvTSIGFile: "./internal/fixtures/invalid_key.conf", + }, + expected: "rfc2136: read TSIG file ./internal/fixtures/invalid_key.conf: invalid key line: key {", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + expected string + nameserver string + tsigFile string + tsigAlgorithm string + tsigKey string + tsigSecret string + }{ + { + desc: "success", + nameserver: "example.com", + }, + { + desc: "missing nameserver", + expected: "rfc2136: nameserver missing", + }, + { + desc: "invalid algorithm", + nameserver: "example.com", + tsigAlgorithm: "foo", + expected: "rfc2136: unsupported TSIG algorithm: foo.", + }, + { + desc: "valid TSIG file", + nameserver: "example.com", + tsigFile: "./internal/fixtures/sample.conf", + }, + { + desc: "invalid TSIG file", + nameserver: "example.com", + tsigFile: "./internal/fixtures/invalid_key.conf", + expected: "rfc2136: read TSIG file ./internal/fixtures/invalid_key.conf: invalid key line: key {", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Nameserver = test.nameserver + config.TSIGFile = test.tsigFile + config.TSIGAlgorithm = test.tsigAlgorithm + config.TSIGKey = test.tsigKey + config.TSIGSecret = test.tsigSecret + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + func TestCanaryLocalTestServer(t *testing.T) { dns01.ClearFqdnCache() dns.HandleFunc("example.com.", serverHandlerHello)