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