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"
2016-02-29 18:27:31 +00:00
"github.com/fatih/structs"
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"
2016-04-06 00:30:38 +00:00
"github.com/hashicorp/vault/helper/strutil"
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/"
2016-03-09 14:05:04 +00:00
// accessorPrefix is the prefix used to store the index from
// Accessor to Token ID
accessorPrefix = "accessor/"
2015-03-18 20:19:19 +00:00
// 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/"
2016-03-01 17:33:35 +00:00
// rolesPrefix is the prefix used to store role information
rolesPrefix = "roles/"
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-]" )
2016-02-29 18:27:31 +00:00
2016-03-01 20:30:37 +00:00
// pathSuffixSanitize is used to ensure a path suffix in a role is valid.
pathSuffixSanitize = regexp . MustCompile ( "\\w[\\w-.]+\\w" )
2015-04-15 21:24:07 +00:00
)
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 {
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 {
2016-02-29 18:27:31 +00:00
Pattern : "roles/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : t . tokenStoreRoleList ,
} ,
2016-03-01 18:02:40 +00:00
HelpSynopsis : tokenListRolesHelp ,
HelpDescription : tokenListRolesHelp ,
2016-02-29 18:27:31 +00:00
} ,
& framework . Path {
Pattern : "roles/" + framework . GenericNameRegex ( "role_name" ) ,
Fields : map [ string ] * framework . FieldSchema {
"role_name" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Name of the role" ,
} ,
"allowed_policies" : & framework . FieldSchema {
2016-03-03 16:04:05 +00:00
Type : framework . TypeString ,
Default : "" ,
Description : tokenAllowedPoliciesHelp ,
2016-02-29 18:27:31 +00:00
} ,
"orphan" : & framework . FieldSchema {
2016-03-07 15:07:04 +00:00
Type : framework . TypeBool ,
Default : false ,
Description : tokenOrphanHelp ,
2016-02-29 18:27:31 +00:00
} ,
"period" : & framework . FieldSchema {
2016-03-03 16:04:05 +00:00
Type : framework . TypeDurationSecond ,
Default : 0 ,
Description : tokenPeriodHelp ,
2016-02-29 18:27:31 +00:00
} ,
2016-03-01 20:30:37 +00:00
"path_suffix" : & framework . FieldSchema {
2016-03-03 16:04:05 +00:00
Type : framework . TypeString ,
Default : "" ,
Description : tokenPathSuffixHelp + pathSuffixSanitize . String ( ) ,
2016-02-29 18:27:31 +00:00
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : t . tokenStoreRoleRead ,
2016-03-09 16:59:54 +00:00
logical . CreateOperation : t . tokenStoreRoleCreateUpdate ,
logical . UpdateOperation : t . tokenStoreRoleCreateUpdate ,
2016-02-29 18:27:31 +00:00
logical . DeleteOperation : t . tokenStoreRoleDelete ,
} ,
2016-03-09 16:59:54 +00:00
ExistenceCheck : t . tokenStoreRoleExistenceCheck ,
2016-03-01 18:02:40 +00:00
HelpSynopsis : tokenPathRolesHelp ,
HelpDescription : tokenPathRolesHelp ,
2016-02-29 18:27:31 +00:00
} ,
& framework . Path {
2016-02-29 19:56:04 +00:00
Pattern : "create-orphan$" ,
2015-11-03 20:10:46 +00:00
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 ) ,
} ,
2016-02-29 18:27:31 +00:00
& framework . Path {
Pattern : "create/" + framework . GenericNameRegex ( "role_name" ) ,
Fields : map [ string ] * framework . FieldSchema {
"role_name" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Name of the role" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-03-01 20:30:37 +00:00
logical . UpdateOperation : t . handleCreateAgainstRole ,
2016-02-29 18:27:31 +00:00
} ,
2016-03-01 18:02:40 +00:00
HelpSynopsis : strings . TrimSpace ( tokenCreateRoleHelp ) ,
HelpDescription : strings . TrimSpace ( tokenCreateRoleHelp ) ,
2016-02-29 18:27:31 +00:00
} ,
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 {
2016-03-14 23:14:36 +00:00
Pattern : "lookup" + framework . OptionalParamRegex ( "token" ) ,
2015-03-31 19:51:00 +00:00
Fields : map [ string ] * framework . FieldSchema {
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to lookup" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-03-14 22:56:00 +00:00
logical . ReadOperation : t . handleLookup ,
logical . UpdateOperation : t . handleLookup ,
2015-03-31 19:51:00 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenLookupHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupHelp ) ,
} ,
2016-03-08 20:13:29 +00:00
& framework . Path {
2016-03-14 23:14:36 +00:00
Pattern : "lookup-accessor" + framework . OptionalParamRegex ( "accessor" ) ,
2016-03-08 20:13:29 +00:00
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 ,
2016-03-09 14:31:09 +00:00
Description : "Accessor of the token to lookup" ,
2016-03-08 20:13:29 +00:00
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-03-09 14:48:32 +00:00
logical . UpdateOperation : t . handleUpdateLookupAccessor ,
2016-03-08 20:13:29 +00:00
} ,
2016-03-09 22:23:34 +00:00
HelpSynopsis : strings . TrimSpace ( tokenLookupAccessorHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupAccessorHelp ) ,
2016-03-08 20:13:29 +00:00
} ,
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 {
2016-03-14 23:14:36 +00:00
Pattern : "revoke-accessor" + framework . OptionalParamRegex ( "accessor" ) ,
2016-03-08 20:13:29 +00:00
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 14:05:04 +00:00
Description : "Accessor 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 {
2016-03-09 14:48:32 +00:00
logical . UpdateOperation : t . handleUpdateRevokeAccessor ,
2016-03-08 20:13:29 +00:00
} ,
2016-03-09 22:23:34 +00:00
HelpSynopsis : strings . TrimSpace ( tokenRevokeAccessorHelp ) ,
HelpDescription : strings . TrimSpace ( tokenRevokeAccessorHelp ) ,
2016-03-08 20:13:29 +00:00
} ,
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 {
2016-03-14 23:14:36 +00:00
Pattern : "revoke" + framework . OptionalParamRegex ( "token" ) ,
2015-03-31 19:48:19 +00:00
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 {
2016-03-14 23:14:36 +00:00
Pattern : "revoke-orphan" + framework . OptionalParamRegex ( "token" ) ,
2015-03-31 19:48:19 +00:00
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
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 ,
2016-03-01 17:33:35 +00:00
Default : 0 ,
2015-10-07 16:49:13 +00:00
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 {
2016-03-14 23:14:36 +00:00
Pattern : "renew" + framework . OptionalParamRegex ( "token" ) ,
2015-04-03 19:11:49 +00:00
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 ,
2016-03-01 17:33:35 +00:00
Default : 0 ,
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 14:05:04 +00:00
Accessor string // Accessor 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
2016-02-29 18:27:31 +00:00
Role string // If set, the role that was used for parameters at creation time
}
// tsRoleEntry contains token store role information
type tsRoleEntry struct {
// The name of the role. Embedded so it can be used for pathing
2016-02-29 19:13:09 +00:00
Name string ` json:"name" mapstructure:"name" structs:"name" `
2016-02-29 18:27:31 +00:00
// The policies that creation functions using this role can assign to a token,
// escaping or further locking down normal subset checking
2016-02-29 19:13:09 +00:00
AllowedPolicies [ ] string ` json:"allowed_policies" mapstructure:"allowed_policies" structs:"allowed_policies" `
2016-02-29 18:27:31 +00:00
// If true, tokens created using this role will be orphans
2016-02-29 19:13:09 +00:00
Orphan bool ` json:"orphan" mapstructure:"orphan" structs:"orphan" `
2016-02-29 18:27:31 +00:00
// If non-zero, tokens created using this role will be able to be renewed
// forever, but will have a fixed renewal period of this value
2016-02-29 19:13:09 +00:00
Period time . Duration ` json:"period" mapstructure:"period" structs:"period" `
2016-02-29 18:27:31 +00:00
2016-03-01 20:30:37 +00:00
// If set, a suffix will be set on the token path, making it easier to
// revoke using 'revoke-prefix'.
PathSuffix string ` json:"path_suffix" mapstructure:"path_suffix" structs:"path_suffix" `
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 14:31:09 +00:00
// createAccessor is used to create an identifier for the token ID.
2016-03-09 14:48:32 +00:00
// A storage index, mapping the accessor 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
2016-03-09 14:05:04 +00:00
// Create a random accessor
2016-03-08 17:51:38 +00:00
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-09 14:31:09 +00:00
// Create index entry, mapping the accessor to the token ID
2016-03-09 14:05:04 +00:00
path := accessorPrefix + 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 14:05:04 +00:00
return fmt . Errorf ( "failed to persist accessor 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
2016-04-25 19:39:04 +00:00
// their available uses. Note: this is potentially racy, but the simple
// solution of a global lock would be severely detrimental to performance. Also
// note the specific revoke case below.
2015-04-17 18:51:04 +00:00
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-09 14:05:04 +00:00
// Clear the accessor index if any
2016-03-09 11:23:31 +00:00
if entry != nil && entry . Accessor != "" {
2016-03-09 14:05:04 +00:00
path := accessorPrefix + 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 {
2016-03-31 19:10:25 +00:00
if err := ts . expiration . RevokeByToken ( entry ) ; err != nil {
2015-04-10 22:12:04 +00:00
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-01 20:30:37 +00:00
// handleCreateAgainstRole handles the auth/token/create path for a role
func ( ts * TokenStore ) handleCreateAgainstRole (
2016-02-29 18:27:31 +00:00
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
name := d . Get ( "role_name" ) . ( string )
2016-03-01 20:30:37 +00:00
roleEntry , err := ts . tokenStoreRole ( name )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
if roleEntry == nil {
return logical . ErrorResponse ( fmt . Sprintf ( "unknown role %s" , name ) ) , nil
}
return ts . handleCreateCommon ( req , d , false , roleEntry )
}
2016-03-09 11:23:31 +00:00
func ( ts * TokenStore ) lookupByAccessor ( accessor string ) ( string , error ) {
2016-03-09 14:05:04 +00:00
entry , err := ts . view . Get ( accessorPrefix + ts . SaltID ( accessor ) )
2016-03-08 23:07:27 +00:00
if err != nil {
2016-03-09 14:05:04 +00:00
return "" , fmt . Errorf ( "failed to read index using accessor: %s" , err )
2016-03-08 23:07:27 +00:00
}
if entry == nil {
2016-03-09 14:05:04 +00:00
return "" , & StatusBadRequest { Err : "invalid accessor" }
2016-03-08 23:07:27 +00:00
}
return string ( entry . Value ) , nil
}
2016-03-09 14:48:32 +00:00
// handleUpdateLookupAccessor handles the auth/token/lookup-accessor path for returning
2016-03-09 14:05:04 +00:00
// the properties of the token associated with the accessor
2016-03-09 14:48:32 +00:00
func ( ts * TokenStore ) handleUpdateLookupAccessor ( 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
2016-03-09 14:05:04 +00:00
if resp . Data != nil {
2016-03-08 22:38:19 +00:00
resp . Data [ "id" ] = ""
}
return resp , nil
2016-03-08 20:13:29 +00:00
}
2016-03-09 14:48:32 +00:00
// handleUpdateRevokeAccessor handles the auth/token/revoke-accessor path for revoking
2016-03-09 14:05:04 +00:00
// the token associated with the accessor
2016-03-09 14:48:32 +00:00
func ( ts * TokenStore ) handleUpdateRevokeAccessor ( 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 ) {
2016-02-29 18:27:31 +00:00
return ts . handleCreateCommon ( req , d , true , nil )
2015-11-03 20:10:46 +00:00
}
// 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 ) {
2016-02-29 18:27:31 +00:00
return ts . handleCreateCommon ( req , d , false , nil )
2015-11-03 20:10:46 +00:00
}
// handleCreateCommon handles the auth/token/create path for creation of new tokens
func ( ts * TokenStore ) handleCreateCommon (
2016-02-29 18:27:31 +00:00
req * logical . Request , d * framework . FieldData , orphan bool , role * tsRoleEntry ) ( * 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 {
2016-04-07 15:39:46 +00:00
Parent : req . ClientToken ,
// The mount point is always the same since we have only one token
// store; using req.MountPoint causes trouble in tests since they don't
// have an official mount
Path : fmt . Sprintf ( "auth/token/%s" , req . Path ) ,
2015-09-18 20:33:52 +00:00
Meta : data . Metadata ,
DisplayName : "token" ,
NumUses : data . NumUses ,
CreationTime : time . Now ( ) . Unix ( ) ,
2015-04-15 21:24:07 +00:00
}
2016-03-07 15:07:04 +00:00
// If the role is not nil, we add the role name as part of the token's
// path. This makes it much easier to later revoke tokens that were issued
// by a role (using revoke-prefix). Users can further specify a PathSuffix
// in the role; that way they can use something like "v1", "v2" to indicate
// role revisions, and revoke only tokens issued with a previous revision.
2016-02-29 18:27:31 +00:00
if role != nil {
te . Role = role . Name
2016-03-01 20:30:37 +00:00
if role . PathSuffix != "" {
te . Path = fmt . Sprintf ( "%s/%s" , te . Path , role . PathSuffix )
2016-02-29 18:27:31 +00:00
}
}
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
}
2016-02-29 18:27:31 +00:00
switch {
2016-03-31 18:52:49 +00:00
// If we have a role, and the role defines policies, we don't even consider
// parent policies; the role allowed policies trumps all
case role != nil && len ( role . AllowedPolicies ) > 0 :
2016-03-09 15:42:04 +00:00
if len ( data . Policies ) == 0 {
data . Policies = role . AllowedPolicies
} else {
2016-04-06 00:30:38 +00:00
if ! strutil . StrListSubset ( role . AllowedPolicies , data . Policies ) {
2016-03-09 15:42:04 +00:00
return logical . ErrorResponse ( "token policies must be subset of the role's allowed policies" ) , logical . ErrInvalidRequest
}
2016-02-29 18:27:31 +00:00
}
case len ( data . Policies ) == 0 :
2015-04-07 21:19:52 +00:00
data . Policies = parent . Policies
2016-02-29 18:27:31 +00:00
// When a role is not in use, only permit policies to be a subset unless
// the client has root or sudo privileges
2016-04-06 00:30:38 +00:00
case ! isSudo && ! strutil . 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
2016-02-29 18:27:31 +00:00
switch {
case role != nil :
if role . Orphan {
te . Parent = ""
}
case data . NoParent :
// Only allow an orphan token if the client has sudo policy
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 = ""
2016-02-29 18:27:31 +00:00
default :
2015-11-03 20:10:46 +00:00
// This comes from create-orphan, which can be properly ACLd
if orphan {
te . Parent = ""
}
2015-03-24 22:10:46 +00:00
}
2016-02-29 18:27:31 +00:00
if role != nil && role . Period > 0 {
te . TTL = role . Period
} else {
// 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 != "" {
dur , err := time . ParseDuration ( data . Lease )
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
if dur < 0 {
return logical . ErrorResponse ( "lease must be positive" ) , logical . ErrInvalidRequest
}
te . TTL = dur
2015-03-24 22:10:46 +00:00
}
2015-09-18 20:33:52 +00:00
2016-02-29 18:27:31 +00:00
sysView := ts . System ( )
2015-09-18 20:33:52 +00:00
2016-02-29 18:27:31 +00:00
// Set the default lease if non-provided, root tokens are exempt
2016-04-06 00:30:38 +00:00
if te . TTL == 0 && ! strutil . StrListContains ( te . Policies , "root" ) {
2016-02-29 18:27:31 +00:00
te . TTL = sysView . DefaultLeaseTTL ( )
}
2015-09-18 20:33:52 +00:00
2016-02-29 18:27:31 +00:00
// Limit the lease duration
2016-04-07 15:27:14 +00:00
if te . TTL > sysView . MaxLeaseTTL ( ) && sysView . MaxLeaseTTL ( ) != time . Duration ( 0 ) {
2016-02-29 18:27:31 +00:00
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-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 ,
2016-03-09 16:29:09 +00:00
"accessor" : out . Accessor ,
2016-01-04 21:43:07 +00:00
"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 ) ,
2016-02-29 18:27:31 +00:00
"role" : out . Role ,
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 ) {
2016-02-29 18:27:31 +00:00
if req . Auth == nil {
return nil , fmt . Errorf ( "request auth is nil" )
}
2016-01-29 22:44:09 +00:00
2016-03-01 17:33:35 +00:00
f := framework . LeaseExtend ( req . Auth . Increment , 0 , ts . System ( ) )
2016-02-29 18:27:31 +00:00
2016-03-09 16:07:13 +00:00
te , err := ts . Lookup ( req . Auth . ClientToken )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , fmt . Errorf ( "error looking up token: %s" , err )
}
if te == nil {
return nil , fmt . Errorf ( "no token entry found during lookup" )
}
// No role? Use normal LeaseExtend semantics
if te . Role == "" {
return f ( req , d )
}
2016-03-01 20:30:37 +00:00
role , err := ts . tokenStoreRole ( te . Role )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , fmt . Errorf ( "error looking up role %s: %s" , te . Role , err )
}
if role == nil {
2016-03-07 15:07:04 +00:00
return logical . ErrorResponse ( fmt . Sprintf ( "original token role (%s) could not be found, not renewing" , te . Role ) ) , nil
2016-02-29 18:27:31 +00:00
}
2016-03-07 15:07:04 +00:00
// If role.Period is not zero, this is a periodic token. The TTL for a
// periodic token is always the same (the role's period value). It is not
// subject to normal maximum TTL checks that would come from calling
// LeaseExtend, so we fast path it.
2016-02-29 18:27:31 +00:00
if role . Period != 0 {
req . Auth . TTL = role . Period
return & logical . Response { Auth : req . Auth } , nil
}
2016-01-29 22:44:09 +00:00
return f ( req , d )
}
2016-03-01 20:30:37 +00:00
func ( ts * TokenStore ) tokenStoreRole ( name string ) ( * tsRoleEntry , error ) {
2016-03-01 17:33:35 +00:00
entry , err := ts . view . Get ( fmt . Sprintf ( "%s%s" , rolesPrefix , name ) )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
if entry == nil {
return nil , nil
}
var result tsRoleEntry
if err := entry . DecodeJSON ( & result ) ; err != nil {
return nil , err
}
return & result , nil
}
func ( ts * TokenStore ) tokenStoreRoleList (
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2016-03-01 17:33:35 +00:00
entries , err := ts . view . List ( rolesPrefix )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
2016-02-29 19:13:09 +00:00
ret := make ( [ ] string , len ( entries ) )
for i , entry := range entries {
2016-03-01 17:33:35 +00:00
ret [ i ] = strings . TrimPrefix ( entry , rolesPrefix )
2016-02-29 19:13:09 +00:00
}
return logical . ListResponse ( ret ) , nil
2016-02-29 18:27:31 +00:00
}
func ( ts * TokenStore ) tokenStoreRoleDelete (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2016-03-01 17:33:35 +00:00
err := ts . view . Delete ( fmt . Sprintf ( "%s%s" , rolesPrefix , data . Get ( "role_name" ) . ( string ) ) )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
return nil , nil
}
func ( ts * TokenStore ) tokenStoreRoleRead (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2016-03-01 20:30:37 +00:00
role , err := ts . tokenStoreRole ( data . Get ( "role_name" ) . ( string ) )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
if role == nil {
return nil , nil
}
resp := & logical . Response {
Data : structs . New ( role ) . Map ( ) ,
}
2016-04-14 10:10:22 +00:00
// Make the period nicer
if role . Period != 0 {
resp . Data [ "period" ] = role . Period . Seconds ( )
}
2016-02-29 18:27:31 +00:00
return resp , nil
}
2016-03-09 16:59:54 +00:00
func ( ts * TokenStore ) tokenStoreRoleExistenceCheck ( req * logical . Request , data * framework . FieldData ) ( bool , error ) {
name := data . Get ( "role_name" ) . ( string )
if name == "" {
return false , fmt . Errorf ( "role name cannot be empty" )
}
role , err := ts . tokenStoreRole ( name )
if err != nil {
return false , err
}
return role != nil , nil
}
func ( ts * TokenStore ) tokenStoreRoleCreateUpdate (
2016-02-29 18:27:31 +00:00
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
name := data . Get ( "role_name" ) . ( string )
if name == "" {
return logical . ErrorResponse ( "role name cannot be empty" ) , nil
}
2016-03-09 16:59:54 +00:00
entry , err := ts . tokenStoreRole ( name )
if err != nil {
return nil , err
}
2016-02-29 18:27:31 +00:00
2016-03-09 16:59:54 +00:00
// Due to the existence check, entry will only be nil if it's a create
// operation, so just create a new one
if entry == nil {
entry = & tsRoleEntry {
Name : name ,
2016-02-29 18:27:31 +00:00
}
}
2016-03-09 16:59:54 +00:00
// In this series of blocks, if we do not find a user-provided value and
// it's a creation operation, we call data.Get to get the appropriate
// default
orphanInt , ok := data . GetOk ( "orphan" )
if ok {
entry . Orphan = orphanInt . ( bool )
} else if req . Operation == logical . CreateOperation {
entry . Orphan = data . Get ( "orphan" ) . ( bool )
}
periodInt , ok := data . GetOk ( "period" )
if ok {
entry . Period = time . Second * time . Duration ( periodInt . ( int ) )
} else if req . Operation == logical . CreateOperation {
entry . Period = time . Second * time . Duration ( data . Get ( "period" ) . ( int ) )
2016-02-29 18:27:31 +00:00
}
2016-03-09 16:59:54 +00:00
pathSuffixInt , ok := data . GetOk ( "path_suffix" )
if ok {
pathSuffix := pathSuffixInt . ( string )
if pathSuffix != "" {
matched := pathSuffixSanitize . MatchString ( pathSuffix )
if ! matched {
return logical . ErrorResponse ( fmt . Sprintf ( "given role path suffix contains invalid characters; must match %s" , pathSuffixSanitize . String ( ) ) ) , nil
}
entry . PathSuffix = pathSuffix
}
} else if req . Operation == logical . CreateOperation {
entry . PathSuffix = data . Get ( "path_suffix" ) . ( string )
}
allowedPoliciesInt , ok := data . GetOk ( "allowed_policies" )
if ok {
allowedPolicies := allowedPoliciesInt . ( string )
if allowedPolicies != "" {
entry . AllowedPolicies = strings . Split ( allowedPolicies , "," )
}
} else if req . Operation == logical . CreateOperation {
entry . AllowedPolicies = strings . Split ( data . Get ( "allowed_policies" ) . ( string ) , "," )
2016-02-29 18:27:31 +00:00
}
// Store it
2016-03-01 17:33:35 +00:00
jsonEntry , err := logical . StorageEntryJSON ( fmt . Sprintf ( "%s%s" , rolesPrefix , name ) , entry )
2016-02-29 18:27:31 +00:00
if err != nil {
return nil , err
}
2016-03-01 17:33:35 +00:00
if err := ts . view . Put ( jsonEntry ) ; err != nil {
2016-02-29 18:27:31 +00:00
return nil , err
}
return nil , nil
}
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-03 16:04:05 +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. `
tokenCreateRoleHelp = ` This token create path is used to create new tokens adhering to the given role. `
tokenListRolesHelp = ` This endpoint lists configured roles. `
2016-03-09 22:23:34 +00:00
tokenLookupAccessorHelp = ` This endpoint will lookup a token associated with the given accessor and its properties. Response will not contain the token ID. `
2016-03-03 16:04:05 +00:00
tokenLookupHelp = ` This endpoint will lookup a token and its properties. `
tokenPathRolesHelp = ` This endpoint allows creating, reading, and deleting roles. `
2016-03-09 22:23:34 +00:00
tokenRevokeAccessorHelp = ` This endpoint will delete the token associated with the accessor and all of its child tokens. `
2016-03-03 16:04:05 +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. `
tokenRevokeOrphanHelp = ` This endpoint will delete the token and orphan its 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. `
tokenAllowedPoliciesHelp = ` If set , tokens created via this role
can be created with any subset of this list ,
rather than the normal semantics of a subset
of the client token ' s policies . This
parameter should be sent as a comma - delimited
string . `
2016-03-07 15:07:04 +00:00
tokenOrphanHelp = ` If true , tokens created via this role
will be orphan tokens ( have no parent ) `
2016-03-03 16:04:05 +00:00
tokenPeriodHelp = ` If set , tokens created via this role
will have no max lifetime ; instead , their
renewal period will be fixed to this value .
This takes an integer number of seconds ,
or a string duration ( e . g . "24h" ) . `
tokenPathSuffixHelp = ` If set , tokens created via this role
will contain the given suffix as a part of
their path . This can be used to assist use
of the ' revoke - prefix ' endpoint later on .
The given suffix must match the regular
expression `
2015-03-24 22:10:46 +00:00
)