Port over bits (#3575)

This commit is contained in:
Jeff Mitchell
2017-11-13 15:31:32 -05:00
committed by GitHub
parent c09165b5ff
commit c77196cea2
19 changed files with 257 additions and 83 deletions

View File

@@ -40,6 +40,7 @@ type Secret struct {
// available in WrappedAccessor.
type SecretWrapInfo struct {
Token string `json:"token"`
Accessor string `json:"accessor"`
TTL int `json:"ttl"`
CreationTime time.Time `json:"creation_time"`
CreationPath string `json:"creation_path"`

View File

@@ -21,6 +21,7 @@ func TestParseSecret(t *testing.T) {
],
"wrap_info": {
"token": "token",
"accessor": "accessor",
"ttl": 60,
"creation_time": "2016-06-07T15:52:10-04:00",
"wrapped_accessor": "abcd1234"
@@ -46,6 +47,7 @@ func TestParseSecret(t *testing.T) {
},
WrapInfo: &SecretWrapInfo{
Token: "token",
Accessor: "accessor",
TTL: 60,
CreationTime: rawTime,
WrappedAccessor: "abcd1234",

View File

@@ -242,12 +242,13 @@ func (f *AuditFormatter) FormatResponse(
// Cache and restore accessor in the response
if resp != nil {
var accessor, wrappedAccessor string
var accessor, wrappedAccessor, wrappingAccessor string
if !config.HMACAccessor && resp != nil && resp.Auth != nil && resp.Auth.Accessor != "" {
accessor = resp.Auth.Accessor
}
if !config.HMACAccessor && resp != nil && resp.WrapInfo != nil && resp.WrapInfo.WrappedAccessor != "" {
wrappedAccessor = resp.WrapInfo.WrappedAccessor
wrappingAccessor = resp.WrapInfo.Accessor
}
if err := Hash(salt, resp); err != nil {
return err
@@ -258,6 +259,9 @@ func (f *AuditFormatter) FormatResponse(
if wrappedAccessor != "" {
resp.WrapInfo.WrappedAccessor = wrappedAccessor
}
if wrappingAccessor != "" {
resp.WrapInfo.Accessor = wrappingAccessor
}
}
}
@@ -301,6 +305,7 @@ func (f *AuditFormatter) FormatResponse(
respWrapInfo = &AuditResponseWrapInfo{
TTL: int(resp.WrapInfo.TTL / time.Second),
Token: token,
Accessor: resp.WrapInfo.Accessor,
CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano),
CreationPath: resp.WrapInfo.CreationPath,
WrappedAccessor: resp.WrapInfo.WrappedAccessor,
@@ -412,6 +417,7 @@ type AuditSecret struct {
type AuditResponseWrapInfo struct {
TTL int `json:"ttl"`
Token string `json:"token"`
Accessor string `json:"accessor"`
CreationTime string `json:"creation_time"`
CreationPath string `json:"creation_path"`
WrappedAccessor string `json:"wrapped_accessor,omitempty"`

View File

@@ -93,6 +93,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {
}
s.Token = fn(s.Token)
s.Accessor = fn(s.Accessor)
if s.WrappedAccessor != "" {
s.WrappedAccessor = fn(s.WrappedAccessor)

View File

@@ -148,6 +148,7 @@ func TestHash(t *testing.T) {
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "bar",
Accessor: "flimflam",
CreationTime: now,
WrappedAccessor: "bar",
},
@@ -160,6 +161,7 @@ func TestHash(t *testing.T) {
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390",
CreationTime: now,
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
},
@@ -206,6 +208,11 @@ func TestHash(t *testing.T) {
if err := Hash(localSalt, tc.Input); err != nil {
t.Fatalf("err: %s\n\n%s", err, input)
}
if _, ok := tc.Input.(*logical.Response); ok {
if !reflect.DeepEqual(tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo) {
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output\n%#v", input, tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo)
}
}
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output\n%#v", input, tc.Input, tc.Output)
}

View File

@@ -182,6 +182,7 @@ func (t TableFormatter) OutputSecret(ui cli.Ui, secret, s *api.Secret) error {
if s.WrapInfo != nil {
onceHeader.Do(headerFunc)
input = append(input, fmt.Sprintf("wrapping_token: %s %s", config.Delim, s.WrapInfo.Token))
input = append(input, fmt.Sprintf("wrapping_accessor: %s %s", config.Delim, s.WrapInfo.Accessor))
input = append(input, fmt.Sprintf("wrapping_token_ttl: %s %s", config.Delim, (time.Second*time.Duration(s.WrapInfo.TTL)).String()))
input = append(input, fmt.Sprintf("wrapping_token_creation_time: %s %s", config.Delim, s.WrapInfo.CreationTime.String()))
input = append(input, fmt.Sprintf("wrapping_token_creation_path: %s %s", config.Delim, s.WrapInfo.CreationPath))

View File

@@ -53,6 +53,8 @@ func PrintRawField(ui cli.Ui, secret *api.Secret, field string) int {
switch field {
case "wrapping_token":
val = secret.WrapInfo.Token
case "wrapping_accessor":
val = secret.WrapInfo.Accessor
case "wrapping_token_ttl":
val = secret.WrapInfo.TTL
case "wrapping_token_creation_time":

View File

@@ -28,6 +28,20 @@ func (e *Entity) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (e *Entity) SentinelKeys() []string {
return []string{
"id",
"aliases",
"metadata",
"meta",
"name",
"creation_time",
"last_update_time",
"merged_entity_ids",
"policies",
}
}
func (p *Alias) SentinelGet(key string) (interface{}, error) {
if p == nil {
return nil, nil
@@ -56,6 +70,20 @@ func (p *Alias) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (a *Alias) SentinelKeys() []string {
return []string{
"id",
"mount_type",
"mount_path",
"meta",
"metadata",
"name",
"creation_time",
"last_update_time",
"merged_from_entity_ids",
}
}
func (g *Group) SentinelGet(key string) (interface{}, error) {
if g == nil {
return nil, nil
@@ -81,3 +109,17 @@ func (g *Group) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (g *Group) SentinelKeys() []string {
return []string{
"id",
"name",
"policies",
"parent_group_ids",
"member_entity_ids",
"metadata",
"meta",
"creation_time",
"last_update_time",
}
}

View File

@@ -10,6 +10,9 @@ type ResponseWrapInfo struct {
// The token containing the wrapped response
Token string `json:"token" structs:"token" mapstructure:"token" sentinel:""`
// The token accessor for the wrapped response token
Accessor string `json:"accessor" structs:"accessor" mapstructure:"accessor"`
// The creation time. This can be used with the TTL to figure out an
// expected expiration.
CreationTime time.Time `json:"creation_time" structs:"creation_time" mapstructure:"creation_time" sentinel:""`

View File

@@ -325,6 +325,12 @@ func TestSysMounts_headerAuth_Wrapped(t *testing.T) {
}
expected["wrap_info"].(map[string]interface{})["creation_path"] = actualCreationPath
actualAccessor, ok := actual["wrap_info"].(map[string]interface{})["accessor"]
if !ok || actualAccessor == "" {
t.Fatal("accessor missing in wrap info")
}
expected["wrap_info"].(map[string]interface{})["accessor"] = actualAccessor
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n%T %T", expected, actual, actual["warnings"], actual["data"])
}

View File

@@ -151,6 +151,7 @@ func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request
httpResp = &logical.HTTPResponse{
WrapInfo: &logical.HTTPWrapInfo{
Token: resp.WrapInfo.Token,
Accessor: resp.WrapInfo.Accessor,
TTL: int(resp.WrapInfo.TTL.Seconds()),
CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano),
CreationPath: resp.WrapInfo.CreationPath,

View File

@@ -37,6 +37,13 @@ func (r *RequestWrapInfo) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (r *RequestWrapInfo) SentinelKeys() []string {
return []string{
"ttl",
"ttl_seconds",
}
}
// 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.
@@ -176,6 +183,14 @@ func (r *Request) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (r *Request) SentinelKeys() []string {
return []string{
"path",
"wrapping",
"wrap_info",
}
}
func (r *Request) LastRemoteWAL() uint64 {
return r.lastRemoteWAL
}
@@ -260,4 +275,8 @@ var (
// ErrPermissionDenied is returned if the client is not authorized
ErrPermissionDenied = errors.New("permission denied")
// ErrMultiAuthzPending is returned if the the request needs more
// authorizations
ErrMultiAuthzPending = errors.New("request needs further approval")
)

View File

@@ -92,6 +92,7 @@ type HTTPAuth struct {
type HTTPWrapInfo struct {
Token string `json:"token"`
Accessor string `json:"accessor"`
TTL int `json:"ttl"`
CreationTime string `json:"creation_time"`
CreationPath string `json:"creation_path"`

View File

@@ -221,18 +221,29 @@ func testCoreWithIdentityTokenGithub(t *testing.T) (*Core, *IdentityStore, *Toke
return core, is, ts, ghAccessor
}
func testCoreWithIdentityTokenGithubRoot(t *testing.T) (*Core, *IdentityStore, *TokenStore, string, string) {
is, ghAccessor, core, root := testIdentityStoreWithGithubAuthRoot(t)
ts := testTokenStore(t, core)
return core, is, ts, ghAccessor, root
}
func testIdentityStoreWithGithubAuth(t *testing.T) (*IdentityStore, string, *Core) {
is, ghA, c, _ := testIdentityStoreWithGithubAuthRoot(t)
return is, ghA, c
}
// testIdentityStoreWithGithubAuth returns an instance of identity store which
// is mounted by default. This function also enables the github auth backend to
// assist with testing aliases and entities that require an valid mount
// accessor of an auth backend.
func testIdentityStoreWithGithubAuth(t *testing.T) (*IdentityStore, string, *Core) {
func testIdentityStoreWithGithubAuthRoot(t *testing.T) (*IdentityStore, string, *Core, string) {
// Add github credential factory to core config
err := AddTestCredentialBackend("github", credGithub.Factory)
if err != nil {
t.Fatalf("err: %s", err)
}
c, _, _ := TestCoreUnsealed(t)
c, _, root := TestCoreUnsealed(t)
meGH := &MountEntry{
Table: credentialTableType,
@@ -252,7 +263,7 @@ func testIdentityStoreWithGithubAuth(t *testing.T) (*IdentityStore, string, *Cor
t.Fatalf("failed to fetch identity store from router")
}
return identitystore.(*IdentityStore), meGH.Accessor, c
return identitystore.(*IdentityStore), meGH.Accessor, c, root
}
func TestIdentityStore_MetadataKeyRegex(t *testing.T) {

View File

@@ -6,6 +6,7 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
"strconv"
@@ -2507,42 +2508,36 @@ func (b *SystemBackend) handleWrappingUnwrap(
token = req.ClientToken
}
if thirdParty {
// Use the token to decrement the use count to avoid a second operation on the token.
_, err := b.Core.tokenStore.UseTokenByID(token)
// Get the policies so we can determine if this is a normal response
// wrapping request or a control group token.
//
// We use lookupTainted here because the token might have already been used
// by handleRequest(), this happens when it's a normal response wrapping
// request and the token was provided "first party". We want to inspect the
// token policies but will not use this token entry for anything else.
te, err := b.Core.tokenStore.lookupTainted(token)
if err != nil {
return nil, fmt.Errorf("error decrementing wrapping token's use-count: %v", err)
return nil, err
}
if te == nil {
return nil, errors.New("could not find token")
}
if len(te.Policies) != 1 {
return nil, errors.New("token is not a valid unwrap token")
}
defer b.Core.tokenStore.Revoke(token)
var response string
switch te.Policies[0] {
case responseWrappingPolicyName:
response, err = b.responseWrappingUnwrap(token, thirdParty)
}
cubbyReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "cubbyhole/response",
ClientToken: token,
}
cubbyResp, err := b.Core.router.Route(cubbyReq)
if err != nil {
return nil, fmt.Errorf("error looking up wrapping information: %v", err)
}
if cubbyResp == nil {
return logical.ErrorResponse("no information found; wrapping token may be from a previous Vault version"), nil
}
if cubbyResp != nil && cubbyResp.IsError() {
return cubbyResp, nil
}
if cubbyResp.Data == nil {
return logical.ErrorResponse("wrapping information was nil; wrapping token may be from a previous Vault version"), nil
var respErr *logical.Response
if len(response) > 0 {
respErr = logical.ErrorResponse(response)
}
responseRaw := cubbyResp.Data["response"]
if responseRaw == nil {
return nil, fmt.Errorf("no response found inside the cubbyhole")
}
response, ok := responseRaw.(string)
if !ok {
return nil, fmt.Errorf("could not decode response inside the cubbyhole")
return respErr, err
}
resp := &logical.Response{
@@ -2559,6 +2554,50 @@ func (b *SystemBackend) handleWrappingUnwrap(
return resp, nil
}
// responseWrappingUnwrap will read the stored response in the cubbyhole and
// return the raw HTTP response.
func (b *SystemBackend) responseWrappingUnwrap(token string, thirdParty bool) (string, error) {
if thirdParty {
// Use the token to decrement the use count to avoid a second operation on the token.
_, err := b.Core.tokenStore.UseTokenByID(token)
if err != nil {
return "", fmt.Errorf("error decrementing wrapping token's use-count: %v", err)
}
defer b.Core.tokenStore.Revoke(token)
}
cubbyReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "cubbyhole/response",
ClientToken: token,
}
cubbyResp, err := b.Core.router.Route(cubbyReq)
if err != nil {
return "", fmt.Errorf("error looking up wrapping information: %v", err)
}
if cubbyResp == nil {
return "no information found; wrapping token may be from a previous Vault version", ErrInternalError
}
if cubbyResp != nil && cubbyResp.IsError() {
return cubbyResp.Error().Error(), nil
}
if cubbyResp.Data == nil {
return "wrapping information was nil; wrapping token may be from a previous Vault version", ErrInternalError
}
responseRaw := cubbyResp.Data["response"]
if responseRaw == nil {
return "", fmt.Errorf("no response found inside the cubbyhole")
}
response, ok := responseRaw.(string)
if !ok {
return "", fmt.Errorf("could not decode response inside the cubbyhole")
}
return response, nil
}
func (b *SystemBackend) handleWrappingLookup(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// This ordering of lookups has been validated already in the wrapping

View File

@@ -22,9 +22,16 @@ const (
// policyCacheSize is the number of policies that are kept cached
policyCacheSize = 1024
// defaultPolicyName is the name of the default policy
defaultPolicyName = "default"
// responseWrappingPolicyName is the name of the fixed policy
responseWrappingPolicyName = "response-wrapping"
// controlGroupPolicyName is the name of the fixed policy for control group
// tokens
controlGroupPolicyName = "control-group"
// responseWrappingPolicy is the policy that ensures cubbyhole response
// wrapping can always succeed.
responseWrappingPolicy = `
@@ -117,9 +124,11 @@ var (
immutablePolicies = []string{
"root",
responseWrappingPolicyName,
controlGroupPolicyName,
}
nonAssignablePolicies = []string{
responseWrappingPolicyName,
controlGroupPolicyName,
}
)
@@ -181,28 +190,13 @@ func (c *Core) setupPolicyStore() error {
}
// Ensure that the default policy exists, and if not, create it
policy, err := c.policyStore.GetPolicy("default", PolicyTypeACL)
if err != nil {
return errwrap.Wrapf("error fetching default policy from store: {{err}}", err)
}
if policy == nil {
err := c.policyStore.createDefaultPolicy()
if err != nil {
if err := c.policyStore.loadACLPolicy(defaultPolicyName, defaultPolicy); err != nil {
return err
}
}
// Ensure that the cubbyhole response wrapping policy exists
policy, err = c.policyStore.GetPolicy(responseWrappingPolicyName, PolicyTypeACL)
if err != nil {
return errwrap.Wrapf("error fetching response-wrapping policy from store: {{err}}", err)
}
if policy == nil || policy.Raw != responseWrappingPolicy {
err := c.policyStore.createResponseWrappingPolicy()
if err != nil {
// Ensure that the response wrapping policy exists
if err := c.policyStore.loadACLPolicy(responseWrappingPolicyName, responseWrappingPolicy); err != nil {
return err
}
}
return nil
}
@@ -478,32 +472,30 @@ func (ps *PolicyStore) ACL(names ...string) (*ACL, error) {
return acl, nil
}
func (ps *PolicyStore) createDefaultPolicy() error {
policy, err := ParseACLPolicy(defaultPolicy)
func (ps *PolicyStore) loadACLPolicy(policyName, policyText string) error {
// Check if the policy already exists
policy, err := ps.GetPolicy(policyName, PolicyTypeACL)
if err != nil {
return errwrap.Wrapf("error parsing default policy: {{err}}", err)
return errwrap.Wrapf(fmt.Sprintf("error fetching %s policy from store: {{err}}", policyName), err)
}
if policy != nil {
if !strutil.StrListContains(immutablePolicies, policyName) || policyText == policy.Raw {
return nil
}
}
policy, err = ParseACLPolicy(policyText)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("error parsing %s policy: {{err}}", policyName), err)
}
if policy == nil {
return fmt.Errorf("parsing default policy resulted in nil policy")
return fmt.Errorf("parsing %s policy resulted in nil policy", policyName)
}
policy.Name = "default"
policy.Type = PolicyTypeACL
return ps.setPolicyInternal(policy)
}
func (ps *PolicyStore) createResponseWrappingPolicy() error {
policy, err := ParseACLPolicy(responseWrappingPolicy)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("error parsing %s policy: {{err}}", responseWrappingPolicyName), err)
}
if policy == nil {
return fmt.Errorf("parsing %s policy resulted in nil policy", responseWrappingPolicyName)
}
policy.Name = responseWrappingPolicyName
policy.Name = policyName
policy.Type = PolicyTypeACL
return ps.setPolicyInternal(policy)
}

View File

@@ -68,7 +68,8 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
err == nil &&
!resp.IsError() &&
resp.WrapInfo != nil &&
resp.WrapInfo.TTL != 0
resp.WrapInfo.TTL != 0 &&
resp.WrapInfo.Token == ""
if wrapping {
cubbyResp, cubbyErr := c.wrapInCubbyhole(req, resp, auth)
@@ -161,12 +162,10 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
if ctErr != nil {
// If it is an internal error we return that, otherwise we
// return invalid request so that the status codes can be correct
var errType error
errType := logical.ErrInvalidRequest
switch ctErr {
case ErrInternalError, logical.ErrPermissionDenied:
errType = ctErr
default:
errType = logical.ErrInvalidRequest
}
if err := c.auditBroker.LogRequest(auth, req, c.auditedHeaders, ctErr); err != nil {

View File

@@ -623,6 +623,21 @@ func (te *TokenEntry) SentinelGet(key string) (interface{}, error) {
return nil, nil
}
func (te *TokenEntry) SentinelKeys() []string {
return []string{
"period",
"period_seconds",
"explicit_max_ttl",
"explicit_max_ttl_seconds",
"creation_ttl",
"creation_ttl_seconds",
"creation_time",
"creation_time_unix",
"meta",
"metadata",
}
}
// tsRoleEntry contains token store role information
type tsRoleEntry struct {
// The name of the role. Embedded so it can be used for pathing
@@ -865,6 +880,12 @@ func (ts *TokenStore) UseToken(te *TokenEntry) (*TokenEntry, error) {
return te, nil
}
// If we are attempting to unwrap a control group request, don't use the token.
// It will be manually revoked by the handler.
if len(te.Policies) == 1 && te.Policies[0] == controlGroupPolicyName {
return te, nil
}
lock := locksutil.LockForKey(ts.tokenLocks, te.ID)
lock.Lock()
defer lock.Unlock()
@@ -934,6 +955,25 @@ func (ts *TokenStore) Lookup(id string) (*TokenEntry, error) {
return ts.lookupSalted(saltedID, false)
}
// lookupTainted is used to find a token that may or maynot be tainted given its
// ID. It acquires a read lock, then calls lookupSalted.
func (ts *TokenStore) lookupTainted(id string) (*TokenEntry, error) {
defer metrics.MeasureSince([]string{"token", "lookup"}, time.Now())
if id == "" {
return nil, fmt.Errorf("cannot lookup blank token")
}
lock := locksutil.LockForKey(ts.tokenLocks, id)
lock.RLock()
defer lock.RUnlock()
saltedID, err := ts.SaltID(id)
if err != nil {
return nil, err
}
return ts.lookupSalted(saltedID, true)
}
// lookupSalted is used to find a token given its salted ID. If tainted is
// true, entries that are in some revocation state (currently, indicated by num
// uses < 0), the entry will be returned anyways

View File

@@ -115,6 +115,7 @@ func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response, aut
}
resp.WrapInfo.Token = te.ID
resp.WrapInfo.Accessor = te.Accessor
resp.WrapInfo.CreationTime = creationTime
// If this is not a rewrap, store the request path as creation_path
if req.Path != "sys/wrapping/rewrap" {
@@ -328,7 +329,7 @@ func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) {
return false, nil
}
if te.Policies[0] != responseWrappingPolicyName {
if te.Policies[0] != responseWrappingPolicyName && te.Policies[0] != controlGroupPolicyName {
return false, nil
}