mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-02 14:17:01 +01:00
caddytls: Better handle FileStorage and cleaning up locks on exit
This commit is contained in:
parent
b7028b139f
commit
8811853f6d
10 changed files with 101 additions and 33 deletions
8
caddy.go
8
caddy.go
|
@ -469,9 +469,10 @@ func (i *Instance) Caddyfile() Input {
|
||||||
//
|
//
|
||||||
// This function blocks until all the servers are listening.
|
// This function blocks until all the servers are listening.
|
||||||
func Start(cdyfile Input) (*Instance, error) {
|
func Start(cdyfile Input) (*Instance, error) {
|
||||||
// set up the clustering plugin, if there is one (this should be done
|
// set up the clustering plugin, if there is one (and there should
|
||||||
// exactly once -- but we can't do it during init when they're still
|
// always be one) -- this should be done exactly once, but we can't
|
||||||
// getting plugged in, so do it when starting the first instance)
|
// do it during init while plugins are still registering, so do it
|
||||||
|
// when starting the first instance)
|
||||||
if atomic.CompareAndSwapInt32(&clusterPluginSetup, 0, 1) {
|
if atomic.CompareAndSwapInt32(&clusterPluginSetup, 0, 1) {
|
||||||
clusterPluginName := os.Getenv("CADDY_CLUSTERING")
|
clusterPluginName := os.Getenv("CADDY_CLUSTERING")
|
||||||
if clusterPluginName == "" {
|
if clusterPluginName == "" {
|
||||||
|
@ -486,6 +487,7 @@ func Start(cdyfile Input) (*Instance, error) {
|
||||||
return nil, fmt.Errorf("constructing cluster plugin %s: %v", clusterPluginName, err)
|
return nil, fmt.Errorf("constructing cluster plugin %s: %v", clusterPluginName, err)
|
||||||
}
|
}
|
||||||
certmagic.DefaultStorage = storage
|
certmagic.DefaultStorage = storage
|
||||||
|
OnProcessExit = append(OnProcessExit, certmagic.DefaultStorage.UnlockAllObtained)
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
|
inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
|
||||||
|
|
|
@ -98,7 +98,7 @@ func NewConfig(inst *caddy.Instance) *Config {
|
||||||
certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certmagic.Cache)
|
certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certmagic.Cache)
|
||||||
inst.StorageMu.RUnlock()
|
inst.StorageMu.RUnlock()
|
||||||
if !ok || certCache == nil {
|
if !ok || certCache == nil {
|
||||||
certCache = certmagic.NewCache(certmagic.FileStorage{Path: caddy.AssetsPath()})
|
certCache = certmagic.NewCache(certmagic.DefaultStorage)
|
||||||
inst.OnShutdown = append(inst.OnShutdown, func() error {
|
inst.OnShutdown = append(inst.OnShutdown, func() error {
|
||||||
certCache.Stop()
|
certCache.Stop()
|
||||||
return nil
|
return nil
|
||||||
|
@ -108,7 +108,7 @@ func NewConfig(inst *caddy.Instance) *Config {
|
||||||
inst.StorageMu.Unlock()
|
inst.StorageMu.Unlock()
|
||||||
}
|
}
|
||||||
return &Config{
|
return &Config{
|
||||||
Manager: certmagic.NewWithCache(certCache, certmagic.Config{}), // TODO
|
Manager: certmagic.NewWithCache(certCache, certmagic.Config{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ func setupTLS(c *caddy.Controller) error {
|
||||||
// a single certificate cache is used by the whole caddy.Instance; get a pointer to it
|
// a single certificate cache is used by the whole caddy.Instance; get a pointer to it
|
||||||
certCache, ok := c.Get(CertCacheInstStorageKey).(*certmagic.Cache)
|
certCache, ok := c.Get(CertCacheInstStorageKey).(*certmagic.Cache)
|
||||||
if !ok || certCache == nil {
|
if !ok || certCache == nil {
|
||||||
certCache = certmagic.NewCache(certmagic.FileStorage{Path: caddy.AssetsPath()})
|
certCache = certmagic.NewCache(certmagic.DefaultStorage)
|
||||||
c.OnShutdown(func() error {
|
c.OnShutdown(func() error {
|
||||||
certCache.Stop()
|
certCache.Stop()
|
||||||
return nil
|
return nil
|
||||||
|
@ -252,18 +252,6 @@ func setupTLS(c *caddy.Controller) error {
|
||||||
return c.Errf("Setting up DNS provider '%s': %v", dnsProvName, err)
|
return c.Errf("Setting up DNS provider '%s': %v", dnsProvName, err)
|
||||||
}
|
}
|
||||||
config.Manager.DNSProvider = dnsProv
|
config.Manager.DNSProvider = dnsProv
|
||||||
// TODO
|
|
||||||
// case "storage":
|
|
||||||
// args := c.RemainingArgs()
|
|
||||||
// if len(args) != 1 {
|
|
||||||
// return c.ArgErr()
|
|
||||||
// }
|
|
||||||
// storageProvName := args[0]
|
|
||||||
// storageProvConstr, ok := storageProviders[storageProvName]
|
|
||||||
// if !ok {
|
|
||||||
// return c.Errf("Unsupported Storage provider '%s'", args[0])
|
|
||||||
// }
|
|
||||||
// config.Manager.Storage = storageProvConstr
|
|
||||||
case "alpn":
|
case "alpn":
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
|
5
vendor/github.com/mholt/certmagic/cache.go
generated
vendored
5
vendor/github.com/mholt/certmagic/cache.go
generated
vendored
|
@ -165,6 +165,5 @@ func (certCache *Cache) reloadManagedCertificate(oldCert Certificate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultCache is a convenient, default certificate cache for
|
var defaultCache *Cache
|
||||||
// use by this process when no other certificate cache is provided.
|
var defaultCacheMu sync.Mutex
|
||||||
var defaultCache = NewCache(DefaultStorage)
|
|
||||||
|
|
10
vendor/github.com/mholt/certmagic/client.go
generated
vendored
10
vendor/github.com/mholt/certmagic/client.go
generated
vendored
|
@ -94,7 +94,7 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
|
||||||
legoCfg := lego.NewConfig(&leUser)
|
legoCfg := lego.NewConfig(&leUser)
|
||||||
legoCfg.CADirURL = caURL
|
legoCfg.CADirURL = caURL
|
||||||
legoCfg.KeyType = keyType
|
legoCfg.KeyType = keyType
|
||||||
legoCfg.UserAgent = UserAgent
|
legoCfg.UserAgent = buildUAString()
|
||||||
legoCfg.HTTPClient.Timeout = HTTPTimeout
|
legoCfg.HTTPClient.Timeout = HTTPTimeout
|
||||||
client, err = lego.NewClient(legoCfg)
|
client, err = lego.NewClient(legoCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -373,6 +373,14 @@ func (c *acmeClient) Revoke(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildUAString() string {
|
||||||
|
ua := "CertMagic"
|
||||||
|
if UserAgent != "" {
|
||||||
|
ua += " " + UserAgent
|
||||||
|
}
|
||||||
|
return ua
|
||||||
|
}
|
||||||
|
|
||||||
// Some default values passed down to the underlying lego client.
|
// Some default values passed down to the underlying lego client.
|
||||||
var (
|
var (
|
||||||
UserAgent string
|
UserAgent string
|
||||||
|
|
19
vendor/github.com/mholt/certmagic/config.go
generated
vendored
19
vendor/github.com/mholt/certmagic/config.go
generated
vendored
|
@ -125,15 +125,28 @@ func NewDefault() *Config {
|
||||||
// a default certificate cache. All calls to
|
// a default certificate cache. All calls to
|
||||||
// New() will use the same certificate cache.
|
// New() will use the same certificate cache.
|
||||||
func New(cfg Config) *Config {
|
func New(cfg Config) *Config {
|
||||||
return NewWithCache(defaultCache, cfg)
|
return NewWithCache(nil, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithCache makes a valid new config based on cfg
|
// NewWithCache makes a valid new config based on cfg
|
||||||
// and uses the provided certificate cache.
|
// 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 {
|
func NewWithCache(certCache *Cache, cfg Config) *Config {
|
||||||
// avoid nil pointers with sensible defaults
|
// 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 {
|
if certCache == nil {
|
||||||
|
defaultCacheMu.Lock()
|
||||||
|
if defaultCache == nil {
|
||||||
|
defaultCache = NewCache(DefaultStorage)
|
||||||
|
}
|
||||||
certCache = defaultCache
|
certCache = defaultCache
|
||||||
|
defaultCacheMu.Unlock()
|
||||||
}
|
}
|
||||||
if certCache.storage == nil {
|
if certCache.storage == nil {
|
||||||
certCache.storage = DefaultStorage
|
certCache.storage = DefaultStorage
|
||||||
|
|
49
vendor/github.com/mholt/certmagic/filestorage.go
generated
vendored
49
vendor/github.com/mholt/certmagic/filestorage.go
generated
vendored
|
@ -17,6 +17,7 @@ package certmagic
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -171,18 +172,36 @@ func (fs FileStorage) TryLock(key string) (Waiter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fw = &fileStorageWaiter{
|
fw = &fileStorageWaiter{
|
||||||
|
key: key,
|
||||||
filename: filepath.Join(lockDir, StorageKeys.safe(key)+".lock"),
|
filename: filepath.Join(lockDir, StorageKeys.safe(key)+".lock"),
|
||||||
wg: new(sync.WaitGroup),
|
wg: new(sync.WaitGroup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var checkedStaleLock bool // sentinel value to avoid infinite goto-ing
|
||||||
|
|
||||||
|
createLock:
|
||||||
// create the file in a special mode such that an
|
// create the file in a special mode such that an
|
||||||
// error is returned if it already exists
|
// error is returned if it already exists
|
||||||
lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644)
|
lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
// another process has the lock; use it to wait
|
// another process has the lock
|
||||||
|
|
||||||
|
// check to see if the lock is stale, if we haven't already
|
||||||
|
if !checkedStaleLock {
|
||||||
|
checkedStaleLock = true
|
||||||
|
if fs.lockFileStale(fw.filename) {
|
||||||
|
log.Printf("[INFO][%s] Lock for '%s' is stale; removing then retrying: %s",
|
||||||
|
fs, key, fw.filename)
|
||||||
|
os.Remove(fw.filename)
|
||||||
|
goto createLock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if lock is not stale, wait upon it
|
||||||
return fw, nil
|
return fw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, this was some unexpected error
|
// otherwise, this was some unexpected error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -225,10 +244,33 @@ func (fs FileStorage) Unlock(key string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnlockAllObtained removes all locks obtained by
|
||||||
|
// this instance of fs.
|
||||||
|
func (fs FileStorage) UnlockAllObtained() {
|
||||||
|
for key, fw := range fileStorageNameLocks {
|
||||||
|
err := fs.Unlock(fw.key)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR][%s] Releasing obtained lock for %s: %v", fs, key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs FileStorage) lockFileStale(filename string) bool {
|
||||||
|
info, err := os.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
|
return true // no good way to handle this, really; lock is useless?
|
||||||
|
}
|
||||||
|
return time.Since(info.ModTime()) > staleLockDuration
|
||||||
|
}
|
||||||
|
|
||||||
func (fs FileStorage) lockDir() string {
|
func (fs FileStorage) lockDir() string {
|
||||||
return filepath.Join(fs.Path, "locks")
|
return filepath.Join(fs.Path, "locks")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs FileStorage) String() string {
|
||||||
|
return "FileStorage:" + fs.Path
|
||||||
|
}
|
||||||
|
|
||||||
// fileStorageWaiter waits for a file to disappear; it
|
// fileStorageWaiter waits for a file to disappear; it
|
||||||
// polls the file system to check for the existence of
|
// polls the file system to check for the existence of
|
||||||
// a file. It also uses a WaitGroup to optimize the
|
// a file. It also uses a WaitGroup to optimize the
|
||||||
|
@ -237,6 +279,7 @@ func (fs FileStorage) lockDir() string {
|
||||||
// the lock will still block, but must wait for the
|
// the lock will still block, but must wait for the
|
||||||
// polling to get their answer.)
|
// polling to get their answer.)
|
||||||
type fileStorageWaiter struct {
|
type fileStorageWaiter struct {
|
||||||
|
key string
|
||||||
filename string
|
filename string
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
@ -259,3 +302,7 @@ var fileStorageNameLocksMu sync.Mutex
|
||||||
|
|
||||||
var _ Storage = FileStorage{}
|
var _ Storage = FileStorage{}
|
||||||
var _ Waiter = &fileStorageWaiter{}
|
var _ Waiter = &fileStorageWaiter{}
|
||||||
|
|
||||||
|
// staleLockDuration is the length of time
|
||||||
|
// before considering a lock to be stale.
|
||||||
|
const staleLockDuration = 2 * time.Hour
|
||||||
|
|
12
vendor/github.com/mholt/certmagic/maintain.go
generated
vendored
12
vendor/github.com/mholt/certmagic/maintain.go
generated
vendored
|
@ -38,21 +38,21 @@ func (certCache *Cache) maintainAssets() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-renewalTicker.C:
|
case <-renewalTicker.C:
|
||||||
log.Println("[INFO] Scanning for expiring certificates")
|
log.Printf("[INFO][%s] Scanning for expiring certificates", certCache.storage)
|
||||||
err := certCache.RenewManagedCertificates(false)
|
err := certCache.RenewManagedCertificates(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Renewing managed certificates: %v", err)
|
log.Printf("[ERROR][%s] Renewing managed certificates: %v", certCache.storage, err)
|
||||||
}
|
}
|
||||||
log.Println("[INFO] Done checking certificates")
|
log.Printf("[INFO][%s] Done scanning certificates", certCache.storage)
|
||||||
case <-ocspTicker.C:
|
case <-ocspTicker.C:
|
||||||
log.Println("[INFO] Scanning for stale OCSP staples")
|
log.Printf("[INFO][%s] Scanning for stale OCSP staples", certCache.storage)
|
||||||
certCache.updateOCSPStaples()
|
certCache.updateOCSPStaples()
|
||||||
certCache.deleteOldStapleFiles()
|
certCache.deleteOldStapleFiles()
|
||||||
log.Println("[INFO] Done checking OCSP staples")
|
log.Printf("[INFO][%s] Done checking OCSP staples", certCache.storage)
|
||||||
case <-certCache.stopChan:
|
case <-certCache.stopChan:
|
||||||
renewalTicker.Stop()
|
renewalTicker.Stop()
|
||||||
ocspTicker.Stop()
|
ocspTicker.Stop()
|
||||||
log.Println("[INFO] Stopped certificate maintenance routine")
|
log.Printf("[INFO][%s] Stopped certificate maintenance routine", certCache.storage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
vendor/github.com/mholt/certmagic/storage.go
generated
vendored
11
vendor/github.com/mholt/certmagic/storage.go
generated
vendored
|
@ -30,6 +30,8 @@ import (
|
||||||
// same Storage value (its implementation and configuration)
|
// same Storage value (its implementation and configuration)
|
||||||
// in order to share certificates and other TLS resources
|
// in order to share certificates and other TLS resources
|
||||||
// with the cluster.
|
// with the cluster.
|
||||||
|
//
|
||||||
|
// Implementations of Storage must be safe for concurrent use.
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
// Locker provides atomic synchronization
|
// Locker provides atomic synchronization
|
||||||
// operations, making Storage safe to share.
|
// operations, making Storage safe to share.
|
||||||
|
@ -87,6 +89,15 @@ type Locker interface {
|
||||||
// TryLock or if Unlock was not called at all. Unlock should also
|
// TryLock or if Unlock was not called at all. Unlock should also
|
||||||
// clean up any unused resources allocated during TryLock.
|
// clean up any unused resources allocated during TryLock.
|
||||||
Unlock(key string) error
|
Unlock(key string) error
|
||||||
|
|
||||||
|
// UnlockAllObtained removes all locks obtained by this process,
|
||||||
|
// upon which others may be waiting. The importer should call
|
||||||
|
// this on shutdowns (and crashes, ideally) to avoid leaving stale
|
||||||
|
// locks, but Locker implementations must NOT rely on this being
|
||||||
|
// the case and should anticipate and handle stale locks. Errors
|
||||||
|
// should be printed or logged, since there could be multiple,
|
||||||
|
// with no good way to handle them anyway.
|
||||||
|
UnlockAllObtained()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Waiter is a type that can block until a lock is released.
|
// Waiter is a type that can block until a lock is released.
|
||||||
|
|
2
vendor/manifest
vendored
2
vendor/manifest
vendored
|
@ -138,7 +138,7 @@
|
||||||
"importpath": "github.com/mholt/certmagic",
|
"importpath": "github.com/mholt/certmagic",
|
||||||
"repository": "https://github.com/mholt/certmagic",
|
"repository": "https://github.com/mholt/certmagic",
|
||||||
"vcs": "git",
|
"vcs": "git",
|
||||||
"revision": "dc98c40439d15f67021f10f0d9219a39d7cf2990",
|
"revision": "fe722057f2654b33cd528b8fd8b90e53fa495564",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue