forked from TrueCloudLab/lego
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "CLOUDNS_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "CLOUDNS_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "CLOUDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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(` - "CLOUDNS_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
|
@ -43,6 +43,7 @@ More information [here](/lego/dns/#configuration-and-credentials).
|
||||||
| `CLOUDNS_HTTP_TIMEOUT` | API request timeout |
|
| `CLOUDNS_HTTP_TIMEOUT` | API request timeout |
|
||||||
| `CLOUDNS_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `CLOUDNS_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||||
| `CLOUDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `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 |
|
| `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.
|
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||||
|
|
|
@ -17,6 +17,7 @@ const (
|
||||||
envNamespace = "CLOUDNS_"
|
envNamespace = "CLOUDNS_"
|
||||||
|
|
||||||
EnvAuthID = envNamespace + "AUTH_ID"
|
EnvAuthID = envNamespace + "AUTH_ID"
|
||||||
|
EnvSubAuthID = envNamespace + "SUB_AUTH_ID"
|
||||||
EnvAuthPassword = envNamespace + "AUTH_PASSWORD"
|
EnvAuthPassword = envNamespace + "AUTH_PASSWORD"
|
||||||
|
|
||||||
EnvTTL = envNamespace + "TTL"
|
EnvTTL = envNamespace + "TTL"
|
||||||
|
@ -28,6 +29,7 @@ const (
|
||||||
// Config is used to configure the creation of the DNSProvider
|
// Config is used to configure the creation of the DNSProvider
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AuthID string
|
AuthID string
|
||||||
|
SubAuthID string
|
||||||
AuthPassword string
|
AuthPassword string
|
||||||
PropagationTimeout time.Duration
|
PropagationTimeout time.Duration
|
||||||
PollingInterval time.Duration
|
PollingInterval time.Duration
|
||||||
|
@ -57,13 +59,24 @@ type DNSProvider struct {
|
||||||
// Credentials must be passed in the environment variables:
|
// Credentials must be passed in the environment variables:
|
||||||
// CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD.
|
// CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD.
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ClouDNS: %w", err)
|
return nil, fmt.Errorf("ClouDNS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
config := NewDefaultConfig()
|
||||||
config.AuthID = values[EnvAuthID]
|
config.AuthID = authID
|
||||||
|
config.SubAuthID = subAuthID
|
||||||
config.AuthPassword = values[EnvAuthPassword]
|
config.AuthPassword = values[EnvAuthPassword]
|
||||||
|
|
||||||
return NewDNSProviderConfig(config)
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ClouDNS: %w", err)
|
return nil, fmt.Errorf("ClouDNS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ Example = ''''''
|
||||||
CLOUDNS_AUTH_ID = "The API user ID"
|
CLOUDNS_AUTH_ID = "The API user ID"
|
||||||
CLOUDNS_AUTH_PASSWORD = "The password for API user ID"
|
CLOUDNS_AUTH_PASSWORD = "The password for API user ID"
|
||||||
[Configuration.Additional]
|
[Configuration.Additional]
|
||||||
|
CLOUDNS_SUB_AUTH_ID = "The API sub user ID"
|
||||||
CLOUDNS_POLLING_INTERVAL = "Time between DNS propagation check"
|
CLOUDNS_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
CLOUDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
CLOUDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
CLOUDNS_TTL = "The TTL of the TXT record used for the DNS challenge"
|
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(
|
var envTest = tester.NewEnvTest(
|
||||||
EnvAuthID,
|
EnvAuthID,
|
||||||
|
EnvSubAuthID,
|
||||||
EnvAuthPassword).
|
EnvAuthPassword).
|
||||||
WithDomain(envDomain)
|
WithDomain(envDomain)
|
||||||
|
|
||||||
|
@ -22,9 +23,18 @@ func TestNewDNSProvider(t *testing.T) {
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "success",
|
desc: "success auth-id",
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
EnvAuthID: "123",
|
EnvAuthID: "123",
|
||||||
|
EnvSubAuthID: "",
|
||||||
|
EnvAuthPassword: "456",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "success sub-auth-id",
|
||||||
|
envVars: map[string]string{
|
||||||
|
EnvAuthID: "",
|
||||||
|
EnvSubAuthID: "123",
|
||||||
EnvAuthPassword: "456",
|
EnvAuthPassword: "456",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -32,22 +42,34 @@ func TestNewDNSProvider(t *testing.T) {
|
||||||
desc: "missing credentials",
|
desc: "missing credentials",
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
EnvAuthID: "",
|
EnvAuthID: "",
|
||||||
|
EnvSubAuthID: "",
|
||||||
EnvAuthPassword: "",
|
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",
|
desc: "missing auth-id",
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
EnvAuthID: "",
|
EnvAuthID: "",
|
||||||
|
EnvSubAuthID: "",
|
||||||
EnvAuthPassword: "456",
|
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",
|
desc: "missing auth-password",
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
EnvAuthID: "123",
|
EnvAuthID: "123",
|
||||||
|
EnvSubAuthID: "",
|
||||||
EnvAuthPassword: "",
|
EnvAuthPassword: "",
|
||||||
},
|
},
|
||||||
expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_PASSWORD",
|
expected: "ClouDNS: some credentials information are missing: CLOUDNS_AUTH_PASSWORD",
|
||||||
|
@ -79,22 +101,39 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
authID string
|
authID string
|
||||||
|
subAuthID string
|
||||||
authPassword string
|
authPassword string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "success",
|
desc: "success auth-id",
|
||||||
authID: "123",
|
authID: "123",
|
||||||
|
subAuthID: "",
|
||||||
|
authPassword: "456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "success sub-auth-id",
|
||||||
|
authID: "",
|
||||||
|
subAuthID: "123",
|
||||||
authPassword: "456",
|
authPassword: "456",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "missing credentials",
|
desc: "missing credentials",
|
||||||
expected: "ClouDNS: credentials missing: authID",
|
expected: "ClouDNS: credentials missing: authID or subAuthID",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "missing auth-id",
|
desc: "missing auth-id",
|
||||||
|
authID: "",
|
||||||
|
subAuthID: "",
|
||||||
authPassword: "456",
|
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",
|
desc: "missing auth-password",
|
||||||
|
@ -107,6 +146,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
config := NewDefaultConfig()
|
config := NewDefaultConfig()
|
||||||
config.AuthID = test.authID
|
config.AuthID = test.authID
|
||||||
|
config.SubAuthID = test.subAuthID
|
||||||
config.AuthPassword = test.authPassword
|
config.AuthPassword = test.authPassword
|
||||||
|
|
||||||
p, err := NewDNSProviderConfig(config)
|
p, err := NewDNSProviderConfig(config)
|
||||||
|
|
|
@ -41,9 +41,9 @@ type TXTRecord struct {
|
||||||
type TXTRecords map[string]TXTRecord
|
type TXTRecords map[string]TXTRecord
|
||||||
|
|
||||||
// NewClient creates a ClouDNS client
|
// NewClient creates a ClouDNS client
|
||||||
func NewClient(authID string, authPassword string) (*Client, error) {
|
func NewClient(authID string, subAuthID string, authPassword string) (*Client, error) {
|
||||||
if authID == "" {
|
if authID == "" && subAuthID == "" {
|
||||||
return nil, errors.New("credentials missing: authID")
|
return nil, errors.New("credentials missing: authID or subAuthID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if authPassword == "" {
|
if authPassword == "" {
|
||||||
|
@ -57,6 +57,7 @@ func NewClient(authID string, authPassword string) (*Client, error) {
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
authID: authID,
|
authID: authID,
|
||||||
|
subAuthID: subAuthID,
|
||||||
authPassword: authPassword,
|
authPassword: authPassword,
|
||||||
HTTPClient: &http.Client{},
|
HTTPClient: &http.Client{},
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
@ -66,6 +67,7 @@ func NewClient(authID string, authPassword string) (*Client, error) {
|
||||||
// Client ClouDNS client
|
// Client ClouDNS client
|
||||||
type Client struct {
|
type Client struct {
|
||||||
authID string
|
authID string
|
||||||
|
subAuthID string
|
||||||
authPassword string
|
authPassword string
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
BaseURL *url.URL
|
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) {
|
func (c *Client) buildRequest(method string, url *url.URL) (*http.Request, error) {
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
|
|
||||||
|
if c.subAuthID != "" {
|
||||||
|
q.Add("sub-auth-id", c.subAuthID)
|
||||||
|
} else {
|
||||||
q.Add("auth-id", c.authID)
|
q.Add("auth-id", c.authID)
|
||||||
|
}
|
||||||
|
|
||||||
q.Add("auth-password", c.authPassword)
|
q.Add("auth-password", c.authPassword)
|
||||||
|
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
req, err := http.NewRequest(method, url.String(), nil)
|
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) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse))
|
server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse))
|
||||||
|
|
||||||
client, _ := NewClient("myAuthID", "myAuthPassword")
|
client, _ := NewClient("myAuthID", "", "myAuthPassword")
|
||||||
mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
|
mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
|
||||||
client.BaseURL = mockBaseURL
|
client.BaseURL = mockBaseURL
|
||||||
|
|
||||||
|
@ -140,7 +140,9 @@ func TestClientFindTxtRecord(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
server := httptest.NewServer(handlerMock(http.MethodGet, test.apiResponse))
|
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))
|
mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
|
||||||
client.BaseURL = mockBaseURL
|
client.BaseURL = mockBaseURL
|
||||||
|
|
||||||
|
@ -164,6 +166,8 @@ func TestClientAddTxtRecord(t *testing.T) {
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
authID string
|
||||||
|
subAuthID string
|
||||||
zone *Zone
|
zone *Zone
|
||||||
authFQDN string
|
authFQDN string
|
||||||
value string
|
value string
|
||||||
|
@ -173,6 +177,7 @@ func TestClientAddTxtRecord(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "sub-zone",
|
desc: "sub-zone",
|
||||||
|
authID: "myAuthID",
|
||||||
zone: &Zone{
|
zone: &Zone{
|
||||||
Name: "bar.com",
|
Name: "bar.com",
|
||||||
Type: "master",
|
Type: "master",
|
||||||
|
@ -188,7 +193,8 @@ func TestClientAddTxtRecord(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "main zone",
|
desc: "main zone (authID)",
|
||||||
|
authID: "myAuthID",
|
||||||
zone: &Zone{
|
zone: &Zone{
|
||||||
Name: "bar.com",
|
Name: "bar.com",
|
||||||
Type: "master",
|
Type: "master",
|
||||||
|
@ -203,8 +209,27 @@ func TestClientAddTxtRecord(t *testing.T) {
|
||||||
Query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=60`,
|
Query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=bar.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=60`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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",
|
desc: "invalid status",
|
||||||
|
authID: "myAuthID",
|
||||||
zone: &Zone{
|
zone: &Zone{
|
||||||
Name: "bar.com",
|
Name: "bar.com",
|
||||||
Type: "master",
|
Type: "master",
|
||||||
|
@ -231,11 +256,13 @@ func TestClientAddTxtRecord(t *testing.T) {
|
||||||
handlerMock(http.MethodPost, test.apiResponse).ServeHTTP(rw, req)
|
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))
|
mockBaseURL, _ := url.Parse(fmt.Sprintf("%s/", server.URL))
|
||||||
client.BaseURL = mockBaseURL
|
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 != "" {
|
if test.expected.Error != "" {
|
||||||
require.EqualError(t, err, test.expected.Error)
|
require.EqualError(t, err, test.expected.Error)
|
||||||
|
|
Loading…
Reference in a new issue