Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
caddy
Commits
0b83014f
Commit
0b83014f
authored
Dec 19, 2018
by
Matthew Holt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
caddytls: Use latest certmagic package, with updated Storage interface
parent
0684cf86
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
282 additions
and
227 deletions
+282
-227
caddy.go
caddy.go
+0
-1
caddytls/setup.go
caddytls/setup.go
+1
-1
vendor/github.com/mholt/certmagic/certificates.go
vendor/github.com/mholt/certmagic/certificates.go
+1
-2
vendor/github.com/mholt/certmagic/certmagic.go
vendor/github.com/mholt/certmagic/certmagic.go
+32
-3
vendor/github.com/mholt/certmagic/client.go
vendor/github.com/mholt/certmagic/client.go
+26
-18
vendor/github.com/mholt/certmagic/config.go
vendor/github.com/mholt/certmagic/config.go
+49
-17
vendor/github.com/mholt/certmagic/crypto.go
vendor/github.com/mholt/certmagic/crypto.go
+33
-0
vendor/github.com/mholt/certmagic/filestorage.go
vendor/github.com/mholt/certmagic/filestorage.go
+128
-161
vendor/github.com/mholt/certmagic/storage.go
vendor/github.com/mholt/certmagic/storage.go
+11
-23
vendor/manifest
vendor/manifest
+1
-1
No files found.
caddy.go
View file @
0b83014f
...
@@ -487,7 +487,6 @@ func Start(cdyfile Input) (*Instance, error) {
...
@@ -487,7 +487,6 @@ 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
{})}
...
...
caddytls/setup.go
View file @
0b83014f
...
@@ -432,5 +432,5 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
...
@@ -432,5 +432,5 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
}
}
func
constructDefaultClusterPlugin
()
(
certmagic
.
Storage
,
error
)
{
func
constructDefaultClusterPlugin
()
(
certmagic
.
Storage
,
error
)
{
return
certmagic
.
FileStorage
{
Path
:
caddy
.
AssetsPath
()},
nil
return
&
certmagic
.
FileStorage
{
Path
:
caddy
.
AssetsPath
()},
nil
}
}
vendor/github.com/mholt/certmagic/certificates.go
View file @
0b83014f
...
@@ -64,12 +64,11 @@ func (c Certificate) NeedsRenewal() bool {
...
@@ -64,12 +64,11 @@ func (c Certificate) NeedsRenewal() bool {
if
c
.
NotAfter
.
IsZero
()
{
if
c
.
NotAfter
.
IsZero
()
{
return
false
return
false
}
}
timeLeft
:=
c
.
NotAfter
.
UTC
()
.
Sub
(
time
.
Now
()
.
UTC
())
renewDurationBefore
:=
DefaultRenewDurationBefore
renewDurationBefore
:=
DefaultRenewDurationBefore
if
len
(
c
.
configs
)
>
0
&&
c
.
configs
[
0
]
.
RenewDurationBefore
>
0
{
if
len
(
c
.
configs
)
>
0
&&
c
.
configs
[
0
]
.
RenewDurationBefore
>
0
{
renewDurationBefore
=
c
.
configs
[
0
]
.
RenewDurationBefore
renewDurationBefore
=
c
.
configs
[
0
]
.
RenewDurationBefore
}
}
return
time
Left
<
renewDurationBefore
return
time
.
Until
(
c
.
NotAfter
)
<
renewDurationBefore
}
}
// CacheManagedCertificate loads the certificate for domain into the
// CacheManagedCertificate loads the certificate for domain into the
...
...
vendor/github.com/mholt/certmagic/certmagic.go
View file @
0b83014f
...
@@ -52,6 +52,16 @@ import (
...
@@ -52,6 +52,16 @@ import (
// HTTPS serves mux for all domainNames using the HTTP
// HTTPS serves mux for all domainNames using the HTTP
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
// and HTTPS ports, redirecting all HTTP requests to HTTPS.
//
//
// 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.
//
// Calling this function signifies your acceptance to
// Calling this function signifies your acceptance to
// the CA's Subscriber Agreement and/or Terms of Service.
// the CA's Subscriber Agreement and/or Terms of Service.
func
HTTPS
(
domainNames
[]
string
,
mux
http
.
Handler
)
error
{
func
HTTPS
(
domainNames
[]
string
,
mux
http
.
Handler
)
error
{
...
@@ -96,13 +106,32 @@ func HTTPS(domainNames []string, mux http.Handler) error {
...
@@ -96,13 +106,32 @@ func HTTPS(domainNames []string, mux http.Handler) error {
hln
,
hsln
:=
httpLn
,
httpsLn
hln
,
hsln
:=
httpLn
,
httpsLn
lnMu
.
Unlock
()
lnMu
.
Unlock
()
httpHandler
:=
cfg
.
HTTPChallengeHandler
(
http
.
HandlerFunc
(
httpRedirectHandler
))
// create HTTP/S servers that are configured
// with sane default timeouts and appropriate
// handlers (the HTTP server solves the HTTP
// challenge and issues redirects to HTTPS,
// while the HTTPS server simply serves the
// user's handler)
httpServer
:=
&
http
.
Server
{
ReadHeaderTimeout
:
5
*
time
.
Second
,
ReadTimeout
:
5
*
time
.
Second
,
WriteTimeout
:
5
*
time
.
Second
,
IdleTimeout
:
5
*
time
.
Second
,
Handler
:
cfg
.
HTTPChallengeHandler
(
http
.
HandlerFunc
(
httpRedirectHandler
)),
}
httpsServer
:=
&
http
.
Server
{
ReadHeaderTimeout
:
10
*
time
.
Second
,
ReadTimeout
:
30
*
time
.
Second
,
WriteTimeout
:
2
*
time
.
Minute
,
IdleTimeout
:
5
*
time
.
Minute
,
Handler
:
mux
,
}
log
.
Printf
(
"%v Serving HTTP->HTTPS on %s and %s"
,
log
.
Printf
(
"%v Serving HTTP->HTTPS on %s and %s"
,
domainNames
,
hln
.
Addr
(),
hsln
.
Addr
())
domainNames
,
hln
.
Addr
(),
hsln
.
Addr
())
go
http
.
Serve
(
hln
,
httpHandler
)
go
http
Server
.
Serve
(
hln
)
return
http
.
Serve
(
hsln
,
mux
)
return
http
sServer
.
Serve
(
hsln
)
}
}
func
httpRedirectHandler
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
func
httpRedirectHandler
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
...
...
vendor/github.com/mholt/certmagic/client.go
View file @
0b83014f
...
@@ -208,6 +208,8 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
...
@@ -208,6 +208,8 @@ func (cfg *Config) newACMEClient(interactive bool) (*acmeClient, error) {
return
c
,
nil
return
c
,
nil
}
}
// lockKey returns a key for a lock that is specific to the operation
// named op being performed related to domainName and this config's CA.
func
(
cfg
*
Config
)
lockKey
(
op
,
domainName
string
)
string
{
func
(
cfg
*
Config
)
lockKey
(
op
,
domainName
string
)
string
{
return
fmt
.
Sprintf
(
"%s:%s:%s"
,
op
,
domainName
,
cfg
.
CA
)
return
fmt
.
Sprintf
(
"%s:%s:%s"
,
op
,
domainName
,
cfg
.
CA
)
}
}
...
@@ -215,30 +217,34 @@ func (cfg *Config) lockKey(op, domainName string) string {
...
@@ -215,30 +217,34 @@ func (cfg *Config) lockKey(op, domainName string) string {
// Obtain obtains a single certificate for name. It stores the certificate
// Obtain obtains a single certificate for name. It stores the certificate
// on the disk if successful. This function is safe for concurrent use.
// on the disk if successful. This function is safe for concurrent use.
//
//
// Right now our storage mechanism only supports one name per certificate,
// Our storage mechanism only supports one name per certificate, so this
// so this function (along with Renew and Revoke) only accepts one domain
// function (along with Renew and Revoke) only accepts one domain as input.
// as input. It can be easily modified to support SAN certificates if our
// It could be easily modified to support SAN certificates if our storage
// storage mechanism is upgraded later.
// mechanism is upgraded later, but that will increase logical complexity
// in other areas.
//
//
// Callers who have access to a Config value should use the ObtainCert
// Callers who have access to a Config value should use the ObtainCert
// method on that instead of this lower-level method.
// method on that instead of this lower-level method.
func
(
c
*
acmeClient
)
Obtain
(
name
string
)
error
{
func
(
c
*
acmeClient
)
Obtain
(
name
string
)
error
{
// ensure idempotency of the obtain operation for this name
lockKey
:=
c
.
config
.
lockKey
(
"cert_acme"
,
name
)
lockKey
:=
c
.
config
.
lockKey
(
"cert_acme"
,
name
)
waiter
,
err
:=
c
.
config
.
certCache
.
storage
.
Try
Lock
(
lockKey
)
err
:=
c
.
config
.
certCache
.
storage
.
Lock
(
lockKey
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
if
waiter
!=
nil
{
log
.
Printf
(
"[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting"
,
name
)
waiter
.
Wait
()
return
nil
// we assume the process with the lock succeeded, rather than hammering this execution path again
}
defer
func
()
{
defer
func
()
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
log
.
Printf
(
"[ERROR]
Unable to unlock obtain call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR]
[%s] Obtain: Unable to unlock '%s': %v"
,
name
,
lockKey
,
err
)
}
}
}()
}()
// check if obtain is still needed -- might have
// been obtained during lock
if
c
.
config
.
storageHasCertResources
(
name
)
{
log
.
Printf
(
"[INFO][%s] Obtain: Certificate already exists in storage"
,
name
)
return
nil
}
for
attempts
:=
0
;
attempts
<
2
;
attempts
++
{
for
attempts
:=
0
;
attempts
<
2
;
attempts
++
{
request
:=
certificate
.
ObtainRequest
{
request
:=
certificate
.
ObtainRequest
{
Domains
:
[]
string
{
name
},
Domains
:
[]
string
{
name
},
...
@@ -280,19 +286,15 @@ func (c *acmeClient) Obtain(name string) error {
...
@@ -280,19 +286,15 @@ func (c *acmeClient) Obtain(name string) error {
// Callers who have access to a Config value should use the RenewCert
// Callers who have access to a Config value should use the RenewCert
// method on that instead of this lower-level method.
// method on that instead of this lower-level method.
func
(
c
*
acmeClient
)
Renew
(
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
)
lockKey
:=
c
.
config
.
lockKey
(
"cert_acme"
,
name
)
waiter
,
err
:=
c
.
config
.
certCache
.
storage
.
Try
Lock
(
lockKey
)
err
:=
c
.
config
.
certCache
.
storage
.
Lock
(
lockKey
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
if
waiter
!=
nil
{
log
.
Printf
(
"[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting"
,
name
)
waiter
.
Wait
()
return
nil
// assume that the worker that renewed the cert succeeded to avoid hammering this path over and over
}
defer
func
()
{
defer
func
()
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
if
err
:=
c
.
config
.
certCache
.
storage
.
Unlock
(
lockKey
);
err
!=
nil
{
log
.
Printf
(
"[ERROR]
Unable to unlock renew call for %s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR]
[%s] Renew: Unable to unlock '%s': %v"
,
name
,
lockKey
,
err
)
}
}
}()
}()
...
@@ -302,6 +304,12 @@ func (c *acmeClient) Renew(name string) error {
...
@@ -302,6 +304,12 @@ func (c *acmeClient) Renew(name string) error {
return
err
return
err
}
}
// Check if renew is still needed - might have been renewed while waiting for lock
if
!
c
.
config
.
managedCertNeedsRenewal
(
certRes
)
{
log
.
Printf
(
"[INFO][%s] Renew: Certificate appears to have been renewed already"
,
name
)
return
nil
}
// Perform renewal and retry if necessary, but not too many times.
// Perform renewal and retry if necessary, but not too many times.
var
newCertMeta
*
certificate
.
Resource
var
newCertMeta
*
certificate
.
Resource
var
success
bool
var
success
bool
...
...
vendor/github.com/mholt/certmagic/config.go
View file @
0b83014f
...
@@ -21,6 +21,7 @@ import (
...
@@ -21,6 +21,7 @@ import (
"time"
"time"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego"
"github.com/xenolf/lego/lego"
...
@@ -277,12 +278,6 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
...
@@ -277,12 +278,6 @@ func (cfg *Config) ObtainCert(name string, interactive bool) error {
return
nil
return
nil
}
}
// we expect this to be a new site; if the
// cert already exists, then no-op
if
cfg
.
certCache
.
storage
.
Exists
(
StorageKeys
.
SiteCert
(
cfg
.
CA
,
name
))
{
return
nil
}
client
,
err
:=
cfg
.
newACMEClient
(
interactive
)
client
,
err
:=
cfg
.
newACMEClient
(
interactive
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
...
@@ -317,24 +312,37 @@ func (cfg *Config) RevokeCert(domain string, interactive bool) error {
...
@@ -317,24 +312,37 @@ func (cfg *Config) RevokeCert(domain string, interactive bool) error {
return
client
.
Revoke
(
domain
)
return
client
.
Revoke
(
domain
)
}
}
// TLSConfig returns a TLS configuration that
// TLSConfig is an opinionated method that returns a
// can be used to configure TLS listeners. It
// recommended, modern TLS configuration that can be
// supports the TLS-ALPN challenge and serves
// used to configure TLS listeners, which also supports
// up certificates managed by cfg.
// the TLS-ALPN challenge and serves up certificates
// managed by cfg.
//
// Unlike the package TLS() function, this method does
// not, by itself, enable certificate management for
// any domain names.
//
// Feel free to further customize the returned tls.Config,
// but do not mess with the GetCertificate or NextProtos
// fields unless you know what you're doing, as they're
// necessary to solve the TLS-ALPN challenge.
func
(
cfg
*
Config
)
TLSConfig
()
*
tls
.
Config
{
func
(
cfg
*
Config
)
TLSConfig
()
*
tls
.
Config
{
return
&
tls
.
Config
{
return
&
tls
.
Config
{
// these two fields necessary for TLS-ALPN challenge
GetCertificate
:
cfg
.
GetCertificate
,
GetCertificate
:
cfg
.
GetCertificate
,
NextProtos
:
[]
string
{
"h2"
,
"http/1.1"
,
tlsalpn01
.
ACMETLS1Protocol
},
NextProtos
:
[]
string
{
"h2"
,
"http/1.1"
,
tlsalpn01
.
ACMETLS1Protocol
},
// the rest recommended for modern TLS servers
MinVersion
:
tls
.
VersionTLS12
,
CurvePreferences
:
[]
tls
.
CurveID
{
tls
.
X25519
,
tls
.
CurveP256
,
},
CipherSuites
:
preferredDefaultCipherSuites
(),
PreferServerCipherSuites
:
true
,
}
}
}
}
// RenewAllCerts triggers a renewal check of all
// certificates in the cache. It only renews
// certificates if they need to be renewed.
// func (cfg *Config) RenewAllCerts(interactive bool) error {
// return cfg.certCache.RenewManagedCertificates(interactive)
// }
// preObtainOrRenewChecks perform a few simple checks before
// preObtainOrRenewChecks perform a few simple checks before
// obtaining or renewing a certificate with ACME, and returns
// obtaining or renewing a certificate with ACME, and returns
// whether this name should be skipped (like if it's not
// whether this name should be skipped (like if it's not
...
@@ -356,3 +364,27 @@ func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool,
...
@@ -356,3 +364,27 @@ func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool,
return
false
,
nil
return
false
,
nil
}
}
// storageHasCertResources returns true if the storage
// associated with cfg's certificate cache has all the
// resources related to the certificate for domain: the
// certificate, the private key, and the metadata.
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
)
}
// 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
)
if
err
!=
nil
{
return
true
}
return
cert
.
NeedsRenewal
()
}
vendor/github.com/mholt/certmagic/crypto.go
View file @
0b83014f
...
@@ -19,12 +19,14 @@ import (
...
@@ -19,12 +19,14 @@ import (
"crypto/ecdsa"
"crypto/ecdsa"
"crypto/rsa"
"crypto/rsa"
"crypto/sha256"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509"
"encoding/json"
"encoding/json"
"encoding/pem"
"encoding/pem"
"fmt"
"fmt"
"hash/fnv"
"hash/fnv"
"github.com/klauspost/cpuid"
"github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/certificate"
)
)
...
@@ -153,3 +155,34 @@ func hashCertificateChain(certChain [][]byte) string {
...
@@ -153,3 +155,34 @@ func hashCertificateChain(certChain [][]byte) string {
}
}
return
fmt
.
Sprintf
(
"%x"
,
h
.
Sum
(
nil
))
return
fmt
.
Sprintf
(
"%x"
,
h
.
Sum
(
nil
))
}
}
// preferredDefaultCipherSuites returns an appropriate
// cipher suite to use depending on hardware support
// for AES-NI.
//
// See https://github.com/mholt/caddy/issues/1674
func
preferredDefaultCipherSuites
()
[]
uint16
{
if
cpuid
.
CPU
.
AesNi
()
{
return
defaultCiphersPreferAES
}
return
defaultCiphersPreferChaCha
}
var
(
defaultCiphersPreferAES
=
[]
uint16
{
tls
.
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
,
}
defaultCiphersPreferChaCha
=
[]
uint16
{
tls
.
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
,
}
)
vendor/github.com/mholt/certmagic/filestorage.go
View file @
0b83014f
...
@@ -22,7 +22,6 @@ import (
...
@@ -22,7 +22,6 @@ import (
"path"
"path"
"path/filepath"
"path/filepath"
"runtime"
"runtime"
"sync"
"time"
"time"
)
)
...
@@ -34,13 +33,13 @@ type FileStorage struct {
...
@@ -34,13 +33,13 @@ type FileStorage struct {
}
}
// Exists returns true if key exists in fs.
// Exists returns true if key exists in fs.
func
(
fs
FileStorage
)
Exists
(
key
string
)
bool
{
func
(
fs
*
FileStorage
)
Exists
(
key
string
)
bool
{
_
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
_
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
return
!
os
.
IsNotExist
(
err
)
return
!
os
.
IsNotExist
(
err
)
}
}
// Store saves value at key.
// Store saves value at key.
func
(
fs
FileStorage
)
Store
(
key
string
,
value
[]
byte
)
error
{
func
(
fs
*
FileStorage
)
Store
(
key
string
,
value
[]
byte
)
error
{
filename
:=
fs
.
Filename
(
key
)
filename
:=
fs
.
Filename
(
key
)
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
filename
),
0700
)
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
filename
),
0700
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -50,7 +49,7 @@ func (fs FileStorage) Store(key string, value []byte) error {
...
@@ -50,7 +49,7 @@ func (fs FileStorage) Store(key string, value []byte) error {
}
}
// Load retrieves the value at key.
// Load retrieves the value at key.
func
(
fs
FileStorage
)
Load
(
key
string
)
([]
byte
,
error
)
{
func
(
fs
*
FileStorage
)
Load
(
key
string
)
([]
byte
,
error
)
{
contents
,
err
:=
ioutil
.
ReadFile
(
fs
.
Filename
(
key
))
contents
,
err
:=
ioutil
.
ReadFile
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
if
os
.
IsNotExist
(
err
)
{
return
nil
,
ErrNotExist
(
err
)
return
nil
,
ErrNotExist
(
err
)
...
@@ -59,8 +58,7 @@ func (fs FileStorage) Load(key string) ([]byte, error) {
...
@@ -59,8 +58,7 @@ func (fs FileStorage) Load(key string) ([]byte, error) {
}
}
// Delete deletes the value at key.
// Delete deletes the value at key.
// TODO: Delete any empty folders caused by this operation
func
(
fs
*
FileStorage
)
Delete
(
key
string
)
error
{
func
(
fs
FileStorage
)
Delete
(
key
string
)
error
{
err
:=
os
.
Remove
(
fs
.
Filename
(
key
))
err
:=
os
.
Remove
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
if
os
.
IsNotExist
(
err
)
{
return
ErrNotExist
(
err
)
return
ErrNotExist
(
err
)
...
@@ -69,7 +67,7 @@ func (fs FileStorage) Delete(key string) error {
...
@@ -69,7 +67,7 @@ func (fs FileStorage) Delete(key string) error {
}
}
// List returns all keys that match prefix.
// List returns all keys that match prefix.
func
(
fs
FileStorage
)
List
(
prefix
string
,
recursive
bool
)
([]
string
,
error
)
{
func
(
fs
*
FileStorage
)
List
(
prefix
string
,
recursive
bool
)
([]
string
,
error
)
{
var
keys
[]
string
var
keys
[]
string
walkPrefix
:=
fs
.
Filename
(
prefix
)
walkPrefix
:=
fs
.
Filename
(
prefix
)
...
@@ -100,7 +98,7 @@ func (fs FileStorage) List(prefix string, recursive bool) ([]string, error) {
...
@@ -100,7 +98,7 @@ func (fs FileStorage) List(prefix string, recursive bool) ([]string, error) {
}
}
// Stat returns information about key.
// Stat returns information about key.
func
(
fs
FileStorage
)
Stat
(
key
string
)
(
KeyInfo
,
error
)
{
func
(
fs
*
FileStorage
)
Stat
(
key
string
)
(
KeyInfo
,
error
)
{
fi
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
fi
,
err
:=
os
.
Stat
(
fs
.
Filename
(
key
))
if
os
.
IsNotExist
(
err
)
{
if
os
.
IsNotExist
(
err
)
{
return
KeyInfo
{},
ErrNotExist
(
err
)
return
KeyInfo
{},
ErrNotExist
(
err
)
...
@@ -118,191 +116,160 @@ func (fs FileStorage) Stat(key string) (KeyInfo, error) {
...
@@ -118,191 +116,160 @@ func (fs FileStorage) Stat(key string) (KeyInfo, error) {
// Filename returns the key as a path on the file
// Filename returns the key as a path on the file
// system prefixed by fs.Path.
// system prefixed by fs.Path.
func
(
fs
FileStorage
)
Filename
(
key
string
)
string
{
func
(
fs
*
FileStorage
)
Filename
(
key
string
)
string
{
return
filepath
.
Join
(
fs
.
Path
,
filepath
.
FromSlash
(
key
))
return
filepath
.
Join
(
fs
.
Path
,
filepath
.
FromSlash
(
key
))
}
}
// homeDir returns the best guess of the current user's home
// Lock obtains a lock named by the given key. It blocks
// directory from environment variables. If unknown, "." (the
// until the lock can be obtained or an error is returned.
// current directory) is returned instead.
func
(
fs
*
FileStorage
)
Lock
(
key
string
)
error
{
func
homeDir
()
string
{
start
:=
time
.
Now
()
home
:=
os
.
Getenv
(
"HOME"
)
filename
:=
fs
.
lockFilename
(
key
)
if
home
==
""
&&
runtime
.
GOOS
==
"windows"
{
drive
:=
os
.
Getenv
(
"HOMEDRIVE"
)
path
:=
os
.
Getenv
(
"HOMEPATH"
)
home
=
drive
+
path
if
drive
==
""
||
path
==
""
{
home
=
os
.
Getenv
(
"USERPROFILE"
)
}
}
if
home
==
""
{
home
=
"."
}
return
home
}
func
dataDir
()
string
{
baseDir
:=
filepath
.
Join
(
homeDir
(),
".local"
,
"share"
)
if
xdgData
:=
os
.
Getenv
(
"XDG_DATA_HOME"
);
xdgData
!=
""
{
baseDir
=
xdgData
}
return
filepath
.
Join
(
baseDir
,
"certmagic"
)
}
// TryLock attempts to get a lock for name, otherwise it returns
for
{
// a Waiter value to wait until the other process is finished.
err
:=
createLockfile
(
filename
)
func
(
fs
FileStorage
)
TryLock
(
key
string
)
(
Waiter
,
error
)
{
if
err
==
nil
{
fileStorageNameLocksMu
.
Lock
()
// got the lock, yay
defer
fileStorageNameLocksMu
.
Unlock
()
return
nil
// see if lock already exists within this process - allows
// for faster unlocking since we don't have to poll the disk
fw
,
ok
:=
fileStorageNameLocks
[
key
]
if
ok
{
// lock already created within process, let caller wait on it
return
fw
,
nil
}
}
if
!
os
.
IsExist
(
err
)
{
// attempt to persist lock to disk by creating lock file
// unexpected error
return
fmt
.
Errorf
(
"creating lock file: %v"
,
err
)
// parent dir must exist
lockDir
:=
fs
.
lockDir
()
if
err
:=
os
.
MkdirAll
(
lockDir
,
0700
);
err
!=
nil
{
return
nil
,
err
}
}
fw
=
&
fileStorageWaiter
{
// lock file already exists
key
:
key
,
filename
:
filepath
.
Join
(
lockDir
,
StorageKeys
.
safe
(
key
)
+
".lock"
),
wg
:
new
(
sync
.
WaitGroup
),
}
var
checkedStaleLock
bool
// sentinel value to avoid infinite goto-ing
info
,
err
:=
os
.
Stat
(
filename
)
switch
{
case
os
.
IsNotExist
(
err
)
:
// must have just been removed; try again to create it
continue
createLock
:
case
err
!=
nil
:
// create the file in a special mode such that an
// unexpected error
// error is returned if it already exists
return
fmt
.
Errorf
(
"accessing lock file: %v"
,
err
)
lf
,
err
:=
os
.
OpenFile
(
fw
.
filename
,
os
.
O_CREATE
|
os
.
O_EXCL
,
0644
)
if
err
!=
nil
{
if
os
.
IsExist
(
err
)
{
// another process has the lock
// check to see if the lock is stale, if we haven't already
case
fileLockIsStale
(
info
)
:
if
!
checkedStaleLock
{
// lock file is stale - delete it and try again to create one
checkedStaleLock
=
true
if
fs
.
lockFileStale
(
fw
.
filename
)
{
log
.
Printf
(
"[INFO][%s] Lock for '%s' is stale; removing then retrying: %s"
,
log
.
Printf
(
"[INFO][%s] Lock for '%s' is stale; removing then retrying: %s"
,
fs
,
key
,
fw
.
filename
)
fs
,
key
,
filename
)
os
.
Remove
(
fw
.
filename
)
removeLockfile
(
filename
)
goto
createLock
continue
case
time
.
Since
(
start
)
>
staleLockDuration
*
2
:
// should never happen, hopefully
return
fmt
.
Errorf
(
"possible deadlock: %s passed trying to obtain lock for %s"
,
time
.
Since
(
start
),
key
)
default
:
// lockfile exists and is not stale;
// just wait a moment and try again
time
.
Sleep
(
fileLockPollInterval
)
}
}
}
}
// if lock is not stale, wait upon it
return
fw
,
nil
}
// otherwise, this was some unexpected error
return
nil
,
err
}
lf
.
Close
()
// looks like we get the lock
fw
.
wg
.
Add
(
1
)
fileStorageNameLocks
[
key
]
=
fw
return
nil
,
nil
}
}
// Unlock releases the lock for name.
// Unlock releases the lock for name.
func
(
fs
FileStorage
)
Unlock
(
key
string
)
error
{
func
(
fs
*
FileStorage
)
Unlock
(
key
string
)
error
{
fileStorageNameLocksMu
.
Lock
()
return
removeLockfile
(
fs
.
lockFilename
(
key
))
defer
fileStorageNameLocksMu
.
Unlock
()
}
fw
,
ok
:=
fileStorageNameLocks
[
key
]
if
!
ok
{
return
fmt
.
Errorf
(
"FileStorage: no lock to release for %s"
,
key
)
}
// remove lock file
os
.
Remove
(
fw
.
filename
)
// if parent folder is now empty, remove it too to keep it tidy
dir
,
err
:=
os
.
Open
(
fs
.
lockDir
())
// OK to ignore error here
if
err
==
nil
{
items
,
_
:=
dir
.
Readdirnames
(
3
)
// OK to ignore error here
if
len
(
items
)
==
0
{
os
.
Remove
(
dir
.
Name
())
}
dir
.
Close
()
}
// clean up in memory
func
(
fs
*
FileStorage
)
String
()
string
{
fw
.
wg
.
Done
()
return
"FileStorage:"
+
fs
.
Path
delete
(
fileStorageNameLocks
,
key
)
}
return
nil
func
(
fs
*
FileStorage
)
lockFilename
(
key
string
)
string
{
return
filepath
.
Join
(
fs
.
lockDir
(),
StorageKeys
.
safe
(
key
)
+
".lock"
)
}
}
// UnlockAllObtained removes all locks obtained by
func
(
fs
*
FileStorage
)
lockDir
()
string
{
// this instance of fs.
return
filepath
.
Join
(
fs
.
Path
,
"locks"
)
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
{
func
fileLockIsStale
(
info
os
.
FileInfo
)
bool
{
info
,
err
:=
os
.
Stat
(
filename
)
if
info
==
nil
{
if
err
!=
nil
{
return
true
return
true
// no good way to handle this, really; lock is useless?
}
}
return
time
.
Since
(
info
.
ModTime
())
>
staleLockDuration
return
time
.
Since
(
info
.
ModTime
())
>
staleLockDuration
}
}
func
(
fs
FileStorage
)
lockDir
()
string
{
// createLockfile atomically creates the lockfile
return
filepath
.
Join
(
fs
.
Path
,
"locks"
)
// identified by filename. A successfully created
// lockfile should be removed with removeLockfile.
func
createLockfile
(
filename
string
)
error
{
err
:=
atomicallyCreateFile
(
filename
)
if
err
==
nil
{
// if the app crashes in removeLockfile(), there is a
// small chance the .unlock file is left behind; it's
// safe to simply remove it as it's a guard against
// double removal of the .lock file.
os
.
Remove
(
filename
+
".unlock"
)
}
return
err
}
}
func
(
fs
FileStorage
)
String
()
string
{
// removeLockfile atomically removes filename,
return
"FileStorage:"
+
fs
.
Path
// which must be a lockfile created by createLockfile.
// See discussion in PR #7 for more background:
// https://github.com/mholt/certmagic/pull/7
func
removeLockfile
(
filename
string
)
error
{
unlockFilename
:=
filename
+
".unlock"
if
err
:=
atomicallyCreateFile
(
unlockFilename
);
err
!=
nil
{
if
os
.
IsExist
(
err
)
{
// another process is handling the unlocking
return
nil
}
return
err
}
defer
os
.
Remove
(
unlockFilename
)
return
os
.
Remove
(
filename
)
}
}
// fileStorageWaiter waits for a file to disappear; it
// atomicallyCreateFile atomically creates the file
// polls the file system to check for the existence of
// identified by filename if it doesn't already exist.
// a file. It also uses a WaitGroup to optimize the
func
atomicallyCreateFile
(
filename
string
)
error
{
// polling in the case when this process is the only
// no need to check this, we only really care about the file creation error
// one waiting. (Other processes that are waiting for
os
.
MkdirAll
(
filepath
.
Dir
(
filename
),
0700
)
// the lock will still block, but must wait for the
f
,
err
:=
os
.
OpenFile
(
filename
,
os
.
O_CREATE
|
os
.
O_EXCL
,
0644
)
// polling to get their answer.)
if
err
==
nil
{
type
fileStorageWaiter
struct
{
f
.
Close
()
key
string
}
filename
string
return
err
wg
*
sync
.
WaitGroup
}
}
// Wait waits until the lock is released.
// homeDir returns the best guess of the current user's home
func
(
fw
*
fileStorageWaiter
)
Wait
()
{
// directory from environment variables. If unknown, "." (the
start
:=
time
.
Now
()
// current directory) is returned instead.
fw
.
wg
.
Wait
()
func
homeDir
()
string
{
for
time
.
Since
(
start
)
<
1
*
time
.
Hour
{
home
:=
os
.
Getenv
(
"HOME"
)
_
,
err
:=
os
.
Stat
(
fw
.
filename
)
if
home
==
""
&&
runtime
.
GOOS
==
"windows"
{
if
os
.
IsNotExist
(
err
)
{
drive
:=
os
.
Getenv
(
"HOMEDRIVE"
)
return
path
:=
os
.
Getenv
(
"HOMEPATH"
)
home
=
drive
+
path
if
drive
==
""
||
path
==
""
{
home
=
os
.
Getenv
(
"USERPROFILE"
)
}
}
}
time
.
Sleep
(
1
*
time
.
Second
)
if
home
==
""
{
home
=
"."
}
}
return
home
}
}
var
fileStorageNameLocks
=
make
(
map
[
string
]
*
fileStorageWaiter
)
func
dataDir
()
string
{
var
fileStorageNameLocksMu
sync
.
Mutex
baseDir
:=
filepath
.
Join
(
homeDir
(),
".local"
,
"share"
)
if
xdgData
:=
os
.
Getenv
(
"XDG_DATA_HOME"
);
xdgData
!=
""
{
var
_
Storage
=
FileStorage
{}
baseDir
=
xdgData
var
_
Waiter
=
&
fileStorageWaiter
{}
}
return
filepath
.
Join
(
baseDir
,
"certmagic"
)
}
// staleLockDuration is the length of time
// staleLockDuration is the length of time
// before considering a lock to be stale.
// before considering a lock to be stale.
const
staleLockDuration
=
2
*
time
.
Hour
const
staleLockDuration
=
2
*
time
.
Hour
// fileLockPollInterval is how frequently
// to check the existence of a lock file
const
fileLockPollInterval
=
1
*
time
.
Second
var
_
Storage
=
(
*
FileStorage
)(
nil
)
vendor/github.com/mholt/certmagic/storage.go
View file @
0b83014f
...
@@ -64,22 +64,24 @@ type Storage interface {
...
@@ -64,22 +64,24 @@ type Storage interface {
// Locker facilitates synchronization of certificate tasks across
// Locker facilitates synchronization of certificate tasks across
// machines and networks.
// machines and networks.
type
Locker
interface
{
type
Locker
interface
{
// TryLock will attempt to acquire the lock for key. If a
// Lock acquires the lock for key, blocking until the lock
// lock could be obtained, nil values are returned as no
// can be obtained or an error is returned. Note that, even
// waiting is required. If not (meaning another process is
// after acquiring a lock, an idempotent operation may have
// already working on key), a Waiter value will be returned,
// already been performed by another process that acquired
// upon which you should Wait() until it is finished.
// the lock before - so always check to make sure idempotent
// operations still need to be performed after acquiring the
// lock.
//
//
// The actual implementation of obtaining of a lock must be
// The actual implementation of obtaining of a lock must be
// an atomic operation so that multiple
Try
Lock calls at the
// an atomic operation so that multiple Lock calls at the
// same time always results in only one caller receiving the
// same time always results in only one caller receiving the
// lock
. TryLock always returns without waiting
.
// lock
at any given time
.
//
//
// To prevent deadlocks, all implementations (where this concern
// To prevent deadlocks, all implementations (where this concern
// is relevant) should put a reasonable expiration on the lock in
// is relevant) should put a reasonable expiration on the lock in
// case Unlock is unable to be called due to some sort of network
// case Unlock is unable to be called due to some sort of network
// or system failure or crash.
// or system failure or crash.
TryLock
(
key
string
)
(
Waiter
,
error
)
Lock
(
key
string
)
error
// Unlock releases the lock for key. This method must ONLY be
// Unlock releases the lock for key. This method must ONLY be
// called after a successful call to TryLock where no Waiter was
// called after a successful call to TryLock where no Waiter was
...
@@ -89,20 +91,6 @@ type Locker interface {
...
@@ -89,20 +91,6 @@ 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.
type
Waiter
interface
{
Wait
()
}
}
// KeyInfo holds information about a key in storage.
// KeyInfo holds information about a key in storage.
...
@@ -281,7 +269,7 @@ type ErrNotExist interface {
...
@@ -281,7 +269,7 @@ type ErrNotExist interface {
// defaultFileStorage is a convenient, default storage
// defaultFileStorage is a convenient, default storage
// implementation using the local file system.
// implementation using the local file system.
var
defaultFileStorage
=
FileStorage
{
Path
:
dataDir
()}
var
defaultFileStorage
=
&
FileStorage
{
Path
:
dataDir
()}
// DefaultStorage is the default Storage implementation.
// DefaultStorage is the default Storage implementation.
var
DefaultStorage
Storage
=
defaultFileStorage
var
DefaultStorage
Storage
=
defaultFileStorage
vendor/manifest
View file @
0b83014f
...
@@ -138,7 +138,7 @@
...
@@ -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": "
fe722057f2654b33cd528b8fd8b90e53fa495564
",
"revision": "
a3b276a1b44e1c2c3dcab752729976ea04f4839b
",
"branch": "master",
"branch": "master",
"notests": true
"notests": true
},
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment