gandiv5: add Personal Access Token support (#2007)
This commit is contained in:
parent
766e581f8d
commit
113648a368
4 changed files with 39 additions and 26 deletions
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"github.com/go-acme/lego/v4/log"
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
"github.com/go-acme/lego/v4/platform/config/env"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/gandiv5/internal"
|
"github.com/go-acme/lego/v4/providers/dns/gandiv5/internal"
|
||||||
)
|
)
|
||||||
|
@ -24,6 +25,7 @@ const (
|
||||||
envNamespace = "GANDIV5_"
|
envNamespace = "GANDIV5_"
|
||||||
|
|
||||||
EnvAPIKey = envNamespace + "API_KEY"
|
EnvAPIKey = envNamespace + "API_KEY"
|
||||||
|
EnvPersonalAccessToken = envNamespace + "PERSONAL_ACCESS_TOKEN"
|
||||||
|
|
||||||
EnvTTL = envNamespace + "TTL"
|
EnvTTL = envNamespace + "TTL"
|
||||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||||
|
@ -40,7 +42,8 @@ type inProgressInfo struct {
|
||||||
// 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 {
|
||||||
BaseURL string
|
BaseURL string
|
||||||
APIKey string
|
APIKey string // Deprecated use PersonalAccessToken
|
||||||
|
PersonalAccessToken string
|
||||||
PropagationTimeout time.Duration
|
PropagationTimeout time.Duration
|
||||||
PollingInterval time.Duration
|
PollingInterval time.Duration
|
||||||
TTL int
|
TTL int
|
||||||
|
@ -76,13 +79,10 @@ type DNSProvider struct {
|
||||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||||
// Credentials must be passed in the environment variable: GANDIV5_API_KEY.
|
// Credentials must be passed in the environment variable: GANDIV5_API_KEY.
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
values, err := env.Get(EnvAPIKey)
|
// TODO(ldez): rewrite this when APIKey will be removed.
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("gandi: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
config := NewDefaultConfig()
|
||||||
config.APIKey = values[EnvAPIKey]
|
config.APIKey = env.GetOrFile(EnvAPIKey)
|
||||||
|
config.PersonalAccessToken = env.GetOrFile(EnvPersonalAccessToken)
|
||||||
|
|
||||||
return NewDNSProviderConfig(config)
|
return NewDNSProviderConfig(config)
|
||||||
}
|
}
|
||||||
|
@ -93,15 +93,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
return nil, errors.New("gandiv5: the configuration of the DNS provider is nil")
|
return nil, errors.New("gandiv5: the configuration of the DNS provider is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.APIKey == "" {
|
if config.APIKey != "" {
|
||||||
return nil, errors.New("gandiv5: no API Key given")
|
log.Print("gandiv5: API Key is deprecated, use Personal Access Token instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.APIKey == "" && config.PersonalAccessToken == "" {
|
||||||
|
return nil, errors.New("gandiv5: credentials information are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.TTL < minTTL {
|
if config.TTL < minTTL {
|
||||||
return nil, fmt.Errorf("gandiv5: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
return nil, fmt.Errorf("gandiv5: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := internal.NewClient(config.APIKey)
|
client := internal.NewClient(config.APIKey, config.PersonalAccessToken)
|
||||||
|
|
||||||
if config.BaseURL != "" {
|
if config.BaseURL != "" {
|
||||||
baseURL, err := url.Parse(config.BaseURL)
|
baseURL, err := url.Parse(config.BaseURL)
|
||||||
|
|
|
@ -5,13 +5,14 @@ Code = "gandiv5"
|
||||||
Since = "v0.5.0"
|
Since = "v0.5.0"
|
||||||
|
|
||||||
Example = '''
|
Example = '''
|
||||||
GANDIV5_API_KEY=abcdefghijklmnopqrstuvwx \
|
GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \
|
||||||
lego --email you@example.com --dns gandiv5 --domains my.example.org run
|
lego --email you@example.com --dns gandiv5 --domains my.example.org run
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[Configuration]
|
[Configuration]
|
||||||
[Configuration.Credentials]
|
[Configuration.Credentials]
|
||||||
GANDIV5_API_KEY = "API key"
|
GANDIV5_PERSONAL_ACCESS_TOKEN = "Personal Access Token"
|
||||||
|
GANDIV5_API_KEY = "API key (Deprecated)"
|
||||||
[Configuration.Additional]
|
[Configuration.Additional]
|
||||||
GANDIV5_POLLING_INTERVAL = "Time between DNS propagation check"
|
GANDIV5_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
GANDIV5_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
GANDIV5_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
|
|
|
@ -10,11 +10,10 @@ import (
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
"github.com/go-acme/lego/v4/platform/tester"
|
"github.com/go-acme/lego/v4/platform/tester"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/gandiv5/internal"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var envTest = tester.NewEnvTest(EnvAPIKey)
|
var envTest = tester.NewEnvTest(EnvAPIKey, EnvPersonalAccessToken)
|
||||||
|
|
||||||
func TestNewDNSProvider(t *testing.T) {
|
func TestNewDNSProvider(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -33,7 +32,7 @@ func TestNewDNSProvider(t *testing.T) {
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
EnvAPIKey: "",
|
EnvAPIKey: "",
|
||||||
},
|
},
|
||||||
expected: "gandi: some credentials information are missing: GANDIV5_API_KEY",
|
expected: "gandiv5: credentials information are missing",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "missing credentials",
|
desc: "missing credentials",
|
||||||
expected: "gandiv5: no API Key given",
|
expected: "gandiv5: credentials information are missing",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +121,8 @@ func TestDNSProvider(t *testing.T) {
|
||||||
mux.HandleFunc("/domains/example.com/records/_acme-challenge.abc.def/TXT", func(rw http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/domains/example.com/records/_acme-challenge.abc.def/TXT", func(rw http.ResponseWriter, req *http.Request) {
|
||||||
log.Infof("request: %s %s", req.Method, req.URL)
|
log.Infof("request: %s %s", req.Method, req.URL)
|
||||||
|
|
||||||
if req.Header.Get(internal.APIKeyHeader) == "" {
|
if req.Header.Get("Authorization") == "" {
|
||||||
http.Error(rw, `{"message": "missing API key"}`, http.StatusUnauthorized)
|
http.Error(rw, `{"message": "missing Authorization"}`, http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +164,7 @@ func TestDNSProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
config := NewDefaultConfig()
|
||||||
config.APIKey = "123412341234123412341234"
|
config.PersonalAccessToken = "123412341234123412341234"
|
||||||
config.BaseURL = server.URL
|
config.BaseURL = server.URL
|
||||||
|
|
||||||
provider, err := NewDNSProviderConfig(config)
|
provider, err := NewDNSProviderConfig(config)
|
||||||
|
|
|
@ -20,20 +20,25 @@ const defaultBaseURL = "https://dns.api.gandi.net/api/v5"
|
||||||
// APIKeyHeader API key header.
|
// APIKeyHeader API key header.
|
||||||
const APIKeyHeader = "X-Api-Key"
|
const APIKeyHeader = "X-Api-Key"
|
||||||
|
|
||||||
|
// Related to Personal Access Token.
|
||||||
|
const authorizationHeader = "Authorization"
|
||||||
|
|
||||||
// Client the Gandi API v5 client.
|
// Client the Gandi API v5 client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
|
pat string
|
||||||
|
|
||||||
BaseURL *url.URL
|
BaseURL *url.URL
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient Creates a new Client.
|
// NewClient Creates a new Client.
|
||||||
func NewClient(apiKey string) *Client {
|
func NewClient(apiKey, pat string) *Client {
|
||||||
baseURL, _ := url.Parse(defaultBaseURL)
|
baseURL, _ := url.Parse(defaultBaseURL)
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
|
pat: pat,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||||||
}
|
}
|
||||||
|
@ -128,6 +133,10 @@ func (c *Client) do(req *http.Request, result any) error {
|
||||||
req.Header.Set(APIKeyHeader, c.apiKey)
|
req.Header.Set(APIKeyHeader, c.apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.pat != "" {
|
||||||
|
req.Header.Set(authorizationHeader, c.pat)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errutils.NewHTTPDoError(req, err)
|
return errutils.NewHTTPDoError(req, err)
|
||||||
|
|
Loading…
Reference in a new issue