diff --git a/builtin/credential/app-id/backend.go b/builtin/credential/app-id/backend.go index f0bd321487..76d9a6e996 100644 --- a/builtin/credential/app-id/backend.go +++ b/builtin/credential/app-id/backend.go @@ -17,20 +17,10 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { } func Backend(conf *logical.BackendConfig) (*framework.Backend, error) { - // Initialize the salt - salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ - HashFunc: salt.SHA1Hash, - }) - if err != nil { - return nil, err - } - var b backend - b.Salt = salt b.MapAppId = &framework.PolicyMap{ PathMap: framework.PathMap{ Name: "app-id", - Salt: salt, Schema: map[string]*framework.FieldSchema{ "display_name": &framework.FieldSchema{ Type: framework.TypeString, @@ -48,7 +38,6 @@ func Backend(conf *logical.BackendConfig) (*framework.Backend, error) { b.MapUserId = &framework.PathMap{ Name: "user-id", - Salt: salt, Schema: map[string]*framework.FieldSchema{ "cidr_block": &framework.FieldSchema{ Type: framework.TypeString, @@ -81,17 +70,11 @@ func Backend(conf *logical.BackendConfig) (*framework.Backend, error) { ), AuthRenew: b.pathLoginRenew, + + Init: b.initialize, } - // Since the salt is new in 0.2, we need to handle this by migrating - // any existing keys to use the salt. We can deprecate this eventually, - // but for now we want a smooth upgrade experience by automatically - // upgrading to use salting. - if salt.DidGenerate() { - if err := b.upgradeToSalted(conf.StorageView); err != nil { - return nil, err - } - } + b.view = conf.StorageView return b.Backend, nil } @@ -100,10 +83,36 @@ type backend struct { *framework.Backend Salt *salt.Salt + view logical.Storage MapAppId *framework.PolicyMap MapUserId *framework.PathMap } +func (b *backend) initialize() error { + salt, err := salt.NewSalt(b.view, &salt.Config{ + HashFunc: salt.SHA1Hash, + }) + if err != nil { + return err + } + b.Salt = salt + + b.MapAppId.Salt = salt + b.MapUserId.Salt = salt + + // Since the salt is new in 0.2, we need to handle this by migrating + // any existing keys to use the salt. We can deprecate this eventually, + // but for now we want a smooth upgrade experience by automatically + // upgrading to use salting. + if salt.DidGenerate() { + if err := b.upgradeToSalted(b.view); err != nil { + return err + } + } + + return nil +} + // upgradeToSalted is used to upgrade the non-salted keys prior to // Vault 0.2 to be salted. This is done on mount time and is only // done once. It can be deprecated eventually, but should be around diff --git a/builtin/credential/app-id/backend_test.go b/builtin/credential/app-id/backend_test.go index 55bb3e9d85..2960e4060e 100644 --- a/builtin/credential/app-id/backend_test.go +++ b/builtin/credential/app-id/backend_test.go @@ -72,6 +72,10 @@ func TestBackend_upgradeToSalted(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + err = backend.Initialize() + if err != nil { + t.Fatalf("err: %v", err) + } // Check the keys have been upgraded out, err := inm.Get("struct/map/app-id/foo") diff --git a/builtin/credential/approle/backend.go b/builtin/credential/approle/backend.go index df10e9d1bb..d40b75ebff 100644 --- a/builtin/credential/approle/backend.go +++ b/builtin/credential/approle/backend.go @@ -17,6 +17,9 @@ type backend struct { // by this backend. salt *salt.Salt + // The view to use when creating the salt + view logical.Storage + // Guard to clean-up the expired SecretID entries tidySecretIDCASGuard uint32 @@ -57,18 +60,9 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { } func Backend(conf *logical.BackendConfig) (*backend, error) { - // Initialize the salt - salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ - HashFunc: salt.SHA256Hash, - }) - if err != nil { - return nil, err - } - // Create a backend object b := &backend{ - // Set the salt object for the backend - salt: salt, + view: conf.StorageView, // Create the map of locks to modify the registered roles roleLocksMap: make(map[string]*sync.RWMutex, 257), @@ -83,6 +77,8 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { secretIDAccessorLocksMap: make(map[string]*sync.RWMutex, 257), } + var err error + // Create 256 locks each for managing RoleID and SecretIDs. This will avoid // a superfluous number of locks directly proportional to the number of RoleID // and SecretIDs. These locks can be accessed by indexing based on the first two @@ -129,10 +125,22 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { pathTidySecretID(b), }, ), + Init: b.initialize, } return b, nil } +func (b *backend) initialize() error { + salt, err := salt.NewSalt(b.view, &salt.Config{ + HashFunc: salt.SHA256Hash, + }) + if err != nil { + return err + } + b.salt = salt + return nil +} + // periodicFunc of the backend will be invoked once a minute by the RollbackManager. // RoleRole backend utilizes this function to delete expired SecretID entries. // This could mean that the SecretID may live in the backend upto 1 min after its diff --git a/builtin/credential/approle/backend_test.go b/builtin/credential/approle/backend_test.go index 2a3e3773ec..e49cf48f8b 100644 --- a/builtin/credential/approle/backend_test.go +++ b/builtin/credential/approle/backend_test.go @@ -21,5 +21,9 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { if err != nil { t.Fatal(err) } + err = b.Initialize() + if err != nil { + t.Fatal(err) + } return b, config.StorageView } diff --git a/builtin/credential/aws-ec2/backend.go b/builtin/credential/aws-ec2/backend.go index f24f3bfe88..33ef4a80ce 100644 --- a/builtin/credential/aws-ec2/backend.go +++ b/builtin/credential/aws-ec2/backend.go @@ -23,6 +23,9 @@ type backend struct { *framework.Backend Salt *salt.Salt + // Used during initialization to set the salt + view logical.Storage + // Lock to make changes to any of the backend's configuration endpoints. configMutex sync.RWMutex @@ -59,18 +62,11 @@ type backend struct { } func Backend(conf *logical.BackendConfig) (*backend, error) { - salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ - HashFunc: salt.SHA256Hash, - }) - if err != nil { - return nil, err - } - b := &backend{ // Setting the periodic func to be run once in an hour. // If there is a real need, this can be made configurable. tidyCooldownPeriod: time.Hour, - Salt: salt, + view: conf.StorageView, EC2ClientsMap: make(map[string]map[string]*ec2.EC2), IAMClientsMap: make(map[string]map[string]*iam.IAM), } @@ -83,6 +79,9 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { Unauthenticated: []string{ "login", }, + LocalStorage: []string{ + "whitelist/identity/", + }, }, Paths: []*framework.Path{ pathLogin(b), @@ -104,11 +103,26 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { pathIdentityWhitelist(b), pathTidyIdentityWhitelist(b), }, + + Invalidate: b.invalidate, + + Init: b.initialize, } return b, nil } +func (b *backend) initialize() error { + salt, err := salt.NewSalt(b.view, &salt.Config{ + HashFunc: salt.SHA256Hash, + }) + if err != nil { + return err + } + b.Salt = salt + return nil +} + // periodicFunc performs the tasks that the backend wishes to do periodically. // Currently this will be triggered once in a minute by the RollbackManager. // @@ -169,6 +183,16 @@ func (b *backend) periodicFunc(req *logical.Request) error { return nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/client": + b.configMutex.Lock() + defer b.configMutex.Unlock() + b.flushCachedEC2Clients() + b.flushCachedIAMClients() + } +} + const backendHelp = ` aws-ec2 auth backend takes in PKCS#7 signature of an AWS EC2 instance and a client created nonce to authenticates the EC2 instance with Vault. diff --git a/builtin/credential/cert/backend.go b/builtin/credential/cert/backend.go index a07ec33671..088cc41ab1 100644 --- a/builtin/credential/cert/backend.go +++ b/builtin/credential/cert/backend.go @@ -1,6 +1,7 @@ package cert import ( + "strings" "sync" "github.com/hashicorp/vault/logical" @@ -13,7 +14,7 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { if err != nil { return b, err } - return b, b.populateCRLs(conf.StorageView) + return b, nil } func Backend() *backend { @@ -36,9 +37,10 @@ func Backend() *backend { }), AuthRenew: b.pathLoginRenew, + + Invalidate: b.invalidate, } - b.crls = map[string]CRLInfo{} b.crlUpdateMutex = &sync.RWMutex{} return &b @@ -52,6 +54,15 @@ type backend struct { crlUpdateMutex *sync.RWMutex } +func (b *backend) invalidate(key string) { + switch { + case strings.HasPrefix(key, "crls/"): + b.crlUpdateMutex.Lock() + defer b.crlUpdateMutex.Unlock() + b.crls = nil + } +} + const backendHelp = ` The "cert" credential provider allows authentication using TLS client certificates. A client connects to Vault and uses diff --git a/builtin/credential/cert/path_crls.go b/builtin/credential/cert/path_crls.go index f45effbdd4..234b93adc7 100644 --- a/builtin/credential/cert/path_crls.go +++ b/builtin/credential/cert/path_crls.go @@ -45,6 +45,12 @@ func (b *backend) populateCRLs(storage logical.Storage) error { b.crlUpdateMutex.Lock() defer b.crlUpdateMutex.Unlock() + if b.crls != nil { + return nil + } + + b.crls = map[string]CRLInfo{} + keys, err := storage.List("crls/") if err != nil { return fmt.Errorf("error listing CRLs: %v", err) @@ -56,6 +62,7 @@ func (b *backend) populateCRLs(storage logical.Storage) error { for _, key := range keys { entry, err := storage.Get("crls/" + key) if err != nil { + b.crls = nil return fmt.Errorf("error loading CRL %s: %v", key, err) } if entry == nil { @@ -64,6 +71,7 @@ func (b *backend) populateCRLs(storage logical.Storage) error { var crlInfo CRLInfo err = entry.DecodeJSON(&crlInfo) if err != nil { + b.crls = nil return fmt.Errorf("error decoding CRL %s: %v", key, err) } b.crls[key] = crlInfo @@ -121,6 +129,10 @@ func (b *backend) pathCRLDelete( return logical.ErrorResponse(`"name" parameter cannot be empty`), nil } + if err := b.populateCRLs(req.Storage); err != nil { + return nil, err + } + b.crlUpdateMutex.Lock() defer b.crlUpdateMutex.Unlock() @@ -131,8 +143,7 @@ func (b *backend) pathCRLDelete( )), nil } - err := req.Storage.Delete("crls/" + name) - if err != nil { + if err := req.Storage.Delete("crls/" + name); err != nil { return logical.ErrorResponse(fmt.Sprintf( "error deleting crl %s: %v", name, err), ), nil @@ -150,6 +161,10 @@ func (b *backend) pathCRLRead( return logical.ErrorResponse(`"name" parameter must be set`), nil } + if err := b.populateCRLs(req.Storage); err != nil { + return nil, err + } + b.crlUpdateMutex.RLock() defer b.crlUpdateMutex.RUnlock() @@ -185,6 +200,10 @@ func (b *backend) pathCRLWrite( return logical.ErrorResponse("parsed CRL is nil"), nil } + if err := b.populateCRLs(req.Storage); err != nil { + return nil, err + } + b.crlUpdateMutex.Lock() defer b.crlUpdateMutex.Unlock() diff --git a/builtin/logical/aws/backend.go b/builtin/logical/aws/backend.go index 6c9ced381b..246e25cf24 100644 --- a/builtin/logical/aws/backend.go +++ b/builtin/logical/aws/backend.go @@ -17,6 +17,12 @@ func Backend() *backend { b.Backend = &framework.Backend{ Help: strings.TrimSpace(backendHelp), + PathsSpecial: &logical.Paths{ + LocalStorage: []string{ + framework.WALPrefix, + }, + }, + Paths: []*framework.Path{ pathConfigRoot(), pathConfigLease(&b), diff --git a/builtin/logical/cassandra/backend.go b/builtin/logical/cassandra/backend.go index 81149f806d..c2e769ccb4 100644 --- a/builtin/logical/cassandra/backend.go +++ b/builtin/logical/cassandra/backend.go @@ -31,6 +31,8 @@ func Backend() *backend { secretCreds(&b), }, + Invalidate: b.invalidate, + Clean: func() { b.ResetDB(nil) }, @@ -107,6 +109,13 @@ func (b *backend) ResetDB(newSession *gocql.Session) { b.session = newSession } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.ResetDB(nil) + } +} + const backendHelp = ` The Cassandra backend dynamically generates database users. diff --git a/builtin/logical/mongodb/backend.go b/builtin/logical/mongodb/backend.go index 8360e971da..e9f3b29add 100644 --- a/builtin/logical/mongodb/backend.go +++ b/builtin/logical/mongodb/backend.go @@ -33,6 +33,8 @@ func Backend() *framework.Backend { }, Clean: b.ResetSession, + + Invalidate: b.invalidate, } return b.Backend @@ -97,6 +99,13 @@ func (b *backend) ResetSession() { b.session = nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.ResetSession() + } +} + // LeaseConfig returns the lease configuration func (b *backend) LeaseConfig(s logical.Storage) (*configLease, error) { entry, err := s.Get("config/lease") diff --git a/builtin/logical/mssql/backend.go b/builtin/logical/mssql/backend.go index 02597788b5..2a7689f17f 100644 --- a/builtin/logical/mssql/backend.go +++ b/builtin/logical/mssql/backend.go @@ -32,6 +32,8 @@ func Backend() *backend { secretCreds(&b), }, + Invalidate: b.invalidate, + Clean: b.ResetDB, } @@ -112,6 +114,13 @@ func (b *backend) ResetDB() { b.db = nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.ResetDB() + } +} + // LeaseConfig returns the lease configuration func (b *backend) LeaseConfig(s logical.Storage) (*configLease, error) { entry, err := s.Get("config/lease") diff --git a/builtin/logical/mysql/backend.go b/builtin/logical/mysql/backend.go index 6bc3057340..7ae0335e1a 100644 --- a/builtin/logical/mysql/backend.go +++ b/builtin/logical/mysql/backend.go @@ -32,6 +32,8 @@ func Backend() *backend { secretCreds(&b), }, + Invalidate: b.invalidate, + Clean: b.ResetDB, } @@ -105,6 +107,13 @@ func (b *backend) ResetDB() { b.db = nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.ResetDB() + } +} + // Lease returns the lease information func (b *backend) Lease(s logical.Storage) (*configLease, error) { entry, err := s.Get("config/lease") diff --git a/builtin/logical/pki/backend.go b/builtin/logical/pki/backend.go index 19e2cbc075..6128028346 100644 --- a/builtin/logical/pki/backend.go +++ b/builtin/logical/pki/backend.go @@ -29,6 +29,12 @@ func Backend() *backend { "crl/pem", "crl", }, + + LocalStorage: []string{ + "revoked/", + "crl", + "certs/", + }, }, Paths: []*framework.Path{ diff --git a/builtin/logical/postgresql/backend.go b/builtin/logical/postgresql/backend.go index 8dc59aa211..6f4befd8a1 100644 --- a/builtin/logical/postgresql/backend.go +++ b/builtin/logical/postgresql/backend.go @@ -34,6 +34,8 @@ func Backend(conf *logical.BackendConfig) *backend { }, Clean: b.ResetDB, + + Invalidate: b.invalidate, } b.logger = conf.Logger @@ -126,6 +128,13 @@ func (b *backend) ResetDB() { b.db = nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.ResetDB() + } +} + // Lease returns the lease information func (b *backend) Lease(s logical.Storage) (*configLease, error) { entry, err := s.Get("config/lease") diff --git a/builtin/logical/rabbitmq/backend.go b/builtin/logical/rabbitmq/backend.go index e0123753e5..4f9cde0d7e 100644 --- a/builtin/logical/rabbitmq/backend.go +++ b/builtin/logical/rabbitmq/backend.go @@ -35,6 +35,8 @@ func Backend() *backend { }, Clean: b.resetClient, + + Invalidate: b.invalidate, } return &b @@ -99,6 +101,13 @@ func (b *backend) resetClient() { b.client = nil } +func (b *backend) invalidate(key string) { + switch key { + case "config/connection": + b.resetClient() + } +} + // Lease returns the lease information func (b *backend) Lease(s logical.Storage) (*configLease, error) { entry, err := s.Get("config/lease") diff --git a/builtin/logical/ssh/backend.go b/builtin/logical/ssh/backend.go index d39cae2a50..06fc4ccd82 100644 --- a/builtin/logical/ssh/backend.go +++ b/builtin/logical/ssh/backend.go @@ -10,6 +10,7 @@ import ( type backend struct { *framework.Backend + view logical.Storage salt *salt.Salt } @@ -22,15 +23,8 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) { } func Backend(conf *logical.BackendConfig) (*backend, error) { - salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ - HashFunc: salt.SHA256Hash, - }) - if err != nil { - return nil, err - } - var b backend - b.salt = salt + b.view = conf.StorageView b.Backend = &framework.Backend{ Help: strings.TrimSpace(backendHelp), @@ -38,6 +32,10 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { Unauthenticated: []string{ "verify", }, + + LocalStorage: []string{ + "otp/", + }, }, Paths: []*framework.Path{ @@ -54,10 +52,23 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { secretDynamicKey(&b), secretOTP(&b), }, + + Init: b.Initialize, } return &b, nil } +func (b *backend) Initialize() error { + salt, err := salt.NewSalt(b.view, &salt.Config{ + HashFunc: salt.SHA256Hash, + }) + if err != nil { + return err + } + b.salt = salt + return nil +} + const backendHelp = ` The SSH backend generates credentials allowing clients to establish SSH connections to remote hosts. diff --git a/builtin/logical/ssh/backend_test.go b/builtin/logical/ssh/backend_test.go index 2fa3a0be21..5012465326 100644 --- a/builtin/logical/ssh/backend_test.go +++ b/builtin/logical/ssh/backend_test.go @@ -73,6 +73,10 @@ func TestBackend_allowed_users(t *testing.T) { if err != nil { t.Fatal(err) } + err = b.Initialize() + if err != nil { + t.Fatal(err) + } roleData := map[string]interface{}{ "key_type": "otp", diff --git a/builtin/logical/transit/backend.go b/builtin/logical/transit/backend.go index 7737a8a756..37ebca43e4 100644 --- a/builtin/logical/transit/backend.go +++ b/builtin/logical/transit/backend.go @@ -1,6 +1,8 @@ package transit import ( + "strings" + "github.com/hashicorp/vault/helper/keysutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" @@ -39,6 +41,8 @@ func Backend(conf *logical.BackendConfig) *backend { }, Secrets: []*framework.Secret{}, + + Invalidate: b.invalidate, } b.lm = keysutil.NewLockManager(conf.System.CachingDisabled()) @@ -50,3 +54,14 @@ type backend struct { *framework.Backend lm *keysutil.LockManager } + +func (b *backend) invalidate(key string) { + if b.Logger().IsTrace() { + b.Logger().Trace("transit: invalidating key", "key", key) + } + switch { + case strings.HasPrefix(key, "policy/"): + name := strings.TrimPrefix(key, "policy/") + b.lm.InvalidatePolicy(name) + } +} diff --git a/command/mount.go b/command/mount.go index bfdba87591..9700dccdbf 100644 --- a/command/mount.go +++ b/command/mount.go @@ -15,11 +15,13 @@ type MountCommand struct { func (c *MountCommand) Run(args []string) int { var description, path, defaultLeaseTTL, maxLeaseTTL string + var local bool flags := c.Meta.FlagSet("mount", meta.FlagSetDefault) flags.StringVar(&description, "description", "", "") flags.StringVar(&path, "path", "", "") flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "") flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "") + flags.BoolVar(&local, "local", false, "") flags.Usage = func() { c.Ui.Error(c.Help()) } if err := flags.Parse(args); err != nil { return 1 @@ -54,6 +56,7 @@ func (c *MountCommand) Run(args []string) int { DefaultLeaseTTL: defaultLeaseTTL, MaxLeaseTTL: maxLeaseTTL, }, + Local: local, } if err := client.Sys().Mount(path, mountInfo); err != nil { @@ -102,6 +105,10 @@ Mount Options: the previously set value. Set to '0' to explicitly set it to use the global default. + -local Mark the mount as a local mount. Local mounts + are not replicated nor (if a secondary) + removed by replication. + ` return strings.TrimSpace(helpText) } diff --git a/command/mounts.go b/command/mounts.go index 823aa57e75..d57837dd2c 100644 --- a/command/mounts.go +++ b/command/mounts.go @@ -42,7 +42,7 @@ func (c *MountsCommand) Run(args []string) int { } sort.Strings(paths) - columns := []string{"Path | Type | Default TTL | Max TTL | Description"} + columns := []string{"Path | Type | Default TTL | Max TTL | Replication Behavior | Description"} for _, path := range paths { mount := mounts[path] defTTL := "system" @@ -63,8 +63,12 @@ func (c *MountsCommand) Run(args []string) int { case mount.Config.MaxLeaseTTL != 0: maxTTL = strconv.Itoa(mount.Config.MaxLeaseTTL) } + replicatedBehavior := "replicated" + if mount.Local { + replicatedBehavior = "local" + } columns = append(columns, fmt.Sprintf( - "%s | %s | %s | %s | %s", path, mount.Type, defTTL, maxTTL, mount.Description)) + "%s | %s | %s | %s | %s | %s", path, mount.Type, defTTL, maxTTL, replicatedBehavior, mount.Description)) } c.Ui.Output(columnize.SimpleFormat(columns)) diff --git a/command/server.go b/command/server.go index cf8a442d49..e0b81f90a4 100644 --- a/command/server.go +++ b/command/server.go @@ -61,7 +61,7 @@ type ServerCommand struct { } func (c *ServerCommand) Run(args []string) int { - var dev, verifyOnly, devHA bool + var dev, verifyOnly, devHA, devTransactional bool var configPath []string var logLevel, devRootTokenID, devListenAddress string flags := c.Meta.FlagSet("server", meta.FlagSetDefault) @@ -70,7 +70,8 @@ func (c *ServerCommand) Run(args []string) int { flags.StringVar(&devListenAddress, "dev-listen-address", "", "") flags.StringVar(&logLevel, "log-level", "info", "") flags.BoolVar(&verifyOnly, "verify-only", false, "") - flags.BoolVar(&devHA, "dev-ha", false, "") + flags.BoolVar(&devHA, "ha", false, "") + flags.BoolVar(&devTransactional, "transactional", false, "") flags.Usage = func() { c.Ui.Output(c.Help()) } flags.Var((*sliceflag.StringFlag)(&configPath), "config", "config") if err := flags.Parse(args); err != nil { @@ -122,7 +123,7 @@ func (c *ServerCommand) Run(args []string) int { devListenAddress = os.Getenv("VAULT_DEV_LISTEN_ADDRESS") } - if devHA { + if devHA || devTransactional { dev = true } @@ -143,7 +144,7 @@ func (c *ServerCommand) Run(args []string) int { // Load the configuration var config *server.Config if dev { - config = server.DevConfig(devHA) + config = server.DevConfig(devHA, devTransactional) if devListenAddress != "" { config.Listeners[0].Config["address"] = devListenAddress } @@ -235,6 +236,9 @@ func (c *ServerCommand) Run(args []string) int { ClusterName: config.ClusterName, CacheSize: config.CacheSize, } + if dev { + coreConfig.DevToken = devRootTokenID + } var disableClustering bool diff --git a/command/server/config.go b/command/server/config.go index 8840aa5efc..77bece5b7f 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -38,7 +38,7 @@ type Config struct { } // DevConfig is a Config that is used for dev mode of Vault. -func DevConfig(ha bool) *Config { +func DevConfig(ha, transactional bool) *Config { ret := &Config{ DisableCache: false, DisableMlock: true, @@ -63,7 +63,12 @@ func DevConfig(ha bool) *Config { DefaultLeaseTTL: 32 * 24 * time.Hour, } - if ha { + switch { + case ha && transactional: + ret.Backend.Type = "inmem_transactional_ha" + case !ha && transactional: + ret.Backend.Type = "inmem_transactional" + case ha && !transactional: ret.Backend.Type = "inmem_ha" } diff --git a/command/server_ha_test.go b/command/server_ha_test.go index 30280c5571..26dc00878f 100644 --- a/command/server_ha_test.go +++ b/command/server_ha_test.go @@ -33,7 +33,7 @@ func TestServer_CommonHA(t *testing.T) { args := []string{"-config", tmpfile.Name(), "-verify-only", "true"} if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + t.Fatalf("bad: %d\n\n%s\n\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) } if !strings.Contains(ui.OutputWriter.String(), "(HA available)") { @@ -61,7 +61,7 @@ func TestServer_GoodSeparateHA(t *testing.T) { args := []string{"-config", tmpfile.Name(), "-verify-only", "true"} if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + t.Fatalf("bad: %d\n\n%s\n\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) } if !strings.Contains(ui.OutputWriter.String(), "HA Backend:") { diff --git a/helper/keysutil/lock_manager.go b/helper/keysutil/lock_manager.go index 1549c27401..380c39af11 100644 --- a/helper/keysutil/lock_manager.go +++ b/helper/keysutil/lock_manager.go @@ -71,6 +71,15 @@ func (lm *LockManager) CacheActive() bool { return lm.cache != nil } +func (lm *LockManager) InvalidatePolicy(name string) { + // Check if it's in our cache. If so, return right away. + if lm.CacheActive() { + lm.cacheMutex.Lock() + defer lm.cacheMutex.Unlock() + delete(lm.cache, name) + } +} + func (lm *LockManager) policyLock(name string, lockType bool) *sync.RWMutex { lm.locksMutex.RLock() lock := lm.locks[name] diff --git a/vault/logical_cubbyhole.go b/vault/logical_cubbyhole.go index 76353b0bed..697655538f 100644 --- a/vault/logical_cubbyhole.go +++ b/vault/logical_cubbyhole.go @@ -16,6 +16,12 @@ func CubbyholeBackendFactory(conf *logical.BackendConfig) (logical.Backend, erro b.Backend = &framework.Backend{ Help: strings.TrimSpace(cubbyholeHelp), + PathsSpecial: &logical.Paths{ + LocalStorage: []string{ + "*", + }, + }, + Paths: []*framework.Path{ &framework.Path{ Pattern: ".*", diff --git a/vault/logical_cubbyhole_test.go b/vault/logical_cubbyhole_test.go index fcf3ec3a0c..79531d6e39 100644 --- a/vault/logical_cubbyhole_test.go +++ b/vault/logical_cubbyhole_test.go @@ -12,9 +12,13 @@ import ( func TestCubbyholeBackend_RootPaths(t *testing.T) { b := testCubbyholeBackend() - root := b.SpecialPaths() - if root != nil { - t.Fatalf("unexpected: %v", root) + expected := []string{ + "*", + } + + actual := b.SpecialPaths().LocalStorage + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) } } diff --git a/vault/logical_system.go b/vault/logical_system.go index 7438003218..e0f1163214 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/duration" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" @@ -39,12 +40,14 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen "audit", "audit/*", "raw/*", + "replication/primary/secondary-token", "rotate", "config/auditing/*", }, Unauthenticated: []string{ "wrapping/pubkey", + "replication/status", }, }, @@ -226,6 +229,11 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen Type: framework.TypeMap, Description: strings.TrimSpace(sysHelp["mount_config"][0]), }, + "local": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: false, + Description: strings.TrimSpace(sysHelp["mount_local"][0]), + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -377,6 +385,11 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen Type: framework.TypeString, Description: strings.TrimSpace(sysHelp["auth_desc"][0]), }, + "local": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: false, + Description: strings.TrimSpace(sysHelp["mount_local"][0]), + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -495,6 +508,11 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen Type: framework.TypeMap, Description: strings.TrimSpace(sysHelp["audit_opts"][0]), }, + "local": &framework.FieldSchema{ + Type: framework.TypeBool, + Default: false, + Description: strings.TrimSpace(sysHelp["mount_local"][0]), + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -657,6 +675,10 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen }, } + b.Backend.Paths = append(b.Backend.Paths, b.replicationPaths()...) + + b.Backend.Invalidate = b.invalidate + return b.Backend.Setup(config) } @@ -668,6 +690,20 @@ type SystemBackend struct { Backend *framework.Backend } +func (b *SystemBackend) invalidate(key string) { + if b.Core.logger.IsTrace() { + b.Core.logger.Trace("sys: invaliding key", "key", key) + } + switch { + case strings.HasPrefix(key, policySubPath): + b.Core.stateLock.RLock() + defer b.Core.stateLock.RUnlock() + if b.Core.policyStore != nil { + b.Core.policyStore.invalidate(strings.TrimPrefix(key, policySubPath)) + } + } +} + // handleAuditedHeaderUpdate creates or overwrites a header entry func (b *SystemBackend) handleAuditedHeaderUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { header := d.Get("header").(string) @@ -869,6 +905,7 @@ func (b *SystemBackend) handleMountTable( "default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()), "max_lease_ttl": int64(entry.Config.MaxLeaseTTL.Seconds()), }, + "local": entry.Local, } resp.Data[entry.Path] = info @@ -880,6 +917,15 @@ func (b *SystemBackend) handleMountTable( // handleMount is used to mount a new path func (b *SystemBackend) handleMount( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + + local := data.Get("local").(bool) + if !local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot add a non-local mount to a replication secondary"), nil + } + // Get all the options path := data.Get("path").(string) logicalType := data.Get("type").(string) @@ -954,6 +1000,7 @@ func (b *SystemBackend) handleMount( Type: logicalType, Description: description, Config: config, + Local: local, } // Attempt mount @@ -979,6 +1026,10 @@ func handleError( // handleUnmount is used to unmount a path func (b *SystemBackend) handleUnmount( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + suffix := strings.TrimPrefix(req.Path, "mounts/") if len(suffix) == 0 { return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest @@ -986,6 +1037,11 @@ func (b *SystemBackend) handleUnmount( suffix = sanitizeMountPath(suffix) + entry := b.Core.router.MatchingMountEntry(suffix) + if entry != nil && !entry.Local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot unmount a non-local mount on a replication secondary"), nil + } + // Attempt unmount if existed, err := b.Core.unmount(suffix); existed && err != nil { b.Backend.Logger().Error("sys: unmount failed", "path", suffix, "error", err) @@ -998,6 +1054,10 @@ func (b *SystemBackend) handleUnmount( // handleRemount is used to remount a path func (b *SystemBackend) handleRemount( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + // Get the paths fromPath := data.Get("from").(string) toPath := data.Get("to").(string) @@ -1010,6 +1070,11 @@ func (b *SystemBackend) handleRemount( fromPath = sanitizeMountPath(fromPath) toPath = sanitizeMountPath(toPath) + entry := b.Core.router.MatchingMountEntry(fromPath) + if entry != nil && !entry.Local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot remount a non-local mount on a replication secondary"), nil + } + // Attempt remount if err := b.Core.remount(fromPath, toPath); err != nil { b.Backend.Logger().Error("sys: remount failed", "from_path", fromPath, "to_path", toPath, "error", err) @@ -1095,6 +1160,10 @@ func (b *SystemBackend) handleMountTuneWrite( // handleTuneWriteCommon is used to set config settings on a path func (b *SystemBackend) handleTuneWriteCommon( path string, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + path = sanitizeMountPath(path) // Prevent protected paths from being changed @@ -1110,6 +1179,9 @@ func (b *SystemBackend) handleTuneWriteCommon( b.Backend.Logger().Error("sys: tune failed: no mount entry found", "path", path) return handleError(fmt.Errorf("sys: tune of path '%s' failed: no mount entry found", path)) } + if mountEntry != nil && !mountEntry.Local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot tune a non-local mount on a replication secondary"), nil + } var lock *sync.RWMutex switch { @@ -1249,6 +1321,7 @@ func (b *SystemBackend) handleAuthTable( "default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()), "max_lease_ttl": int64(entry.Config.MaxLeaseTTL.Seconds()), }, + "local": entry.Local, } resp.Data[entry.Path] = info } @@ -1258,6 +1331,15 @@ func (b *SystemBackend) handleAuthTable( // handleEnableAuth is used to enable a new credential backend func (b *SystemBackend) handleEnableAuth( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + + local := data.Get("local").(bool) + if !local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot add a non-local mount to a replication secondary"), nil + } + // Get all the options path := data.Get("path").(string) logicalType := data.Get("type").(string) @@ -1277,6 +1359,7 @@ func (b *SystemBackend) handleEnableAuth( Path: path, Type: logicalType, Description: description, + Local: local, } // Attempt enabling @@ -1391,6 +1474,7 @@ func (b *SystemBackend) handleAuditTable( "type": entry.Type, "description": entry.Description, "options": entry.Options, + "local": entry.Local, } resp.Data[entry.Path] = info } @@ -1424,6 +1508,15 @@ func (b *SystemBackend) handleAuditHash( // handleEnableAudit is used to enable a new audit backend func (b *SystemBackend) handleEnableAudit( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + + local := data.Get("local").(bool) + if !local && repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot add a non-local mount to a replication secondary"), nil + } + // Get all the options path := data.Get("path").(string) backendType := data.Get("type").(string) @@ -1447,6 +1540,7 @@ func (b *SystemBackend) handleEnableAudit( Type: backendType, Description: description, Options: optionMap, + Local: local, } // Attempt enabling @@ -1562,6 +1656,13 @@ func (b *SystemBackend) handleKeyStatus( // handleRotate is used to trigger a key rotation func (b *SystemBackend) handleRotate( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.Core.clusterParamsLock.RLock() + repState := b.Core.replicationState + b.Core.clusterParamsLock.RUnlock() + if repState == consts.ReplicationSecondary { + return logical.ErrorResponse("cannot rotate on a replication secondary"), nil + } + // Rotate to the new term newTerm, err := b.Core.barrier.Rotate() if err != nil { @@ -1584,6 +1685,17 @@ func (b *SystemBackend) handleRotate( } }) } + + // Write to the canary path, which will force a synchronous truing during + // replication + if err := b.Core.barrier.Put(&Entry{ + Key: coreKeyringCanaryPath, + Value: []byte(fmt.Sprintf("new-rotation-term-%d", newTerm)), + }); err != nil { + b.Core.logger.Error("core: error saving keyring canary", "error", err) + return nil, fmt.Errorf("failed to save keyring canary: %v", err) + } + return nil, nil } @@ -1950,6 +2062,11 @@ west coast. and max_lease_ttl.`, }, + "mount_local": { + `Mark the mount as a local mount, which is not replicated +and is unaffected by replication.`, + }, + "tune_default_lease_ttl": { `The default lease TTL for this mount.`, }, diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 62737744b2..71cf59c2a8 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -21,6 +21,7 @@ func TestSystemBackend_RootPaths(t *testing.T) { "audit", "audit/*", "raw/*", + "replication/*", "rotate", "config/auditing/*", } @@ -50,6 +51,7 @@ func TestSystemBackend_mounts(t *testing.T) { "default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64), "max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), }, + "local": false, }, "sys/": map[string]interface{}{ "type": "system", @@ -58,6 +60,7 @@ func TestSystemBackend_mounts(t *testing.T) { "default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64), "max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), }, + "local": false, }, "cubbyhole/": map[string]interface{}{ "description": "per-token private secret storage", @@ -66,6 +69,7 @@ func TestSystemBackend_mounts(t *testing.T) { "default_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64), "max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), }, + "local": false, }, } if !reflect.DeepEqual(resp.Data, exp) { @@ -580,6 +584,7 @@ func TestSystemBackend_authTable(t *testing.T) { "default_lease_ttl": int64(0), "max_lease_ttl": int64(0), }, + "local": false, }, } if !reflect.DeepEqual(resp.Data, exp) { @@ -843,6 +848,7 @@ func TestSystemBackend_auditTable(t *testing.T) { req.Data["options"] = map[string]interface{}{ "foo": "bar", } + req.Data["local"] = true b.HandleRequest(req) req = logical.TestRequest(t, logical.ReadOperation, "audit") @@ -859,6 +865,7 @@ func TestSystemBackend_auditTable(t *testing.T) { "options": map[string]string{ "foo": "bar", }, + "local": true, }, } if !reflect.DeepEqual(resp.Data, exp) { diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index ab40d4f6e1..018945341b 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -11,7 +11,7 @@