mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package logical
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/sha256"
 | 
						|
	"encoding/base64"
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	sockaddr "github.com/hashicorp/go-sockaddr"
 | 
						|
)
 | 
						|
 | 
						|
type TokenType uint8
 | 
						|
 | 
						|
const (
 | 
						|
	// TokenTypeDefault means "use the default, if any, that is currently set
 | 
						|
	// on the mount". If not set, results in a Service token.
 | 
						|
	TokenTypeDefault TokenType = iota
 | 
						|
 | 
						|
	// TokenTypeService is a "normal" Vault token for long-lived services
 | 
						|
	TokenTypeService
 | 
						|
 | 
						|
	// TokenTypeBatch is a batch token
 | 
						|
	TokenTypeBatch
 | 
						|
 | 
						|
	// TokenTypeDefaultService configured on a mount, means that if
 | 
						|
	// TokenTypeDefault is sent back by the mount, create Service tokens
 | 
						|
	TokenTypeDefaultService
 | 
						|
 | 
						|
	// TokenTypeDefaultBatch configured on a mount, means that if
 | 
						|
	// TokenTypeDefault is sent back by the mount, create Batch tokens
 | 
						|
	TokenTypeDefaultBatch
 | 
						|
 | 
						|
	// ClientIDTWEDelimiter Delimiter between the string fields used to generate a client
 | 
						|
	// ID for tokens without entities. This is the 0 character, which
 | 
						|
	// is a non-printable string. Please see unicode.IsPrint for details.
 | 
						|
	ClientIDTWEDelimiter = rune('\x00')
 | 
						|
 | 
						|
	// SortedPoliciesTWEDelimiter Delimiter between each policy in the sorted policies used to
 | 
						|
	// generate a client ID for tokens without entities. This is the 127
 | 
						|
	// character, which is a non-printable string. Please see unicode.IsPrint
 | 
						|
	// for details.
 | 
						|
	SortedPoliciesTWEDelimiter = rune('\x7F')
 | 
						|
)
 | 
						|
 | 
						|
