123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- // Copyright 2015 Matthew Holt
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package certmagic
-
- import (
- "context"
- "path"
- "regexp"
- "strings"
- "sync"
- "time"
-
- "go.uber.org/zap"
- )
-
- // Storage is a type that implements a key-value store.
- // Keys are prefix-based, with forward slash '/' as separators
- // and without a leading slash.
- //
- // Processes running in a cluster will wish to use the
- // same Storage value (its implementation and configuration)
- // in order to share certificates and other TLS resources
- // with the cluster.
- //
- // The Load, Delete, List, and Stat methods should return
- // ErrNotExist if the key does not exist.
- //
- // Implementations of Storage must be safe for concurrent use.
- type Storage interface {
- // Locker provides atomic synchronization
- // operations, making Storage safe to share.
- Locker
-
- // Store puts value at key.
- Store(key string, value []byte) error
-
- // Load retrieves the value at key.
- Load(key string) ([]byte, error)
-
- // Delete deletes key. An error should be
- // returned only if the key still exists
- // when the method returns.
- Delete(key string) error
-
- // Exists returns true if the key exists
- // and there was no error checking.
- Exists(key string) bool
-
- // List returns all keys that match prefix.
- // If recursive is true, non-terminal keys
- // will be enumerated (i.e. "directories"
- // should be walked); otherwise, only keys
- // prefixed exactly by prefix will be listed.
- List(prefix string, recursive bool) ([]string, error)
-
- // Stat returns information about key.
- Stat(key string) (KeyInfo, error)
- }
-
- // Locker facilitates synchronization of certificate tasks across
- // machines and networks.
- type Locker interface {
- // Lock acquires the lock for key, blocking until the lock
- // can be obtained or an error is returned. Note that, even
- // after acquiring a lock, an idempotent operation may have
- // already been performed by another process that acquired
- // 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
- // an atomic operation so that multiple Lock calls at the
- // same time always results in only one caller receiving the
- // lock at any given time.
- //
- // To prevent deadlocks, all implementations (where this concern
- // is relevant) should put a reasonable expiration on the lock in
- // case Unlock is unable to be called due to some sort of network
- // failure or system crash. Additionally, implementations should
- // honor context cancellation as much as possible (in case the
- // caller wishes to give up and free resources before the lock
- // can be obtained).
- Lock(ctx context.Context, key string) error
-
- // Unlock releases the lock for key. This method must ONLY be
- // called after a successful call to Lock, and only after the
- // critical section is finished, even if it errored or timed
- // out. Unlock cleans up any resources allocated during Lock.
- Unlock(key string) error
- }
-
- // KeyInfo holds information about a key in storage.
- // Key and IsTerminal are required; Modified and Size
- // are optional if the storage implementation is not
- // able to get that information. Setting them will
- // make certain operations more consistent or
- // predictable, but it is not crucial to basic
- // functionality.
- type KeyInfo struct {
- Key string
- Modified time.Time
- Size int64
- IsTerminal bool // false for keys that only contain other keys (like directories)
- }
-
- // storeTx stores all the values or none at all.
- func storeTx(s Storage, all []keyValue) error {
- for i, kv := range all {
- err := s.Store(kv.key, kv.value)
- if err != nil {
- for j := i - 1; j >= 0; j-- {
- s.Delete(all[j].key)
- }
- return err
- }
- }
- return nil
- }
-
- // keyValue pairs a key and a value.
- type keyValue struct {
- key string
- value []byte
- }
-
- // KeyBuilder provides a namespace for methods that
- // build keys and key prefixes, for addressing items
- // in a Storage implementation.
- type KeyBuilder struct{}
-
- // CertsPrefix returns the storage key prefix for
- // the given certificate issuer.
- func (keys KeyBuilder) CertsPrefix(issuerKey string) string {
- return path.Join(prefixCerts, keys.Safe(issuerKey))
- }
-
- // CertsSitePrefix returns a key prefix for items associated with
- // the site given by domain using the given issuer key.
- func (keys KeyBuilder) CertsSitePrefix(issuerKey, domain string) string {
- return path.Join(keys.CertsPrefix(issuerKey), keys.Safe(domain))
- }
-
- // SiteCert returns the path to the certificate file for domain
- // that is associated with the issuer with the given issuerKey.
- func (keys KeyBuilder) SiteCert(issuerKey, domain string) string {
- safeDomain := keys.Safe(domain)
- return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".crt")
- }
-
- // SitePrivateKey returns the path to the private key file for domain
- // that is associated with the certificate from the given issuer with
- // the given issuerKey.
- func (keys KeyBuilder) SitePrivateKey(issuerKey, domain string) string {
- safeDomain := keys.Safe(domain)
- return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".key")
- }
-
- // SiteMeta returns the path to the metadata file for domain that
- // is associated with the certificate from the given issuer with
- // the given issuerKey.
- func (keys KeyBuilder) SiteMeta(issuerKey, domain string) string {
- safeDomain := keys.Safe(domain)
- return path.Join(keys.CertsSitePrefix(issuerKey, domain), safeDomain+".json")
- }
-
- // OCSPStaple returns a key for the OCSP staple associated
- // with the given certificate. If you have the PEM bundle
- // handy, pass that in to save an extra encoding step.
- func (keys KeyBuilder) OCSPStaple(cert *Certificate, pemBundle []byte) string {
- var ocspFileName string
- if len(cert.Names) > 0 {
- firstName := keys.Safe(cert.Names[0])
- ocspFileName = firstName + "-"
- }
- ocspFileName += fastHash(pemBundle)
- return path.Join(prefixOCSP, ocspFileName)
- }
-
- // Safe standardizes and sanitizes str for use as
- // a single component of a storage key. This method
- // is idempotent.
- func (keys KeyBuilder) Safe(str string) string {
- str = strings.ToLower(str)
- str = strings.TrimSpace(str)
-
- // replace a few specific characters
- repl := strings.NewReplacer(
- " ", "_",
- "+", "_plus_",
- "*", "wildcard_",
- ":", "-",
- "..", "", // prevent directory traversal (regex allows single dots)
- )
- str = repl.Replace(str)
-
- // finally remove all non-word characters
- return safeKeyRE.ReplaceAllLiteralString(str, "")
- }
-
- // CleanUpOwnLocks immediately cleans up all
- // current locks obtained by this process. Since
- // this does not cancel the operations that
- // the locks are synchronizing, this should be
- // called only immediately before process exit.
- // Errors are only reported if a logger is given.
- func CleanUpOwnLocks(logger *zap.Logger) {
- locksMu.Lock()
- defer locksMu.Unlock()
- for lockKey, storage := range locks {
- err := storage.Unlock(lockKey)
- if err == nil {
- delete(locks, lockKey)
- } else if logger != nil {
- logger.Error("unable to clean up lock in storage backend",
- zap.Any("storage", storage),
- zap.String("lock_key", lockKey),
- zap.Error(err),
- )
- }
- }
- }
-
- func acquireLock(ctx context.Context, storage Storage, lockKey string) error {
- err := storage.Lock(ctx, lockKey)
- if err == nil {
- locksMu.Lock()
- locks[lockKey] = storage
- locksMu.Unlock()
- }
- return err
- }
-
- func releaseLock(storage Storage, lockKey string) error {
- err := storage.Unlock(lockKey)
- if err == nil {
- locksMu.Lock()
- delete(locks, lockKey)
- locksMu.Unlock()
- }
- return err
- }
-
- // locks stores a reference to all the current
- // locks obtained by this process.
- var locks = make(map[string]Storage)
- var locksMu sync.Mutex
-
- // StorageKeys provides methods for accessing
- // keys and key prefixes for items in a Storage.
- // Typically, you will not need to use this
- // because accessing storage is abstracted away
- // for most cases. Only use this if you need to
- // directly access TLS assets in your application.
- var StorageKeys KeyBuilder
-
- const (
- prefixCerts = "certificates"
- prefixOCSP = "ocsp"
- )
-
- // safeKeyRE matches any undesirable characters in storage keys.
- // Note that this allows dots, so you'll have to strip ".." manually.
- var safeKeyRE = regexp.MustCompile(`[^\w@.-]`)
-
- // ErrNotExist is returned by Storage implementations when
- // a resource is not found. It is similar to os.IsNotExist
- // except this is a type, not a variable.
- // TODO: use new Go error wrapping conventions
- type ErrNotExist interface {
- error
- }
-
- // defaultFileStorage is a convenient, default storage
- // implementation using the local file system.
- var defaultFileStorage = &FileStorage{Path: dataDir()}
|