acme: new HTTP and TLS challenges implementations.
This commit is contained in:
parent
49cdb67ddc
commit
05333b9579
13 changed files with 398 additions and 254 deletions
|
@ -2,85 +2,126 @@ package acme
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*challengeHTTP)(nil)
|
||||
// ChallengeHTTP HTTP challenge provider implements challenge.Provider.
|
||||
type ChallengeHTTP struct {
|
||||
httpChallenges map[string]map[string][]byte
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type challengeHTTP struct {
|
||||
Store ChallengeStore
|
||||
// NewChallengeHTTP creates a new ChallengeHTTP.
|
||||
func NewChallengeHTTP() *ChallengeHTTP {
|
||||
return &ChallengeHTTP{
|
||||
httpChallenges: make(map[string]map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// Present presents a challenge to obtain new ACME certificate.
|
||||
func (c *challengeHTTP) Present(domain, token, keyAuth string) error {
|
||||
return c.Store.SetHTTPChallengeToken(token, domain, []byte(keyAuth))
|
||||
func (c *ChallengeHTTP) Present(domain, token, keyAuth string) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if _, ok := c.httpChallenges[token]; !ok {
|
||||
c.httpChallenges[token] = map[string][]byte{}
|
||||
}
|
||||
|
||||
c.httpChallenges[token][domain] = []byte(keyAuth)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp cleans the challenges when certificate is obtained.
|
||||
func (c *challengeHTTP) CleanUp(domain, token, keyAuth string) error {
|
||||
return c.Store.RemoveHTTPChallengeToken(token, domain)
|
||||
func (c *ChallengeHTTP) CleanUp(domain, token, _ string) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.httpChallenges == nil && len(c.httpChallenges) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := c.httpChallenges[token]; ok {
|
||||
delete(c.httpChallenges[token], domain)
|
||||
|
||||
if len(c.httpChallenges[token]) == 0 {
|
||||
delete(c.httpChallenges, token)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout calculates the maximum of time allowed to resolved an ACME challenge.
|
||||
func (c *challengeHTTP) Timeout() (timeout, interval time.Duration) {
|
||||
func (c *ChallengeHTTP) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
|
||||
// CreateHandler creates a HTTP handler to expose the token for the HTTP challenge.
|
||||
func (p *Provider) CreateHandler(notFoundHandler http.Handler) http.Handler {
|
||||
router := mux.NewRouter().SkipClean(true)
|
||||
router.NotFoundHandler = notFoundHandler
|
||||
func (c *ChallengeHTTP) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := log.With(req.Context(), log.Str(log.ProviderName, "acme"))
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
Path(http01.ChallengePath("{token}")).
|
||||
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
token, err := getPathParam(req.URL)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to get token: %v.", err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, p.ResolverName+".acme"))
|
||||
logger := log.FromContext(ctx)
|
||||
if token != "" {
|
||||
domain, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
logger.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
||||
domain = req.Host
|
||||
}
|
||||
|
||||
if token, ok := vars["token"]; ok {
|
||||
domain, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
logger.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
||||
domain = req.Host
|
||||
}
|
||||
|
||||
tokenValue := getTokenValue(ctx, token, domain, p.ChallengeStore)
|
||||
if len(tokenValue) > 0 {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, err = rw.Write(tokenValue)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to write token: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
tokenValue := c.getTokenValue(ctx, token, domain)
|
||||
if len(tokenValue) > 0 {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, err = rw.Write(tokenValue)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to write token: %v", err)
|
||||
}
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return router
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func getTokenValue(ctx context.Context, token, domain string, store ChallengeStore) []byte {
|
||||
func (c *ChallengeHTTP) getTokenValue(ctx context.Context, token, domain string) []byte {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Debugf("Retrieving the ACME challenge for token %v...", token)
|
||||
logger.Debugf("Retrieving the ACME challenge for token %s...", token)
|
||||
|
||||
var result []byte
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
result, err = store.GetHTTPChallengeToken(token, domain)
|
||||
return err
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
if _, ok := c.httpChallenges[token]; !ok {
|
||||
return fmt.Errorf("cannot find challenge for token %s", token)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
result, ok = c.httpChallenges[token][domain]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot find challenge for domain %s", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
|
@ -97,3 +138,14 @@ func getTokenValue(ctx context.Context, token, domain string, store ChallengeSto
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
func getPathParam(uri *url.URL) (string, error) {
|
||||
exp := regexp.MustCompile(fmt.Sprintf(`^%s([^/]+)/?$`, http01.ChallengePath("")))
|
||||
parts := exp.FindStringSubmatch(uri.Path)
|
||||
|
||||
if len(parts) != 2 {
|
||||
return "", errors.New("missing token")
|
||||
}
|
||||
|
||||
return parts[1], nil
|
||||
}
|
||||
|
|
|
@ -1,22 +1,45 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
var _ challenge.Provider = (*challengeTLSALPN)(nil)
|
||||
const providerNameALPN = "tlsalpn.acme"
|
||||
|
||||
type challengeTLSALPN struct {
|
||||
Store ChallengeStore
|
||||
// ChallengeTLSALPN TLSALPN challenge provider implements challenge.Provider.
|
||||
type ChallengeTLSALPN struct {
|
||||
Timeout time.Duration
|
||||
|
||||
chans map[string]chan struct{}
|
||||
muChans sync.Mutex
|
||||
|
||||
certs map[string]*Certificate
|
||||
muCerts sync.Mutex
|
||||
|
||||
configurationChan chan<- dynamic.Message
|
||||
}
|
||||
|
||||
func (c *challengeTLSALPN) Present(domain, token, keyAuth string) error {
|
||||
log.WithoutContext().WithField(log.ProviderName, "acme").
|
||||
// NewChallengeTLSALPN creates a new ChallengeTLSALPN.
|
||||
func NewChallengeTLSALPN(timeout time.Duration) *ChallengeTLSALPN {
|
||||
return &ChallengeTLSALPN{
|
||||
Timeout: timeout,
|
||||
chans: make(map[string]chan struct{}),
|
||||
certs: make(map[string]*Certificate),
|
||||
}
|
||||
}
|
||||
|
||||
// Present presents a challenge to obtain new ACME certificate.
|
||||
func (c *ChallengeTLSALPN) Present(domain, _, keyAuth string) error {
|
||||
log.WithoutContext().WithField(log.ProviderName, providerNameALPN).
|
||||
Debugf("TLS Challenge Present temp certificate for %s", domain)
|
||||
|
||||
certPEMBlock, keyPEMBlock, err := tlsalpn01.ChallengeBlocks(domain, keyAuth)
|
||||
|
@ -25,31 +48,113 @@ func (c *challengeTLSALPN) Present(domain, token, keyAuth string) error {
|
|||
}
|
||||
|
||||
cert := &Certificate{Certificate: certPEMBlock, Key: keyPEMBlock, Domain: types.Domain{Main: "TEMP-" + domain}}
|
||||
return c.Store.AddTLSChallenge(domain, cert)
|
||||
|
||||
c.muChans.Lock()
|
||||
ch := make(chan struct{})
|
||||
c.chans[string(certPEMBlock)] = ch
|
||||
c.muChans.Unlock()
|
||||
|
||||
c.muCerts.Lock()
|
||||
c.certs[keyAuth] = cert
|
||||
conf := createMessage(c.certs)
|
||||
c.muCerts.Unlock()
|
||||
|
||||
c.configurationChan <- conf
|
||||
|
||||
timer := time.NewTimer(c.Timeout)
|
||||
|
||||
var errC error
|
||||
select {
|
||||
case t := <-timer.C:
|
||||
timer.Stop()
|
||||
close(c.chans[string(certPEMBlock)])
|
||||
errC = fmt.Errorf("timeout %s", t)
|
||||
case <-ch:
|
||||
// noop
|
||||
}
|
||||
|
||||
c.muChans.Lock()
|
||||
delete(c.chans, string(certPEMBlock))
|
||||
c.muChans.Unlock()
|
||||
|
||||
return errC
|
||||
}
|
||||
|
||||
func (c *challengeTLSALPN) CleanUp(domain, token, keyAuth string) error {
|
||||
log.WithoutContext().WithField(log.ProviderName, "acme").
|
||||
// CleanUp cleans the challenges when certificate is obtained.
|
||||
func (c *ChallengeTLSALPN) CleanUp(domain, _, keyAuth string) error {
|
||||
log.WithoutContext().WithField(log.ProviderName, providerNameALPN).
|
||||
Debugf("TLS Challenge CleanUp temp certificate for %s", domain)
|
||||
|
||||
return c.Store.RemoveTLSChallenge(domain)
|
||||
c.muCerts.Lock()
|
||||
delete(c.certs, keyAuth)
|
||||
conf := createMessage(c.certs)
|
||||
c.muCerts.Unlock()
|
||||
|
||||
c.configurationChan <- conf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTLSALPNCertificate Get the temp certificate for ACME TLS-ALPN-O1 challenge.
|
||||
func (p *Provider) GetTLSALPNCertificate(domain string) (*tls.Certificate, error) {
|
||||
cert, err := p.ChallengeStore.GetTLSChallenge(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cert == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
// Init the provider.
|
||||
func (c *ChallengeTLSALPN) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
|
||||
func (c *ChallengeTLSALPN) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
|
||||
c.configurationChan = configurationChan
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListenConfiguration sets a new Configuration into the configurationChan.
|
||||
func (c *ChallengeTLSALPN) ListenConfiguration(conf dynamic.Configuration) {
|
||||
for _, certificate := range conf.TLS.Certificates {
|
||||
if !containsACMETLS1(certificate.Stores) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.muChans.Lock()
|
||||
if _, ok := c.chans[certificate.CertFile.String()]; ok {
|
||||
close(c.chans[certificate.CertFile.String()])
|
||||
}
|
||||
c.muChans.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func createMessage(certs map[string]*Certificate) dynamic.Message {
|
||||
conf := dynamic.Message{
|
||||
ProviderName: providerNameALPN,
|
||||
Configuration: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, cert := range certs {
|
||||
certConf := &traefiktls.CertAndStores{
|
||||
Certificate: traefiktls.Certificate{
|
||||
CertFile: traefiktls.FileOrContent(cert.Certificate),
|
||||
KeyFile: traefiktls.FileOrContent(cert.Key),
|
||||
},
|
||||
Stores: []string{tlsalpn01.ACMETLS1Protocol},
|
||||
}
|
||||
conf.Configuration.TLS.Certificates = append(conf.Configuration.TLS.Certificates, certConf)
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func containsACMETLS1(stores []string) bool {
|
||||
for _, store := range stores {
|
||||
if store == tlsalpn01.ACMETLS1Protocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package acme
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
@ -171,112 +170,3 @@ func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertA
|
|||
|
||||
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 *LocalChallengeStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
s.storedData.HTTPChallenges = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; !ok {
|
||||
return nil, fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
|
||||
result, ok := s.storedData.HTTPChallenges[token][domain]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot find challenge for token %v", token)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetHTTPChallengeToken Set the http challenge token in the store.
|
||||
func (s *LocalChallengeStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
s.storedData.HTTPChallenges = map[string]map[string][]byte{}
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; !ok {
|
||||
s.storedData.HTTPChallenges[token] = map[string][]byte{}
|
||||
}
|
||||
|
||||
s.storedData.HTTPChallenges[token][domain] = keyAuth
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveHTTPChallengeToken Remove the http challenge token in the store.
|
||||
func (s *LocalChallengeStore) RemoveHTTPChallengeToken(token, domain string) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.HTTPChallenges == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := s.storedData.HTTPChallenges[token]; ok {
|
||||
delete(s.storedData.HTTPChallenges[token], domain)
|
||||
if len(s.storedData.HTTPChallenges[token]) == 0 {
|
||||
delete(s.storedData.HTTPChallenges, token)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTLSChallenge Add a certificate to the ACME TLS-ALPN-01 certificates storage.
|
||||
func (s *LocalChallengeStore) AddTLSChallenge(domain string, cert *Certificate) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.TLSChallenges == nil {
|
||||
s.storedData.TLSChallenges = make(map[string]*Certificate)
|
||||
}
|
||||
|
||||
s.storedData.TLSChallenges[domain] = cert
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTLSChallenge Get a certificate from the ACME TLS-ALPN-01 certificates storage.
|
||||
func (s *LocalChallengeStore) GetTLSChallenge(domain string) (*Certificate, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.TLSChallenges == nil {
|
||||
s.storedData.TLSChallenges = make(map[string]*Certificate)
|
||||
}
|
||||
|
||||
return s.storedData.TLSChallenges[domain], nil
|
||||
}
|
||||
|
||||
// RemoveTLSChallenge Remove a certificate from the ACME TLS-ALPN-01 certificates storage.
|
||||
func (s *LocalChallengeStore) RemoveTLSChallenge(domain string) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData.TLSChallenges == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(s.storedData.TLSChallenges, domain)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -82,9 +82,12 @@ 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"`
|
||||
ChallengeStore ChallengeStore
|
||||
ResolverName string
|
||||
Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"`
|
||||
|
||||
TLSChallengeProvider challenge.Provider
|
||||
HTTPChallengeProvider challenge.Provider
|
||||
|
||||
certificates []*CertAndStore
|
||||
account *Account
|
||||
client *lego.Client
|
||||
|
@ -285,7 +288,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
||||
logger.Debug("Using HTTP Challenge provider.")
|
||||
|
||||
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.ChallengeStore})
|
||||
err = client.Challenge.SetHTTP01Provider(p.HTTPChallengeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -294,7 +297,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
if p.TLSChallenge != nil {
|
||||
logger.Debug("Using TLS Challenge provider.")
|
||||
|
||||
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.ChallengeStore})
|
||||
err = client.Challenge.SetTLSALPN01Provider(p.TLSChallengeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -6,12 +6,6 @@ type StoredData struct {
|
|||
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.
|
||||
type Store interface {
|
||||
GetAccount(string) (*Account, error)
|
||||
|
@ -19,14 +13,3 @@ type Store interface {
|
|||
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
|
||||
|
||||
AddTLSChallenge(domain string, cert *Certificate) error
|
||||
GetTLSChallenge(domain string) (*Certificate, error)
|
||||
RemoveTLSChallenge(domain string) error
|
||||
}
|
||||
|
|
|
@ -73,11 +73,39 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati
|
|||
i.redirection(ctx, cfg)
|
||||
i.serverTransport(cfg)
|
||||
|
||||
i.acme(cfg)
|
||||
|
||||
cfg.HTTP.Services["noop"] = &dynamic.Service{}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (i *Provider) acme(cfg *dynamic.Configuration) {
|
||||
var eps []string
|
||||
|
||||
uniq := map[string]struct{}{}
|
||||
for _, resolver := range i.staticCfg.CertificatesResolvers {
|
||||
if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
|
||||
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok {
|
||||
eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
|
||||
uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(eps) > 0 {
|
||||
rt := &dynamic.Router{
|
||||
Rule: "PathPrefix(`/.well-known/acme-challenge/`)",
|
||||
EntryPoints: eps,
|
||||
Service: "acme-http@internal",
|
||||
Priority: math.MaxInt32,
|
||||
}
|
||||
|
||||
cfg.HTTP.Routers["acme-http"] = rt
|
||||
cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
|
||||
for name, ep := range i.staticCfg.EntryPoints {
|
||||
if ep.HTTP.Redirections == nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue