149 lines
5.1 KiB
Go
149 lines
5.1 KiB
Go
// Copyright 2015 Matthew Holt
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package certmagic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"path/filepath"
|
|
|
|
"github.com/xenolf/lego/challenge"
|
|
"github.com/xenolf/lego/challenge/tlsalpn01"
|
|
)
|
|
|
|
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
|
|
// an existing listener and our custom, in-memory certificate cache.
|
|
type tlsALPNSolver struct {
|
|
certCache *Cache
|
|
}
|
|
|
|
// Present adds the challenge certificate to the cache.
|
|
func (s tlsALPNSolver) Present(domain, token, keyAuth string) error {
|
|
cert, err := tlsalpn01.ChallengeCert(domain, keyAuth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
certHash := hashCertificateChain(cert.Certificate)
|
|
s.certCache.mu.Lock()
|
|
s.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
|
|
Certificate: *cert,
|
|
Names: []string{domain},
|
|
Hash: certHash, // perhaps not necesssary
|
|
}
|
|
s.certCache.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// CleanUp removes the challenge certificate from the cache.
|
|
func (s tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
|
|
s.certCache.mu.Lock()
|
|
delete(s.certCache.cache, domain)
|
|
s.certCache.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// tlsALPNCertKeyName returns the key to use when caching a cert
|
|
// for use with the TLS-ALPN ACME challenge. It is simply to help
|
|
// avoid conflicts (although at time of writing, there shouldn't
|
|
// be, since the cert cache is keyed by hash of certificate chain).
|
|
func tlsALPNCertKeyName(sniName string) string {
|
|
return sniName + ":acme-tls-alpn"
|
|
}
|
|
|
|
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
|
|
// to be solved by an instance other than the one which initiated it.
|
|
// This is useful behind load balancers or in other cluster/fleet
|
|
// configurations. The only requirement is that the instance which
|
|
// initiates the challenge shares the same storage and locker with
|
|
// the others in the cluster. The storage backing the certificate
|
|
// cache in distributedSolver.config is crucial.
|
|
//
|
|
// Obviously, the instance which completes the challenge must be
|
|
// serving on the HTTPChallengePort for the HTTP-01 challenge or the
|
|
// TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
|
|
// the packets port-forwarded) to receive and handle the request. The
|
|
// server which receives the challenge must handle it by checking to
|
|
// see if the challenge token exists in storage, and if so, decode it
|
|
// and use it to serve up the correct response. HTTPChallengeHandler
|
|
// in this package as well as the GetCertificate method implemented
|
|
// by a Config support and even require this behavior.
|
|
//
|
|
// In short: the only two requirements for cluster operation are
|
|
// sharing sync and storage, and using the facilities provided by
|
|
// this package for solving the challenges.
|
|
type distributedSolver struct {
|
|
// The config with a certificate cache
|
|
// with a reference to the storage to
|
|
// use which is shared among all the
|
|
// instances in the cluster - REQUIRED.
|
|
config *Config
|
|
|
|
// Since the distributedSolver is only a
|
|
// wrapper over an actual solver, place
|
|
// the actual solver here.
|
|
providerServer challenge.Provider
|
|
}
|
|
|
|
// Present invokes the underlying solver's Present method
|
|
// and also stores domain, token, and keyAuth to the storage
|
|
// backing the certificate cache of dhs.config.
|
|
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
|
|
if dhs.providerServer != nil {
|
|
err := dhs.providerServer.Present(domain, token, keyAuth)
|
|
if err != nil {
|
|
return fmt.Errorf("presenting with standard provider server: %v", err)
|
|
}
|
|
}
|
|
|
|
infoBytes, err := json.Marshal(challengeInfo{
|
|
Domain: domain,
|
|
Token: token,
|
|
KeyAuth: keyAuth,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dhs.config.certCache.storage.Store(dhs.challengeTokensKey(domain), infoBytes)
|
|
}
|
|
|
|
// CleanUp invokes the underlying solver's CleanUp method
|
|
// and also cleans up any assets saved to storage.
|
|
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
|
|
if dhs.providerServer != nil {
|
|
err := dhs.providerServer.CleanUp(domain, token, keyAuth)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
|
|
}
|
|
}
|
|
return dhs.config.certCache.storage.Delete(dhs.challengeTokensKey(domain))
|
|
}
|
|
|
|
// challengeTokensPrefix returns the key prefix for challenge info.
|
|
func (dhs distributedSolver) challengeTokensPrefix() string {
|
|
return filepath.Join(StorageKeys.CAPrefix(dhs.config.CA), "challenge_tokens")
|
|
}
|
|
|
|
// challengeTokensKey returns the key to use to store and access
|
|
// challenge info for domain.
|
|
func (dhs distributedSolver) challengeTokensKey(domain string) string {
|
|
return filepath.Join(dhs.challengeTokensPrefix(), StorageKeys.safe(domain)+".json")
|
|
}
|
|
|
|
type challengeInfo struct {
|
|
Domain, Token, KeyAuth string
|
|
}
|