fix application bug

This commit is contained in:
Jeff
2019-05-13 11:19:18 +08:00
committed by zryfish
parent 996d6fe4c5
commit 5462f51e65
717 changed files with 87703 additions and 53426 deletions

View File

@@ -193,9 +193,8 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
r := recover()
if err != nil || r != nil {
for _, fn := range i.OnRestartFailed {
err2 := fn()
if err2 != nil {
log.Printf("[ERROR] Restart failed callback returned error: %v", err2)
if err := fn(); err != nil {
log.Printf("[ERROR] Restart failed callback returned error: %v", err)
}
}
if err != nil {
@@ -781,6 +780,10 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
}
}
inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
}
for _, s := range inst.servers {
inst.wg.Add(2)
stopWg.Add(2)
func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
@@ -799,9 +802,7 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
}()
errChan <- s.ServePacket(pc)
}()
}(s, ln, pc, inst)
inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
}(s.server, s.listener, s.packet, inst)
}
// Log errors that may be returned from Serve() calls,

View File

@@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
@@ -43,20 +44,20 @@ import (
func init() {
caddy.TrapSignals()
setVersion()
flag.BoolVar(&certmagic.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.CA, "ca", certmagic.CA, "URL to certificate authority's ACME server directory")
flag.StringVar(&certmagic.DefaultServerName, "default-sni", certmagic.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate")
flag.BoolVar(&certmagic.DisableHTTPChallenge, "disable-http-challenge", certmagic.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
flag.BoolVar(&certmagic.Default.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.Default.CA, "ca", certmagic.Default.CA, "URL to certificate authority's ACME server directory")
flag.StringVar(&certmagic.Default.DefaultServerName, "default-sni", certmagic.Default.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate")
flag.BoolVar(&certmagic.Default.DisableHTTPChallenge, "disable-http-challenge", certmagic.Default.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.Default.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.Default.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.StringVar(&envFile, "env", "", "Path to file with environment variables to load in KEY=VALUE format")
flag.BoolVar(&printEnv, "env", false, "Enable to print environment variables")
flag.StringVar(&envFile, "envfile", "", "Path to file with environment variables to load in KEY=VALUE format")
flag.BoolVar(&fromJSON, "json-to-caddyfile", false, "From JSON stdin to Caddyfile stdout")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&certmagic.Email, "email", "", "Default ACME CA account email address")
flag.StringVar(&certmagic.Default.Email, "email", "", "Default ACME CA account email address")
flag.DurationVar(&certmagic.HTTPTimeout, "catimeout", certmagic.HTTPTimeout, "Default ACME CA HTTP timeout")
flag.StringVar(&logfile, "log", "", "Process log file")
flag.IntVar(&logRollMB, "log-roll-mb", 100, "Roll process log when it reaches this many megabytes (0 to disable rolling)")
@@ -77,9 +78,12 @@ func init() {
func Run() {
flag.Parse()
module := getBuildModule()
cleanModVersion := strings.TrimPrefix(module.Version, "v")
caddy.AppName = appName
caddy.AppVersion = appVersion
certmagic.UserAgent = appName + "/" + appVersion
caddy.AppVersion = module.Version
certmagic.UserAgent = appName + "/" + cleanModVersion
// Set up process log before anything bad happens
switch logfile {
@@ -117,6 +121,12 @@ func Run() {
mustLogFatalf("%v", err)
}
if printEnv {
for _, v := range os.Environ() {
fmt.Println(v)
}
}
// initialize telemetry client
if EnableTelemetry {
err := initTelemetry()
@@ -137,9 +147,11 @@ func Run() {
os.Exit(0)
}
if version {
fmt.Printf("%s %s (unofficial)\n", appName, appVersion)
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
if module.Sum != "" {
// a build with a known version will also have a checksum
fmt.Printf("Caddy %s (%s)\n", module.Version, module.Sum)
} else {
fmt.Println(module.Version)
}
os.Exit(0)
}
@@ -184,7 +196,7 @@ func Run() {
}
// Begin telemetry (these are no-ops if telemetry disabled)
telemetry.Set("caddy_version", appVersion)
telemetry.Set("caddy_version", module.Version)
telemetry.Set("num_listeners", len(instance.Servers()))
telemetry.Set("server_type", serverType)
telemetry.Set("os", runtime.GOOS)
@@ -265,25 +277,25 @@ func defaultLoader(serverType string) (caddy.Input, error) {
}, nil
}
// setVersion figures out the version information
// based on variables set by -ldflags.
func setVersion() {
// A development build is one that's not at a tag or has uncommitted changes
devBuild = gitTag == "" || gitShortStat != ""
if buildDate != "" {
buildDate = " " + buildDate
}
// Only set the appVersion if -ldflags was used
if gitNearestTag != "" || gitTag != "" {
if devBuild && gitNearestTag != "" {
appVersion = fmt.Sprintf("%s (+%s%s)",
strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate)
} else if gitTag != "" {
appVersion = strings.TrimPrefix(gitTag, "v")
// getBuildModule returns the build info of Caddy
// from debug.BuildInfo (requires Go modules). If
// no version information is available, a non-nil
// value will still be returned, but with an
// unknown version.
func getBuildModule() *debug.Module {
bi, ok := debug.ReadBuildInfo()
if ok {
// The recommended way to build Caddy involves
// creating a separate main module, which
// preserves caddy a read-only dependency
// TODO: track related Go issue: https://github.com/golang/go/issues/29228
for _, mod := range bi.Deps {
if mod.Path == "github.com/mholt/caddy" {
return mod
}
}
}
return &debug.Module{Version: "unknown"}
}
func checkJSONCaddyfile() {
@@ -580,22 +592,10 @@ var (
toJSON bool
version bool
plugins bool
printEnv bool
validate bool
disabledMetrics string
)
// Build information obtained with the help of -ldflags
var (
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup
buildDate string // date -u
gitTag string // git describe --exact-match HEAD 2> /dev/null
gitNearestTag string // git describe --abbrev=0 --tags HEAD
gitCommit string // git rev-parse HEAD
gitShortStat string // git diff-index --shortstat
gitFilesModified string // git diff-index --name-only HEAD
)
// EnableTelemetry defines whether telemetry is enabled in Run.
var EnableTelemetry = true

View File

@@ -26,6 +26,7 @@ import (
"crypto/subtle"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
@@ -193,11 +194,15 @@ func PlainMatcher(passw string) PasswordMatcher {
// compare hashes of equal length instead of actual password
// to avoid leaking password length
passwHash := sha1.New()
passwHash.Write([]byte(passw))
if _, err := passwHash.Write([]byte(passw)); err != nil {
log.Printf("[ERROR] unable to write password hash: %v", err)
}
passwSum := passwHash.Sum(nil)
return func(pw string) bool {
pwHash := sha1.New()
pwHash.Write([]byte(pw))
if _, err := pwHash.Write([]byte(pw)); err != nil {
log.Printf("[ERROR] unable to write password hash: %v", err)
}
pwSum := pwHash.Sum(nil)
return subtle.ConstantTimeCompare([]byte(pwSum), []byte(passwSum)) == 1
}

View File

@@ -59,34 +59,34 @@ type Config struct {
// A Listing is the context used to fill out a template.
type Listing struct {
// The name of the directory (the last element of the path)
// The name of the directory (the last element of the path).
Name string
// The full path of the request
// The full path of the request.
Path string
// Whether the parent directory is browsable
// Whether the parent directory is browse-able.
CanGoUp bool
// The items (files and folders) in the path
// The items (files and folders) in the path.
Items []FileInfo
// The number of directories in the listing
// The number of directories in the listing.
NumDirs int
// The number of files (items that aren't directories) in the listing
// The number of files (items that aren't directories) in the listing.
NumFiles int
// Which sorting order is used
// Which sorting order is used.
Sort string
// And which order
// And which order.
Order string
// If ≠0 then Items have been limited to that many elements
// If ≠0 then Items have been limited to that many elements.
ItemsLimitedTo int
// Optional custom variables for use in browse templates
// Optional custom variables for use in browse templates.
User interface{}
httpserver.Context
@@ -244,7 +244,7 @@ func (l Listing) applySort() {
func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config *Config) (Listing, bool) {
var (
fileinfos []FileInfo
fileInfos []FileInfo
dirCount, fileCount int
hasIndexFile bool
)
@@ -272,14 +272,14 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
continue
}
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
fileinfos = append(fileinfos, FileInfo{
fileInfos = append(fileInfos, FileInfo{
IsDir: isDir,
IsSymlink: isSymlink(f),
Name: f.Name(),
Size: f.Size(),
URL: url.String(),
URL: u.String(),
ModTime: f.ModTime().UTC(),
Mode: f.Mode(),
})
@@ -289,7 +289,7 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
Name: path.Base(urlPath),
Path: urlPath,
CanGoUp: canGoUp,
Items: fileinfos,
Items: fileInfos,
NumDirs: dirCount,
NumFiles: fileCount,
}, hasIndexFile
@@ -504,7 +504,7 @@ func (b Browse) ServeListing(w http.ResponseWriter, r *http.Request, requestedFi
}
buf.WriteTo(w)
_, _ = buf.WriteTo(w)
return http.StatusOK, nil
}

View File

@@ -175,15 +175,13 @@ func (rec *record) read(r io.Reader) (buf []byte, err error) {
// FCGIClient implements a FastCGI client, which is a standard for
// interfacing external applications with Web servers.
type FCGIClient struct {
mutex sync.Mutex
rwc io.ReadWriteCloser
h header
buf bytes.Buffer
stderr bytes.Buffer
keepAlive bool
reqID uint16
readTimeout time.Duration
sendTimeout time.Duration
mutex sync.Mutex
rwc io.ReadWriteCloser
h header
buf bytes.Buffer
stderr bytes.Buffer
keepAlive bool
reqID uint16
}
// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer
@@ -397,7 +395,7 @@ func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err er
body := newWriter(c, Stdin)
if req != nil {
io.Copy(body, req)
_, _ = io.Copy(body, req)
}
body.Close()

View File

@@ -234,7 +234,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// trusted CA (obviously not a perfect heuristic)
var looksLikeProductionCA bool
for _, publicCAEndpoint := range caddytls.KnownACMECAs {
if strings.Contains(certmagic.CA, publicCAEndpoint) {
if strings.Contains(certmagic.Default.CA, publicCAEndpoint) {
looksLikeProductionCA = true
break
}
@@ -685,6 +685,7 @@ var directives = []string{
"gopkg", // github.com/zikes/gopkg
"restic", // github.com/restic/caddy
"wkd", // github.com/emersion/caddy-wkd
"dyndns", // github.com/linkonoid/caddy-dyndns
}
const (

View File

@@ -29,7 +29,6 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/lucas-clemente/quic-go/h2quic"
@@ -43,8 +42,6 @@ import (
type Server struct {
Server *http.Server
quicServer *h2quic.Server
listener net.Listener
listenerMu sync.Mutex
sites []*SiteConfig
connTimeout time.Duration // max time to wait for a connection before force stop
tlsGovChan chan struct{} // close to stop the TLS maintenance goroutine
@@ -237,7 +234,9 @@ func makeHTTPServerWithTimeouts(addr string, group []*SiteConfig) *http.Server {
func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.quicServer.SetQuicHeaders(w.Header())
if err := s.quicServer.SetQuicHeaders(w.Header()); err != nil {
log.Println("[Error] failed to set proper headers for QUIC: ", err)
}
previousHandler.ServeHTTP(w, r)
}
}
@@ -246,7 +245,7 @@ func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFu
// used to serve requests.
func (s *Server) Listen() (net.Listener, error) {
if s.Server == nil {
return nil, fmt.Errorf("Server field is nil")
return nil, fmt.Errorf("server field is nil")
}
ln, err := net.Listen("tcp", s.Server.Addr)
@@ -310,10 +309,6 @@ func (s *Server) ListenPacket() (net.PacketConn, error) {
// Serve serves requests on ln. It blocks until ln is closed.
func (s *Server) Serve(ln net.Listener) error {
s.listenerMu.Lock()
s.listener = ln
s.listenerMu.Unlock()
if s.Server.TLSConfig != nil {
// Create TLS listener - note that we do not replace s.listener
// with this TLS listener; tls.listener is unexported and does
@@ -329,14 +324,19 @@ func (s *Server) Serve(ln net.Listener) error {
s.tlsGovChan = caddytls.RotateSessionTicketKeys(s.Server.TLSConfig)
}
defer func() {
if s.quicServer != nil {
if err := s.quicServer.Close(); err != nil {
log.Println("[ERROR] failed to close QUIC server: ", err)
}
}
}()
err := s.Server.Serve(ln)
if err == http.ErrServerClosed {
err = nil // not an error worth reporting since closing a server is intentional
if err != nil && err != http.ErrServerClosed {
return err
}
if s.quicServer != nil {
s.quicServer.Close()
}
return err
return nil
}
// ServePacket serves QUIC requests on pc until it is closed.
@@ -559,8 +559,12 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
if err = tc.SetKeepAlive(true); err != nil {
return
}
if err = tc.SetKeepAlivePeriod(3 * time.Minute); err != nil {
return
}
return tc, nil
}
@@ -598,7 +602,9 @@ func WriteTextResponse(w http.ResponseWriter, status int, body string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(status)
w.Write([]byte(body))
if _, err := w.Write([]byte(body)); err != nil {
log.Println("[Error] failed to write body: ", err)
}
}
// SafePath joins siteRoot and reqPath and converts it to a path that can

View File

@@ -19,6 +19,7 @@ import (
"crypto/rand"
"fmt"
"io/ioutil"
"log"
mathrand "math/rand"
"net"
"net/http"
@@ -420,7 +421,9 @@ func (c Context) RandomString(minLen, maxLen int) string {
// secureRandomBytes returns a number of bytes using crypto/rand.
secureRandomBytes := func(numBytes int) []byte {
randomBytes := make([]byte, numBytes)
rand.Read(randomBytes)
if _, err := rand.Read(randomBytes); err != nil {
log.Println("[ERROR] failed to read bytes: ", err)
}
return randomBytes
}

View File

@@ -17,6 +17,7 @@
package markdown
import (
"log"
"net/http"
"os"
"path"
@@ -168,7 +169,9 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
w.Header().Set("Content-Length", strconv.Itoa(len(html)))
httpserver.SetLastModifiedHeader(w, lastModTime)
if r.Method == http.MethodGet {
w.Write(html)
if _, err := w.Write(html); err != nil {
log.Println("[ERROR] failed to write html response: ", err)
}
}
return http.StatusOK, nil
}

View File

@@ -16,6 +16,7 @@ package proxy
import (
"hash/fnv"
"log"
"math"
"math/rand"
"net"
@@ -139,7 +140,9 @@ func hostByHashing(pool HostPool, s string) *UpstreamHost {
// hash calculates a hash based on string s
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
if _, err := h.Write([]byte(s)); err != nil {
log.Println("[ERROR] failed to write bytes: ", err)
}
return h.Sum32()
}

View File

@@ -92,8 +92,10 @@ type UpstreamHost struct {
// This is an int32 so that we can use atomic operations to do concurrent
// reads & writes to this value. The default value of 0 indicates that it
// is healthy and any non-zero value indicates unhealthy.
Unhealthy int32
HealthCheckResult atomic.Value
Unhealthy int32
HealthCheckResult atomic.Value
UpstreamHeaderReplacements headerReplacements
DownstreamHeaderReplacements headerReplacements
}
// Down checks whether the upstream host is down or not.
@@ -220,7 +222,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// set headers for request going upstream
if host.UpstreamHeaders != nil {
// modify headers for request that will be sent to the upstream host
mutateHeadersByRules(outreq.Header, host.UpstreamHeaders, replacer)
mutateHeadersByRules(outreq.Header, host.UpstreamHeaders, replacer, host.UpstreamHeaderReplacements)
if hostHeaders, ok := outreq.Header["Host"]; ok && len(hostHeaders) > 0 {
outreq.Host = hostHeaders[len(hostHeaders)-1]
}
@@ -230,7 +232,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// headers coming back downstream
var downHeaderUpdateFn respUpdateFn
if host.DownstreamHeaders != nil {
downHeaderUpdateFn = createRespHeaderUpdateFn(host.DownstreamHeaders, replacer)
downHeaderUpdateFn = createRespHeaderUpdateFn(host.DownstreamHeaders, replacer, host.DownstreamHeaderReplacements)
}
// Before we retry the request we have to make sure
@@ -376,13 +378,13 @@ func createUpstreamRequest(rw http.ResponseWriter, r *http.Request) (*http.Reque
return outreq, cancel
}
func createRespHeaderUpdateFn(rules http.Header, replacer httpserver.Replacer) respUpdateFn {
func createRespHeaderUpdateFn(rules http.Header, replacer httpserver.Replacer, replacements headerReplacements) respUpdateFn {
return func(resp *http.Response) {
mutateHeadersByRules(resp.Header, rules, replacer)
mutateHeadersByRules(resp.Header, rules, replacer, replacements)
}
}
func mutateHeadersByRules(headers, rules http.Header, repl httpserver.Replacer) {
func mutateHeadersByRules(headers, rules http.Header, repl httpserver.Replacer, replacements headerReplacements) {
for ruleField, ruleValues := range rules {
if strings.HasPrefix(ruleField, "+") {
for _, ruleValue := range ruleValues {
@@ -400,6 +402,19 @@ func mutateHeadersByRules(headers, rules http.Header, repl httpserver.Replacer)
}
}
}
for ruleField, ruleValues := range replacements {
for _, ruleValue := range ruleValues {
// Replace variables in replacement string
replacement := repl.Replace(ruleValue.to)
original := headers.Get(ruleField)
if len(replacement) > 0 && len(original) > 0 {
// Replace matches in original string with replacement string
replaced := ruleValue.regexp.ReplaceAllString(original, replacement)
headers.Set(ruleField, replaced)
}
}
}
}
const CustomStatusContextCancelled = 499

View File

@@ -31,6 +31,7 @@ import (
"crypto/x509"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
@@ -69,7 +70,9 @@ func pooledIoCopy(dst io.Writer, src io.Reader) {
// Due to that we extend buf's length to its capacity here and
// ensure it's always non-zero.
bufCap := cap(buf)
io.CopyBuffer(dst, src, buf[0:bufCap:bufCap])
if _, err := io.CopyBuffer(dst, src, buf[0:bufCap:bufCap]); err != nil {
log.Println("[ERROR] failed to copy buffer: ", err)
}
}
// onExitFlushLoop is a callback set by tests to detect the state of the
@@ -132,12 +135,12 @@ func (rp *ReverseProxy) srvDialerFunc(locator string, timeout time.Duration) fun
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
aSlash := strings.HasSuffix(a, "/")
bSlash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
case aSlash && bSlash:
return a + b[1:]
case !aslash && !bslash && b != "":
case !aSlash && !bSlash && b != "":
return a + "/" + b
}
return a + b
@@ -275,7 +278,9 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, t
transport.MaxIdleConnsPerHost = keepalive
}
if httpserver.HTTP2 {
http2.ConfigureTransport(transport)
if err := http2.ConfigureTransport(transport); err != nil {
log.Println("[ERROR] failed to configure transport to use HTTP/2: ", err)
}
}
rp.Transport = transport
} else {
@@ -284,7 +289,9 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int, t
Dial: rp.dialer.Dial,
}
if httpserver.HTTP2 {
http2.ConfigureTransport(transport)
if err := http2.ConfigureTransport(transport); err != nil {
log.Println("[ERROR] failed to configure transport to use HTTP/2: ", err)
}
}
rp.Transport = transport
}
@@ -394,7 +401,9 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request,
if err != nil {
return err
}
outreq.Write(backendConn)
if err := outreq.Write(backendConn); err != nil {
log.Println("[ERROR] failed to write: ", err)
}
}
defer backendConn.Close()
@@ -416,7 +425,9 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request,
if err != nil {
return err
}
backendConn.Write(rbuf)
if _, err := backendConn.Write(rbuf); err != nil {
log.Println("[ERROR] failed to write data to connection: ", err)
}
}
}
go func() {
@@ -434,7 +445,7 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request,
bodyOpen := true
closeBody := func() {
if bodyOpen {
res.Body.Close()
_ = res.Body.Close()
bodyOpen = false
}
}
@@ -681,7 +692,7 @@ func getTransportDialTLS(t *http.Transport) func(network, addr string) (net.Conn
errc <- err
}()
if err := <-errc; err != nil {
plainConn.Close()
_ = plainConn.Close()
return nil, err
}
if !tlsClientConfig.InsecureSkipVerify {
@@ -690,7 +701,7 @@ func getTransportDialTLS(t *http.Transport) func(network, addr string) (net.Conn
hostname = stripPort(addr)
}
if err := tlsConn.VerifyHostname(hostname); err != nil {
plainConn.Close()
_ = plainConn.Close()
return nil, err
}
}

View File

@@ -21,10 +21,13 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/textproto"
"net/url"
"path"
"regexp"
"strconv"
"strings"
"sync"
@@ -65,18 +68,39 @@ type staticUpstream struct {
Port string
ContentString string
}
WithoutPathPrefix string
IgnoredSubPaths []string
insecureSkipVerify bool
MaxFails int32
resolver srvResolver
CaCertPool *x509.CertPool
WithoutPathPrefix string
IgnoredSubPaths []string
insecureSkipVerify bool
MaxFails int32
resolver srvResolver
CaCertPool *x509.CertPool
upstreamHeaderReplacements headerReplacements
downstreamHeaderReplacements headerReplacements
}
type srvResolver interface {
LookupSRV(context.Context, string, string, string) (string, []*net.SRV, error)
}
// headerReplacement stores a compiled regex matcher and a string replacer, for replacement rules
type headerReplacement struct {
regexp *regexp.Regexp
to string
}
// headerReplacements stores a mapping of canonical MIME header to headerReplacement
// Implements a subset of http.Header functions, to allow convenient addition and deletion of rules
type headerReplacements map[string][]headerReplacement
func (h headerReplacements) Add(key string, value headerReplacement) {
key = textproto.CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}
func (h headerReplacements) Del(key string) {
delete(h, textproto.CanonicalMIMEHeaderKey(key))
}
// NewStaticUpstreams parses the configuration input and sets up
// static upstreams for the proxy middleware. The host string parameter,
// if not empty, is used for setting the upstream Host header for the
@@ -86,18 +110,20 @@ func NewStaticUpstreams(c caddyfile.Dispenser, host string) ([]Upstream, error)
for c.Next() {
upstream := &staticUpstream{
from: "",
stop: make(chan struct{}),
upstreamHeaders: make(http.Header),
downstreamHeaders: make(http.Header),
Hosts: nil,
Policy: &Random{},
MaxFails: 1,
TryInterval: 250 * time.Millisecond,
MaxConns: 0,
KeepAlive: http.DefaultMaxIdleConnsPerHost,
Timeout: 30 * time.Second,
resolver: net.DefaultResolver,
from: "",
stop: make(chan struct{}),
upstreamHeaders: make(http.Header),
downstreamHeaders: make(http.Header),
Hosts: nil,
Policy: &Random{},
MaxFails: 1,
TryInterval: 250 * time.Millisecond,
MaxConns: 0,
KeepAlive: http.DefaultMaxIdleConnsPerHost,
Timeout: 30 * time.Second,
resolver: net.DefaultResolver,
upstreamHeaderReplacements: make(headerReplacements),
downstreamHeaderReplacements: make(headerReplacements),
}
if !c.Args(&upstream.from) {
@@ -220,9 +246,11 @@ func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) {
return false
}
}(u),
WithoutPathPrefix: u.WithoutPathPrefix,
MaxConns: u.MaxConns,
HealthCheckResult: atomic.Value{},
WithoutPathPrefix: u.WithoutPathPrefix,
MaxConns: u.MaxConns,
HealthCheckResult: atomic.Value{},
UpstreamHeaderReplacements: u.upstreamHeaderReplacements,
DownstreamHeaderReplacements: u.downstreamHeaderReplacements,
}
baseURL, err := url.Parse(uh.Name)
@@ -302,6 +330,8 @@ func parseUpstream(u string) ([]string, error) {
}
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream, hasSrv bool) error {
var isUpstream bool
switch c.Val() {
case "policy":
if !c.NextArg() {
@@ -431,23 +461,37 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream, hasSrv bool) error {
}
u.HealthCheck.ContentString = c.Val()
case "header_upstream":
var header, value string
if !c.Args(&header, &value) {
// When removing a header, the value can be optional.
if !strings.HasPrefix(header, "-") {
return c.ArgErr()
}
}
u.upstreamHeaders.Add(header, value)
isUpstream = true
fallthrough
case "header_downstream":
var header, value string
if !c.Args(&header, &value) {
// When removing a header, the value can be optional.
if !strings.HasPrefix(header, "-") {
var header, value, replaced string
if c.Args(&header, &value, &replaced) {
// Don't allow - or + in replacements
if strings.HasPrefix(header, "-") || strings.HasPrefix(header, "+") {
return c.ArgErr()
}
r, err := regexp.Compile(value)
if err != nil {
return err
}
if isUpstream {
u.upstreamHeaderReplacements.Add(header, headerReplacement{r, replaced})
} else {
u.downstreamHeaderReplacements.Add(header, headerReplacement{r, replaced})
}
} else {
if len(value) == 0 {
// When removing a header, the value can be optional.
if !strings.HasPrefix(header, "-") {
return c.ArgErr()
}
}
if isUpstream {
u.upstreamHeaders.Add(header, value)
} else {
u.downstreamHeaders.Add(header, value)
}
}
u.downstreamHeaders.Add(header, value)
case "transparent":
// Note: X-Forwarded-For header is always being appended for proxy connections
// See implementation of createUpstreamRequest in proxy.go
@@ -589,8 +633,10 @@ func (u *staticUpstream) healthCheck() {
return true
}
defer func() {
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
if _, err := io.Copy(ioutil.Discard, r.Body); err != nil {
log.Println("[ERROR] failed to copy: ", err)
}
_ = r.Body.Close()
}()
if r.StatusCode < 200 || r.StatusCode >= 400 {
return true

View File

@@ -21,6 +21,7 @@ import (
"bufio"
"bytes"
"io"
"log"
"net"
"net/http"
"os"
@@ -80,9 +81,9 @@ type (
// ServeHTTP converts the HTTP request to a WebSocket connection and serves it up.
func (ws WebSocket) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
for _, sockconfig := range ws.Sockets {
if httpserver.Path(r.URL.Path).Matches(sockconfig.Path) {
return serveWS(w, r, &sockconfig)
for _, sockConfig := range ws.Sockets {
if httpserver.Path(r.URL.Path).Matches(sockConfig.Path) {
return serveWS(w, r, &sockConfig)
}
}
@@ -135,7 +136,7 @@ func serveWS(w http.ResponseWriter, r *http.Request, config *Config) (int, error
go pumpStdout(conn, stdout, done)
pumpStdin(conn, stdin)
stdin.Close() // close stdin to end the process
_ = stdin.Close() // close stdin to end the process
if err := cmd.Process.Signal(os.Interrupt); err != nil { // signal an interrupt to kill the process
return http.StatusInternalServerError, err
@@ -155,7 +156,9 @@ func serveWS(w http.ResponseWriter, r *http.Request, config *Config) (int, error
// status for an "exited" process is greater
// than 0, but isn't really an error per se.
// just going to ignore it for now.
cmd.Wait()
if err := cmd.Wait(); err != nil {
log.Println("[ERROR] failed to release resources: ", err)
}
return 0, nil
}
@@ -221,8 +224,15 @@ func pumpStdin(conn *websocket.Conn, stdin io.WriteCloser) {
// Setup our connection's websocket ping/pong handlers from our const values.
defer conn.Close()
conn.SetReadLimit(maxMessageSize)
conn.SetReadDeadline(time.Now().Add(pongWait))
conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
if err := conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
log.Println("[ERROR] failed to set read deadline: ", err)
}
conn.SetPongHandler(func(string) error {
if err := conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
log.Println("[ERROR] failed to set read deadline: ", err)
}
return nil
})
for {
_, message, err := conn.ReadMessage()
if err != nil {
@@ -240,19 +250,24 @@ func pumpStdin(conn *websocket.Conn, stdin io.WriteCloser) {
func pumpStdout(conn *websocket.Conn, stdout io.Reader, done chan struct{}) {
go pinger(conn, done)
defer func() {
conn.Close()
_ = conn.Close()
close(done) // make sure to close the pinger when we are done.
}()
s := bufio.NewScanner(stdout)
for s.Scan() {
conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
log.Println("[ERROR] failed to set write deadline: ", err)
}
if err := conn.WriteMessage(websocket.TextMessage, bytes.TrimSpace(s.Bytes())); err != nil {
break
}
}
if s.Err() != nil {
conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, s.Err().Error()), time.Time{})
err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, s.Err().Error()), time.Time{})
if err != nil {
log.Println("[ERROR] WriteControl failed: ", err)
}
}
}
@@ -265,7 +280,10 @@ func pinger(conn *websocket.Conn, done chan struct{}) {
select {
case <-ticker.C:
if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, err.Error()), time.Time{})
err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, err.Error()), time.Time{})
if err != nil {
log.Println("[ERROR] WriteControl failed: ", err)
}
return
}
case <-done:

View File

@@ -21,13 +21,14 @@ import (
"io/ioutil"
"os"
"sync/atomic"
"time"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/certcrypto"
"github.com/klauspost/cpuid"
"github.com/mholt/caddy"
"github.com/mholt/certmagic"
"github.com/xenolf/lego/certcrypto"
)
// Config describes how TLS should be configured and used.
@@ -117,22 +118,48 @@ func NewConfig(inst *caddy.Instance) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("constructing cluster plugin %s: %v", clusterPluginName, err)
}
certmagic.DefaultStorage = storage
certmagic.Default.Storage = storage
} else {
return nil, fmt.Errorf("unrecognized cluster plugin (was it included in the Caddy build?): %s", clusterPluginName)
}
}
certCache = certmagic.NewCache(certmagic.DefaultStorage)
certCache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (certmagic.Config, error) {
inst.StorageMu.Lock()
cfgMap, ok := inst.Storage[configMapKey].(map[string]*Config)
inst.StorageMu.Unlock()
if ok {
for hostname, cfg := range cfgMap {
if cfg.Manager != nil && hostname == cert.Names[0] {
return *cfg.Manager, nil
}
}
}
// returning Default not strictly necessary, since Default is used as template
// anyway; but this makes it clear that that's what we fall back to
return certmagic.Default, nil
},
})
storageCleaningTicker := time.NewTicker(12 * time.Hour)
go func() {
for range storageCleaningTicker.C {
certmagic.CleanStorage(certmagic.Default.Storage, certmagic.CleanStorageOptions{
OCSPStaples: true,
})
}
}()
inst.OnShutdown = append(inst.OnShutdown, func() error {
certCache.Stop()
storageCleaningTicker.Stop()
return nil
})
inst.StorageMu.Lock()
inst.Storage[CertCacheInstStorageKey] = certCache
inst.StorageMu.Unlock()
}
return &Config{
Manager: certmagic.NewWithCache(certCache, certmagic.Config{}),
Manager: certmagic.New(certCache, certmagic.Config{}),
}, nil
}
@@ -418,7 +445,6 @@ func SetDefaultTLSParams(config *Config) {
var supportedKeyTypes = map[string]certcrypto.KeyType{
"P384": certcrypto.EC384,
"P256": certcrypto.EC256,
"RSA8192": certcrypto.RSA8192,
"RSA4096": certcrypto.RSA4096,
"RSA2048": certcrypto.RSA2048,
}

View File

@@ -42,7 +42,7 @@ type configGroup map[string]*Config
func (cg configGroup) getConfig(hello *tls.ClientHelloInfo) *Config {
name := certmagic.NormalizedName(hello.ServerName)
if name == "" {
name = certmagic.NormalizedName(certmagic.DefaultServerName)
name = certmagic.NormalizedName(certmagic.Default.DefaultServerName)
}
// if SNI is empty, prefer matching IP address (it is

View File

@@ -14,7 +14,7 @@ import (
"strings"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/go-acme/lego/certcrypto"
)
// newSelfSignedCertificate returns a new self-signed certificate.

View File

@@ -36,7 +36,9 @@ import (
func init() {
// opt-in TLS 1.3 for Go1.12
// TODO: remove this line when Go1.13 is released.
os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
if err := os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1"); err != nil {
log.Println("[ERROR] failed to set environment variable: ", err)
}
caddy.RegisterPlugin("tls", caddy.Plugin{Action: setupTLS})
@@ -63,7 +65,7 @@ func setupTLS(c *caddy.Controller) error {
if err != nil {
return fmt.Errorf("constructing cluster plugin %s: %v", clusterPluginName, err)
}
certmagic.DefaultStorage = storage
certmagic.Default.Storage = storage
} else {
return fmt.Errorf("unrecognized cluster plugin (was it included in the Caddy build?): %s", clusterPluginName)
}
@@ -363,6 +365,14 @@ func setupTLS(c *caddy.Controller) error {
telemetry.Increment("tls_self_signed_count")
}
// store this as a custom config
cfgMap, ok := c.Get(configMapKey).(map[string]*Config)
if !ok || cfgMap == nil {
cfgMap = make(map[string]*Config)
}
cfgMap[config.Hostname] = config
c.Set(configMapKey, cfgMap)
return nil
}
@@ -401,26 +411,34 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
if derBlock.Type == "CERTIFICATE" {
// Re-encode certificate as PEM, appending to certificate chain
pem.Encode(certBuilder, derBlock)
if err := pem.Encode(certBuilder, derBlock); err != nil {
log.Println("[ERROR] failed to write PEM encoding: ", err)
}
} else if derBlock.Type == "EC PARAMETERS" {
// EC keys generated from openssl can be composed of two blocks:
// parameters and key (parameter block should come first)
if !foundKey {
// Encode parameters
pem.Encode(keyBuilder, derBlock)
if err := pem.Encode(keyBuilder, derBlock); err != nil {
log.Println("[ERROR] failed to write PEM encoding: ", err)
}
// Key must immediately follow
derBlock, bundle = pem.Decode(bundle)
if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
return c.Errf("%s: expected elliptic private key to immediately follow EC parameters", path)
}
pem.Encode(keyBuilder, derBlock)
if err := pem.Encode(keyBuilder, derBlock); err != nil {
log.Println("[ERROR] failed to write PEM encoding: ", err)
}
foundKey = true
}
} else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
// RSA key
if !foundKey {
pem.Encode(keyBuilder, derBlock)
if err := pem.Encode(keyBuilder, derBlock); err != nil {
log.Println("[ERROR] failed to write PEM encoding: ", err)
}
foundKey = true
}
} else {
@@ -449,3 +467,5 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
func constructDefaultClusterPlugin() (certmagic.Storage, error) {
return &certmagic.FileStorage{Path: caddy.AssetsPath()}, nil
}
const configMapKey = "tls_custom_configs"

View File

@@ -29,9 +29,9 @@
package caddytls
import (
"github.com/go-acme/lego/challenge"
"github.com/mholt/caddy"
"github.com/mholt/certmagic"
"github.com/xenolf/lego/challenge"
)
// ConfigHolder is any type that has a Config; it presumably is
@@ -93,7 +93,7 @@ var KnownACMECAs = []string{
//
// challenge.Provider is an interface that allows the implementation of custom
// challenge providers. For more details, see:
// https://godoc.org/github.com/xenolf/lego/acme#ChallengeProvider
// https://godoc.org/github.com/go-acme/lego/acme#ChallengeProvider
type ChallengeProvider challenge.Provider
// DNSProviderConstructor is a function that takes credentials and

View File

@@ -20,12 +20,15 @@ func setup(c *caddy.Controller) error {
}
// Register Event Hooks.
c.OncePerServerBlock(func() error {
err = c.OncePerServerBlock(func() error {
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
return nil
})
if err != nil {
return err
}
return nil
}

View File

@@ -283,7 +283,10 @@ func atomicAdd(key string, amount int) {
// Do not use this for cryptographic purposes.
func FastHash(input []byte) string {
h := fnv.New32a()
h.Write(input)
if _, err := h.Write(input); err != nil {
log.Println("[ERROR] failed to write bytes: ", err)
}
return fmt.Sprintf("%x", h.Sum32())
}

View File

@@ -21,149 +21,251 @@ import (
)
// Cache is a structure that stores certificates in memory.
// Generally, there should only be one per process. However,
// complex applications that virtualize the concept of a
// "process" (such as Caddy, which virtualizes processes as
// "instances" so it can do graceful, in-memory reloads of
// its configuration) may use more of these per OS process.
// A Cache indexes certificates by name for quick access
// during TLS handshakes, and avoids duplicating certificates
// in memory. Generally, there should only be one per process.
// However, that is not a strict requirement; but using more
// than one is a code smell, and may indicate an
// over-engineered design.
//
// Using just one cache per process avoids duplication of
// certificates across multiple configurations and makes
// maintenance easier.
// An empty cache is INVALID and must not be used. Be sure
// to call NewCache to get a valid value.
//
// An empty cache is INVALID and must not be used.
// Be sure to call NewCertificateCache to get one.
//
// These should be very long-lived values, and must not be
// These should be very long-lived values and must not be
// copied. Before all references leave scope to be garbage
// collected, ensure you call Stop() to stop maintenance
// maintenance on the certificates stored in this cache.
// collected, ensure you call Stop() to stop maintenance on
// the certificates stored in this cache and release locks.
//
// Caches are not usually manipulated directly; create a
// Config value with a pointer to a Cache, and then use
// the Config to interact with the cache. Caches are
// agnostic of any particular storage or ACME config,
// since each certificate may be managed and stored
// differently.
type Cache struct {
// How often to check certificates for renewal
RenewInterval time.Duration
// How often to check if OCSP stapling needs updating
OCSPInterval time.Duration
// The storage implementation
storage Storage
// User configuration of the cache
options CacheOptions
// The cache is keyed by certificate hash
cache map[string]Certificate
// Protects the cache map
// cacheIndex is a map of SAN to cache key (cert hash)
cacheIndex map[string][]string
// Protects the cache and index maps
mu sync.RWMutex
// Close this channel to cancel asset maintenance
stopChan chan struct{}
// Used to signal when stopping is completed
doneChan chan struct{}
}
// NewCache returns a new, valid Cache backed by the
// given storage implementation. It also begins a
// maintenance goroutine for any managed certificates
// stored in this cache.
// NewCache returns a new, valid Cache for efficiently
// accessing certificates in memory. It also begins a
// maintenance goroutine to tend to the certificates
// in the cache. Call Stop() when you are done with the
// cache so it can clean up locks and stuff.
//
// See the godoc for Cache to use it properly.
// Most users of this package will not need to call this
// because a default certificate cache is created for you.
// Only advanced use cases require creating a new cache.
//
// Note that all processes running in a cluster
// configuration must use the same storage value
// in order to share certificates. (A single storage
// value may be shared by multiple clusters as well.)
func NewCache(storage Storage) *Cache {
c := &Cache{
RenewInterval: DefaultRenewInterval,
OCSPInterval: DefaultOCSPInterval,
storage: storage,
cache: make(map[string]Certificate),
stopChan: make(chan struct{}),
// This function panics if opts.GetConfigForCert is not
// set. The reason is that a cache absolutely needs to
// be able to get a Config with which to manage TLS
// assets, and it is not safe to assume that the Default
// config is always the correct one, since you have
// created the cache yourself.
//
// See the godoc for Cache to use it properly. When
// no longer needed, caches should be stopped with
// Stop() to clean up resources even if the process
// is being terminated, so that it can clean up
// any locks for other processes to unblock!
func NewCache(opts CacheOptions) *Cache {
// assume default options if necessary
if opts.OCSPCheckInterval <= 0 {
opts.OCSPCheckInterval = DefaultOCSPCheckInterval
}
if opts.RenewCheckInterval <= 0 {
opts.RenewCheckInterval = DefaultRenewCheckInterval
}
// this must be set, because we cannot not
// safely assume that the Default Config
// is always the correct one to use
if opts.GetConfigForCert == nil {
panic("cache must be initialized with a GetConfigForCert callback")
}
c := &Cache{
options: opts,
cache: make(map[string]Certificate),
cacheIndex: make(map[string][]string),
stopChan: make(chan struct{}),
doneChan: make(chan struct{}),
}
go c.maintainAssets()
return c
}
// Stop stops the maintenance goroutine for
// certificates in certCache.
// certificates in certCache. It blocks until
// stopping is complete. Once a cache is
// stopped, it cannot be reused.
func (certCache *Cache) Stop() {
close(certCache.stopChan)
close(certCache.stopChan) // signal to stop
<-certCache.doneChan // wait for stop to complete
}
// replaceCertificate replaces oldCert with newCert in the cache, and
// updates all configs that are pointing to the old certificate to
// point to the new one instead. newCert must already be loaded into
// the cache (this method does NOT load it into the cache).
// CacheOptions is used to configure certificate caches.
// Once a cache has been created with certain options,
// those settings cannot be changed.
type CacheOptions struct {
// REQUIRED. A function that returns a configuration
// used for managing a certificate, or for accessing
// that certificate's asset storage (e.g. for
// OCSP staples, etc). The returned Config MUST
// be associated with the same Cache as the caller.
//
// The reason this is a callback function, dynamically
// returning a Config (instead of attaching a static
// pointer to a Config on each certificate) is because
// the config for how to manage a domain's certificate
// might change from maintenance to maintenance. The
// cache is so long-lived, we cannot assume that the
// host's situation will always be the same; e.g. the
// certificate might switch DNS providers, so the DNS
// challenge (if used) would need to be adjusted from
// the last time it was run ~8 weeks ago.
GetConfigForCert ConfigGetter
// How often to check certificates for renewal;
// if unset, DefaultOCSPCheckInterval will be used.
OCSPCheckInterval time.Duration
// How often to check certificates for renewal;
// if unset, DefaultRenewCheckInterval will be used.
RenewCheckInterval time.Duration
}
// ConfigGetter is a function that returns a config that
// should be used when managing the given certificate
// or its assets.
type ConfigGetter func(Certificate) (Config, error)
// cacheCertificate calls unsyncedCacheCertificate with a write lock.
//
// Note that all the names on the old certificate will be deleted
// from the name lookup maps of each config, then all the names on
// the new certificate will be added to the lookup maps as long as
// they do not overwrite any entries.
// This function is safe for concurrent use.
func (certCache *Cache) cacheCertificate(cert Certificate) {
certCache.mu.Lock()
certCache.unsyncedCacheCertificate(cert)
certCache.mu.Unlock()
}
// unsyncedCacheCertificate adds cert to the in-memory cache unless
// it already exists in the cache (according to cert.Hash). It
// updates the name index.
//
// The newCert may be modified and its cache entry updated.
// This function is NOT safe for concurrent use. Callers MUST acquire
// a write lock on certCache.mu first.
func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
// no-op if this certificate already exists in the cache
if _, ok := certCache.cache[cert.Hash]; ok {
return
}
// store the certificate
certCache.cache[cert.Hash] = cert
// update the index so we can access it by name
for _, name := range cert.Names {
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.Hash)
}
}
// removeCertificate removes cert from the cache.
//
// This function is NOT safe for concurrent use; callers
// MUST first acquire a write lock on certCache.mu.
func (certCache *Cache) removeCertificate(cert Certificate) {
// delete all mentions of this cert from the name index
for _, name := range cert.Names {
keyList := certCache.cacheIndex[name]
for i, cacheKey := range keyList {
if cacheKey == cert.Hash {
keyList = append(keyList[:i], keyList[i+1:]...)
}
}
if len(keyList) == 0 {
delete(certCache.cacheIndex, name)
} else {
certCache.cacheIndex[name] = keyList
}
}
// delete the actual cert from the cache
delete(certCache.cache, cert.Hash)
}
// replaceCertificate atomically replaces oldCert with newCert in
// the cache.
//
// This method is safe for concurrent use.
func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) error {
func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
certCache.mu.Lock()
defer certCache.mu.Unlock()
// have all the configs that are pointing to the old
// certificate point to the new certificate instead
for _, cfg := range oldCert.configs {
// first delete all the name lookup entries that
// pointed to the old certificate
for name, certKey := range cfg.certificates {
if certKey == oldCert.Hash {
delete(cfg.certificates, name)
}
}
// then add name lookup entries for the names
// on the new certificate, but don't overwrite
// entries that may already exist, not only as
// a courtesy, but importantly: because if we
// overwrote a value here, and this config no
// longer pointed to a certain certificate in
// the cache, that certificate's list of configs
// referring to it would be incorrect; so just
// insert entries, don't overwrite any
for _, name := range newCert.Names {
if _, ok := cfg.certificates[name]; !ok {
cfg.certificates[name] = newCert.Hash
}
}
}
// since caching a new certificate attaches only the config
// that loaded it, the new certificate needs to be given the
// list of all the configs that use it, so copy the list
// over from the old certificate to the new certificate
// in the cache
newCert.configs = oldCert.configs
certCache.cache[newCert.Hash] = newCert
// finally, delete the old certificate from the cache
delete(certCache.cache, oldCert.Hash)
return nil
certCache.removeCertificate(oldCert)
certCache.unsyncedCacheCertificate(newCert)
certCache.mu.Unlock()
}
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
// on oldCert into the cache, from storage. This also replaces the old certificate
// with the new one, so that all configurations that used the old cert now point
// to the new cert.
func (certCache *Cache) reloadManagedCertificate(oldCert Certificate) error {
// get the certificate from storage and cache it
newCert, err := oldCert.configs[0].CacheManagedCertificate(oldCert.Names[0])
if err != nil {
return fmt.Errorf("unable to reload certificate for %v into cache: %v", oldCert.Names, err)
func (certCache *Cache) getFirstMatchingCert(name string) (Certificate, bool) {
certCache.mu.RLock()
defer certCache.mu.RUnlock()
allCertKeys := certCache.cacheIndex[name]
if len(allCertKeys) == 0 {
return Certificate{}, false
}
// and replace the old certificate with the new one
err = certCache.replaceCertificate(oldCert, newCert)
if err != nil {
return fmt.Errorf("replacing certificate %v: %v", oldCert.Names, err)
}
return nil
cert, ok := certCache.cache[allCertKeys[0]]
return cert, ok
}
var defaultCache *Cache
var defaultCacheMu sync.Mutex
// TODO: This seems unused (but could be useful if TLS
// handshakes serve up different certs for a single
// name depending on other properties such as key type)
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
certCache.mu.RLock()
defer certCache.mu.RUnlock()
allCertKeys := certCache.cacheIndex[name]
certs := make([]Certificate, len(allCertKeys))
for i := range allCertKeys {
certs[i] = certCache.cache[allCertKeys[i]]
}
return certs
}
func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
cfg, err := certCache.options.GetConfigForCert(cert)
if err != nil {
return nil, err
}
if cfg.certCache != nil && cfg.certCache != certCache {
return nil, fmt.Errorf("config returned for certificate %v is not nil and points to different cache; got %p, expected %p (this one)",
cert.Names, cfg.certCache, certCache)
}
return New(certCache, cfg), nil
}
var (
defaultCache *Cache
defaultCacheMu sync.Mutex
)

View File

@@ -46,29 +46,21 @@ type Certificate struct {
// The hex-encoded hash of this cert's chain's bytes.
Hash string
// configs is the list of configs that use or refer to
// The first one is assumed to be the config that is
// "in charge" of this certificate (i.e. determines
// whether it is managed, how it is managed, etc).
// This field will be populated by cacheCertificate.
// Only meddle with it if you know what you're doing!
configs []*Config
// whether this certificate is under our management
// Whether this certificate is under our management
managed bool
}
// NeedsRenewal returns true if the certificate is
// expiring soon or has expired.
func (c Certificate) NeedsRenewal() bool {
if c.NotAfter.IsZero() {
// expiring soon (according to cfg) or has expired.
func (cert Certificate) NeedsRenewal(cfg *Config) bool {
if cert.NotAfter.IsZero() {
return false
}
renewDurationBefore := DefaultRenewDurationBefore
if len(c.configs) > 0 && c.configs[0].RenewDurationBefore > 0 {
renewDurationBefore = c.configs[0].RenewDurationBefore
if cfg.RenewDurationBefore > 0 {
renewDurationBefore = cfg.RenewDurationBefore
}
return time.Until(c.NotAfter) < renewDurationBefore
return time.Until(cert.NotAfter) < renewDurationBefore
}
// CacheManagedCertificate loads the certificate for domain into the
@@ -79,19 +71,30 @@ func (c Certificate) NeedsRenewal() bool {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
cert, err := cfg.loadManagedCertificate(domain)
if err != nil {
return cert, err
}
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_managed_cert", cert.Names)
}
return cert, nil
}
// loadManagedCertificate loads the managed certificate for domain,
// but it does not add it to the cache. It just loads from storage.
func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
certRes, err := cfg.loadCertResource(domain)
if err != nil {
return Certificate{}, err
}
cert, err := cfg.makeCertificateWithOCSP(certRes.Certificate, certRes.PrivateKey)
cert, err := makeCertificateWithOCSP(cfg.Storage, certRes.Certificate, certRes.PrivateKey)
if err != nil {
return cert, err
}
cert.managed = true
if cfg.OnEvent != nil {
cfg.OnEvent("cached_managed_cert", cert.Names)
}
return cfg.cacheCertificate(cert), nil
return cert, nil
}
// CacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
@@ -100,11 +103,11 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
cert, err := cfg.makeCertificateFromDiskWithOCSP(certFile, keyFile)
cert, err := makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
@@ -121,14 +124,14 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
if err != nil {
return err
}
err = cfg.certCache.stapleOCSP(&cert, nil)
err = stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
return nil
}
@@ -137,11 +140,11 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate) error {
//
// This method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
cert, err := makeCertificateWithOCSP(cfg.Storage, certBytes, keyBytes)
if err != nil {
return err
}
cfg.cacheCertificate(cert)
cfg.certCache.cacheCertificate(cert)
if cfg.OnEvent != nil {
cfg.OnEvent("cached_unmanaged_cert", cert.Names)
}
@@ -152,7 +155,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte)
// certificate and key files. It fills out all the fields in
// the certificate except for the Managed and OnDemand flags.
// (It is up to the caller to set those.) It staples OCSP.
func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Certificate, error) {
func makeCertificateFromDiskWithOCSP(storage Storage, certFile, keyFile string) (Certificate, error) {
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return Certificate{}, err
@@ -161,7 +164,21 @@ func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Ce
if err != nil {
return Certificate{}, err
}
return cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
return makeCertificateWithOCSP(storage, certPEMBlock, keyPEMBlock)
}
// makeCertificateWithOCSP is the same as makeCertificate except that it also
// staples OCSP to the certificate.
func makeCertificateWithOCSP(storage Storage, certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
cert, err := makeCertificate(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
err = stapleOCSP(storage, &cert, certPEMBlock)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
return cert, nil
}
// makeCertificate turns a certificate PEM bundle and a key PEM block into
@@ -169,7 +186,7 @@ func (cfg *Config) makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Ce
// its struct fields for convenience (except for the OnDemand and Managed
// flags; it is up to the caller to set those properties!). This function
// does NOT staple OCSP.
func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
var cert Certificate
// Convert to a tls.Certificate
@@ -187,20 +204,6 @@ func (*Config) makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, e
return cert, nil
}
// makeCertificateWithOCSP is the same as makeCertificate except that it also
// staples OCSP to the certificate.
func (cfg *Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
cert, err := cfg.makeCertificate(certPEMBlock, keyPEMBlock)
if err != nil {
return cert, err
}
err = cfg.certCache.stapleOCSP(&cert, certPEMBlock)
if err != nil {
log.Printf("[WARNING] Stapling OCSP: %v", err)
}
return cert, nil
}
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
if len(tlsCert.Certificate) == 0 {
@@ -253,11 +256,7 @@ func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
// means that another instance renewed the certificate in the
// meantime, and it would be a good idea to simply load the cert
// into our cache rather than repeating the renewal process again.
func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
if len(cert.configs) == 0 {
return false, fmt.Errorf("no configs for certificate")
}
cfg := cert.configs[0]
func (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
certRes, err := cfg.loadCertResource(cert.Names[0])
if err != nil {
return false, err
@@ -274,54 +273,17 @@ func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
return timeLeft < cfg.RenewDurationBefore, nil
}
// cacheCertificate adds cert to the in-memory cache. If a certificate
// with the same hash is already cached, it is NOT overwritten; instead,
// cfg is added to the existing certificate's list of configs if not
// already in the list. Then all the names on cert are used to add
// entries to cfg.certificates (the config's name lookup map).
// Then the certificate is stored/updated in the cache. It returns
// a copy of the certificate that ends up being stored in the cache.
//
// It is VERY important, even for some test cases, that the Hash field
// of the cert be set properly.
//
// This function is safe for concurrent use.
func (cfg *Config) cacheCertificate(cert Certificate) Certificate {
cfg.certCache.mu.Lock()
defer cfg.certCache.mu.Unlock()
// if this certificate already exists in the cache,
// use it instead of overwriting it -- very important!
if existingCert, ok := cfg.certCache.cache[cert.Hash]; ok {
cert = existingCert
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
// on oldCert into the cache, from storage. This also replaces the old certificate
// with the new one, so that all configurations that used the old cert now point
// to the new cert.
func (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
newCert, err := cfg.loadManagedCertificate(oldCert.Names[0])
if err != nil {
return fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
}
// attach this config to the certificate so we know which
// configs are referencing/using the certificate, but don't
// duplicate entries
var found bool
for _, c := range cert.configs {
if c == cfg {
found = true
break
}
}
if !found {
cert.configs = append(cert.configs, cfg)
}
// key the certificate by all its names for this config only,
// this is how we find the certificate during handshakes
// (yes, if certs overlap in the names they serve, one will
// overwrite another here, but that's just how it goes)
for _, name := range cert.Names {
cfg.certificates[NormalizedName(name)] = cert.Hash
}
// store the certificate
cfg.certCache.cache[cert.Hash] = cert
return cert
cfg.certCache.replaceCertificate(oldCert, newCert)
return nil
}
// HostQualifies returns true if the hostname alone

View File

@@ -23,8 +23,9 @@
// a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic
// makes it easy. See the HTTPS, Listen, and TLS functions for that.
//
// If you need more control, create a Config using New() and then call
// Manage() on the config; but you'll have to be sure to solve the HTTP
// If you need more control, create a Cache using NewCache() and then make
// a Config using New(). You can then call Manage() on the config. But if
// you use this lower-level API, you'll have to be sure to solve the HTTP
// and TLS-ALPN challenges yourself (unless you disabled them or use the
// DNS challenge) by using the provided Config.GetCertificate function
// in your tls.Config and/or Config.HTTPChallangeHandler in your HTTP
@@ -45,22 +46,22 @@ import (
"sync/atomic"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/challenge"
"github.com/go-acme/lego/certcrypto"
)
// HTTPS serves mux for all domainNames using the HTTP
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
// It uses the Default config.
//
// This high-level convenience function is opinionated and
// applies sane defaults for production use, including
// timeouts for HTTP requests and responses. To allow very
// long-lived requests or connections, you should make your
// own http.Server values and use this package's Listen(),
// TLS(), or Config.TLSConfig() functions to customize to
// your needs. For example, servers which need to support
// large uploads or downloads with slow clients may need to
// use longer timeouts, thus this function is not suitable.
// long-lived connections, you should make your own
// http.Server values and use this package's Listen(), TLS(),
// or Config.TLSConfig() functions to customize to your needs.
// For example, servers which need to support large uploads or
// downloads with slow clients may need to use longer timeouts,
// thus this function is not suitable.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
@@ -69,7 +70,10 @@ func HTTPS(domainNames []string, mux http.Handler) error {
mux = http.DefaultServeMux
}
cfg, err := manageWithDefaultConfig(domainNames, false)
Default.Agreed = true
cfg := NewDefault()
err := cfg.Manage(domainNames)
if err != nil {
return err
}
@@ -154,31 +158,40 @@ func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
}
// TLS enables management of certificates for domainNames
// and returns a valid tls.Config.
// and returns a valid tls.Config. It uses the Default
// config.
//
// Because this is a convenience function that returns
// only a tls.Config, it does not assume HTTP is being
// served on the HTTP port, so the HTTP challenge is
// disabled (no HTTPChallengeHandler is necessary).
// disabled (no HTTPChallengeHandler is necessary). The
// package variable Default is modified so that the
// HTTP challenge is disabled.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func TLS(domainNames []string) (*tls.Config, error) {
cfg, err := manageWithDefaultConfig(domainNames, true)
return cfg.TLSConfig(), err
Default.Agreed = true
Default.DisableHTTPChallenge = true
cfg := NewDefault()
return cfg.TLSConfig(), cfg.Manage(domainNames)
}
// Listen manages certificates for domainName and returns a
// TLS listener.
// TLS listener. It uses the Default config.
//
// Because this convenience function returns only a TLS-enabled
// listener and does not presume HTTP is also being served,
// the HTTP challenge will be disabled.
// the HTTP challenge will be disabled. The package variable
// Default is modified so that the HTTP challenge is disabled.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func Listen(domainNames []string) (net.Listener, error) {
cfg, err := manageWithDefaultConfig(domainNames, true)
Default.Agreed = true
Default.DisableHTTPChallenge = true
cfg := NewDefault()
err := cfg.Manage(domainNames)
if err != nil {
return nil, err
}
@@ -186,36 +199,36 @@ func Listen(domainNames []string) (net.Listener, error) {
}
// Manage obtains certificates for domainNames and keeps them
// renewed using the returned Config.
// renewed using the Default config.
//
// This is a slightly lower-level function; you will need to
// wire up support for the ACME challenges yourself. You can
// obtain a Config to help you do that by calling NewDefault().
//
// You will need to ensure that you use a TLS config that gets
// certificates from this Config and that the HTTP and TLS-ALPN
// challenges can be solved. The easiest way to do this is to
// use cfg.TLSConfig() as your TLS config and to wrap your
// HTTP handler with cfg.HTTPChallengeHandler(). If you don't
// have an HTTP server, you will need to disable the HTTP
// challenge.
// use NewDefault().TLSConfig() as your TLS config and to wrap
// your HTTP handler with NewDefault().HTTPChallengeHandler().
// If you don't have an HTTP server, you will need to disable
// the HTTP challenge.
//
// If you already have a TLS config you want to use, you can
// simply set its GetCertificate field to cfg.GetCertificate.
// simply set its GetCertificate field to
// NewDefault().GetCertificate.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
func Manage(domainNames []string) (cfg *Config, err error) {
return manageWithDefaultConfig(domainNames, false)
}
// manageWithDefaultConfig returns a TLS configuration that
// is fully managed for the given names, optionally
// with the HTTP challenge disabled.
func manageWithDefaultConfig(domainNames []string, disableHTTPChallenge bool) (*Config, error) {
cfg := NewDefault()
cfg.DisableHTTPChallenge = disableHTTPChallenge
return cfg, cfg.Manage(domainNames)
func Manage(domainNames []string) error {
Default.Agreed = true
return NewDefault().Manage(domainNames)
}
// OnDemandConfig contains some state relevant for providing
// on-demand TLS.
// on-demand TLS. Important note: If you are using the
// MaxObtain property to limit the maximum number of certs
// to be issued, the count of how many certs were issued
// will be reset if this struct gets garbage-collected.
type OnDemandConfig struct {
// If set, this function will be the absolute
// authority on whether the hostname (according
@@ -246,6 +259,8 @@ type OnDemandConfig struct {
// The number of certificates that have been issued on-demand
// by this config. It is only safe to modify this count atomically.
// If it reaches MaxObtain, on-demand issuances must fail.
// Note that this will necessarily be reset to 0 if the
// struct leaves scope and/or gets garbage-collected.
obtainedCount int32
}
@@ -405,98 +420,28 @@ func isInternal(addr string) bool {
return false
}
// Package defaults
var (
// The endpoint of the directory for the ACME
// CA we are to use
CA = LetsEncryptProductionCA
// The email address to use when creating or
// selecting an existing ACME server account
Email string
// The synchronization implementation - all
// instances of certmagic in a cluster must
// use the same value here, otherwise some
// cert operations will not be properly
// coordinated
Sync Locker
// Set to true if agreed to the CA's
// subscriber agreement
Agreed bool
// Disable all HTTP challenges
DisableHTTPChallenge bool
// Disable all TLS-ALPN challenges
DisableTLSALPNChallenge bool
// How long before expiration to renew certificates
RenewDurationBefore = DefaultRenewDurationBefore
// How long before expiration to require a renewed
// certificate when in interactive mode, like when
// the program is first starting up (see
// mholt/caddy#1680). A wider window between
// RenewDurationBefore and this value will suppress
// errors under duress (bad) but hopefully this duration
// will give it enough time for the blockage to be
// relieved.
RenewDurationBeforeAtStartup = DefaultRenewDurationBeforeAtStartup
// An optional event callback clients can set
// to subscribe to certain things happening
// internally by this config; invocations are
// synchronous, so make them return quickly!
OnEvent func(event string, data interface{})
// The host (ONLY the host, not port) to listen
// on if necessary to start a listener to solve
// an ACME challenge
ListenHost string
// The alternate port to use for the ACME HTTP
// challenge; if non-empty, this port will be
// used instead of HTTPChallengePort to spin up
// a listener for the HTTP challenge
AltHTTPPort int
// The alternate port to use for the ACME
// TLS-ALPN challenge; the system must forward
// TLSALPNChallengePort to this port for
// challenge to succeed
AltTLSALPNPort int
// The DNS provider to use when solving the
// ACME DNS challenge
DNSProvider challenge.Provider
// The type of key to use when generating
// certificates
KeyType = certcrypto.RSA2048
// The maximum amount of time to allow for
// obtaining a certificate. If empty, the
// default from the underlying lego lib is
// used. If set, it must not be too low so
// as to cancel orders too early, running
// the risk of rate limiting.
CertObtainTimeout time.Duration
// Set the default server name for clients
// not indicating a server name using SNI.
// In most cases this will be the primary
// domain that is being served.
DefaultServerName string
// The state needed to operate on-demand TLS
OnDemand *OnDemandConfig
// Add the must staple TLS extension to the
// CSR generated by lego/acme
MustStaple bool
)
// Default contains the package defaults for the
// various Config fields. This is used as a template
// when creating your own Configs with New(), and it
// is also used as the Config by all the high-level
// functions in this package.
//
// The fields of this value will be used for Config
// fields which are unset. Feel free to modify these
// defaults, but do not use this Config by itself: it
// is only a template. Valid configurations can be
// obtained by calling New() (if you have your own
// certificate cache) or NewDefault() (if you only
// need a single config and want to use the default
// cache). This is the only Config which can access
// the default certificate cache.
var Default = Config{
CA: LetsEncryptProductionCA,
RenewDurationBefore: DefaultRenewDurationBefore,
RenewDurationBeforeAtStartup: DefaultRenewDurationBeforeAtStartup,
KeyType: certcrypto.EC256,
Storage: defaultFileStorage,
}
const (
// HTTPChallengePort is the officially-designated port for
@@ -520,16 +465,16 @@ const (
var (
// HTTPPort is the port on which to serve HTTP
// and, by extension, the HTTP challenge (unless
// AltHTTPPort is set).
// Default.AltHTTPPort is set).
HTTPPort = 80
// HTTPSPort is the port on which to serve HTTPS
// and, by extension, the TLS-ALPN challenge
// (unless AltTLSALPNPort is set).
// (unless Default.AltTLSALPNPort is set).
HTTPSPort = 443
)
// Variables for conveniently serving HTTPS
// Variables for conveniently serving HTTPS.
var (
httpLn, httpsLn net.Listener
lnMu sync.Mutex

View File

@@ -23,12 +23,12 @@ import (
"sync"
"time"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
"github.com/xenolf/lego/registration"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/http01"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/lego"
"github.com/go-acme/lego/registration"
)
// acmeMu ensures that only one ACME challenge occurs at a time.
@@ -52,6 +52,13 @@ func listenerAddressInUse(addr string) bool {
return err == nil
}
func (cfg *Config) newManager(interactive bool) (Manager, error) {
if cfg.NewManager != nil {
return cfg.NewManager(interactive)
}
return cfg.newACMEClient(interactive)
}
func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
// look up or create the user account
leUser, err := cfg.getUser(cfg.Email)
@@ -62,15 +69,15 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
// ensure key type and timeout are set
keyType := cfg.KeyType
if keyType == "" {
keyType = KeyType
keyType = Default.KeyType
}
certObtainTimeout := cfg.CertObtainTimeout
if certObtainTimeout == 0 {
certObtainTimeout = CertObtainTimeout
certObtainTimeout = Default.CertObtainTimeout
}
// ensure CA URL (directory endpoint) is set
caURL := CA
caURL := Default.CA
if cfg.CA != "" {
caURL = cfg.CA
}
@@ -91,6 +98,7 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
clientKey := caURL + leUser.Email + string(keyType)
// if an underlying client with this configuration already exists, reuse it
// TODO: Could this be a global cache instead, perhaps?
cfg.acmeClientsMu.Lock()
client, ok := cfg.acmeClients[clientKey]
if !ok {
@@ -232,12 +240,12 @@ func (cfg *Config) lockKey(op, domainName string) string {
func (c *acmeClient) Obtain(name string) error {
// ensure idempotency of the obtain operation for this name
lockKey := c.config.lockKey("cert_acme", name)
err := c.config.certCache.storage.Lock(lockKey)
err := c.config.Storage.Lock(lockKey)
if err != nil {
return err
}
defer func() {
if err := c.config.certCache.storage.Unlock(lockKey); err != nil {
if err := c.config.Storage.Unlock(lockKey); err != nil {
log.Printf("[ERROR][%s] Obtain: Unable to unlock '%s': %v", name, lockKey, err)
}
}()
@@ -292,12 +300,12 @@ func (c *acmeClient) Obtain(name string) error {
func (c *acmeClient) Renew(name string) error {
// ensure idempotency of the renew operation for this name
lockKey := c.config.lockKey("cert_acme", name)
err := c.config.certCache.storage.Lock(lockKey)
err := c.config.Storage.Lock(lockKey)
if err != nil {
return err
}
defer func() {
if err := c.config.certCache.storage.Unlock(lockKey); err != nil {
if err := c.config.Storage.Unlock(lockKey); err != nil {
log.Printf("[ERROR][%s] Renew: Unable to unlock '%s': %v", name, lockKey, err)
}
}()
@@ -351,7 +359,7 @@ func (c *acmeClient) Renew(name string) error {
// Revoke revokes the certificate for name and deletes
// it from storage.
func (c *acmeClient) Revoke(name string) error {
if !c.config.certCache.storage.Exists(StorageKeys.SitePrivateKey(c.config.CA, name)) {
if !c.config.Storage.Exists(StorageKeys.SitePrivateKey(c.config.CA, name)) {
return fmt.Errorf("private key not found for %s", name)
}
@@ -369,15 +377,15 @@ func (c *acmeClient) Revoke(name string) error {
c.config.OnEvent("acme_cert_revoked", name)
}
err = c.config.certCache.storage.Delete(StorageKeys.SiteCert(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SiteCert(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate file: %v", err)
}
err = c.config.certCache.storage.Delete(StorageKeys.SitePrivateKey(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SitePrivateKey(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete private key: %v", err)
}
err = c.config.certCache.storage.Delete(StorageKeys.SiteMeta(c.config.CA, name))
err = c.config.Storage.Delete(StorageKeys.SiteMeta(c.config.CA, name))
if err != nil {
return fmt.Errorf("certificate revoked, but unable to delete certificate metadata: %v", err)
}
@@ -398,3 +406,6 @@ var (
UserAgent string
HTTPTimeout = 30 * time.Second
)
// Interface guard
var _ Manager = (*acmeClient)(nil)

View File

@@ -20,11 +20,11 @@ import (
"sync"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
"github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/lego"
)
// Config configures a certificate manager instance.
@@ -74,7 +74,7 @@ type Config struct {
ListenHost string
// The alternate port to use for the ACME HTTP
// challenge; if non-empty, this port will be
// challenge; if non-empty, this port will be
// used instead of HTTPChallengePort to spin up
// a listener for the HTTP challenge
AltHTTPPort int
@@ -113,117 +113,167 @@ type Config struct {
// CSR generated by lego/acme
MustStaple bool
// Map of hostname to certificate hash; used
// to complete handshakes and serve the right
// certificate given SNI
certificates map[string]string
// The storage to access when storing or
// loading TLS assets
Storage Storage
// Pointer to the certificate store to use
// NewManager returns a new Manager. If nil,
// an ACME client will be created and used.
NewManager func(interactive bool) (Manager, error)
// Pointer to the in-memory certificate cache
certCache *Cache
// Map of client config key to ACME clients
// so they can be reused
// TODO: It might be better if these were globally cached, rather than per-config, which are ephemeral... but maybe evict them after a certain time, like 1 day or something
acmeClients map[string]*lego.Client
acmeClientsMu *sync.Mutex
}
// NewDefault returns a new, valid, default config.
// NewDefault makes a valid config based on the package
// Default config. Most users will call this function
// instead of New() since most use cases require only a
// single config for any and all certificates.
//
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
// If your requirements are more advanced (for example,
// multiple configs depending on the certificate), then use
// New() instead. (You will need to make your own Cache
// first.) If you only need a single Config to manage your
// certs (even if that config changes, as long as it is the
// only one), customize the Default package variable before
// calling NewDefault().
//
// All calls to NewDefault() will return configs that use the
// same, default certificate cache. All configs returned
// by NewDefault() are based on the values of the fields of
// Default at the time it is called.
func NewDefault() *Config {
return New(Config{Agreed: true})
}
// New makes a valid config based on cfg and uses
// a default certificate cache. All calls to
// New() will use the same certificate cache.
func New(cfg Config) *Config {
return NewWithCache(nil, cfg)
}
// NewWithCache makes a valid new config based on cfg
// and uses the provided certificate cache. If certCache
// is nil, a new, default one will be created using
// DefaultStorage; or, if a default cache has already
// been created, it will be reused.
func NewWithCache(certCache *Cache, cfg Config) *Config {
// avoid nil pointers with sensible defaults,
// careful to initialize a default cache (which
// begins its maintenance goroutine) only if
// needed - and only once (we don't initialize
// it at package init to give importers a chance
// to set DefaultStorage if they so desire)
if certCache == nil {
defaultCacheMu.Lock()
if defaultCache == nil {
defaultCache = NewCache(DefaultStorage)
}
certCache = defaultCache
defaultCacheMu.Unlock()
defaultCacheMu.Lock()
if defaultCache == nil {
defaultCache = NewCache(CacheOptions{
// the cache will likely need to renew certificates,
// so it will need to know how to do that, which
// depends on the certificate being managed and which
// can change during the lifetime of the cache; this
// callback makes it possible to get the latest and
// correct config with which to manage the cert,
// but if the user does not provide one, we can only
// assume that we are to use the default config
GetConfigForCert: func(Certificate) (Config, error) {
return Default, nil
},
})
}
if certCache.storage == nil {
certCache.storage = DefaultStorage
certCache := defaultCache
defaultCacheMu.Unlock()
return newWithCache(certCache, Default)
}
// New makes a new, valid config based on cfg and
// uses the provided certificate cache. certCache
// MUST NOT be nil or this function will panic.
//
// Use this method when you have an advanced use case
// that requires a custom certificate cache and config
// that may differ from the Default. For example, if
// not all certificates are managed/renewed the same
// way, you need to make your own Cache value with a
// GetConfigForCert callback that returns the correct
// configuration for each certificate. However, for
// the vast majority of cases, there will be only a
// single Config, thus the default cache (which always
// uses the default Config) and default config will
// suffice, and you should use New() instead.
func New(certCache *Cache, cfg Config) *Config {
if certCache == nil {
panic("a certificate cache is required")
}
if certCache.options.GetConfigForCert == nil {
panic("cache must have GetConfigForCert set in its options")
}
return newWithCache(certCache, cfg)
}
// newWithCache ensures that cfg is a valid config by populating
// zero-value fields from the Default Config. If certCache is
// nil, this function panics.
func newWithCache(certCache *Cache, cfg Config) *Config {
if certCache == nil {
panic("cannot make a valid config without a pointer to a certificate cache")
}
// fill in default values
if cfg.CA == "" {
cfg.CA = CA
cfg.CA = Default.CA
}
if cfg.Email == "" {
cfg.Email = Email
cfg.Email = Default.Email
}
if cfg.OnDemand == nil {
cfg.OnDemand = OnDemand
cfg.OnDemand = Default.OnDemand
}
if !cfg.Agreed {
cfg.Agreed = Agreed
cfg.Agreed = Default.Agreed
}
if !cfg.DisableHTTPChallenge {
cfg.DisableHTTPChallenge = DisableHTTPChallenge
cfg.DisableHTTPChallenge = Default.DisableHTTPChallenge
}
if !cfg.DisableTLSALPNChallenge {
cfg.DisableTLSALPNChallenge = DisableTLSALPNChallenge
cfg.DisableTLSALPNChallenge = Default.DisableTLSALPNChallenge
}
if cfg.RenewDurationBefore == 0 {
cfg.RenewDurationBefore = RenewDurationBefore
cfg.RenewDurationBefore = Default.RenewDurationBefore
}
if cfg.RenewDurationBeforeAtStartup == 0 {
cfg.RenewDurationBeforeAtStartup = RenewDurationBeforeAtStartup
cfg.RenewDurationBeforeAtStartup = Default.RenewDurationBeforeAtStartup
}
if cfg.OnEvent == nil {
cfg.OnEvent = OnEvent
cfg.OnEvent = Default.OnEvent
}
if cfg.ListenHost == "" {
cfg.ListenHost = ListenHost
cfg.ListenHost = Default.ListenHost
}
if cfg.AltHTTPPort == 0 {
cfg.AltHTTPPort = AltHTTPPort
cfg.AltHTTPPort = Default.AltHTTPPort
}
if cfg.AltTLSALPNPort == 0 {
cfg.AltTLSALPNPort = AltTLSALPNPort
cfg.AltTLSALPNPort = Default.AltTLSALPNPort
}
if cfg.DNSProvider == nil {
cfg.DNSProvider = DNSProvider
cfg.DNSProvider = Default.DNSProvider
}
if cfg.KeyType == "" {
cfg.KeyType = KeyType
cfg.KeyType = Default.KeyType
}
if cfg.CertObtainTimeout == 0 {
cfg.CertObtainTimeout = CertObtainTimeout
cfg.CertObtainTimeout = Default.CertObtainTimeout
}
if cfg.DefaultServerName == "" {
cfg.DefaultServerName = DefaultServerName
cfg.DefaultServerName = Default.DefaultServerName
}
if cfg.OnDemand == nil {
cfg.OnDemand = OnDemand
cfg.OnDemand = Default.OnDemand
}
if !cfg.MustStaple {
cfg.MustStaple = MustStaple
cfg.MustStaple = Default.MustStaple
}
if cfg.Storage == nil {
cfg.Storage = Default.Storage
}
if cfg.NewManager == nil {
cfg.NewManager = Default.NewManager
}
// absolutely don't allow a nil storage,
// because that would make almost anything
// a config can do pointless
if cfg.Storage == nil {
cfg.Storage = defaultFileStorage
}
// ensure the unexported fields are valid
cfg.certificates = make(map[string]string)
cfg.certCache = certCache
cfg.acmeClients = make(map[string]*lego.Client)
cfg.acmeClientsMu = new(sync.Mutex)
@@ -272,7 +322,7 @@ func (cfg *Config) Manage(domainNames []string) error {
}
// for existing certificates, make sure it is renewed
if cert.NeedsRenewal() {
if cert.NeedsRenewal(cfg) {
err := cfg.RenewCert(domainName, false)
if err != nil {
return fmt.Errorf("%s: renewing certificate: %v", domainName, err)
@@ -303,11 +353,11 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
if skip {
return nil
}
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Obtain(name)
return manager.Obtain(name)
}
// RenewCert renews the certificate for name using cfg. It stows the
@@ -320,20 +370,20 @@ func (cfg *Config) RenewCert(name string, interactive bool) error {
if skip {
return nil
}
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Renew(name)
return manager.Renew(name)
}
// RevokeCert revokes the certificate for domain via ACME protocol.
func (cfg *Config) RevokeCert(domain string, interactive bool) error {
client, err := cfg.newACMEClient(interactive)
manager, err := cfg.newManager(interactive)
if err != nil {
return err
}
return client.Revoke(domain)
return manager.Revoke(domain)
}
// TLSConfig is an opinionated method that returns a
@@ -394,18 +444,26 @@ func (cfg *Config) storageHasCertResources(domain string) bool {
certKey := StorageKeys.SiteCert(cfg.CA, domain)
keyKey := StorageKeys.SitePrivateKey(cfg.CA, domain)
metaKey := StorageKeys.SiteMeta(cfg.CA, domain)
return cfg.certCache.storage.Exists(certKey) &&
cfg.certCache.storage.Exists(keyKey) &&
cfg.certCache.storage.Exists(metaKey)
return cfg.Storage.Exists(certKey) &&
cfg.Storage.Exists(keyKey) &&
cfg.Storage.Exists(metaKey)
}
// managedCertNeedsRenewal returns true if certRes is
// expiring soon or already expired, or if the process
// of checking the expiration returned an error.
func (cfg *Config) managedCertNeedsRenewal(certRes certificate.Resource) bool {
cert, err := cfg.makeCertificate(certRes.Certificate, certRes.PrivateKey)
cert, err := makeCertificate(certRes.Certificate, certRes.PrivateKey)
if err != nil {
return true
}
return cert.NeedsRenewal()
return cert.NeedsRenewal(cfg)
}
// Manager is a type that can manage a certificate.
// They are usually very short-lived.
type Manager interface {
Obtain(name string) error
Renew(name string) error
Revoke(name string) error
}

View File

@@ -26,8 +26,8 @@ import (
"fmt"
"hash/fnv"
"github.com/go-acme/lego/certificate"
"github.com/klauspost/cpuid"
"github.com/xenolf/lego/certificate"
)
// encodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
@@ -119,20 +119,20 @@ func (cfg *Config) saveCertResource(cert *certificate.Resource) error {
},
}
return storeTx(cfg.certCache.storage, all)
return storeTx(cfg.Storage, all)
}
func (cfg *Config) loadCertResource(domain string) (certificate.Resource, error) {
var certRes certificate.Resource
certBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
certBytes, err := cfg.Storage.Load(StorageKeys.SiteCert(cfg.CA, domain))
if err != nil {
return certRes, err
}
keyBytes, err := cfg.certCache.storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
keyBytes, err := cfg.Storage.Load(StorageKeys.SitePrivateKey(cfg.CA, domain))
if err != nil {
return certRes, err
}
metaBytes, err := cfg.certCache.storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
metaBytes, err := cfg.Storage.Load(StorageKeys.SiteMeta(cfg.CA, domain))
if err != nil {
return certRes, err
}

View File

@@ -25,7 +25,7 @@ import (
"sync/atomic"
"time"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/challenge/tlsalpn01"
)
// GetCertificate gets a certificate to satisfy clientHello. In getting
@@ -94,10 +94,6 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate, matched, defaulted bool) {
name := NormalizedName(hello.ServerName)
cfg.certCache.mu.RLock()
defer cfg.certCache.mu.RUnlock()
var certKey string
var ok bool
if name == "" {
@@ -108,8 +104,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
if err == nil {
addr = ip
}
if certKey, ok = cfg.certificates[addr]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(addr); ok {
matched = true
return
}
@@ -118,16 +113,14 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
// fall back to a "default" certificate, if specified
if cfg.DefaultServerName != "" {
normDefault := NormalizedName(cfg.DefaultServerName)
if certKey, ok := cfg.certificates[normDefault]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(normDefault); ok {
defaulted = true
return
}
}
} else {
// if SNI is specified, try an exact match first
if certKey, ok = cfg.certificates[name]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(name); ok {
matched = true
return
}
@@ -138,8 +131,7 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
for i := range labels {
labels[i] = "*"
candidate := strings.Join(labels, ".")
if certKey, ok = cfg.certificates[candidate]; ok {
cert = cfg.certCache.cache[certKey]
if cert, ok = cfg.certCache.getFirstMatchingCert(candidate); ok {
matched = true
return
}
@@ -153,7 +145,10 @@ func (cfg *Config) getCertificate(hello *tls.ClientHelloInfo) (cert Certificate,
// whether it complies with RFC 6066 about SNI, but I think
// it does, soooo...)
// (this is how we solved the former ACME TLS-SNI challenge)
if directCert, ok := cfg.certCache.cache[name]; ok {
cfg.certCache.mu.RLock()
directCert, ok := cfg.certCache.cache[name]
cfg.certCache.mu.RUnlock()
if ok {
cert = directCert
matched = true
return
@@ -316,7 +311,7 @@ func (cfg *Config) handshakeMaintenance(hello *tls.ClientHelloInfo, cert Certifi
if cert.OCSP != nil {
refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2)
if time.Now().After(refreshTime) {
err := cfg.certCache.stapleOCSP(&cert, nil)
err := stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is
// quite common considering not all certs have issuer URLs that support it.
@@ -362,15 +357,12 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
// even though the recursive nature of the dynamic cert loading
// would just call this function anyway, we do it here to
// make the replacement as atomic as possible.
newCert, err := currentCert.configs[0].CacheManagedCertificate(name)
newCert, err := cfg.CacheManagedCertificate(name)
if err != nil {
log.Printf("[ERROR] loading renewed certificate for %s: %v", name, err)
} else {
// replace the old certificate with the new one
err = cfg.certCache.replaceCertificate(currentCert, newCert)
if err != nil {
log.Printf("[ERROR] Replacing certificate for %s: %v", name, err)
}
cfg.certCache.replaceCertificate(currentCert, newCert)
}
}
@@ -396,7 +388,7 @@ func (cfg *Config) renewDynamicCertificate(hello *tls.ClientHelloInfo, currentCe
// A boolean true is returned if a valid certificate is returned.
func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) {
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(clientHello.ServerName)
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
if err != nil {
if _, ok := err.(ErrNotExist); ok {
return Certificate{}, false, nil

View File

@@ -20,7 +20,7 @@ import (
"net/http"
"strings"
"github.com/xenolf/lego/challenge/http01"
"github.com/go-acme/lego/challenge/http01"
)
// HTTPChallengeHandler wraps h in a handler that can solve the ACME
@@ -57,7 +57,7 @@ func (cfg *Config) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) b
if cfg.DisableHTTPChallenge {
return false
}
if !strings.HasPrefix(r.URL.Path, challengeBasePath) {
if !LooksLikeHTTPChallenge(r) {
return false
}
return cfg.distributedHTTPChallengeSolver(w, r)
@@ -73,7 +73,7 @@ func (cfg *Config) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http
}
tokenKey := distributedSolver{config: cfg}.challengeTokensKey(r.Host)
chalInfoBytes, err := cfg.certCache.storage.Load(tokenKey)
chalInfoBytes, err := cfg.Storage.Load(tokenKey)
if err != nil {
if _, ok := err.(ErrNotExist); !ok {
log.Printf("[ERROR][%s] Opening distributed HTTP challenge token file: %v", r.Host, err)
@@ -108,4 +108,10 @@ func answerHTTPChallenge(w http.ResponseWriter, r *http.Request, chalInfo challe
return false
}
// LooksLikeHTTPChallenge returns true if r looks like an ACME
// HTTP challenge request from an ACME server.
func LooksLikeHTTPChallenge(r *http.Request) bool {
return r.Method == "GET" && strings.HasPrefix(r.URL.Path, challengeBasePath)
}
const challengeBasePath = "/.well-known/acme-challenge"

View File

@@ -24,37 +24,34 @@ import (
// maintainAssets is a permanently-blocking function
// that loops indefinitely and, on a regular schedule, checks
// certificates for expiration and initiates a renewal of certs
// that are expiring soon. It also updates OCSP stapling and
// performs other maintenance of assets. It should only be
// called once per process.
//
// You must pass in the channel which you'll close when
// maintenance should stop, to allow this goroutine to clean up
// after itself and unblock. (Not that you HAVE to stop it...)
// that are expiring soon. It also updates OCSP stapling. It
// should only be called once per cache.
func (certCache *Cache) maintainAssets() {
renewalTicker := time.NewTicker(certCache.RenewInterval)
ocspTicker := time.NewTicker(certCache.OCSPInterval)
renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval)
ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval)
log.Printf("[INFO][%s] Started certificate maintenance routine", certCache.storage)
log.Printf("[INFO][cache:%p] Started certificate maintenance routine", certCache)
for {
select {
case <-renewalTicker.C:
log.Printf("[INFO][%s] Scanning for expiring certificates", certCache.storage)
log.Printf("[INFO][cache:%p] Scanning for expiring certificates", certCache)
err := certCache.RenewManagedCertificates(false)
if err != nil {
log.Printf("[ERROR][%s] Renewing managed certificates: %v", certCache.storage, err)
log.Printf("[ERROR][cache:%p] Renewing managed certificates: %v", certCache, err)
}
log.Printf("[INFO][%s] Done scanning certificates", certCache.storage)
log.Printf("[INFO][cache:%p] Done scanning certificates", certCache)
case <-ocspTicker.C:
log.Printf("[INFO][%s] Scanning for stale OCSP staples", certCache.storage)
log.Printf("[INFO][cache:%p] Scanning for stale OCSP staples", certCache)
certCache.updateOCSPStaples()
certCache.deleteOldStapleFiles()
log.Printf("[INFO][%s] Done checking OCSP staples", certCache.storage)
// certCache.deleteOldStapleFiles()
log.Printf("[INFO][cache:%p] Done checking OCSP staples", certCache)
case <-certCache.stopChan:
renewalTicker.Stop()
ocspTicker.Stop()
log.Printf("[INFO][%s] Stopped certificate maintenance routine", certCache.storage)
// TODO: stop any in-progress maintenance operations and clear locks we made
log.Printf("[INFO][cache:%p] Stopped certificate maintenance routine", certCache)
close(certCache.doneChan)
return
}
}
@@ -65,6 +62,10 @@ func (certCache *Cache) maintainAssets() {
// automatically on a regular basis; normally you will not
// need to call this.
func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// configs will hold a map of certificate name to the config
// to use when managing that certificate
configs := make(map[string]*Config)
// we use the queues for a very important reason: to do any and all
// operations that could require an exclusive write lock outside
// of the read lock! otherwise we get a deadlock, yikes. in other
@@ -75,11 +76,6 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
certCache.mu.RLock()
for certKey, cert := range certCache.cache {
if len(cert.configs) == 0 {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No associated TLS config for certificate with names %v; unable to manage", cert.Names)
continue
}
if !cert.managed {
continue
}
@@ -91,13 +87,26 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
continue
}
// get the config associated with this certificate
cfg, err := certCache.getConfig(cert)
if err != nil {
log.Printf("[ERROR] Getting configuration to manage certificate for names %v; unable to renew: %v", cert.Names, err)
continue
}
if cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage", cert.Names)
continue
}
configs[cert.Names[0]] = cfg
// if time is up or expires soon, we need to try to renew it
if cert.NeedsRenewal() {
if cert.NeedsRenewal(cfg) {
// see if the certificate in storage has already been renewed, possibly by another
// instance that didn't coordinate with this one; if so, just load it (this
// might happen if another instance already renewed it - kinda sloppy but checking disk
// first is a simple way to possibly drastically reduce rate limit problems)
storedCertExpiring, err := managedCertInStorageExpiresSoon(cert)
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(cert)
if err != nil {
// hmm, weird, but not a big deal, maybe it was deleted or something
log.Printf("[NOTICE] Error while checking if certificate for %v in storage is also expiring soon: %v",
@@ -125,7 +134,9 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
log.Printf("[INFO] Certificate for %v expires in %v, but is already renewed in storage; reloading stored certificate",
oldCert.Names, timeLeft)
err := certCache.reloadManagedCertificate(oldCert)
cfg := configs[oldCert.Names[0]]
err := cfg.reloadManagedCertificate(oldCert)
if err != nil {
if interactive {
return err // operator is present, so report error immediately
@@ -139,28 +150,30 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", oldCert.Names, timeLeft)
cfg := configs[oldCert.Names[0]]
// Get the name which we should use to renew this certificate;
// we only support managing certificates with one name per cert,
// so this should be easy.
renewName := oldCert.Names[0]
// perform renewal
err := oldCert.configs[0].RenewCert(renewName, interactive)
err := cfg.RenewCert(renewName, interactive)
if err != nil {
if interactive {
// Certificate renewal failed and the operator is present. See a discussion about
// this in issue mholt/caddy#642. For a while, we only stopped if the certificate
// was expired, but in reality, there is no difference between reporting it now
// versus later, except that there's somebody present to deal withit right now.
// versus later, except that there's somebody present to deal with it right now.
// Follow-up: See issue mholt/caddy#1680. Only fail in this case if the certificate
// is dangerously close to expiration.
timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
if timeLeft < oldCert.configs[0].RenewDurationBeforeAtStartup {
if timeLeft < cfg.RenewDurationBeforeAtStartup {
return err
}
}
log.Printf("[ERROR] %v", err)
if oldCert.configs[0].OnDemand != nil {
if cfg.OnDemand != nil {
// loaded dynamically, remove dynamically
deleteQueue = append(deleteQueue, oldCert)
}
@@ -169,7 +182,7 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// successful renewal, so update in-memory cache by loading
// renewed certificate so it will be used with handshakes
err = certCache.reloadManagedCertificate(oldCert)
err = cfg.reloadManagedCertificate(oldCert)
if err != nil {
if interactive {
return err // operator is present, so report error immediately
@@ -180,18 +193,7 @@ func (certCache *Cache) RenewManagedCertificates(interactive bool) error {
// Deletion queue
for _, cert := range deleteQueue {
certCache.mu.Lock()
// remove any pointers to this certificate from Configs
for _, cfg := range cert.configs {
for name, certKey := range cfg.certificates {
if certKey == cert.Hash {
delete(cfg.certificates, name)
}
}
}
// then delete the certificate from the cache
delete(certCache.cache, cert.Hash)
certCache.mu.Unlock()
certCache.removeCertificate(cert)
}
return nil
@@ -229,7 +231,18 @@ func (certCache *Cache) updateOCSPStaples() {
}
}
err := certCache.stapleOCSP(&cert, nil)
cfg, err := certCache.getConfig(cert)
if err != nil {
log.Printf("[ERROR] Getting configuration to manage OCSP for certificate with names %v; unable to refresh: %v", cert.Names, err)
continue
}
if cfg == nil {
// this is bad if this happens, probably a programmer error (oops)
log.Printf("[ERROR] No configuration associated with certificate for names %v; unable to manage OCSP", cert.Names)
continue
}
err = stapleOCSP(cfg.Storage, &cert, nil)
if err != nil {
if cert.OCSP != nil {
// if there was no staple before, that's fine; otherwise we should log the error
@@ -260,16 +273,32 @@ func (certCache *Cache) updateOCSPStaples() {
}
}
// deleteOldStapleFiles deletes cached OCSP staples that have expired.
// CleanStorageOptions specifies how to clean up a storage unit.
type CleanStorageOptions struct {
OCSPStaples bool
// TODO: long-expired certificates
}
// CleanStorage tidies up the given storage according to opts; this
// generally involves deleting assets which are no longer required.
// TODO: We should do this for long-expired certificates, too.
func (certCache *Cache) deleteOldStapleFiles() {
ocspKeys, err := certCache.storage.List(prefixOCSP, false)
func CleanStorage(storage Storage, opts CleanStorageOptions) {
if opts.OCSPStaples {
err := deleteOldOCSPStaples(storage)
if err != nil {
log.Printf("[ERROR] Deleting old OCSP staples: %v", err)
}
}
}
func deleteOldOCSPStaples(storage Storage) error {
ocspKeys, err := storage.List(prefixOCSP, false)
if err != nil {
// maybe just hasn't been created yet; no big deal
return
return nil
}
for _, key := range ocspKeys {
ocspBytes, err := certCache.storage.Load(key)
ocspBytes, err := storage.Load(key)
if err != nil {
log.Printf("[ERROR] While deleting old OCSP staples, unable to load staple file: %v", err)
continue
@@ -277,7 +306,7 @@ func (certCache *Cache) deleteOldStapleFiles() {
resp, err := ocsp.ParseResponse(ocspBytes, nil)
if err != nil {
// contents are invalid; delete it
err = certCache.storage.Delete(key)
err = storage.Delete(key)
if err != nil {
log.Printf("[ERROR] Purging corrupt staple file %s: %v", key, err)
}
@@ -285,17 +314,18 @@ func (certCache *Cache) deleteOldStapleFiles() {
}
if time.Now().After(resp.NextUpdate) {
// response has expired; delete it
err = certCache.storage.Delete(key)
err = storage.Delete(key)
if err != nil {
log.Printf("[ERROR] Purging expired staple file %s: %v", key, err)
}
}
}
return nil
}
const (
// DefaultRenewInterval is how often to check certificates for renewal.
DefaultRenewInterval = 12 * time.Hour
// DefaultRenewCheckInterval is how often to check certificates for renewal.
DefaultRenewCheckInterval = 12 * time.Hour
// DefaultRenewDurationBefore is how long before expiration to renew certificates.
DefaultRenewDurationBefore = (24 * time.Hour) * 30
@@ -304,6 +334,6 @@ const (
// a renewed certificate when the process is first starting up (see mholt/caddy#1680).
DefaultRenewDurationBeforeAtStartup = (24 * time.Hour) * 7
// DefaultOCSPInterval is how often to check if OCSP stapling needs updating.
DefaultOCSPInterval = 1 * time.Hour
// DefaultOCSPCheckInterval is how often to check if OCSP stapling needs updating.
DefaultOCSPCheckInterval = 1 * time.Hour
)

View File

@@ -35,7 +35,7 @@ import (
//
// Errors here are not necessarily fatal, it could just be that the
// certificate doesn't have an issuer URL.
func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
func stapleOCSP(storage Storage, cert *Certificate, pemBundle []byte) error {
if pemBundle == nil {
// we need a PEM encoding only for some function calls below
bundle := new(bytes.Buffer)
@@ -53,7 +53,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// First try to load OCSP staple from storage and see if
// we can still use it.
ocspStapleKey := StorageKeys.OCSPStaple(cert, pemBundle)
cachedOCSP, err := certCache.storage.Load(ocspStapleKey)
cachedOCSP, err := storage.Load(ocspStapleKey)
if err == nil {
resp, err := ocsp.ParseResponse(cachedOCSP, nil)
if err == nil {
@@ -69,7 +69,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// because we loaded it by name, whereas the maintenance routine
// just iterates the list of files, even if somehow a non-staple
// file gets in the folder. in this case we are sure it is corrupt.)
err := certCache.storage.Delete(ocspStapleKey)
err := storage.Delete(ocspStapleKey)
if err != nil {
log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
}
@@ -104,7 +104,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
cert.Certificate.OCSPStaple = ocspBytes
cert.OCSP = ocspResp
if gotNewOCSP {
err := certCache.storage.Store(ocspStapleKey, ocspBytes)
err := storage.Store(ocspStapleKey, ocspBytes)
if err != nil {
return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
}
@@ -121,7 +121,7 @@ func (certCache *Cache) stapleOCSP(cert *Certificate, pemBundle []byte) error {
// IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return
// values are nil, the OCSP status may be assumed OCSPUnknown.
//
// Borrowed from github.com/xenolf/lego
// Borrowed from github.com/go-acme/lego
func getOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
// TODO: Perhaps this should be synchronized too, with a Locker?

View File

@@ -20,8 +20,8 @@ import (
"log"
"path/filepath"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/tlsalpn01"
)
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
@@ -117,7 +117,7 @@ func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
return err
}
return dhs.config.certCache.storage.Store(dhs.challengeTokensKey(domain), infoBytes)
return dhs.config.Storage.Store(dhs.challengeTokensKey(domain), infoBytes)
}
// CleanUp invokes the underlying solver's CleanUp method
@@ -129,7 +129,7 @@ func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
}
}
return dhs.config.certCache.storage.Delete(dhs.challengeTokensKey(domain))
return dhs.config.Storage.Delete(dhs.challengeTokensKey(domain))
}
// challengeTokensPrefix returns the key prefix for challenge info.

View File

@@ -267,6 +267,3 @@ type ErrNotExist interface {
// defaultFileStorage is a convenient, default storage
// implementation using the local file system.
var defaultFileStorage = &FileStorage{Path: dataDir()}
// DefaultStorage is the default Storage implementation.
var DefaultStorage Storage = defaultFileStorage

View File

@@ -29,8 +29,8 @@ import (
"sort"
"strings"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/registration"
"github.com/go-acme/lego/acme"
"github.com/go-acme/lego/registration"
)
// user represents a Let's Encrypt user account.
@@ -79,10 +79,12 @@ func (cfg *Config) newUser(email string) (user, error) {
// will NOT be prompted and an empty email may be returned.
func (cfg *Config) getEmail(allowPrompts bool) error {
leEmail := cfg.Email
// First try package default email
if leEmail == "" {
leEmail = Email
leEmail = Default.Email
}
// Then try to get most recent user email from storage
var gotRecentEmail bool
if leEmail == "" {
@@ -96,13 +98,15 @@ func (cfg *Config) getEmail(allowPrompts bool) error {
if err != nil {
return err
}
// User might have just signified their agreement
cfg.Agreed = Default.Agreed
}
// save the email for later and ensure it is consistent
// for repeated use; then update cfg with our new defaults
Email = strings.TrimSpace(strings.ToLower(leEmail))
cfg.Email = Email
cfg.Agreed = Agreed
// for repeated use; then update cfg with the email
Default.Email = strings.TrimSpace(strings.ToLower(leEmail))
cfg.Email = Default.Email
return nil
}
@@ -111,7 +115,7 @@ func (cfg *Config) getAgreementURL() (string, error) {
if agreementTestURL != "" {
return agreementTestURL, nil
}
caURL := CA
caURL := Default.CA
if cfg.CA != "" {
caURL = cfg.CA
}
@@ -149,7 +153,7 @@ func (cfg *Config) promptUserForEmail() (string, error) {
return "", fmt.Errorf("reading email address: %v", err)
}
leEmail = strings.TrimSpace(leEmail)
Agreed = true
Default.Agreed = true
return leEmail, nil
}
@@ -161,7 +165,7 @@ func (cfg *Config) promptUserForEmail() (string, error) {
func (cfg *Config) getUser(email string) (user, error) {
var user user
regBytes, err := cfg.certCache.storage.Load(StorageKeys.UserReg(cfg.CA, email))
regBytes, err := cfg.Storage.Load(StorageKeys.UserReg(cfg.CA, email))
if err != nil {
if _, ok := err.(ErrNotExist); ok {
// create a new user
@@ -169,7 +173,7 @@ func (cfg *Config) getUser(email string) (user, error) {
}
return user, err
}
keyBytes, err := cfg.certCache.storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
keyBytes, err := cfg.Storage.Load(StorageKeys.UserPrivateKey(cfg.CA, email))
if err != nil {
if _, ok := err.(ErrNotExist); ok {
// create a new user
@@ -212,7 +216,7 @@ func (cfg *Config) saveUser(user user) error {
},
}
return storeTx(cfg.certCache.storage, all)
return storeTx(cfg.Storage, all)
}
// promptUserAgreement simply outputs the standard user
@@ -246,13 +250,13 @@ func (cfg *Config) askUserAgreement(agreementURL string) bool {
// account, errors here are discarded to simplify code flow in
// the caller, and errors are not important here anyway.
func (cfg *Config) mostRecentUserEmail() (string, bool) {
userList, err := cfg.certCache.storage.List(StorageKeys.UsersPrefix(cfg.CA), false)
userList, err := cfg.Storage.List(StorageKeys.UsersPrefix(cfg.CA), false)
if err != nil || len(userList) == 0 {
return "", false
}
sort.Slice(userList, func(i, j int) bool {
iInfo, _ := cfg.certCache.storage.Stat(userList[i])
jInfo, _ := cfg.certCache.storage.Stat(userList[j])
iInfo, _ := cfg.Storage.Stat(userList[i])
jInfo, _ := cfg.Storage.Stat(userList[j])
return jInfo.Modified.Before(iInfo.Modified)
})
user, err := cfg.getUser(path.Base(userList[0]))