Skip to content

Commit

Permalink
ACME Default Certificate
Browse files Browse the repository at this point in the history
Co-authored-by: Ludovic Fernandez <[email protected]>
Co-authored-by: Julien Salleyron <[email protected]>
  • Loading branch information
3 people authored Sep 13, 2022
1 parent 693d5da commit a002ccf
Show file tree
Hide file tree
Showing 22 changed files with 754 additions and 240 deletions.
70 changes: 69 additions & 1 deletion docs/content/https/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,75 @@ data:
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
```
If no default certificate is provided, Traefik generates and uses a self-signed certificate.
If no `defaultCertificate` is provided, Traefik will use the generated one.

### ACME Default Certificate

You can configure Traefik to use an ACME provider (like Let's Encrypt) to generate the default certificate.
The configuration to resolve the default certificate should be defined in a TLS store:

!!! important "Precedence with the `defaultGeneratedCert` option"

The `defaultGeneratedCert` definition takes precedence over the ACME default certificate configuration.

```yaml tab="File (YAML)"
# Dynamic configuration
tls:
stores:
default:
defaultGeneratedCert:
resolver: myresolver
domain:
main: example.org
sans:
- foo.example.org
- bar.example.org
```

```toml tab="File (TOML)"
# Dynamic configuration
[tls.stores]
[tls.stores.default.defaultGeneratedCert]
resolver = "myresolver"
[tls.stores.default.defaultGeneratedCert.domain]
main = "example.org"
sans = ["foo.example.org", "bar.example.org"]
```

```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
name: default
namespace: default
spec:
defaultGeneratedCert:
resolver: myresolver
domain:
main: example.org
sans:
- foo.example.org
- bar.example.org
```

```yaml tab="Docker"
## Dynamic configuration
labels:
- "traefik.tls.stores.default.defaultgeneratedcert.resolver=myresolver"
- "traefik.tls.stores.default.defaultgeneratedcert.domain.main=example.org"
- "traefik.tls.stores.default.defaultgeneratedcert.domain.sans=foo.example.org, bar.example.org"
```

```json tab="Marathon"
labels: {
"traefik.tls.stores.default.defaultgeneratedcert.resolver": "myresolver",
"traefik.tls.stores.default.defaultgeneratedcert.domain.main": "example.org",
"traefik.tls.stores.default.defaultgeneratedcert.domain.sans": "foo.example.org, bar.example.org",
}
```

## TLS Options

Expand Down
10 changes: 10 additions & 0 deletions docs/content/reference/dynamic-configuration/docker-labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,13 @@
- "traefik.udp.routers.udprouter1.entrypoints=foobar, foobar"
- "traefik.udp.routers.udprouter1.service=foobar"
- "traefik.udp.services.udpservice01.loadbalancer.server.port=foobar"
- "traefik.tls.stores.Store0.defaultcertificate.certfile=foobar"
- "traefik.tls.stores.Store0.defaultcertificate.keyfile=foobar"
- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.main=foobar"
- "traefik.tls.stores.Store0.defaultgeneratedcert.domain.sans=foobar, foobar"
- "traefik.tls.stores.Store0.defaultgeneratedcert.resolver=foobar"
- "traefik.tls.stores.Store1.defaultcertificate.certfile=foobar"
- "traefik.tls.stores.Store1.defaultcertificate.keyfile=foobar"
- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.main=foobar"
- "traefik.tls.stores.Store1.defaultgeneratedcert.domain.sans=foobar, foobar"
- "traefik.tls.stores.Store1.defaultgeneratedcert.resolver=foobar"
10 changes: 10 additions & 0 deletions docs/content/reference/dynamic-configuration/file.toml
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,17 @@
[tls.stores.Store0.defaultCertificate]
certFile = "foobar"
keyFile = "foobar"
[tls.stores.Store0.defaultGeneratedCert]
resolver = "foobar"
[tls.stores.Store0.defaultGeneratedCert.domain]
main = "foobar"
sans = ["foobar", "foobar"]
[tls.stores.Store1]
[tls.stores.Store1.defaultCertificate]
certFile = "foobar"
keyFile = "foobar"
[tls.stores.Store1.defaultGeneratedCert]
resolver = "foobar"
[tls.stores.Store1.defaultGeneratedCert.domain]
main = "foobar"
sans = ["foobar", "foobar"]
14 changes: 14 additions & 0 deletions docs/content/reference/dynamic-configuration/file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,21 @@ tls:
defaultCertificate:
certFile: foobar
keyFile: foobar
defaultGeneratedCert:
resolver: foobar
domain:
main: foobar
sans:
- foobar
- foobar
Store1:
defaultCertificate:
certFile: foobar
keyFile: foobar
defaultGeneratedCert:
resolver: foobar
domain:
main: foobar
sans:
- foobar
- foobar
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,27 @@ spec:
required:
- secretName
type: object
defaultGeneratedCert:
description: DefaultGeneratedCert defines the default generated certificate
configuration.
properties:
domain:
description: Domain is the domain definition for the DefaultCertificate.
properties:
main:
description: Main defines the main domain name.
type: string
sans:
description: SANs defines the subject alternative domain names.
items:
type: string
type: array
type: object
resolver:
description: Resolver is the name of the resolver that will be
used to issue the DefaultCertificate.
type: string
type: object
type: object
required:
- metadata
Expand Down
8 changes: 8 additions & 0 deletions docs/content/reference/dynamic-configuration/kv-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,16 @@
| `traefik/tls/options/Options1/sniStrict` | `true` |
| `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` |
| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` |
| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | `foobar` |
| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | `foobar` |
| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | `foobar` |
| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/main` | `foobar` |
| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/0` | `foobar` |
| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/1` | `foobar` |
| `traefik/tls/stores/Store1/defaultGeneratedCert/resolver` | `foobar` |
| `traefik/udp/routers/UDPRouter0/entryPoints/0` | `foobar` |
| `traefik/udp/routers/UDPRouter0/entryPoints/1` | `foobar` |
| `traefik/udp/routers/UDPRouter0/service` | `foobar` |
Expand Down
10 changes: 10 additions & 0 deletions docs/content/reference/dynamic-configuration/marathon-labels.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,13 @@
"traefik.udp.routers.udprouter1.entrypoints": "foobar, foobar",
"traefik.udp.routers.udprouter1.service": "foobar",
"traefik.udp.services.udpservice01.loadbalancer.server.port": "foobar",
"traefik.tls.stores.Store0.defaultcertificate.certfile": "foobar",
"traefik.tls.stores.Store0.defaultcertificate.keyfile": "foobar",
"traefik.tls.stores.Store0.defaultgeneratedcert.domain.main": "foobar",
"traefik.tls.stores.Store0.defaultgeneratedcert.domain.sans": "foobar, foobar",
"traefik.tls.stores.Store0.defaultgeneratedcert.resolver": "foobar",
"traefik.tls.stores.Store1.defaultcertificate.certfile": "foobar",
"traefik.tls.stores.Store1.defaultcertificate.keyfile": "foobar",
"traefik.tls.stores.Store1.defaultgeneratedcert.domain.main": "foobar",
"traefik.tls.stores.Store1.defaultgeneratedcert.domain.sans": "foobar, foobar",
"traefik.tls.stores.Store1.defaultgeneratedcert.resolver": "foobar",
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ spec:
required:
- secretName
type: object
defaultGeneratedCert:
description: DefaultGeneratedCert defines the default generated certificate
configuration.
properties:
domain:
description: Domain is the domain definition for the DefaultCertificate.
properties:
main:
description: Main defines the main domain name.
type: string
sans:
description: SANs defines the subject alternative domain names.
items:
type: string
type: array
type: object
resolver:
description: Resolver is the name of the resolver that will be
used to issue the DefaultCertificate.
type: string
type: object
type: object
required:
- metadata
Expand Down
24 changes: 24 additions & 0 deletions integration/acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type acmeTestCase struct {
}

type templateModel struct {
Domain types.Domain
Domains []types.Domain
PortHTTP string
PortHTTPS string
Expand Down Expand Up @@ -149,6 +150,29 @@ func (s *AcmeSuite) TestHTTP01Domains(c *check.C) {
s.retrieveAcmeCertificate(c, testCase)
}

func (s *AcmeSuite) TestHTTP01StoreDomains(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_store_domains.toml",
subCases: []subCases{{
host: acmeDomain,
expectedCommonName: acmeDomain,
expectedAlgorithm: x509.RSA,
}},
template: templateModel{
Domain: types.Domain{
Main: "traefik.acme.wtf",
},
Acme: map[string]static.CertificateResolver{
"default": {ACME: &acme.Configuration{
HTTPChallenge: &acme.HTTPChallenge{EntryPoint: "web"},
}},
},
},
}

s.retrieveAcmeCertificate(c, testCase)
}

func (s *AcmeSuite) TestHTTP01DomainsInSAN(c *check.C) {
testCase := acmeTestCase{
traefikConfFilePath: "fixtures/acme/acme_domains.toml",
Expand Down
60 changes: 60 additions & 0 deletions integration/fixtures/acme/acme_store_domains.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false

[log]
level = "DEBUG"

[entryPoints]
[entryPoints.web]
address = "{{ .PortHTTP }}"
[entryPoints.websecure]
address = "{{ .PortHTTPS }}"

{{range $name, $resolvers := .Acme }}

[certificatesResolvers.{{ $name }}.acme]
email = "[email protected]"
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]
insecure = true

[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 = ["websecure"]
rule = "PathPrefix(`/`)"
service = "test"
[http.routers.test.tls]

[tls.stores]
[tls.stores.default.defaultGeneratedCert]
resolver = "default"
[tls.stores.default.defaultGeneratedCert.domain]
main = "{{ .Domain.Main }}"
sans = [{{range .Domain.SANs }}
"{{.}}",
{{end}}]
21 changes: 21 additions & 0 deletions integration/fixtures/k8s/01-traefik-crd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,27 @@ spec:
required:
- secretName
type: object
defaultGeneratedCert:
description: DefaultGeneratedCert defines the default generated certificate
configuration.
properties:
domain:
description: Domain is the domain definition for the DefaultCertificate.
properties:
main:
description: Main defines the main domain name.
type: string
sans:
description: SANs defines the subject alternative domain names.
items:
type: string
type: array
type: object
resolver:
description: Resolver is the name of the resolver that will be
used to issue the DefaultCertificate.
type: string
type: object
type: object
required:
- metadata
Expand Down
8 changes: 4 additions & 4 deletions integration/https_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) {

cs := conn.ConnectionState()
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))

proto := cs.NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2")
Expand Down Expand Up @@ -360,7 +360,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) {

cs := conn.ConnectionState()
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))

proto := cs.NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2")
Expand Down Expand Up @@ -397,7 +397,7 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) {

cs := conn.ConnectionState()
err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))

proto := cs.NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2")
Expand Down Expand Up @@ -434,7 +434,7 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) {

cs := conn.ConnectionState()
err = cs.PeerCertificates[0].VerifyHostname("www.snitest.com")
c.Assert(err, checker.IsNil, check.Commentf("certificate did not serve correct default certificate"))
c.Assert(err, checker.IsNil, check.Commentf("server did not serve correct default certificate"))

proto := cs.NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2")
Expand Down
Loading

0 comments on commit a002ccf

Please sign in to comment.