cloudns: Add subuser support (#1098)
This commit is contained in:
parent
692dd913ea
commit
6b6ab3fd51
7 changed files with 112 additions and 20 deletions
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue