Sync over

This commit is contained in:
Jeff Mitchell
2017-10-23 16:42:56 -04:00
parent d79e0d2ad5
commit 98168dc791
36 changed files with 521 additions and 167 deletions

View File

@@ -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 }

View File

@@ -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
}

View File

@@ -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",
},
},

View File

@@ -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{} }

View File

@@ -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:""`
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -25,6 +25,7 @@ type Storage interface {
type StorageEntry struct {
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 {

View File

@@ -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,

View File

@@ -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.
`
}
)
@@ -55,6 +60,8 @@ type Meta struct {
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.

View File

@@ -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"},
},
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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.

View File

@@ -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()
}
}

View File

@@ -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",

View File

@@ -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,
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,
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...)
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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
}
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 := c.router.MatchingBackend(fullPath)
if backend != nil {
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 {

View File

@@ -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{

View File

@@ -162,6 +162,7 @@ type BarrierEncryptor interface {
type Entry struct {
Key string
Value []byte
SealWrap bool
}
// Logical turns the Entry into a logical storage entry.
@@ -169,6 +170,7 @@ func (e *Entry) Logical() *logical.StorageEntry {
return &logical.StorageEntry{
Key: e.Key,
Value: e.Value,
SealWrap: e.SealWrap,
}
}

22
vault/barrier_access.go Normal file
View File

@@ -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)
}

View File

@@ -492,6 +492,7 @@ func (b *AESGCMBarrier) CreateUpgrade(term uint32) error {
pe := &physical.Entry{
Key: key,
Value: value,
SealWrap: true,
}
return b.backend.Put(pe)
}
@@ -644,6 +645,7 @@ func (b *AESGCMBarrier) Put(entry *Entry) error {
pe := &physical.Entry{
Key: entry.Key,
Value: b.encrypt(entry.Key, term, primary, entry.Value),
SealWrap: entry.SealWrap,
}
return b.backend.Put(pe)
}
@@ -675,6 +677,7 @@ func (b *AESGCMBarrier) Get(key string) (*Entry, error) {
entry := &Entry{
Key: key,
Value: plain,
SealWrap: pe.SealWrap,
}
return entry, nil
}

View File

@@ -68,6 +68,7 @@ func (v *BarrierView) Get(key string) (*logical.StorageEntry, error) {
return &logical.StorageEntry{
Key: entry.Key,
Value: entry.Value,
SealWrap: entry.SealWrap,
}, nil
}
@@ -86,6 +87,7 @@ func (v *BarrierView) Put(entry *logical.StorageEntry) error {
nested := &Entry{
Key: expandedKey,
Value: entry.Value,
SealWrap: entry.SealWrap,
}
return v.barrier.Put(nested)
}

View File

@@ -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
}

View File

@@ -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)