Certificate resolvers.

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-07-19 11:52:04 +02:00 committed by Traefiker Bot
parent e3627e9cba
commit f75f73f3d2
47 changed files with 1573 additions and 1249 deletions

View file

@ -20,6 +20,7 @@ import (
"github.com/containous/traefik/pkg/config/dynamic"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider/acme"
"github.com/containous/traefik/pkg/provider/aggregator"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/server"
@ -88,7 +89,9 @@ func runCmd(staticConfiguration *static.Configuration) error {
}
staticConfiguration.SetEffectiveConfiguration()
staticConfiguration.ValidateConfiguration()
if err := staticConfiguration.ValidateConfiguration(); err != nil {
return err
}
log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
@ -112,15 +115,9 @@ func runCmd(staticConfiguration *static.Configuration) error {
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
acmeProvider, err := staticConfiguration.InitACMEProvider()
if err != nil {
log.WithoutContext().Errorf("Unable to initialize ACME provider: %v", err)
} else if acmeProvider != nil {
if err := providerAggregator.AddProvider(acmeProvider); err != nil {
log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err)
acmeProvider = nil
}
}
tlsManager := traefiktls.NewManager()
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
serverEntryPointsTCP := make(server.TCPEntryPoints)
for entryPointName, config := range staticConfiguration.EntryPoints {
@ -129,27 +126,31 @@ func runCmd(staticConfiguration *static.Configuration) error {
if err != nil {
return fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProvider)
serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProviders)
}
tlsManager := traefiktls.NewManager()
if acmeProvider != nil {
acmeProvider.SetTLSManager(tlsManager)
if acmeProvider.TLSChallenge != nil &&
acmeProvider.HTTPChallenge == nil &&
acmeProvider.DNSChallenge == nil {
tlsManager.TLSAlpnGetter = acmeProvider.GetTLSALPNCertificate
}
}
svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPointsTCP, tlsManager)
if acmeProvider != nil && acmeProvider.OnHostRule {
acmeProvider.SetConfigListenerChan(make(chan dynamic.Configuration))
svr.AddListener(acmeProvider.ListenConfiguration)
resolverNames := map[string]struct{}{}
for _, p := range acmeProviders {
resolverNames[p.ResolverName] = struct{}{}
svr.AddListener(p.ListenConfiguration)
}
svr.AddListener(func(config dynamic.Configuration) {
for rtName, rt := range config.HTTP.Routers {
if rt.TLS == nil || rt.TLS.CertResolver == "" {
continue
}
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
}
}
})
ctx := cmd.ContextWithSignal(context.Background())
if staticConfiguration.Ping != nil {
@ -196,6 +197,40 @@ func runCmd(staticConfiguration *static.Configuration) error {
return nil
}
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider {
challengeStore := acme.NewLocalChallengeStore()
localStores := map[string]*acme.LocalStore{}
var resolvers []*acme.Provider
for name, resolver := range c.CertificatesResolvers {
if resolver.ACME != nil {
if localStores[resolver.ACME.Storage] == nil {
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
}
p := &acme.Provider{
Configuration: resolver.ACME,
Store: localStores[resolver.ACME.Storage],
ChallengeStore: challengeStore,
ResolverName: name,
}
if err := providerAggregator.AddProvider(p); err != nil {
log.WithoutContext().Errorf("Unable to add ACME provider to the providers list: %v", err)
continue
}
p.SetTLSManager(tlsManager)
if p.TLSChallenge != nil {
tlsManager.TLSAlpnGetter = p.GetTLSALPNCertificate
}
p.SetConfigListenerChan(make(chan dynamic.Configuration))
resolvers = append(resolvers, p)
}
}
return resolvers
}
func configureLogging(staticConfiguration *static.Configuration) {
// configure default log flags
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)

View file

@ -11,8 +11,8 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
## Configuration Examples
??? example "Enabling ACME"
```toml tab="TOML"
```toml tab="File (TOML)"
[entryPoints]
[entryPoints.web]
address = ":80"
@ -20,18 +20,15 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
[entryPoints.web-secure]
address = ":443"
# every router with TLS enabled will now be able to use ACME for its certificates
[acme]
[certificatesResolvers.sample.acme]
email = "your-email@your-domain.org"
storage = "acme.json"
# dynamic generation based on the Host() & HostSNI() matchers
onHostRule = true
[acme.httpChallenge]
# used during the challenge
entryPoint = "web"
```
```yaml tab="YAML"
```yaml tab="File (YAML)"
entryPoints:
web:
address: ":80"
@ -39,50 +36,24 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
web-secure:
address: ":443"
# every router with TLS enabled will now be able to use ACME for its certificates
acme:
email: your-email@your-domain.org
storage: acme.json
# dynamic generation based on the Host() & HostSNI() matchers
onHostRule: true
httpChallenge:
# used during the challenge
entryPoint: web
```
??? example "Configuring Wildcard Certificates"
```toml tab="TOML"
[entryPoints]
[entryPoints.web-secure]
address = ":443"
[acme]
email = "your-email@your-domain.org"
storage = "acme.json"
[acme.dnsChallenge]
provider = "xxx"
[[acme.domains]]
main = "*.mydomain.com"
sans = ["mydomain.com"]
certificatesResolvers:
sample:
acme:
email: your-email@your-domain.org
storage: acme.json
httpChallenge:
# used during the challenge
entryPoint: web
```
```yaml tab="YAML"
entryPoints:
web-secure:
address: ":443"
acme:
email: your-email@your-domain.org
storage: acme.json
dnsChallenge:
provide: xxx
domains:
- main: "*.mydomain.com"
sans:
- mydomain.com
```bash tab="CLI"
--entryPoints.web.address=":80"
--entryPoints.websecure.address=":443"
# ...
--certificatesResolvers.sample.acme.email: your-email@your-domain.org
--certificatesResolvers.sample.acme.storage: acme.json
# used during the challenge
--certificatesResolvers.sample.acme.httpChallenge.entryPoint: web
```
??? note "Configuration Reference"
@ -90,13 +61,17 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom
There are many available options for ACME.
For a quick glance at what's possible, browse the configuration reference:
```toml tab="TOML"
```toml tab="File (TOML)"
--8<-- "content/https/ref-acme.toml"
```
```yaml tab="YAML"
```yaml tab="File (YAML)"
--8<-- "content/https/ref-acme.yaml"
```
```bash tab="CLI"
--8<-- "content/https/ref-acme.txt"
```
## Automatic Renewals
@ -118,16 +93,25 @@ when using the `TLS-ALPN-01` challenge, Traefik must be reachable by Let's Encry
??? example "Configuring the `tlsChallenge`"
```toml tab="TOML"
[acme]
[acme.tlsChallenge]
```toml tab="File (TOML)"
[certificatesResolvers.sample.acme]
# ...
[certificatesResolvers.sample.acme.tlsChallenge]
```
```yaml tab="YAML"
acme:
tlsChallenge: {}
```yaml tab="File (YAML)"
certificatesResolvers:
sample:
acme:
# ...
tlsChallenge: {}
```
```bash tab="CLI"
# ...
--certificatesResolvers.sample.acme.tlsChallenge
```
### `httpChallenge`
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning an HTTP resource under a well-known URI.
@ -137,7 +121,7 @@ when using the `HTTP-01` challenge, `acme.httpChallenge.entryPoint` must be reac
??? example "Using an EntryPoint Called http for the `httpChallenge`"
```toml tab="TOML"
```toml tab="File (TOML)"
[entryPoints]
[entryPoints.web]
address = ":80"
@ -145,13 +129,13 @@ when using the `HTTP-01` challenge, `acme.httpChallenge.entryPoint` must be reac
[entryPoints.web-secure]
address = ":443"
[acme]
[certificatesResolvers.sample.acme]
# ...
[acme.httpChallenge]
[certificatesResolvers.sample.acme.httpChallenge]
entryPoint = "web"
```
```yaml tab="YAML"
```yaml tab="File (YAML)"
entryPoints:
web:
address: ":80"
@ -159,10 +143,19 @@ when using the `HTTP-01` challenge, `acme.httpChallenge.entryPoint` must be reac
web-secure:
address: ":443"
acme:
# ...
httpChallenge:
entryPoint: web
certificatesResolvers:
sample:
acme:
# ...
httpChallenge:
entryPoint: web
```
```bash tab="CLI"
--entryPoints.web.address=":80"
--entryPoints.websecure.address=":443"
# ...
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
```
!!! note
@ -174,21 +167,30 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
??? example "Configuring a `dnsChallenge` with the DigitalOcean Provider"
```toml tab="TOML"
[acme]
```toml tab="File (TOML)"
[certificatesResolvers.sample.acme]
# ...
[acme.dnsChallenge]
[certificatesResolvers.sample.acme.dnsChallenge]
provider = "digitalocean"
delayBeforeCheck = 0
# ...
```
```yaml tab="YAML"
acme:
# ...
dnsChallenge:
provider: digitalocean
delayBeforeCheck: 0
```yaml tab="File (YAML)"
certificatesResolvers:
sample:
acme:
# ...
dnsChallenge:
provider: digitalocean
delayBeforeCheck: 0
# ...
```
```bash tab="CLI"
# ...
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
# ...
```
@ -238,7 +240,7 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) |
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) |
| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linodev4) |
| manual | - | none, but you need to run Traefik interactively [^4], turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | |
| manual | - | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press <kbd>Enter</kbd>. | |
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/namecheap) |
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
@ -276,22 +278,29 @@ For example, `CF_API_EMAIL_FILE=/run/secrets/traefik_cf-api-email` could be used
Use custom DNS servers to resolve the FQDN authority.
```toml tab="TOML"
[acme]
```toml tab="File (TOML)"
[certificatesResolvers.sample.acme]
# ...
[acme.dnsChallenge]
[certificatesResolvers.sample.acme.dnsChallenge]
# ...
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
```
```yaml tab="YAML"
acme:
# ...
dnsChallenge:
# ...
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
```yaml tab="File (YAML)"
certificatesResolvers:
sample:
acme:
# ...
dnsChallenge:
# ...
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
```
```bash tab="CLI"
# ...
--certificatesResolvers.sample.acme.dnsChallenge.resolvers:="1.1.1.1:53,8.8.8.8:53"
```
#### Wildcard Domains
@ -299,140 +308,56 @@ acme:
[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).
```toml tab="TOML"
[acme]
# ...
[[acme.domains]]
main = "*.local1.com"
sans = ["local1.com"]
# ...
```
```yaml tab="YAML"
acme:
# ...
domains:
- main: "*.local1.com"
sans:
- local1.com
# ...
```
!!! note "Double Wildcard Certificates"
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
In this case the generated DNS TXT record for both domains is the same.
Even though this behavior is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant,
it can lead to problems as all DNS providers keep DNS records cached for a given time (TTL) and this TTL can be greater than the challenge timeout making the `DNS-01` challenge fail.
The Traefik ACME client library [LEGO](https://github.com/go-acme/lego) supports some but not all DNS providers to work around this issue.
The [Supported `provider` table](#providers) indicates if they allow generating certificates for a wildcard domain and its root domain.
## Known Domains, SANs
You can set SANs (alternative domains) for each main domain.
Every domain must have A/AAAA records pointing to Traefik.
Each domain & SAN will lead to a certificate request.
```toml tab="TOML"
[acme]
# ...
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
main = "local2.com"
[[acme.domains]]
main = "*.local3.com"
sans = ["local3.com", "test1.test1.local3.com"]
# ...
```
```yaml tab="YAML"
acme:
# ...
domains:
- main: "local1.com"
sans:
- "test1.local1.com"
- "test2.local1.com"
- main: "local2.com"
- main: "*.local3.com"
sans:
- "local3.com"
- "test1.test1.local3.com"
# ...
```
!!! important
The certificates for the domains listed in `acme.domains` are negotiated at Traefik startup only.
!!! note
Wildcard certificates can only be verified through a `DNS-01` challenge.
## `caServer`
??? example "Using the Let's Encrypt staging server"
```toml tab="TOML"
[acme]
```toml tab="File (TOML)"
[certificatesResolvers.sample.acme]
# ...
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# ...
```
```yaml tab="YAML"
acme:
# ...
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
# ...
```yaml tab="File (YAML)"
certificatesResolvers:
sample:
acme:
# ...
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
# ...
```
## `onHostRule`
Enable certificate generation on [routers](../routing/routers/index.md) `Host` & `HostSNI` rules.
This will request a certificate from Let's Encrypt for each router with a Host rule.
```toml tab="TOML"
[acme]
# ...
onHostRule = true
# ...
```
```yaml tab="YAML"
acme:
# ...
onHostRule: true
# ...
```
!!! note "Multiple Hosts in a Rule"
The rule `Host(test1.traefik.io,test2.traefik.io)` will request a certificate with the main domain `test1.traefik.io` and SAN `test2.traefik.io`.
!!! warning
`onHostRule` option can not be used to generate wildcard certificates. Refer to [wildcard generation](#wildcard-domains) for further information.
```bash tab="CLI"
# ...
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
# ...
```
## `storage`
The `storage` option sets the location where your ACME certificates are saved to.
```toml tab="TOML"
[acme]
```toml tab="File (TOML)"
[certificatesResolvers.sample.acme]
# ...
storage = "acme.json"
# ...
```
```yaml tab="YAML"
acme
# ...
storage: acme.json
# ...
```toml tab="File (TOML)"
certificatesResolvers:
sample:
acme:
# ...
storage: acme.json
# ...
```
```bash tab="CLI"
# ...
--certificatesResolvers.sample.acme.storage=acme.json
# ...
```
The value can refer to some kinds of storage:

View file

@ -1,123 +1,89 @@
# Enable ACME (Let's Encrypt): automatic SSL.
[acme]
[certificatesResolvers.sample.acme]
# Email address used for registration.
#
# Required
#
email = "test@traefik.io"
# File or key used for certificates storage.
#
# Required
#
storage = "acme.json"
# If true, display debug log messages from the acme client library.
#
# Optional
# Default: false
#
# acmeLogging = true
# If true, override certificates in key-value store when using storeconfig.
#
# Optional
# Default: false
#
# overrideCertificates = true
# Enable certificate generation on routers host rules.
#
# Optional
# Default: false
#
# onHostRule = true
# CA server to use.
# Uncomment the line to use Let's Encrypt's staging server,
# leave commented to go to prod.
#
# Optional
# Default: "https://acme-v02.api.letsencrypt.org/directory"
#
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# KeyType to use.
#
# Optional
# Default: "RSA4096"
#
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
# KeyType = "RSA4096"
# Use a TLS-ALPN-01 ACME challenge.
#
# Optional (but recommended)
#
[acme.tlsChallenge]
# Use a HTTP-01 ACME challenge.
#
# Optional
#
# [acme.httpChallenge]
# EntryPoint to use for the HTTP-01 challenges.
# Email address used for registration.
#
# Required
#
# entryPoint = "web"
email = "test@traefik.io"
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
# Note: mandatory for wildcard certificate generation.
#
# Optional
#
# [acme.dnsChallenge]
# DNS provider used.
# File or key used for certificates storage.
#
# Required
#
# provider = "digitalocean"
storage = "acme.json"
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
# Useful if internal networks block external DNS queries.
# CA server to use.
# Uncomment the line to use Let's Encrypt's staging server,
# leave commented to go to prod.
#
# Optional
# Default: 0
# Default: "https://acme-v02.api.letsencrypt.org/directory"
#
# delayBeforeCheck = 0
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
# Use following DNS servers to resolve the FQDN authority.
# KeyType to use.
#
# Optional
# Default: empty
# Default: "RSA4096"
#
# resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
# keyType = "RSA4096"
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
# Use a TLS-ALPN-01 ACME challenge.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
# Optional (but recommended)
#
[certificatesResolvers.sample.acme.tlsChallenge]
# Use a HTTP-01 ACME challenge.
#
# Optional
# Default: false
#
# disablePropagationCheck = true
# [certificatesResolvers.sample.acme.httpChallenge]
# Domains list.
# Only domains defined here can generate wildcard certificates.
# The certificates for these domains are negotiated at traefik startup only.
#
# [[acme.domains]]
# main = "local1.com"
# sans = ["test1.local1.com", "test2.local1.com"]
# [[acme.domains]]
# main = "local2.com"
# [[acme.domains]]
# main = "*.local3.com"
# sans = ["local3.com", "test1.test1.local3.com"]
# EntryPoint to use for the HTTP-01 challenges.
#
# Required
#
# entryPoint = "web"
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
# Note: mandatory for wildcard certificate generation.
#
# Optional
#
# [certificatesResolvers.sample.acme.dnsChallenge]
# DNS provider used.
#
# Required
#
# provider = "digitalocean"
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Default: 0
#
# delayBeforeCheck = 0
# Use following DNS servers to resolve the FQDN authority.
#
# Optional
# Default: empty
#
# resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
#
# Optional
# Default: false
#
# disablePropagationCheck = true

View file

@ -0,0 +1,89 @@
# Enable ACME (Let's Encrypt): automatic SSL.
--certificatesResolvers.sample.acme
# Email address used for registration.
#
# Required
#
--certificatesResolvers.sample.acme.email="test@traefik.io"
# File or key used for certificates storage.
#
# Required
#
--certificatesResolvers.sample.acme.storage="acme.json"
# CA server to use.
# Uncomment the line to use Let's Encrypt's staging server,
# leave commented to go to prod.
#
# Optional
# Default: "https://acme-v02.api.letsencrypt.org/directory"
#
--certificatesResolvers.sample.acme.caServer="https://acme-staging-v02.api.letsencrypt.org/directory"
# KeyType to use.
#
# Optional
# Default: "RSA4096"
#
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
--certificatesResolvers.sample.acme.keyType=RSA4096
# Use a TLS-ALPN-01 ACME challenge.
#
# Optional (but recommended)
#
--certificatesResolvers.sample.acme.tlsChallenge
# Use a HTTP-01 ACME challenge.
#
# Optional
#
--certificatesResolvers.sample.acme.httpChallenge
# EntryPoint to use for the HTTP-01 challenges.
#
# Required
#
--certificatesResolvers.sample.acme.httpChallenge.entryPoint=web
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
# Note: mandatory for wildcard certificate generation.
#
# Optional
#
--certificatesResolvers.sample.acme.dnsChallenge
# DNS provider used.
#
# Required
#
--certificatesResolvers.sample.acme.dnsChallenge.provider=digitalocean
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Default: 0
#
--certificatesResolvers.sample.acme.dnsChallenge.delayBeforeCheck=0
# Use following DNS servers to resolve the FQDN authority.
#
# Optional
# Default: empty
#
--certificatesResolvers.sample.acme.dnsChallenge.resolvers="1.1.1.1:53,8.8.8.8:53"
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
#
# Optional
# Default: false
#
--certificatesResolvers.sample.acme.dnsChallenge.disablePropagationCheck=true

View file

@ -1,127 +1,93 @@
# Enable ACME (Let's Encrypt): automatic SSL.
acme:
certificatesResolvers:
sample:
# Enable ACME (Let's Encrypt): automatic SSL.
acme:
# Email address used for registration.
#
# Required
#
email: "test@traefik.io"
# Email address used for registration.
#
# Required
#
email: "test@traefik.io"
# File or key used for certificates storage.
#
# Required
#
storage: "acme.json"
# File or key used for certificates storage.
#
# Required
#
storage: "acme.json"
# If true, display debug log messages from the acme client library.
#
# Optional
# Default: false
#
# acmeLogging: true
# CA server to use.
# Uncomment the line to use Let's Encrypt's staging server,
# leave commented to go to prod.
#
# Optional
# Default: "https://acme-v02.api.letsencrypt.org/directory"
#
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# If true, override certificates in key-value store when using storeconfig.
#
# Optional
# Default: false
#
# overrideCertificates: true
# KeyType to use.
#
# Optional
# Default: "RSA4096"
#
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
# keyType: RSA4096
# Enable certificate generation on routers host rules.
#
# Optional
# Default: false
#
# onHostRule: true
# Use a TLS-ALPN-01 ACME challenge.
#
# Optional (but recommended)
#
tlsChallenge:
# CA server to use.
# Uncomment the line to use Let's Encrypt's staging server,
# leave commented to go to prod.
#
# Optional
# Default: "https://acme-v02.api.letsencrypt.org/directory"
#
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# Use a HTTP-01 ACME challenge.
#
# Optional
#
# httpChallenge:
# KeyType to use.
#
# Optional
# Default: "RSA4096"
#
# Available values : "EC256", "EC384", "RSA2048", "RSA4096", "RSA8192"
#
# KeyType: RSA4096
# EntryPoint to use for the HTTP-01 challenges.
#
# Required
#
# entryPoint: web
# Use a TLS-ALPN-01 ACME challenge.
#
# Optional (but recommended)
#
tlsChallenge:
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
# Note: mandatory for wildcard certificate generation.
#
# Optional
#
# dnsChallenge:
# Use a HTTP-01 ACME challenge.
#
# Optional
#
# httpChallenge:
# DNS provider used.
#
# Required
#
# provider: digitalocean
# EntryPoint to use for the HTTP-01 challenges.
#
# Required
#
# entryPoint: web
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Default: 0
#
# delayBeforeCheck: 0
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
# Note: mandatory for wildcard certificate generation.
#
# Optional
#
# dnsChallenge:
# Use following DNS servers to resolve the FQDN authority.
#
# Optional
# Default: empty
#
# resolvers
# - "1.1.1.1:53"
# - "8.8.8.8:53"
# DNS provider used.
#
# Required
#
# provider: digitalocean
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
# Useful if internal networks block external DNS queries.
#
# Optional
# Default: 0
#
# delayBeforeCheck: 0
# Use following DNS servers to resolve the FQDN authority.
#
# Optional
# Default: empty
#
# resolvers
# - "1.1.1.1:53"
# - "8.8.8.8:53"
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
#
# Optional
# Default: false
#
# disablePropagationCheck: true
# Domains list.
# Only domains defined here can generate wildcard certificates.
# The certificates for these domains are negotiated at traefik startup only.
#
# domains:
# - main: "local1.com"
# sans:
# - "test1.local1.com"
# - "test2.local1.com"
# - main: "local2.com"
# - main: "*.local3.com"
# sans:
# - "local3.com"
# - "test1.test1.local3.com"
# Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready.
#
# NOT RECOMMENDED:
# Increase the risk of reaching Let's Encrypt's rate limits.
#
# Optional
# Default: false
#
# disablePropagationCheck: true

View file

@ -36,60 +36,6 @@ Keep access logs with status codes in the specified range.
`--accesslog.format`:
Access log format: json | common (Default: ```common```)
`--acme.acmelogging`:
Enable debug logging of ACME actions. (Default: ```false```)
`--acme.caserver`:
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
`--acme.dnschallenge`:
Activate DNS-01 Challenge. (Default: ```false```)
`--acme.dnschallenge.delaybeforecheck`:
Assume DNS propagates after a delay in seconds rather than finding and querying nameservers. (Default: ```0```)
`--acme.dnschallenge.disablepropagationcheck`:
Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended] (Default: ```false```)
`--acme.dnschallenge.provider`:
Use a DNS-01 based challenge provider rather than HTTPS.
`--acme.dnschallenge.resolvers`:
Use following DNS servers to resolve the FQDN authority.
`--acme.domains`:
The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge.
`--acme.domains[n].main`:
Default subject name.
`--acme.domains[n].sans`:
Subject alternative names.
`--acme.email`:
Email address used for registration.
`--acme.entrypoint`:
EntryPoint to use.
`--acme.httpchallenge`:
Activate HTTP-01 Challenge. (Default: ```false```)
`--acme.httpchallenge.entrypoint`:
HTTP challenge EntryPoint
`--acme.keytype`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`--acme.onhostrule`:
Enable certificate generation on router Host rules. (Default: ```false```)
`--acme.storage`:
Storage to use. (Default: ```acme.json```)
`--acme.tlschallenge`:
Activate TLS-ALPN-01 Challenge. (Default: ```true```)
`--api`:
Enable api/dashboard. (Default: ```false```)
@ -111,6 +57,45 @@ Enable more detailed statistics. (Default: ```false```)
`--api.statistics.recenterrors`:
Number of recent errors logged. (Default: ```10```)
`--certificatesresolvers.<name>`:
Certificates resolvers configuration. (Default: ```false```)
`--certificatesresolvers.<name>.acme.caserver`:
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
`--certificatesresolvers.<name>.acme.dnschallenge`:
Activate DNS-01 Challenge. (Default: ```false```)
`--certificatesresolvers.<name>.acme.dnschallenge.delaybeforecheck`:
Assume DNS propagates after a delay in seconds rather than finding and querying nameservers. (Default: ```0```)
`--certificatesresolvers.<name>.acme.dnschallenge.disablepropagationcheck`:
Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended] (Default: ```false```)
`--certificatesresolvers.<name>.acme.dnschallenge.provider`:
Use a DNS-01 based challenge provider rather than HTTPS.
`--certificatesresolvers.<name>.acme.dnschallenge.resolvers`:
Use following DNS servers to resolve the FQDN authority.
`--certificatesresolvers.<name>.acme.email`:
Email address used for registration.
`--certificatesresolvers.<name>.acme.httpchallenge`:
Activate HTTP-01 Challenge. (Default: ```false```)
`--certificatesresolvers.<name>.acme.httpchallenge.entrypoint`:
HTTP challenge EntryPoint
`--certificatesresolvers.<name>.acme.keytype`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`--certificatesresolvers.<name>.acme.storage`:
Storage to use. (Default: ```acme.json```)
`--certificatesresolvers.<name>.acme.tlschallenge`:
Activate TLS-ALPN-01 Challenge. (Default: ```true```)
`--entrypoints.<name>`:
Entry points definition. (Default: ```false```)

View file

@ -36,60 +36,6 @@ Keep access logs with status codes in the specified range.
`TRAEFIK_ACCESSLOG_FORMAT`:
Access log format: json | common (Default: ```common```)
`TRAEFIK_ACME_ACMELOGGING`:
Enable debug logging of ACME actions. (Default: ```false```)
`TRAEFIK_ACME_CASERVER`:
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
`TRAEFIK_ACME_DNSCHALLENGE`:
Activate DNS-01 Challenge. (Default: ```false```)
`TRAEFIK_ACME_DNSCHALLENGE_DELAYBEFORECHECK`:
Assume DNS propagates after a delay in seconds rather than finding and querying nameservers. (Default: ```0```)
`TRAEFIK_ACME_DNSCHALLENGE_DISABLEPROPAGATIONCHECK`:
Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended] (Default: ```false```)
`TRAEFIK_ACME_DNSCHALLENGE_PROVIDER`:
Use a DNS-01 based challenge provider rather than HTTPS.
`TRAEFIK_ACME_DNSCHALLENGE_RESOLVERS`:
Use following DNS servers to resolve the FQDN authority.
`TRAEFIK_ACME_DOMAINS`:
The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge.
`TRAEFIK_ACME_DOMAINS[n]_MAIN`:
Default subject name.
`TRAEFIK_ACME_DOMAINS[n]_SANS`:
Subject alternative names.
`TRAEFIK_ACME_EMAIL`:
Email address used for registration.
`TRAEFIK_ACME_ENTRYPOINT`:
EntryPoint to use.
`TRAEFIK_ACME_HTTPCHALLENGE`:
Activate HTTP-01 Challenge. (Default: ```false```)
`TRAEFIK_ACME_HTTPCHALLENGE_ENTRYPOINT`:
HTTP challenge EntryPoint
`TRAEFIK_ACME_KEYTYPE`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`TRAEFIK_ACME_ONHOSTRULE`:
Enable certificate generation on router Host rules. (Default: ```false```)
`TRAEFIK_ACME_STORAGE`:
Storage to use. (Default: ```acme.json```)
`TRAEFIK_ACME_TLSCHALLENGE`:
Activate TLS-ALPN-01 Challenge. (Default: ```true```)
`TRAEFIK_API`:
Enable api/dashboard. (Default: ```false```)
@ -111,6 +57,45 @@ Enable more detailed statistics. (Default: ```false```)
`TRAEFIK_API_STATISTICS_RECENTERRORS`:
Number of recent errors logged. (Default: ```10```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>`:
Certificates resolvers configuration. (Default: ```false```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASERVER`:
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE`:
Activate DNS-01 Challenge. (Default: ```false```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_DELAYBEFORECHECK`:
Assume DNS propagates after a delay in seconds rather than finding and querying nameservers. (Default: ```0```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_DISABLEPROPAGATIONCHECK`:
Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended] (Default: ```false```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_PROVIDER`:
Use a DNS-01 based challenge provider rather than HTTPS.
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE_RESOLVERS`:
Use following DNS servers to resolve the FQDN authority.
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_EMAIL`:
Email address used for registration.
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_HTTPCHALLENGE`:
Activate HTTP-01 Challenge. (Default: ```false```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_HTTPCHALLENGE_ENTRYPOINT`:
HTTP challenge EntryPoint
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_KEYTYPE`:
KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_STORAGE`:
Storage to use. (Default: ```acme.json```)
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_TLSCHALLENGE`:
Activate TLS-ALPN-01 Challenge. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>`:
Entry points definition. (Default: ```false```)

View file

@ -221,12 +221,10 @@
[acme]
email = "foobar"
acmeLogging = true
caServer = "foobar"
storage = "foobar"
entryPoint = "foobar"
keyType = "foobar"
onHostRule = true
[acme.dnsChallenge]
provider = "foobar"
delayBeforeCheck = 42

View file

@ -230,12 +230,10 @@ hostResolver:
resolvDepth: 42
acme:
email: foobar
acmeLogging: true
caServer: foobar
storage: foobar
entryPoint: foobar
keyType: foobar
onHostRule: true
dnsChallenge:
provider: foobar
delayBeforeCheck: 42

View file

@ -325,9 +325,9 @@ Traefik will terminate the SSL connections (meaning that it will send decrypted
service: service-id
```
#### `Options`
#### `options`
The `Options` field enables fine-grained control of the TLS parameters.
The `options` field enables fine-grained control of the TLS parameters.
It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied only if a `Host` rule is defined.
!!! note "Server Name Association"
@ -384,13 +384,13 @@ It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied
[http.routers.routerfoo]
rule = "Host(`snitest.com`) && Path(`/foo`)"
[http.routers.routerfoo.tls]
options="foo"
options = "foo"
[http.routers]
[http.routers.routerbar]
rule = "Host(`snitest.com`) && Path(`/bar`)"
[http.routers.routerbar.tls]
options="bar"
options = "bar"
```
```yaml tab="YAML"
@ -409,6 +409,76 @@ It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied
If that happens, both mappings are discarded, and the host name (`snitest.com` in this case) for these routers gets associated with the default TLS options instead.
#### `certResolver`
If `certResolver` is defined, Traefik will try to generate certificates based on routers `Host` & `HostSNI` rules.
```toml tab="TOML"
[http.routers]
[http.routers.routerfoo]
rule = "Host(`snitest.com`) && Path(`/foo`)"
[http.routers.routerfoo.tls]
certResolver = "foo"
```
```yaml tab="YAML"
http:
routers:
routerfoo:
rule: "Host(`snitest.com`) && Path(`/foo`)"
tls:
certResolver: foo
```
!!! note "Multiple Hosts in a Rule"
The rule `Host(test1.traefik.io,test2.traefik.io)` will request a certificate with the main domain `test1.traefik.io` and SAN `test2.traefik.io`.
#### `domains`
You can set SANs (alternative domains) for each main domain.
Every domain must have A/AAAA records pointing to Traefik.
Each domain & SAN will lead to a certificate request.
```toml tab="TOML"
[http.routers]
[http.routers.routerbar]
rule = "Host(`snitest.com`) && Path(`/bar`)"
[http.routers.routerbar.tls]
certResolver = "bar"
[[http.routers.routerbar.tls.domains]]
main = "snitest.com"
sans = "*.snitest.com"
```
```yaml tab="YAML"
http:
routers:
routerbar:
rule: "Host(`snitest.com`) && Path(`/bar`)"
tls:
certResolver: "bar"
domains:
- main: "snitest.com"
sans: "*.snitest.com"
```
[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](./../../https/acme.md#dnschallenge).
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
In this case the generated DNS TXT record for both domains is the same.
Even though this behavior is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant,
it can lead to problems as all DNS providers keep DNS records cached for a given time (TTL) and this TTL can be greater than the challenge timeout making the `DNS-01` challenge fail.
The Traefik ACME client library [LEGO](https://github.com/go-acme/lego) supports some but not all DNS providers to work around this issue.
The [Supported `provider` table](./../../https/acme.md#providers) indicates if they allow generating certificates for a wildcard domain and its root domain.
!!! note
Wildcard certificates can only be verified through a `DNS-01` challenge.
!!! note "Double Wildcard Certificates"
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
## Configuring TCP Routers
### General
@ -593,9 +663,9 @@ Services are the target for the router.
In the current version, with [ACME](../../https/acme.md) enabled, automatic certificate generation will apply to every router declaring a TLS section.
#### `Options`
#### `options`
The `Options` field enables fine-grained control of the TLS parameters.
The `options` field enables fine-grained control of the TLS parameters.
It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied only if a `HostSNI` rule is defined.
??? example "Configuring the tls options"
@ -636,3 +706,51 @@ It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_RSA_WITH_AES_256_GCM_SHA384"
```
#### `certResolver`
See [`certResolver` for HTTP router](./index.md#certresolver) for more information.
```toml tab="TOML"
[tcp.routers]
[tcp.routers.routerfoo]
rule = "HostSNI(`snitest.com`)"
[tcp.routers.routerfoo.tls]
certResolver = "foo"
```
```yaml tab="YAML"
tcp:
routers:
routerfoo:
rule: "HostSNI(`snitest.com`)"
tls:
certResolver: foo
```
#### `domains`
See [`domains` for HTTP router](./index.md#domains) for more information.
```toml tab="TOML"
[tcp.routers]
[tcp.routers.routerbar]
rule = "HostSNI(`snitest.com`)"
[tcp.routers.routerbar.tls]
certResolver = "bar"
[[tcp.routers.routerbar.tls.domains]]
main = "snitest.com"
sans = "*.snitest.com"
```
```yaml tab="YAML"
tcp:
routers:
routerbar:
rule: "HostSNI(`snitest.com`)"
tls:
certResolver: "bar"
domains:
- main: "snitest.com"
sans: "*.snitest.com"
```

View file

@ -33,16 +33,13 @@ spec:
- --entrypoints.web.Address=:8000
- --entrypoints.websecure.Address=:4443
- --providers.kubernetescrd
- --acme
- --acme.acmelogging
- --acme.tlschallenge
- --acme.onhostrule
- --acme.email=foo@you.com
- --acme.entrypoint=websecure
- --acme.storage=acme.json
- --certificatesresolvers.default.acme.tlschallenge
- --certificatesresolvers.default.acme.email=foo@you.com
- --certificatesresolvers.default.acme.entrypoint=websecure
- --certificatesresolvers.default.acme.storage=acme.json
# Please note that this is the staging Let's Encrypt server.
# Once you get things working, you should remove that whole line altogether.
- --acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesresolvers.default.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
ports:
- name: web
containerPort: 8000

View file

@ -26,5 +26,5 @@ spec:
services:
- name: whoami
port: 80
# Please note the use of an empty TLS object to enable TLS with Let's Encrypt.
tls: {}
tls:
certResolver: default

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/containous/traefik/integration/try"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/provider/acme"
"github.com/containous/traefik/pkg/testhelpers"
"github.com/containous/traefik/pkg/types"
@ -26,17 +27,23 @@ type AcmeSuite struct {
fakeDNSServer *dns.Server
}
type subCases struct {
host string
expectedCommonName string
expectedAlgorithm x509.PublicKeyAlgorithm
}
type acmeTestCase struct {
template templateModel
traefikConfFilePath string
expectedCommonName string
expectedAlgorithm x509.PublicKeyAlgorithm
subCases []subCases
}
type templateModel struct {
Domains []types.Domain
PortHTTP string
PortHTTPS string
Acme acme.Configuration
Acme map[string]static.CertificateResolver
}
const (
@ -120,40 +127,48 @@ func (s *AcmeSuite) TearDownSuite(c *check.C) {
}
}
func (s *AcmeSuite) TestHTTP01DomainsAtStart(c *check.C) {
c.Skip("We need to fix DefaultCertificate at start")
func (s *AcmeSuite) TestHTTP01Domains(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
Domains: types.Domains{types.Domain{
Main: "traefik.acme.wtf",
Domains: []types.Domain{{
Main: "traefik.acme.wtf",
}},
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestHTTP01DomainsInSANAtStart(c *check.C) {
c.Skip("We need to fix DefaultCertificate at start")
func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: "acme.wtf",
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
Domains: types.Domains{types.Domain{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
Domains: []types.Domain{{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
}},
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
expectedCommonName: "acme.wtf",
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -162,14 +177,49 @@ func (s *AcmeSuite) TestHTTP01DomainsInSANAtStart(c *check.C) {
func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestMultipleResolver(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_multiple_resolvers.toml",
subCases: []subCases{
{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
},
{
host: "tchouk.acme.wtf",
expectedCommonName: "tchouk.acme.wtf",
expectedAlgorithm: x509.ECDSA,
},
},
template: templateModel{
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
"tchouk": {ACME: &acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
KeyType: "EC256",
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -178,15 +228,19 @@ func (s *AcmeSuite) TestHTTP01OnHostRule(c *check.C) {
func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.ECDSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
KeyType: "EC384",
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
KeyType: "EC384",
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.ECDSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -195,31 +249,39 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleECDSA(c *check.C) {
func (s *AcmeSuite) TestHTTP01OnHostRuleInvalidAlgo(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
KeyType: "INVALID",
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
KeyType: "INVALID",
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestHTTP01OnHostRuleStaticCertificatesWithWildcard(c *check.C) {
func (s *AcmeSuite) TestHTTP01OnHostRuleDefaultDynamicCertificatesWithWildcard(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tls.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: wildcardDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
expectedCommonName: wildcardDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -228,14 +290,38 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleStaticCertificatesWithWildcard(c *check.
func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tls_dynamic.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: wildcardDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestTLSALPN01OnHostRuleTCP(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tcp.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
}},
},
},
expectedCommonName: wildcardDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -244,72 +330,65 @@ func (s *AcmeSuite) TestHTTP01OnHostRuleDynamicCertificatesWithWildcard(c *check
func (s *AcmeSuite) TestTLSALPN01OnHostRule(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
OnHostRule: true,
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestTLSALPN01DomainsAtStart(c *check.C) {
c.Skip("We need to fix DefaultCertificate at start")
func (s *AcmeSuite) TestTLSALPN01Domains(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
Domains: types.Domains{types.Domain{
Main: "traefik.acme.wtf",
Domains: []types.Domain{{
Main: "traefik.acme.wtf",
}},
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
}},
},
},
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestTLSALPN01DomainsInSANAtStart(c *check.C) {
c.Skip("We need to fix DefaultCertificate at start")
func (s *AcmeSuite) TestTLSALPN01DomainsInSAN(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_base.toml",
traefikConfFilePath: "fixtures/acme/acme_domains.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: "acme.wtf",
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Acme: acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
Domains: types.Domains{types.Domain{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
Domains: []types.Domain{{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
}},
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
}},
},
},
expectedCommonName: "acme.wtf",
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
}
func (s *AcmeSuite) TestTLSALPN01DomainsWithProvidedWildcardDomainAtStart(c *check.C) {
c.Skip("We need to fix DefaultCertificate at start")
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_tls.toml",
template: templateModel{
Acme: acme.Configuration{
TLSChallenge: &acme.TLSChallenge{},
Domains: types.Domains{types.Domain{
Main: acmeDomain,
}},
},
},
expectedCommonName: wildcardDomain,
expectedAlgorithm: x509.RSA,
}
s.retrieveAcmeCertificate(c, testCase)
@ -318,10 +397,11 @@ func (s *AcmeSuite) TestTLSALPN01DomainsWithProvidedWildcardDomainAtStart(c *che
// Test Let's encrypt down
func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) {
file := s.adaptFile(c, "fixtures/acme/acme_base.toml", templateModel{
Acme: acme.Configuration{
CAServer: "http://wrongurl:4001/directory",
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
OnHostRule: true,
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
CAServer: "http://wrongurl:4001/directory",
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
})
defer os.Remove(file)
@ -347,8 +427,10 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
testCase.template.PortHTTPS = ":5001"
}
if len(testCase.template.Acme.CAServer) == 0 {
testCase.template.Acme.CAServer = s.getAcmeURL()
for _, value := range testCase.template.Acme {
if len(value.ACME.CAServer) == 0 {
value.ACME.CAServer = s.getAcmeURL()
}
}
file := s.adaptFile(c, testCase.traefikConfFilePath, testCase.template)
@ -365,57 +447,59 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
backend := startTestServer("9010", http.StatusOK)
defer backend.Close()
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// wait for traefik (generating acme account take some seconds)
err = try.Do(90*time.Second, func() error {
_, errGet := client.Get("https://127.0.0.1:5001")
return errGet
})
c.Assert(err, checker.IsNil)
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: acmeDomain,
for _, sub := range testCase.subCases {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
},
}
// wait for traefik (generating acme account take some seconds)
err = try.Do(60*time.Second, func() error {
_, errGet := client.Get("https://127.0.0.1:5001")
return errGet
})
c.Assert(err, checker.IsNil)
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: sub.host,
},
},
}
req := testhelpers.MustNewRequest(http.MethodGet, "https://127.0.0.1:5001/", nil)
req.Host = sub.host
req.Header.Set("Host", sub.host)
req.Header.Set("Accept", "*/*")
var resp *http.Response
// Retry to send a Request which uses the LE generated certificate
err = try.Do(60*time.Second, func() error {
resp, err = client.Do(req)
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
req.Close = true
if err != nil {
return err
}
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
if cn != sub.expectedCommonName {
return fmt.Errorf("domain %s found instead of %s", cn, sub.expectedCommonName)
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
// Check Domain into response certificate
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, sub.expectedCommonName)
c.Assert(resp.TLS.PeerCertificates[0].PublicKeyAlgorithm, checker.Equals, sub.expectedAlgorithm)
}
req := testhelpers.MustNewRequest(http.MethodGet, "https://127.0.0.1:5001/", nil)
req.Host = acmeDomain
req.Header.Set("Host", acmeDomain)
req.Header.Set("Accept", "*/*")
var resp *http.Response
// Retry to send a Request which uses the LE generated certificate
err = try.Do(60*time.Second, func() error {
resp, err = client.Do(req)
// /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\
req.Close = true
if err != nil {
return err
}
cn := resp.TLS.PeerCertificates[0].Subject.CommonName
if cn != testCase.expectedCommonName {
return fmt.Errorf("domain %s found instead of %s", cn, testCase.expectedCommonName)
}
return nil
})
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
// Check Domain into response certificate
c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Equals, testCase.expectedCommonName)
c.Assert(resp.TLS.PeerCertificates[0].PublicKeyAlgorithm, checker.Equals, testCase.expectedAlgorithm)
}

View file

@ -11,31 +11,24 @@
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
[acme]
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
# entryPoint = "https"
acmeLogging = true
onHostRule = {{ .Acme.OnHostRule }}
keyType = "{{ .Acme.KeyType }}"
caServer = "{{ .Acme.CAServer }}"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if .Acme.HTTPChallenge }}
[acme.httpChallenge]
entryPoint = "{{ .Acme.HTTPChallenge.EntryPoint }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if .Acme.TLSChallenge }}
[acme.tlsChallenge]
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{range .Acme.Domains}}
[[acme.domains]]
main = "{{ .Main }}"
sans = [{{range .SANs }}
"{{.}}",
{{end}}]
{{end}}
{{end}}
[api]
@ -55,3 +48,4 @@
rule = "Host(`traefik.acme.wtf`)"
service = "test"
[http.routers.test.tls]
certResolver = "default"

View file

@ -0,0 +1,58 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{end}}
[api]
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.services]
[http.services.test.loadBalancer]
[[http.services.test.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[http.routers]
[http.routers.test]
entryPoints = ["web-secure"]
rule = "PathPrefix(`/`)"
service = "test"
[http.routers.test.tls]
certResolver = "default"
{{range .Domains}}
[[http.routers.test.tls.domains]]
main = "{{ .Main }}"
sans = [{{range .SANs }}
"{{.}}",
{{end}}]
{{end}}

View file

@ -0,0 +1,58 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{end}}
[api]
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.services]
[http.services.test.loadBalancer]
[[http.services.test.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[http.routers]
[http.routers.test]
entryPoints = ["web-secure"]
rule = "Host(`traefik.acme.wtf`)"
service = "test"
[http.routers.test.tls]
certResolver = "default"
[http.routers.tchouk]
entryPoints = ["web-secure"]
rule = "Host(`tchouk.acme.wtf`)"
service = "test"
[http.routers.tchouk.tls]
certResolver = "tchouk"

View file

@ -0,0 +1,51 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{end}}
[api]
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[tcp.services]
[tcp.services.test.loadBalancer]
[[tcp.services.test.loadBalancer.servers]]
address = "127.0.0.1:9010"
[tcp.routers]
[tcp.routers.test]
entryPoints = ["web-secure"]
rule = "HostSNI(`traefik.acme.wtf`)"
service = "test"
[tcp.routers.test.tls]
certResolver = "default"

View file

@ -11,31 +11,24 @@
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
[acme]
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
# entryPoint = "https"
acmeLogging = true
onHostRule = {{ .Acme.OnHostRule }}
keyType = "{{ .Acme.KeyType }}"
caServer = "{{ .Acme.CAServer }}"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if .Acme.HTTPChallenge }}
[acme.httpChallenge]
entryPoint = "{{ .Acme.HTTPChallenge.EntryPoint }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if .Acme.TLSChallenge }}
[acme.tlsChallenge]
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{range .Acme.Domains}}
[[acme.domains]]
main = "{{ .Main }}"
sans = [{{range .SANs }}
"{{.}}",
{{end}}]
{{end}}
{{end}}
[api]

View file

@ -7,32 +7,29 @@
[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
address = "{{ .PortHTTP }}"
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
address = "{{ .PortHTTPS }}"
[acme]
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
# entryPoint = "https"
acmeLogging = true
onHostRule = {{ .Acme.OnHostRule }}
keyType = "{{ .Acme.KeyType }}"
caServer = "{{ .Acme.CAServer }}"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if .Acme.HTTPChallenge }}
[acme.httpChallenge]
entryPoint = "{{ .Acme.HTTPChallenge.EntryPoint }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{range .Acme.Domains}}
[[acme.domains]]
main = "{{ .Main }}"
sans = [{{range .SANs }}
"{{.}}",
{{end}}]
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{end}}
[api]
[providers]

View file

@ -8,42 +8,29 @@
[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
[entryPoints.web-secure]
address = "{{ .PortHTTPS }}"
[entryPoints.traefik]
address = ":9000"
# FIXME
# [entryPoints.traefik.tls]
# [entryPoints.traefik.tls.defaultCertificate]
# certFile = "fixtures/acme/ssl/wildcard.crt"
# keyFile = "fixtures/acme/ssl/wildcard.key"
[acme]
{{range $name, $resolvers := .Acme }}
[certificatesResolvers.{{ $name }}.acme]
email = "test@traefik.io"
storage = "/tmp/acme.json"
# entryPoint = "https"
acmeLogging = true
onHostRule = {{ .Acme.OnHostRule }}
keyType = "{{ .Acme.KeyType }}"
caServer = "{{ .Acme.CAServer }}"
keyType = "{{ $resolvers.ACME.KeyType }}"
caServer = "{{ $resolvers.ACME.CAServer }}"
{{if .Acme.HTTPChallenge }}
[acme.httpChallenge]
entryPoint = "{{ .Acme.HTTPChallenge.EntryPoint }}"
{{if $resolvers.ACME.HTTPChallenge }}
[certificatesResolvers.{{ $name }}.acme.httpChallenge]
entryPoint = "{{ $resolvers.ACME.HTTPChallenge.EntryPoint }}"
{{end}}
{{if .Acme.TLSChallenge }}
[acme.tlsChallenge]
{{if $resolvers.ACME.TLSChallenge }}
[certificatesResolvers.{{ $name }}.acme.tlsChallenge]
{{end}}
{{range .Acme.Domains}}
[[acme.domains]]
main = "{{ .Main }}"
sans = [{{range .SANs }}
"{{.}}",
{{end}}]
{{end}}
{{end}}
[api]

View file

@ -265,7 +265,7 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) {
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
// with unknown tls option
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(fmt.Sprintf("found different TLS options for routers on the same host %v, so using the default TLS option instead", tr4.TLSClientConfig.ServerName)))
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(fmt.Sprintf("found different TLS options for routers on the same host %v, so using the default TLS options instead", tr4.TLSClientConfig.ServerName)))
c.Assert(err, checker.IsNil)
}

View file

@ -537,7 +537,7 @@ func (s *SimpleSuite) TestRouterConfigErrors(c *check.C) {
defer cmd.Process.Kill()
// All errors
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS option instead"]`))
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS options instead"]`))
c.Assert(err, checker.IsNil)
// router4 is enabled, but in warning state because its tls options conf was messed up

View file

@ -89,23 +89,18 @@ func TestDo_globalConfiguration(t *testing.T) {
},
},
}
config.ACME = &acme.Configuration{
Email: "acme Email",
ACMELogging: true,
CAServer: "CAServer",
Storage: "Storage",
EntryPoint: "EntryPoint",
KeyType: "MyKeyType",
OnHostRule: true,
DNSChallenge: &acmeprovider.DNSChallenge{Provider: "DNSProvider"},
HTTPChallenge: &acmeprovider.HTTPChallenge{
EntryPoint: "MyEntryPoint",
},
TLSChallenge: &acmeprovider.TLSChallenge{},
Domains: []types.Domain{
{
Main: "Domains Main",
SANs: []string{"Domains acme SANs 1", "Domains acme SANs 2", "Domains acme SANs 3"},
config.CertificatesResolvers = map[string]static.CertificateResolver{
"default": {
ACME: &acme.Configuration{
Email: "acme Email",
CAServer: "CAServer",
Storage: "Storage",
KeyType: "MyKeyType",
DNSChallenge: &acmeprovider.DNSChallenge{Provider: "DNSProvider"},
HTTPChallenge: &acmeprovider.HTTPChallenge{
EntryPoint: "MyEntryPoint",
},
TLSChallenge: &acmeprovider.TLSChallenge{},
},
},
}
@ -126,9 +121,6 @@ func TestDo_globalConfiguration(t *testing.T) {
config.API = &static.API{
EntryPoint: "traefik",
Dashboard: true,
Statistics: &types.Statistics{
RecentErrors: 111,
},
DashboardAssets: &assetfs.AssetFS{
Asset: func(path string) ([]byte, error) {
return nil, nil

View file

@ -212,7 +212,6 @@
storage = "foobar"
entryPoint = "foobar"
keyType = "foobar"
onHostRule = true
[acme.dnsChallenge]
provider = "foobar"
delayBeforeCheck = 42

View file

@ -1,6 +1,10 @@
package dynamic
import "reflect"
import (
"reflect"
"github.com/containous/traefik/pkg/types"
)
// +k8s:deepcopy-gen=true
@ -34,7 +38,9 @@ type Router struct {
// RouterTLSConfig holds the TLS configuration for a router
type RouterTLSConfig struct {
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"`
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"`
CertResolver string `json:"certResolver,omitempty" toml:"certResolver,omitempty" yaml:"certResolver,omitempty"`
Domains []types.Domain `json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty"`
}
// +k8s:deepcopy-gen=true

View file

@ -1,6 +1,10 @@
package dynamic
import "reflect"
import (
"reflect"
"github.com/containous/traefik/pkg/types"
)
// +k8s:deepcopy-gen=true
@ -31,8 +35,10 @@ type TCPRouter struct {
// RouterTCPTLSConfig holds the TLS configuration for a router
type RouterTCPTLSConfig struct {
Passthrough bool `json:"passthrough" toml:"passthrough" yaml:"passthrough"`
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"`
Passthrough bool `json:"passthrough" toml:"passthrough" yaml:"passthrough"`
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty"`
CertResolver string `json:"certResolver,omitempty" toml:"certResolver,omitempty" yaml:"certResolver,omitempty"`
Domains []types.Domain `json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty"`
}
// +k8s:deepcopy-gen=true

View file

@ -30,6 +30,7 @@ package dynamic
import (
tls "github.com/containous/traefik/pkg/tls"
types "github.com/containous/traefik/pkg/types"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -876,7 +877,7 @@ func (in *Router) DeepCopyInto(out *Router) {
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(RouterTLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}
@ -894,6 +895,13 @@ func (in *Router) DeepCopy() *Router {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
*out = *in
if in.Domains != nil {
in, out := &in.Domains, &out.Domains
*out = make([]types.Domain, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@ -910,6 +918,13 @@ func (in *RouterTCPTLSConfig) DeepCopy() *RouterTCPTLSConfig {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouterTLSConfig) DeepCopyInto(out *RouterTLSConfig) {
*out = *in
if in.Domains != nil {
in, out := &in.Domains, &out.Domains
*out = make([]types.Domain, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@ -1096,7 +1111,7 @@ func (in *TCPRouter) DeepCopyInto(out *TCPRouter) {
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(RouterTCPTLSConfig)
**out = **in
(*in).DeepCopyInto(*out)
}
return
}

View file

@ -44,7 +44,7 @@ func Test_getRootFieldNames(t *testing.T) {
func Test_decodeFileToNode_compare(t *testing.T) {
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "ACME")
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
if err != nil {
t.Fatal(err)
}
@ -59,7 +59,7 @@ func Test_decodeFileToNode_compare(t *testing.T) {
func Test_decodeFileToNode_Toml(t *testing.T) {
node, err := decodeFileToNode("./fixtures/sample.toml",
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "ACME")
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
if err != nil {
t.Fatal(err)
}
@ -85,42 +85,35 @@ func Test_decodeFileToNode_Toml(t *testing.T) {
{Name: "retryAttempts", Value: "true"},
{Name: "statusCodes", Value: "foobar,foobar"}}},
{Name: "format", Value: "foobar"}}},
{Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "domains", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "main", Value: "foobar"},
{Name: "sans", Value: "foobar,foobar"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "main", Value: "foobar"},
{Name: "sans", Value: "foobar,foobar"},
}},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"}}},
{Name: "keyType", Value: "foobar"},
{Name: "onHostRule", Value: "true"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
{Name: "api", Children: []*parser.Node{
{Name: "dashboard", Value: "true"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
{Name: "statistics", Children: []*parser.Node{
{Name: "recentErrors", Value: "42"}}}}},
{Name: "certificatesResolvers", Children: []*parser.Node{
{Name: "default", Children: []*parser.Node{
{Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"}}},
{Name: "keyType", Value: "foobar"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
}},
}},
{Name: "entryPoints", Children: []*parser.Node{
{Name: "EntryPoint0", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
@ -327,42 +320,35 @@ func Test_decodeFileToNode_Yaml(t *testing.T) {
{Name: "retryAttempts", Value: "true"},
{Name: "statusCodes", Value: "foobar,foobar"}}},
{Name: "format", Value: "foobar"}}},
{Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "domains", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "main", Value: "foobar"},
{Name: "sans", Value: "foobar,foobar"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "main", Value: "foobar"},
{Name: "sans", Value: "foobar,foobar"},
}},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"}}},
{Name: "keyType", Value: "foobar"},
{Name: "onHostRule", Value: "true"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
{Name: "api", Children: []*parser.Node{
{Name: "dashboard", Value: "true"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
{Name: "statistics", Children: []*parser.Node{
{Name: "recentErrors", Value: "42"}}}}},
{Name: "certificatesResolvers", Children: []*parser.Node{
{Name: "default", Children: []*parser.Node{
{Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"}}},
{Name: "keyType", Value: "foobar"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
}},
}},
{Name: "entryPoints", Children: []*parser.Node{
{Name: "EntryPoint0", Children: []*parser.Node{
{Name: "address", Value: "foobar"},

View file

@ -205,30 +205,21 @@
resolvConfig = "foobar"
resolvDepth = 42
[acme]
[certificatesResolvers.default.acme]
email = "foobar"
acmeLogging = true
caServer = "foobar"
storage = "foobar"
entryPoint = "foobar"
keyType = "foobar"
onHostRule = true
[acme.dnsChallenge]
[certificatesResolvers.default.acme.dnsChallenge]
provider = "foobar"
delayBeforeCheck = 42
resolvers = ["foobar", "foobar"]
disablePropagationCheck = true
[acme.httpChallenge]
[certificatesResolvers.default.acme.httpChallenge]
entryPoint = "foobar"
[acme.tlsChallenge]
[[acme.domains]]
main = "foobar"
sans = ["foobar", "foobar"]
[[acme.domains]]
main = "foobar"
sans = ["foobar", "foobar"]
[certificatesResolvers.default.acme.tlsChallenge]
## Dynamic configuration

View file

@ -214,30 +214,23 @@ hostResolver:
cnameFlattening: true
resolvConfig: foobar
resolvDepth: 42
acme:
email: foobar
acmeLogging: true
caServer: foobar
storage: foobar
entryPoint: foobar
keyType: foobar
onHostRule: true
dnsChallenge:
provider: foobar
delayBeforeCheck: 42
resolvers:
- foobar
- foobar
disablePropagationCheck: true
httpChallenge:
entryPoint: foobar
tlsChallenge: {}
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
certificatesResolvers:
default:
acme:
email: foobar
acmeLogging: true
caServer: foobar
storage: foobar
entryPoint: foobar
keyType: foobar
dnsChallenge:
provider: foobar
delayBeforeCheck: 42
resolvers:
- foobar
- foobar
disablePropagationCheck: true
httpChallenge:
entryPoint: foobar
tlsChallenge: {}

View file

@ -1,7 +1,8 @@
package static
import (
"errors"
"fmt"
stdlog "log"
"strings"
"time"
@ -23,7 +24,8 @@ import (
"github.com/containous/traefik/pkg/tracing/zipkin"
"github.com/containous/traefik/pkg/types"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/go-acme/lego/challenge/dns01"
legolog "github.com/go-acme/lego/log"
"github.com/sirupsen/logrus"
)
const (
@ -59,6 +61,11 @@ type Configuration struct {
HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." json:"hostResolver,omitempty" toml:"hostResolver,omitempty" yaml:"hostResolver,omitempty" label:"allowEmpty" export:"true"`
CertificatesResolvers map[string]CertificateResolver `description:"Certificates resolvers configuration." json:"certificatesResolvers,omitempty" toml:"certificatesResolvers,omitempty" yaml:"certificatesResolvers,omitempty" export:"true"`
}
// CertificateResolver contains the configuration for the different types of certificates resolver.
type CertificateResolver struct {
ACME *acmeprovider.Configuration `description:"Enable ACME (Let's Encrypt): automatic SSL." json:"acme,omitempty" toml:"acme,omitempty" yaml:"acme,omitempty" export:"true"`
}
@ -194,64 +201,35 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.initACMEProvider()
}
// FIXME handle on new configuration ACME struct
func (c *Configuration) initACMEProvider() {
if c.ACME != nil {
c.ACME.CAServer = getSafeACMECAServer(c.ACME.CAServer)
if c.ACME.DNSChallenge != nil && c.ACME.HTTPChallenge != nil {
log.Warn("Unable to use DNS challenge and HTTP challenge at the same time. Fallback to DNS challenge.")
c.ACME.HTTPChallenge = nil
}
if c.ACME.DNSChallenge != nil && c.ACME.TLSChallenge != nil {
log.Warn("Unable to use DNS challenge and TLS challenge at the same time. Fallback to DNS challenge.")
c.ACME.TLSChallenge = nil
}
if c.ACME.HTTPChallenge != nil && c.ACME.TLSChallenge != nil {
log.Warn("Unable to use HTTP challenge and TLS challenge at the same time. Fallback to TLS challenge.")
c.ACME.HTTPChallenge = nil
for _, resolver := range c.CertificatesResolvers {
if resolver.ACME != nil {
resolver.ACME.CAServer = getSafeACMECAServer(resolver.ACME.CAServer)
}
}
}
// InitACMEProvider create an acme provider from the ACME part of globalConfiguration
func (c *Configuration) InitACMEProvider() (*acmeprovider.Provider, error) {
if c.ACME != nil {
if len(c.ACME.Storage) == 0 {
return nil, errors.New("unable to initialize ACME provider with no storage location for the certificates")
}
return &acmeprovider.Provider{
Configuration: c.ACME,
}, nil
}
return nil, nil
legolog.Logger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "legolog: ", 0)
}
// ValidateConfiguration validate that configuration is coherent
func (c *Configuration) ValidateConfiguration() {
if c.ACME != nil {
for _, domain := range c.ACME.Domains {
if domain.Main != dns01.UnFqdn(domain.Main) {
log.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main)
}
for _, san := range domain.SANs {
if san != dns01.UnFqdn(san) {
log.Warnf("FQDN detected, please remove the trailing dot: %s", san)
}
}
func (c *Configuration) ValidateConfiguration() error {
var acmeEmail string
for name, resolver := range c.CertificatesResolvers {
if resolver.ACME == nil {
continue
}
if len(resolver.ACME.Storage) == 0 {
return fmt.Errorf("unable to initialize certificates resolver %q with no storage location for the certificates", name)
}
if acmeEmail != "" && resolver.ACME.Email != acmeEmail {
return fmt.Errorf("unable to initialize certificates resolver %q, all the acme resolvers must use the same email", name)
}
acmeEmail = resolver.ACME.Email
}
// FIXME Validate store config?
// if c.ACME != nil {
// if _, ok := c.EntryPoints[c.ACME.EntryPoint]; !ok {
// log.Fatalf("Unknown entrypoint %q for ACME configuration", c.ACME.EntryPoint)
// }
// else if c.EntryPoints[c.ACME.EntryPoint].TLS == nil {
// log.Fatalf("Entrypoint %q has no TLS configuration for ACME configuration", c.ACME.EntryPoint)
// }
// }
return nil
}
func getSafeACMECAServer(caServerSrc string) string {

View file

@ -17,7 +17,7 @@ import (
var _ challenge.ProviderTimeout = (*challengeHTTP)(nil)
type challengeHTTP struct {
Store Store
Store ChallengeStore
}
// Present presents a challenge to obtain new ACME certificate.
@ -52,7 +52,7 @@ func (p *Provider) Append(router *mux.Router) {
domain = req.Host
}
tokenValue := getTokenValue(ctx, token, domain, p.Store)
tokenValue := getTokenValue(ctx, token, domain, p.ChallengeStore)
if len(tokenValue) > 0 {
rw.WriteHeader(http.StatusOK)
_, err = rw.Write(tokenValue)
@ -66,7 +66,7 @@ func (p *Provider) Append(router *mux.Router) {
}))
}
func getTokenValue(ctx context.Context, token, domain string, store Store) []byte {
func getTokenValue(ctx context.Context, token, domain string, store ChallengeStore) []byte {
logger := log.FromContext(ctx)
logger.Debugf("Retrieving the ACME challenge for token %v...", token)

View file

@ -12,7 +12,7 @@ import (
var _ challenge.Provider = (*challengeTLSALPN)(nil)
type challengeTLSALPN struct {
Store Store
Store ChallengeStore
}
func (c *challengeTLSALPN) Present(domain, token, keyAuth string) error {
@ -37,7 +37,7 @@ func (c *challengeTLSALPN) CleanUp(domain, token, keyAuth string) error {
// GetTLSALPNCertificate Get the temp certificate for ACME TLS-ALPN-O1 challenge.
func (p *Provider) GetTLSALPNCertificate(domain string) (*tls.Certificate, error) {
cert, err := p.Store.GetTLSChallenge(domain)
cert, err := p.ChallengeStore.GetTLSChallenge(domain)
if err != nil {
return nil, err
}

View file

@ -5,7 +5,6 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"sync"
"github.com/containous/traefik/pkg/log"
@ -16,25 +15,34 @@ var _ Store = (*LocalStore)(nil)
// LocalStore Stores implementation for local file
type LocalStore struct {
saveDataChan chan map[string]*StoredData
filename string
storedData *StoredData
SaveDataChan chan *StoredData `json:"-"`
lock sync.RWMutex
lock sync.RWMutex
storedData map[string]*StoredData
}
// NewLocalStore initializes a new LocalStore with a file name
func NewLocalStore(filename string) *LocalStore {
store := &LocalStore{filename: filename, SaveDataChan: make(chan *StoredData)}
store := &LocalStore{filename: filename, saveDataChan: make(chan map[string]*StoredData)}
store.listenSaveAction()
return store
}
func (s *LocalStore) get() (*StoredData, error) {
func (s *LocalStore) save(resolverName string, storedData *StoredData) {
s.lock.Lock()
defer s.lock.Unlock()
s.storedData[resolverName] = storedData
s.saveDataChan <- s.storedData
}
func (s *LocalStore) get(resolverName string) (*StoredData, error) {
s.lock.Lock()
defer s.lock.Unlock()
if s.storedData == nil {
s.storedData = &StoredData{
HTTPChallenges: make(map[string]map[string][]byte),
TLSChallenges: make(map[string]*Certificate),
}
s.storedData = map[string]*StoredData{}
hasData, err := CheckFile(s.filename)
if err != nil {
@ -56,49 +64,40 @@ func (s *LocalStore) get() (*StoredData, error) {
}
if len(file) > 0 {
if err := json.Unmarshal(file, s.storedData); err != nil {
if err := json.Unmarshal(file, &s.storedData); err != nil {
return nil, err
}
}
// Check if ACME Account is in ACME V1 format
if s.storedData.Account != nil && s.storedData.Account.Registration != nil {
isOldRegistration, err := regexp.MatchString(RegistrationURLPathV1Regexp, s.storedData.Account.Registration.URI)
if err != nil {
return nil, err
}
if isOldRegistration {
logger.Debug("Reseting ACME account.")
s.storedData.Account = nil
s.SaveDataChan <- s.storedData
}
}
// Delete all certificates with no value
var certificates []*Certificate
for _, certificate := range s.storedData.Certificates {
if len(certificate.Certificate) == 0 || len(certificate.Key) == 0 {
logger.Debugf("Deleting empty certificate %v for %v", certificate, certificate.Domain.ToStrArray())
continue
var certificates []*CertAndStore
for _, storedData := range s.storedData {
for _, certificate := range storedData.Certificates {
if len(certificate.Certificate.Certificate) == 0 || len(certificate.Key) == 0 {
logger.Debugf("Deleting empty certificate %v for %v", certificate, certificate.Domain.ToStrArray())
continue
}
certificates = append(certificates, certificate)
}
if len(certificates) < len(storedData.Certificates) {
storedData.Certificates = certificates
s.saveDataChan <- s.storedData
}
certificates = append(certificates, certificate)
}
if len(certificates) < len(s.storedData.Certificates) {
s.storedData.Certificates = certificates
s.SaveDataChan <- s.storedData
}
}
}
return s.storedData, nil
if s.storedData[resolverName] == nil {
s.storedData[resolverName] = &StoredData{}
}
return s.storedData[resolverName], nil
}
// listenSaveAction listens to a chan to store ACME data in json format into LocalStore.filename
func (s *LocalStore) listenSaveAction() {
safe.Go(func() {
logger := log.WithoutContext().WithField(log.ProviderName, "acme")
for object := range s.SaveDataChan {
for object := range s.saveDataChan {
data, err := json.MarshalIndent(object, "", " ")
if err != nil {
logger.Error(err)
@ -113,8 +112,8 @@ func (s *LocalStore) listenSaveAction() {
}
// GetAccount returns ACME Account
func (s *LocalStore) GetAccount() (*Account, error) {
storedData, err := s.get()
func (s *LocalStore) GetAccount(resolverName string) (*Account, error) {
storedData, err := s.get(resolverName)
if err != nil {
return nil, err
}
@ -123,21 +122,21 @@ func (s *LocalStore) GetAccount() (*Account, error) {
}
// SaveAccount stores ACME Account
func (s *LocalStore) SaveAccount(account *Account) error {
storedData, err := s.get()
func (s *LocalStore) SaveAccount(resolverName string, account *Account) error {
storedData, err := s.get(resolverName)
if err != nil {
return err
}
storedData.Account = account
s.SaveDataChan <- storedData
s.save(resolverName, storedData)
return nil
}
// GetCertificates returns ACME Certificates list
func (s *LocalStore) GetCertificates() ([]*Certificate, error) {
storedData, err := s.get()
func (s *LocalStore) GetCertificates(resolverName string) ([]*CertAndStore, error) {
storedData, err := s.get(resolverName)
if err != nil {
return nil, err
}
@ -146,20 +145,37 @@ func (s *LocalStore) GetCertificates() ([]*Certificate, error) {
}
// SaveCertificates stores ACME Certificates list
func (s *LocalStore) SaveCertificates(certificates []*Certificate) error {
storedData, err := s.get()
func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertAndStore) error {
storedData, err := s.get(resolverName)
if err != nil {
return err
}
storedData.Certificates = certificates
s.SaveDataChan <- storedData
s.save(resolverName, storedData)
return nil
}
// LocalChallengeStore is an implementation of the ChallengeStore in memory.
type LocalChallengeStore struct {
storedData *StoredChallengeData
lock sync.RWMutex
}
// NewLocalChallengeStore initializes a new LocalChallengeStore.
func NewLocalChallengeStore() *LocalChallengeStore {
return &LocalChallengeStore{
storedData: &StoredChallengeData{
HTTPChallenges: make(map[string]map[string][]byte),
TLSChallenges: make(map[string]*Certificate),
},
}
}
// GetHTTPChallengeToken Get the http challenge token from the store
func (s *LocalStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
func (s *LocalChallengeStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
s.lock.RLock()
defer s.lock.RUnlock()
@ -179,7 +195,7 @@ func (s *LocalStore) GetHTTPChallengeToken(token, domain string) ([]byte, error)
}
// SetHTTPChallengeToken Set the http challenge token in the store
func (s *LocalStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
func (s *LocalChallengeStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
s.lock.Lock()
defer s.lock.Unlock()
@ -196,7 +212,7 @@ func (s *LocalStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte)
}
// RemoveHTTPChallengeToken Remove the http challenge token in the store
func (s *LocalStore) RemoveHTTPChallengeToken(token, domain string) error {
func (s *LocalChallengeStore) RemoveHTTPChallengeToken(token, domain string) error {
s.lock.Lock()
defer s.lock.Unlock()
@ -214,7 +230,7 @@ func (s *LocalStore) RemoveHTTPChallengeToken(token, domain string) error {
}
// AddTLSChallenge Add a certificate to the ACME TLS-ALPN-01 certificates storage
func (s *LocalStore) AddTLSChallenge(domain string, cert *Certificate) error {
func (s *LocalChallengeStore) AddTLSChallenge(domain string, cert *Certificate) error {
s.lock.Lock()
defer s.lock.Unlock()
@ -227,7 +243,7 @@ func (s *LocalStore) AddTLSChallenge(domain string, cert *Certificate) error {
}
// GetTLSChallenge Get a certificate from the ACME TLS-ALPN-01 certificates storage
func (s *LocalStore) GetTLSChallenge(domain string) (*Certificate, error) {
func (s *LocalChallengeStore) GetTLSChallenge(domain string) (*Certificate, error) {
s.lock.Lock()
defer s.lock.Unlock()
@ -239,7 +255,7 @@ func (s *LocalStore) GetTLSChallenge(domain string) (*Certificate, error) {
}
// RemoveTLSChallenge Remove a certificate from the ACME TLS-ALPN-01 certificates storage
func (s *LocalStore) RemoveTLSChallenge(domain string) error {
func (s *LocalChallengeStore) RemoveTLSChallenge(domain string) error {
s.lock.Lock()
defer s.lock.Unlock()

View file

@ -6,8 +6,6 @@ import (
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
fmtlog "log"
"net/url"
"reflect"
"strings"
@ -25,10 +23,8 @@ import (
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/lego"
legolog "github.com/go-acme/lego/log"
"github.com/go-acme/lego/providers/dns"
"github.com/go-acme/lego/registration"
"github.com/sirupsen/logrus"
)
var (
@ -39,16 +35,12 @@ var (
// Configuration holds ACME configuration provided by users
type Configuration struct {
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
ACMELogging bool `description:"Enable debug logging of ACME actions." json:"acmeLogging,omitempty" toml:"acmeLogging,omitempty" yaml:"acmeLogging,omitempty"`
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
EntryPoint string `description:"EntryPoint to use." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
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"`
OnHostRule bool `description:"Enable certificate generation on router Host rules." json:"onHostRule,omitempty" toml:"onHostRule,omitempty" yaml:"onHostRule,omitempty"`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"`
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"`
Domains []types.Domain `description:"The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge." json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty"`
}
// SetDefaults sets the default values.
@ -58,6 +50,12 @@ func (a *Configuration) SetDefaults() {
a.KeyType = "RSA4096"
}
// CertAndStore allows mapping a TLS certificate to a TLS store.
type CertAndStore struct {
Certificate
Store string
}
// Certificate is a struct which contains all data needed from an ACME certificate
type Certificate struct {
Domain types.Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
@ -84,11 +82,13 @@ type TLSChallenge struct{}
// Provider holds configurations of the provider.
type Provider struct {
*Configuration
ResolverName string
Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"`
certificates []*Certificate
ChallengeStore ChallengeStore
certificates []*CertAndStore
account *Account
client *lego.Client
certsChan chan *Certificate
certsChan chan *CertAndStore
configurationChan chan<- dynamic.Message
tlsManager *traefiktls.Manager
clientMutex sync.Mutex
@ -113,41 +113,20 @@ func (p *Provider) ListenConfiguration(config dynamic.Configuration) {
p.configFromListenerChan <- config
}
// ListenRequest resolves new certificates for a domain from an incoming request and return a valid Certificate to serve (onDemand option)
func (p *Provider) ListenRequest(domain string) (*tls.Certificate, error) {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
acmeCert, err := p.resolveCertificate(ctx, types.Domain{Main: domain}, false)
if acmeCert == nil || err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
return &cert, err
}
// Init for compatibility reason the BaseProvider implements an empty Init
func (p *Provider) Init() error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
logger := log.FromContext(ctx)
if p.ACMELogging {
legolog.Logger = fmtlog.New(logger.WriterLevel(logrus.InfoLevel), "legolog: ", 0)
} else {
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
}
if len(p.Configuration.Storage) == 0 {
return errors.New("unable to initialize ACME provider with no storage location for the certificates")
}
p.Store = NewLocalStore(p.Configuration.Storage)
var err error
p.account, err = p.Store.GetAccount()
p.account, err = p.Store.GetAccount(p.ResolverName)
if err != nil {
return fmt.Errorf("unable to get ACME account : %v", err)
return fmt.Errorf("unable to get ACME account: %v", err)
}
// Reset Account if caServer changed, thus registration URI can be updated
@ -156,7 +135,7 @@ func (p *Provider) Init() error {
p.account = nil
}
p.certificates, err = p.Store.GetCertificates()
p.certificates, err = p.Store.GetCertificates(p.ResolverName)
if err != nil {
return fmt.Errorf("unable to get ACME certificates : %v", err)
}
@ -188,7 +167,7 @@ func isAccountMatchingCaServer(ctx context.Context, accountURI string, serverURI
// Provide allows the file provider to provide configurations to traefik
// using the given Configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme."+p.ResolverName))
p.pool = pool
@ -198,17 +177,6 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
p.configurationChan = configurationChan
p.refreshCertificates()
p.deleteUnnecessaryDomains(ctx)
for i := 0; i < len(p.Domains); i++ {
domain := p.Domains[i]
safe.Go(func() {
if _, err := p.resolveCertificate(ctx, domain, true); err != nil {
log.WithoutContext().WithField(log.ProviderName, "acme").
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
}
})
}
p.renewCertificates(ctx)
ticker := time.NewTicker(24 * time.Hour)
@ -275,13 +243,18 @@ func (p *Provider) getClient() (*lego.Client, error) {
// Save the account once before all the certificates generation/storing
// No certificate can be generated if account is not initialized
err = p.Store.SaveAccount(account)
err = p.Store.SaveAccount(p.ResolverName, account)
if err != nil {
return nil, err
}
switch {
case p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0:
if (p.DNSChallenge == nil || len(p.DNSChallenge.Provider) == 0) &&
(p.HTTPChallenge == nil || len(p.HTTPChallenge.EntryPoint) == 0) &&
p.TLSChallenge == nil {
return nil, errors.New("ACME challenge not specified, please select TLS or HTTP or DNS Challenge")
}
if p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0 {
logger.Debugf("Using DNS Challenge provider: %s", p.DNSChallenge.Provider)
var provider challenge.Provider
@ -304,25 +277,24 @@ func (p *Provider) getClient() (*lego.Client, error) {
if err != nil {
return nil, err
}
}
case p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0:
if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
logger.Debug("Using HTTP Challenge provider.")
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.Store})
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.ChallengeStore})
if err != nil {
return nil, err
}
}
case p.TLSChallenge != nil:
if p.TLSChallenge != nil {
logger.Debug("Using TLS Challenge provider.")
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.Store})
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.ChallengeStore})
if err != nil {
return nil, err
}
default:
return nil, errors.New("ACME challenge not specified, please select TLS or HTTP or DNS Challenge")
}
p.client = client
@ -346,7 +318,7 @@ func (p *Provider) initAccount(ctx context.Context) (*Account, error) {
return p.account, nil
}
func (p *Provider) resolveDomains(ctx context.Context, domains []string) {
func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStore string) {
if len(domains) == 0 {
log.FromContext(ctx).Debug("No domain parsed in provider ACME")
return
@ -362,7 +334,7 @@ func (p *Provider) resolveDomains(ctx context.Context, domains []string) {
}
safe.Go(func() {
if _, err := p.resolveCertificate(ctx, domain, false); err != nil {
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
log.FromContext(ctx).Errorf("Unable to obtain ACME certificate for domains %q: %v", strings.Join(domains, ","), err)
}
})
@ -376,32 +348,72 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
case config := <-p.configFromListenerChan:
if config.TCP != nil {
for routerName, route := range config.TCP.Routers {
if route.TLS == nil {
if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
continue
}
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
domains, err := rules.ParseHostSNI(route.Rule)
if err != nil {
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
continue
tlsStore := "default"
if len(route.TLS.Domains) > 0 {
for _, domain := range route.TLS.Domains {
if domain.Main != dns01.UnFqdn(domain.Main) {
log.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main)
}
for _, san := range domain.SANs {
if san != dns01.UnFqdn(san) {
log.Warnf("FQDN detected, please remove the trailing dot: %s", san)
}
}
}
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
for i := 0; i < len(domains); i++ {
domain := domains[i]
safe.Go(func() {
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
log.WithoutContext().WithField(log.ProviderName, "acme."+p.ResolverName).
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
}
})
}
} else {
domains, err := rules.ParseHostSNI(route.Rule)
if err != nil {
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
continue
}
p.resolveDomains(ctxRouter, domains, tlsStore)
}
p.resolveDomains(ctxRouter, domains)
}
}
for routerName, route := range config.HTTP.Routers {
if route.TLS == nil {
if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
continue
}
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
domains, err := rules.ParseDomains(route.Rule)
if err != nil {
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
continue
tlsStore := "default"
if len(route.TLS.Domains) > 0 {
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
for i := 0; i < len(domains); i++ {
domain := domains[i]
safe.Go(func() {
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
log.WithoutContext().WithField(log.ProviderName, "acme."+p.ResolverName).
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
}
})
}
} else {
domains, err := rules.ParseDomains(route.Rule)
if err != nil {
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
continue
}
p.resolveDomains(ctxRouter, domains, tlsStore)
}
p.resolveDomains(ctxRouter, domains)
}
case <-stop:
return
@ -410,14 +422,14 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
})
}
func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, domainFromConfigurationFile bool) (*certificate.Resource, error) {
domains, err := p.getValidDomains(ctx, domain, domainFromConfigurationFile)
func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, tlsStore string) (*certificate.Resource, error) {
domains, err := p.getValidDomains(ctx, domain)
if err != nil {
return nil, err
}
// Check provided certificates
uncheckedDomains := p.getUncheckedDomains(ctx, domains, !domainFromConfigurationFile)
uncheckedDomains := p.getUncheckedDomains(ctx, domains, tlsStore)
if len(uncheckedDomains) == 0 {
return nil, nil
}
@ -457,7 +469,7 @@ func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain,
} else {
domain = types.Domain{Main: uncheckedDomains[0]}
}
p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey)
p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey, tlsStore)
return cert, nil
}
@ -480,22 +492,22 @@ func (p *Provider) addResolvingDomains(resolvingDomains []string) {
}
}
func (p *Provider) addCertificateForDomain(domain types.Domain, certificate []byte, key []byte) {
p.certsChan <- &Certificate{Certificate: certificate, Key: key, Domain: domain}
func (p *Provider) addCertificateForDomain(domain types.Domain, certificate []byte, key []byte, tlsStore string) {
p.certsChan <- &CertAndStore{Certificate: Certificate{Certificate: certificate, Key: key, Domain: domain}, Store: tlsStore}
}
// deleteUnnecessaryDomains deletes from the configuration :
// - Duplicated domains
// - Domains which are checked by wildcard domain
func (p *Provider) deleteUnnecessaryDomains(ctx context.Context) {
func deleteUnnecessaryDomains(ctx context.Context, domains []types.Domain) []types.Domain {
var newDomains []types.Domain
logger := log.FromContext(ctx)
for idxDomainToCheck, domainToCheck := range p.Domains {
for idxDomainToCheck, domainToCheck := range domains {
keepDomain := true
for idxDomain, domain := range p.Domains {
for idxDomain, domain := range domains {
if idxDomainToCheck == idxDomain {
continue
}
@ -538,11 +550,11 @@ func (p *Provider) deleteUnnecessaryDomains(ctx context.Context) {
}
}
p.Domains = newDomains
return newDomains
}
func (p *Provider) watchCertificate(ctx context.Context) {
p.certsChan = make(chan *Certificate)
p.certsChan = make(chan *CertAndStore)
p.pool.Go(func(stop chan bool) {
for {
@ -550,9 +562,8 @@ func (p *Provider) watchCertificate(ctx context.Context) {
case cert := <-p.certsChan:
certUpdated := false
for _, domainsCertificate := range p.certificates {
if reflect.DeepEqual(cert.Domain, domainsCertificate.Domain) {
if reflect.DeepEqual(cert.Domain, domainsCertificate.Certificate.Domain) {
domainsCertificate.Certificate = cert.Certificate
domainsCertificate.Key = cert.Key
certUpdated = true
break
}
@ -573,7 +584,7 @@ func (p *Provider) watchCertificate(ctx context.Context) {
}
func (p *Provider) saveCertificates() error {
err := p.Store.SaveCertificates(p.certificates)
err := p.Store.SaveCertificates(p.ResolverName, p.certificates)
p.refreshCertificates()
@ -582,7 +593,7 @@ func (p *Provider) saveCertificates() error {
func (p *Provider) refreshCertificates() {
conf := dynamic.Message{
ProviderName: "ACME",
ProviderName: "acme." + p.ResolverName,
Configuration: &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
@ -596,9 +607,10 @@ func (p *Provider) refreshCertificates() {
for _, cert := range p.certificates {
certConf := &traefiktls.CertAndStores{
Certificate: traefiktls.Certificate{
CertFile: traefiktls.FileOrContent(cert.Certificate),
CertFile: traefiktls.FileOrContent(cert.Certificate.Certificate),
KeyFile: traefiktls.FileOrContent(cert.Key),
},
Stores: []string{cert.Store},
}
conf.Configuration.TLS.Certificates = append(conf.Configuration.TLS.Certificates, certConf)
}
@ -611,7 +623,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
logger.Info("Testing certificate renew...")
for _, cert := range p.certificates {
crt, err := getX509Certificate(ctx, cert)
crt, err := getX509Certificate(ctx, &cert.Certificate)
// If there's an error, we assume the cert is broken, and needs update
// <= 30 days left, renew certificate
if err != nil || crt == nil || crt.NotAfter.Before(time.Now().Add(24*30*time.Hour)) {
@ -626,7 +638,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
renewedCert, err := client.Certificate.Renew(certificate.Resource{
Domain: cert.Domain.Main,
PrivateKey: cert.Key,
Certificate: cert.Certificate,
Certificate: cert.Certificate.Certificate,
}, true, oscpMustStaple)
if err != nil {
@ -639,20 +651,20 @@ func (p *Provider) renewCertificates(ctx context.Context) {
continue
}
p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey)
p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey, cert.Store)
}
}
}
// Get provided certificate which check a domains list (Main and SANs)
// from static and dynamic provided certificates
func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []string, checkConfigurationDomains bool) []string {
func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []string, tlsStore string) []string {
p.resolvingDomainsMutex.RLock()
defer p.resolvingDomainsMutex.RUnlock()
log.FromContext(ctx).Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
allDomains := p.tlsManager.GetStore("default").GetAllDomains()
allDomains := p.tlsManager.GetStore(tlsStore).GetAllDomains()
// Get ACME certificates
for _, cert := range p.certificates {
@ -664,13 +676,6 @@ func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []str
allDomains = append(allDomains, domain)
}
// Get Configuration Domains
if checkConfigurationDomains {
for i := 0; i < len(p.Domains); i++ {
allDomains = append(allDomains, strings.Join(p.Domains[i].ToStrArray(), ","))
}
}
return searchUncheckedDomains(ctx, domainsToCheck, allDomains)
}
@ -712,17 +717,13 @@ func getX509Certificate(ctx context.Context, cert *Certificate) (*x509.Certifica
}
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it
func (p *Provider) getValidDomains(ctx context.Context, domain types.Domain, wildcardAllowed bool) ([]string, error) {
func (p *Provider) getValidDomains(ctx context.Context, domain types.Domain) ([]string, error) {
domains := domain.ToStrArray()
if len(domains) == 0 {
return nil, errors.New("unable to generate a certificate in ACME provider when no domain is given")
}
if strings.HasPrefix(domain.Main, "*") {
if !wildcardAllowed {
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q from a 'Host' rule", strings.Join(domains, ","))
}
if p.DNSChallenge == nil {
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
}

View file

@ -30,7 +30,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
desc string
dynamicCerts *safe.Safe
resolvingDomains map[string]struct{}
acmeCertificates []*Certificate
acmeCertificates []*CertAndStore
domains []string
expectedDomains []string
}{
@ -48,9 +48,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
{
desc: "wildcard already exists in ACME certificates",
domains: []string{"*.traefik.wtf"},
acmeCertificates: []*Certificate{
acmeCertificates: []*CertAndStore{
{
Domain: types.Domain{Main: "*.traefik.wtf"},
Certificate: Certificate{
Domain: types.Domain{Main: "*.traefik.wtf"},
},
},
},
expectedDomains: nil,
@ -69,9 +71,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
{
desc: "domain CN already exists in ACME certificates and SANs to generate",
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
acmeCertificates: []*Certificate{
acmeCertificates: []*CertAndStore{
{
Domain: types.Domain{Main: "traefik.wtf"},
Certificate: Certificate{
Domain: types.Domain{Main: "traefik.wtf"},
},
},
},
expectedDomains: []string{"foo.traefik.wtf"},
@ -85,9 +89,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
{
desc: "domain already exists in ACME certificates",
domains: []string{"traefik.wtf"},
acmeCertificates: []*Certificate{
acmeCertificates: []*CertAndStore{
{
Domain: types.Domain{Main: "traefik.wtf"},
Certificate: Certificate{
Domain: types.Domain{Main: "traefik.wtf"},
},
},
},
expectedDomains: nil,
@ -101,9 +107,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
{
desc: "domain matched by wildcard in ACME certificates",
domains: []string{"who.traefik.wtf", "foo.traefik.wtf"},
acmeCertificates: []*Certificate{
acmeCertificates: []*CertAndStore{
{
Domain: types.Domain{Main: "*.traefik.wtf"},
Certificate: Certificate{
Domain: types.Domain{Main: "*.traefik.wtf"},
},
},
},
expectedDomains: nil,
@ -111,9 +119,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
{
desc: "root domain with wildcard in ACME certificates",
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
acmeCertificates: []*Certificate{
acmeCertificates: []*CertAndStore{
{
Domain: types.Domain{Main: "*.traefik.wtf"},
Certificate: Certificate{
Domain: types.Domain{Main: "*.traefik.wtf"},
},
},
},
expectedDomains: []string{"traefik.wtf"},
@ -171,7 +181,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
resolvingDomains: test.resolvingDomains,
}
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, false)
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default")
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
})
}
@ -181,7 +191,6 @@ func TestGetValidDomain(t *testing.T) {
testCases := []struct {
desc string
domains types.Domain
wildcardAllowed bool
dnsChallenge *DNSChallenge
expectedErr string
expectedDomains []string
@ -190,7 +199,6 @@ func TestGetValidDomain(t *testing.T) {
desc: "valid wildcard",
domains: types.Domain{Main: "*.traefik.wtf"},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf"},
},
@ -199,22 +207,12 @@ func TestGetValidDomain(t *testing.T) {
domains: types.Domain{Main: "traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
dnsChallenge: &DNSChallenge{},
expectedErr: "",
wildcardAllowed: true,
expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"},
},
{
desc: "unauthorized wildcard",
domains: types.Domain{Main: "*.traefik.wtf"},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: false,
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf\" from a 'Host' rule",
expectedDomains: nil,
},
{
desc: "no domain",
domains: types.Domain{},
dnsChallenge: nil,
wildcardAllowed: true,
expectedErr: "unable to generate a certificate in ACME provider when no domain is given",
expectedDomains: nil,
},
@ -222,7 +220,6 @@ func TestGetValidDomain(t *testing.T) {
desc: "no DNSChallenge",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
dnsChallenge: nil,
wildcardAllowed: true,
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge",
expectedDomains: nil,
},
@ -230,7 +227,6 @@ func TestGetValidDomain(t *testing.T) {
desc: "unauthorized wildcard with SAN",
domains: types.Domain{Main: "*.*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain",
expectedDomains: nil,
},
@ -238,7 +234,6 @@ func TestGetValidDomain(t *testing.T) {
desc: "wildcard and SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"traefik.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
},
@ -246,7 +241,6 @@ func TestGetValidDomain(t *testing.T) {
desc: "wildcard SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"*.acme.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf", "*.acme.wtf"},
},
@ -259,7 +253,7 @@ func TestGetValidDomain(t *testing.T) {
acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}}
domains, err := acmeProvider.getValidDomains(context.Background(), test.domains, test.wildcardAllowed)
domains, err := acmeProvider.getValidDomains(context.Background(), test.domains)
if len(test.expectedErr) > 0 {
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
@ -439,10 +433,8 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
acmeProvider := Provider{Configuration: &Configuration{Domains: test.domains}}
acmeProvider.deleteUnnecessaryDomains(context.Background())
assert.Equal(t, test.expectedDomains, acmeProvider.Domains, "unexpected domain")
domains := deleteUnnecessaryDomains(context.Background(), test.domains)
assert.Equal(t, test.expectedDomains, domains, "unexpected domain")
})
}
}

View file

@ -1,20 +1,27 @@
package acme
// StoredData represents the data managed by Store
// StoredData represents the data managed by Store.
type StoredData struct {
Account *Account
Certificates []*Certificate
Account *Account
Certificates []*CertAndStore
}
// StoredChallengeData represents the data managed by ChallengeStore.
type StoredChallengeData struct {
HTTPChallenges map[string]map[string][]byte
TLSChallenges map[string]*Certificate
}
// Store is a generic interface that represents a storage
// Store is a generic interface that represents a storage.
type Store interface {
GetAccount() (*Account, error)
SaveAccount(*Account) error
GetCertificates() ([]*Certificate, error)
SaveCertificates([]*Certificate) error
GetAccount(string) (*Account, error)
SaveAccount(string, *Account) error
GetCertificates(string) ([]*CertAndStore, error)
SaveCertificates(string, []*CertAndStore) error
}
// ChallengeStore is a generic interface that represents a store for challenge data.
type ChallengeStore interface {
GetHTTPChallengeToken(token, domain string) ([]byte, error)
SetHTTPChallengeToken(token, domain string, keyAuth []byte) error
RemoveHTTPChallengeToken(token, domain string) error

View file

@ -438,7 +438,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
}
if ingressRoute.Spec.TLS != nil {
tlsConf := &dynamic.RouterTLSConfig{}
tlsConf := &dynamic.RouterTLSConfig{
CertResolver: ingressRoute.Spec.TLS.CertResolver,
}
if ingressRoute.Spec.TLS.Options != nil && len(ingressRoute.Spec.TLS.Options.Name) > 0 {
tlsOptionsName := ingressRoute.Spec.TLS.Options.Name
// Is a Kubernetes CRD reference, (i.e. not a cross-provider reference)
@ -537,7 +540,8 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
if ingressRouteTCP.Spec.TLS != nil {
conf.Routers[serviceName].TLS = &dynamic.RouterTCPTLSConfig{
Passthrough: ingressRouteTCP.Spec.TLS.Passthrough,
Passthrough: ingressRouteTCP.Spec.TLS.Passthrough,
CertResolver: ingressRouteTCP.Spec.TLS.CertResolver,
}
if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 {

View file

@ -32,7 +32,8 @@ type TLS struct {
// certificate details.
SecretName string `json:"secretName"`
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
Options *TLSOptionRef `json:"options"`
Options *TLSOptionRef `json:"options"`
CertResolver string `json:"certResolver"`
}
// TLSOptionRef is a ref to the TLSOption resources.

View file

@ -30,7 +30,8 @@ type TLSTCP struct {
SecretName string `json:"secretName"`
Passthrough bool `json:"passthrough"`
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
Options *TLSOptionTCPRef `json:"options"`
Options *TLSOptionTCPRef `json:"options"`
CertResolver string `json:"certResolver"`
}
// TLSOptionTCPRef is a ref to the TLSOption resources.

View file

@ -11,7 +11,7 @@ import (
)
// NewRouteAppenderFactory Creates a new RouteAppenderFactory
func NewRouteAppenderFactory(staticConfiguration static.Configuration, entryPointName string, acmeProvider *acme.Provider) *RouteAppenderFactory {
func NewRouteAppenderFactory(staticConfiguration static.Configuration, entryPointName string, acmeProvider []*acme.Provider) *RouteAppenderFactory {
return &RouteAppenderFactory{
staticConfiguration: staticConfiguration,
entryPointName: entryPointName,
@ -23,15 +23,18 @@ func NewRouteAppenderFactory(staticConfiguration static.Configuration, entryPoin
type RouteAppenderFactory struct {
staticConfiguration static.Configuration
entryPointName string
acmeProvider *acme.Provider
acmeProvider []*acme.Provider
}
// NewAppender Creates a new RouteAppender
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *runtime.Configuration) types.RouteAppender {
aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, runtimeConfiguration)
if r.acmeProvider != nil && r.acmeProvider.HTTPChallenge != nil && r.acmeProvider.HTTPChallenge.EntryPoint == r.entryPointName {
aggregator.AddAppender(r.acmeProvider)
for _, p := range r.acmeProvider {
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == r.entryPointName {
aggregator.AddAppender(p)
break
}
}
return aggregator

View file

@ -79,6 +79,11 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
return entryPointHandlers
}
type nameAndConfig struct {
routerName string // just so we have it as additional information when logging
TLSConfig *tls.Config
}
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, configsHTTP map[string]*runtime.RouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
router := &tcp.Router{}
router.HTTPHandler(handlerHTTP)
@ -86,15 +91,11 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
defaultTLSConf, err := m.tlsManager.Get("default", defaultTLSConfigName)
if err != nil {
return nil, err
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
}
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
type nameAndConfig struct {
routerName string // just so we have it as additional information when logging
TLSConfig *tls.Config
}
// Keyed by domain, then by options reference.
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
for routerHTTPName, routerHTTPConfig := range configsHTTP {
@ -156,7 +157,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
} else {
routers := make([]string, 0, len(tlsConfigs))
for _, v := range tlsConfigs {
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS option instead", hostSNI), false)
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
routers = append(routers, v.routerName)
}
logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)

View file

@ -74,17 +74,22 @@ func (m *Manager) Get(storeName string, configName string) (*tls.Config, error)
m.lock.RLock()
defer m.lock.RUnlock()
var tlsConfig *tls.Config
var err error
config, ok := m.configs[configName]
if !ok {
return nil, fmt.Errorf("unknown TLS options: %s", configName)
err = fmt.Errorf("unknown TLS options: %s", configName)
tlsConfig = &tls.Config{}
}
store := m.getStore(storeName)
tlsConfig, err := buildTLSConfig(config)
if err != nil {
log.Error(err)
tlsConfig = &tls.Config{}
if err == nil {
tlsConfig, err = buildTLSConfig(config)
if err != nil {
tlsConfig = &tls.Config{}
}
}
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
@ -113,7 +118,8 @@ func (m *Manager) Get(storeName string, configName string) (*tls.Config, error)
log.WithoutContext().Debugf("Serving default certificate for request: %q", domainToCheck)
return store.DefaultCertificate, nil
}
return tlsConfig, nil
return tlsConfig, err
}
func (m *Manager) getStore(storeName string) *CertificateStore {
@ -143,7 +149,7 @@ func buildCertificateStore(tlsStore Store) (*CertificateStore, error) {
}
certificateStore.DefaultCertificate = cert
} else {
log.Debug("No default certificate, generate one")
log.Debug("No default certificate, generating one")
cert, err := generate.DefaultCertificate()
if err != nil {
return certificateStore, err

View file

@ -152,11 +152,21 @@ func TestManager_Get(t *testing.T) {
func TestClientAuth(t *testing.T) {
tlsConfigs := map[string]Options{
"eca": {ClientAuth: ClientAuth{}},
"ecat": {ClientAuth: ClientAuth{ClientAuthType: ""}},
"ncc": {ClientAuth: ClientAuth{ClientAuthType: "NoClientCert"}},
"rcc": {ClientAuth: ClientAuth{ClientAuthType: "RequestClientCert"}},
"racc": {ClientAuth: ClientAuth{ClientAuthType: "RequireAnyClientCert"}},
"eca": {
ClientAuth: ClientAuth{},
},
"ecat": {
ClientAuth: ClientAuth{ClientAuthType: ""},
},
"ncc": {
ClientAuth: ClientAuth{ClientAuthType: "NoClientCert"},
},
"rcc": {
ClientAuth: ClientAuth{ClientAuthType: "RequestClientCert"},
},
"racc": {
ClientAuth: ClientAuth{ClientAuthType: "RequireAnyClientCert"},
},
"vccig": {
ClientAuth: ClientAuth{
CAFiles: []FileOrContent{localhostCert},
@ -166,7 +176,9 @@ func TestClientAuth(t *testing.T) {
"vccigwca": {
ClientAuth: ClientAuth{ClientAuthType: "VerifyClientCertIfGiven"},
},
"ravcc": {ClientAuth: ClientAuth{ClientAuthType: "RequireAndVerifyClientCert"}},
"ravcc": {
ClientAuth: ClientAuth{ClientAuthType: "RequireAndVerifyClientCert"},
},
"ravccwca": {
ClientAuth: ClientAuth{
CAFiles: []FileOrContent{localhostCert},
@ -179,7 +191,9 @@ func TestClientAuth(t *testing.T) {
ClientAuthType: "RequireAndVerifyClientCert",
},
},
"ucat": {ClientAuth: ClientAuth{ClientAuthType: "Unknown"}},
"ucat": {
ClientAuth: ClientAuth{ClientAuthType: "Unknown"},
},
}
block, _ := pem.Decode([]byte(localhostCert))
@ -191,6 +205,7 @@ func TestClientAuth(t *testing.T) {
tlsOptionsName string
expectedClientAuth tls.ClientAuthType
expectedRawSubject []byte
expectedError bool
}{
{
desc: "Empty ClientAuth option should get a tls.NoClientCert (default value)",
@ -223,14 +238,16 @@ func TestClientAuth(t *testing.T) {
expectedClientAuth: tls.VerifyClientCertIfGiven,
},
{
desc: "VerifyClientCertIfGiven option without CAFiles yields a default ClientAuthType (NoClientCert)",
desc: "VerifyClientCertIfGiven option without CAFiles yields a default ClientAuthType (NoClientCert)",
tlsOptionsName: "vccigwca",
expectedClientAuth: tls.NoClientCert,
expectedError: true,
},
{
desc: "RequireAndVerifyClientCert option without CAFiles yields a default ClientAuthType (NoClientCert)",
desc: "RequireAndVerifyClientCert option without CAFiles yields a default ClientAuthType (NoClientCert)",
tlsOptionsName: "ravcc",
expectedClientAuth: tls.NoClientCert,
expectedError: true,
},
{
desc: "RequireAndVerifyClientCert option should get a tls.RequireAndVerifyClientCert as ClientAuthType with CA files",
@ -242,11 +259,13 @@ func TestClientAuth(t *testing.T) {
desc: "Unknown option yields a default ClientAuthType (NoClientCert)",
tlsOptionsName: "ucat",
expectedClientAuth: tls.NoClientCert,
expectedError: true,
},
{
desc: "Bad CA certificate content yields a default ClientAuthType (NoClientCert)",
tlsOptionsName: "ravccwbca",
expectedClientAuth: tls.NoClientCert,
expectedError: true,
},
}
@ -259,6 +278,12 @@ func TestClientAuth(t *testing.T) {
t.Parallel()
config, err := tlsManager.Get("default", test.tlsOptionsName)
if test.expectedError {
assert.Error(t, err)
return
}
assert.NoError(t, err)
if test.expectedRawSubject != nil {

View file

@ -1,10 +1,11 @@
package types
import (
"fmt"
"strings"
)
// +k8s:deepcopy-gen=true
// Domain holds a domain name with SANs.
type Domain struct {
Main string `description:"Default subject name." json:"main,omitempty" toml:"main,omitempty" yaml:"main,omitempty"`
@ -28,44 +29,6 @@ func (d *Domain) Set(domains []string) {
}
}
// Domains parse []Domain.
type Domains []Domain
// Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("parse error ACME.Domain. Unable to parse %s", str)
}
d := Domain{
Main: slice[0],
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
// Get []Domain.
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
// String returns []Domain in string.
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
// SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = val.([]Domain)
}
// MatchDomain returns true if a domain match the cert domain.
func MatchDomain(domain string, certDomain string) bool {
if domain == certDomain {

View file

@ -0,0 +1,50 @@
// +build !ignore_autogenerated
/*
The MIT License (MIT)
Copyright (c) 2016-2019 Containous SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package types
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Domain) DeepCopyInto(out *Domain) {
*out = *in
if in.SANs != nil {
in, out := &in.SANs, &out.SANs
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
func (in *Domain) DeepCopy() *Domain {
if in == nil {
return nil
}
out := new(Domain)
in.DeepCopyInto(out)
return out
}

View file

@ -11,4 +11,8 @@ REPO_ROOT=${HACK_DIR}/..
--go-header-file "${HACK_DIR}"/boilerplate.go.tmpl \
"$@"
deepcopy-gen --input-dirs github.com/containous/traefik/pkg/config/dynamic --input-dirs github.com/containous/traefik/pkg/tls -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl
deepcopy-gen \
--input-dirs github.com/containous/traefik/pkg/config/dynamic \
--input-dirs github.com/containous/traefik/pkg/tls \
--input-dirs github.com/containous/traefik/pkg/types \
-O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl