2015-03-18 20:19:19 +00:00
package vault
import (
"encoding/json"
"fmt"
2015-04-15 21:24:07 +00:00
"regexp"
2015-12-30 20:18:30 +00:00
"sort"
2015-03-24 22:10:46 +00:00
"strings"
"time"
2015-03-18 20:19:19 +00:00
2015-04-08 23:43:17 +00:00
"github.com/armon/go-metrics"
2015-12-16 17:56:20 +00:00
"github.com/hashicorp/go-uuid"
2015-10-30 14:59:26 +00:00
"github.com/hashicorp/vault/helper/salt"
2015-03-18 20:19:19 +00:00
"github.com/hashicorp/vault/logical"
2015-03-31 19:48:19 +00:00
"github.com/hashicorp/vault/logical/framework"
2015-04-07 21:16:35 +00:00
"github.com/mitchellh/mapstructure"
2015-03-18 20:19:19 +00:00
)
const (
// lookupPrefix is the prefix used to store tokens for their
// primary ID based index
lookupPrefix = "id/"
// parentPrefix is the prefix used to store tokens for their
// secondar parent based index
parentPrefix = "parent/"
2015-03-18 21:00:42 +00:00
// tokenSubPath is the sub-path used for the token store
// view. This is nested under the system view.
tokenSubPath = "token/"
2015-03-18 20:19:19 +00:00
)
2015-04-15 21:24:07 +00:00
var (
// displayNameSanitize is used to sanitize a display name given to a token.
displayNameSanitize = regexp . MustCompile ( "[^a-zA-Z0-9-]" )
)
2015-03-18 20:19:19 +00:00
// TokenStore is used to manage client tokens. Tokens are used for
// clients to authenticate, and each token is mapped to an applicable
// set of policy which is used for authorization.
type TokenStore struct {
2015-03-31 19:48:19 +00:00
* framework . Backend
2015-03-18 20:19:19 +00:00
view * BarrierView
2015-06-30 21:08:21 +00:00
salt * salt . Salt
2015-04-03 18:40:08 +00:00
expiration * ExpirationManager
2015-09-10 01:58:09 +00:00
2015-09-15 17:49:53 +00:00
cubbyholeBackend * CubbyholeBackend
2015-10-07 19:30:54 +00:00
2015-11-06 16:36:40 +00:00
policyLookupFunc func ( string ) ( * Policy , error )
2015-03-18 20:19:19 +00:00
}
// NewTokenStore is used to construct a token store that is
// backed by the given barrier view.
2015-09-04 20:58:12 +00:00
func NewTokenStore ( c * Core , config * logical . BackendConfig ) ( * TokenStore , error ) {
2015-03-19 02:11:52 +00:00
// Create a sub-view
2015-09-04 20:58:12 +00:00
view := c . systemBarrierView . SubView ( tokenSubPath )
2015-03-19 02:11:52 +00:00
2015-03-18 20:19:19 +00:00
// Initialize the store
t := & TokenStore {
2015-09-15 15:28:07 +00:00
view : view ,
2015-03-18 20:19:19 +00:00
}
2015-11-06 16:52:26 +00:00
if c . policyStore != nil {
t . policyLookupFunc = c . policyStore . GetPolicy
2015-10-07 19:30:54 +00:00
}
2015-06-30 21:08:21 +00:00
// Setup the salt
2015-09-18 16:18:37 +00:00
salt , err := salt . NewSalt ( view , & salt . Config {
HashFunc : salt . SHA1Hash ,
} )
2015-03-18 20:19:19 +00:00
if err != nil {
2015-06-30 21:08:21 +00:00
return nil , err
2015-03-18 20:19:19 +00:00
}
2015-06-30 21:08:21 +00:00
t . salt = salt
2015-03-31 19:48:19 +00:00
// Setup the framework endpoints
t . Backend = & framework . Backend {
2016-01-29 22:44:09 +00:00
AuthRenew : t . authRenew ,
2015-04-11 23:28:16 +00:00
2015-04-03 18:40:08 +00:00
PathsSpecial : & logical . Paths {
Root : [ ] string {
"revoke-prefix/*" ,
2015-09-16 13:22:15 +00:00
"revoke-orphan/*" ,
2015-04-03 18:40:08 +00:00
} ,
} ,
2015-03-31 19:48:19 +00:00
Paths : [ ] * framework . Path {
2015-11-03 20:10:46 +00:00
& framework . Path {
Pattern : "create-orphan$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleCreateOrphan ,
2015-11-03 20:10:46 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenCreateOrphanHelp ) ,
HelpDescription : strings . TrimSpace ( tokenCreateOrphanHelp ) ,
} ,
2015-03-31 19:48:19 +00:00
& framework . Path {
Pattern : "create$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleCreate ,
2015-03-31 19:48:19 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenCreateHelp ) ,
HelpDescription : strings . TrimSpace ( tokenCreateHelp ) ,
} ,
& framework . Path {
2015-03-31 19:51:00 +00:00
Pattern : "lookup/(?P<token>.+)" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to lookup" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : t . handleLookup ,
} ,
HelpSynopsis : strings . TrimSpace ( tokenLookupHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupHelp ) ,
} ,
2016-03-08 20:13:29 +00:00
& framework . Path {
Pattern : "lookup-accessor$" ,
Fields : map [ string ] * framework . FieldSchema {
2016-03-09 11:23:31 +00:00
"accessor" : & framework . FieldSchema {
2016-03-08 20:13:29 +00:00
Type : framework . TypeString ,
Description : "Accessor ID to lookup" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-03-08 22:38:19 +00:00
logical . UpdateOperation : t . handleLookupAccessor ,
2016-03-08 20:13:29 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenLookupAccessorHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupAccessorHelp ) ,
} ,
2015-03-31 19:51:00 +00:00
& framework . Path {
Pattern : "lookup-self$" ,
2015-03-31 19:48:19 +00:00
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to lookup" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : t . handleLookup ,
} ,
HelpSynopsis : strings . TrimSpace ( tokenLookupHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupHelp ) ,
} ,
2016-03-08 20:13:29 +00:00
& framework . Path {
Pattern : "revoke-accessor$" ,
2016-03-08 23:07:27 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-03-09 11:23:31 +00:00
"accessor" : & framework . FieldSchema {
2016-03-08 23:07:27 +00:00
Type : framework . TypeString ,
2016-03-09 03:27:24 +00:00
Description : "Accessor ID of the token" ,
2016-03-08 23:07:27 +00:00
} ,
} ,
2016-03-08 20:13:29 +00:00
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . UpdateOperation : t . handleRevokeAccessor ,
} ,
HelpSynopsis : strings . TrimSpace ( tokenRevokeAccessorHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokeAccessorHelp ) ,
} ,
2015-09-17 17:22:30 +00:00
& framework . Path {
2015-10-07 16:49:13 +00:00
Pattern : "revoke-self$" ,
2015-09-17 17:22:30 +00:00
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRevokeSelf ,
2015-09-17 17:22:30 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRevokeSelfHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokeSelfHelp ) ,
} ,
2015-03-31 19:48:19 +00:00
& framework . Path {
Pattern : "revoke/(?P<token>.+)" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to revoke" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRevokeTree ,
2015-03-31 19:48:19 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRevokeHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokeHelp ) ,
} ,
2015-04-03 19:11:49 +00:00
2015-03-31 19:48:19 +00:00
& framework . Path {
Pattern : "revoke-orphan/(?P<token>.+)" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to revoke" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRevokeOrphan ,
2015-03-31 19:48:19 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRevokeOrphanHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokeOrphanHelp ) ,
} ,
2015-04-03 18:40:08 +00:00
& framework . Path {
Pattern : "revoke-prefix/(?P<prefix>.+)" ,
Fields : map [ string ] * framework . FieldSchema {
"prefix" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token source prefix to revoke" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRevokePrefix ,
2015-04-03 18:40:08 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRevokePrefixHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokePrefixHelp ) ,
} ,
2015-04-03 19:11:49 +00:00
2015-10-07 16:49:13 +00:00
& framework . Path {
Pattern : "renew-self$" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to renew" ,
} ,
"increment" : & framework . FieldSchema {
Type : framework . TypeDurationSecond ,
Description : "The desired increment in seconds to the token expiration" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRenewSelf ,
2015-10-07 16:49:13 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRenewSelfHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRenewSelfHelp ) ,
} ,
2015-04-03 19:11:49 +00:00
& framework . Path {
Pattern : "renew/(?P<token>.+)" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to renew" ,
} ,
2015-04-09 21:23:37 +00:00
"increment" : & framework . FieldSchema {
2015-06-17 22:58:20 +00:00
Type : framework . TypeDurationSecond ,
2015-04-09 21:23:37 +00:00
Description : "The desired increment in seconds to the token expiration" ,
} ,
2015-04-03 19:11:49 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-01-07 15:30:47 +00:00
logical . UpdateOperation : t . handleRenew ,
2015-04-03 19:11:49 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenRenewHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRenewHelp ) ,
} ,
2015-03-31 19:48:19 +00:00
} ,
}
2015-09-04 20:58:12 +00:00
t . Backend . Setup ( config )
2015-03-18 20:19:19 +00:00
return t , nil
}
// TokenEntry is used to represent a given token
type TokenEntry struct {
2016-01-04 21:43:07 +00:00
ID string // ID of this entry, generally a random UUID
2016-03-09 11:23:31 +00:00
Accessor string // Accessor ID for this token, a random UUID
2016-01-04 21:43:07 +00:00
Parent string // Parent token, used for revocation trees
Policies [ ] string // Which named policies should be used
Path string // Used for audit trails, this is something like "auth/user/login"
Meta map [ string ] string // Used for auditing. This could include things like "source", "user", "ip"
DisplayName string // Used for operators to be able to associate with the source
NumUses int // Used to restrict the number of uses (zero is unlimited). This is to support one-time-tokens (generalized).
CreationTime int64 // Time of token creation
TTL time . Duration // Duration set when token was created
2015-03-18 20:19:19 +00:00
}
2015-04-03 18:40:08 +00:00
// SetExpirationManager is used to provide the token store with
// an expiration manager. This is used to manage prefix based revocation
// of tokens and to cleanup entries when removed from the token store.
2015-09-18 20:33:52 +00:00
func ( ts * TokenStore ) SetExpirationManager ( exp * ExpirationManager ) {
ts . expiration = exp
2015-04-03 18:40:08 +00:00
}
2015-04-03 00:39:38 +00:00
// SaltID is used to apply a salt and hash to an ID to make sure its not reversable
func ( ts * TokenStore ) SaltID ( id string ) string {
2015-06-30 21:08:21 +00:00
return ts . salt . SaltID ( id )
2015-03-18 20:19:19 +00:00
}
2015-03-24 00:16:37 +00:00
// RootToken is used to generate a new token with root privileges and no parent
2015-10-30 14:59:26 +00:00
func ( ts * TokenStore ) rootToken ( ) ( * TokenEntry , error ) {
2015-03-24 00:16:37 +00:00
te := & TokenEntry {
2015-09-18 20:33:52 +00:00
Policies : [ ] string { "root" } ,
Path : "auth/token/root" ,
DisplayName : "root" ,
CreationTime : time . Now ( ) . Unix ( ) ,
2015-03-24 00:16:37 +00:00
}
2015-10-30 14:59:26 +00:00
if err := ts . create ( te ) ; err != nil {
2015-03-24 00:16:37 +00:00
return nil , err
}
return te , nil
}
2016-03-09 11:23:31 +00:00
// CreateAccessor is used to create an identifier for the token ID.
2016-03-09 03:27:24 +00:00
// An storage index, mapping the accessor ID to the token ID is also created.
2016-03-09 11:23:31 +00:00
func ( ts * TokenStore ) createAccessor ( entry * TokenEntry ) error {
defer metrics . MeasureSince ( [ ] string { "token" , "createAccessor" } , time . Now ( ) )
2016-03-08 17:51:38 +00:00
// Create a random accessor ID
accessorUUID , err := uuid . GenerateUUID ( )
if err != nil {
return err
}
2016-03-09 11:23:31 +00:00
entry . Accessor = accessorUUID
2016-03-08 18:12:53 +00:00
2016-03-08 20:13:29 +00:00
// Create index entry, mapping the Accessor ID to the Token ID
2016-03-09 11:23:31 +00:00
path := lookupPrefix + ts . SaltID ( entry . Accessor )
2016-03-08 18:12:53 +00:00
le := & logical . StorageEntry { Key : path , Value : [ ] byte ( entry . ID ) }
if err := ts . view . Put ( le ) ; err != nil {
2016-03-09 03:27:24 +00:00
return fmt . Errorf ( "failed to persist accessor ID index entry: %v" , err )
2016-03-08 18:12:53 +00:00
}
2016-03-08 17:51:38 +00:00
return nil
}
2015-03-18 20:19:19 +00:00
// Create is used to create a new token entry. The entry is assigned
2015-03-24 21:22:50 +00:00
// a newly generated ID if not provided.
2015-10-30 14:59:26 +00:00
func ( ts * TokenStore ) create ( entry * TokenEntry ) error {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "token" , "create" } , time . Now ( ) )
2015-03-24 21:22:50 +00:00
// Generate an ID if necessary
if entry . ID == "" {
2016-01-13 18:40:08 +00:00
entryUUID , err := uuid . GenerateUUID ( )
if err != nil {
return err
}
entry . ID = entryUUID
2015-03-24 21:22:50 +00:00
}
2015-12-30 19:30:02 +00:00
2016-03-09 11:23:31 +00:00
err := ts . createAccessor ( entry )
2016-03-08 17:51:38 +00:00
if err != nil {
return err
}
2015-12-30 19:30:02 +00:00
return ts . storeCommon ( entry , true )
}
2016-01-04 21:43:07 +00:00
// Store is used to store an updated token entry without writing the
// secondary index.
2015-12-30 19:30:02 +00:00
func ( ts * TokenStore ) store ( entry * TokenEntry ) error {
defer metrics . MeasureSince ( [ ] string { "token" , "store" } , time . Now ( ) )
return ts . storeCommon ( entry , false )
}
// storeCommon handles the actual storage of an entry, possibly generating
// secondary indexes
func ( ts * TokenStore ) storeCommon ( entry * TokenEntry , writeSecondary bool ) error {
2015-04-03 00:39:38 +00:00
saltedId := ts . SaltID ( entry . ID )
2015-03-24 21:22:50 +00:00
// Marshal the entry
2015-03-18 20:19:19 +00:00
enc , err := json . Marshal ( entry )
if err != nil {
return fmt . Errorf ( "failed to encode entry: %v" , err )
}
2015-12-30 19:30:02 +00:00
if writeSecondary {
// Write the secondary index if necessary. This is done before the
// primary index because we'd rather have a dangling pointer with
// a missing primary instead of missing the parent index and potentially
// escaping the revocation chain.
if entry . Parent != "" {
// Ensure the parent exists
parent , err := ts . Lookup ( entry . Parent )
if err != nil {
return fmt . Errorf ( "failed to lookup parent: %v" , err )
}
if parent == nil {
return fmt . Errorf ( "parent token not found" )
}
2015-03-18 20:19:19 +00:00
2015-12-30 19:30:02 +00:00
// Create the index entry
path := parentPrefix + ts . SaltID ( entry . Parent ) + "/" + saltedId
le := & logical . StorageEntry { Key : path }
if err := ts . view . Put ( le ) ; err != nil {
return fmt . Errorf ( "failed to persist entry: %v" , err )
}
2015-03-18 20:19:19 +00:00
}
}
// Write the primary ID
path := lookupPrefix + saltedId
le := & logical . StorageEntry { Key : path , Value : enc }
if err := ts . view . Put ( le ) ; err != nil {
return fmt . Errorf ( "failed to persist entry: %v" , err )
}
return nil
}
2015-04-17 18:51:04 +00:00
// UseToken is used to manage restricted use tokens and decrement
// their available uses.
func ( ts * TokenStore ) UseToken ( te * TokenEntry ) error {
// If the token is not restricted, there is nothing to do
if te . NumUses == 0 {
return nil
}
// Decrement the count
te . NumUses -= 1
// Revoke the token if there are no remaining uses.
// XXX: There is a race condition here with parallel
// requests using the same token. This would require
// some global coordination to avoid, as we must ensure
// no requests using the same restricted token are handled
// in parallel.
if te . NumUses == 0 {
return ts . Revoke ( te . ID )
}
// Marshal the entry
enc , err := json . Marshal ( te )
if err != nil {
return fmt . Errorf ( "failed to encode entry: %v" , err )
}
// Write under the primary ID
saltedId := ts . SaltID ( te . ID )
path := lookupPrefix + saltedId
le := & logical . StorageEntry { Key : path , Value : enc }
if err := ts . view . Put ( le ) ; err != nil {
return fmt . Errorf ( "failed to persist entry: %v" , err )
}
return nil
}
2015-03-18 20:19:19 +00:00
// Lookup is used to find a token given its ID
func ( ts * TokenStore ) Lookup ( id string ) ( * TokenEntry , error ) {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "token" , "lookup" } , time . Now ( ) )
2015-03-18 20:21:16 +00:00
if id == "" {
return nil , fmt . Errorf ( "cannot lookup blank token" )
}
2015-04-03 00:39:38 +00:00
return ts . lookupSalted ( ts . SaltID ( id ) )
2015-03-18 20:19:19 +00:00
}
// lookupSlated is used to find a token given its salted ID
func ( ts * TokenStore ) lookupSalted ( saltedId string ) ( * TokenEntry , error ) {
// Lookup token
path := lookupPrefix + saltedId
raw , err := ts . view . Get ( path )
if err != nil {
return nil , fmt . Errorf ( "failed to read entry: %v" , err )
}
// Bail if not found
if raw == nil {
return nil , nil
}
// Unmarshal the token
entry := new ( TokenEntry )
if err := json . Unmarshal ( raw . Value , entry ) ; err != nil {
return nil , fmt . Errorf ( "failed to decode entry: %v" , err )
}
return entry , nil
}
// Revoke is used to invalidate a given token, any child tokens
// will be orphaned.
func ( ts * TokenStore ) Revoke ( id string ) error {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "token" , "revoke" } , time . Now ( ) )
2015-03-18 20:21:16 +00:00
if id == "" {
return fmt . Errorf ( "cannot revoke blank token" )
}
2015-09-10 01:58:09 +00:00
2015-04-03 00:39:38 +00:00
return ts . revokeSalted ( ts . SaltID ( id ) )
2015-03-18 20:19:19 +00:00
}
// revokeSalted is used to invalidate a given salted token,
// any child tokens will be orphaned.
func ( ts * TokenStore ) revokeSalted ( saltedId string ) error {
// Lookup the token first
entry , err := ts . lookupSalted ( saltedId )
if err != nil {
return err
}
// Nuke the primary key first
path := lookupPrefix + saltedId
if ts . view . Delete ( path ) ; err != nil {
return fmt . Errorf ( "failed to delete entry: %v" , err )
}
// Clear the secondary index if any
if entry != nil && entry . Parent != "" {
2015-04-03 00:39:38 +00:00
path := parentPrefix + ts . SaltID ( entry . Parent ) + "/" + saltedId
2015-03-18 20:19:19 +00:00
if ts . view . Delete ( path ) ; err != nil {
return fmt . Errorf ( "failed to delete entry: %v" , err )
}
}
2015-04-10 22:12:04 +00:00
2016-03-08 19:04:20 +00:00
// Clear the accessor ID index if any
2016-03-09 11:23:31 +00:00
if entry != nil && entry . Accessor != "" {
path := lookupPrefix + ts . SaltID ( entry . Accessor )
2016-03-08 19:04:20 +00:00
if ts . view . Delete ( path ) ; err != nil {
return fmt . Errorf ( "failed to delete entry: %v" , err )
}
}
2015-04-10 22:12:04 +00:00
// Revoke all secrets under this token
if entry != nil {
if err := ts . expiration . RevokeByToken ( entry . ID ) ; err != nil {
return err
}
}
2015-09-10 01:58:09 +00:00
// Destroy the cubby space
2015-09-15 15:28:07 +00:00
err = ts . destroyCubbyhole ( saltedId )
2015-09-10 01:58:09 +00:00
if err != nil {
return err
}
2015-03-18 20:19:19 +00:00
return nil
}
// RevokeTree is used to invalide a given token and all
// child tokens.
func ( ts * TokenStore ) RevokeTree ( id string ) error {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "token" , "revoke-tree" } , time . Now ( ) )
2015-03-18 20:21:16 +00:00
// Verify the token is not blank
if id == "" {
return fmt . Errorf ( "cannot revoke blank token" )
}
2015-03-18 20:19:19 +00:00
// Get the salted ID
2015-04-03 00:39:38 +00:00
saltedId := ts . SaltID ( id )
2015-03-18 20:19:19 +00:00
2015-04-10 22:06:54 +00:00
// Nuke the entire tree recursively
2015-03-18 20:19:19 +00:00
if err := ts . revokeTreeSalted ( saltedId ) ; err != nil {
return err
}
return nil
}
// revokeTreeSalted is used to invalide a given token and all
// child tokens using a saltedID.
func ( ts * TokenStore ) revokeTreeSalted ( saltedId string ) error {
// Scan for child tokens
path := parentPrefix + saltedId + "/"
children , err := ts . view . List ( path )
if err != nil {
return fmt . Errorf ( "failed to scan for children: %v" , err )
}
// Recursively nuke the children. The subtle nuance here is that
// we don't have the acutal ID of the child, but we have the salted
// value. Turns out, this is good enough!
for _ , child := range children {
if err := ts . revokeTreeSalted ( child ) ; err != nil {
2015-04-10 22:06:54 +00:00
return err
2015-03-18 20:19:19 +00:00
}
}
2015-04-10 22:06:54 +00:00
// Revoke this entry
if err := ts . revokeSalted ( saltedId ) ; err != nil {
return fmt . Errorf ( "failed to revoke entry: %v" , err )
2015-03-18 20:19:19 +00:00
}
return nil
}
2016-03-09 11:23:31 +00:00
func ( ts * TokenStore ) lookupByAccessor ( accessor string ) ( string , error ) {
entry , err := ts . view . Get ( lookupPrefix + ts . SaltID ( accessor ) )
2016-03-08 23:07:27 +00:00
if err != nil {
return "" , fmt . Errorf ( "failed to read index using accessor ID: %s" , err )
}
if entry == nil {
2016-03-09 03:27:24 +00:00
return "" , & StatusBadRequest { Err : "invalid accessor ID" }
2016-03-08 23:07:27 +00:00
}
return string ( entry . Value ) , nil
}
2016-03-08 20:13:29 +00:00
// handleLookupAccessor handles the auth/token/lookup-accessor path for returning
// the properties of the token associated with the accessor ID
2016-03-08 22:38:19 +00:00
func ( ts * TokenStore ) handleLookupAccessor ( req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2016-03-09 11:23:31 +00:00
accessor := data . Get ( "accessor" ) . ( string )
if accessor == "" {
return nil , & StatusBadRequest { Err : "missing accessor" }
2016-03-08 22:38:19 +00:00
}
2016-03-09 11:23:31 +00:00
tokenID , err := ts . lookupByAccessor ( accessor )
2016-03-08 22:38:19 +00:00
if err != nil {
2016-03-08 23:07:27 +00:00
return nil , err
2016-03-08 22:38:19 +00:00
}
// Prepare the field data required for a lookup call
d := & framework . FieldData {
Raw : map [ string ] interface { } {
2016-03-08 23:07:27 +00:00
"token" : tokenID ,
2016-03-08 22:38:19 +00:00
} ,
Schema : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to lookup" ,
} ,
} ,
}
resp , err := ts . handleLookup ( req , d )
if err != nil {
return nil , err
}
if resp == nil {
return nil , fmt . Errorf ( "failed to lookup the token" )
}
if resp . IsError ( ) {
return resp , nil
}
// Remove the token ID from the response
if resp . Data != nil && resp . Data [ "id" ] != "" {
resp . Data [ "id" ] = ""
}
return resp , nil
2016-03-08 20:13:29 +00:00
}
// handleRevokeAccessor handles the auth/token/revoke-accessor path for revoking
// the token associated with the accessor ID
2016-03-08 22:38:19 +00:00
func ( ts * TokenStore ) handleRevokeAccessor ( req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2016-03-09 11:23:31 +00:00
accessor := data . Get ( "accessor" ) . ( string )
if accessor == "" {
return nil , & StatusBadRequest { Err : "missing accessor" }
2016-03-08 23:07:27 +00:00
}
2016-03-09 11:23:31 +00:00
tokenID , err := ts . lookupByAccessor ( accessor )
2016-03-08 23:07:27 +00:00
if err != nil {
return nil , err
}
// Revoke the token and its children
if err := ts . RevokeTree ( tokenID ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
2016-03-08 20:13:29 +00:00
return nil , nil
}
2015-11-03 20:10:46 +00:00
// handleCreate handles the auth/token/create path for creation of new orphan
// tokens
func ( ts * TokenStore ) handleCreateOrphan (
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
return ts . handleCreateCommon ( req , d , true )
}
// handleCreate handles the auth/token/create path for creation of new non-orphan
// tokens
2015-03-31 19:48:19 +00:00
func ( ts * TokenStore ) handleCreate (
2015-04-07 21:16:35 +00:00
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2015-11-03 20:10:46 +00:00
return ts . handleCreateCommon ( req , d , false )
}
// handleCreateCommon handles the auth/token/create path for creation of new tokens
func ( ts * TokenStore ) handleCreateCommon (
req * logical . Request , d * framework . FieldData , orphan bool ) ( * logical . Response , error ) {
2015-03-24 22:10:46 +00:00
// Read the parent policy
parent , err := ts . Lookup ( req . ClientToken )
if err != nil || parent == nil {
return logical . ErrorResponse ( "parent token lookup failed" ) , logical . ErrInvalidRequest
}
2015-04-17 18:34:25 +00:00
// A token with a restricted number of uses cannot create a new token
// otherwise it could escape the restriction count.
if parent . NumUses > 0 {
return logical . ErrorResponse ( "restricted use token cannot generate child tokens" ) ,
logical . ErrInvalidRequest
}
2015-09-21 14:04:03 +00:00
// Check if the client token has sudo/root privileges for the requested path
isSudo := ts . System ( ) . SudoPrivilege ( req . MountPoint + req . Path , req . ClientToken )
2015-03-24 22:10:46 +00:00
// Read and parse the fields
2015-04-07 21:16:35 +00:00
var data struct {
2015-11-09 22:30:50 +00:00
ID string
Policies [ ] string
Metadata map [ string ] string ` mapstructure:"meta" `
NoParent bool ` mapstructure:"no_parent" `
NoDefaultPolicy bool ` mapstructure:"no_default_policy" `
Lease string
TTL string
DisplayName string ` mapstructure:"display_name" `
NumUses int ` mapstructure:"num_uses" `
2015-04-07 21:16:35 +00:00
}
if err := mapstructure . WeakDecode ( req . Data , & data ) ; err != nil {
return logical . ErrorResponse ( fmt . Sprintf (
"Error decoding request: %s" , err ) ) , logical . ErrInvalidRequest
}
2015-03-24 22:10:46 +00:00
2015-04-17 18:34:25 +00:00
// Verify the number of uses is positive
if data . NumUses < 0 {
return logical . ErrorResponse ( "number of uses cannot be negative" ) ,
logical . ErrInvalidRequest
}
2015-03-24 22:10:46 +00:00
// Setup the token entry
te := TokenEntry {
2015-09-18 20:33:52 +00:00
Parent : req . ClientToken ,
Path : "auth/token/create" ,
Meta : data . Metadata ,
DisplayName : "token" ,
NumUses : data . NumUses ,
CreationTime : time . Now ( ) . Unix ( ) ,
2015-04-15 21:24:07 +00:00
}
// Attach the given display name if any
if data . DisplayName != "" {
full := "token-" + data . DisplayName
full = displayNameSanitize . ReplaceAllString ( full , "-" )
full = strings . TrimSuffix ( full , "-" )
te . DisplayName = full
2015-03-24 22:10:46 +00:00
}
2015-09-18 23:59:06 +00:00
// Allow specifying the ID of the token if the client has root or sudo privileges
2015-04-07 21:16:35 +00:00
if data . ID != "" {
2015-09-18 23:59:06 +00:00
if ! isSudo {
return logical . ErrorResponse ( "root or sudo privileges required to specify token id" ) ,
2015-03-24 22:10:46 +00:00
logical . ErrInvalidRequest
}
2015-04-07 21:16:35 +00:00
te . ID = data . ID
2015-03-24 22:10:46 +00:00
}
2015-09-18 23:59:06 +00:00
// Only permit policies to be a subset unless the client has root or sudo privileges
2015-04-07 21:19:52 +00:00
if len ( data . Policies ) == 0 {
data . Policies = parent . Policies
2015-03-24 22:10:46 +00:00
}
2015-09-18 23:59:06 +00:00
if ! isSudo && ! strListSubset ( parent . Policies , data . Policies ) {
2015-04-07 21:19:52 +00:00
return logical . ErrorResponse ( "child policies must be subset of parent" ) , logical . ErrInvalidRequest
2015-03-24 22:10:46 +00:00
}
2015-12-30 20:18:30 +00:00
// Use a map to filter out/prevent duplicates
policyMap := map [ string ] bool { }
for _ , policy := range data . Policies {
2016-01-09 02:23:27 +00:00
if policy == "" {
// Don't allow a policy with no name, even though it is a valid
// slice member
continue
}
2015-12-30 20:18:30 +00:00
policyMap [ policy ] = true
}
if ! policyMap [ "root" ] &&
! data . NoDefaultPolicy {
policyMap [ "default" ] = true
}
for k , _ := range policyMap {
te . Policies = append ( te . Policies , k )
2015-11-06 22:27:15 +00:00
}
2015-12-30 20:18:30 +00:00
sort . Strings ( te . Policies )
2015-03-24 22:10:46 +00:00
2015-09-18 23:59:06 +00:00
// Only allow an orphan token if the client has sudo policy
2015-04-07 21:16:35 +00:00
if data . NoParent {
2015-09-18 23:59:06 +00:00
if ! isSudo {
return logical . ErrorResponse ( "root or sudo privileges required to create orphan token" ) ,
2015-03-24 22:10:46 +00:00
logical . ErrInvalidRequest
}
2015-04-07 21:16:35 +00:00
te . Parent = ""
2015-11-03 20:10:46 +00:00
} else {
// This comes from create-orphan, which can be properly ACLd
if orphan {
te . Parent = ""
}
2015-03-24 22:10:46 +00:00
}
2015-09-25 13:46:20 +00:00
// Parse the TTL/lease if any
if data . TTL != "" {
dur , err := time . ParseDuration ( data . TTL )
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
if dur < 0 {
return logical . ErrorResponse ( "ttl must be positive" ) , logical . ErrInvalidRequest
}
te . TTL = dur
} else if data . Lease != "" {
2015-04-07 21:16:35 +00:00
dur , err := time . ParseDuration ( data . Lease )
2015-03-24 22:10:46 +00:00
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
if dur < 0 {
return logical . ErrorResponse ( "lease must be positive" ) , logical . ErrInvalidRequest
}
2015-09-18 20:33:52 +00:00
te . TTL = dur
}
sysView := ts . System ( )
// Set the default lease if non-provided, root tokens are exempt
if te . TTL == 0 && ! strListContains ( te . Policies , "root" ) {
te . TTL = sysView . DefaultLeaseTTL ( )
}
// Limit the lease duration
if te . TTL > sysView . MaxLeaseTTL ( ) {
te . TTL = sysView . MaxLeaseTTL ( )
2015-03-24 22:10:46 +00:00
}
// Create the token
2015-10-30 14:59:26 +00:00
if err := ts . create ( & te ) ; err != nil {
2015-03-24 22:10:46 +00:00
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
// Generate the response
resp := & logical . Response {
2015-03-31 03:26:39 +00:00
Auth : & logical . Auth {
2015-04-26 03:21:35 +00:00
DisplayName : te . DisplayName ,
Policies : te . Policies ,
Metadata : te . Meta ,
2015-04-09 19:14:04 +00:00
LeaseOptions : logical . LeaseOptions {
2016-01-29 22:44:09 +00:00
TTL : te . TTL ,
Renewable : true ,
2015-04-09 19:14:04 +00:00
} ,
ClientToken : te . ID ,
2016-03-09 11:23:31 +00:00
Accessor : te . Accessor ,
2015-03-24 22:10:46 +00:00
} ,
}
2015-03-31 03:26:39 +00:00
2015-10-07 19:30:54 +00:00
if ts . policyLookupFunc != nil {
2015-11-06 16:36:40 +00:00
for _ , p := range te . Policies {
policy , err := ts . policyLookupFunc ( p )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "could not look up policy %s" , p ) ) , nil
2015-10-07 19:30:54 +00:00
}
2015-11-06 16:36:40 +00:00
if policy == nil {
resp . AddWarning ( fmt . Sprintf ( "policy \"%s\" does not exist" , p ) )
2015-10-07 19:30:54 +00:00
}
}
}
2015-03-24 22:10:46 +00:00
return resp , nil
}
2015-09-17 17:22:30 +00:00
// handleRevokeSelf handles the auth/token/revoke-self path for revocation of tokens
// in a way that revokes all child tokens. Normally, using sys/revoke/leaseID will revoke
// the token and all children anyways, but that is only available when there is a lease.
func ( ts * TokenStore ) handleRevokeSelf (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
// Revoke the token and its children
if err := ts . RevokeTree ( req . ClientToken ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
return nil , nil
}
2015-03-24 22:30:09 +00:00
// handleRevokeTree handles the auth/token/revoke/id path for revocation of tokens
2015-04-08 20:35:32 +00:00
// in a way that revokes all child tokens. Normally, using sys/revoke/leaseID will revoke
2015-03-24 22:30:09 +00:00
// the token and all children anyways, but that is only available when there is a lease.
2015-03-31 19:48:19 +00:00
func ( ts * TokenStore ) handleRevokeTree (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
id := data . Get ( "token" ) . ( string )
2015-03-24 22:30:09 +00:00
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
// Revoke the token and its children
if err := ts . RevokeTree ( id ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
return nil , nil
}
2015-03-24 22:10:46 +00:00
// handleRevokeOrphan handles the auth/token/revoke-orphan/id path for revocation of tokens
2015-04-08 20:35:32 +00:00
// in a way that leaves child tokens orphaned. Normally, using sys/revoke/leaseID will revoke
2015-03-24 22:10:46 +00:00
// the token and all children.
2015-03-31 19:48:19 +00:00
func ( ts * TokenStore ) handleRevokeOrphan (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2015-03-24 22:30:09 +00:00
// Parse the id
2015-03-31 19:48:19 +00:00
id := data . Get ( "token" ) . ( string )
2015-03-24 22:30:09 +00:00
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
2015-09-16 13:22:15 +00:00
parent , err := ts . Lookup ( req . ClientToken )
if err != nil {
return logical . ErrorResponse ( fmt . Sprintf ( "parent token lookup failed: %s" , err . Error ( ) ) ) , logical . ErrInvalidRequest
}
if parent == nil {
return logical . ErrorResponse ( "parent token lookup failed" ) , logical . ErrInvalidRequest
}
2015-09-21 14:04:03 +00:00
// Check if the client token has sudo/root privileges for the requested path
isSudo := ts . System ( ) . SudoPrivilege ( req . MountPoint + req . Path , req . ClientToken )
2015-09-16 13:22:15 +00:00
2015-09-18 23:59:06 +00:00
if ! isSudo {
return logical . ErrorResponse ( "root or sudo privileges required to revoke and orphan" ) ,
2015-09-16 13:22:15 +00:00
logical . ErrInvalidRequest
}
2015-03-24 22:30:09 +00:00
// Revoke and orphan
if err := ts . Revoke ( id ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
2015-03-20 20:54:57 +00:00
return nil , nil
}
2015-03-24 22:10:46 +00:00
2015-04-03 18:40:08 +00:00
// handleRevokePrefix handles the auth/token/revoke-prefix/path for revocation of tokens
// generated by a given path.
func ( ts * TokenStore ) handleRevokePrefix (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
// Parse the prefix
prefix := data . Get ( "prefix" ) . ( string )
if prefix == "" {
return logical . ErrorResponse ( "missing source prefix" ) , logical . ErrInvalidRequest
}
// Revoke using the prefix
if err := ts . expiration . RevokePrefix ( prefix ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
return nil , nil
}
2015-03-24 22:39:33 +00:00
// handleLookup handles the auth/token/lookup/id path for querying information about
// a particular token. This can be used to see which policies are applicable.
2015-03-31 19:48:19 +00:00
func ( ts * TokenStore ) handleLookup (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
id := data . Get ( "token" ) . ( string )
2015-03-31 19:50:07 +00:00
if id == "" {
id = req . ClientToken
}
2015-03-24 22:39:33 +00:00
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
// Lookup the token
out , err := ts . Lookup ( id )
2015-06-19 01:30:18 +00:00
2015-03-24 22:39:33 +00:00
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
if out == nil {
2015-06-19 01:30:18 +00:00
return logical . ErrorResponse ( "bad token" ) , logical . ErrPermissionDenied
2015-03-24 22:39:33 +00:00
}
// Generate a response. We purposely omit the parent reference otherwise
2015-10-02 17:33:19 +00:00
// you could escalate your privileges.
2015-03-24 22:39:33 +00:00
resp := & logical . Response {
Data : map [ string ] interface { } {
2016-01-04 21:43:07 +00:00
"id" : out . ID ,
"policies" : out . Policies ,
"path" : out . Path ,
"meta" : out . Meta ,
"display_name" : out . DisplayName ,
"num_uses" : out . NumUses ,
"orphan" : false ,
2016-01-04 22:11:22 +00:00
"creation_time" : int64 ( out . CreationTime ) ,
2016-02-01 16:16:32 +00:00
"creation_ttl" : int64 ( out . TTL . Seconds ( ) ) ,
"ttl" : int64 ( 0 ) ,
2015-03-24 22:39:33 +00:00
} ,
}
2015-11-09 18:19:59 +00:00
if out . Parent == "" {
resp . Data [ "orphan" ] = true
}
2016-01-04 21:43:07 +00:00
// Fetch the last renewal time
leaseTimes , err := ts . expiration . FetchLeaseTimesByToken ( out . Path , out . ID )
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
2016-02-01 16:16:32 +00:00
if leaseTimes != nil {
if ! leaseTimes . LastRenewalTime . IsZero ( ) {
resp . Data [ "last_renewal_time" ] = leaseTimes . LastRenewalTime . Unix ( )
}
if ! leaseTimes . ExpireTime . IsZero ( ) {
resp . Data [ "ttl" ] = int64 ( leaseTimes . ExpireTime . Sub ( time . Now ( ) . Round ( time . Second ) ) . Seconds ( ) )
}
2016-01-04 21:43:07 +00:00
}
2015-03-24 22:39:33 +00:00
return resp , nil
}
2015-10-07 16:49:13 +00:00
func ( ts * TokenStore ) handleRenewSelf (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
data . Raw [ "token" ] = req . ClientToken
return ts . handleRenew ( req , data )
}
2015-04-03 19:11:49 +00:00
// handleRenew handles the auth/token/renew/id path for renewal of tokens.
// This is used to prevent token expiration and revocation.
func ( ts * TokenStore ) handleRenew (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
id := data . Get ( "token" ) . ( string )
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
2015-04-09 21:23:37 +00:00
incrementRaw := data . Get ( "increment" ) . ( int )
// Convert the increment
increment := time . Duration ( incrementRaw ) * time . Second
2015-04-03 19:11:49 +00:00
// Lookup the token
2015-12-30 19:30:02 +00:00
te , err := ts . Lookup ( id )
2015-04-03 19:11:49 +00:00
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
// Verify the token exists
2015-12-30 19:30:02 +00:00
if te == nil {
2015-04-03 19:11:49 +00:00
return logical . ErrorResponse ( "token not found" ) , logical . ErrInvalidRequest
}
2015-10-09 21:11:31 +00:00
// Renew the token and its children
2016-03-04 19:56:51 +00:00
return ts . expiration . RenewToken ( req , te . Path , te . ID , increment )
2015-04-03 19:11:49 +00:00
}
2015-09-15 15:28:07 +00:00
func ( ts * TokenStore ) destroyCubbyhole ( saltedID string ) error {
2015-09-15 17:49:53 +00:00
if ts . cubbyholeBackend == nil {
2015-09-15 15:28:07 +00:00
// Should only ever happen in testing
return nil
}
2015-09-15 17:49:53 +00:00
return ts . cubbyholeBackend . revoke ( salt . SaltID ( ts . cubbyholeBackend . saltUUID , saltedID , salt . SHA1Hash ) )
2015-09-15 15:28:07 +00:00
}
2016-01-29 22:44:09 +00:00
func ( ts * TokenStore ) authRenew (
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
f := framework . LeaseExtend ( 0 , 0 , ts . System ( ) )
return f ( req , d )
}
2015-03-24 22:10:46 +00:00
const (
tokenBackendHelp = ` The token credential backend is always enabled and builtin to Vault .
Client tokens are used to identify a client and to allow Vault to associate policies and ACLs
which are enforced on every request . This backend also allows for generating sub - tokens as well
2015-09-12 01:08:32 +00:00
as revocation of tokens . The tokens are renewable if associated with a lease . `
2016-03-08 20:13:29 +00:00
tokenCreateHelp = ` The token create path is used to create new tokens. `
tokenCreateOrphanHelp = ` The token create path is used to create new orphan tokens. `
tokenLookupHelp = ` This endpoint will lookup a token and its properties. `
2016-03-09 03:27:24 +00:00
tokenLookupAccessorHelp = ` This endpoint will lookup a token associated with the given accessor ID and its properties. Response will not contain the token ID. `
2016-03-08 20:13:29 +00:00
tokenRevokeHelp = ` This endpoint will delete the given token and all of its child tokens. `
tokenRevokeSelfHelp = ` This endpoint will delete the token used to call it and all of its child tokens. `
2016-03-09 03:27:24 +00:00
tokenRevokeAccessorHelp = ` This endpoint will delete the token associated with the accessor ID and all of its child tokens. `
2016-03-08 20:13:29 +00:00
tokenRevokeOrphanHelp = ` This endpoint will delete the token and orphan its child tokens. `
tokenRevokePrefixHelp = ` This endpoint will delete all tokens generated under a prefix with their child tokens. `
tokenRenewHelp = ` This endpoint will renew the given token and prevent expiration. `
tokenRenewSelfHelp = ` This endpoint will renew the token used to call it and prevent expiration. `
2015-03-24 22:10:46 +00:00
)