func (t *TokenType) UnmarshalJSON(b []byte) error {
 | 
						|
	if len(b) == 1 {
 | 
						|
		*t = TokenType(b[0] - '0')
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Handle upgrade from pre-1.2 where we were serialized as string:
 | 
						|
	s := string(b)
 | 
						|
	switch s {
 | 
						|
	case `"default"`, `""`:
 | 
						|
		*t = TokenTypeDefault
 | 
						|
	case `"service"`:
 | 
						|
		*t = TokenTypeService
 | 
						|
	case `"batch"`:
 | 
						|
		*t = TokenTypeBatch
 | 
						|
	case `"default-service"`:
 | 
						|
		*t = TokenTypeDefaultService
 | 
						|
	case `"default-batch"`:
 | 
						|
		*t = TokenTypeDefaultBatch
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("unknown token type %q", s)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (t TokenType) String() string {
 | 
						|
	switch t {
 | 
						|
	case TokenTypeDefault:
 | 
						|
		return "default"
 | 
						|
	case TokenTypeService:
 | 
						|
		return "service"
 | 
						|
	case TokenTypeBatch:
 | 
						|
		return "batch"
 | 
						|
	case TokenTypeDefaultService:
 | 
						|
		return "default-service"
 | 
						|
	case TokenTypeDefaultBatch:
 | 
						|
		return "default-batch"
 | 
						|
	default:
 | 
						|
		panic("unreachable")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TokenEntry is used to represent a given token
 | 
						|
type TokenEntry struct {
 | 
						|
	Type TokenType `json:"type" mapstructure:"type" structs:"type" sentinel:""`
 | 
						|
 | 
						|
	// ID of this entry, generally a random UUID
 | 
						|
	ID string `json:"id" mapstructure:"id" structs:"id" sentinel:""`
 | 
						|
 | 
						|
	// ExternalID is the ID of a newly created service
 | 
						|
	// token that will be returned to a user
 | 
						|
	ExternalID string `json:"-"`
 | 
						|
 | 
						|
	// Accessor for this token, a random UUID
 | 
						|
	Accessor string `json:"accessor" mapstructure:"accessor" structs:"accessor" sentinel:""`
 | 
						|
 | 
						|
	// Parent token, used for revocation trees
 | 
						|
	Parent string `json:"parent" mapstructure:"parent" structs:"parent" sentinel:""`
 | 
						|
 | 
						|
	// Which named policies should be used
 | 
						|
	Policies []string `json:"policies" mapstructure:"policies" structs:"policies"`
 | 
						|
 | 
						|
	// InlinePolicy specifies ACL rules to be applied to this token entry.
 | 
						|
	InlinePolicy string `json:"inline_policy" mapstructure:"inline_policy" structs:"inline_policy"`
 | 
						|
 | 
						|
	// Used for audit trails, this is something like "auth/user/login"
 | 
						|
	Path string `json:"path" mapstructure:"path" structs:"path"`
 | 
						|
 | 
						|
	// Used for auditing. This could include things like "source", "user", "ip"
 | 
						|
	Meta map[string]string `json:"meta" mapstructure:"meta" structs:"meta" sentinel:"meta"`
 | 
						|
 | 
						|
	// InternalMeta is used to store internal metadata. This metadata will not be audit logged or returned from lookup APIs.
 | 
						|
	InternalMeta map[string]string `json:"internal_meta" mapstructure:"internal_meta" structs:"internal_meta"`
 | 
						|
 | 
						|
	// Used for operators to be able to associate with the source
 | 
						|
	DisplayName string `json:"display_name" mapstructure:"display_name" structs:"display_name"`
 | 
						|
 | 
						|
	// Used to restrict the number of uses (zero is unlimited). This is to
 | 
						|
	// support one-time-tokens (generalized). There are a few special values:
 | 
						|
	// if it's -1 it has run through its use counts and is executing its final
 | 
						|
	// use; if it's -2 it is tainted, which means revocation is currently
 | 
						|
	// running on it; and if it's -3 it's also tainted but revocation
 | 
						|
	// previously ran and failed, so this hints the tidy function to try it
 | 
						|
	// again.
 | 
						|
	NumUses int `json:"num_uses" mapstructure:"num_uses" structs:"num_uses"`
 | 
						|
 | 
						|
	// Time of token creation
 | 
						|
	CreationTime int64 `json:"creation_time" mapstructure:"creation_time" structs:"creation_time" sentinel:""`
 | 
						|
 | 
						|
	// Duration set when token was created
 | 
						|
	TTL time.Duration `json:"ttl" mapstructure:"ttl" structs:"ttl" sentinel:""`
 | 
						|
 | 
						|
	// Explicit maximum TTL on the token
 | 
						|
	ExplicitMaxTTL time.Duration `json:"explicit_max_ttl" mapstructure:"explicit_max_ttl" structs:"explicit_max_ttl" sentinel:""`
 | 
						|
 | 
						|
	// If set, the role that was used for parameters at creation time
 | 
						|
	Role string `json:"role" mapstructure:"role" structs:"role"`
 | 
						|
 | 
						|
	// If set, the period of the token. This is only used when created directly
 | 
						|
	// through the create endpoint; periods managed by roles or other auth
 | 
						|
	// backends are subject to those renewal rules.
 | 
						|
	Period time.Duration `json:"period" mapstructure:"period" structs:"period" sentinel:""`
 | 
						|
 | 
						|
	// These are the deprecated fields
 | 
						|
	DisplayNameDeprecated    string        `json:"DisplayName" mapstructure:"DisplayName" structs:"DisplayName" sentinel:""`
 | 
						|
	NumUsesDeprecated        int           `json:"NumUses" mapstructure:"NumUses" structs:"NumUses" sentinel:""`
 | 
						|
	CreationTimeDeprecated   int64         `json:"CreationTime" mapstructure:"CreationTime" structs:"CreationTime" sentinel:""`
 | 
						|
	ExplicitMaxTTLDeprecated time.Duration `json:"ExplicitMaxTTL" mapstructure:"ExplicitMaxTTL" structs:"ExplicitMaxTTL" sentinel:""`
 | 
						|
 | 
						|
	// EntityID is the ID of the entity associated with this token.
 | 
						|
	EntityID string `json:"entity_id" mapstructure:"entity_id" structs:"entity_id"`
 | 
						|
 | 
						|
	// If NoIdentityPolicies is true, the token will not inherit
 | 
						|
	// identity policies from the associated EntityID.
 | 
						|
	NoIdentityPolicies bool `json:"no_identity_policies" mapstructure:"no_identity_policies" structs:"no_identity_policies"`
 | 
						|
 | 
						|
	// The set of CIDRs that this token can be used with
 | 
						|
	BoundCIDRs []*sockaddr.SockAddrMarshaler `json:"bound_cidrs" sentinel:""`
 | 
						|
 | 
						|
	// NamespaceID is the identifier of the namespace to which this token is
 | 
						|
	// confined to. Do not return this value over the API when the token is
 | 
						|
	// being looked up.
 | 
						|
	NamespaceID string `json:"namespace_id" mapstructure:"namespace_id" structs:"namespace_id" sentinel:""`
 | 
						|
 | 
						|
	// CubbyholeID is the identifier of the cubbyhole storage belonging to this
 | 
						|
	// token
 | 
						|
	CubbyholeID string `json:"cubbyhole_id" mapstructure:"cubbyhole_id" structs:"cubbyhole_id" sentinel:""`
 | 
						|
}
 | 
						|
 | 
						|
// CreateClientID returns the client ID, and a boolean which is false if the clientID
 | 
						|
// has an entity, and true otherwise
 | 
						|
func (te *TokenEntry) CreateClientID() (string, bool) {
 | 
						|
	var clientIDInputBuilder strings.Builder
 | 
						|
 | 
						|
	// if entry has an associated entity ID, return it
 | 
						|
	if te.EntityID != "" {
 | 
						|
		return te.EntityID, false
 | 
						|
	}
 | 
						|
 | 
						|
	// The entry is associated with a TWE (token without entity). In this case
 | 
						|
	// we must create a client ID by calculating the following formula:
 | 
						|
	// clientID = SHA256(sorted policies + namespace)
 | 
						|
 | 
						|
	// Step 1: Copy entry policies to a new struct
 | 
						|
	sortedPolicies := make([]string, len(te.Policies))
 | 
						|
	copy(sortedPolicies, te.Policies)
 | 
						|
 | 
						|
	// Step 2: Sort and join copied policies
 | 
						|
	sort.Strings(sortedPolicies)
 | 
						|
	for _, pol := range sortedPolicies {
 | 
						|
		clientIDInputBuilder.WriteRune(SortedPoliciesTWEDelimiter)
 | 
						|
		clientIDInputBuilder.WriteString(pol)
 | 
						|
	}
 | 
						|
 | 
						|
	// Step 3: Add namespace ID
 | 
						|
	clientIDInputBuilder.WriteRune(ClientIDTWEDelimiter)
 | 
						|
	clientIDInputBuilder.WriteString(te.NamespaceID)
 | 
						|
 | 
						|
	if clientIDInputBuilder.Len() == 0 {
 | 
						|
		return "", true
 | 
						|
	}
 | 
						|
	// Step 4: Remove the first character in the string, as it's an unnecessary delimiter
 | 
						|
	clientIDInput := clientIDInputBuilder.String()[1:]
 | 
						|
 | 
						|
	// Step 5: Hash the sum
 | 
						|
	hashed := sha256.Sum256([]byte(clientIDInput))
 | 
						|
	return base64.StdEncoding.EncodeToString(hashed[:]), true
 | 
						|
}
 | 
						|
 | 
						|
func (te *TokenEntry) SentinelGet(key string) (interface{}, error) {
 | 
						|
	if te == nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
	switch key {
 | 
						|
	case "policies":
 | 
						|
		return te.Policies, nil
 | 
						|
 | 
						|
	case "path":
 | 
						|
		return te.Path, nil
 | 
						|
 | 
						|
	case "display_name":
 | 
						|
		return te.DisplayName, nil
 | 
						|
 | 
						|
	case "num_uses":
 | 
						|
		return te.NumUses, nil
 | 
						|
 | 
						|
	case "role":
 | 
						|
		return te.Role, nil
 | 
						|
 | 
						|
	case "entity_id":
 | 
						|
		return te.EntityID, nil
 | 
						|
 | 
						|
	case "period":
 | 
						|
		return te.Period, nil
 | 
						|
 | 
						|
	case "period_seconds":
 | 
						|
		return int64(te.Period.Seconds()), nil
 | 
						|
 | 
						|
	case "explicit_max_ttl":
 | 
						|
		return te.ExplicitMaxTTL, nil
 | 
						|
 | 
						|
	case "explicit_max_ttl_seconds":
 | 
						|
		return int64(te.ExplicitMaxTTL.Seconds()), nil
 | 
						|
 | 
						|
	case "creation_ttl":
 | 
						|
		return te.TTL, nil
 | 
						|
 | 
						|
	case "creation_ttl_seconds":
 | 
						|
		return int64(te.TTL.Seconds()), nil
 | 
						|
 | 
						|
	case "creation_time":
 | 
						|
		return time.Unix(te.CreationTime, 0).Format(time.RFC3339Nano), nil
 | 
						|
 | 
						|
	case "creation_time_unix":
 | 
						|
		return time.Unix(te.CreationTime, 0), nil
 | 
						|
 | 
						|
	case "meta", "metadata":
 | 
						|
		return te.Meta, nil
 | 
						|
 | 
						|
	case "type":
 | 
						|
		teType := te.Type
 | 
						|
		switch teType {
 | 
						|
		case TokenTypeBatch, TokenTypeService:
 | 
						|
		case TokenTypeDefault:
 | 
						|
			teType = TokenTypeService
 | 
						|
		default:
 | 
						|
			return "unknown", nil
 | 
						|
		}
 | 
						|
		return teType.String(), nil
 | 
						|
	}
 | 
						|
 | 
						|
	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",
 | 
						|
		"type",
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// IsRoot returns false if the token is not root (or doesn't exist)
 | 
						|
func (te *TokenEntry) IsRoot() bool {
 | 
						|
	if te == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return len(te.Policies) == 1 && te.Policies[0] == "root"
 | 
						|
}
 |