Add support for custom CA certificates by certificate resolver
This commit is contained in:
parent
e222d5cb2f
commit
ac1dad3d14
6 changed files with 210 additions and 4 deletions
|
@ -709,6 +709,109 @@ certificatesResolvers:
|
|||
# ...
|
||||
```
|
||||
|
||||
### `caCertificates`
|
||||
|
||||
_Optional, Default=[]_
|
||||
|
||||
The `caCertificates` option specifies the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
certificatesResolvers:
|
||||
myresolver:
|
||||
acme:
|
||||
# ...
|
||||
caCertificates:
|
||||
- path/certificates1.pem
|
||||
- path/certificates2.pem
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.myresolver.acme]
|
||||
# ...
|
||||
caCertificates = [ "path/certificates1.pem", "path/certificates2.pem" ]
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesresolvers.myresolver.acme.caCertificates="path/certificates1.pem,path/certificates2.pem"
|
||||
# ...
|
||||
```
|
||||
|
||||
??? note "LEGO Environment Variable"
|
||||
|
||||
It can be defined globally by using the environment variable `LEGO_CA_CERTIFICATES`.
|
||||
This environment variable is neither a fallback nor an override of the configuration option.
|
||||
|
||||
### `caSystemCertPool`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
The `caSystemCertPool` option defines if the certificates pool must use a copy of the system cert pool.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
certificatesResolvers:
|
||||
myresolver:
|
||||
acme:
|
||||
# ...
|
||||
caSystemCertPool: true
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.myresolver.acme]
|
||||
# ...
|
||||
caSystemCertPool = true
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesresolvers.myresolver.acme.caSystemCertPool=true
|
||||
# ...
|
||||
```
|
||||
|
||||
??? note "LEGO Environment Variable"
|
||||
|
||||
It can be defined globally by using the environment variable `LEGO_CA_SYSTEM_CERT_POOL`.
|
||||
`LEGO_CA_SYSTEM_CERT_POOL` is ignored if `LEGO_CA_CERTIFICATES` is not set or empty.
|
||||
This environment variable is neither a fallback nor an override of the configuration option.
|
||||
|
||||
### `caServerName`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
The `caServerName` option specifies the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
certificatesResolvers:
|
||||
myresolver:
|
||||
acme:
|
||||
# ...
|
||||
caServerName: "my-server"
|
||||
# ...
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[certificatesResolvers.myresolver.acme]
|
||||
# ...
|
||||
caServerName = "my-server"
|
||||
# ...
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
# ...
|
||||
--certificatesresolvers.myresolver.acme.caServerName="my-server"
|
||||
# ...
|
||||
```
|
||||
|
||||
??? note "LEGO Environment Variable"
|
||||
|
||||
It can be defined globally by using the environment variable `LEGO_CA_SERVER_NAME`.
|
||||
`LEGO_CA_SERVER_NAME` is ignored if `LEGO_CA_CERTIFICATES` is not set or empty.
|
||||
This environment variable is neither a fallback nor an override of the configuration option.
|
||||
|
||||
## Fallback
|
||||
|
||||
If Let's Encrypt is not reachable, the following certificates will apply:
|
||||
|
|
|
@ -57,9 +57,18 @@ Activate API directly on the entryPoint named traefik. (Default: ```false```)
|
|||
`--certificatesresolvers.<name>`:
|
||||
Certificates resolvers configuration. (Default: ```false```)
|
||||
|
||||
`--certificatesresolvers.<name>.acme.cacertificates`:
|
||||
Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
`--certificatesresolvers.<name>.acme.caserver`:
|
||||
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
||||
|
||||
`--certificatesresolvers.<name>.acme.caservername`:
|
||||
Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
`--certificatesresolvers.<name>.acme.casystemcertpool`:
|
||||
Define if the certificates pool must use a copy of the system cert pool. (Default: ```false```)
|
||||
|
||||
`--certificatesresolvers.<name>.acme.certificatesduration`:
|
||||
Certificates' duration in hours. (Default: ```2160```)
|
||||
|
||||
|
|
|
@ -57,9 +57,18 @@ Activate API directly on the entryPoint named traefik. (Default: ```false```)
|
|||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>`:
|
||||
Certificates resolvers configuration. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CACERTIFICATES`:
|
||||
Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASERVER`:
|
||||
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASERVERNAME`:
|
||||
Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list.
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASYSTEMCERTPOOL`:
|
||||
Define if the certificates pool must use a copy of the system cert pool. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CERTIFICATESDURATION`:
|
||||
Certificates' duration in hours. (Default: ```2160```)
|
||||
|
||||
|
|
|
@ -441,6 +441,9 @@
|
|||
storage = "foobar"
|
||||
keyType = "foobar"
|
||||
certificatesDuration = 42
|
||||
caCertificates = ["foobar", "foobar"]
|
||||
caSystemCertPool = true
|
||||
caServerName = "foobar"
|
||||
[certificatesResolvers.CertificateResolver0.acme.eab]
|
||||
kid = "foobar"
|
||||
hmacEncoded = "foobar"
|
||||
|
@ -461,6 +464,9 @@
|
|||
storage = "foobar"
|
||||
keyType = "foobar"
|
||||
certificatesDuration = 42
|
||||
caCertificates = ["foobar", "foobar"]
|
||||
caSystemCertPool = true
|
||||
caServerName = "foobar"
|
||||
[certificatesResolvers.CertificateResolver1.acme.eab]
|
||||
kid = "foobar"
|
||||
hmacEncoded = "foobar"
|
||||
|
|
|
@ -483,6 +483,11 @@ certificatesResolvers:
|
|||
kid: foobar
|
||||
hmacEncoded: foobar
|
||||
certificatesDuration: 42
|
||||
caCertificates:
|
||||
- foobar
|
||||
- foobar
|
||||
caSystemCertPool: true
|
||||
caServerName: foobar
|
||||
dnsChallenge:
|
||||
provider: foobar
|
||||
delayBeforeCheck: 42s
|
||||
|
@ -505,6 +510,11 @@ certificatesResolvers:
|
|||
kid: foobar
|
||||
hmacEncoded: foobar
|
||||
certificatesDuration: 42
|
||||
caCertificates:
|
||||
- foobar
|
||||
- foobar
|
||||
caSystemCertPool: true
|
||||
caServerName: foobar
|
||||
dnsChallenge:
|
||||
provider: foobar
|
||||
delayBeforeCheck: 42s
|
||||
|
|
|
@ -6,9 +6,13 @@ import (
|
|||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -43,6 +47,10 @@ type Configuration struct {
|
|||
EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"`
|
||||
CertificatesDuration int `description:"Certificates' duration in hours." json:"certificatesDuration,omitempty" toml:"certificatesDuration,omitempty" yaml:"certificatesDuration,omitempty" export:"true"`
|
||||
|
||||
CACertificates []string `description:"Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caCertificates,omitempty" toml:"caCertificates,omitempty" yaml:"caCertificates,omitempty"`
|
||||
CASystemCertPool bool `description:"Define if the certificates pool must use a copy of the system cert pool." json:"caSystemCertPool,omitempty" toml:"caSystemCertPool,omitempty" yaml:"caSystemCertPool,omitempty" export:"true"`
|
||||
CAServerName string `description:"Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caServerName,omitempty" toml:"caServerName,omitempty" yaml:"caServerName,omitempty" export:"true"`
|
||||
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
@ -261,6 +269,11 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
config.Certificate.KeyType = GetKeyType(ctx, p.KeyType)
|
||||
config.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||
|
||||
config.HTTPClient, err = p.createHTTPClient()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating HTTP client: %w", err)
|
||||
}
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -340,6 +353,64 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
return p.client, nil
|
||||
}
|
||||
|
||||
func (p *Provider) createHTTPClient() (*http.Client, error) {
|
||||
tlsConfig, err := p.createClientTLSConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating client TLS config: %w", err)
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Timeout: 2 * time.Minute,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 30 * time.Second,
|
||||
ResponseHeaderTimeout: 30 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) createClientTLSConfig() (*tls.Config, error) {
|
||||
if len(p.CACertificates) > 0 || p.CAServerName != "" {
|
||||
certPool, err := lego.CreateCertPool(p.CACertificates, p.CASystemCertPool)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating cert pool with custom certificates: %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
ServerName: p.CAServerName,
|
||||
RootCAs: certPool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Compatibility layer with the lego.
|
||||
// https://github.com/go-acme/lego/blob/834a9089f143e3407b3f5c8b93a0e285ba231fe2/lego/client_config.go#L24-L34
|
||||
// https://github.com/go-acme/lego/blob/834a9089f143e3407b3f5c8b93a0e285ba231fe2/lego/client_config.go#L97-L113
|
||||
|
||||
serverName := os.Getenv("LEGO_CA_SERVER_NAME")
|
||||
customCACertsPath := os.Getenv("LEGO_CA_CERTIFICATES")
|
||||
|
||||
if customCACertsPath == "" && serverName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
useSystemCertPool, _ := strconv.ParseBool(os.Getenv("LEGO_CA_SYSTEM_CERT_POOL"))
|
||||
|
||||
certPool, err := lego.CreateCertPool(strings.Split(customCACertsPath, string(os.PathListSeparator)), useSystemCertPool)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating cert pool: %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
ServerName: serverName,
|
||||
RootCAs: certPool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) initAccount(ctx context.Context) (*Account, error) {
|
||||
if p.account == nil || len(p.account.Email) == 0 {
|
||||
var err error
|
||||
|
@ -424,8 +495,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
|||
|
||||
if len(route.TLS.Domains) > 0 {
|
||||
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
|
||||
for i := range len(domains) {
|
||||
domain := domains[i]
|
||||
for _, domain := range domains {
|
||||
safe.Go(func() {
|
||||
dom, cert, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName)
|
||||
if err != nil {
|
||||
|
@ -461,8 +531,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
|||
|
||||
if len(route.TLS.Domains) > 0 {
|
||||
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
|
||||
for i := range len(domains) {
|
||||
domain := domains[i]
|
||||
for _, domain := range domains {
|
||||
safe.Go(func() {
|
||||
dom, cert, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue