From 6b6ab3fd51064b9860f3e062ba7f2fd9fad0b9b8 Mon Sep 17 00:00:00 2001 From: Daniel Pfankuchen Date: Wed, 25 Mar 2020 09:34:23 +0100 Subject: [PATCH] cloudns: Add subuser support (#1098) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_cloudns.md | 1 + providers/dns/cloudns/cloudns.go | 19 +++++-- providers/dns/cloudns/cloudns.toml | 1 + providers/dns/cloudns/cloudns_test.go | 52 ++++++++++++++++--- providers/dns/cloudns/internal/client.go | 17 ++++-- providers/dns/cloudns/internal/client_test.go | 41 ++++++++++++--- 7 files changed, 112 insertions(+), 20 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 0e5cfb12..d9c810be 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -305,6 +305,7 @@ func displayDNSHelp(name string) error { ew.writeln(` - "CLOUDNS_HTTP_TIMEOUT": API request timeout`) ew.writeln(` - "CLOUDNS_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "CLOUDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "CLOUDNS_SUB_AUTH_ID": The API sub user ID`) ew.writeln(` - "CLOUDNS_TTL": The TTL of the TXT record used for the DNS challenge`) ew.writeln() diff --git a/docs/content/dns/zz_gen_cloudns.md b/docs/content/dns/zz_gen_cloudns.md index 14e792bb..f5823f31 100644 --- a/docs/content/dns/zz_gen_cloudns.md +++ b/docs/content/dns/zz_gen_cloudns.md @@ -43,6 +43,7 @@ More information [here](/lego/dns/#configuration-and-credentials). | `CLOUDNS_HTTP_TIMEOUT` | API request timeout | | `CLOUDNS_POLLING_INTERVAL` | Time between DNS propagation check | | `CLOUDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `CLOUDNS_SUB_AUTH_ID` | The API sub user ID | | `CLOUDNS_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/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index e8483044..73587797 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -17,6 +17,7 @@ const ( envNamespace = "CLOUDNS_" EnvAuthID = envNamespace + "AUTH_ID" + EnvSubAuthID = envNamespace + "SUB_AUTH_ID" EnvAuthPassword = envNamespace + "AUTH_PASSWORD" EnvTTL = envNamespace + "TTL" @@ -28,6 +29,7 @@ const ( // Config is used to configure the creation of the DNSProvider type Config struct { AuthID string + SubAuthID string AuthPassword string PropagationTimeout time.Duration PollingInterval time.Duration @@ -57,13 +59,24 @@ type DNSProvider struct { // Credentials must be passed in the environment variables: // CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAuthID, EnvAuthPassword) + var subAuthID string + authID := env.GetOrFile(EnvAuthID) + if authID == "" { + subAuthID = env.GetOrFile(EnvSubAuthID) + } + + if authID == "" && subAuthID == "" { + return nil, fmt.Errorf("ClouDNS: some credentials information are missing: %s or %s", EnvAuthID, EnvSubAuthID) + } + + values, err := env.Get(EnvAuthPassword) if err != nil { return nil, fmt.Errorf("ClouDNS: %w", err) } config := NewDefaultConfig() - config.AuthID = values[EnvAuthID] + config.AuthID = authID + config.SubAuthID = subAuthID config.AuthPassword = values[EnvAuthPassword] return NewDNSProviderConfig(config) @@ -75,7 +88,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("ClouDNS: the configuration of the DNS provider is nil") } - client, err := internal.NewClient(config.AuthID, config.AuthPassword) + client, err := internal.NewClient(config.AuthID, config.SubAuthID, config.AuthPassword) if err != nil { return nil, fmt.Errorf("ClouDNS: %w", err) } diff --git a/providers/dns/cloudns/cloudns.toml b/providers/dns/cloudns/cloudns.toml index 95176a99..493fa196 100644 --- a/providers/dns/cloudns/cloudns.toml +++ b/providers/dns/cloudns/cloudns.toml @@ -11,6 +11,7 @@ Example = '''''' CLOUDNS_AUTH_ID = "The API user ID" CLOUDNS_AUTH_PASSWORD = "The password for API user ID" [Configuration.Additional] + CLOUDNS_SUB_AUTH_ID = "The API sub user ID" CLOUDNS_POLLING_INTERVAL = "Time between DNS propagation check" CLOUDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" CLOUDNS_TTL = "The TTL of the TXT record used for the DNS challenge" diff --git a/providers/dns/cloudns/cloudns_test.go b/providers/dns/cloudns/cloudns_test.go index e138f62d..678f5b6a 100644 --- a/providers/dns/cloudns/cloudns_test.go +++ b/providers/dns/cloudns/cloudns_test.go @@ -12,6 +12,7 @@ const envDomain = envNamespace + "DOMAIN" var envTest = tester.NewEnvTest( EnvAuthID, + EnvSubAuthID, EnvAuthPassword). WithDomain(envDomain) @@ -22,9 +23,18 @@ func TestNewDNSProvider(t *testing.T) { expected string }{ { - desc: "success", + desc: "success auth-id", envVars: map[string]string{ EnvAuthID: "123", + EnvSubAuthID: "", + EnvAuthPassword: "456", + }, + }, + { + desc: "success sub-auth-id", + envVars: map[string]string{ + EnvAuthID: "", + EnvSubAuthID: "123", EnvAuthPassword: "456", }, }, @@ -32,22 +42,34 @@ func TestNewDNSProvider(t *testing.T) { desc: "missing credentials", envVars: map[string]string{ EnvAuthID: "", + EnvSubAuthID: "", EnvAuthPassword: "", }, - expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_ID,CLOUDNS_AUTH_PASSWORD", + expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID", }, { desc: "missing auth-id", envVars: map[string]string{ EnvAuthID: "", + EnvSubAuthID: "", EnvAuthPassword: "456", }, - expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_ID", + expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID", + }, + { + desc: "missing sub-auth-id", + envVars: map[string]string{ + EnvAuthID: "", + EnvSubAuthID: "", + EnvAuthPassword: "456", + }, + expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID", }, { desc: "missing auth-password", envVars: map[string]string{ EnvAuthID: "123", + EnvSubAuthID: "", EnvAuthPassword: "", }, expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_PASSWORD", @@ -79,22 +101,39 @@ func TestNewDNSProviderConfig(t *testing.T) { testCases := []struct { desc string authID string + subAuthID string authPassword string expected string }{ { - desc: "success", + desc: "success auth-id", authID: "123", + subAuthID: "", + authPassword: "456", + }, + { + desc: "success sub-auth-id", + authID: "", + subAuthID: "123", authPassword: "456", }, { desc: "missing credentials", - expected: "ClouDNS: credentials missing: authID", + expected: "ClouDNS: credentials missing: authID or subAuthID", }, { desc: "missing auth-id", + authID: "", + subAuthID: "", authPassword: "456", - expected: "ClouDNS: credentials missing: authID", + expected: "ClouDNS: credentials missing: authID or subAuthID", + }, + { + desc: "missing sub-auth-id", + authID: "", + subAuthID: "", + authPassword: "456", + expected: "ClouDNS: credentials missing: authID or subAuthID", }, { desc: "missing auth-password", @@ -107,6 +146,7 @@ func TestNewDNSProviderConfig(t *testing.T) { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() config.AuthID = test.authID + config.SubAuthID = test.subAuthID config.AuthPassword = test.authPassword p, err := NewDNSProviderConfig(config) diff --git a/providers/dns/cloudns/internal/client.go b/providers/dns/cloudns/internal/client.go index 5cd54313..c547c770 100644 --- a/providers/dns/cloudns/internal/client.go +++ b/providers/dns/cloudns/internal/client.go @@ -41,9 +41,9 @@ type TXTRecord struct { type TXTRecords map[string]TXTRecord // NewClient creates a ClouDNS client -func NewClient(authID string, authPassword string) (*Client, error) { - if authID == "" { - return nil, errors.New("credentials missing: authID") +func NewClient(authID string, subAuthID string, authPassword string) (*Client, error) { + if authID == "" && subAuthID == "" { + return nil, errors.New("credentials missing: authID or subAuthID") } if authPassword == "" { @@ -57,6 +57,7 @@ func NewClient(authID string, authPassword string) (*Client, error) { return &Client{ authID: authID, + subAuthID: subAuthID, authPassword: authPassword, HTTPClient: &http.Client{}, BaseURL: baseURL, @@ -66,6 +67,7 @@ func NewClient(authID string, authPassword string) (*Client, error) { // Client ClouDNS client type Client struct { authID string + subAuthID string authPassword string HTTPClient *http.Client BaseURL *url.URL @@ -229,8 +231,15 @@ func (c *Client) doRequest(method string, url *url.URL) (json.RawMessage, error) func (c *Client) buildRequest(method string, url *url.URL) (*http.Request, error) { q := url.Query() - q.Add("auth-id", c.authID) + + if c.subAuthID != "" { + q.Add("sub-auth-id", c.subAuthID) + } else { + q.Add("auth-id", c.authID) + } + q.Add("auth-password", c.authPassword) + url.RawQuery = q.Encode() req, err := http.NewRequest(method, url.String(), nil) diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index 7816eebc..5575cccf 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -62,7 +62,7 @@ func TestClientGetZone(t *testing.T) { t.Run(test.desc, func(t *testing.T) { server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse)) - client, _ := NewClient("myAuthID", "myAuthPassword") + client, _ := NewClient("myAuthID", "", "myAuthPassword") mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL)) client.BaseURL = mockBaseURL @@ -140,7 +140,9 @@ func TestClientFindTxtRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse)) - client, _ := NewClient("myAuthID", "myAuthPassword") + client, err := NewClient("myAuthID", "", "myAuthPassword") + require.NoError(t, err) + mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL)) client.BaseURL = mockBaseURL @@ -164,6 +166,8 @@ func TestClientAddTxtRecord(t *testing.T) { testCases := []struct { desc string + authID string + subAuthID string zone *Zone authFQDN string value string @@ -172,7 +176,8 @@ func TestClientAddTxtRecord(t *testing.T) { expected expected }{ { - desc: "sub-zone", + desc: "sub-zone", + authID: "myAuthID", zone: &Zone{ Name: "bar.com", Type: "master", @@ -188,7 +193,8 @@ func TestClientAddTxtRecord(t *testing.T) { }, }, { - desc: "main zone", + desc: "main zone (authID)", + authID: "myAuthID", zone: &Zone{ Name: "bar.com", Type: "master", @@ -204,7 +210,26 @@ func TestClientAddTxtRecord(t *testing.T) { }, }, { - desc: "invalid status", + desc: "main zone (subAuthID)", + authID: "myAuthID", + subAuthID: "mySubAuthID", + zone: &Zone{ + Name: "bar.com", + Type: "master", + Zone: "domain", + Status: "1", + }, + authFQDN: "_acme-challenge.bar.com.", + value: "TXTtxtTXTtxtTXTtxtTXTtxt", + ttl: 60, + apiResponse: []byte(`{"status":"Success","statusDescription":"The record was added successfully."}`), + expected: expected{ + Query: `auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&sub-auth-id=mySubAuthID&ttl=60`, + }, + }, + { + desc: "invalid status", + authID: "myAuthID", zone: &Zone{ Name: "bar.com", Type: "master", @@ -231,11 +256,13 @@ func TestClientAddTxtRecord(t *testing.T) { handlerMock(http.MethodPost, test.apiResponse).ServeHTTP(rw, req) })) - client, _ := NewClient("myAuthID", "myAuthPassword") + client, err := NewClient(test.authID, test.subAuthID, "myAuthPassword") + require.NoError(t, err) + mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL)) client.BaseURL = mockBaseURL - err := client.AddTxtRecord(test.zone.Name, test.authFQDN, test.value, test.ttl) + err = client.AddTxtRecord(test.zone.Name, test.authFQDN, test.value, test.ttl) if test.expected.Error != "" { require.EqualError(t, err, test.expected.Error)