Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gandiv5: add Personal Access Token support #2007

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 20 additions & 16 deletions providers/dns/gandiv5/gandiv5.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions providers/dns/gandiv5/gandiv5.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
13 changes: 6 additions & 7 deletions providers/dns/gandiv5/gandiv5_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
},
}

Expand Down Expand Up @@ -70,7 +69,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
},
{
desc: "missing credentials",
expected: "gandiv5: no API Key given",
expected: "gandiv5: credentials information are missing",
},
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion providers/dns/gandiv5/internal/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
}
Expand Down Expand Up @@ -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)
Expand Down