gandiv5: add Personal Access Token support (#2007)

This commit is contained in:
Ludovic Fernandez 2023-09-20 05:42:25 +02:00 committed by GitHub
parent 766e581f8d
commit 113648a368
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 39 additions and 26 deletions

View file

@ -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"
) )
@ -23,7 +24,8 @@ const minTTL = 300
const ( 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"
@ -39,12 +41,13 @@ 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
PropagationTimeout time.Duration PersonalAccessToken string
PollingInterval time.Duration PropagationTimeout time.Duration
TTL int PollingInterval time.Duration
HTTPClient *http.Client TTL int
HTTPClient *http.Client
} }
// NewDefaultConfig returns a default configuration for the DNSProvider. // NewDefaultConfig returns a default configuration for the DNSProvider.
@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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)