From 113648a36817a5d9667b8dd1f1c6c67eeb914916 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 20 Sep 2023 05:42:25 +0200 Subject: [PATCH] gandiv5: add Personal Access Token support (#2007) --- providers/dns/gandiv5/gandiv5.go | 36 +++++++++++++----------- providers/dns/gandiv5/gandiv5.toml | 5 ++-- providers/dns/gandiv5/gandiv5_test.go | 13 ++++----- providers/dns/gandiv5/internal/client.go | 11 +++++++- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go index 44859397..d5b8bb5b 100644 --- a/providers/dns/gandiv5/gandiv5.go +++ b/providers/dns/gandiv5/gandiv5.go @@ -11,6 +11,7 @@ import ( "time" "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/providers/dns/gandiv5/internal" ) @@ -23,7 +24,8 @@ const minTTL = 300 const ( envNamespace = "GANDIV5_" - EnvAPIKey = envNamespace + "API_KEY" + EnvAPIKey = envNamespace + "API_KEY" + EnvPersonalAccessToken = envNamespace + "PERSONAL_ACCESS_TOKEN" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -39,12 +41,13 @@ type inProgressInfo struct { // Config is used to configure the creation of the DNSProvider. type Config struct { - BaseURL string - APIKey string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client + BaseURL string + APIKey string // Deprecated use PersonalAccessToken + PersonalAccessToken string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -76,13 +79,10 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Gandi. // Credentials must be passed in the environment variable: GANDIV5_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("gandi: %w", err) - } - + // TODO(ldez): rewrite this when APIKey will be removed. config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] + config.APIKey = env.GetOrFile(EnvAPIKey) + config.PersonalAccessToken = env.GetOrFile(EnvPersonalAccessToken) 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") } - if config.APIKey == "" { - return nil, errors.New("gandiv5: no API Key given") + if config.APIKey != "" { + 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 { 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 != "" { baseURL, err := url.Parse(config.BaseURL) diff --git a/providers/dns/gandiv5/gandiv5.toml b/providers/dns/gandiv5/gandiv5.toml index 19ed906e..4d952b2c 100644 --- a/providers/dns/gandiv5/gandiv5.toml +++ b/providers/dns/gandiv5/gandiv5.toml @@ -5,13 +5,14 @@ Code = "gandiv5" Since = "v0.5.0" Example = ''' -GANDIV5_API_KEY=abcdefghijklmnopqrstuvwx \ +GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \ lego --email you@example.com --dns gandiv5 --domains my.example.org run ''' [Configuration] [Configuration.Credentials] - GANDIV5_API_KEY = "API key" + GANDIV5_PERSONAL_ACCESS_TOKEN = "Personal Access Token" + GANDIV5_API_KEY = "API key (Deprecated)" [Configuration.Additional] GANDIV5_POLLING_INTERVAL = "Time between DNS propagation check" GANDIV5_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index 52e3b961..2cc8f1cd 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -10,11 +10,10 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/providers/dns/gandiv5/internal" "github.com/stretchr/testify/require" ) -var envTest = tester.NewEnvTest(EnvAPIKey) +var envTest = tester.NewEnvTest(EnvAPIKey, EnvPersonalAccessToken) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -33,7 +32,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ 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", - 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) { log.Infof("request: %s %s", req.Method, req.URL) - if req.Header.Get(internal.APIKeyHeader) == "" { - http.Error(rw, `{"message": "missing API key"}`, http.StatusUnauthorized) + if req.Header.Get("Authorization") == "" { + http.Error(rw, `{"message": "missing Authorization"}`, http.StatusUnauthorized) return } @@ -165,7 +164,7 @@ func TestDNSProvider(t *testing.T) { } config := NewDefaultConfig() - config.APIKey = "123412341234123412341234" + config.PersonalAccessToken = "123412341234123412341234" config.BaseURL = server.URL provider, err := NewDNSProviderConfig(config) diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index bb280a3c..d91c1422 100644 --- a/providers/dns/gandiv5/internal/client.go +++ b/providers/dns/gandiv5/internal/client.go @@ -20,20 +20,25 @@ const defaultBaseURL = "https://dns.api.gandi.net/api/v5" // APIKeyHeader API key header. const APIKeyHeader = "X-Api-Key" +// Related to Personal Access Token. +const authorizationHeader = "Authorization" + // Client the Gandi API v5 client. type Client struct { apiKey string + pat string BaseURL *url.URL HTTPClient *http.Client } // NewClient Creates a new Client. -func NewClient(apiKey string) *Client { +func NewClient(apiKey, pat string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ apiKey: apiKey, + pat: pat, BaseURL: baseURL, 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) } + if c.pat != "" { + req.Header.Set(authorizationHeader, c.pat) + } + resp, err := c.HTTPClient.Do(req) if err != nil { return errutils.NewHTTPDoError(req, err)