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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue