2015-03-18 20:19:19 +00:00
package vault
import (
"encoding/json"
"fmt"
2015-04-15 21:24:07 +00:00
"regexp"
2015-03-24 22:10:46 +00:00
"strings"
2016-05-02 07:11:14 +00:00
"sync"
2015-03-24 22:10:46 +00:00
"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"
2016-08-02 19:12:45 +00:00
"github.com/hashicorp/vault/helper/duration"
2016-07-06 16:25:40 +00:00
"github.com/hashicorp/vault/helper/jsonutil"
2016-07-20 01:37:28 +00:00
"github.com/hashicorp/vault/helper/locksutil"
2016-05-11 20:51:18 +00:00
"github.com/hashicorp/vault/helper/policyutil"
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 )
2016-05-02 07:11:14 +00:00
tokenLocks map [ string ] * sync . RWMutex
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
2016-05-02 07:11:14 +00:00
t . tokenLocks = map [ string ] * sync . RWMutex { }
2016-07-20 01:37:28 +00:00
// Create 256 locks
2016-07-20 08:48:35 +00:00
if err = locksutil . CreateLocks ( t . tokenLocks , 256 ) ; err != nil {
return nil , fmt . Errorf ( "failed to create locks: %v" , err )
}
2016-07-20 01:37:28 +00:00
2016-05-16 20:11:33 +00:00
t . tokenLocks [ "custom" ] = & sync . RWMutex { }
2016-05-02 07:11:14 +00:00
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/*" ,
2016-07-29 22:20:38 +00:00
"accessors*" ,
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
} ,
2016-07-29 22:20:38 +00:00
& framework . Path {
Pattern : "accessors/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : t . tokenStoreAccessorList ,
} ,
2016-08-01 17:07:41 +00:00
HelpSynopsis : tokenListAccessorsHelp ,
HelpDescription : tokenListAccessorsHelp ,
2016-07-29 22:20:38 +00:00
} ,
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
} ,
2016-08-02 14:33:50 +00:00
"disallowed_policies" : & framework . FieldSchema {
Type : framework . TypeString ,
Default : "" ,
Description : tokenDisallowedPoliciesHelp ,
} ,
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
} ,
2016-05-11 20:51:18 +00:00
"explicit_max_ttl" : & framework . FieldSchema {
Type : framework . TypeDurationSecond ,
Default : 0 ,
Description : tokenExplicitMaxTTLHelp ,
} ,
2016-06-08 19:17:22 +00:00
"renewable" : & framework . FieldSchema {
Type : framework . TypeBool ,
Default : true ,
Description : tokenRenewableHelp ,
} ,
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-04-28 19:15:37 +00:00
Pattern : "lookup" + framework . OptionalParamRegex ( "urltoken" ) ,
2015-03-31 19:51:00 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urltoken" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to lookup (GET/POST URL parameter)" ,
} ,
2015-03-31 19:51:00 +00:00
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Token to lookup (POST request body)" ,
2015-03-31 19:51:00 +00:00
} ,
} ,
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-04-28 19:15:37 +00:00
Pattern : "lookup-accessor" + framework . OptionalParamRegex ( "urlaccessor" ) ,
2016-03-08 20:13:29 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urlaccessor" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Accessor of the token to look up (URL parameter)" ,
} ,
2016-03-09 11:23:31 +00:00
"accessor" : & framework . FieldSchema {
2016-03-08 20:13:29 +00:00
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Accessor of the token to look up (request body)" ,
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 ,
2016-04-28 19:15:37 +00:00
Description : "Token to look up (unused)" ,
2015-03-31 19:48:19 +00:00
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2016-04-28 19:15:37 +00:00
logical . ReadOperation : t . handleLookupSelf ,
2015-03-31 19:48:19 +00:00
} ,
HelpSynopsis : strings . TrimSpace ( tokenLookupHelp ) ,
HelpDescription : strings . TrimSpace ( tokenLookupHelp ) ,
} ,
2016-03-08 20:13:29 +00:00
& framework . Path {
2016-04-28 19:15:37 +00:00
Pattern : "revoke-accessor" + framework . OptionalParamRegex ( "urlaccessor" ) ,
2016-03-08 20:13:29 +00:00
2016-03-08 23:07:27 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urlaccessor" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Accessor of the token (in URL)" ,
} ,
2016-03-09 11:23:31 +00:00
"accessor" : & framework . FieldSchema {
2016-03-08 23:07:27 +00:00
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Accessor of the token (request body)" ,
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-04-28 19:15:37 +00:00
Pattern : "revoke" + framework . OptionalParamRegex ( "urltoken" ) ,
2015-03-31 19:48:19 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urltoken" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to revoke (in URL)" ,
} ,
2015-03-31 19:48:19 +00:00
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Token to revoke (request body)" ,
2015-03-31 19:48:19 +00:00
} ,
} ,
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-04-28 19:15:37 +00:00
Pattern : "revoke-orphan" + framework . OptionalParamRegex ( "urltoken" ) ,
2015-03-31 19:48:19 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urltoken" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to revoke (in URL)" ,
} ,
2015-03-31 19:48:19 +00:00
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Token to revoke (request body)" ,
2015-03-31 19:48:19 +00:00
} ,
} ,
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 ,
2016-04-28 19:15:37 +00:00
Description : "Token to renew (unused)" ,
2015-10-07 16:49:13 +00:00
} ,
"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-04-28 19:15:37 +00:00
Pattern : "renew" + framework . OptionalParamRegex ( "urltoken" ) ,
2015-04-03 19:11:49 +00:00
Fields : map [ string ] * framework . FieldSchema {
2016-04-28 19:15:37 +00:00
"urltoken" : & framework . FieldSchema {
Type : framework . TypeString ,
Description : "Token to renew (in URL)" ,
} ,
2015-04-03 19:11:49 +00:00
"token" : & framework . FieldSchema {
Type : framework . TypeString ,
2016-04-28 19:15:37 +00:00
Description : "Token to renew (request body)" ,
2015-04-03 19:11:49 +00:00
} ,
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-07-07 21:44:14 +00:00
// ID of this entry, generally a random UUID
ID string ` json:"id" mapstructure:"id" structs:"id" `
// Accessor for this token, a random UUID
Accessor string ` json:"accessor" mapstructure:"accessor" structs:"accessor" `
// Parent token, used for revocation trees
Parent string ` json:"parent" mapstructure:"parent" structs:"parent" `
// Which named policies should be used
Policies [ ] string ` json:"policies" mapstructure:"policies" structs:"policies" `
// Used for audit trails, this is something like "auth/user/login"
Path string ` json:"path" mapstructure:"path" structs:"path" `
// Used for auditing. This could include things like "source", "user", "ip"
Meta map [ string ] string ` json:"meta" mapstructure:"meta" structs:"meta" `
// Used for operators to be able to associate with the source
DisplayName string ` json:"display_name" mapstructure:"display_name" structs:"display_name" `
// Used to restrict the number of uses (zero is unlimited). This is to support one-time-tokens (generalized).
NumUses int ` json:"num_uses" mapstructure:"num_uses" structs:"num_uses" `
// Time of token creation
2016-07-12 20:28:27 +00:00
CreationTime int64 ` json:"creation_time" mapstructure:"creation_time" structs:"creation_time" `
2016-07-07 21:44:14 +00:00
// Duration set when token was created
TTL time . Duration ` json:"ttl" mapstructure:"ttl" structs:"ttl" `
// Explicit maximum TTL on the token
ExplicitMaxTTL time . Duration ` json:"" mapstructure:"" structs:"" `
// If set, the role that was used for parameters at creation time
Role string ` json:"role" mapstructure:"role" structs:"role" `
2016-08-13 01:01:30 +00:00
// If set, the period of the token. This is only used when created directly
// through the create endpoint; periods managed by roles or other auth
// backends are subject to those renewal rules.
Period time . Duration ` json:"period" mapstructure:"period" structs:"period" `
2016-02-29 18:27:31 +00:00
}
// 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
2016-08-02 17:33:03 +00:00
// List of policies to be not allowed during token creation using this role
2016-08-02 14:33:50 +00:00
DisallowedPolicies [ ] string ` json:"disallowed_policies" mapstructure:"disallowed_policies" structs:"disallowed_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
2016-05-11 20:51:18 +00:00
// revoke using 'revoke-prefix'
2016-03-01 20:30:37 +00:00
PathSuffix string ` json:"path_suffix" mapstructure:"path_suffix" structs:"path_suffix" `
2016-05-11 20:51:18 +00:00
2016-06-08 19:17:22 +00:00
// If set, controls whether created tokens are marked as being renewable
Renewable bool ` json:"renewable" mapstructure:"renewable" structs:"renewable" `
2016-05-11 20:51:18 +00:00
// If set, the token entry will have an explicit maximum TTL set, rather
// than deferring to role/mount values
ExplicitMaxTTL time . Duration ` json:"explicit_max_ttl" mapstructure:"explicit_max_ttl" structs:"explicit_max_ttl" `
2015-03-18 20:19:19 +00:00
}
2016-07-29 22:20:38 +00:00
type accessorEntry struct {
TokenID string ` json:"token_id" `
AccessorID string ` json:"accessor_id" `
}
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
}
2016-05-15 16:58:36 +00:00
// SaltID is used to apply a salt and hash to an ID to make sure its not reversible
2015-04-03 00:39:38 +00:00
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-07-29 22:20:38 +00:00
func ( ts * TokenStore ) tokenStoreAccessorList (
req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
entries , err := ts . view . List ( accessorPrefix )
if err != nil {
return nil , err
}
resp := & logical . Response { }
ret := make ( [ ] string , 0 , len ( entries ) )
for _ , entry := range entries {
aEntry , err := ts . lookupBySaltedAccessor ( entry )
if err != nil {
resp . AddWarning ( "Found an accessor entry that could not be successfully decoded" )
continue
}
2016-08-01 17:07:41 +00:00
if aEntry . TokenID == "" {
resp . AddWarning ( fmt . Sprintf ( "Found an accessor entry missing a token: %v" , aEntry . AccessorID ) )
} else {
ret = append ( ret , aEntry . AccessorID )
}
2016-07-29 22:20:38 +00:00
}
resp . Data = map [ string ] interface { } {
"keys" : ret ,
}
return resp , 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-07-29 22:23:55 +00:00
aEntry := & accessorEntry {
TokenID : entry . ID ,
AccessorID : entry . Accessor ,
}
aEntryBytes , err := jsonutil . EncodeJSON ( aEntry )
if err != nil {
2016-07-30 03:24:04 +00:00
return fmt . Errorf ( "failed to marshal accessor index entry: %v" , err )
2016-07-29 22:23:55 +00:00
}
le := & logical . StorageEntry { Key : path , Value : aEntryBytes }
2016-03-08 18:12:53 +00:00
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-05-13 15:50:00 +00:00
entry . Policies = policyutil . SanitizePolicies ( entry . Policies , false )
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
}
2016-05-02 07:11:14 +00:00
func ( ts * TokenStore ) getTokenLock ( id string ) * sync . RWMutex {
// Find our multilevel lock, or fall back to global
var lock * sync . RWMutex
var ok bool
2016-05-02 18:57:17 +00:00
if len ( id ) >= 2 {
lock , ok = ts . tokenLocks [ id [ 0 : 2 ] ]
2016-05-02 07:11:14 +00:00
}
if ! ok || lock == nil {
2016-05-16 20:11:33 +00:00
// Fall back for custom token IDs
lock = ts . tokenLocks [ "custom" ]
2016-05-02 07:11:14 +00:00
}
return lock
}
// UseToken is used to manage restricted use tokens and decrement their
// available uses. Returns two values: a potentially updated entry or, if the
// token has been revoked, nil; and whether an error was encountered. The
// locking here isn't perfect, as other parts of the code may update an entry,
// but usually none after the entry is already created...so this is pretty
// good.
func ( ts * TokenStore ) UseToken ( te * TokenEntry ) ( * TokenEntry , error ) {
if te == nil {
return nil , fmt . Errorf ( "invalid token entry provided for use count decrementing" )
}
2016-05-02 18:57:17 +00:00
// This case won't be hit with a token with restricted uses because we go
// from 1 to -1. So it's a nice optimization to check this without a read
// lock.
2015-04-17 18:51:04 +00:00
if te . NumUses == 0 {
2016-05-02 07:11:14 +00:00
return te , nil
}
2016-05-02 18:57:17 +00:00
lock := ts . getTokenLock ( te . ID )
2016-05-02 07:11:14 +00:00
lock . Lock ( )
defer lock . Unlock ( )
2016-05-02 18:57:17 +00:00
// Call lookupSalted instead of Lookup to avoid deadlocking since Lookup grabs a read lock
2016-05-02 07:11:14 +00:00
te , err := ts . lookupSalted ( ts . SaltID ( te . ID ) )
if err != nil {
return nil , fmt . Errorf ( "failed to refresh entry: %v" , err )
}
2016-05-16 20:11:33 +00:00
// If it can't be found we shouldn't be trying to use it, so if we get nil
// back, it is because it has been revoked in the interim or will be
// revoked (NumUses is -1)
2016-05-02 07:11:14 +00:00
if te == nil {
2016-05-16 20:11:33 +00:00
return nil , fmt . Errorf ( "token not found or fully used already" )
2015-04-17 18:51:04 +00:00
}
2016-05-02 18:57:17 +00:00
// Decrement the count. If this is our last use count, we need to indicate
// that this is no longer valid, but revocation is deferred to the end of
// the call, so this will make sure that any Lookup that happens doesn't
2016-05-16 20:11:33 +00:00
// return an entry. This essentially acts as a write-ahead lock and is
// especially useful since revocation can end up (via the expiration
// manager revoking children) attempting to acquire the same lock
// repeatedly.
2016-05-02 18:57:17 +00:00
if te . NumUses == 1 {
2016-05-02 07:11:14 +00:00
te . NumUses = - 1
2016-05-02 18:57:17 +00:00
} else {
te . NumUses -= 1
2015-04-17 18:51:04 +00:00
}
// Marshal the entry
enc , err := json . Marshal ( te )
if err != nil {
2016-05-02 07:11:14 +00:00
return nil , fmt . Errorf ( "failed to encode entry: %v" , err )
2015-04-17 18:51:04 +00:00
}
// 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 {
2016-05-02 07:11:14 +00:00
return nil , fmt . Errorf ( "failed to persist entry: %v" , err )
2015-04-17 18:51:04 +00:00
}
2016-05-02 07:11:14 +00:00
return te , nil
2015-04-17 18:51:04 +00:00
}
2016-05-02 07:11:14 +00:00
// Lookup is used to find a token given its ID. It acquires a read lock, then calls lookupSalted.
2015-03-18 20:19:19 +00:00
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" )
}
2016-05-02 07:11:14 +00:00
lock := ts . getTokenLock ( id )
lock . RLock ( )
defer lock . RUnlock ( )
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 )
2016-07-06 16:25:40 +00:00
if err := jsonutil . DecodeJSON ( raw . Value , entry ) ; err != nil {
2015-03-18 20:19:19 +00:00
return nil , fmt . Errorf ( "failed to decode entry: %v" , err )
}
2016-05-02 07:11:14 +00:00
// This is a token that is awaiting deferred revocation
if entry . NumUses == - 1 {
return nil , nil
}
2015-03-18 20:19:19 +00:00
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-07-29 22:20:38 +00:00
func ( ts * TokenStore ) lookupByAccessor ( accessor string ) ( accessorEntry , error ) {
return ts . lookupBySaltedAccessor ( ts . SaltID ( accessor ) )
}
func ( ts * TokenStore ) lookupBySaltedAccessor ( saltedAccessor string ) ( accessorEntry , error ) {
entry , err := ts . view . Get ( accessorPrefix + saltedAccessor )
var aEntry accessorEntry
2016-03-08 23:07:27 +00:00
if err != nil {
2016-07-29 22:20:38 +00:00
return aEntry , fmt . Errorf ( "failed to read index using accessor: %s" , err )
2016-03-08 23:07:27 +00:00
}
if entry == nil {
2016-07-29 22:20:38 +00:00
return aEntry , & StatusBadRequest { Err : "invalid accessor" }
}
err = jsonutil . DecodeJSON ( entry . Value , & aEntry )
// If we hit an error, assume it's a pre-struct straight token ID
if err != nil {
aEntry . TokenID = string ( entry . Value )
te , err := ts . lookupSalted ( ts . SaltID ( aEntry . TokenID ) )
if err != nil {
return accessorEntry { } , fmt . Errorf ( "failed to look up token using accessor index: %s" , err )
}
2016-08-01 17:07:41 +00:00
// It's hard to reason about what to do here -- it may be that the
// token was revoked async, or that it's an old accessor index entry
// that was somehow not cleared up, or or or. A nonexistent token entry
// on lookup is nil, not an error, so we keep that behavior here to be
// safe...the token ID is simply not filled in.
if te != nil {
aEntry . AccessorID = te . Accessor
}
2016-03-08 23:07:27 +00:00
}
2016-07-29 22:20:38 +00:00
return aEntry , nil
2016-03-08 23:07:27 +00:00
}
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 == "" {
2016-04-28 19:15:37 +00:00
accessor = data . Get ( "urlaccessor" ) . ( string )
if accessor == "" {
return nil , & StatusBadRequest { Err : "missing accessor" }
}
2016-03-08 22:38:19 +00:00
}
2016-07-29 22:20:38 +00:00
aEntry , 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-07-29 22:20:38 +00:00
"token" : aEntry . 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 == "" {
2016-04-28 19:15:37 +00:00
accessor = data . Get ( "urlaccessor" ) . ( string )
if accessor == "" {
return nil , & StatusBadRequest { Err : "missing accessor" }
}
2016-03-08 23:07:27 +00:00
}
2016-07-29 22:20:38 +00:00
aEntry , err := ts . lookupByAccessor ( accessor )
2016-03-08 23:07:27 +00:00
if err != nil {
return nil , err
}
// Revoke the token and its children
2016-07-29 22:20:38 +00:00
if err := ts . RevokeTree ( aEntry . TokenID ) ; err != nil {
2016-03-08 23:07:27 +00:00
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
2016-06-08 15:14:30 +00:00
Renewable * bool
2016-06-08 18:49:48 +00:00
ExplicitMaxTTL string ` mapstructure:"explicit_max_ttl" `
2015-11-09 22:30:50 +00:00
DisplayName string ` mapstructure:"display_name" `
NumUses int ` mapstructure:"num_uses" `
2016-08-13 01:01:30 +00:00
Period string
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-06-08 15:14:30 +00:00
renewable := true
if data . Renewable != nil {
renewable = * data . Renewable
}
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-06-08 19:17:22 +00:00
// If renewable hasn't been disabled in the call and the role has
// renewability disabled, set renewable false
if renewable && ! role . Renewable {
renewable = false
}
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-08-02 14:33:50 +00:00
resp := & logical . Response { }
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-08-02 14:33:50 +00:00
if len ( role . DisallowedPolicies ) > 0 {
2016-08-02 17:33:03 +00:00
resp . AddWarning ( "Both 'allowed_policies' and 'disallowed_policies' are set; only 'allowed_policies' will take effect" )
2016-08-02 14:33:50 +00:00
}
2016-03-09 15:42:04 +00:00
if len ( data . Policies ) == 0 {
data . Policies = role . AllowedPolicies
} else {
2016-05-11 20:51:18 +00:00
// Sanitize passed-in and role policies before comparison
2016-05-13 15:50:00 +00:00
sanitizedInputPolicies := policyutil . SanitizePolicies ( data . Policies , true )
sanitizedRolePolicies := policyutil . SanitizePolicies ( role . AllowedPolicies , true )
2016-05-11 20:51:18 +00:00
if ! strutil . StrListSubset ( sanitizedRolePolicies , sanitizedInputPolicies ) {
return logical . ErrorResponse ( fmt . Sprintf ( "token policies (%v) must be subset of the role's allowed policies (%v)" , sanitizedInputPolicies , sanitizedRolePolicies ) ) , logical . ErrInvalidRequest
2016-03-09 15:42:04 +00:00
}
2016-02-29 18:27:31 +00:00
}
2016-08-02 14:33:50 +00:00
case role != nil && len ( role . DisallowedPolicies ) > 0 :
if len ( data . Policies ) == 0 {
data . Policies = parent . Policies
}
sanitizedInputPolicies := policyutil . SanitizePolicies ( data . Policies , true )
// Do not voluntarily add 'default' to the list of items to check on
sanitizedRolePolicies := policyutil . SanitizePolicies ( role . DisallowedPolicies , false )
for _ , inputPolicy := range sanitizedInputPolicies {
if strutil . StrListContains ( sanitizedRolePolicies , inputPolicy ) {
return logical . ErrorResponse ( fmt . Sprintf ( "token policy (%s) is disallowed by this role" , inputPolicy ) ) , 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-05-11 20:51:18 +00:00
case ! isSudo :
// Sanitize passed-in and parent policies before comparison
2016-05-13 15:50:00 +00:00
sanitizedInputPolicies := policyutil . SanitizePolicies ( data . Policies , true )
sanitizedParentPolicies := policyutil . SanitizePolicies ( parent . Policies , true )
2016-05-11 20:51:18 +00:00
if ! strutil . StrListSubset ( sanitizedParentPolicies , sanitizedInputPolicies ) {
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
2016-05-13 15:50:00 +00:00
// Do not add the 'default' policy if requested not to.
te . Policies = policyutil . SanitizePolicies ( data . Policies , ! data . NoDefaultPolicy )
2015-03-24 22:10:46 +00:00
2016-08-13 01:01:30 +00:00
// Prevent internal policies from being assigned to tokens
for _ , policy := range te . Policies {
if strutil . StrListContains ( nonAssignablePolicies , policy ) {
return logical . ErrorResponse ( fmt . Sprintf ( "cannot assign %s policy" , policy ) ) , nil
}
}
// Prevent attempts to create a root token without an actual root token as parent.
// This is to thwart privilege escalation by tokens having 'sudo' privileges.
if strutil . StrListContains ( data . Policies , "root" ) && ! strutil . StrListContains ( parent . Policies , "root" ) {
return logical . ErrorResponse ( "root tokens may not be created without parent token being root" ) , logical . ErrInvalidRequest
}
//
// NOTE: Do not modify policies below this line. We need the checks above
// to be the last checks as they must look at the final policy set.
//
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-06-08 18:49:48 +00:00
if data . ExplicitMaxTTL != "" {
2016-08-02 19:12:45 +00:00
dur , err := duration . ParseDurationSecond ( data . ExplicitMaxTTL )
2016-06-08 18:49:48 +00:00
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
2015-03-24 22:10:46 +00:00
}
2016-06-08 18:49:48 +00:00
if dur < 0 {
return logical . ErrorResponse ( "explicit_max_ttl must be positive" ) , logical . ErrInvalidRequest
2016-02-29 18:27:31 +00:00
}
2016-06-08 18:49:48 +00:00
te . ExplicitMaxTTL = dur
}
2015-09-18 20:33:52 +00:00
2016-08-13 01:01:30 +00:00
if data . Period != "" {
if ! isSudo {
return logical . ErrorResponse ( "root or sudo privileges required to create periodic token" ) ,
logical . ErrInvalidRequest
}
dur , err := duration . ParseDurationSecond ( data . Period )
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
if dur < 0 {
return logical . ErrorResponse ( "period must be positive" ) , logical . ErrInvalidRequest
}
te . Period = dur
}
2016-06-08 18:49:48 +00:00
// Parse the TTL/lease if any
if data . TTL != "" {
2016-08-02 19:12:45 +00:00
dur , err := duration . ParseDurationSecond ( data . TTL )
2016-06-08 18:49:48 +00:00
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 != "" {
// This block is compatibility
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
2016-02-29 18:27:31 +00:00
}
2016-06-08 18:49:48 +00:00
te . TTL = dur
2015-03-24 22:10:46 +00:00
}
2016-08-13 01:01:30 +00:00
// Set the lesser period/explicit max TTL if defined both in arguments and in role
if role != nil {
if role . ExplicitMaxTTL != 0 {
switch {
case te . ExplicitMaxTTL == 0 :
2016-06-08 18:49:48 +00:00
te . ExplicitMaxTTL = role . ExplicitMaxTTL
2016-08-13 01:01:30 +00:00
default :
if role . ExplicitMaxTTL < te . ExplicitMaxTTL {
te . ExplicitMaxTTL = role . ExplicitMaxTTL
}
resp . AddWarning ( fmt . Sprintf ( "Explicit max TTL specified both during creation call and in role; using the lesser value of %d seconds" , int64 ( te . ExplicitMaxTTL . Seconds ( ) ) ) )
}
}
if role . Period != 0 {
switch {
case te . Period == 0 :
te . Period = role . Period
default :
if role . Period < te . Period {
te . Period = role . Period
}
resp . AddWarning ( fmt . Sprintf ( "Period specified both during creation call and in role; using the lesser value of %d seconds" , int64 ( te . Period . Seconds ( ) ) ) )
2016-06-08 18:49:48 +00:00
}
}
}
2016-05-11 20:51:18 +00:00
2016-06-08 18:49:48 +00:00
sysView := ts . System ( )
2016-08-13 17:21:56 +00:00
if te . Period > 0 {
2016-08-13 01:01:30 +00:00
te . TTL = te . Period
} else {
// Set the default lease if not provided, root tokens are exempt
if te . TTL == 0 && ! strutil . StrListContains ( te . Policies , "root" ) {
te . TTL = sysView . DefaultLeaseTTL ( )
}
// Limit the lease duration
if te . TTL > sysView . MaxLeaseTTL ( ) && sysView . MaxLeaseTTL ( ) != 0 {
te . TTL = sysView . MaxLeaseTTL ( )
}
}
// Run some bounding checks if the explicit max TTL is set; we do not check
// period as it's defined to escape the max TTL
2016-06-08 18:49:48 +00:00
if te . ExplicitMaxTTL > 0 {
2016-08-13 18:03:22 +00:00
// Limit the lease duration, except for periodic tokens -- in that case the explicit max limits the period, which itself can escape normal max
if sysView . MaxLeaseTTL ( ) != 0 && te . ExplicitMaxTTL > sysView . MaxLeaseTTL ( ) && te . Period == 0 {
2016-06-08 19:25:17 +00:00
resp . AddWarning ( fmt . Sprintf (
"Explicit max TTL of %d seconds is greater than system/mount allowed value; value is being capped to %d seconds" ,
int64 ( te . ExplicitMaxTTL . Seconds ( ) ) , int64 ( sysView . MaxLeaseTTL ( ) . Seconds ( ) ) ) )
te . ExplicitMaxTTL = sysView . MaxLeaseTTL ( )
2016-06-08 18:49:48 +00:00
}
if te . TTL == 0 {
2016-08-13 18:03:22 +00:00
// This won't be the case if it's periodic -- it will be set above
2016-06-08 18:49:48 +00:00
te . TTL = te . ExplicitMaxTTL
} else {
2016-08-13 18:03:22 +00:00
// Limit even in the periodic case
2016-06-08 18:49:48 +00:00
if te . TTL > te . ExplicitMaxTTL {
resp . AddWarning ( fmt . Sprintf (
"Requested TTL of %d seconds higher than explicit max TTL; value being capped to %d seconds" ,
int64 ( te . TTL . Seconds ( ) ) , int64 ( te . ExplicitMaxTTL . Seconds ( ) ) ) )
te . TTL = te . ExplicitMaxTTL
}
2016-05-11 20:51:18 +00:00
}
2016-06-08 18:49:48 +00:00
}
2016-05-11 20:51:18 +00:00
2016-08-05 15:15:25 +00:00
// Don't advertise non-expiring root tokens as renewable, as attempts to renew them are denied
if te . TTL == 0 {
2016-08-10 00:32:40 +00:00
if parent . TTL != 0 {
return logical . ErrorResponse ( "expiring root tokens cannot create non-expiring root tokens" ) , logical . ErrInvalidRequest
}
2016-08-05 15:15:25 +00:00
renewable = false
}
2016-08-08 20:44:29 +00:00
// Create the token
if err := ts . create ( & te ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
2015-03-24 22:10:46 +00:00
// Generate the response
2016-05-11 20:51:18 +00:00
resp . Auth = & logical . Auth {
DisplayName : te . DisplayName ,
Policies : te . Policies ,
Metadata : te . Meta ,
LeaseOptions : logical . LeaseOptions {
TTL : te . TTL ,
2016-06-08 15:14:30 +00:00
Renewable : renewable ,
2015-03-24 22:10:46 +00:00
} ,
2016-05-11 20:51:18 +00:00
ClientToken : te . ID ,
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 {
2016-08-02 20:53:06 +00:00
resp . AddWarning ( fmt . Sprintf ( "Policy %q 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 == "" {
2016-04-28 19:15:37 +00:00
id = data . Get ( "urltoken" ) . ( string )
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
2015-03-24 22:30:09 +00:00
}
// 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 == "" {
2016-04-28 19:15:37 +00:00
id = data . Get ( "urltoken" ) . ( string )
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
2015-03-24 22:30:09 +00:00
}
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
2016-04-28 19:15:37 +00:00
func ( ts * TokenStore ) handleLookupSelf (
req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
data . Raw [ "token" ] = req . ClientToken
return ts . handleLookup ( req , data )
}
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 )
2016-04-28 19:15:37 +00:00
if id == "" {
id = data . Get ( "urltoken" ) . ( 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-05-11 20:51:18 +00:00
"id" : out . ID ,
"accessor" : out . Accessor ,
"policies" : out . Policies ,
"path" : out . Path ,
"meta" : out . Meta ,
"display_name" : out . DisplayName ,
"num_uses" : out . NumUses ,
"orphan" : false ,
"creation_time" : int64 ( out . CreationTime ) ,
"creation_ttl" : int64 ( out . TTL . Seconds ( ) ) ,
"ttl" : int64 ( 0 ) ,
"explicit_max_ttl" : int64 ( out . ExplicitMaxTTL . Seconds ( ) ) ,
2015-03-24 22:39:33 +00:00
} ,
}
2015-11-09 18:19:59 +00:00
if out . Parent == "" {
resp . Data [ "orphan" ] = true
}
2016-08-13 01:01:30 +00:00
if out . Role != "" {
resp . Data [ "role" ] = out . Role
} else if out . Period != 0 {
resp . Data [ "period" ] = int64 ( out . Period . Seconds ( ) )
}
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-06-01 21:30:31 +00:00
if err := leaseTimes . renewable ( ) ; err == nil {
resp . Data [ "renewable" ] = true
} else {
resp . Data [ "renewable" ] = false
}
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 == "" {
2016-04-28 19:15:37 +00:00
id = data . Get ( "urltoken" ) . ( string )
if id == "" {
return logical . ErrorResponse ( "missing token ID" ) , logical . ErrInvalidRequest
}
2015-04-03 19:11:49 +00:00
}
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-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" )
}
2016-05-11 20:51:18 +00:00
f := framework . LeaseExtend ( req . Auth . Increment , te . ExplicitMaxTTL , ts . System ( ) )
2016-08-13 01:01:30 +00:00
// If (te/role).Period is not zero, this is a periodic token. The TTL for a
// periodic token is always the same (the period value). It is not subject
// to normal maximum TTL checks that would come from calling LeaseExtend,
// so we fast path it.
//
// The one wrinkle here is if the token has an explicit max TTL. If both
// are set, we treat it as a regular token and use the periodic value as
// the increment.
// No role? Use normal LeaseExtend semantics, taking into account
// TokenEntry properties
2016-02-29 18:27:31 +00:00
if te . Role == "" {
2016-08-13 01:01:30 +00:00
//Explicit max TTL overrides the period, if both are set
if te . Period != 0 {
if te . ExplicitMaxTTL == 0 {
req . Auth . TTL = te . Period
return & logical . Response { Auth : req . Auth } , nil
} else {
2016-08-13 18:03:22 +00:00
maxTime := time . Unix ( te . CreationTime , 0 ) . Add ( te . ExplicitMaxTTL )
2016-08-16 20:29:07 +00:00
if time . Now ( ) . Add ( te . Period ) . After ( maxTime ) {
2016-08-13 18:03:22 +00:00
req . Auth . TTL = maxTime . Sub ( time . Now ( ) )
} else {
req . Auth . TTL = te . Period
}
return & logical . Response { Auth : req . Auth } , nil
2016-08-13 01:01:30 +00:00
}
}
2016-02-29 18:27:31 +00:00
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-05-26 14:21:03 +00:00
return nil , fmt . Errorf ( "original token role (%s) could not be found, not renewing" , te . Role )
2016-02-29 18:27:31 +00:00
}
2016-08-13 01:01:30 +00:00
// Same deal here, but using the role period
if role . Period != 0 {
2016-08-13 18:03:22 +00:00
periodToUse := role . Period
if te . Period < role . Period {
periodToUse = te . Period
}
2016-08-13 01:01:30 +00:00
if te . ExplicitMaxTTL == 0 {
2016-08-13 18:03:22 +00:00
req . Auth . TTL = periodToUse
2016-08-13 01:01:30 +00:00
return & logical . Response { Auth : req . Auth } , nil
} else {
2016-08-13 18:03:22 +00:00
maxTime := time . Unix ( te . CreationTime , 0 ) . Add ( te . ExplicitMaxTTL )
2016-08-16 20:29:07 +00:00
if time . Now ( ) . Add ( periodToUse ) . After ( maxTime ) {
2016-08-13 18:03:22 +00:00
req . Auth . TTL = maxTime . Sub ( time . Now ( ) )
} else {
req . Auth . TTL = periodToUse
}
return & logical . Response { Auth : req . Auth } , nil
2016-08-13 01:01:30 +00:00
}
2016-02-29 18:27:31 +00:00
}
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 {
2016-05-11 20:51:18 +00:00
Data : map [ string ] interface { } {
2016-08-02 14:33:50 +00:00
"period" : int64 ( role . Period . Seconds ( ) ) ,
"explicit_max_ttl" : int64 ( role . ExplicitMaxTTL . Seconds ( ) ) ,
"disallowed_policies" : role . DisallowedPolicies ,
"allowed_policies" : role . AllowedPolicies ,
"name" : role . Name ,
"orphan" : role . Orphan ,
"path_suffix" : role . PathSuffix ,
"renewable" : role . Renewable ,
2016-05-11 20:51:18 +00:00
} ,
2016-04-14 10:10:22 +00:00
}
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-06-08 19:17:22 +00:00
renewableInt , ok := data . GetOk ( "renewable" )
if ok {
entry . Renewable = renewableInt . ( bool )
} else if req . Operation == logical . CreateOperation {
entry . Renewable = data . Get ( "renewable" ) . ( bool )
}
2016-05-11 20:51:18 +00:00
var resp * logical . Response
explicitMaxTTLInt , ok := data . GetOk ( "explicit_max_ttl" )
if ok {
entry . ExplicitMaxTTL = time . Second * time . Duration ( explicitMaxTTLInt . ( int ) )
} else if req . Operation == logical . CreateOperation {
entry . ExplicitMaxTTL = time . Second * time . Duration ( data . Get ( "explicit_max_ttl" ) . ( int ) )
}
if entry . ExplicitMaxTTL != 0 {
sysView := ts . System ( )
if sysView . MaxLeaseTTL ( ) != time . Duration ( 0 ) && entry . ExplicitMaxTTL > sysView . MaxLeaseTTL ( ) {
if resp == nil {
resp = & logical . Response { }
}
resp . AddWarning ( fmt . Sprintf (
"Given explicit max TTL of %d is greater than system/mount allowed value of %d seconds; until this is fixed attempting to create tokens against this role will result in an error" ,
entry . ExplicitMaxTTL . Seconds ( ) , sysView . MaxLeaseTTL ( ) . Seconds ( ) ) )
}
}
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 {
2016-05-11 20:51:18 +00:00
return logical . ErrorResponse ( fmt . Sprintf (
"given role path suffix contains invalid characters; must match %s" ,
pathSuffixSanitize . String ( ) ) ) , nil
2016-03-09 16:59:54 +00:00
}
entry . PathSuffix = pathSuffix
}
} else if req . Operation == logical . CreateOperation {
entry . PathSuffix = data . Get ( "path_suffix" ) . ( string )
}
2016-05-13 16:20:10 +00:00
allowedPoliciesStr , ok := data . GetOk ( "allowed_policies" )
2016-03-09 16:59:54 +00:00
if ok {
2016-08-02 14:33:50 +00:00
entry . AllowedPolicies = policyutil . SanitizePolicies ( strings . Split ( allowedPoliciesStr . ( string ) , "," ) , false )
} else if req . Operation == logical . CreateOperation {
entry . AllowedPolicies = policyutil . SanitizePolicies ( strings . Split ( data . Get ( "allowed_policies" ) . ( string ) , "," ) , false )
}
disallowedPoliciesStr , ok := data . GetOk ( "disallowed_policies" )
if ok {
entry . DisallowedPolicies = policyutil . SanitizePolicies ( strings . Split ( disallowedPoliciesStr . ( string ) , "," ) , false )
2016-03-09 16:59:54 +00:00
} else if req . Operation == logical . CreateOperation {
2016-08-02 14:33:50 +00:00
entry . DisallowedPolicies = policyutil . SanitizePolicies ( strings . Split ( data . Get ( "disallowed_policies" ) . ( string ) , "," ) , false )
}
if len ( entry . AllowedPolicies ) > 0 && len ( entry . DisallowedPolicies ) > 0 {
if resp == nil {
resp = & logical . Response { }
}
2016-08-02 17:33:03 +00:00
resp . AddWarning ( "Both 'allowed_policies' and 'disallowed_policies' are set; only 'allowed_policies' will take effect" )
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
}
2016-05-11 20:51:18 +00:00
return resp , nil
2016-02-29 18:27:31 +00:00
}
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. `
2016-08-02 20:33:22 +00:00
tokenAllowedPoliciesHelp = ` If set , tokens can be created with any subset of the policies in this
list , rather than the normal semantics of tokens being a subset of the
calling token ' s policies . The parameter is a comma - delimited string of
policy names . If this and ' disallowed_policies ' are both set , only this
option takes effect . `
tokenDisallowedPoliciesHelp = ` If set , successful token creation via this role will require that
no policies in the given list are requested . If both
' disallowed_policies ' and ' allowed_policies ' are set , this option has
no effect . The parameter is a comma - delimited string of policy names . `
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
2016-05-11 20:51:18 +00:00
expression . `
tokenExplicitMaxTTLHelp = ` If set , tokens created via this role
carry an explicit maximum TTL . During renewal ,
the current maximum TTL values of the role
and the mount are not checked for changes ,
and any updates to these values will have
no effect on the token being renewed . `
2016-06-08 19:17:22 +00:00
tokenRenewableHelp = ` Tokens created via this role will be
renewable or not according to this value .
Defaults to "true" . `
2016-08-01 17:07:41 +00:00
tokenListAccessorsHelp = ` List token accessors , which can then be
be used to iterate and discover their properities
or revoke them . Because this can be used to
cause a denial of service , this endpoint
requires ' sudo ' capability in addition to
' list ' . `
2015-03-24 22:10:46 +00:00
)