edgedns: add support for .edgerc file (#1340)

This commit is contained in:
Andre Sencioles 2021-02-21 23:56:56 +13:00 committed by GitHub
parent f42caa7393
commit 7cfcc155ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 305 additions and 158 deletions

View file

@ -716,10 +716,12 @@ func displayDNSHelp(name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "AKAMAI_ACCESS_TOKEN": Access token`)
ew.writeln(` - "AKAMAI_CLIENT_SECRET": Client secret`)
ew.writeln(` - "AKAMAI_CLIENT_TOKEN": Client token`)
ew.writeln(` - "AKAMAI_HOST": API host`)
ew.writeln(` - "AKAMAI_ACCESS_TOKEN": Access token, managed by the Akamai EdgeGrid client`)
ew.writeln(` - "AKAMAI_CLIENT_SECRET": Client secret, managed by the Akamai EdgeGrid client`)
ew.writeln(` - "AKAMAI_CLIENT_TOKEN": Client token, managed by the Akamai EdgeGrid client`)
ew.writeln(` - "AKAMAI_EDGERC": Path to the .edgerc file, managed by the Akamai EdgeGrid client`)
ew.writeln(` - "AKAMAI_EDGERC_SECTION": Configuration section, managed by the Akamai EdgeGrid client`)
ew.writeln(` - "AKAMAI_HOST": API host, managed by the Akamai EdgeGrid client`)
ew.writeln()
ew.writeln(`Additional Configuration:`)

View file

@ -36,10 +36,12 @@ lego --email myemail@example.com --dns edgedns --domains my.example.org run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `AKAMAI_ACCESS_TOKEN` | Access token |
| `AKAMAI_CLIENT_SECRET` | Client secret |
| `AKAMAI_CLIENT_TOKEN` | Client token |
| `AKAMAI_HOST` | API host |
| `AKAMAI_ACCESS_TOKEN` | Access token, managed by the Akamai EdgeGrid client |
| `AKAMAI_CLIENT_SECRET` | Client secret, managed by the Akamai EdgeGrid client |
| `AKAMAI_CLIENT_TOKEN` | Client token, managed by the Akamai EdgeGrid client |
| `AKAMAI_EDGERC` | Path to the .edgerc file, managed by the Akamai EdgeGrid client |
| `AKAMAI_EDGERC_SECTION` | Configuration section, managed by the Akamai EdgeGrid client |
| `AKAMAI_HOST` | API host, managed by the Akamai EdgeGrid client |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).
@ -56,6 +58,32 @@ More information [here](/lego/dns/#configuration-and-credentials).
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).
Akamai credentials are automatically detected in the following locations and prioritized in the following order:
1. Section-specific environment variables (where `{SECTION}` is specified using `AKAMAI_EDGERC_SECTION`):
- `AKAMAI_{SECTION}_HOST`
- `AKAMAI_{SECTION}_ACCESS_TOKEN`
- `AKAMAI_{SECTION}_CLIENT_TOKEN`
- `AKAMAI_{SECTION}_CLIENT_SECRET`
2. If `AKAMAI_EDGERC_SECTION` is not defined or is set to `default`, environment variables:
- `AKAMAI_HOST`
- `AKAMAI_ACCESS_TOKEN`
- `AKAMAI_CLIENT_TOKEN`
- `AKAMAI_CLIENT_SECRET`
3. `.edgerc` file located at `AKAMAI_EDGERC`
- defaults to `~/.edgerc`, sections can be specified using `AKAMAI_EDGERC_SECTION`
4. Default environment variables:
- `AKAMAI_HOST`
- `AKAMAI_ACCESS_TOKEN`
- `AKAMAI_CLIENT_TOKEN`
- `AKAMAI_CLIENT_SECRET`
See also:
- [Setting up Akamai credentials](https://developer.akamai.com/api/getting-started)
- [.edgerc Format](https://developer.akamai.com/legacy/introduction/Conf_Client.html#edgercformat)
- [API Client Authentication](https://developer.akamai.com/legacy/introduction/Client_Auth.html)
- [Config from Env](https://github.com/akamai/AkamaiOPEN-edgegrid-golang/blob/master/edgegrid/config.go#L118)

View file

@ -18,6 +18,9 @@ import (
const (
envNamespace = "AKAMAI_"
EnvEdgeRc = envNamespace + "EDGERC"
EnvEdgeRcSection = envNamespace + "EDGERC_SECTION"
EnvHost = envNamespace + "HOST"
EnvClientToken = envNamespace + "CLIENT_TOKEN"
EnvClientSecret = envNamespace + "CLIENT_SECRET"
@ -26,11 +29,15 @@ const (
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
DefaultPropagationTimeout = 3 * time.Minute
DefaultPollInterval = 15 * time.Second
)
const (
defaultPropagationTimeout = 3 * time.Minute
defaultPollInterval = 15 * time.Second
)
const maxBody = 131072
// Config is used to configure the creation of the DNSProvider.
type Config struct {
edgegrid.Config
@ -43,11 +50,9 @@ type Config struct {
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, DefaultPollInterval),
Config: edgegrid.Config{
MaxBody: 131072,
},
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollInterval),
Config: edgegrid.Config{MaxBody: maxBody},
}
}
@ -56,19 +61,29 @@ type DNSProvider struct {
config *Config
}
// NewDNSProvider uses the supplied environment variables to return a DNSProvider instance:
// AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, AKAMAI_ACCESS_TOKEN.
// NewDNSProvider returns a DNSProvider instance configured for Akamai EdgeDNS:
// Akamai credentials are automatically detected in the following locations and prioritized in the following order:
//
// 1. Section-specific environment variables `AKAMAI_{SECTION}_HOST`, `AKAMAI_{SECTION}_ACCESS_TOKEN`, `AKAMAI_{SECTION}_CLIENT_TOKEN`, `AKAMAI_{SECTION}_CLIENT_SECRET` where `{SECTION}` is specified using `AKAMAI_EDGERC_SECTION`
// 2. If `AKAMAI_EDGERC_SECTION` is not defined or is set to `default`: Environment variables `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`
// 3. .edgerc file located at `AKAMAI_EDGERC` (defaults to `~/.edgerc`, sections can be specified using `AKAMAI_EDGERC_SECTION`)
// 4. Default environment variables: `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`
//
// See also: https://developer.akamai.com/api/getting-started
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvHost, EnvClientToken, EnvClientSecret, EnvAccessToken)
config := NewDefaultConfig()
rcPath := env.GetOrDefaultString(EnvEdgeRc, "")
rcSection := env.GetOrDefaultString(EnvEdgeRcSection, "")
conf, err := edgegrid.Init(rcPath, rcSection)
if err != nil {
return nil, fmt.Errorf("edgedns: %w", err)
}
config := NewDefaultConfig()
config.Config.Host = values[EnvHost]
config.Config.ClientToken = values[EnvClientToken]
config.Config.ClientSecret = values[EnvClientSecret]
config.Config.AccessToken = values[EnvAccessToken]
conf.MaxBody = maxBody
config.Config = conf
return NewDNSProviderConfig(config)
}
@ -79,10 +94,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("edgedns: the configuration of the DNS provider is nil")
}
if config.ClientToken == "" || config.ClientSecret == "" || config.AccessToken == "" || config.Host == "" {
return nil, errors.New("edgedns: credentials are missing")
}
configdns.Init(config.Config)
return &DNSProvider{config: config}, nil

View file

@ -15,12 +15,43 @@ AKAMAI_ACCESS_TOKEN=akab-1234567890qwerty-asdfghjklzxcvtnu \
lego --email myemail@example.com --dns edgedns --domains my.example.org run
'''
Additional = '''
Akamai credentials are automatically detected in the following locations and prioritized in the following order:
1. Section-specific environment variables (where `{SECTION}` is specified using `AKAMAI_EDGERC_SECTION`):
- `AKAMAI_{SECTION}_HOST`
- `AKAMAI_{SECTION}_ACCESS_TOKEN`
- `AKAMAI_{SECTION}_CLIENT_TOKEN`
- `AKAMAI_{SECTION}_CLIENT_SECRET`
2. If `AKAMAI_EDGERC_SECTION` is not defined or is set to `default`, environment variables:
- `AKAMAI_HOST`
- `AKAMAI_ACCESS_TOKEN`
- `AKAMAI_CLIENT_TOKEN`
- `AKAMAI_CLIENT_SECRET`
3. `.edgerc` file located at `AKAMAI_EDGERC`
- defaults to `~/.edgerc`, sections can be specified using `AKAMAI_EDGERC_SECTION`
4. Default environment variables:
- `AKAMAI_HOST`
- `AKAMAI_ACCESS_TOKEN`
- `AKAMAI_CLIENT_TOKEN`
- `AKAMAI_CLIENT_SECRET`
See also:
- [Setting up Akamai credentials](https://developer.akamai.com/api/getting-started)
- [.edgerc Format](https://developer.akamai.com/legacy/introduction/Conf_Client.html#edgercformat)
- [API Client Authentication](https://developer.akamai.com/legacy/introduction/Client_Auth.html)
- [Config from Env](https://github.com/akamai/AkamaiOPEN-edgegrid-golang/blob/master/edgegrid/config.go#L118)
'''
[Configuration]
[Configuration.Credentials]
AKAMAI_HOST = "API host"
AKAMAI_CLIENT_TOKEN = "Client token"
AKAMAI_CLIENT_SECRET = "Client secret"
AKAMAI_ACCESS_TOKEN = "Access token"
AKAMAI_HOST = "API host, managed by the Akamai EdgeGrid client"
AKAMAI_CLIENT_TOKEN = "Client token, managed by the Akamai EdgeGrid client"
AKAMAI_CLIENT_SECRET = "Client secret, managed by the Akamai EdgeGrid client"
AKAMAI_ACCESS_TOKEN = "Access token, managed by the Akamai EdgeGrid client"
AKAMAI_EDGERC = "Path to the .edgerc file, managed by the Akamai EdgeGrid client"
AKAMAI_EDGERC_SECTION = "Configuration section, managed by the Akamai EdgeGrid client"
[Configuration.Additional]
AKAMAI_POLLING_INTERVAL = "Time between DNS propagation check. Default: 15 seconds"
AKAMAI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation. Default: 3 minutes"

View file

@ -0,0 +1,86 @@
package edgedns
import (
"fmt"
"testing"
"time"
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
// Present Twice to handle create / update
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
func TestLiveCleanUp(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
time.Sleep(1 * time.Second)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
func TestLiveTTL(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
domain := envTest.GetDomain()
err = provider.Present(domain, "foo", "bar")
require.NoError(t, err)
defer func() {
e := provider.CleanUp(domain, "foo", "bar")
if e != nil {
t.Log(e)
}
}()
fqdn := "_acme-challenge." + domain + "."
zone, err := findZone(domain)
require.NoError(t, err)
resourceRecordSets, err := configdns.GetRecordList(zone, fqdn, "TXT")
require.NoError(t, err)
for i, rrset := range resourceRecordSets.Recordsets {
if rrset.Name != fqdn {
continue
}
t.Run(fmt.Sprintf("testing record set %d", i), func(t *testing.T) {
assert.Equal(t, rrset.Name, fqdn)
assert.Equal(t, rrset.Type, "TXT")
assert.Equal(t, rrset.TTL, dns01.DefaultTTL)
})
}
}

View file

@ -1,47 +1,82 @@
package edgedns
import (
"os"
"testing"
"time"
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2"
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const envDomain = envNamespace + "TEST_DOMAIN"
const (
envDomain = envNamespace + "TEST_DOMAIN"
envTestHost = envNamespace + "TEST_HOST"
envTestClientToken = envNamespace + "TEST_CLIENT_TOKEN"
envTestClientSecret = envNamespace + "TEST_CLIENT_SECRET"
envTestAccessToken = envNamespace + "TEST_ACCESS_TOKEN"
)
var envTest = tester.NewEnvTest(
EnvHost,
EnvClientToken,
EnvClientSecret,
EnvAccessToken).
WithDomain(envDomain)
EnvAccessToken,
EnvEdgeRc,
EnvEdgeRcSection,
envTestHost,
envTestClientToken,
envTestClientSecret,
envTestAccessToken).
WithDomain(envDomain).
WithLiveTestRequirements(EnvHost, EnvClientToken, EnvClientSecret, EnvAccessToken, envDomain)
func TestNewDNSProvider(t *testing.T) {
func TestNewDNSProvider_FromEnv(t *testing.T) {
testCases := []struct {
desc string
envVars map[string]string
expected string
desc string
envVars map[string]string
expectedConfig *edgegrid.Config
expectedErr string
}{
{
desc: "success",
envVars: map[string]string{
EnvHost: "A",
EnvClientToken: "B",
EnvClientSecret: "C",
EnvAccessToken: "D",
EnvHost: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
EnvClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
EnvClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
},
expectedConfig: &edgegrid.Config{
Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
MaxBody: maxBody,
},
},
{
desc: "missing credentials",
desc: "with section",
envVars: map[string]string{
EnvHost: "",
EnvClientToken: "",
EnvClientSecret: "",
EnvAccessToken: "",
EnvEdgeRcSection: "test",
envTestHost: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
envTestClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
envTestClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
envTestAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
},
expected: "edgedns: some credentials information are missing: AKAMAI_HOST,AKAMAI_CLIENT_TOKEN,AKAMAI_CLIENT_SECRET,AKAMAI_ACCESS_TOKEN",
expectedConfig: &edgegrid.Config{
Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx",
MaxBody: maxBody,
},
},
{
desc: "missing credentials",
expectedErr: "edgedns: Unable to create instance using environment or .edgerc file",
},
{
desc: "missing host",
@ -51,7 +86,7 @@ func TestNewDNSProvider(t *testing.T) {
EnvClientSecret: "C",
EnvAccessToken: "D",
},
expected: "edgedns: some credentials information are missing: AKAMAI_HOST",
expectedErr: "edgedns: Unable to create instance using environment or .edgerc file",
},
{
desc: "missing client token",
@ -61,7 +96,7 @@ func TestNewDNSProvider(t *testing.T) {
EnvClientSecret: "C",
EnvAccessToken: "D",
},
expected: "edgedns: some credentials information are missing: AKAMAI_CLIENT_TOKEN",
expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_TOKEN]",
},
{
desc: "missing client secret",
@ -71,7 +106,7 @@ func TestNewDNSProvider(t *testing.T) {
EnvClientSecret: "",
EnvAccessToken: "D",
},
expected: "edgedns: some credentials information are missing: AKAMAI_CLIENT_SECRET",
expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_SECRET]",
},
{
desc: "missing access token",
@ -81,7 +116,7 @@ func TestNewDNSProvider(t *testing.T) {
EnvClientSecret: "C",
EnvAccessToken: "",
},
expected: "edgedns: some credentials information are missing: AKAMAI_ACCESS_TOKEN",
expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_ACCESS_TOKEN]",
},
}
@ -90,91 +125,26 @@ func TestNewDNSProvider(t *testing.T) {
defer envTest.RestoreEnv()
envTest.ClearEnv()
if test.envVars == nil {
test.envVars = map[string]string{}
}
test.envVars[EnvEdgeRc] = "/dev/null"
envTest.Apply(test.envVars)
p, err := NewDNSProvider()
if len(test.expected) == 0 {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
} else {
require.EqualError(t, err, test.expected)
if test.expectedErr != "" {
require.EqualError(t, err, test.expectedErr)
return
}
})
}
}
func TestNewDNSProviderConfig(t *testing.T) {
testCases := []struct {
desc string
host string
clientToken string
clientSecret string
accessToken string
expected string
}{
{
desc: "success",
host: "A",
clientToken: "B",
clientSecret: "C",
accessToken: "D",
},
{
desc: "missing credentials",
expected: "edgedns: credentials are missing",
},
{
desc: "missing host",
host: "",
clientToken: "B",
clientSecret: "C",
accessToken: "D",
expected: "edgedns: credentials are missing",
},
{
desc: "missing client token",
host: "A",
clientToken: "",
clientSecret: "C",
accessToken: "D",
expected: "edgedns: credentials are missing",
},
{
desc: "missing client secret",
host: "A",
clientToken: "B",
clientSecret: "",
accessToken: "B",
expected: "edgedns: credentials are missing",
},
{
desc: "missing access token",
host: "A",
clientToken: "B",
clientSecret: "C",
accessToken: "",
expected: "edgedns: credentials are missing",
},
}
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.ClientToken = test.clientToken
config.ClientSecret = test.clientSecret
config.Host = test.host
config.AccessToken = test.accessToken
p, err := NewDNSProviderConfig(config)
if len(test.expected) == 0 {
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.config)
} else {
require.EqualError(t, err, test.expected)
if test.expectedConfig != nil {
require.Equal(t, *test.expectedConfig, configdns.Config)
}
})
}
@ -205,39 +175,58 @@ func TestDNSProvider_findZone(t *testing.T) {
zone, err := findZone(test.domain)
require.NoError(t, err)
assert.Equal(t, test.expected, zone)
require.Equal(t, test.expected, zone)
})
}
}
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
func TestNewDefaultConfig(t *testing.T) {
defer envTest.RestoreEnv()
testCases := []struct {
desc string
envVars map[string]string
expected *Config
}{
{
desc: "default configuration",
expected: &Config{
TTL: dns01.DefaultTTL,
PropagationTimeout: 3 * time.Minute,
PollingInterval: 15 * time.Second,
Config: edgegrid.Config{
MaxBody: maxBody,
},
},
},
{
desc: "custom values",
envVars: map[string]string{
EnvTTL: "99",
EnvPropagationTimeout: "60",
EnvPollingInterval: "60",
},
expected: &Config{
TTL: 99,
PropagationTimeout: 60 * time.Second,
PollingInterval: 60 * time.Second,
Config: edgegrid.Config{
MaxBody: maxBody,
},
},
},
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
envTest.ClearEnv()
for key, value := range test.envVars {
os.Setenv(key, value)
}
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
config := NewDefaultConfig()
// Present Twice to handle create / update
err = provider.Present(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}
func TestLiveCleanUp(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")
require.Equal(t, test.expected, config)
})
}
envTest.RestoreEnv()
provider, err := NewDNSProvider()
require.NoError(t, err)
time.Sleep(1 * time.Second)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}