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:
parent
e3627e9cba
commit
f75f73f3d2
47 changed files with 1573 additions and 1249 deletions
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
89
docs/content/https/ref-acme.txt
Normal file
89
docs/content/https/ref-acme.txt
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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```)
|
||||
|
||||
|
|
|
@ -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```)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
58
integration/fixtures/acme/acme_domains.toml
Normal file
58
integration/fixtures/acme/acme_domains.toml
Normal 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}}
|
58
integration/fixtures/acme/acme_multiple_resolvers.toml
Normal file
58
integration/fixtures/acme/acme_multiple_resolvers.toml
Normal 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"
|
51
integration/fixtures/acme/acme_tcp.toml
Normal file
51
integration/fixtures/acme/acme_tcp.toml
Normal 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"
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -212,7 +212,6 @@
|
|||
storage = "foobar"
|
||||
entryPoint = "foobar"
|
||||
keyType = "foobar"
|
||||
onHostRule = true
|
||||
[acme.dnsChallenge]
|
||||
provider = "foobar"
|
||||
delayBeforeCheck = 42
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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: {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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, ","))
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
50
pkg/types/zz_generated.deepcopy.go
Normal file
50
pkg/types/zz_generated.deepcopy.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue