mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
acme-dns: fix file path (#2439)
This commit is contained in:
parent
b16da88eb7
commit
29cf89ea49
7 changed files with 101 additions and 35 deletions
|
|
@ -18,17 +18,17 @@ Let's Encrypt client and ACME library written in Go.
|
|||
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses
|
||||
- Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
|
||||
- Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension
|
||||
- Comes with about [150 DNS providers](https://go-acme.github.io/lego/dns)
|
||||
- Register with CA
|
||||
- Obtain certificates, both from scratch or with an existing CSR
|
||||
- Renew certificates
|
||||
- Revoke certificates
|
||||
- Robust implementation of all ACME challenges
|
||||
- Robust implementation of ACME challenges:
|
||||
- HTTP (http-01)
|
||||
- DNS (dns-01)
|
||||
- TLS (tls-alpn-01)
|
||||
- SAN certificate support
|
||||
- [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default
|
||||
- Comes with multiple optional [DNS providers](https://go-acme.github.io/lego/dns)
|
||||
- [Custom challenge solvers](https://go-acme.github.io/lego/usage/library/writing-a-challenge-solver/)
|
||||
- Certificate bundling
|
||||
- OCSP helper function
|
||||
|
|
|
|||
3
cmd/zz_gen_cmd_dnshelp.go
generated
3
cmd/zz_gen_cmd_dnshelp.go
generated
|
|
@ -182,6 +182,9 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "ACME_DNS_STORAGE_PATH": The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates.`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "ACME_DNS_ALLOWLIST": Source networks using CIDR notation (multiple values should be separated with a comma).`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/acme-dns`)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ Let's Encrypt client and ACME library written in Go.
|
|||
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses
|
||||
- Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
|
||||
- Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension
|
||||
- Comes with about [150 DNS providers]({{% ref "dns" %}})
|
||||
- Register with CA
|
||||
- Obtain certificates, both from scratch or with an existing CSR
|
||||
- Renew certificates
|
||||
- Revoke certificates
|
||||
- Robust implementation of all ACME challenges
|
||||
- Robust implementation of ACME challenges:
|
||||
- HTTP (http-01)
|
||||
- DNS (dns-01)
|
||||
- TLS (tls-alpn-01)
|
||||
- SAN certificate support
|
||||
- [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default
|
||||
- Comes with multiple optional [DNS providers]({{% ref "dns" %}})
|
||||
- [Custom challenge solvers]({{% ref "usage/library/Writing-a-Challenge-Solver" %}})
|
||||
- Certificate bundling
|
||||
- OCSP helper function
|
||||
|
|
|
|||
8
docs/content/dns/zz_gen_acme-dns.md
generated
8
docs/content/dns/zz_gen_acme-dns.md
generated
|
|
@ -52,6 +52,14 @@ The environment variable names can be suffixed by `_FILE` to reference a file in
|
|||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
## Additional Configuration
|
||||
|
||||
| Environment Variable Name | Description |
|
||||
|--------------------------------|-------------|
|
||||
| `ACME_DNS_ALLOWLIST` | Source networks using CIDR notation (multiple values should be separated with a comma). |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
|
|
@ -23,6 +24,10 @@ const (
|
|||
// (e.g. https://acmedns.your-domain.com).
|
||||
EnvAPIBase = envNamespace + "API_BASE"
|
||||
|
||||
// EnvAllowList are source networks using CIDR notation,
|
||||
// e.g. "192.168.100.1/24,1.2.3.4/32,2002:c0a8:2a00::0/40".
|
||||
EnvAllowList = envNamespace + "ALLOWLIST"
|
||||
|
||||
// EnvStoragePath is the environment variable name for the ACME-DNS JSON account data file.
|
||||
// A per-domain account will be registered/persisted to this file and used for TXT updates.
|
||||
EnvStoragePath = envNamespace + "STORAGE_PATH"
|
||||
|
|
@ -34,6 +39,19 @@ const (
|
|||
|
||||
var _ challenge.Provider = (*DNSProvider)(nil)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider.
|
||||
type Config struct {
|
||||
APIBase string
|
||||
AllowList []string
|
||||
StoragePath string
|
||||
StorageBaseURL string
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
// acmeDNSClient is an interface describing the goacmedns.Client functions the DNSProvider uses.
|
||||
// It makes it easier for tests to shim a mock Client into the DNSProvider.
|
||||
type acmeDNSClient interface {
|
||||
|
|
@ -47,58 +65,67 @@ type acmeDNSClient interface {
|
|||
|
||||
// DNSProvider implements the challenge.Provider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client acmeDNSClient
|
||||
storage goacmedns.Storage
|
||||
}
|
||||
|
||||
// NewDNSProvider creates an ACME-DNS provider using file based account storage.
|
||||
// Its configuration is loaded from the environment by reading EnvAPIBase and EnvStoragePath.
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Joohoi's acme-dns.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAPIBase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: %w", err)
|
||||
}
|
||||
|
||||
storagePath := env.GetOrFile(EnvStoragePath)
|
||||
storageBaseURL := env.GetOrFile(EnvStorageBaseURL)
|
||||
config := NewDefaultConfig()
|
||||
config.APIBase = values[EnvAPIBase]
|
||||
config.StoragePath = env.GetOrFile(EnvStoragePath)
|
||||
config.StorageBaseURL = env.GetOrFile(EnvStorageBaseURL)
|
||||
|
||||
if storagePath == "" && storageBaseURL == "" {
|
||||
return nil, fmt.Errorf("acme-dns: %s or %s environment variables not set", EnvStoragePath, EnvStorageBaseURL)
|
||||
allowList := env.GetOrFile(EnvAllowList)
|
||||
if allowList != "" {
|
||||
config.AllowList = strings.Split(allowList, ",")
|
||||
}
|
||||
|
||||
if storagePath != "" && storageBaseURL != "" {
|
||||
return nil, fmt.Errorf("acme-dns: %s or %s environment variables cannot be used at the same time", EnvStoragePath, EnvStorageBaseURL)
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Joohoi's acme-dns.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("acme-dns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
var st goacmedns.Storage
|
||||
if storagePath != "" {
|
||||
st = storage.NewFile(values[EnvStoragePath], 0o600)
|
||||
} else {
|
||||
st, err = internal.NewHTTPStorage(storageBaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: new HTTP storage: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := goacmedns.NewClient(values[EnvAPIBase])
|
||||
st, err := getStorage(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: %w", err)
|
||||
}
|
||||
|
||||
return NewDNSProviderClient(client, st)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage].
|
||||
func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("ACME-DNS Client must be not nil")
|
||||
}
|
||||
|
||||
if storage == nil {
|
||||
return nil, errors.New("ACME-DNS Storage must be not nil")
|
||||
client, err := goacmedns.NewClient(config.APIBase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: new client: %w", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
storage: st,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage].
|
||||
// Deprecated: use [NewDNSProviderConfig] instead.
|
||||
func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("acme-dns: Client must be not nil")
|
||||
}
|
||||
|
||||
if storage == nil {
|
||||
return nil, errors.New("acme-dns: Storage must be not nil")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: NewDefaultConfig(),
|
||||
client: client,
|
||||
storage: storage,
|
||||
}, nil
|
||||
|
|
@ -172,8 +199,7 @@ func (d *DNSProvider) CleanUp(_, _, _ string) error {
|
|||
// the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain.
|
||||
// If any other error occurs it is returned as-is.
|
||||
func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error {
|
||||
// TODO(@cpu): Read CIDR whitelists from the environment
|
||||
newAcct, err := d.client.RegisterAccount(ctx, nil)
|
||||
newAcct, err := d.client.RegisterAccount(ctx, d.config.AllowList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -207,3 +233,24 @@ func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error {
|
|||
Target: newAcct.FullDomain,
|
||||
}
|
||||
}
|
||||
|
||||
func getStorage(config *Config) (goacmedns.Storage, error) {
|
||||
if config.StoragePath == "" && config.StorageBaseURL == "" {
|
||||
return nil, errors.New("storagePath or storageBaseURL is not set")
|
||||
}
|
||||
|
||||
if config.StoragePath != "" && config.StorageBaseURL != "" {
|
||||
return nil, errors.New("storagePath and storageBaseURL cannot be used at the same time")
|
||||
}
|
||||
|
||||
if config.StoragePath != "" {
|
||||
return storage.NewFile(config.StoragePath, 0o600), nil
|
||||
}
|
||||
|
||||
st, err := internal.NewHTTPStorage(config.StorageBaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new HTTP storage: %w", err)
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com
|
|||
ACME_DNS_API_BASE = "The ACME-DNS API address"
|
||||
ACME_DNS_STORAGE_PATH = "The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates."
|
||||
ACME_DNS_STORAGE_BASE_URL = "The ACME-DNS JSON account data server."
|
||||
[Configuration.Additional]
|
||||
ACME_DNS_ALLOWLIST = "Source networks using CIDR notation (multiple values should be separated with a comma)."
|
||||
|
||||
[Links]
|
||||
API = "https://github.com/joohoi/acme-dns#api"
|
||||
|
|
|
|||
|
|
@ -68,17 +68,20 @@ func TestPresent(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)})
|
||||
require.NoError(t, err)
|
||||
p := &DNSProvider{
|
||||
config: NewDefaultConfig(),
|
||||
client: test.Client,
|
||||
storage: mockStorage{make(map[string]goacmedns.Account)},
|
||||
}
|
||||
|
||||
// override the storage mock if required by the test case.
|
||||
if test.Storage != nil {
|
||||
dp.storage = test.Storage
|
||||
p.storage = test.Storage
|
||||
}
|
||||
|
||||
// call Present. The token argument can be garbage because the ACME-DNS
|
||||
// provider does not use it.
|
||||
err = dp.Present(egDomain, "foo", egKeyAuth)
|
||||
err := p.Present(egDomain, "foo", egKeyAuth)
|
||||
if test.ExpectedError != nil {
|
||||
assert.Equal(t, test.ExpectedError, err)
|
||||
} else {
|
||||
|
|
@ -134,16 +137,19 @@ func TestRegister(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)})
|
||||
require.NoError(t, err)
|
||||
p := &DNSProvider{
|
||||
config: NewDefaultConfig(),
|
||||
client: test.Client,
|
||||
storage: mockStorage{make(map[string]goacmedns.Account)},
|
||||
}
|
||||
|
||||
// override the storage mock if required by the testcase.
|
||||
if test.Storage != nil {
|
||||
dp.storage = test.Storage
|
||||
p.storage = test.Storage
|
||||
}
|
||||
|
||||
// Call register for the example domain/fqdn.
|
||||
err = dp.register(context.Background(), egDomain, egFQDN)
|
||||
err := p.register(context.Background(), egDomain, egFQDN)
|
||||
if test.ExpectedError != nil {
|
||||
assert.Equal(t, test.ExpectedError, err)
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue