acme: add external account binding support.
This commit is contained in:
parent
b5db753e11
commit
a488430f23
6 changed files with 96 additions and 21 deletions
|
@ -10,7 +10,7 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
|
||||||
|
|
||||||
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
||||||
when experimenting to avoid hitting this limit too fast.
|
when experimenting to avoid hitting this limit too fast.
|
||||||
|
|
||||||
## Certificate Resolvers
|
## Certificate Resolvers
|
||||||
|
|
||||||
Traefik requires you to define "Certificate Resolvers" in the [static configuration](../getting-started/configuration-overview.md#the-static-configuration),
|
Traefik requires you to define "Certificate Resolvers" in the [static configuration](../getting-started/configuration-overview.md#the-static-configuration),
|
||||||
|
@ -408,6 +408,35 @@ certificatesResolvers:
|
||||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) supports wildcard certificates.
|
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) supports wildcard certificates.
|
||||||
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](#dnschallenge).
|
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](#dnschallenge).
|
||||||
|
|
||||||
|
## External Account Binding
|
||||||
|
|
||||||
|
- `kid`: Key identifier from External CA
|
||||||
|
- `hmacEncoded`: HMAC key from External CA, should be in Base64 URL Encoding without padding format
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[certificatesResolvers.myresolver.acme]
|
||||||
|
# ...
|
||||||
|
[certificatesResolvers.myresolver.acme.eab]
|
||||||
|
kid = "abc-keyID-xyz"
|
||||||
|
hmacEncoded = "abc-hmac-xyz"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
certificatesResolvers:
|
||||||
|
myresolver:
|
||||||
|
acme:
|
||||||
|
# ...
|
||||||
|
eab:
|
||||||
|
kid: abc-keyID-xyz
|
||||||
|
hmacEncoded: abc-hmac-xyz
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
# ...
|
||||||
|
--certificatesresolvers.myresolver.acme.eab.kid=abc-keyID-xyz
|
||||||
|
--certificatesresolvers.myresolver.acme.eab.hmacencoded=abc-hmac-xyz
|
||||||
|
```
|
||||||
|
|
||||||
## More Configuration
|
## More Configuration
|
||||||
|
|
||||||
### `caServer`
|
### `caServer`
|
||||||
|
|
|
@ -69,6 +69,12 @@ Use a DNS-01 based challenge provider rather than HTTPS.
|
||||||
`--certificatesresolvers.<name>.acme.dnschallenge.resolvers`:
|
`--certificatesresolvers.<name>.acme.dnschallenge.resolvers`:
|
||||||
Use following DNS servers to resolve the FQDN authority.
|
Use following DNS servers to resolve the FQDN authority.
|
||||||
|
|
||||||
|
`--certificatesresolvers.<name>.acme.eab.hmacencoded`:
|
||||||
|
Base64 encoded HMAC key from External CA.
|
||||||
|
|
||||||
|
`--certificatesresolvers.<name>.acme.eab.kid`:
|
||||||
|
Key identifier from External CA.
|
||||||
|
|
||||||
`--certificatesresolvers.<name>.acme.email`:
|
`--certificatesresolvers.<name>.acme.email`:
|
||||||
Email address used for registration.
|
Email address used for registration.
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,12 @@ Use a DNS-01 based challenge provider rather than HTTPS.
|
||||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_RESOLVERS`:
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_RESOLVERS`:
|
||||||
Use following DNS servers to resolve the FQDN authority.
|
Use following DNS servers to resolve the FQDN authority.
|
||||||
|
|
||||||
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_EAB_HMACENCODED`:
|
||||||
|
Base64 encoded HMAC key from External CA.
|
||||||
|
|
||||||
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_EAB_KID`:
|
||||||
|
Key identifier from External CA.
|
||||||
|
|
||||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_EMAIL`:
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_EMAIL`:
|
||||||
Email address used for registration.
|
Email address used for registration.
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
namespaces = ["foobar", "foobar"]
|
namespaces = ["foobar", "foobar"]
|
||||||
labelSelector = "foobar"
|
labelSelector = "foobar"
|
||||||
ingressClass = "foobar"
|
ingressClass = "foobar"
|
||||||
throttleDuration = "10s"
|
throttleDuration = "42s"
|
||||||
[providers.kubernetesIngress.ingressEndpoint]
|
[providers.kubernetesIngress.ingressEndpoint]
|
||||||
ip = "foobar"
|
ip = "foobar"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
|
@ -251,9 +251,6 @@
|
||||||
addEntryPointsLabels = true
|
addEntryPointsLabels = true
|
||||||
addServicesLabels = true
|
addServicesLabels = true
|
||||||
|
|
||||||
[pilot]
|
|
||||||
token = "foobar"
|
|
||||||
|
|
||||||
[ping]
|
[ping]
|
||||||
entryPoint = "foobar"
|
entryPoint = "foobar"
|
||||||
manualRouting = true
|
manualRouting = true
|
||||||
|
@ -343,6 +340,9 @@
|
||||||
preferredChain = "foobar"
|
preferredChain = "foobar"
|
||||||
storage = "foobar"
|
storage = "foobar"
|
||||||
keyType = "foobar"
|
keyType = "foobar"
|
||||||
|
[certificatesResolvers.CertificateResolver0.acme.eab]
|
||||||
|
kid = "foobar"
|
||||||
|
hmacEncoded = "foobar"
|
||||||
[certificatesResolvers.CertificateResolver0.acme.dnsChallenge]
|
[certificatesResolvers.CertificateResolver0.acme.dnsChallenge]
|
||||||
provider = "foobar"
|
provider = "foobar"
|
||||||
delayBeforeCheck = 42
|
delayBeforeCheck = 42
|
||||||
|
@ -358,6 +358,9 @@
|
||||||
preferredChain = "foobar"
|
preferredChain = "foobar"
|
||||||
storage = "foobar"
|
storage = "foobar"
|
||||||
keyType = "foobar"
|
keyType = "foobar"
|
||||||
|
[certificatesResolvers.CertificateResolver1.acme.eab]
|
||||||
|
kid = "foobar"
|
||||||
|
hmacEncoded = "foobar"
|
||||||
[certificatesResolvers.CertificateResolver1.acme.dnsChallenge]
|
[certificatesResolvers.CertificateResolver1.acme.dnsChallenge]
|
||||||
provider = "foobar"
|
provider = "foobar"
|
||||||
delayBeforeCheck = 42
|
delayBeforeCheck = 42
|
||||||
|
@ -367,6 +370,9 @@
|
||||||
entryPoint = "foobar"
|
entryPoint = "foobar"
|
||||||
[certificatesResolvers.CertificateResolver1.acme.tlsChallenge]
|
[certificatesResolvers.CertificateResolver1.acme.tlsChallenge]
|
||||||
|
|
||||||
|
[pilot]
|
||||||
|
token = "foobar"
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
[experimental.plugins]
|
[experimental.plugins]
|
||||||
[experimental.plugins.Descriptor0]
|
[experimental.plugins.Descriptor0]
|
||||||
|
|
|
@ -270,8 +270,6 @@ metrics:
|
||||||
password: foobar
|
password: foobar
|
||||||
addEntryPointsLabels: true
|
addEntryPointsLabels: true
|
||||||
addServicesLabels: true
|
addServicesLabels: true
|
||||||
pilot:
|
|
||||||
token: foobar
|
|
||||||
ping:
|
ping:
|
||||||
entryPoint: foobar
|
entryPoint: foobar
|
||||||
manualRouting: true
|
manualRouting: true
|
||||||
|
@ -358,6 +356,9 @@ certificatesResolvers:
|
||||||
preferredChain: foobar
|
preferredChain: foobar
|
||||||
storage: foobar
|
storage: foobar
|
||||||
keyType: foobar
|
keyType: foobar
|
||||||
|
eab:
|
||||||
|
kid: foobar
|
||||||
|
hmacEncoded: foobar
|
||||||
dnsChallenge:
|
dnsChallenge:
|
||||||
provider: foobar
|
provider: foobar
|
||||||
delayBeforeCheck: 42
|
delayBeforeCheck: 42
|
||||||
|
@ -375,6 +376,9 @@ certificatesResolvers:
|
||||||
preferredChain: foobar
|
preferredChain: foobar
|
||||||
storage: foobar
|
storage: foobar
|
||||||
keyType: foobar
|
keyType: foobar
|
||||||
|
eab:
|
||||||
|
kid: foobar
|
||||||
|
hmacEncoded: foobar
|
||||||
dnsChallenge:
|
dnsChallenge:
|
||||||
provider: foobar
|
provider: foobar
|
||||||
delayBeforeCheck: 42
|
delayBeforeCheck: 42
|
||||||
|
@ -385,6 +389,8 @@ certificatesResolvers:
|
||||||
httpChallenge:
|
httpChallenge:
|
||||||
entryPoint: foobar
|
entryPoint: foobar
|
||||||
tlsChallenge: {}
|
tlsChallenge: {}
|
||||||
|
pilot:
|
||||||
|
token: foobar
|
||||||
experimental:
|
experimental:
|
||||||
plugins:
|
plugins:
|
||||||
Descriptor0:
|
Descriptor0:
|
||||||
|
|
|
@ -33,14 +33,16 @@ var oscpMustStaple = false
|
||||||
|
|
||||||
// Configuration holds ACME configuration provided by users.
|
// Configuration holds ACME configuration provided by users.
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
|
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
|
||||||
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
|
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
|
||||||
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty" export:"true"`
|
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty" export:"true"`
|
||||||
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty" export:"true"`
|
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty" export:"true"`
|
||||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty" export:"true"`
|
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty" export:"true"`
|
||||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"`
|
||||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
|
||||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults sets the default values.
|
// SetDefaults sets the default values.
|
||||||
|
@ -63,7 +65,13 @@ type Certificate struct {
|
||||||
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
|
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSChallenge contains DNS challenge Configuration.
|
// EAB contains External Account Binding configuration.
|
||||||
|
type EAB struct {
|
||||||
|
Kid string `description:"Key identifier from External CA." json:"kid,omitempty" toml:"kid,omitempty" yaml:"kid,omitempty"`
|
||||||
|
HmacEncoded string `description:"Base64 encoded HMAC key from External CA." json:"hmacEncoded,omitempty" toml:"hmacEncoded,omitempty" yaml:"hmacEncoded,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSChallenge contains DNS challenge configuration.
|
||||||
type DNSChallenge struct {
|
type DNSChallenge struct {
|
||||||
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS." json:"provider,omitempty" toml:"provider,omitempty" yaml:"provider,omitempty" export:"true"`
|
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS." json:"provider,omitempty" toml:"provider,omitempty" yaml:"provider,omitempty" export:"true"`
|
||||||
DelayBeforeCheck ptypes.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." json:"delayBeforeCheck,omitempty" toml:"delayBeforeCheck,omitempty" yaml:"delayBeforeCheck,omitempty" export:"true"`
|
DelayBeforeCheck ptypes.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." json:"delayBeforeCheck,omitempty" toml:"delayBeforeCheck,omitempty" yaml:"delayBeforeCheck,omitempty" export:"true"`
|
||||||
|
@ -71,12 +79,12 @@ type DNSChallenge struct {
|
||||||
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]" json:"disablePropagationCheck,omitempty" toml:"disablePropagationCheck,omitempty" yaml:"disablePropagationCheck,omitempty" export:"true"`
|
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]" json:"disablePropagationCheck,omitempty" toml:"disablePropagationCheck,omitempty" yaml:"disablePropagationCheck,omitempty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPChallenge contains HTTP challenge Configuration.
|
// HTTPChallenge contains HTTP challenge configuration.
|
||||||
type HTTPChallenge struct {
|
type HTTPChallenge struct {
|
||||||
EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSChallenge contains TLS challenge Configuration.
|
// TLSChallenge contains TLS challenge configuration.
|
||||||
type TLSChallenge struct{}
|
type TLSChallenge struct{}
|
||||||
|
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
|
@ -233,9 +241,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
||||||
|
|
||||||
// New users will need to register; be sure to save it
|
// New users will need to register; be sure to save it
|
||||||
if account.GetRegistration() == nil {
|
if account.GetRegistration() == nil {
|
||||||
logger.Info("Register...")
|
reg, errR := p.register(ctx, client)
|
||||||
|
|
||||||
reg, errR := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
||||||
if errR != nil {
|
if errR != nil {
|
||||||
return nil, errR
|
return nil, errR
|
||||||
}
|
}
|
||||||
|
@ -324,6 +330,22 @@ func (p *Provider) initAccount(ctx context.Context) (*Account, error) {
|
||||||
return p.account, nil
|
return p.account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) register(ctx context.Context, client *lego.Client) (*registration.Resource, error) {
|
||||||
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
|
if p.EAB != nil {
|
||||||
|
logger.Info("Register with external account binding...")
|
||||||
|
|
||||||
|
eabOptions := registration.RegisterEABOptions{TermsOfServiceAgreed: true, Kid: p.EAB.Kid, HmacEncoded: p.EAB.HmacEncoded}
|
||||||
|
|
||||||
|
return client.Registration.RegisterWithExternalAccountBinding(eabOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Register...")
|
||||||
|
|
||||||
|
return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStore string) {
|
func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStore string) {
|
||||||
if len(domains) == 0 {
|
if len(domains) == 0 {
|
||||||
log.FromContext(ctx).Debug("No domain parsed in provider ACME")
|
log.FromContext(ctx).Debug("No domain parsed in provider ACME")
|
||||||
|
|
Loading…
Reference in a new issue