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
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/coreos/go-systemd/daemon"
|
"github.com/coreos/go-systemd/daemon"
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/traefik/paerser/cli"
|
"github.com/traefik/paerser/cli"
|
||||||
"github.com/traefik/traefik/v2/autogen/genstatic"
|
"github.com/traefik/traefik/v2/autogen/genstatic"
|
||||||
|
@ -181,7 +182,16 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
|
|
||||||
tlsManager := traefiktls.NewManager()
|
tlsManager := traefiktls.NewManager()
|
||||||
|
|
||||||
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
|
httpChallengeProvider := acme.NewChallengeHTTP()
|
||||||
|
|
||||||
|
tlsChallengeProvider := acme.NewChallengeTLSALPN(time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration))
|
||||||
|
|
||||||
|
err = providerAggregator.AddProvider(tlsChallengeProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider)
|
||||||
|
|
||||||
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
|
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -214,7 +224,16 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
accessLog := setupAccessLog(staticConfiguration.AccessLog)
|
||||||
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
|
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager)
|
|
||||||
|
var acmeHTTPHandler http.Handler
|
||||||
|
for _, p := range acmeProviders {
|
||||||
|
if p != nil && p.HTTPChallenge != nil {
|
||||||
|
acmeHTTPHandler = httpChallengeProvider
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler)
|
||||||
|
|
||||||
client, plgs, devPlugin, err := initPlugins(staticConfiguration)
|
client, plgs, devPlugin, err := initPlugins(staticConfiguration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -264,7 +283,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
roundTripperManager.Update(conf.HTTP.ServersTransports)
|
roundTripperManager.Update(conf.HTTP.ServersTransports)
|
||||||
})
|
})
|
||||||
|
|
||||||
watcher.AddListener(switchRouter(routerFactory, acmeProviders, serverEntryPointsTCP, serverEntryPointsUDP, aviator))
|
watcher.AddListener(switchRouter(routerFactory, serverEntryPointsTCP, serverEntryPointsUDP, aviator))
|
||||||
|
|
||||||
watcher.AddListener(func(conf dynamic.Configuration) {
|
watcher.AddListener(func(conf dynamic.Configuration) {
|
||||||
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
|
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
|
||||||
|
@ -277,6 +296,8 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watcher.AddListener(tlsChallengeProvider.ListenConfiguration)
|
||||||
|
|
||||||
resolverNames := map[string]struct{}{}
|
resolverNames := map[string]struct{}{}
|
||||||
for _, p := range acmeProviders {
|
for _, p := range acmeProviders {
|
||||||
resolverNames[p.ResolverName] = struct{}{}
|
resolverNames[p.ResolverName] = struct{}{}
|
||||||
|
@ -298,21 +319,12 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil
|
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchRouter(routerFactory *server.RouterFactory, acmeProviders []*acme.Provider, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints, aviator *pilot.Pilot) func(conf dynamic.Configuration) {
|
func switchRouter(routerFactory *server.RouterFactory, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints, aviator *pilot.Pilot) func(conf dynamic.Configuration) {
|
||||||
return func(conf dynamic.Configuration) {
|
return func(conf dynamic.Configuration) {
|
||||||
rtConf := runtime.NewConfig(conf)
|
rtConf := runtime.NewConfig(conf)
|
||||||
|
|
||||||
routers, udpRouters := routerFactory.CreateRouters(rtConf)
|
routers, udpRouters := routerFactory.CreateRouters(rtConf)
|
||||||
|
|
||||||
for entryPointName, rt := range routers {
|
|
||||||
for _, p := range acmeProviders {
|
|
||||||
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == entryPointName {
|
|
||||||
rt.HTTPHandler(p.CreateHandler(rt.GetHTTPHandler()))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if aviator != nil {
|
if aviator != nil {
|
||||||
aviator.SetRuntimeConfiguration(rtConf)
|
aviator.SetRuntimeConfiguration(rtConf)
|
||||||
}
|
}
|
||||||
|
@ -323,8 +335,7 @@ func switchRouter(routerFactory *server.RouterFactory, acmeProviders []*acme.Pro
|
||||||
}
|
}
|
||||||
|
|
||||||
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration.
|
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration.
|
||||||
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider {
|
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider) []*acme.Provider {
|
||||||
challengeStore := acme.NewLocalChallengeStore()
|
|
||||||
localStores := map[string]*acme.LocalStore{}
|
localStores := map[string]*acme.LocalStore{}
|
||||||
|
|
||||||
var resolvers []*acme.Provider
|
var resolvers []*acme.Provider
|
||||||
|
@ -335,10 +346,11 @@ func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.Pr
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &acme.Provider{
|
p := &acme.Provider{
|
||||||
Configuration: resolver.ACME,
|
Configuration: resolver.ACME,
|
||||||
Store: localStores[resolver.ACME.Storage],
|
Store: localStores[resolver.ACME.Storage],
|
||||||
ChallengeStore: challengeStore,
|
ResolverName: name,
|
||||||
ResolverName: name,
|
HTTPChallengeProvider: httpChallengeProvider,
|
||||||
|
TLSChallengeProvider: tlsChallengeProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := providerAggregator.AddProvider(p); err != nil {
|
if err := providerAggregator.AddProvider(p); err != nil {
|
||||||
|
@ -348,15 +360,12 @@ func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.Pr
|
||||||
|
|
||||||
p.SetTLSManager(tlsManager)
|
p.SetTLSManager(tlsManager)
|
||||||
|
|
||||||
if p.TLSChallenge != nil {
|
|
||||||
tlsManager.TLSAlpnGetter = p.GetTLSALPNCertificate
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SetConfigListenerChan(make(chan dynamic.Configuration))
|
p.SetConfigListenerChan(make(chan dynamic.Configuration))
|
||||||
|
|
||||||
resolvers = append(resolvers, p)
|
resolvers = append(resolvers, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvers
|
return resolvers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,85 +2,126 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"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/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/safe"
|
"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 {
|
// NewChallengeHTTP creates a new ChallengeHTTP.
|
||||||
Store ChallengeStore
|
func NewChallengeHTTP() *ChallengeHTTP {
|
||||||
|
return &ChallengeHTTP{
|
||||||
|
httpChallenges: make(map[string]map[string][]byte),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present presents a challenge to obtain new ACME certificate.
|
// Present presents a challenge to obtain new ACME certificate.
|
||||||
func (c *challengeHTTP) Present(domain, token, keyAuth string) error {
|
func (c *ChallengeHTTP) Present(domain, token, keyAuth string) error {
|
||||||
return c.Store.SetHTTPChallengeToken(token, domain, []byte(keyAuth))
|
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.
|
// CleanUp cleans the challenges when certificate is obtained.
|
||||||
func (c *challengeHTTP) CleanUp(domain, token, keyAuth string) error {
|
func (c *ChallengeHTTP) CleanUp(domain, token, _ string) error {
|
||||||
return c.Store.RemoveHTTPChallengeToken(token, domain)
|
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.
|
// 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
|
return 60 * time.Second, 5 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHandler creates a HTTP handler to expose the token for the HTTP challenge.
|
func (c *ChallengeHTTP) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
func (p *Provider) CreateHandler(notFoundHandler http.Handler) http.Handler {
|
ctx := log.With(req.Context(), log.Str(log.ProviderName, "acme"))
|
||||||
router := mux.NewRouter().SkipClean(true)
|
logger := log.FromContext(ctx)
|
||||||
router.NotFoundHandler = notFoundHandler
|
|
||||||
|
|
||||||
router.Methods(http.MethodGet).
|
token, err := getPathParam(req.URL)
|
||||||
Path(http01.ChallengePath("{token}")).
|
if err != nil {
|
||||||
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
logger.Errorf("Unable to get token: %v.", err)
|
||||||
vars := mux.Vars(req)
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, p.ResolverName+".acme"))
|
if token != "" {
|
||||||
logger := log.FromContext(ctx)
|
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 {
|
tokenValue := c.getTokenValue(ctx, token, domain)
|
||||||
domain, _, err := net.SplitHostPort(req.Host)
|
if len(tokenValue) > 0 {
|
||||||
if err != nil {
|
rw.WriteHeader(http.StatusOK)
|
||||||
logger.Debugf("Unable to split host and port: %v. Fallback to request host.", err)
|
_, err = rw.Write(tokenValue)
|
||||||
domain = req.Host
|
if err != nil {
|
||||||
}
|
logger.Errorf("Unable to write token: %v", err)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 := 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
|
var result []byte
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
var err error
|
c.lock.RLock()
|
||||||
result, err = store.GetHTTPChallengeToken(token, domain)
|
defer c.lock.RUnlock()
|
||||||
return err
|
|
||||||
|
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) {
|
notify := func(err error, time time.Duration) {
|
||||||
|
@ -97,3 +138,14 @@ func getTokenValue(ctx context.Context, token, domain string, store ChallengeSto
|
||||||
|
|
||||||
return result
|
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
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
"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/log"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/safe"
|
||||||
|
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
|
||||||
"github.com/traefik/traefik/v2/pkg/types"
|
"github.com/traefik/traefik/v2/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ challenge.Provider = (*challengeTLSALPN)(nil)
|
const providerNameALPN = "tlsalpn.acme"
|
||||||
|
|
||||||
type challengeTLSALPN struct {
|
// ChallengeTLSALPN TLSALPN challenge provider implements challenge.Provider.
|
||||||
Store ChallengeStore
|
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 {
|
// NewChallengeTLSALPN creates a new ChallengeTLSALPN.
|
||||||
log.WithoutContext().WithField(log.ProviderName, "acme").
|
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)
|
Debugf("TLS Challenge Present temp certificate for %s", domain)
|
||||||
|
|
||||||
certPEMBlock, keyPEMBlock, err := tlsalpn01.ChallengeBlocks(domain, keyAuth)
|
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}}
|
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 {
|
// CleanUp cleans the challenges when certificate is obtained.
|
||||||
log.WithoutContext().WithField(log.ProviderName, "acme").
|
func (c *ChallengeTLSALPN) CleanUp(domain, _, keyAuth string) error {
|
||||||
|
log.WithoutContext().WithField(log.ProviderName, providerNameALPN).
|
||||||
Debugf("TLS Challenge CleanUp temp certificate for %s", domain)
|
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.
|
// Init the provider.
|
||||||
func (p *Provider) GetTLSALPNCertificate(domain string) (*tls.Certificate, error) {
|
func (c *ChallengeTLSALPN) Init() error {
|
||||||
cert, err := p.ChallengeStore.GetTLSChallenge(domain)
|
return nil
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
// 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 {
|
||||||
if cert == nil {
|
c.configurationChan = configurationChan
|
||||||
return nil, nil
|
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.Key)
|
|
||||||
if err != nil {
|
// ListenConfiguration sets a new Configuration into the configurationChan.
|
||||||
return nil, err
|
func (c *ChallengeTLSALPN) ListenConfiguration(conf dynamic.Configuration) {
|
||||||
}
|
for _, certificate := range conf.TLS.Certificates {
|
||||||
|
if !containsACMETLS1(certificate.Stores) {
|
||||||
return &certificate, nil
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -171,112 +170,3 @@ func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertA
|
||||||
|
|
||||||
return nil
|
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.
|
// Provider holds configurations of the provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
*Configuration
|
*Configuration
|
||||||
ResolverName string
|
ResolverName string
|
||||||
Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"`
|
Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"`
|
||||||
ChallengeStore ChallengeStore
|
|
||||||
|
TLSChallengeProvider challenge.Provider
|
||||||
|
HTTPChallengeProvider challenge.Provider
|
||||||
|
|
||||||
certificates []*CertAndStore
|
certificates []*CertAndStore
|
||||||
account *Account
|
account *Account
|
||||||
client *lego.Client
|
client *lego.Client
|
||||||
|
@ -285,7 +288,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
||||||
if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
||||||
logger.Debug("Using HTTP Challenge provider.")
|
logger.Debug("Using HTTP Challenge provider.")
|
||||||
|
|
||||||
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.ChallengeStore})
|
err = client.Challenge.SetHTTP01Provider(p.HTTPChallengeProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -294,7 +297,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
||||||
if p.TLSChallenge != nil {
|
if p.TLSChallenge != nil {
|
||||||
logger.Debug("Using TLS Challenge provider.")
|
logger.Debug("Using TLS Challenge provider.")
|
||||||
|
|
||||||
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.ChallengeStore})
|
err = client.Challenge.SetTLSALPN01Provider(p.TLSChallengeProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,6 @@ type StoredData struct {
|
||||||
Certificates []*CertAndStore
|
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 {
|
type Store interface {
|
||||||
GetAccount(string) (*Account, error)
|
GetAccount(string) (*Account, error)
|
||||||
|
@ -19,14 +13,3 @@ type Store interface {
|
||||||
GetCertificates(string) ([]*CertAndStore, error)
|
GetCertificates(string) ([]*CertAndStore, error)
|
||||||
SaveCertificates(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.redirection(ctx, cfg)
|
||||||
i.serverTransport(cfg)
|
i.serverTransport(cfg)
|
||||||
|
|
||||||
|
i.acme(cfg)
|
||||||
|
|
||||||
cfg.HTTP.Services["noop"] = &dynamic.Service{}
|
cfg.HTTP.Services["noop"] = &dynamic.Service{}
|
||||||
|
|
||||||
return cfg
|
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) {
|
func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
|
||||||
for name, ep := range i.staticCfg.EntryPoints {
|
for name, ep := range i.staticCfg.EntryPoints {
|
||||||
if ep.HTTP.Redirections == nil {
|
if ep.HTTP.Redirections == nil {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/provider"
|
"github.com/traefik/traefik/v2/pkg/server/provider"
|
||||||
|
@ -77,7 +78,13 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.TLS != nil {
|
if configuration.TLS != nil {
|
||||||
conf.TLS.Certificates = append(conf.TLS.Certificates, configuration.TLS.Certificates...)
|
for _, cert := range configuration.TLS.Certificates {
|
||||||
|
if containsACMETLS1(cert.Stores) && pvd != "tlsalpn.acme" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.TLS.Certificates = append(conf.TLS.Certificates, cert)
|
||||||
|
}
|
||||||
|
|
||||||
for key, store := range configuration.TLS.Stores {
|
for key, store := range configuration.TLS.Stores {
|
||||||
if key != "default" {
|
if key != "default" {
|
||||||
|
@ -160,3 +167,13 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containsACMETLS1(stores []string) bool {
|
||||||
|
for _, store := range stores {
|
||||||
|
if store == tlsalpn01.ACMETLS1Protocol {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
|
@ -122,6 +123,55 @@ func Test_mergeConfiguration(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_mergeConfiguration_tlsCertificates(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
given dynamic.Configurations
|
||||||
|
expected []*tls.CertAndStores
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Skip temp certificates from another provider than tlsalpn",
|
||||||
|
given: dynamic.Configurations{
|
||||||
|
"provider-1": &dynamic.Configuration{
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Certificates: []*tls.CertAndStores{
|
||||||
|
{Certificate: tls.Certificate{}, Stores: []string{tlsalpn01.ACMETLS1Protocol}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Allows tlsalpn provider to give certificates",
|
||||||
|
given: dynamic.Configurations{
|
||||||
|
"tlsalpn.acme": &dynamic.Configuration{
|
||||||
|
TLS: &dynamic.TLSConfiguration{
|
||||||
|
Certificates: []*tls.CertAndStores{{
|
||||||
|
Certificate: tls.Certificate{CertFile: "foo", KeyFile: "bar"},
|
||||||
|
Stores: []string{tlsalpn01.ACMETLS1Protocol},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*tls.CertAndStores{{
|
||||||
|
Certificate: tls.Certificate{CertFile: "foo", KeyFile: "bar"},
|
||||||
|
Stores: []string{tlsalpn01.ACMETLS1Protocol},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual := mergeConfiguration(test.given, []string{"defaultEP"})
|
||||||
|
assert.Equal(t, test.expected, actual.TLS.Certificates)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
|
@ -50,7 +50,7 @@ func TestReuseService(t *testing.T) {
|
||||||
|
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
@ -186,7 +186,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
||||||
|
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
@ -227,7 +227,7 @@ func TestInternalServices(t *testing.T) {
|
||||||
|
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||||
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
|
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
|
||||||
|
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceManager interface {
|
type serviceManager interface {
|
||||||
|
@ -22,22 +20,19 @@ type InternalHandlers struct {
|
||||||
rest http.Handler
|
rest http.Handler
|
||||||
prometheus http.Handler
|
prometheus http.Handler
|
||||||
ping http.Handler
|
ping http.Handler
|
||||||
|
acmeHTTP http.Handler
|
||||||
serviceManager
|
serviceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInternalHandlers creates a new InternalHandlers.
|
// NewInternalHandlers creates a new InternalHandlers.
|
||||||
func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Handler, configuration *runtime.Configuration, rest, metricsHandler, pingHandler, dashboard http.Handler, next serviceManager) *InternalHandlers {
|
func NewInternalHandlers(next serviceManager, apiHandler, rest, metricsHandler, pingHandler, dashboard, acmeHTTP http.Handler) *InternalHandlers {
|
||||||
var apiHandler http.Handler
|
|
||||||
if api != nil {
|
|
||||||
apiHandler = api(configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &InternalHandlers{
|
return &InternalHandlers{
|
||||||
api: apiHandler,
|
api: apiHandler,
|
||||||
dashboard: dashboard,
|
dashboard: dashboard,
|
||||||
rest: rest,
|
rest: rest,
|
||||||
prometheus: metricsHandler,
|
prometheus: metricsHandler,
|
||||||
ping: pingHandler,
|
ping: pingHandler,
|
||||||
|
acmeHTTP: acmeHTTP,
|
||||||
serviceManager: next,
|
serviceManager: next,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +58,12 @@ func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
|
||||||
rw.WriteHeader(http.StatusTeapot)
|
rw.WriteHeader(http.StatusTeapot)
|
||||||
}), nil
|
}), nil
|
||||||
|
|
||||||
|
case "acme-http@internal":
|
||||||
|
if m.acmeHTTP == nil {
|
||||||
|
return nil, errors.New("HTTP challenge is not enabled")
|
||||||
|
}
|
||||||
|
return m.acmeHTTP, nil
|
||||||
|
|
||||||
case "api@internal":
|
case "api@internal":
|
||||||
if m.api == nil {
|
if m.api == nil {
|
||||||
return nil, errors.New("api is not enabled")
|
return nil, errors.New("api is not enabled")
|
||||||
|
|
|
@ -21,16 +21,18 @@ type ManagerFactory struct {
|
||||||
dashboardHandler http.Handler
|
dashboardHandler http.Handler
|
||||||
metricsHandler http.Handler
|
metricsHandler http.Handler
|
||||||
pingHandler http.Handler
|
pingHandler http.Handler
|
||||||
|
acmeHTTPHandler http.Handler
|
||||||
|
|
||||||
routinesPool *safe.Pool
|
routinesPool *safe.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManagerFactory creates a new ManagerFactory.
|
// NewManagerFactory creates a new ManagerFactory.
|
||||||
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager) *ManagerFactory {
|
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory {
|
||||||
factory := &ManagerFactory{
|
factory := &ManagerFactory{
|
||||||
metricsRegistry: metricsRegistry,
|
metricsRegistry: metricsRegistry,
|
||||||
routinesPool: routinesPool,
|
routinesPool: routinesPool,
|
||||||
roundTripperManager: roundTripperManager,
|
roundTripperManager: roundTripperManager,
|
||||||
|
acmeHTTPHandler: acmeHTTPHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
if staticConfiguration.API != nil {
|
if staticConfiguration.API != nil {
|
||||||
|
@ -62,5 +64,11 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
|
||||||
// Build creates a service manager.
|
// Build creates a service manager.
|
||||||
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
|
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
|
||||||
svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager)
|
svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager)
|
||||||
return NewInternalHandlers(f.api, configuration, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, svcManager)
|
|
||||||
|
var apiHandler http.Handler
|
||||||
|
if f.api != nil {
|
||||||
|
apiHandler = f.api(configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewInternalHandlers(svcManager, apiHandler, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, f.acmeHTTPHandler)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,11 @@ var DefaultTLSOptions = Options{}
|
||||||
|
|
||||||
// Manager is the TLS option/store/configuration factory.
|
// Manager is the TLS option/store/configuration factory.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
storesConfig map[string]Store
|
storesConfig map[string]Store
|
||||||
stores map[string]*CertificateStore
|
stores map[string]*CertificateStore
|
||||||
configs map[string]Options
|
configs map[string]Options
|
||||||
certs []*CertAndStores
|
certs []*CertAndStores
|
||||||
TLSAlpnGetter func(string) (*tls.Certificate, error)
|
lock sync.RWMutex
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new Manager.
|
// NewManager creates a new Manager.
|
||||||
|
@ -95,6 +94,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
store := m.getStore(storeName)
|
store := m.getStore(storeName)
|
||||||
|
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tlsConfig, err = buildTLSConfig(config)
|
tlsConfig, err = buildTLSConfig(config)
|
||||||
|
@ -106,15 +106,13 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
domainToCheck := types.CanonicalDomain(clientHello.ServerName)
|
||||||
|
|
||||||
if m.TLSAlpnGetter != nil && isACMETLS(clientHello) {
|
if isACMETLS(clientHello) {
|
||||||
cert, err := m.TLSAlpnGetter(domainToCheck)
|
certificate := acmeTLSStore.GetBestCertificate(clientHello)
|
||||||
if err != nil {
|
if certificate == nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("no certificate for TLSALPN challenge: %s", domainToCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cert != nil {
|
return certificate, nil
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bestCertificate := store.GetBestCertificate(clientHello)
|
bestCertificate := store.GetBestCertificate(clientHello)
|
||||||
|
|
Loading…
Reference in a new issue