diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1ca1e203d1..7008b20d58 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -941,6 +941,7 @@ func displayDNSHelp(name string) error { ew.writeln(` - "INWX_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "INWX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) ew.writeln(` - "INWX_SANDBOX": Activate the sandbox (boolean)`) + ew.writeln(` - "INWX_SHARED_SECRET": shared secret related to 2FA`) ew.writeln(` - "INWX_TTL": The TTL of the TXT record used for the DNS challenge`) ew.writeln() diff --git a/docs/content/dns/zz_gen_inwx.md b/docs/content/dns/zz_gen_inwx.md index a8a43f29f8..9adcdc0544 100644 --- a/docs/content/dns/zz_gen_inwx.md +++ b/docs/content/dns/zz_gen_inwx.md @@ -18,9 +18,19 @@ Configuration for [INWX](https://www.inwx.de/en). - Code: `inwx` -{{% notice note %}} -_Please contribute by adding a CLI example._ -{{% /notice %}} +Here is an example bash command using the INWX provider: + +```bash +INWX_USERNAME=xxxxxxxxxx \ +INWX_PASSWORD=yyyyyyyyyy \ +lego --dns inwx --domains my.domain.com --email my@email.com run + +# 2FA +INWX_USERNAME=xxxxxxxxxx \ +INWX_PASSWORD=yyyyyyyyyy \ +INWX_SHARED_SECRET=zzzzzzzzzz \ +lego --dns inwx --domains my.domain.com --email my@email.com run +``` @@ -43,6 +53,7 @@ More information [here](/lego/dns/#configuration-and-credentials). | `INWX_POLLING_INTERVAL` | Time between DNS propagation check | | `INWX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | | `INWX_SANDBOX` | Activate the sandbox (boolean) | +| `INWX_SHARED_SECRET` | shared secret related to 2FA | | `INWX_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/go.mod b/go.mod index 996fbaa9e5..cb5b654999 100644 --- a/go.mod +++ b/go.mod @@ -30,10 +30,11 @@ require ( github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.0.1 github.com/nrdcg/dnspod-go v0.4.0 - github.com/nrdcg/goinwx v0.6.1 + github.com/nrdcg/goinwx v0.7.0 github.com/nrdcg/namesilo v0.2.1 github.com/oracle/oci-go-sdk v7.0.0+incompatible github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 + github.com/pquerna/otp v1.2.0 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 github.com/sacloud/libsacloud v1.26.1 github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index 63d21e1ed7..c7421e204d 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/aws/aws-sdk-go v1.30.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU= github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= @@ -202,8 +204,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181 h1:TrxPzApUukas24OMMVDUMlCs1XCExJtnGaDEiIAR4oQ= -github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= +github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc= +github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -231,8 +233,8 @@ github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= +github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -249,8 +251,8 @@ github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= -github.com/nrdcg/goinwx v0.6.1 h1:AJnjoWPELyCtofhGcmzzcEMFd9YdF2JB/LgutWsWt/s= -github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ= +github.com/nrdcg/goinwx v0.7.0 h1:j6JlOp0nNwtvaP09TvKqc9pktjH81nOad0+Gx9S1t9U= +github.com/nrdcg/goinwx v0.7.0/go.mod h1:4tKJOCi/1lTxuw9/yB2Ez0aojwtUCSkckjc22eALpqE= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -270,6 +272,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 20f08ee999..8deb3f5539 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -10,15 +10,17 @@ import ( "github.com/go-acme/lego/v3/log" "github.com/go-acme/lego/v3/platform/config/env" "github.com/nrdcg/goinwx" + "github.com/pquerna/otp/totp" ) // Environment variables names. const ( envNamespace = "INWX_" - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - EnvSandbox = envNamespace + "SANDBOX" + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvSharedSecret = envNamespace + "SHARED_SECRET" + EnvSandbox = envNamespace + "SANDBOX" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -29,6 +31,7 @@ const ( type Config struct { Username string Password string + SharedSecret string Sandbox bool PropagationTimeout time.Duration PollingInterval time.Duration @@ -53,7 +56,7 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Dyn DNS. // Credentials must be passed in the environment variables: -// INWX_USERNAME and INWX_PASSWORD. +// INWX_USERNAME, INWX_PASSWORD, and INWX_SHARED_SECRET. func NewDNSProvider() (*DNSProvider, error) { values, err := env.Get(EnvUsername, EnvPassword) if err != nil { @@ -63,6 +66,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Username = values[EnvUsername] config.Password = values[EnvPassword] + config.SharedSecret = env.GetOrFile(EnvSharedSecret) return NewDNSProviderConfig(config) } @@ -95,7 +99,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("inwx: %w", err) } - err = d.client.Account.Login() + info, err := d.client.Account.Login() if err != nil { return fmt.Errorf("inwx: %w", err) } @@ -107,6 +111,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } }() + err = d.twoFactorAuth(info) + if err != nil { + return fmt.Errorf("inwx: %w", err) + } + var request = &goinwx.NameserverRecordRequest{ Domain: dns01.UnFqdn(authZone), Name: dns01.UnFqdn(fqdn), @@ -140,7 +149,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("inwx: %w", err) } - err = d.client.Account.Login() + info, err := d.client.Account.Login() if err != nil { return fmt.Errorf("inwx: %w", err) } @@ -152,6 +161,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } }() + err = d.twoFactorAuth(info) + if err != nil { + return fmt.Errorf("inwx: %w", err) + } + response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{ Domain: dns01.UnFqdn(authZone), Name: dns01.UnFqdn(fqdn), @@ -177,3 +191,20 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +func (d *DNSProvider) twoFactorAuth(info *goinwx.LoginResponse) error { + if info.TFA != "GOOGLE-AUTH" { + return nil + } + + if d.config.SharedSecret == "" { + return errors.New("two factor authentication but no shared secret is given") + } + + tan, err := totp.GenerateCode(d.config.SharedSecret, time.Now()) + if err != nil { + return err + } + + return d.client.Account.Unlock(tan) +} diff --git a/providers/dns/inwx/inwx.toml b/providers/dns/inwx/inwx.toml index 31434800c1..95a9494a57 100644 --- a/providers/dns/inwx/inwx.toml +++ b/providers/dns/inwx/inwx.toml @@ -4,13 +4,24 @@ URL = "https://www.inwx.de/en" Code = "inwx" Since = "v2.0.0" -Example = '''''' +Example = ''' +INWX_USERNAME=xxxxxxxxxx \ +INWX_PASSWORD=yyyyyyyyyy \ +lego --dns inwx --domains my.domain.com --email my@email.com run + +# 2FA +INWX_USERNAME=xxxxxxxxxx \ +INWX_PASSWORD=yyyyyyyyyy \ +INWX_SHARED_SECRET=zzzzzzzzzz \ +lego --dns inwx --domains my.domain.com --email my@email.com run +''' [Configuration] [Configuration.Credentials] INWX_USERNAME = "Username" INWX_PASSWORD = "Password" [Configuration.Additional] + INWX_SHARED_SECRET = "shared secret related to 2FA" INWX_POLLING_INTERVAL = "Time between DNS propagation check" INWX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" INWX_TTL = "The TTL of the TXT record used for the DNS challenge" diff --git a/providers/dns/inwx/inwx_test.go b/providers/dns/inwx/inwx_test.go index 9ea1d2d7dd..e0ecefcf4b 100644 --- a/providers/dns/inwx/inwx_test.go +++ b/providers/dns/inwx/inwx_test.go @@ -12,6 +12,7 @@ const envDomain = envNamespace + "DOMAIN" var envTest = tester.NewEnvTest( EnvUsername, EnvPassword, + EnvSharedSecret, EnvSandbox, EnvTTL). WithDomain(envDomain).