From c144f95be0a2f05a1c26a58c158580b08225ed00 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 23 Oct 2017 16:42:56 -0400 Subject: [PATCH] Sync over --- helper/consts/replication.go | 22 +++++ helper/identity/sentinel.go | 83 +++++++++++++++++++ helper/mfa/duo/path_duo_config.go | 2 +- helper/storagepacker/types.pb.go | 8 +- logical/connection.go | 2 +- logical/framework/field_type.go | 2 + logical/lease.go | 4 +- logical/logical.go | 5 ++ logical/plugin/mock/path_internal.go | 2 - logical/request.go | 92 ++++++++++++++++----- logical/secret.go | 4 +- logical/storage.go | 9 ++- logical/translate_response.go | 6 +- meta/meta.go | 39 +++++++-- meta/meta_test.go | 2 +- physical/cache.go | 2 +- physical/cockroachdb/cockroachdb.go | 4 +- physical/consul/consul.go | 4 +- physical/file/file.go | 2 +- physical/inmem/inmem.go | 2 +- physical/inmem/transactions_test.go | 2 +- physical/latency.go | 2 +- physical/physical.go | 1 + physical/physical_access.go | 36 +++++++++ physical/testing.go | 19 +++-- physical/transactions.go | 33 ++++---- vault/acl.go | 116 ++++++++++++++++++++------- vault/acl_test.go | 64 +++++++-------- vault/auth.go | 56 +++++++++---- vault/auth_test.go | 2 +- vault/barrier.go | 10 ++- vault/barrier_access.go | 22 +++++ vault/barrier_aes_gcm.go | 15 ++-- vault/barrier_view.go | 10 ++- vault/capabilities.go | 2 +- vault/capabilities_test.go | 2 +- 36 files changed, 521 insertions(+), 167 deletions(-) create mode 100644 helper/identity/sentinel.go create mode 100644 physical/physical_access.go create mode 100644 vault/barrier_access.go diff --git a/helper/consts/replication.go b/helper/consts/replication.go index 7fbeb883a..3df5da1be 100644 --- a/helper/consts/replication.go +++ b/helper/consts/replication.go @@ -33,6 +33,28 @@ func (r ReplicationState) String() string { return "disabled" } +func (r ReplicationState) GetDRString() string { + switch { + case r.HasState(ReplicationDRPrimary): + return ReplicationDRPrimary.String() + case r.HasState(ReplicationDRSecondary): + return ReplicationDRSecondary.String() + default: + return ReplicationDisabled.String() + } +} + +func (r ReplicationState) GetPerformanceString() string { + switch { + case r.HasState(ReplicationPerformancePrimary): + return ReplicationPerformancePrimary.String() + case r.HasState(ReplicationPerformanceSecondary): + return ReplicationPerformanceSecondary.String() + default: + return ReplicationDisabled.String() + } +} + func (r ReplicationState) HasState(flag ReplicationState) bool { return r&flag != 0 } func (r *ReplicationState) AddState(flag ReplicationState) { *r |= flag } func (r *ReplicationState) ClearState(flag ReplicationState) { *r &= ^flag } diff --git a/helper/identity/sentinel.go b/helper/identity/sentinel.go new file mode 100644 index 000000000..5ed4587ee --- /dev/null +++ b/helper/identity/sentinel.go @@ -0,0 +1,83 @@ +package identity + +import "github.com/golang/protobuf/ptypes" + +func (e *Entity) SentinelGet(key string) (interface{}, error) { + if e == nil { + return nil, nil + } + switch key { + case "aliases": + return e.Aliases, nil + case "id": + return e.ID, nil + case "meta", "metadata": + return e.Metadata, nil + case "name": + return e.Name, nil + case "creation_time": + return ptypes.TimestampString(e.CreationTime), nil + case "last_update_time": + return ptypes.TimestampString(e.LastUpdateTime), nil + case "merged_entity_ids": + return e.MergedEntityIDs, nil + case "policies": + return e.Policies, nil + } + + return nil, nil +} + +func (p *Alias) SentinelGet(key string) (interface{}, error) { + if p == nil { + return nil, nil + } + switch key { + case "id": + return p.ID, nil + case "mount_type": + return p.MountType, nil + case "mount_accessor": + return p.MountAccessor, nil + case "mount_path": + return p.MountPath, nil + case "meta", "metadata": + return p.Metadata, nil + case "name": + return p.Name, nil + case "creation_time": + return ptypes.TimestampString(p.CreationTime), nil + case "last_update_time": + return ptypes.TimestampString(p.LastUpdateTime), nil + case "merged_from_entity_ids": + return p.MergedFromEntityIDs, nil + } + + return nil, nil +} + +func (g *Group) SentinelGet(key string) (interface{}, error) { + if g == nil { + return nil, nil + } + switch key { + case "id": + return g.ID, nil + case "name": + return g.Name, nil + case "policies": + return g.Policies, nil + case "parent_group_ids": + return g.ParentGroupIDs, nil + case "member_entity_ids": + return g.MemberEntityIDs, nil + case "meta", "metadata": + return g.Metadata, nil + case "creation_time": + return ptypes.TimestampString(g.CreationTime), nil + case "last_update_time": + return ptypes.TimestampString(g.LastUpdateTime), nil + } + + return nil, nil +} diff --git a/helper/mfa/duo/path_duo_config.go b/helper/mfa/duo/path_duo_config.go index 88c564783..3fb5ee565 100644 --- a/helper/mfa/duo/path_duo_config.go +++ b/helper/mfa/duo/path_duo_config.go @@ -22,7 +22,7 @@ func pathDuoConfig() *framework.Path { }, "push_info": &framework.FieldSchema{ Type: framework.TypeString, - Description: "A string of URL-encoded key/value pairs that provides additional context about the authentication attemmpt in the Duo Mobile app", + Description: "A string of URL-encoded key/value pairs that provides additional context about the authentication attempt in the Duo Mobile app", }, }, diff --git a/helper/storagepacker/types.pb.go b/helper/storagepacker/types.pb.go index 251989025..80a15f756 100644 --- a/helper/storagepacker/types.pb.go +++ b/helper/storagepacker/types.pb.go @@ -30,8 +30,8 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Item struct { - ID string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Message *google_protobuf.Any `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + ID string `sentinel:"" protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` + Message *google_protobuf.Any `sentinel:"" protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` } func (m *Item) Reset() { *m = Item{} } @@ -54,8 +54,8 @@ func (m *Item) GetMessage() *google_protobuf.Any { } type Bucket struct { - Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` - Items []*Item `protobuf:"bytes,2,rep,name=items" json:"items,omitempty"` + Key string `sentinel:"" protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Items []*Item `sentinel:"" protobuf:"bytes,2,rep,name=items" json:"items,omitempty"` } func (m *Bucket) Reset() { *m = Bucket{} } diff --git a/logical/connection.go b/logical/connection.go index d54a0f532..a504b10c3 100644 --- a/logical/connection.go +++ b/logical/connection.go @@ -11,5 +11,5 @@ type Connection struct { RemoteAddr string `json:"remote_addr"` // ConnState is the TLS connection state if applicable. - ConnState *tls.ConnectionState + ConnState *tls.ConnectionState `sentinel:""` } diff --git a/logical/framework/field_type.go b/logical/framework/field_type.go index 304d45ff0..78e499c3e 100644 --- a/logical/framework/field_type.go +++ b/logical/framework/field_type.go @@ -16,9 +16,11 @@ const ( // TypeSlice represents a slice of any type TypeSlice + // TypeStringSlice is a helper for TypeSlice that returns a sanitized // slice of strings TypeStringSlice + // TypeCommaStringSlice is a helper for TypeSlice that returns a sanitized // slice of strings and also supports parsing a comma-separated list in // a string field diff --git a/logical/lease.go b/logical/lease.go index ed0b26b51..9661796ca 100644 --- a/logical/lease.go +++ b/logical/lease.go @@ -1,6 +1,8 @@ package logical -import "time" +import ( + "time" +) // LeaseOptions is an embeddable struct to capture common lease // settings between a Secret and Auth diff --git a/logical/logical.go b/logical/logical.go index 9ce0d85e7..7ccc92031 100644 --- a/logical/logical.go +++ b/logical/logical.go @@ -117,4 +117,9 @@ type Paths struct { // LocalStorage are paths (prefixes) that are local to this instance; this // indicates that these paths should not be replicated LocalStorage []string + + // SealWrapStorage are storage paths that, when using a capable seal, + // should be seal wrapped with extra encryption. It is exact matching + // unless it ends with '/' in which case it will be treated as a prefix. + SealWrapStorage []string } diff --git a/logical/plugin/mock/path_internal.go b/logical/plugin/mock/path_internal.go index 92c4f8bfa..6d0b6e0b3 100644 --- a/logical/plugin/mock/path_internal.go +++ b/logical/plugin/mock/path_internal.go @@ -26,7 +26,6 @@ func (b *backend) pathInternalUpdate( b.internal = value // Return the secret return nil, nil - } func (b *backend) pathInternalRead( @@ -37,5 +36,4 @@ func (b *backend) pathInternalRead( "value": b.internal, }, }, nil - } diff --git a/logical/request.go b/logical/request.go index a1afeba2b..3aa041d82 100644 --- a/logical/request.go +++ b/logical/request.go @@ -3,6 +3,7 @@ package logical import ( "errors" "fmt" + "strings" "time" ) @@ -11,23 +12,41 @@ import ( type RequestWrapInfo struct { // Setting to non-zero specifies that the response should be wrapped. // Specifies the desired TTL of the wrapping token. - TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"` + TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl" sentinel:""` // The format to use for the wrapped response; if not specified it's a bare // token - Format string `json:"format" structs:"format" mapstructure:"format"` + Format string `json:"format" structs:"format" mapstructure:"format" sentinel:""` } -// Request is a struct that stores the parameters and context -// of a request being made to Vault. It is used to abstract -// the details of the higher level request protocol from the handlers. +func (r *RequestWrapInfo) SentinelGet(key string) (interface{}, error) { + if r == nil { + return nil, nil + } + switch key { + case "ttl": + return r.TTL, nil + case "ttl_seconds": + return int64(r.TTL.Seconds()), nil + } + + return nil, nil +} + +// Request is a struct that stores the parameters and context of a request +// being made to Vault. It is used to abstract the details of the higher level +// request protocol from the handlers. +// +// Note: Many of these have Sentinel disabled because they are values populated +// by the router after policy checks; the token namespace would be the right +// place to access them via Sentinel type Request struct { // Id is the uuid associated with each request - ID string `json:"id" structs:"id" mapstructure:"id"` + ID string `json:"id" structs:"id" mapstructure:"id" sentinel:""` // If set, the name given to the replication secondary where this request // originated - ReplicationCluster string `json:"replication_cluster" structs:"replication_cluster" mapstructure:"replication_cluster"` + ReplicationCluster string `json:"replication_cluster" structs:"replication_cluster" mapstructure:"replication_cluster" sentinel:""` // Operation is the requested operation type Operation Operation `json:"operation" structs:"operation" mapstructure:"operation"` @@ -36,26 +55,26 @@ type Request struct { // routing. As an example, if the original request path is "prod/aws/foo" // and the AWS logical backend is mounted at "prod/aws/", then the // final path is "foo" since the mount prefix is trimmed. - Path string `json:"path" structs:"path" mapstructure:"path"` + Path string `json:"path" structs:"path" mapstructure:"path" sentinel:""` // Request data is an opaque map that must have string keys. Data map[string]interface{} `json:"map" structs:"data" mapstructure:"data"` // Storage can be used to durably store and retrieve state. - Storage Storage `json:"-"` + Storage Storage `json:"-" sentinel:""` // Secret will be non-nil only for Revoke and Renew operations // to represent the secret that was returned prior. - Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret"` + Secret *Secret `json:"secret" structs:"secret" mapstructure:"secret" sentinel:""` // Auth will be non-nil only for Renew operations // to represent the auth that was returned prior. - Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth"` + Auth *Auth `json:"auth" structs:"auth" mapstructure:"auth" sentinel:""` // Headers will contain the http headers from the request. This value will // be used in the audit broker to ensure we are auditing only the allowed // headers. - Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers"` + Headers map[string][]string `json:"headers" structs:"headers" mapstructure:"headers" sentinel:""` // Connection will be non-nil only for credential providers to // inspect the connection information and potentially use it for @@ -66,45 +85,58 @@ type Request struct { // can be verified and ACLs applied. This value is passed // through to the logical backends but after being salted and // hashed. - ClientToken string `json:"client_token" structs:"client_token" mapstructure:"client_token"` + ClientToken string `json:"client_token" structs:"client_token" mapstructure:"client_token" sentinel:""` // ClientTokenAccessor is provided to the core so that the it can get // logged as part of request audit logging. - ClientTokenAccessor string `json:"client_token_accessor" structs:"client_token_accessor" mapstructure:"client_token_accessor"` + ClientTokenAccessor string `json:"client_token_accessor" structs:"client_token_accessor" mapstructure:"client_token_accessor" sentinel:""` // DisplayName is provided to the logical backend to help associate // dynamic secrets with the source entity. This is not a sensitive // name, but is useful for operators. - DisplayName string `json:"display_name" structs:"display_name" mapstructure:"display_name"` + DisplayName string `json:"display_name" structs:"display_name" mapstructure:"display_name" sentinel:""` // MountPoint is provided so that a logical backend can generate // paths relative to itself. The `Path` is effectively the client // request path with the MountPoint trimmed off. - MountPoint string `json:"mount_point" structs:"mount_point" mapstructure:"mount_point"` + MountPoint string `json:"mount_point" structs:"mount_point" mapstructure:"mount_point" sentinel:""` // MountType is provided so that a logical backend can make decisions // based on the specific mount type (e.g., if a mount type has different // aliases, generating different defaults depending on the alias) - MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type"` + MountType string `json:"mount_type" structs:"mount_type" mapstructure:"mount_type" sentinel:""` // MountAccessor is provided so that identities returned by the authentication // backends can be tied to the mount it belongs to. - MountAccessor string `json:"mount_accessor" structs:"mount_accessor" mapstructure:"mount_accessor"` + MountAccessor string `json:"mount_accessor" structs:"mount_accessor" mapstructure:"mount_accessor" sentinel:""` // WrapInfo contains requested response wrapping parameters - WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` + WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info" sentinel:""` // ClientTokenRemainingUses represents the allowed number of uses left on the // token supplied ClientTokenRemainingUses int `json:"client_token_remaining_uses" structs:"client_token_remaining_uses" mapstructure:"client_token_remaining_uses"` + // MFACreds holds the parsed MFA information supplied over the API as part of + // X-Vault-MFA header + MFACreds MFACreds `json:"mfa_creds" structs:"mfa_creds" mapstructure:"mfa_creds" sentinel:""` + // EntityID is the identity of the caller extracted out of the token used // to make this request - EntityID string `json:"entity_id" structs:"entity_id" mapstructure:"entity_id"` + EntityID string `json:"entity_id" structs:"entity_id" mapstructure:"entity_id" sentinel:""` + + // PolicyOverride indicates that the requestor wishes to override + // soft-mandatory Sentinel policies + PolicyOverride bool `json:"policy_override" structs:"policy_override" mapstructure:"policy_override"` + + // Whether the request is unauthenticated, as in, had no client token + // attached. Useful in some situations where the client token is not made + // accessible. + Unauthenticated bool `json:"unauthenticated" structs:"unauthenticated" mapstructure:"unauthenticated"` // For replication, contains the last WAL on the remote side after handling // the request, used for best-effort avoidance of stale read-after-write - lastRemoteWAL uint64 + lastRemoteWAL uint64 `sentinel:""` } // Get returns a data field and guards for nil Data @@ -126,6 +158,24 @@ func (r *Request) GoString() string { return fmt.Sprintf("*%#v", *r) } +func (r *Request) SentinelGet(key string) (interface{}, error) { + switch key { + case "path": + // Sanitize it here so that it's consistent in policies + return strings.TrimPrefix(r.Path, "/"), nil + + case "wrapping", "wrap_info": + // If the pointer is nil accessing the wrap info is considered + // "undefined" so this allows us to instead discover a TTL of zero + if r.WrapInfo == nil { + return &RequestWrapInfo{}, nil + } + return r.WrapInfo, nil + } + + return nil, nil +} + func (r *Request) LastRemoteWAL() uint64 { return r.lastRemoteWAL } diff --git a/logical/secret.go b/logical/secret.go index 27ad8d9a2..a2128d868 100644 --- a/logical/secret.go +++ b/logical/secret.go @@ -9,12 +9,12 @@ type Secret struct { // InternalData is JSON-encodable data that is stored with the secret. // This will be sent back during a Renew/Revoke for storing internal data // used for those operations. - InternalData map[string]interface{} `json:"internal_data"` + InternalData map[string]interface{} `json:"internal_data" sentinel:""` // LeaseID is the ID returned to the user to manage this secret. // This is generated by Vault core. Any set value will be ignored. // For requests, this will always be blank. - LeaseID string + LeaseID string `sentinel:""` } func (s *Secret) Validate() error { diff --git a/logical/storage.go b/logical/storage.go index 51487ded9..18d97b2c6 100644 --- a/logical/storage.go +++ b/logical/storage.go @@ -23,8 +23,9 @@ type Storage interface { // StorageEntry is the entry for an item in a Storage implementation. type StorageEntry struct { - Key string - Value []byte + Key string + Value []byte + SealWrap bool } // DecodeJSON decodes the 'Value' present in StorageEntry. @@ -94,6 +95,10 @@ func CollectKeys(view ClearableView) ([]string, error) { // ClearView is used to delete all the keys in a view func ClearView(view ClearableView) error { + if view == nil { + return nil + } + // Collect all the keys keys, err := CollectKeys(view) if err != nil { diff --git a/logical/translate_response.go b/logical/translate_response.go index bf2dae9d1..8d2b38623 100644 --- a/logical/translate_response.go +++ b/logical/translate_response.go @@ -8,9 +8,9 @@ import ( ) // This logic was pulled from the http package so that it can be used for -// encoding wrapped responses as well. It simply translates the logical request -// to an http response, with the values we want and omitting the values we -// don't. +// encoding wrapped responses as well. It simply translates the logical +// response to an http response, with the values we want and omitting the +// values we don't. func LogicalResponseToHTTPResponse(input *Response) *HTTPResponse { httpResp := &HTTPResponse{ Data: input.Data, diff --git a/meta/meta.go b/meta/meta.go index a81cbde8a..d75f1054c 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -4,10 +4,12 @@ import ( "bufio" "flag" "io" + "os" "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/command/token" + "github.com/hashicorp/vault/helper/flag-slice" "github.com/mitchellh/cli" ) @@ -34,6 +36,9 @@ var ( "s", "m", or "h"; if no suffix is specified it will be parsed as seconds. May also be specified via VAULT_WRAP_TTL. + + -policy-override Indicates that any soft-mandatory Sentinel policies + be overridden. ` } ) @@ -48,13 +53,15 @@ type Meta struct { ForceAddress string // Address to force for API clients // These are set by the command line flags. - flagAddress string - flagCACert string - flagCAPath string - flagClientCert string - flagClientKey string - flagWrapTTL string - flagInsecure bool + flagAddress string + flagCACert string + flagCAPath string + flagClientCert string + flagClientKey string + flagWrapTTL string + flagInsecure bool + flagMFA []string + flagPolicyOverride bool // Queried if no token can be found TokenHelper TokenHelperFunc @@ -105,6 +112,22 @@ func (m *Meta) Client() (*api.Client, error) { client.SetWrappingLookupFunc(m.DefaultWrappingLookupFunc) + var mfaCreds []string + + // Extract the MFA credentials from environment variable first + if os.Getenv(api.EnvVaultMFA) != "" { + mfaCreds = []string{os.Getenv(api.EnvVaultMFA)} + } + + // If CLI MFA flags were supplied, prefer that over environment variable + if len(m.flagMFA) != 0 { + mfaCreds = m.flagMFA + } + + client.SetMFACreds(mfaCreds) + + client.SetPolicyOverride(m.flagPolicyOverride) + // If we have a token directly, then set that token := m.ClientToken @@ -154,6 +177,8 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { f.StringVar(&m.flagWrapTTL, "wrap-ttl", "", "") f.BoolVar(&m.flagInsecure, "insecure", false, "") f.BoolVar(&m.flagInsecure, "tls-skip-verify", false, "") + f.BoolVar(&m.flagPolicyOverride, "policy-override", false, "") + f.Var((*sliceflag.StringFlag)(&m.flagMFA), "mfa", "") } // Create an io.Writer that writes to our Ui properly for errors. diff --git a/meta/meta_test.go b/meta/meta_test.go index 1ef9c139f..99a294d24 100644 --- a/meta/meta_test.go +++ b/meta/meta_test.go @@ -18,7 +18,7 @@ func TestFlagSet(t *testing.T) { }, { FlagSetServer, - []string{"address", "ca-cert", "ca-path", "client-cert", "client-key", "insecure", "tls-skip-verify", "wrap-ttl"}, + []string{"address", "ca-cert", "ca-path", "client-cert", "client-key", "insecure", "mfa", "policy-override", "tls-skip-verify", "wrap-ttl"}, }, } diff --git a/physical/cache.go b/physical/cache.go index fc44d091f..6a109a32b 100644 --- a/physical/cache.go +++ b/physical/cache.go @@ -136,7 +136,7 @@ func (c *Cache) List(prefix string) ([]string, error) { return c.backend.List(prefix) } -func (c *TransactionalCache) Transaction(txns []TxnEntry) error { +func (c *TransactionalCache) Transaction(txns []*TxnEntry) error { // Lock the world for _, lock := range c.locks { lock.Lock() diff --git a/physical/cockroachdb/cockroachdb.go b/physical/cockroachdb/cockroachdb.go index 395c2da2b..baa998f42 100644 --- a/physical/cockroachdb/cockroachdb.go +++ b/physical/cockroachdb/cockroachdb.go @@ -196,7 +196,7 @@ func (c *CockroachDBBackend) List(prefix string) ([]string, error) { } // Transaction is used to run multiple entries via a transaction -func (c *CockroachDBBackend) Transaction(txns []physical.TxnEntry) error { +func (c *CockroachDBBackend) Transaction(txns []*physical.TxnEntry) error { defer metrics.MeasureSince([]string{"cockroachdb", "transaction"}, time.Now()) if len(txns) == 0 { return nil @@ -210,7 +210,7 @@ func (c *CockroachDBBackend) Transaction(txns []physical.TxnEntry) error { }) } -func (c *CockroachDBBackend) transaction(tx *sql.Tx, txns []physical.TxnEntry) error { +func (c *CockroachDBBackend) transaction(tx *sql.Tx, txns []*physical.TxnEntry) error { deleteStmt, err := tx.Prepare(c.rawStatements["delete"]) if err != nil { return err diff --git a/physical/consul/consul.go b/physical/consul/consul.go index 6c3146683..6027a3a27 100644 --- a/physical/consul/consul.go +++ b/physical/consul/consul.go @@ -303,7 +303,7 @@ func setupTLSConfig(conf map[string]string) (*tls.Config, error) { } // Used to run multiple entries via a transaction -func (c *ConsulBackend) Transaction(txns []physical.TxnEntry) error { +func (c *ConsulBackend) Transaction(txns []*physical.TxnEntry) error { if len(txns) == 0 { return nil } @@ -334,7 +334,7 @@ func (c *ConsulBackend) Transaction(txns []physical.TxnEntry) error { if err != nil { return err } - if ok { + if ok && len(resp.Errors) == 0 { return nil } diff --git a/physical/file/file.go b/physical/file/file.go index aa4958972..b02efc78c 100644 --- a/physical/file/file.go +++ b/physical/file/file.go @@ -273,7 +273,7 @@ func (b *FileBackend) validatePath(path string) error { return nil } -func (b *TransactionalFileBackend) Transaction(txns []physical.TxnEntry) error { +func (b *TransactionalFileBackend) Transaction(txns []*physical.TxnEntry) error { b.permitPool.Acquire() defer b.permitPool.Release() diff --git a/physical/inmem/inmem.go b/physical/inmem/inmem.go index d4f92019c..7bbf1801a 100644 --- a/physical/inmem/inmem.go +++ b/physical/inmem/inmem.go @@ -132,7 +132,7 @@ func (i *InmemBackend) ListInternal(prefix string) ([]string, error) { } // Implements the transaction interface -func (t *TransactionalInmemBackend) Transaction(txns []physical.TxnEntry) error { +func (t *TransactionalInmemBackend) Transaction(txns []*physical.TxnEntry) error { t.permitPool.Acquire() defer t.permitPool.Release() diff --git a/physical/inmem/transactions_test.go b/physical/inmem/transactions_test.go index 5565fbe35..3b957525d 100644 --- a/physical/inmem/transactions_test.go +++ b/physical/inmem/transactions_test.go @@ -54,7 +54,7 @@ func (f *faultyPseudo) List(prefix string) ([]string, error) { return f.underlying.List(prefix) } -func (f *faultyPseudo) Transaction(txns []physical.TxnEntry) error { +func (f *faultyPseudo) Transaction(txns []*physical.TxnEntry) error { f.underlying.permitPool.Acquire() defer f.underlying.permitPool.Release() diff --git a/physical/latency.go b/physical/latency.go index 3253036da..050875131 100644 --- a/physical/latency.go +++ b/physical/latency.go @@ -84,7 +84,7 @@ func (l *LatencyInjector) List(prefix string) ([]string, error) { } // Transaction is a latent transaction request -func (l *TransactionalLatencyInjector) Transaction(txns []TxnEntry) error { +func (l *TransactionalLatencyInjector) Transaction(txns []*TxnEntry) error { l.addLatency() return l.Transactional.Transaction(txns) } diff --git a/physical/physical.go b/physical/physical.go index 088a86b99..af7ea13dc 100644 --- a/physical/physical.go +++ b/physical/physical.go @@ -110,6 +110,7 @@ type Lock interface { type Entry struct { Key string Value []byte + SealWrap bool `json:"seal_wrap,omitempty"` } // Factory is the factory function to create a physical backend. diff --git a/physical/physical_access.go b/physical/physical_access.go new file mode 100644 index 000000000..1a553a568 --- /dev/null +++ b/physical/physical_access.go @@ -0,0 +1,36 @@ +package physical + +// PhysicalAccess is a wrapper around physical.Backend that allows Core to +// expose its physical storage operations through PhysicalAccess() while +// restricting the ability to modify Core.physical itself. +type PhysicalAccess struct { + physical Backend +} + +var _ Backend = (*PhysicalAccess)(nil) + +func NewPhysicalAccess(physical Backend) *PhysicalAccess { + return &PhysicalAccess{physical: physical} +} + +func (p *PhysicalAccess) Put(entry *Entry) error { + return p.physical.Put(entry) +} + +func (p *PhysicalAccess) Get(key string) (*Entry, error) { + return p.physical.Get(key) +} + +func (p *PhysicalAccess) Delete(key string) error { + return p.physical.Delete(key) +} + +func (p *PhysicalAccess) List(prefix string) ([]string, error) { + return p.physical.List(prefix) +} + +func (p *PhysicalAccess) Purge() { + if purgeable, ok := p.physical.(Purgable); ok { + purgeable.Purge() + } +} diff --git a/physical/testing.go b/physical/testing.go index 69f716759..4c7537860 100644 --- a/physical/testing.go +++ b/physical/testing.go @@ -8,6 +8,7 @@ import ( ) func ExerciseBackend(t *testing.T, b Backend) { + t.Helper() // Should be empty keys, err := b.List("") if err != nil { @@ -196,6 +197,7 @@ func ExerciseBackend(t *testing.T, b Backend) { } func ExerciseBackend_ListPrefix(t *testing.T, b Backend) { + t.Helper() e1 := &Entry{Key: "foo", Value: []byte("test")} e2 := &Entry{Key: "foo/bar", Value: []byte("test")} e3 := &Entry{Key: "foo/bar/baz", Value: []byte("test")} @@ -266,6 +268,7 @@ func ExerciseBackend_ListPrefix(t *testing.T, b Backend) { } func ExerciseHABackend(t *testing.T, b HABackend, b2 HABackend) { + t.Helper() // Get the lock lock, err := b.LockWith("foo", "bar") if err != nil { @@ -342,6 +345,7 @@ func ExerciseHABackend(t *testing.T, b HABackend, b2 HABackend) { } func ExerciseTransactionalBackend(t *testing.T, b Backend) { + t.Helper() tb, ok := b.(Transactional) if !ok { t.Fatal("Not a transactional backend") @@ -395,7 +399,8 @@ func ExerciseTransactionalBackend(t *testing.T, b Backend) { } } -func SetupTestingTransactions(t *testing.T, b Backend) []TxnEntry { +func SetupTestingTransactions(t *testing.T, b Backend) []*TxnEntry { + t.Helper() // Add a few keys so that we test rollback with deletion if err := b.Put(&Entry{ Key: "foo", @@ -420,34 +425,34 @@ func SetupTestingTransactions(t *testing.T, b Backend) []TxnEntry { t.Fatal(err) } - txns := []TxnEntry{ - TxnEntry{ + txns := []*TxnEntry{ + &TxnEntry{ Operation: PutOperation, Entry: &Entry{ Key: "foo", Value: []byte("bar2"), }, }, - TxnEntry{ + &TxnEntry{ Operation: DeleteOperation, Entry: &Entry{ Key: "deleteme", }, }, - TxnEntry{ + &TxnEntry{ Operation: PutOperation, Entry: &Entry{ Key: "foo", Value: []byte("bar3"), }, }, - TxnEntry{ + &TxnEntry{ Operation: DeleteOperation, Entry: &Entry{ Key: "deleteme2", }, }, - TxnEntry{ + &TxnEntry{ Operation: PutOperation, Entry: &Entry{ Key: "zip", diff --git a/physical/transactions.go b/physical/transactions.go index f8668d2be..d6b3d467c 100644 --- a/physical/transactions.go +++ b/physical/transactions.go @@ -1,6 +1,8 @@ package physical -import multierror "github.com/hashicorp/go-multierror" +import ( + multierror "github.com/hashicorp/go-multierror" +) // TxnEntry is an operation that takes atomically as part of // a transactional update. Only supported by Transactional backends. @@ -14,7 +16,7 @@ type TxnEntry struct { // required for some features such as replication. type Transactional interface { // The function to run a transaction - Transaction([]TxnEntry) error + Transaction([]*TxnEntry) error } type PseudoTransactional interface { @@ -27,8 +29,8 @@ type PseudoTransactional interface { } // Implements the transaction interface -func GenericTransactionHandler(t PseudoTransactional, txns []TxnEntry) (retErr error) { - rollbackStack := make([]TxnEntry, 0, len(txns)) +func GenericTransactionHandler(t PseudoTransactional, txns []*TxnEntry) (retErr error) { + rollbackStack := make([]*TxnEntry, 0, len(txns)) var dirty bool // We walk the transactions in order; each successful operation goes into a @@ -47,11 +49,12 @@ TxnWalk: // Nothing to delete or roll back continue } - rollbackEntry := TxnEntry{ + rollbackEntry := &TxnEntry{ Operation: PutOperation, Entry: &Entry{ - Key: entry.Key, - Value: entry.Value, + Key: entry.Key, + Value: entry.Value, + SealWrap: entry.SealWrap, }, } err = t.DeleteInternal(txn.Entry.Key) @@ -60,7 +63,7 @@ TxnWalk: dirty = true break TxnWalk } - rollbackStack = append([]TxnEntry{rollbackEntry}, rollbackStack...) + rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...) case PutOperation: entry, err := t.GetInternal(txn.Entry.Key) @@ -70,30 +73,32 @@ TxnWalk: break TxnWalk } // Nothing existed so in fact rolling back requires a delete - var rollbackEntry TxnEntry + var rollbackEntry *TxnEntry if entry == nil { - rollbackEntry = TxnEntry{ + rollbackEntry = &TxnEntry{ Operation: DeleteOperation, Entry: &Entry{ Key: txn.Entry.Key, }, } } else { - rollbackEntry = TxnEntry{ + rollbackEntry = &TxnEntry{ Operation: PutOperation, Entry: &Entry{ - Key: entry.Key, - Value: entry.Value, + Key: entry.Key, + Value: entry.Value, + SealWrap: entry.SealWrap, }, } } + err = t.PutInternal(txn.Entry) if err != nil { retErr = multierror.Append(retErr, err) dirty = true break TxnWalk } - rollbackStack = append([]TxnEntry{rollbackEntry}, rollbackStack...) + rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...) } } diff --git a/vault/acl.go b/vault/acl.go index 73601788f..5326d4828 100644 --- a/vault/acl.go +++ b/vault/acl.go @@ -23,6 +23,25 @@ type ACL struct { root bool } +type PolicyCheckOpts struct { + RootPrivsRequired bool + Unauth bool +} + +type AuthResults struct { + ACLResults *ACLResults + Allowed bool + RootPrivs bool + Error *multierror.Error +} + +type ACLResults struct { + Allowed bool + RootPrivs bool + IsRoot bool + MFAMethods []string +} + // New is used to construct a policy based ACL from a set of policies. func NewACL(policies []*Policy) (*ACL, error) { // Initialize @@ -38,6 +57,13 @@ func NewACL(policies []*Policy) (*ACL, error) { if policy == nil { continue } + + switch policy.Type { + case PolicyTypeACL: + default: + return nil, fmt.Errorf("unable to parse policy (wrong type)") + } + // Check if this is root if policy.Name == "root" { a.root = true @@ -61,7 +87,7 @@ func NewACL(policies []*Policy) (*ACL, error) { } // these are the ones already in the tree - existingPerms := raw.(*Permissions) + existingPerms := raw.(*ACLPermissions) switch { case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0: @@ -142,7 +168,6 @@ func NewACL(policies []*Policy) (*ACL, error) { INSERT: tree.Insert(pc.Prefix, existingPerms) - } } return a, nil @@ -159,7 +184,7 @@ func (a *ACL) Capabilities(path string) (pathCapabilities []string) { raw, ok := a.exactRules.Get(path) if ok { - perm := raw.(*Permissions) + perm := raw.(*ACLPermissions) capabilities = perm.CapabilitiesBitmap goto CHECK } @@ -169,7 +194,7 @@ func (a *ACL) Capabilities(path string) (pathCapabilities []string) { if !ok { return []string{DenyCapability} } else { - perm := raw.(*Permissions) + perm := raw.(*ACLPermissions) capabilities = perm.CapabilitiesBitmap } @@ -201,29 +226,33 @@ CHECK: return } -// AllowOperation is used to check if the given operation is permitted. The -// first bool indicates if an op is allowed, the second whether sudo priviliges -// exist for that op and path. -func (a *ACL) AllowOperation(req *logical.Request) (bool, bool) { +// AllowOperation is used to check if the given operation is permitted. +func (a *ACL) AllowOperation(req *logical.Request) (ret *ACLResults) { + ret = new(ACLResults) + // Fast-path root if a.root { - return true, true + ret.Allowed = true + ret.RootPrivs = true + ret.IsRoot = true + return } op := req.Operation path := req.Path // Help is always allowed if op == logical.HelpOperation { - return true, false + ret.Allowed = true + return } - var permissions *Permissions + var permissions *ACLPermissions // Find an exact matching rule, look for glob if no match var capabilities uint32 raw, ok := a.exactRules.Get(path) if ok { - permissions = raw.(*Permissions) + permissions = raw.(*ACLPermissions) capabilities = permissions.CapabilitiesBitmap goto CHECK } @@ -231,9 +260,9 @@ func (a *ACL) AllowOperation(req *logical.Request) (bool, bool) { // Find a glob rule, default deny if no match _, raw, ok = a.globRules.LongestPrefix(path) if !ok { - return false, false + return } else { - permissions = raw.(*Permissions) + permissions = raw.(*ACLPermissions) capabilities = permissions.CapabilitiesBitmap } @@ -241,7 +270,8 @@ CHECK: // Check if the minimum permissions are met // If "deny" has been explicitly set, only deny will be in the map, so we // only need to check for the existence of other values - sudo := capabilities&SudoCapabilityInt > 0 + ret.RootPrivs = capabilities&SudoCapabilityInt > 0 + operationAllowed := false switch op { case logical.ReadOperation: @@ -261,21 +291,21 @@ CHECK: operationAllowed = capabilities&UpdateCapabilityInt > 0 default: - return false, false + return } if !operationAllowed { - return false, sudo + return } if permissions.MaxWrappingTTL > 0 { if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL { - return false, sudo + return } } if permissions.MinWrappingTTL > 0 { if req.WrapInfo == nil || req.WrapInfo.TTL < permissions.MinWrappingTTL { - return false, sudo + return } } // This situation can happen because of merging, even though in a single @@ -283,7 +313,7 @@ CHECK: if permissions.MinWrappingTTL != 0 && permissions.MaxWrappingTTL != 0 && permissions.MaxWrappingTTL < permissions.MinWrappingTTL { - return false, sudo + return } // Only check parameter permissions for operations that can modify @@ -291,7 +321,8 @@ CHECK: if op == logical.UpdateOperation || op == logical.CreateOperation { // If there are no data fields, allow if len(req.Data) == 0 { - return true, sudo + ret.Allowed = true + return } if len(permissions.DeniedParameters) == 0 { @@ -300,7 +331,7 @@ CHECK: // Check if all parameters have been denied if _, ok := permissions.DeniedParameters["*"]; ok { - return false, sudo + return } for parameter, value := range req.Data { @@ -308,7 +339,7 @@ CHECK: if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok { // If the value exists in denied values slice, deny if valueInParameterList(value, valueSlice) { - return false, sudo + return } } } @@ -316,30 +347,59 @@ CHECK: ALLOWED_PARAMETERS: // If we don't have any allowed parameters set, allow if len(permissions.AllowedParameters) == 0 { - return true, sudo + ret.Allowed = true + return } _, allowedAll := permissions.AllowedParameters["*"] if len(permissions.AllowedParameters) == 1 && allowedAll { - return true, sudo + ret.Allowed = true + return } for parameter, value := range req.Data { valueSlice, ok := permissions.AllowedParameters[strings.ToLower(parameter)] // Requested parameter is not in allowed list if !ok && !allowedAll { - return false, sudo + return } // If the value doesn't exists in the allowed values slice, // deny if ok && !valueInParameterList(value, valueSlice) { - return false, sudo + return } } } - return true, sudo + ret.Allowed = true + return +} +func (c *Core) performPolicyChecks(acl *ACL, te *TokenEntry, req *logical.Request, inEntity *identity.Entity, opts *PolicyCheckOpts) (ret *AuthResults) { + ret = new(AuthResults) + + // First, perform normal ACL checks if requested. The only time no ACL + // should be applied is if we are only processing EGPs against a login + // path in which case opts.Unauth will be set. + if acl != nil && !opts.Unauth { + ret.ACLResults = acl.AllowOperation(req) + ret.RootPrivs = ret.ACLResults.RootPrivs + // Root is always allowed; skip Sentinel/MFA checks + if ret.ACLResults.IsRoot { + //c.logger.Warn("policy: token is root, skipping checks") + ret.Allowed = true + return + } + if !ret.ACLResults.Allowed { + return + } + if !ret.RootPrivs && opts.RootPrivsRequired { + return + } + } + + ret.Allowed = true + return } func valueInParameterList(v interface{}, list []interface{}) bool { diff --git a/vault/acl_test.go b/vault/acl_test.go index 638fed6d0..c753bb0ed 100644 --- a/vault/acl_test.go +++ b/vault/acl_test.go @@ -23,7 +23,7 @@ func TestACL_Capabilities(t *testing.T) { t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected) } - policies, err := Parse(aclPolicy) + policies, err := ParseACLPolicy(aclPolicy) if err != nil { t.Fatalf("err: %v", err) } @@ -64,17 +64,17 @@ func TestACL_Root(t *testing.T) { request := new(logical.Request) request.Operation = logical.UpdateOperation request.Path = "sys/mount/foo" - allowed, rootPrivs := acl.AllowOperation(request) - if !rootPrivs { + authResults := acl.AllowOperation(request) + if !authResults.RootPrivs { t.Fatalf("expected root") } - if !allowed { + if !authResults.Allowed { t.Fatalf("expected permissions") } } func TestACL_Single(t *testing.T) { - policy, err := Parse(aclPolicy) + policy, err := ParseACLPolicy(aclPolicy) if err != nil { t.Fatalf("err: %v", err) } @@ -89,8 +89,8 @@ func TestACL_Single(t *testing.T) { request := new(logical.Request) request.Operation = logical.ReadOperation request.Path = "sys/mount/foo" - _, rootPrivs := acl.AllowOperation(request) - if rootPrivs { + authResults := acl.AllowOperation(request) + if authResults.RootPrivs { t.Fatalf("unexpected root") } @@ -128,23 +128,23 @@ func TestACL_Single(t *testing.T) { request := new(logical.Request) request.Operation = tc.op request.Path = tc.path - allowed, rootPrivs := acl.AllowOperation(request) - if allowed != tc.allowed { - t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs) + authResults := acl.AllowOperation(request) + if authResults.Allowed != tc.allowed { + t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs) } - if rootPrivs != tc.rootPrivs { - t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs) + if authResults.RootPrivs != tc.rootPrivs { + t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs) } } } func TestACL_Layered(t *testing.T) { - policy1, err := Parse(aclPolicy) + policy1, err := ParseACLPolicy(aclPolicy) if err != nil { t.Fatalf("err: %v", err) } - policy2, err := Parse(aclPolicy2) + policy2, err := ParseACLPolicy(aclPolicy2) if err != nil { t.Fatalf("err: %v", err) } @@ -162,8 +162,8 @@ func testLayeredACL(t *testing.T, acl *ACL) { request := new(logical.Request) request.Operation = logical.ReadOperation request.Path = "sys/mount/foo" - _, rootPrivs := acl.AllowOperation(request) - if rootPrivs { + authResults := acl.AllowOperation(request) + if authResults.RootPrivs { t.Fatalf("unexpected root") } @@ -206,18 +206,18 @@ func testLayeredACL(t *testing.T, acl *ACL) { request := new(logical.Request) request.Operation = tc.op request.Path = tc.path - allowed, rootPrivs := acl.AllowOperation(request) - if allowed != tc.allowed { - t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs) + authResults := acl.AllowOperation(request) + if authResults.Allowed != tc.allowed { + t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs) } - if rootPrivs != tc.rootPrivs { - t.Fatalf("bad: case %#v: %v, %v", tc, allowed, rootPrivs) + if authResults.RootPrivs != tc.rootPrivs { + t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs) } } } func TestACL_PolicyMerge(t *testing.T) { - policy, err := Parse(mergingPolicies) + policy, err := ParseACLPolicy(mergingPolicies) if err != nil { t.Fatalf("err: %v", err) } @@ -256,7 +256,7 @@ func TestACL_PolicyMerge(t *testing.T) { t.Fatalf("Could not find acl entry for path %s", tc.path) } - p := raw.(*Permissions) + p := raw.(*ACLPermissions) if !reflect.DeepEqual(tc.allowed, p.AllowedParameters) { t.Fatalf("Allowed paramaters did not match, Expected: %#v, Got: %#v", tc.allowed, p.AllowedParameters) } @@ -273,7 +273,7 @@ func TestACL_PolicyMerge(t *testing.T) { } func TestACL_AllowOperation(t *testing.T) { - policy, err := Parse(permissionsPolicy) + policy, err := ParseACLPolicy(permissionsPolicy) if err != nil { t.Fatalf("err: %v", err) } @@ -333,16 +333,16 @@ func TestACL_AllowOperation(t *testing.T) { } for _, op := range toperations { request.Operation = op - allowed, _ := acl.AllowOperation(&request) - if allowed != tc.allowed { - t.Fatalf("bad: case %#v: %v", tc, allowed) + authResults := acl.AllowOperation(&request) + if authResults.Allowed != tc.allowed { + t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed) } } } } func TestACL_ValuePermissions(t *testing.T) { - policy, err := Parse(valuePermissionsPolicy) + policy, err := ParseACLPolicy(valuePermissionsPolicy) if err != nil { t.Fatalf("err: %v", err) } @@ -408,9 +408,9 @@ func TestACL_ValuePermissions(t *testing.T) { } for _, op := range toperations { request.Operation = op - allowed, _ := acl.AllowOperation(&request) - if allowed != tc.allowed { - t.Fatalf("bad: case %#v: %v", tc, allowed) + authResults := acl.AllowOperation(&request) + if authResults.Allowed != tc.allowed { + t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed) } } } @@ -418,7 +418,7 @@ func TestACL_ValuePermissions(t *testing.T) { // NOTE: this test doesn't catch any races ATM func TestACL_CreationRace(t *testing.T) { - policy, err := Parse(valuePermissionsPolicy) + policy, err := ParseACLPolicy(valuePermissionsPolicy) if err != nil { t.Fatalf("err: %v", err) } diff --git a/vault/auth.go b/vault/auth.go index 5900449f1..be37990e7 100644 --- a/vault/auth.go +++ b/vault/auth.go @@ -95,6 +95,8 @@ func (c *Core) enableCredential(entry *MountEntry) error { } viewPath := credentialBarrierPrefix + entry.UUID + "/" view := NewBarrierView(c.barrier, viewPath) + var err error + var backend logical.Backend sysView := c.mountEntrySysView(entry) conf := make(map[string]string) if entry.Config.PluginName != "" { @@ -102,7 +104,7 @@ func (c *Core) enableCredential(entry *MountEntry) error { } // Create the new backend - backend, err := c.newCredentialBackend(entry.Type, sysView, view, conf) + backend, err = c.newCredentialBackend(entry.Type, sysView, view, conf) if err != nil { return err } @@ -155,7 +157,7 @@ func (c *Core) disableCredential(path string) error { // Store the view for this backend fullPath := credentialRoutePrefix + path - view := c.router.MatchingStorageView(fullPath) + view := c.router.MatchingStorageByAPIPath(fullPath) if view == nil { return fmt.Errorf("no matching backend %s", fullPath) } @@ -170,14 +172,13 @@ func (c *Core) disableCredential(path string) error { return err } - // Revoke credentials from this path - if err := c.expiration.RevokePrefix(fullPath); err != nil { - return err - } - - // Call cleanup function if it exists - backend := c.router.MatchingBackend(fullPath) if backend != nil { + // Revoke credentials from this path + if err := c.expiration.RevokePrefix(fullPath); err != nil { + return err + } + + // Call cleanup function if it exists backend.Cleanup() } @@ -186,11 +187,14 @@ func (c *Core) disableCredential(path string) error { return err } - // Clear the data in the view - if view != nil { + switch { + case entry.Local, !c.replicationState.HasState(consts.ReplicationPerformanceSecondary): + // Have writable storage, remove the whole thing if err := logical.ClearView(view); err != nil { + c.logger.Error("core: failed to clear view for path being unmounted", "error", err, "path", path) return err } + } // Remove the mount table entry @@ -226,6 +230,27 @@ func (c *Core) removeCredEntry(path string) error { return nil } +// remountCredEntryForce takes a copy of the mount entry for the path and fully +// unmounts and remounts the backend to pick up any changes, such as filtered +// paths +func (c *Core) remountCredEntryForce(path string) error { + fullPath := credentialRoutePrefix + path + me := c.router.MatchingMountEntry(fullPath) + if me == nil { + return fmt.Errorf("cannot find mount for path '%s'", path) + } + + me, err := me.Clone() + if err != nil { + return err + } + + if err := c.disableCredential(path); err != nil { + return err + } + return c.enableCredential(me) +} + // taintCredEntry is used to mark an entry in the auth table as tainted func (c *Core) taintCredEntry(path string) error { c.authLock.Lock() @@ -398,9 +423,9 @@ func (c *Core) persistAuth(table *MountTable, localOnly bool) error { // setupCredentials is invoked after we've loaded the auth table to // initialize the credential backends and setup the router func (c *Core) setupCredentials() error { - var view *BarrierView var err error var persistNeeded bool + var view *BarrierView c.authLock.Lock() defer c.authLock.Unlock() @@ -416,13 +441,13 @@ func (c *Core) setupCredentials() error { // Create a barrier view using the UUID viewPath := credentialBarrierPrefix + entry.UUID + "/" view = NewBarrierView(c.barrier, viewPath) + // Initialize the backend sysView := c.mountEntrySysView(entry) conf := make(map[string]string) if entry.Config.PluginName != "" { conf["plugin_name"] = entry.Config.PluginName } - // Initialize the backend backend, err = c.newCredentialBackend(entry.Type, sysView, view, conf) if err != nil { c.logger.Error("core: failed to create credential entry", "path", entry.Path, "error", err) @@ -439,8 +464,9 @@ func (c *Core) setupCredentials() error { } // Check for the correct backend type - if entry.Type == "plugin" && backend.Type() != logical.TypeCredential { - return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backend.Type()) + backendType := backend.Type() + if entry.Type == "plugin" && backendType != logical.TypeCredential { + return fmt.Errorf("cannot mount '%s' of type '%s' as an auth backend", entry.Config.PluginName, backendType) } if err := backend.Initialize(); err != nil { diff --git a/vault/auth_test.go b/vault/auth_test.go index c81b26492..eef7b1c6e 100644 --- a/vault/auth_test.go +++ b/vault/auth_test.go @@ -294,7 +294,7 @@ func TestCore_DisableCredential_Cleanup(t *testing.T) { } // Store the view - view := c.router.MatchingStorageView("auth/foo/") + view := c.router.MatchingStorageByAPIPath("auth/foo/") // Inject data se := &logical.StorageEntry{ diff --git a/vault/barrier.go b/vault/barrier.go index 7c9acc055..f8474616f 100644 --- a/vault/barrier.go +++ b/vault/barrier.go @@ -160,15 +160,17 @@ type BarrierEncryptor interface { // Entry is used to represent data stored by the security barrier type Entry struct { - Key string - Value []byte + Key string + Value []byte + SealWrap bool } // Logical turns the Entry into a logical storage entry. func (e *Entry) Logical() *logical.StorageEntry { return &logical.StorageEntry{ - Key: e.Key, - Value: e.Value, + Key: e.Key, + Value: e.Value, + SealWrap: e.SealWrap, } } diff --git a/vault/barrier_access.go b/vault/barrier_access.go new file mode 100644 index 000000000..71a2b5d39 --- /dev/null +++ b/vault/barrier_access.go @@ -0,0 +1,22 @@ +package vault + +// BarrierEncryptorAccess is a wrapper around BarrierEncryptor that allows Core +// to expose its barrier encrypt/decrypt operations through BarrierEncryptorAccess() +// while restricting the ability to modify Core.barrier itself. +type BarrierEncryptorAccess struct { + barrierEncryptor BarrierEncryptor +} + +var _ BarrierEncryptor = (*BarrierEncryptorAccess)(nil) + +func NewBarrierEncryptorAccess(barrierEncryptor BarrierEncryptor) *BarrierEncryptorAccess { + return &BarrierEncryptorAccess{barrierEncryptor: barrierEncryptor} +} + +func (b *BarrierEncryptorAccess) Encrypt(key string, plaintext []byte) ([]byte, error) { + return b.barrierEncryptor.Encrypt(key, plaintext) +} + +func (b *BarrierEncryptorAccess) Decrypt(key string, ciphertext []byte) ([]byte, error) { + return b.barrierEncryptor.Decrypt(key, ciphertext) +} diff --git a/vault/barrier_aes_gcm.go b/vault/barrier_aes_gcm.go index 37c191bd6..e8053d291 100644 --- a/vault/barrier_aes_gcm.go +++ b/vault/barrier_aes_gcm.go @@ -490,8 +490,9 @@ func (b *AESGCMBarrier) CreateUpgrade(term uint32) error { value := b.encrypt(key, prevTerm, primary, buf) // Create upgrade key pe := &physical.Entry{ - Key: key, - Value: value, + Key: key, + Value: value, + SealWrap: true, } return b.backend.Put(pe) } @@ -642,8 +643,9 @@ func (b *AESGCMBarrier) Put(entry *Entry) error { } pe := &physical.Entry{ - Key: entry.Key, - Value: b.encrypt(entry.Key, term, primary, entry.Value), + Key: entry.Key, + Value: b.encrypt(entry.Key, term, primary, entry.Value), + SealWrap: entry.SealWrap, } return b.backend.Put(pe) } @@ -673,8 +675,9 @@ func (b *AESGCMBarrier) Get(key string) (*Entry, error) { // Wrap in a logical entry entry := &Entry{ - Key: key, - Value: plain, + Key: key, + Value: plain, + SealWrap: pe.SealWrap, } return entry, nil } diff --git a/vault/barrier_view.go b/vault/barrier_view.go index 3512aba49..4b86f685c 100644 --- a/vault/barrier_view.go +++ b/vault/barrier_view.go @@ -66,8 +66,9 @@ func (v *BarrierView) Get(key string) (*logical.StorageEntry, error) { } return &logical.StorageEntry{ - Key: entry.Key, - Value: entry.Value, + Key: entry.Key, + Value: entry.Value, + SealWrap: entry.SealWrap, }, nil } @@ -84,8 +85,9 @@ func (v *BarrierView) Put(entry *logical.StorageEntry) error { } nested := &Entry{ - Key: expandedKey, - Value: entry.Value, + Key: expandedKey, + Value: entry.Value, + SealWrap: entry.SealWrap, } return v.barrier.Put(nested) } diff --git a/vault/capabilities.go b/vault/capabilities.go index 6994e52ed..bbde54c8f 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -30,7 +30,7 @@ func (c *Core) Capabilities(token, path string) ([]string, error) { var policies []*Policy for _, tePolicy := range te.Policies { - policy, err := c.policyStore.GetPolicy(tePolicy) + policy, err := c.policyStore.GetPolicy(tePolicy, PolicyTypeToken) if err != nil { return nil, err } diff --git a/vault/capabilities_test.go b/vault/capabilities_test.go index 0db7eacf2..19bd40426 100644 --- a/vault/capabilities_test.go +++ b/vault/capabilities_test.go @@ -18,7 +18,7 @@ func TestCapabilities(t *testing.T) { } // Create a policy - policy, _ := Parse(aclPolicy) + policy, _ := ParseACLPolicy(aclPolicy) err = c.policyStore.SetPolicy(policy) if err != nil { t.Fatalf("err: %v", err)