2015-03-09 23:33:27 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
2015-03-11 18:43:36 +00:00
|
|
|
"bytes"
|
2015-03-10 00:45:34 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2015-03-09 23:33:27 +00:00
|
|
|
"fmt"
|
2015-03-10 00:45:34 +00:00
|
|
|
"log"
|
2015-03-11 18:52:01 +00:00
|
|
|
"os"
|
2015-04-03 01:05:23 +00:00
|
|
|
"strings"
|
2015-03-10 00:45:34 +00:00
|
|
|
"sync"
|
2015-04-08 23:43:17 +00:00
|
|
|
"time"
|
2015-03-09 23:33:27 +00:00
|
|
|
|
2015-04-08 23:43:17 +00:00
|
|
|
"github.com/armon/go-metrics"
|
2015-03-27 20:45:13 +00:00
|
|
|
"github.com/hashicorp/vault/audit"
|
2015-03-15 21:53:41 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
2015-03-09 23:33:27 +00:00
|
|
|
"github.com/hashicorp/vault/physical"
|
2015-03-11 18:34:08 +00:00
|
|
|
"github.com/hashicorp/vault/shamir"
|
2015-03-09 23:33:27 +00:00
|
|
|
)
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
const (
|
|
|
|
// coreSealConfigPath is the path used to store our seal configuration.
|
|
|
|
// This value is stored in plaintext, since we must be able to read
|
|
|
|
// it even with the Vault sealed. This is required so that we know
|
|
|
|
// how many secret parts must be used to reconstruct the master key.
|
|
|
|
coreSealConfigPath = "core/seal-config"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrSealed is returned if an operation is performed on
|
|
|
|
// a sealed barrier. No operation is expected to succeed before unsealing
|
|
|
|
ErrSealed = errors.New("Vault is sealed")
|
|
|
|
|
|
|
|
// ErrAlreadyInit is returned if the core is already
|
|
|
|
// initialized. This prevents a re-initialization.
|
|
|
|
ErrAlreadyInit = errors.New("Vault is already initialized")
|
|
|
|
|
|
|
|
// ErrNotInit is returned if a non-initialized barrier
|
|
|
|
// is attempted to be unsealed.
|
|
|
|
ErrNotInit = errors.New("Vault is not initialized")
|
2015-03-16 22:28:50 +00:00
|
|
|
|
|
|
|
// ErrInternalError is returned when we don't want to leak
|
|
|
|
// any information about an internal error
|
|
|
|
ErrInternalError = errors.New("internal error")
|
2015-03-10 00:45:34 +00:00
|
|
|
)
|
|
|
|
|
2015-03-09 23:33:27 +00:00
|
|
|
// SealConfig is used to describe the seal configuration
|
|
|
|
type SealConfig struct {
|
2015-03-11 18:34:08 +00:00
|
|
|
// SecretShares is the number of shares the secret is
|
2015-03-09 23:33:27 +00:00
|
|
|
// split into. This is the N value of Shamir
|
2015-03-11 18:34:08 +00:00
|
|
|
SecretShares int `json:"secret_shares"`
|
2015-03-09 23:33:27 +00:00
|
|
|
|
|
|
|
// SecretThreshold is the number of parts required
|
|
|
|
// to open the vault. This is the T value of Shamir
|
|
|
|
SecretThreshold int `json:"secret_threshold"`
|
|
|
|
}
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
// Validate is used to sanity check the seal configuration
|
|
|
|
func (s *SealConfig) Validate() error {
|
2015-03-11 18:34:08 +00:00
|
|
|
if s.SecretShares < 1 {
|
|
|
|
return fmt.Errorf("secret shares must be at least one")
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
2015-03-11 18:34:08 +00:00
|
|
|
if s.SecretThreshold < 1 {
|
|
|
|
return fmt.Errorf("secret threshold must be at least one")
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
2015-03-12 18:20:27 +00:00
|
|
|
if s.SecretShares > 255 {
|
|
|
|
return fmt.Errorf("secret shares must be less than 256")
|
|
|
|
}
|
|
|
|
if s.SecretThreshold > 255 {
|
|
|
|
return fmt.Errorf("secret threshold must be less than 256")
|
|
|
|
}
|
2015-03-11 18:34:08 +00:00
|
|
|
if s.SecretThreshold > s.SecretShares {
|
|
|
|
return fmt.Errorf("secret threshold cannot be larger than secret shares")
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// InitResult is used to provide the key parts back after
|
|
|
|
// they are generated as part of the initialization.
|
|
|
|
type InitResult struct {
|
2015-03-11 18:34:08 +00:00
|
|
|
SecretShares [][]byte
|
2015-03-24 00:31:30 +00:00
|
|
|
RootToken string
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
|
|
|
|
2015-03-12 18:20:27 +00:00
|
|
|
// ErrInvalidKey is returned if there is an error with a
|
|
|
|
// provided unseal key.
|
|
|
|
type ErrInvalidKey struct {
|
|
|
|
Reason string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ErrInvalidKey) Error() string {
|
|
|
|
return fmt.Sprintf("invalid key: %v", e.Reason)
|
|
|
|
}
|
|
|
|
|
2015-03-09 23:33:27 +00:00
|
|
|
// Core is used as the central manager of Vault activity. It is the primary point of
|
|
|
|
// interface for API handlers and is responsible for managing the logical and physical
|
|
|
|
// backends, router, security barrier, and audit trails.
|
|
|
|
type Core struct {
|
|
|
|
// physical backend is the un-trusted backend with durable data
|
|
|
|
physical physical.Backend
|
|
|
|
|
|
|
|
// barrier is the security barrier wrapping the physical backend
|
|
|
|
barrier SecurityBarrier
|
|
|
|
|
|
|
|
// router is responsible for managing the mount points for logical backends.
|
|
|
|
router *Router
|
2015-03-10 00:45:34 +00:00
|
|
|
|
2015-03-18 22:21:41 +00:00
|
|
|
// logicalBackends is the mapping of backends to use for this core
|
|
|
|
logicalBackends map[string]logical.Factory
|
|
|
|
|
|
|
|
// credentialBackends is the mapping of backends to use for this core
|
2015-03-31 01:07:05 +00:00
|
|
|
credentialBackends map[string]logical.Factory
|
2015-03-15 23:25:38 +00:00
|
|
|
|
2015-03-27 20:45:13 +00:00
|
|
|
// auditBackends is the mapping of backends to use for this core
|
|
|
|
auditBackends map[string]audit.Factory
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
// stateLock protects mutable state
|
|
|
|
stateLock sync.RWMutex
|
|
|
|
sealed bool
|
|
|
|
|
|
|
|
// unlockParts has the keys provided to Unseal until
|
|
|
|
// the threshold number of parts is available.
|
|
|
|
unlockParts [][]byte
|
|
|
|
|
2015-03-11 22:19:41 +00:00
|
|
|
// mounts is loaded after unseal since it is a protected
|
|
|
|
// configuration
|
2015-03-17 22:28:01 +00:00
|
|
|
mounts *MountTable
|
2015-03-11 22:19:41 +00:00
|
|
|
|
2015-03-18 22:46:07 +00:00
|
|
|
// auth is loaded after unseal since it is a protected
|
|
|
|
// configuration
|
2015-03-19 16:54:57 +00:00
|
|
|
auth *MountTable
|
2015-03-18 22:46:07 +00:00
|
|
|
|
2015-03-27 20:45:13 +00:00
|
|
|
// audit is loaded after unseal since it is a protected
|
|
|
|
// configuration
|
|
|
|
audit *MountTable
|
|
|
|
|
2015-03-31 20:22:40 +00:00
|
|
|
// auditBroker is used to ingest the audit events and fan
|
|
|
|
// out into the configured audit backends
|
|
|
|
auditBroker *AuditBroker
|
|
|
|
|
2015-03-12 19:41:12 +00:00
|
|
|
// systemView is the barrier view for the system backend
|
|
|
|
systemView *BarrierView
|
|
|
|
|
2015-04-08 20:35:32 +00:00
|
|
|
// expiration manager is used for managing LeaseIDs,
|
2015-03-12 19:44:22 +00:00
|
|
|
// renewal, expiration and revocation
|
|
|
|
expiration *ExpirationManager
|
|
|
|
|
2015-03-17 23:23:58 +00:00
|
|
|
// rollback manager is used to run rollbacks periodically
|
|
|
|
rollback *RollbackManager
|
|
|
|
|
2015-03-18 21:00:42 +00:00
|
|
|
// policy store is used to manage named ACL policies
|
|
|
|
policy *PolicyStore
|
|
|
|
|
2015-03-23 20:41:05 +00:00
|
|
|
// token store is used to manage authentication tokens
|
|
|
|
tokenStore *TokenStore
|
|
|
|
|
2015-04-08 23:43:17 +00:00
|
|
|
// metricsCh is used to stop the metrics streaming
|
|
|
|
metricsCh chan struct{}
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
logger *log.Logger
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
2015-03-11 18:52:01 +00:00
|
|
|
// CoreConfig is used to parameterize a core
|
|
|
|
type CoreConfig struct {
|
2015-03-18 22:21:41 +00:00
|
|
|
LogicalBackends map[string]logical.Factory
|
2015-03-31 01:07:05 +00:00
|
|
|
CredentialBackends map[string]logical.Factory
|
2015-03-27 20:45:13 +00:00
|
|
|
AuditBackends map[string]audit.Factory
|
2015-03-18 22:21:41 +00:00
|
|
|
Physical physical.Backend
|
|
|
|
Logger *log.Logger
|
2015-04-14 18:08:04 +00:00
|
|
|
DisableCache bool // Disables the LRU cache on the physical backend
|
|
|
|
CacheSize int // Custom cache size of zero for default
|
2015-03-11 18:52:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewCore isk used to construct a new core
|
|
|
|
func NewCore(conf *CoreConfig) (*Core, error) {
|
2015-04-14 18:08:04 +00:00
|
|
|
// Wrap the backend in a cache unless disabled
|
|
|
|
if !conf.DisableCache {
|
|
|
|
_, isCache := conf.Physical.(*physical.Cache)
|
|
|
|
_, isInmem := conf.Physical.(*physical.InmemBackend)
|
|
|
|
if !isCache && !isInmem {
|
|
|
|
cache := physical.NewCache(conf.Physical, conf.CacheSize)
|
|
|
|
conf.Physical = cache
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-09 23:33:27 +00:00
|
|
|
// Construct a new AES-GCM barrier
|
2015-03-12 17:22:12 +00:00
|
|
|
barrier, err := NewAESGCMBarrier(conf.Physical)
|
2015-03-09 23:33:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("barrier setup failed: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-03-11 18:52:01 +00:00
|
|
|
// Make a default logger if not provided
|
2015-03-12 17:22:12 +00:00
|
|
|
if conf.Logger == nil {
|
|
|
|
conf.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
2015-03-11 18:52:01 +00:00
|
|
|
}
|
|
|
|
|
2015-03-09 23:33:27 +00:00
|
|
|
// Setup the core
|
|
|
|
c := &Core{
|
2015-03-12 17:22:12 +00:00
|
|
|
physical: conf.Physical,
|
2015-03-09 23:33:27 +00:00
|
|
|
barrier: barrier,
|
|
|
|
router: NewRouter(),
|
2015-03-10 00:45:34 +00:00
|
|
|
sealed: true,
|
2015-03-12 17:22:12 +00:00
|
|
|
logger: conf.Logger,
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
2015-03-15 23:25:38 +00:00
|
|
|
|
|
|
|
// Setup the backends
|
2015-03-18 22:21:41 +00:00
|
|
|
logicalBackends := make(map[string]logical.Factory)
|
|
|
|
for k, f := range conf.LogicalBackends {
|
|
|
|
logicalBackends[k] = f
|
2015-03-15 23:25:38 +00:00
|
|
|
}
|
2015-03-18 22:21:41 +00:00
|
|
|
logicalBackends["generic"] = PassthroughBackendFactory
|
|
|
|
logicalBackends["system"] = func(map[string]string) (logical.Backend, error) {
|
2015-03-16 00:35:59 +00:00
|
|
|
return NewSystemBackend(c), nil
|
2015-03-15 23:25:38 +00:00
|
|
|
}
|
2015-03-18 22:21:41 +00:00
|
|
|
c.logicalBackends = logicalBackends
|
2015-03-15 23:25:38 +00:00
|
|
|
|
2015-03-31 01:07:05 +00:00
|
|
|
credentialBackends := make(map[string]logical.Factory)
|
2015-03-18 22:21:41 +00:00
|
|
|
for k, f := range conf.CredentialBackends {
|
|
|
|
credentialBackends[k] = f
|
|
|
|
}
|
2015-03-31 01:07:05 +00:00
|
|
|
credentialBackends["token"] = func(map[string]string) (logical.Backend, error) {
|
2015-03-19 02:11:52 +00:00
|
|
|
return NewTokenStore(c)
|
|
|
|
}
|
2015-03-18 22:21:41 +00:00
|
|
|
c.credentialBackends = credentialBackends
|
2015-03-27 20:45:13 +00:00
|
|
|
|
|
|
|
auditBackends := make(map[string]audit.Factory)
|
|
|
|
for k, f := range conf.AuditBackends {
|
|
|
|
auditBackends[k] = f
|
|
|
|
}
|
|
|
|
c.auditBackends = auditBackends
|
2015-03-09 23:33:27 +00:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleRequest is used to handle a new incoming request
|
2015-03-15 21:53:41 +00:00
|
|
|
func (c *Core) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
2015-03-11 21:31:55 +00:00
|
|
|
c.stateLock.RLock()
|
|
|
|
defer c.stateLock.RUnlock()
|
|
|
|
if c.sealed {
|
|
|
|
return nil, ErrSealed
|
|
|
|
}
|
|
|
|
|
2015-03-31 03:26:39 +00:00
|
|
|
if c.router.LoginPath(req.Path) {
|
|
|
|
return c.handleLoginRequest(req)
|
|
|
|
} else {
|
|
|
|
return c.handleRequest(req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Core) handleRequest(req *logical.Request) (*logical.Response, error) {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "handle_request"}, time.Now())
|
2015-03-31 16:59:02 +00:00
|
|
|
// Validate the token
|
2015-04-01 21:33:48 +00:00
|
|
|
auth, err := c.checkToken(req.Operation, req.Path, req.ClientToken)
|
2015-03-24 18:37:07 +00:00
|
|
|
if err != nil {
|
2015-03-31 16:59:02 +00:00
|
|
|
// If it is an internal error we return that, otherwise we
|
|
|
|
// return invalid request so that the status codes can be correct
|
2015-04-01 21:11:26 +00:00
|
|
|
var errType error
|
2015-03-31 16:59:02 +00:00
|
|
|
switch err {
|
2015-04-01 21:11:26 +00:00
|
|
|
case ErrInternalError, logical.ErrPermissionDenied:
|
2015-03-31 16:59:02 +00:00
|
|
|
errType = err
|
2015-04-01 21:11:26 +00:00
|
|
|
default:
|
|
|
|
errType = logical.ErrInvalidRequest
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
2015-03-24 18:37:07 +00:00
|
|
|
|
2015-03-31 16:59:02 +00:00
|
|
|
return logical.ErrorResponse(err.Error()), errType
|
2015-03-24 18:37:07 +00:00
|
|
|
}
|
2015-03-11 21:31:55 +00:00
|
|
|
|
2015-04-01 21:33:48 +00:00
|
|
|
// Create an audit trail of the request
|
|
|
|
if err := c.auditBroker.LogRequest(auth, req); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to audit request (%#v): %v",
|
|
|
|
req, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
2015-03-11 21:31:55 +00:00
|
|
|
// Route the request
|
2015-03-16 22:28:50 +00:00
|
|
|
resp, err := c.router.Route(req)
|
|
|
|
|
2015-03-19 22:11:42 +00:00
|
|
|
// If there is a secret, we must register it with the expiration manager.
|
2015-04-01 04:01:12 +00:00
|
|
|
if resp != nil && resp.Secret != nil {
|
2015-04-03 22:42:34 +00:00
|
|
|
// Apply the default lease if none given
|
|
|
|
if resp.Secret.Lease == 0 {
|
|
|
|
resp.Secret.Lease = defaultLeaseDuration
|
|
|
|
}
|
|
|
|
|
|
|
|
// Limit the lease duration
|
|
|
|
if resp.Secret.Lease > maxLeaseDuration {
|
|
|
|
resp.Secret.Lease = maxLeaseDuration
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register the lease
|
2015-04-08 20:35:32 +00:00
|
|
|
leaseID, err := c.expiration.Register(req, resp)
|
2015-03-16 22:28:50 +00:00
|
|
|
if err != nil {
|
2015-03-19 22:11:42 +00:00
|
|
|
c.logger.Printf(
|
|
|
|
"[ERR] core: failed to register lease "+
|
|
|
|
"(request: %#v, response: %#v): %v", req, resp, err)
|
2015-03-16 22:28:50 +00:00
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
2015-04-08 20:35:32 +00:00
|
|
|
resp.Secret.LeaseID = leaseID
|
2015-03-16 22:28:50 +00:00
|
|
|
}
|
|
|
|
|
2015-04-03 01:05:23 +00:00
|
|
|
// Only the token store is allowed to return an auth block, for any
|
|
|
|
// other request this is an internal error
|
|
|
|
if resp != nil && resp.Auth != nil {
|
|
|
|
if !strings.HasPrefix(req.Path, "auth/token/") {
|
|
|
|
c.logger.Printf(
|
|
|
|
"[ERR] core: unexpected Auth response for non-token backend "+
|
|
|
|
"(request: %#v, response: %#v)", req, resp)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the default lease if non-provided, root tokens are exempt
|
|
|
|
if resp.Auth.Lease == 0 && !strListContains(resp.Auth.Policies, "root") {
|
|
|
|
resp.Auth.Lease = defaultLeaseDuration
|
|
|
|
}
|
|
|
|
|
2015-04-03 22:42:34 +00:00
|
|
|
// Limit the lease duration
|
|
|
|
if resp.Auth.Lease > maxLeaseDuration {
|
|
|
|
resp.Auth.Lease = maxLeaseDuration
|
|
|
|
}
|
|
|
|
|
2015-04-03 01:05:23 +00:00
|
|
|
// Register with the expiration manager
|
|
|
|
if err := c.expiration.RegisterAuth(req.Path, resp.Auth); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to register token lease "+
|
|
|
|
"(request: %#v, response: %#v): %v", req, resp, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-01 21:33:48 +00:00
|
|
|
// Create an audit trail of the response
|
|
|
|
if err := c.auditBroker.LogResponse(auth, req, resp, err); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to audit response (request: %#v, response: %#v): %v",
|
|
|
|
req, resp, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
2015-03-16 22:28:50 +00:00
|
|
|
// Return the response and error
|
|
|
|
return resp, err
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
2015-03-31 03:26:39 +00:00
|
|
|
// handleLoginRequest is used to handle a login request, which is an
|
|
|
|
// unauthenticated request to the backend.
|
|
|
|
func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, error) {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now())
|
|
|
|
|
2015-04-01 21:48:37 +00:00
|
|
|
// Create an audit trail of the request, auth is not available on login requests
|
|
|
|
if err := c.auditBroker.LogRequest(nil, req); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to audit request (%#v): %v",
|
|
|
|
req, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
2015-03-23 20:56:43 +00:00
|
|
|
// Route the request
|
2015-03-31 03:26:39 +00:00
|
|
|
resp, err := c.router.Route(req)
|
2015-03-23 20:56:43 +00:00
|
|
|
|
2015-03-31 03:26:39 +00:00
|
|
|
// If the response generated an authentication, then generate the token
|
2015-04-01 21:48:37 +00:00
|
|
|
var auth *logical.Auth
|
2015-03-31 03:26:39 +00:00
|
|
|
if resp != nil && resp.Auth != nil {
|
2015-04-03 00:52:11 +00:00
|
|
|
auth = resp.Auth
|
|
|
|
|
2015-03-23 20:56:43 +00:00
|
|
|
// Generate a token
|
|
|
|
te := TokenEntry{
|
|
|
|
Path: req.Path,
|
2015-04-03 00:52:11 +00:00
|
|
|
Policies: auth.Policies,
|
|
|
|
Meta: auth.Metadata,
|
2015-03-23 20:56:43 +00:00
|
|
|
}
|
|
|
|
if err := c.tokenStore.Create(&te); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to create token: %v", err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
2015-03-31 03:26:39 +00:00
|
|
|
// Populate the client token
|
|
|
|
resp.Auth.ClientToken = te.ID
|
2015-03-23 20:56:43 +00:00
|
|
|
|
2015-04-03 00:52:11 +00:00
|
|
|
// Set the default lease if non-provided, root tokens are exempt
|
|
|
|
if auth.Lease == 0 && !strListContains(auth.Policies, "root") {
|
|
|
|
auth.Lease = defaultLeaseDuration
|
|
|
|
}
|
2015-04-01 21:48:37 +00:00
|
|
|
|
2015-04-03 22:42:34 +00:00
|
|
|
// Limit the lease duration
|
|
|
|
if resp.Auth.Lease > maxLeaseDuration {
|
|
|
|
resp.Auth.Lease = maxLeaseDuration
|
|
|
|
}
|
|
|
|
|
2015-04-03 00:52:11 +00:00
|
|
|
// Register with the expiration manager
|
|
|
|
if err := c.expiration.RegisterAuth(req.Path, auth); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to register token lease "+
|
|
|
|
"(request: %#v, response: %#v): %v", req, resp, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
2015-03-23 20:56:43 +00:00
|
|
|
}
|
2015-03-31 03:26:39 +00:00
|
|
|
|
2015-04-01 21:48:37 +00:00
|
|
|
// Create an audit trail of the response
|
|
|
|
if err := c.auditBroker.LogResponse(auth, req, resp, err); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to audit response (request: %#v, response: %#v): %v",
|
|
|
|
req, resp, err)
|
|
|
|
return nil, ErrInternalError
|
|
|
|
}
|
|
|
|
|
2015-03-23 20:56:43 +00:00
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
2015-03-31 16:59:02 +00:00
|
|
|
func (c *Core) checkToken(
|
2015-04-01 21:33:48 +00:00
|
|
|
op logical.Operation, path string, token string) (*logical.Auth, error) {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "check_token"}, time.Now())
|
|
|
|
|
2015-03-31 16:59:02 +00:00
|
|
|
// Ensure there is a client token
|
|
|
|
if token == "" {
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, fmt.Errorf("missing client token")
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve the token policy
|
|
|
|
te, err := c.tokenStore.Lookup(token)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to lookup token: %v", err)
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, ErrInternalError
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the token is valid
|
|
|
|
if te == nil {
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, fmt.Errorf("invalid client token")
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Construct the corresponding ACL object
|
|
|
|
acl, err := c.policy.ACL(te.Policies...)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to construct ACL: %v", err)
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, ErrInternalError
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if this is a root protected path
|
2015-04-01 21:03:17 +00:00
|
|
|
if c.router.RootPath(path) && !acl.RootPrivilege(path) {
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, logical.ErrPermissionDenied
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the standard non-root ACLs
|
|
|
|
if !acl.AllowOperation(op, path) {
|
2015-04-01 21:33:48 +00:00
|
|
|
return nil, logical.ErrPermissionDenied
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
2015-04-01 21:33:48 +00:00
|
|
|
// Create the auth response
|
|
|
|
auth := &logical.Auth{
|
|
|
|
ClientToken: token,
|
|
|
|
Policies: te.Policies,
|
|
|
|
Metadata: te.Meta,
|
|
|
|
}
|
|
|
|
return auth, nil
|
2015-03-31 16:59:02 +00:00
|
|
|
}
|
|
|
|
|
2015-03-09 23:33:27 +00:00
|
|
|
// Initialized checks if the Vault is already initialized
|
|
|
|
func (c *Core) Initialized() (bool, error) {
|
2015-03-10 00:45:34 +00:00
|
|
|
// Check the barrier first
|
|
|
|
init, err := c.barrier.Initialized()
|
2015-03-11 22:50:27 +00:00
|
|
|
if err != nil {
|
2015-03-10 00:45:34 +00:00
|
|
|
c.logger.Printf("[ERR] core: barrier init check failed: %v", err)
|
|
|
|
return false, err
|
|
|
|
}
|
2015-03-11 22:50:27 +00:00
|
|
|
if !init {
|
|
|
|
return false, nil
|
|
|
|
}
|
2015-03-10 00:45:34 +00:00
|
|
|
if !init {
|
|
|
|
c.logger.Printf("[INFO] core: security barrier not initialized")
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the seal configuration
|
|
|
|
sealConf, err := c.SealConfig()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if sealConf == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize is used to initialize the Vault with the given
|
|
|
|
// configurations.
|
2015-03-10 00:45:34 +00:00
|
|
|
func (c *Core) Initialize(config *SealConfig) (*InitResult, error) {
|
|
|
|
// Check if the seal configuraiton is valid
|
|
|
|
if err := config.Validate(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
|
|
|
|
return nil, fmt.Errorf("invalid seal configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid an initialization race
|
|
|
|
c.stateLock.Lock()
|
|
|
|
defer c.stateLock.Unlock()
|
|
|
|
|
|
|
|
// Check if we are initialized
|
|
|
|
init, err := c.Initialized()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if init {
|
|
|
|
return nil, ErrAlreadyInit
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode the seal configuration
|
|
|
|
buf, err := json.Marshal(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to encode seal configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the seal configuration
|
|
|
|
pe := &physical.Entry{
|
|
|
|
Key: coreSealConfigPath,
|
|
|
|
Value: buf,
|
|
|
|
}
|
|
|
|
if err := c.physical.Put(pe); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to read seal configuration: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to check seal configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a master key
|
|
|
|
masterKey, err := c.barrier.GenerateKey()
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to generate master key: %v", err)
|
|
|
|
return nil, fmt.Errorf("master key generation failed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the barrier
|
|
|
|
if err := c.barrier.Initialize(masterKey); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to initialize barrier: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to initialize barrier: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the master key if only a single key part is used
|
|
|
|
results := new(InitResult)
|
2015-03-11 18:34:08 +00:00
|
|
|
if config.SecretShares == 1 {
|
|
|
|
results.SecretShares = append(results.SecretShares, masterKey)
|
2015-03-10 00:45:34 +00:00
|
|
|
|
|
|
|
} else {
|
2015-03-11 18:34:08 +00:00
|
|
|
// Split the master key using the Shamir algorithm
|
|
|
|
shares, err := shamir.Split(masterKey, config.SecretShares, config.SecretThreshold)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to generate shares: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to generate shares: %v", err)
|
|
|
|
}
|
|
|
|
results.SecretShares = shares
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
2015-03-11 18:34:08 +00:00
|
|
|
c.logger.Printf("[INFO] core: security barrier initialized")
|
2015-03-24 00:31:30 +00:00
|
|
|
|
|
|
|
// Unseal the barrier
|
|
|
|
if err := c.barrier.Unseal(masterKey); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to unseal barrier: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to unseal barrier: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the barrier is re-sealed
|
|
|
|
defer func() {
|
|
|
|
if err := c.barrier.Seal(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to seal barrier: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Perform initial setup
|
|
|
|
if err := c.postUnseal(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: post-unseal setup failed: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a new root token
|
|
|
|
rootToken, err := c.tokenStore.RootToken()
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: root token generation failed: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
results.RootToken = rootToken.ID
|
|
|
|
c.logger.Printf("[INFO] core: root token generated")
|
|
|
|
|
|
|
|
// Prepare to re-seal
|
|
|
|
if err := c.preSeal(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: pre-seal teardown failed: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-10 00:45:34 +00:00
|
|
|
return results, nil
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sealed checks if the Vault is current sealed
|
|
|
|
func (c *Core) Sealed() (bool, error) {
|
2015-03-10 00:45:34 +00:00
|
|
|
c.stateLock.RLock()
|
|
|
|
defer c.stateLock.RUnlock()
|
|
|
|
return c.sealed, nil
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SealConfiguration is used to return information
|
|
|
|
// about the configuration of the Vault and it's current
|
|
|
|
// status.
|
|
|
|
func (c *Core) SealConfig() (*SealConfig, error) {
|
2015-03-10 00:45:34 +00:00
|
|
|
// Fetch the core configuration
|
|
|
|
pe, err := c.physical.Get(coreSealConfigPath)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to read seal configuration: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to check seal configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the seal configuration is missing, we are not initialized
|
|
|
|
if pe == nil {
|
|
|
|
c.logger.Printf("[INFO] core: seal configuration missing, not initialized")
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the barrier entry
|
|
|
|
var conf SealConfig
|
|
|
|
if err := json.Unmarshal(pe.Value, &conf); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: failed to decode seal configuration: %v", err)
|
|
|
|
return nil, fmt.Errorf("failed to decode seal configuration: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a valid seal configuration
|
|
|
|
if err := conf.Validate(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: invalid seal configuration: %v", err)
|
|
|
|
return nil, fmt.Errorf("seal validation failed: %v", err)
|
|
|
|
}
|
|
|
|
return &conf, nil
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
|
|
|
|
2015-03-11 18:52:01 +00:00
|
|
|
// SecretProgress returns the number of keys provided so far
|
|
|
|
func (c *Core) SecretProgress() int {
|
|
|
|
c.stateLock.RLock()
|
|
|
|
defer c.stateLock.RUnlock()
|
|
|
|
return len(c.unlockParts)
|
|
|
|
}
|
|
|
|
|
2015-03-15 00:47:11 +00:00
|
|
|
// Unseal is used to provide one of the key parts to unseal the Vault.
|
2015-03-15 01:25:36 +00:00
|
|
|
//
|
|
|
|
// They key given as a parameter will automatically be zerod after
|
|
|
|
// this method is done with it. If you want to keep the key around, a copy
|
|
|
|
// should be made.
|
|
|
|
func (c *Core) Unseal(key []byte) (bool, error) {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "unseal"}, time.Now())
|
|
|
|
|
2015-03-12 18:20:27 +00:00
|
|
|
// Verify the key length
|
|
|
|
min, max := c.barrier.KeyLength()
|
|
|
|
max += shamir.ShareOverhead
|
|
|
|
if len(key) < min {
|
|
|
|
return false, &ErrInvalidKey{fmt.Sprintf("key is shorter than minimum %d bytes", min)}
|
|
|
|
}
|
|
|
|
if len(key) > max {
|
|
|
|
return false, &ErrInvalidKey{fmt.Sprintf("key is longer than maximum %d bytes", max)}
|
|
|
|
}
|
|
|
|
|
2015-03-11 18:43:36 +00:00
|
|
|
// Get the seal configuration
|
|
|
|
config, err := c.SealConfig()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the barrier is initialized
|
|
|
|
if config == nil {
|
|
|
|
return false, ErrNotInit
|
|
|
|
}
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
c.stateLock.Lock()
|
|
|
|
defer c.stateLock.Unlock()
|
|
|
|
|
2015-03-11 18:43:36 +00:00
|
|
|
// Check if already unsealed
|
|
|
|
if !c.sealed {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we already have this piece
|
|
|
|
for _, existing := range c.unlockParts {
|
|
|
|
if bytes.Equal(existing, key) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store this key
|
|
|
|
c.unlockParts = append(c.unlockParts, key)
|
|
|
|
|
|
|
|
// Check if we don't have enough keys to unlock
|
|
|
|
if len(c.unlockParts) < config.SecretThreshold {
|
|
|
|
c.logger.Printf("[DEBUG] core: cannot unseal, have %d of %d keys",
|
|
|
|
len(c.unlockParts), config.SecretThreshold)
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recover the master key
|
|
|
|
var masterKey []byte
|
|
|
|
if config.SecretThreshold == 1 {
|
|
|
|
masterKey = c.unlockParts[0]
|
|
|
|
c.unlockParts = nil
|
|
|
|
} else {
|
|
|
|
masterKey, err = shamir.Combine(c.unlockParts)
|
|
|
|
c.unlockParts = nil
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("failed to compute master key: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer memzero(masterKey)
|
|
|
|
|
|
|
|
// Attempt to unlock
|
|
|
|
if err := c.barrier.Unseal(masterKey); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[INFO] core: vault is unsealed")
|
2015-03-11 18:43:36 +00:00
|
|
|
|
2015-03-11 22:19:41 +00:00
|
|
|
// Do post-unseal setup
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[INFO] core: post-unseal setup starting")
|
2015-03-11 22:19:41 +00:00
|
|
|
if err := c.postUnseal(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: post-unseal setup failed: %v", err)
|
|
|
|
c.barrier.Seal()
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[WARN] core: vault is sealed")
|
2015-03-11 22:19:41 +00:00
|
|
|
return false, err
|
|
|
|
}
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[INFO] core: post-unseal setup complete")
|
2015-03-11 22:19:41 +00:00
|
|
|
|
2015-03-11 18:43:36 +00:00
|
|
|
// Success!
|
|
|
|
c.sealed = false
|
|
|
|
return true, nil
|
2015-03-09 23:33:27 +00:00
|
|
|
}
|
2015-03-10 00:45:34 +00:00
|
|
|
|
2015-03-13 18:16:24 +00:00
|
|
|
// Seal is used to re-seal the Vault. This requires the Vault to
|
2015-03-10 00:45:34 +00:00
|
|
|
// be unsealed again to perform any further operations.
|
2015-03-31 16:59:02 +00:00
|
|
|
func (c *Core) Seal(token string) error {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "seal"}, time.Now())
|
2015-03-10 00:45:34 +00:00
|
|
|
c.stateLock.Lock()
|
|
|
|
defer c.stateLock.Unlock()
|
|
|
|
if c.sealed {
|
|
|
|
return nil
|
|
|
|
}
|
2015-03-31 16:59:02 +00:00
|
|
|
|
|
|
|
// Validate the token is a root token
|
2015-04-01 21:33:48 +00:00
|
|
|
_, err := c.checkToken(logical.WriteOperation, "sys/seal", token)
|
2015-03-31 16:59:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-03-10 00:45:34 +00:00
|
|
|
c.sealed = true
|
2015-03-13 18:16:24 +00:00
|
|
|
|
|
|
|
// Do pre-seal teardown
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[INFO] core: pre-seal teardown starting")
|
2015-03-13 18:16:24 +00:00
|
|
|
if err := c.preSeal(); err != nil {
|
|
|
|
c.logger.Printf("[ERR] core: pre-seal teardown failed: %v", err)
|
|
|
|
return fmt.Errorf("internal error")
|
|
|
|
}
|
2015-03-13 18:34:40 +00:00
|
|
|
c.logger.Printf("[INFO] core: pre-seal teardown complete")
|
2015-03-13 18:16:24 +00:00
|
|
|
|
2015-03-13 18:34:40 +00:00
|
|
|
if err := c.barrier.Seal(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.logger.Printf("[INFO] core: vault is sealed")
|
|
|
|
return nil
|
2015-03-10 00:45:34 +00:00
|
|
|
}
|
2015-03-11 22:19:41 +00:00
|
|
|
|
|
|
|
// postUnseal is invoked after the barrier is unsealed, but before
|
|
|
|
// allowing any user operations. This allows us to setup any state that
|
|
|
|
// requires the Vault to be unsealed such as mount tables, logical backends,
|
|
|
|
// credential stores, etc.
|
|
|
|
func (c *Core) postUnseal() error {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "post_unseal"}, time.Now())
|
2015-04-14 18:08:04 +00:00
|
|
|
if cache, ok := c.physical.(*physical.Cache); ok {
|
|
|
|
cache.Purge()
|
|
|
|
}
|
2015-03-11 22:19:41 +00:00
|
|
|
if err := c.loadMounts(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-11 22:50:27 +00:00
|
|
|
if err := c.setupMounts(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-17 23:23:58 +00:00
|
|
|
if err := c.startRollback(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-18 21:00:42 +00:00
|
|
|
if err := c.setupPolicyStore(); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2015-03-18 22:46:07 +00:00
|
|
|
if err := c.loadCredentials(); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2015-03-18 22:30:31 +00:00
|
|
|
if err := c.setupCredentials(); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2015-03-24 01:00:14 +00:00
|
|
|
if err := c.setupExpiration(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-27 21:00:38 +00:00
|
|
|
if err := c.loadAudits(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := c.setupAudits(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-08 23:43:17 +00:00
|
|
|
c.metricsCh = make(chan struct{})
|
|
|
|
go c.emitMetrics(c.metricsCh)
|
2015-03-11 22:19:41 +00:00
|
|
|
return nil
|
|
|
|
}
|
2015-03-13 18:16:24 +00:00
|
|
|
|
|
|
|
// preSeal is invoked before the barrier is sealed, allowing
|
|
|
|
// for any state teardown required.
|
|
|
|
func (c *Core) preSeal() error {
|
2015-04-08 23:43:17 +00:00
|
|
|
defer metrics.MeasureSince([]string{"core", "pre_seal"}, time.Now())
|
|
|
|
if c.metricsCh != nil {
|
|
|
|
close(c.metricsCh)
|
|
|
|
c.metricsCh = nil
|
|
|
|
}
|
2015-03-27 21:00:38 +00:00
|
|
|
if err := c.teardownAudits(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-24 01:00:14 +00:00
|
|
|
if err := c.stopExpiration(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-18 22:30:31 +00:00
|
|
|
if err := c.teardownCredentials(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-18 21:00:42 +00:00
|
|
|
if err := c.teardownPolicyStore(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-17 23:23:58 +00:00
|
|
|
if err := c.stopRollback(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-03-13 18:16:24 +00:00
|
|
|
if err := c.unloadMounts(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-14 18:08:04 +00:00
|
|
|
if cache, ok := c.physical.(*physical.Cache); ok {
|
|
|
|
cache.Purge()
|
|
|
|
}
|
2015-03-13 18:16:24 +00:00
|
|
|
return nil
|
|
|
|
}
|
2015-04-08 23:43:17 +00:00
|
|
|
|
|
|
|
// emitMetrics is used to periodically expose metrics while runnig
|
|
|
|
func (c *Core) emitMetrics(stopCh chan struct{}) {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
c.expiration.emitMetrics()
|
|
|
|
case <-stopCh:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|