mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Batch tokens (#755)
This commit is contained in:
		| @@ -271,4 +271,5 @@ type TokenCreateRequest struct { | ||||
| 	DisplayName     string            `json:"display_name"` | ||||
| 	NumUses         int               `json:"num_uses"` | ||||
| 	Renewable       *bool             `json:"renewable,omitempty"` | ||||
| 	Type            string            `json:"type"` | ||||
| } | ||||
|   | ||||
| @@ -73,46 +73,8 @@ func (c *Sys) DisableAuth(path string) error { | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Structures for the requests/resposne are all down here. They aren't | ||||
| // individually documented because the map almost directly to the raw HTTP API | ||||
| // documentation. Please refer to that documentation for more details. | ||||
|  | ||||
| type EnableAuthOptions struct { | ||||
| 	Type        string            `json:"type"` | ||||
| 	Description string            `json:"description"` | ||||
| 	Config      AuthConfigInput   `json:"config"` | ||||
| 	Local       bool              `json:"local"` | ||||
| 	PluginName  string            `json:"plugin_name,omitempty"` | ||||
| 	SealWrap    bool              `json:"seal_wrap" mapstructure:"seal_wrap"` | ||||
| 	Options     map[string]string `json:"options" mapstructure:"options"` | ||||
| } | ||||
|  | ||||
| type AuthConfigInput struct { | ||||
| 	DefaultLeaseTTL           string   `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	MaxLeaseTTL               string   `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	PluginName                string   `json:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||
| 	AuditNonHMACRequestKeys   []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
|  | ||||
| type AuthMount struct { | ||||
| 	Type        string            `json:"type" mapstructure:"type"` | ||||
| 	Description string            `json:"description" mapstructure:"description"` | ||||
| 	Accessor    string            `json:"accessor" mapstructure:"accessor"` | ||||
| 	Config      AuthConfigOutput  `json:"config" mapstructure:"config"` | ||||
| 	Local       bool              `json:"local" mapstructure:"local"` | ||||
| 	SealWrap    bool              `json:"seal_wrap" mapstructure:"seal_wrap"` | ||||
| 	Options     map[string]string `json:"options" mapstructure:"options"` | ||||
| } | ||||
|  | ||||
| type AuthConfigOutput struct { | ||||
| 	DefaultLeaseTTL           int      `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	MaxLeaseTTL               int      `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	PluginName                string   `json:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||
| 	AuditNonHMACRequestKeys   []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| } | ||||
| // Rather than duplicate, we can use modern Go's type aliasing | ||||
| type EnableAuthOptions = MountInput | ||||
| type AuthConfigInput = MountConfigInput | ||||
| type AuthMount = MountOutput | ||||
| type AuthConfigOutput = MountConfigOutput | ||||
|   | ||||
| @@ -132,10 +132,10 @@ type MountInput struct { | ||||
| 	Type        string            `json:"type"` | ||||
| 	Description string            `json:"description"` | ||||
| 	Config      MountConfigInput  `json:"config"` | ||||
| 	Options     map[string]string `json:"options"` | ||||
| 	Local       bool              `json:"local"` | ||||
| 	PluginName  string            `json:"plugin_name,omitempty"` | ||||
| 	SealWrap    bool              `json:"seal_wrap" mapstructure:"seal_wrap"` | ||||
| 	Options     map[string]string `json:"options"` | ||||
| } | ||||
|  | ||||
| type MountConfigInput struct { | ||||
| @@ -149,6 +149,7 @@ type MountConfigInput struct { | ||||
| 	AuditNonHMACResponseKeys  []string          `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string            `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string          `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	TokenType                 string            `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| } | ||||
|  | ||||
| type MountOutput struct { | ||||
| @@ -170,4 +171,5 @@ type MountConfigOutput struct { | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	TokenType                 string   `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| } | ||||
|   | ||||
| @@ -134,6 +134,7 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config | ||||
| 			Metadata:                  auth.Metadata, | ||||
| 			EntityID:                  auth.EntityID, | ||||
| 			RemainingUses:             req.ClientTokenRemainingUses, | ||||
| 			TokenType:                 auth.TokenType.String(), | ||||
| 		}, | ||||
|  | ||||
| 		Request: AuditRequest{ | ||||
| @@ -304,6 +305,8 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config | ||||
| 			ExternalNamespacePolicies: resp.Auth.ExternalNamespacePolicies, | ||||
| 			Metadata:                  resp.Auth.Metadata, | ||||
| 			NumUses:                   resp.Auth.NumUses, | ||||
| 			EntityID:                  resp.Auth.EntityID, | ||||
| 			TokenType:                 resp.Auth.TokenType.String(), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -334,16 +337,17 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config | ||||
| 		Type:  "response", | ||||
| 		Error: errString, | ||||
| 		Auth: AuditAuth{ | ||||
| 			ClientToken:               auth.ClientToken, | ||||
| 			Accessor:                  auth.Accessor, | ||||
| 			DisplayName:               auth.DisplayName, | ||||
| 			Policies:                  auth.Policies, | ||||
| 			TokenPolicies:             auth.TokenPolicies, | ||||
| 			IdentityPolicies:          auth.IdentityPolicies, | ||||
| 			ExternalNamespacePolicies: auth.ExternalNamespacePolicies, | ||||
| 			Metadata:                  auth.Metadata, | ||||
| 			ClientToken:               auth.ClientToken, | ||||
| 			Accessor:                  auth.Accessor, | ||||
| 			RemainingUses:             req.ClientTokenRemainingUses, | ||||
| 			EntityID:                  auth.EntityID, | ||||
| 			TokenType:                 auth.TokenType.String(), | ||||
| 		}, | ||||
|  | ||||
| 		Request: AuditRequest{ | ||||
| @@ -437,6 +441,7 @@ type AuditAuth struct { | ||||
| 	NumUses                   int                 `json:"num_uses,omitempty"` | ||||
| 	RemainingUses             int                 `json:"remaining_uses,omitempty"` | ||||
| 	EntityID                  string              `json:"entity_id"` | ||||
| 	TokenType                 string              `json:"token_type"` | ||||
| } | ||||
|  | ||||
| type AuditSecret struct { | ||||
|   | ||||
| @@ -37,7 +37,13 @@ func TestFormatJSON_formatRequest(t *testing.T) { | ||||
| 		ExpectedStr string | ||||
| 	}{ | ||||
| 		"auth, request": { | ||||
| 			&logical.Auth{ClientToken: "foo", Accessor: "bar", DisplayName: "testtoken", Policies: []string{"root"}}, | ||||
| 			&logical.Auth{ | ||||
| 				ClientToken: "foo", | ||||
| 				Accessor:    "bar", | ||||
| 				DisplayName: "testtoken", | ||||
| 				Policies:    []string{"root"}, | ||||
| 				TokenType:   logical.TokenTypeService, | ||||
| 			}, | ||||
| 			&logical.Request{ | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      "/foo", | ||||
| @@ -56,7 +62,13 @@ func TestFormatJSON_formatRequest(t *testing.T) { | ||||
| 			expectedResultStr, | ||||
| 		}, | ||||
| 		"auth, request with prefix": { | ||||
| 			&logical.Auth{ClientToken: "foo", Accessor: "bar", DisplayName: "testtoken", Policies: []string{"root"}}, | ||||
| 			&logical.Auth{ | ||||
| 				ClientToken: "foo", | ||||
| 				Accessor:    "bar", | ||||
| 				DisplayName: "testtoken", | ||||
| 				Policies:    []string{"root"}, | ||||
| 				TokenType:   logical.TokenTypeService, | ||||
| 			}, | ||||
| 			&logical.Request{ | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      "/foo", | ||||
| @@ -127,5 +139,5 @@ func TestFormatJSON_formatRequest(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const testFormatJSONReqBasicStrFmt = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"client_token":"%s","accessor":"bar","display_name":"testtoken","policies":["root"],"metadata":null},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1","headers":{"foo":["bar"]}},"error":"this is an error"} | ||||
| const testFormatJSONReqBasicStrFmt = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"client_token":"%s","accessor":"bar","display_name":"testtoken","policies":["root"],"metadata":null,"entity_id":"","token_type":"service"},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1","headers":{"foo":["bar"]}},"error":"this is an error"} | ||||
| ` | ||||
|   | ||||
| @@ -36,7 +36,13 @@ func TestFormatJSONx_formatRequest(t *testing.T) { | ||||
| 		ExpectedStr string | ||||
| 	}{ | ||||
| 		"auth, request": { | ||||
| 			&logical.Auth{ClientToken: "foo", Accessor: "bar", DisplayName: "testtoken", Policies: []string{"root"}}, | ||||
| 			&logical.Auth{ | ||||
| 				ClientToken: "foo", | ||||
| 				Accessor:    "bar", | ||||
| 				DisplayName: "testtoken", | ||||
| 				Policies:    []string{"root"}, | ||||
| 				TokenType:   logical.TokenTypeService, | ||||
| 			}, | ||||
| 			&logical.Request{ | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      "/foo", | ||||
| @@ -53,11 +59,17 @@ func TestFormatJSONx_formatRequest(t *testing.T) { | ||||
| 			errors.New("this is an error"), | ||||
| 			"", | ||||
| 			"", | ||||
| 			fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:object name="namespace"><json:string name="id">root</json:string><json:string name="path"></json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">false</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`, | ||||
| 			fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:object name="namespace"><json:string name="id">root</json:string><json:string name="path"></json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">false</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`, | ||||
| 				fooSalted), | ||||
| 		}, | ||||
| 		"auth, request with prefix": { | ||||
| 			&logical.Auth{ClientToken: "foo", Accessor: "bar", DisplayName: "testtoken", Policies: []string{"root"}}, | ||||
| 			&logical.Auth{ | ||||
| 				ClientToken: "foo", | ||||
| 				Accessor:    "bar", | ||||
| 				DisplayName: "testtoken", | ||||
| 				Policies:    []string{"root"}, | ||||
| 				TokenType:   logical.TokenTypeService, | ||||
| 			}, | ||||
| 			&logical.Request{ | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      "/foo", | ||||
| @@ -74,7 +86,7 @@ func TestFormatJSONx_formatRequest(t *testing.T) { | ||||
| 			errors.New("this is an error"), | ||||
| 			"", | ||||
| 			"@cee: ", | ||||
| 			fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:object name="namespace"><json:string name="id">root</json:string><json:string name="path"></json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">false</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`, | ||||
| 			fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:object name="namespace"><json:string name="id">root</json:string><json:string name="path"></json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">false</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`, | ||||
| 				fooSalted), | ||||
| 		}, | ||||
| 	} | ||||
|   | ||||
| @@ -304,6 +304,15 @@ func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, dat | ||||
| 		BoundCIDRs: tokenBoundCIDRs, | ||||
| 	} | ||||
|  | ||||
| 	switch role.TokenType { | ||||
| 	case "default": | ||||
| 		auth.TokenType = logical.TokenTypeDefault | ||||
| 	case "batch": | ||||
| 		auth.TokenType = logical.TokenTypeBatch | ||||
| 	case "service": | ||||
| 		auth.TokenType = logical.TokenTypeService | ||||
| 	} | ||||
|  | ||||
| 	return &logical.Response{ | ||||
| 		Auth: auth, | ||||
| 	}, nil | ||||
|   | ||||
| @@ -84,6 +84,9 @@ type roleStorageEntry struct { | ||||
| 	// SecretIDPrefix is the storage prefix for persisting secret IDs. This | ||||
| 	// differs based on whether the secret IDs are cluster local or not. | ||||
| 	SecretIDPrefix string `json:"secret_id_prefix" mapstructure:"secret_id_prefix"` | ||||
|  | ||||
| 	// TokenType is the type of token to generate | ||||
| 	TokenType string `json:"token_type" mapstructure:"token_type"` | ||||
| } | ||||
|  | ||||
| // roleIDStorageEntry represents the reverse mapping from RoleID to Role | ||||
| @@ -196,6 +199,11 @@ TTL will be set to the value of this parameter.`, | ||||
| 					Description: `If set, the secret IDs generated using this role will be cluster local. This | ||||
| can only be set during role creation and once set, it can't be reset later.`, | ||||
| 				}, | ||||
| 				"token_type": &framework.FieldSchema{ | ||||
| 					Type:        framework.TypeString, | ||||
| 					Default:     "default", | ||||
| 					Description: `The type of token to generate ("service" or "batch"), or "default" to use the default`, | ||||
| 				}, | ||||
| 			}, | ||||
| 			ExistenceCheck: b.pathRoleExistenceCheck, | ||||
| 			Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| @@ -1007,6 +1015,30 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request | ||||
| 		role.TokenMaxTTL = time.Second * time.Duration(data.Get("token_max_ttl").(int)) | ||||
| 	} | ||||
|  | ||||
| 	tokenType := role.TokenType | ||||
| 	if tokenTypeRaw, ok := data.GetOk("token_type"); ok { | ||||
| 		tokenType = tokenTypeRaw.(string) | ||||
| 		switch tokenType { | ||||
| 		case "": | ||||
| 			tokenType = "default" | ||||
| 		case "service", "batch", "default": | ||||
| 		default: | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("invalid 'token_type' value %q", tokenType)), nil | ||||
| 		} | ||||
| 	} else if req.Operation == logical.CreateOperation { | ||||
| 		tokenType = data.Get("token_type").(string) | ||||
| 	} | ||||
| 	role.TokenType = tokenType | ||||
|  | ||||
| 	if role.TokenType == "batch" { | ||||
| 		if role.Period != 0 { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate periodic tokens"), nil | ||||
| 		} | ||||
| 		if role.TokenNumUses != 0 { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate tokens with limited use count"), nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Check that the TokenTTL value provided is less than the TokenMaxTTL. | ||||
| 	// Sanitizing the TTL and MaxTTL is not required now and can be performed | ||||
| 	// at credential issue time. | ||||
| @@ -1061,6 +1093,7 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data * | ||||
| 		"token_num_uses":        role.TokenNumUses, | ||||
| 		"token_ttl":             role.TokenTTL / time.Second, | ||||
| 		"local_secret_ids":      false, | ||||
| 		"token_type":            role.TokenType, | ||||
| 	} | ||||
|  | ||||
| 	if role.SecretIDPrefix == secretIDLocalPrefix { | ||||
|   | ||||
| @@ -1159,6 +1159,7 @@ func TestAppRole_RoleCRUD(t *testing.T) { | ||||
| 		"secret_id_bound_cidrs": []string{"127.0.0.1/32", "127.0.0.1/16"}, | ||||
| 		"bound_cidr_list":       []string{"127.0.0.1/32", "127.0.0.1/16"}, // returned for backwards compatibility | ||||
| 		"token_bound_cidrs":     []string{}, | ||||
| 		"token_type":            "default", | ||||
| 	} | ||||
|  | ||||
| 	var expectedStruct roleStorageEntry | ||||
| @@ -1637,6 +1638,7 @@ func TestAppRole_RoleWithTokenBoundCIDRsCRUD(t *testing.T) { | ||||
| 		"token_bound_cidrs":     []string{"127.0.0.1/32", "127.0.0.1/16"}, | ||||
| 		"secret_id_bound_cidrs": []string{"127.0.0.1/32", "127.0.0.1/16"}, | ||||
| 		"bound_cidr_list":       []string{"127.0.0.1/32", "127.0.0.1/16"}, // provided for backwards compatibility | ||||
| 		"token_type":            "default", | ||||
| 	} | ||||
|  | ||||
| 	var expectedStruct roleStorageEntry | ||||
|   | ||||
| @@ -30,6 +30,7 @@ type AuthEnableCommand struct { | ||||
| 	flagOptions                   map[string]string | ||||
| 	flagLocal                     bool | ||||
| 	flagSealWrap                  bool | ||||
| 	flagTokenType                 string | ||||
| 	flagVersion                   int | ||||
| } | ||||
|  | ||||
| @@ -162,6 +163,12 @@ func (c *AuthEnableCommand) Flags() *FlagSets { | ||||
| 		Usage:   "Enable seal wrapping of critical values in the secrets engine.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:   flagNameTokenType, | ||||
| 		Target: &c.flagTokenType, | ||||
| 		Usage:  "Sets a forced token type for the mount.", | ||||
| 	}) | ||||
|  | ||||
| 	f.IntVar(&IntVar{ | ||||
| 		Name:    "version", | ||||
| 		Target:  &c.flagVersion, | ||||
| @@ -257,6 +264,10 @@ func (c *AuthEnableCommand) Run(args []string) int { | ||||
| 		if fl.Name == flagNamePassthroughRequestHeaders { | ||||
| 			authOpts.Config.PassthroughRequestHeaders = c.flagPassthroughRequestHeaders | ||||
| 		} | ||||
|  | ||||
| 		if fl.Name == flagNameTokenType { | ||||
| 			authOpts.Config.TokenType = c.flagTokenType | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	if err := client.Sys().EnableAuthWithOptions(authPath, authOpts); err != nil { | ||||
|   | ||||
| @@ -143,7 +143,7 @@ func (c *AuthListCommand) detailedMounts(auths map[string]*api.AuthMount) []stri | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out := []string{"Path | Type | Accessor | Plugin | Default TTL | Max TTL | Replication | Seal Wrap | Options | Description"} | ||||
| 	out := []string{"Path | Type | Accessor | Plugin | Default TTL | Max TTL | Token Type | Replication | Seal Wrap | Options | Description"} | ||||
| 	for _, path := range paths { | ||||
| 		mount := auths[path] | ||||
|  | ||||
| @@ -155,13 +155,14 @@ func (c *AuthListCommand) detailedMounts(auths map[string]*api.AuthMount) []stri | ||||
| 			replication = "local" | ||||
| 		} | ||||
|  | ||||
| 		out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %t | %v | %s", | ||||
| 		out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %s | %t | %v | %s", | ||||
| 			path, | ||||
| 			mount.Type, | ||||
| 			mount.Accessor, | ||||
| 			mount.Config.PluginName, | ||||
| 			defaultTTL, | ||||
| 			maxTTL, | ||||
| 			mount.Config.TokenType, | ||||
| 			replication, | ||||
| 			mount.SealWrap, | ||||
| 			mount.Options, | ||||
|   | ||||
| @@ -25,6 +25,7 @@ type AuthTuneCommand struct { | ||||
| 	flagListingVisibility        string | ||||
| 	flagMaxLeaseTTL              time.Duration | ||||
| 	flagOptions                  map[string]string | ||||
| 	flagTokenType                string | ||||
| 	flagVersion                  int | ||||
| } | ||||
|  | ||||
| @@ -112,6 +113,12 @@ func (c *AuthTuneCommand) Flags() *FlagSets { | ||||
| 			"This can be specified multiple times.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:   flagNameTokenType, | ||||
| 		Target: &c.flagTokenType, | ||||
| 		Usage:  "Sets a forced token type for the mount.", | ||||
| 	}) | ||||
|  | ||||
| 	f.IntVar(&IntVar{ | ||||
| 		Name:    "version", | ||||
| 		Target:  &c.flagVersion, | ||||
| @@ -184,6 +191,10 @@ func (c *AuthTuneCommand) Run(args []string) int { | ||||
| 		if fl.Name == flagNameListingVisibility { | ||||
| 			mountConfigInput.ListingVisibility = c.flagListingVisibility | ||||
| 		} | ||||
|  | ||||
| 		if fl.Name == flagNameTokenType { | ||||
| 			mountConfigInput.TokenType = c.flagTokenType | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	// Append /auth (since that's where auths live) and a trailing slash to | ||||
|   | ||||
| @@ -92,6 +92,8 @@ const ( | ||||
| 	flagNameListingVisibility = "listing-visibility" | ||||
| 	// flagNamePassthroughRequestHeaders is the flag name used to set passthrough request headers to the backend | ||||
| 	flagNamePassthroughRequestHeaders = "passthrough-request-headers" | ||||
| 	// flagNameTokenType is the flag name used to force a specific token type | ||||
| 	flagNameTokenType = "token-type" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
|   | ||||
| @@ -26,6 +26,7 @@ type TokenCreateCommand struct { | ||||
| 	flagNoDefaultPolicy bool | ||||
| 	flagUseLimit        int | ||||
| 	flagRole            string | ||||
| 	flagType            string | ||||
| 	flagMetadata        map[string]string | ||||
| 	flagPolicies        []string | ||||
|  | ||||
| @@ -153,6 +154,13 @@ func (c *TokenCreateCommand) Flags() *FlagSets { | ||||
| 			"must have permission for \"auth/token/create/<role>\".", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:    "type", | ||||
| 		Target:  &c.flagType, | ||||
| 		Default: "service", | ||||
| 		Usage:   `The type of token to create. Can be "service" or "batch".`, | ||||
| 	}) | ||||
|  | ||||
| 	f.StringMapVar(&StringMapVar{ | ||||
| 		Name:       "metadata", | ||||
| 		Target:     &c.flagMetadata, | ||||
| @@ -213,6 +221,10 @@ func (c *TokenCreateCommand) Run(args []string) int { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if c.flagType == "batch" { | ||||
| 		c.flagRenewable = false | ||||
| 	} | ||||
|  | ||||
| 	client, err := c.Client() | ||||
| 	if err != nil { | ||||
| 		c.UI.Error(err.Error()) | ||||
| @@ -231,6 +243,7 @@ func (c *TokenCreateCommand) Run(args []string) int { | ||||
| 		Renewable:       &c.flagRenewable, | ||||
| 		ExplicitMaxTTL:  c.flagExplicitMaxTTL.String(), | ||||
| 		Period:          c.flagPeriod.String(), | ||||
| 		Type:            c.flagType, | ||||
| 	} | ||||
|  | ||||
| 	var secret *api.Secret | ||||
|   | ||||
| @@ -104,7 +104,18 @@ func Canonicalize(nsPath string) string { | ||||
| func SplitIDFromString(input string) (string, string) { | ||||
| 	prefix := "" | ||||
| 	slashIdx := strings.LastIndex(input, "/") | ||||
| 	if slashIdx > 0 { | ||||
|  | ||||
| 	switch { | ||||
| 	case strings.HasPrefix(input, "b."): | ||||
| 		prefix = "b." | ||||
| 		input = input[2:] | ||||
|  | ||||
| 	case strings.HasPrefix(input, "s."): | ||||
| 		prefix = "s." | ||||
| 		input = input[2:] | ||||
|  | ||||
| 	case slashIdx > 0: | ||||
| 		// Leases will never have a b./s. to start | ||||
| 		if slashIdx == len(input)-1 { | ||||
| 			return input, "" | ||||
| 		} | ||||
|   | ||||
| @@ -48,6 +48,21 @@ func TestSplitIDFromString(t *testing.T) { | ||||
| 			"", | ||||
| 			"foo.foo/", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"b.foo", | ||||
| 			"", | ||||
| 			"b.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"s.foo", | ||||
| 			"", | ||||
| 			"s.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"t.foo", | ||||
| 			"foo", | ||||
| 			"t", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, c := range tcases { | ||||
|   | ||||
| @@ -5,14 +5,33 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/vault/helper/consts" | ||||
| 	"github.com/hashicorp/vault/helper/xor" | ||||
| 	vaulthttp "github.com/hashicorp/vault/http" | ||||
| 	"github.com/hashicorp/vault/physical" | ||||
| 	"github.com/hashicorp/vault/physical/inmem" | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| 	"github.com/mitchellh/go-testing-interface" | ||||
| 	testing "github.com/mitchellh/go-testing-interface" | ||||
| ) | ||||
|  | ||||
| type ReplicatedTestClusters struct { | ||||
| 	PerfPrimaryCluster     *vault.TestCluster | ||||
| 	PerfSecondaryCluster   *vault.TestCluster | ||||
| 	PerfPrimaryDRCluster   *vault.TestCluster | ||||
| 	PerfSecondaryDRCluster *vault.TestCluster | ||||
| } | ||||
|  | ||||
| func (r *ReplicatedTestClusters) Cleanup() { | ||||
| 	r.PerfPrimaryCluster.Cleanup() | ||||
| 	r.PerfSecondaryCluster.Cleanup() | ||||
| 	r.PerfPrimaryDRCluster.Cleanup() | ||||
| 	r.PerfSecondaryDRCluster.Cleanup() | ||||
| } | ||||
|  | ||||
| // Generates a root token on the target cluster. | ||||
| func GenerateRoot(t testing.T, cluster *vault.TestCluster, drToken bool) string { | ||||
| 	token, err := GenerateRootWithError(t, cluster, drToken) | ||||
| @@ -127,6 +146,65 @@ func WaitForReplicationState(t testing.T, c *vault.Core, state consts.Replicatio | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetClusterAndCore(t testing.T, logger log.Logger) (*vault.TestCluster, *vault.TestClusterCore) { | ||||
| 	inm, err := inmem.NewTransactionalInmem(nil, logger) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	inmha, err := inmem.NewInmemHA(nil, logger) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	coreConfig := &vault.CoreConfig{ | ||||
| 		Physical:   inm, | ||||
| 		HAPhysical: inmha.(physical.HABackend), | ||||
| 	} | ||||
|  | ||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ | ||||
| 		HandlerFunc: vaulthttp.Handler, | ||||
| 		Logger:      logger, | ||||
| 	}) | ||||
| 	cluster.Start() | ||||
|  | ||||
| 	cores := cluster.Cores | ||||
| 	core := cores[0] | ||||
|  | ||||
| 	vault.TestWaitActive(t, core.Core) | ||||
|  | ||||
| 	return cluster, core | ||||
| } | ||||
|  | ||||
| func GetFourReplicatedClusters(t testing.T) *ReplicatedTestClusters { | ||||
| 	ret := &ReplicatedTestClusters{} | ||||
|  | ||||
| 	logger := log.New(&log.LoggerOptions{ | ||||
| 		Mutex: &sync.Mutex{}, | ||||
| 		Level: log.Trace, | ||||
| 	}) | ||||
| 	// Set this lower so that state populates quickly to standby nodes | ||||
| 	vault.HeartbeatInterval = 2 * time.Second | ||||
|  | ||||
| 	ret.PerfPrimaryCluster, _ = GetClusterAndCore(t, logger.Named("perf-pri")) | ||||
|  | ||||
| 	ret.PerfSecondaryCluster, _ = GetClusterAndCore(t, logger.Named("perf-sec")) | ||||
|  | ||||
| 	ret.PerfPrimaryDRCluster, _ = GetClusterAndCore(t, logger.Named("perf-pri-dr")) | ||||
|  | ||||
| 	ret.PerfSecondaryDRCluster, _ = GetClusterAndCore(t, logger.Named("perf-sec-dr")) | ||||
|  | ||||
| 	SetupFourClusterReplication(t, ret.PerfPrimaryCluster, ret.PerfSecondaryCluster, ret.PerfPrimaryDRCluster, ret.PerfSecondaryDRCluster) | ||||
|  | ||||
| 	// Wait until poison pills have been read | ||||
| 	time.Sleep(45 * time.Second) | ||||
| 	EnsureCoresUnsealed(t, ret.PerfPrimaryCluster) | ||||
| 	EnsureCoresUnsealed(t, ret.PerfSecondaryCluster) | ||||
| 	EnsureCoresUnsealed(t, ret.PerfPrimaryDRCluster) | ||||
| 	EnsureCoresUnsealed(t, ret.PerfSecondaryDRCluster) | ||||
|  | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func SetupFourClusterReplication(t testing.T, perfPrimary, perfSecondary, perfDRSecondary, perfSecondaryDRSecondary *vault.TestCluster) { | ||||
| 	// Enable dr primary | ||||
| 	_, err := perfPrimary.Cores[0].Client.Logical().Write("sys/replication/dr/primary/enable", nil) | ||||
| @@ -137,7 +215,7 @@ func SetupFourClusterReplication(t testing.T, perfPrimary, perfSecondary, perfDR | ||||
| 	WaitForReplicationState(t, perfPrimary.Cores[0].Core, consts.ReplicationDRPrimary) | ||||
|  | ||||
| 	// Enable performance primary | ||||
| 	_, err = perfPrimary.Cores[0].Client.Logical().Write("sys/replication/primary/enable", nil) | ||||
| 	_, err = perfPrimary.Cores[0].Client.Logical().Write("sys/replication/performance/primary/enable", nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @@ -167,7 +245,7 @@ func SetupFourClusterReplication(t testing.T, perfPrimary, perfSecondary, perfDR | ||||
| 	EnsureCoresUnsealed(t, perfDRSecondary) | ||||
|  | ||||
| 	// get performance token | ||||
| 	secret, err = perfPrimary.Cores[0].Client.Logical().Write("sys/replication/primary/secondary-token", map[string]interface{}{ | ||||
| 	secret, err = perfPrimary.Cores[0].Client.Logical().Write("sys/replication/performance/primary/secondary-token", map[string]interface{}{ | ||||
| 		"id": "1", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| @@ -177,7 +255,7 @@ func SetupFourClusterReplication(t testing.T, perfPrimary, perfSecondary, perfDR | ||||
| 	token = secret.WrapInfo.Token | ||||
|  | ||||
| 	// enable performace secondary | ||||
| 	secret, err = perfSecondary.Cores[0].Client.Logical().Write("sys/replication/secondary/enable", map[string]interface{}{ | ||||
| 	secret, err = perfSecondary.Cores[0].Client.Logical().Write("sys/replication/performance/secondary/enable", map[string]interface{}{ | ||||
| 		"token":   token, | ||||
| 		"ca_file": perfPrimary.CACertPEMFile, | ||||
| 	}) | ||||
|   | ||||
| @@ -421,9 +421,16 @@ func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handle | ||||
| 				return | ||||
| 			} | ||||
| 			path := ns.TrimmedPath(r.URL.Path[len("/v1/"):]) | ||||
| 			if !perfStandbyAlwaysForwardPaths.HasPath(path) { | ||||
| 			switch { | ||||
| 			case !perfStandbyAlwaysForwardPaths.HasPath(path): | ||||
| 				handler.ServeHTTP(w, r) | ||||
| 				return | ||||
| 			case strings.HasPrefix(path, "auth/token/create/"): | ||||
| 				isBatch, err := core.IsBatchTokenCreationRequest(r.Context(), path) | ||||
| 				if err == nil && isBatch { | ||||
| 					handler.ServeHTTP(w, r) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-test/deep" | ||||
| 	log "github.com/hashicorp/go-hclog" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/helper/consts" | ||||
| @@ -58,8 +59,8 @@ func TestLogical(t *testing.T) { | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	delete(actual, "lease_id") | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad:\nactual:\n%#v\nexpected:\n%#v", actual, expected) | ||||
| 	if diff := deep.Equal(actual, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
|  | ||||
| 	// DELETE | ||||
| @@ -163,6 +164,7 @@ func TestLogical_StandbyRedirect(t *testing.T) { | ||||
| 			"explicit_max_ttl": json.Number("0"), | ||||
| 			"expire_time":      nil, | ||||
| 			"entity_id":        "", | ||||
| 			"type":             "service", | ||||
| 		}, | ||||
| 		"warnings":  nilWarnings, | ||||
| 		"wrap_info": nil, | ||||
| @@ -177,8 +179,8 @@ func TestLogical_StandbyRedirect(t *testing.T) { | ||||
| 	actual["data"] = actualDataMap | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| 	delete(actual, "lease_id") | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad: got %#v; expected %#v", actual, expected) | ||||
| 	if diff := deep.Equal(actual, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
|  | ||||
| 	//// DELETE to standby | ||||
| @@ -214,6 +216,7 @@ func TestLogical_CreateToken(t *testing.T) { | ||||
| 			"lease_duration": json.Number("0"), | ||||
| 			"renewable":      false, | ||||
| 			"entity_id":      "", | ||||
| 			"token_type":     "service", | ||||
| 		}, | ||||
| 		"warnings": nilWarnings, | ||||
| 	} | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-test/deep" | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| ) | ||||
|  | ||||
| @@ -32,6 +33,8 @@ func TestSysAuth(t *testing.T) { | ||||
| 					"default_lease_ttl": json.Number("0"), | ||||
| 					"max_lease_ttl":     json.Number("0"), | ||||
| 					"plugin_name":       "", | ||||
| 					"token_type":        "default-service", | ||||
| 					"force_no_cache":    false, | ||||
| 				}, | ||||
| 				"local":     false, | ||||
| 				"seal_wrap": false, | ||||
| @@ -45,6 +48,8 @@ func TestSysAuth(t *testing.T) { | ||||
| 				"default_lease_ttl": json.Number("0"), | ||||
| 				"max_lease_ttl":     json.Number("0"), | ||||
| 				"plugin_name":       "", | ||||
| 				"token_type":        "default-service", | ||||
| 				"force_no_cache":    false, | ||||
| 			}, | ||||
| 			"local":     false, | ||||
| 			"seal_wrap": false, | ||||
| @@ -98,6 +103,8 @@ func TestSysEnableAuth(t *testing.T) { | ||||
| 					"default_lease_ttl": json.Number("0"), | ||||
| 					"max_lease_ttl":     json.Number("0"), | ||||
| 					"plugin_name":       "", | ||||
| 					"token_type":        "default-service", | ||||
| 					"force_no_cache":    false, | ||||
| 				}, | ||||
| 				"local":     false, | ||||
| 				"seal_wrap": false, | ||||
| @@ -110,6 +117,8 @@ func TestSysEnableAuth(t *testing.T) { | ||||
| 					"default_lease_ttl": json.Number("0"), | ||||
| 					"max_lease_ttl":     json.Number("0"), | ||||
| 					"plugin_name":       "", | ||||
| 					"force_no_cache":    false, | ||||
| 					"token_type":        "default-service", | ||||
| 				}, | ||||
| 				"local":     false, | ||||
| 				"seal_wrap": false, | ||||
| @@ -123,6 +132,8 @@ func TestSysEnableAuth(t *testing.T) { | ||||
| 				"default_lease_ttl": json.Number("0"), | ||||
| 				"max_lease_ttl":     json.Number("0"), | ||||
| 				"plugin_name":       "", | ||||
| 				"token_type":        "default-service", | ||||
| 				"force_no_cache":    false, | ||||
| 			}, | ||||
| 			"local":     false, | ||||
| 			"seal_wrap": false, | ||||
| @@ -135,6 +146,8 @@ func TestSysEnableAuth(t *testing.T) { | ||||
| 				"default_lease_ttl": json.Number("0"), | ||||
| 				"max_lease_ttl":     json.Number("0"), | ||||
| 				"plugin_name":       "", | ||||
| 				"token_type":        "default-service", | ||||
| 				"force_no_cache":    false, | ||||
| 			}, | ||||
| 			"local":     false, | ||||
| 			"seal_wrap": false, | ||||
| @@ -153,8 +166,8 @@ func TestSysEnableAuth(t *testing.T) { | ||||
| 		expected["data"].(map[string]interface{})[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"] | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual) | ||||
| 	if diff := deep.Equal(actual, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -189,6 +202,8 @@ func TestSysDisableAuth(t *testing.T) { | ||||
| 					"default_lease_ttl": json.Number("0"), | ||||
| 					"max_lease_ttl":     json.Number("0"), | ||||
| 					"plugin_name":       "", | ||||
| 					"token_type":        "default-service", | ||||
| 					"force_no_cache":    false, | ||||
| 				}, | ||||
| 				"description": "token based credentials", | ||||
| 				"type":        "token", | ||||
| @@ -202,6 +217,8 @@ func TestSysDisableAuth(t *testing.T) { | ||||
| 				"default_lease_ttl": json.Number("0"), | ||||
| 				"max_lease_ttl":     json.Number("0"), | ||||
| 				"plugin_name":       "", | ||||
| 				"token_type":        "default-service", | ||||
| 				"force_no_cache":    false, | ||||
| 			}, | ||||
| 			"description": "token based credentials", | ||||
| 			"type":        "token", | ||||
| @@ -222,8 +239,8 @@ func TestSysDisableAuth(t *testing.T) { | ||||
| 		expected["data"].(map[string]interface{})[k].(map[string]interface{})["accessor"] = v.(map[string]interface{})["accessor"] | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual) | ||||
| 	if diff := deep.Equal(actual, expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -263,12 +280,14 @@ func TestSysTuneAuth_nonHMACKeys(t *testing.T) { | ||||
| 			"force_no_cache":               false, | ||||
| 			"audit_non_hmac_request_keys":  []interface{}{"foo"}, | ||||
| 			"audit_non_hmac_response_keys": []interface{}{"bar"}, | ||||
| 			"token_type":                   "default-service", | ||||
| 		}, | ||||
| 		"default_lease_ttl":            json.Number("2764800"), | ||||
| 		"max_lease_ttl":                json.Number("2764800"), | ||||
| 		"force_no_cache":               false, | ||||
| 		"audit_non_hmac_request_keys":  []interface{}{"foo"}, | ||||
| 		"audit_non_hmac_response_keys": []interface{}{"bar"}, | ||||
| 		"token_type":                   "default-service", | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| @@ -302,10 +321,12 @@ func TestSysTuneAuth_nonHMACKeys(t *testing.T) { | ||||
| 			"default_lease_ttl": json.Number("2764800"), | ||||
| 			"max_lease_ttl":     json.Number("2764800"), | ||||
| 			"force_no_cache":    false, | ||||
| 			"token_type":        "default-service", | ||||
| 		}, | ||||
| 		"default_lease_ttl": json.Number("2764800"), | ||||
| 		"max_lease_ttl":     json.Number("2764800"), | ||||
| 		"force_no_cache":    false, | ||||
| 		"token_type":        "default-service", | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| @@ -336,10 +357,12 @@ func TestSysTuneAuth_showUIMount(t *testing.T) { | ||||
| 			"default_lease_ttl": json.Number("2764800"), | ||||
| 			"max_lease_ttl":     json.Number("2764800"), | ||||
| 			"force_no_cache":    false, | ||||
| 			"token_type":        "default-service", | ||||
| 		}, | ||||
| 		"default_lease_ttl": json.Number("2764800"), | ||||
| 		"max_lease_ttl":     json.Number("2764800"), | ||||
| 		"force_no_cache":    false, | ||||
| 		"token_type":        "default-service", | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
| @@ -370,11 +393,13 @@ func TestSysTuneAuth_showUIMount(t *testing.T) { | ||||
| 			"max_lease_ttl":      json.Number("2764800"), | ||||
| 			"force_no_cache":     false, | ||||
| 			"listing_visibility": "unauth", | ||||
| 			"token_type":         "default-service", | ||||
| 		}, | ||||
| 		"default_lease_ttl":  json.Number("2764800"), | ||||
| 		"max_lease_ttl":      json.Number("2764800"), | ||||
| 		"force_no_cache":     false, | ||||
| 		"listing_visibility": "unauth", | ||||
| 		"token_type":         "default-service", | ||||
| 	} | ||||
| 	testResponseBody(t, resp, &actual) | ||||
| 	expected["request_id"] = actual["request_id"] | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-test/deep" | ||||
| 	"github.com/hashicorp/vault/helper/pgpkeys" | ||||
| 	"github.com/hashicorp/vault/helper/xor" | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| @@ -333,6 +334,7 @@ func TestSysGenerateRoot_Update_OTP(t *testing.T) { | ||||
| 		"explicit_max_ttl": json.Number("0"), | ||||
| 		"expire_time":      nil, | ||||
| 		"entity_id":        "", | ||||
| 		"type":             "service", | ||||
| 	} | ||||
|  | ||||
| 	resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self") | ||||
| @@ -431,6 +433,7 @@ func TestSysGenerateRoot_Update_PGP(t *testing.T) { | ||||
| 		"explicit_max_ttl": json.Number("0"), | ||||
| 		"expire_time":      nil, | ||||
| 		"entity_id":        "", | ||||
| 		"type":             "service", | ||||
| 	} | ||||
|  | ||||
| 	resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self") | ||||
| @@ -440,7 +443,7 @@ func TestSysGenerateRoot_Update_PGP(t *testing.T) { | ||||
| 	expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"] | ||||
| 	expected["accessor"] = actual["data"].(map[string]interface{})["accessor"] | ||||
|  | ||||
| 	if !reflect.DeepEqual(actual["data"], expected) { | ||||
| 		t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"]) | ||||
| 	if diff := deep.Equal(actual["data"], expected); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -89,6 +89,9 @@ type Auth struct { | ||||
| 	// change the perceived path of the lease, even though they don't change | ||||
| 	// the request path itself. | ||||
| 	CreationPath string `json:"creation_path"` | ||||
|  | ||||
| 	// TokenType is the type of token being requested | ||||
| 	TokenType TokenType `json:"token_type"` | ||||
| } | ||||
|  | ||||
| func (a *Auth) GoString() string { | ||||
|   | ||||
| @@ -528,6 +528,8 @@ type Auth struct { | ||||
| 	// Explicit maximum lifetime for the token. Unlike normal TTLs, the maximum | ||||
| 	// TTL is a hard limit and cannot be exceeded, also counts for periodic tokens. | ||||
| 	ExplicitMaxTTL int64 `sentinel:"" protobuf:"varint,16,opt,name=explicit_max_ttl,json=explicitMaxTtl,proto3" json:"explicit_max_ttl,omitempty"` | ||||
| 	// TokenType is the type of token being requested | ||||
| 	TokenType            uint32   `sentinel:"" protobuf:"varint,17,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{} `json:"-"` | ||||
| 	XXX_unrecognized     []byte   `json:"-"` | ||||
| 	XXX_sizecache        int32    `json:"-"` | ||||
| @@ -670,6 +672,13 @@ func (m *Auth) GetExplicitMaxTTL() int64 { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (m *Auth) GetTokenType() uint32 { | ||||
| 	if m != nil { | ||||
| 		return m.TokenType | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| type TokenEntry struct { | ||||
| 	ID                   string            `sentinel:"" protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` | ||||
| 	Accessor             string            `sentinel:"" protobuf:"bytes,2,opt,name=accessor,proto3" json:"accessor,omitempty"` | ||||
| @@ -688,6 +697,7 @@ type TokenEntry struct { | ||||
| 	BoundCIDRs           []string          `sentinel:"" protobuf:"bytes,15,rep,name=bound_cidrs,json=boundCidrs,proto3" json:"bound_cidrs,omitempty"` | ||||
| 	NamespaceID          string            `sentinel:"" protobuf:"bytes,16,opt,name=namespace_id,json=namespaceID,proto3" json:"namespace_id,omitempty"` | ||||
| 	CubbyholeID          string            `sentinel:"" protobuf:"bytes,17,opt,name=cubbyhole_id,json=cubbyholeId,proto3" json:"cubbyhole_id,omitempty"` | ||||
| 	Type                 uint32            `sentinel:"" protobuf:"varint,18,opt,name=type,proto3" json:"type,omitempty"` | ||||
| 	XXX_NoUnkeyedLiteral struct{}          `json:"-"` | ||||
| 	XXX_unrecognized     []byte            `json:"-"` | ||||
| 	XXX_sizecache        int32             `json:"-"` | ||||
| @@ -837,6 +847,13 @@ func (m *TokenEntry) GetCubbyholeID() string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *TokenEntry) GetType() uint32 { | ||||
| 	if m != nil { | ||||
| 		return m.Type | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| type LeaseOptions struct { | ||||
| 	TTL                  int64                `sentinel:"" protobuf:"varint,1,opt,name=TTL,proto3" json:"TTL,omitempty"` | ||||
| 	Renewable            bool                 `sentinel:"" protobuf:"varint,2,opt,name=renewable,proto3" json:"renewable,omitempty"` | ||||
| @@ -3614,159 +3631,161 @@ var _SystemView_serviceDesc = grpc.ServiceDesc{ | ||||
| func init() { proto.RegisterFile("logical/plugin/pb/backend.proto", fileDescriptor_25821d34acc7c5ef) } | ||||
|  | ||||
| var fileDescriptor_25821d34acc7c5ef = []byte{ | ||||
| 	// 2462 bytes of a gzipped FileDescriptorProto | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0x5f, 0x73, 0xdb, 0xc6, | ||||
| 	0x11, 0x1f, 0xfe, 0x27, 0x97, 0xff, 0xa4, 0xb3, 0xa2, 0xc2, 0x8c, 0x53, 0x33, 0x48, 0x6d, 0x2b, | ||||
| 	0xae, 0x4d, 0xd9, 0x4a, 0xd3, 0x38, 0xed, 0x24, 0x1d, 0x45, 0x56, 0x1c, 0x35, 0x52, 0xa2, 0x81, | ||||
| 	0xe8, 0xa6, 0xff, 0x66, 0x90, 0x23, 0x70, 0xa2, 0x30, 0x02, 0x01, 0xf4, 0x70, 0x90, 0xc5, 0xa7, | ||||
| 	0x7e, 0x8b, 0xbe, 0xf4, 0x43, 0xf4, 0xad, 0xd3, 0xb7, 0xbe, 0x75, 0x3a, 0xd3, 0xe7, 0x7e, 0x8d, | ||||
| 	0x7e, 0x86, 0xce, 0xed, 0x1d, 0x40, 0x80, 0xa4, 0x62, 0x67, 0xa6, 0x7d, 0xbb, 0xdb, 0xdd, 0xdb, | ||||
| 	0xbb, 0xdb, 0xfb, 0xed, 0x6f, 0x17, 0x24, 0xdc, 0xf5, 0xc3, 0xa9, 0xe7, 0x50, 0x7f, 0x37, 0xf2, | ||||
| 	0x93, 0xa9, 0x17, 0xec, 0x46, 0x93, 0xdd, 0x09, 0x75, 0x2e, 0x59, 0xe0, 0x8e, 0x22, 0x1e, 0x8a, | ||||
| 	0x90, 0x94, 0xa3, 0xc9, 0xe0, 0xee, 0x34, 0x0c, 0xa7, 0x3e, 0xdb, 0x45, 0xc9, 0x24, 0x39, 0xdf, | ||||
| 	0x15, 0xde, 0x8c, 0xc5, 0x82, 0xce, 0x22, 0x65, 0x34, 0xd8, 0x4e, 0xbd, 0x78, 0x2e, 0x0b, 0x84, | ||||
| 	0x27, 0xe6, 0x5a, 0xbe, 0x55, 0xf4, 0xae, 0xa4, 0x66, 0x03, 0x6a, 0x87, 0xb3, 0x48, 0xcc, 0xcd, | ||||
| 	0x21, 0xd4, 0xbf, 0x60, 0xd4, 0x65, 0x9c, 0x6c, 0x43, 0xfd, 0x02, 0x47, 0x46, 0x69, 0x58, 0xd9, | ||||
| 	0x69, 0x59, 0x7a, 0x66, 0xfe, 0x0e, 0xe0, 0x54, 0xae, 0x39, 0xe4, 0x3c, 0xe4, 0xe4, 0x36, 0x34, | ||||
| 	0x19, 0xe7, 0xb6, 0x98, 0x47, 0xcc, 0x28, 0x0d, 0x4b, 0x3b, 0x5d, 0xab, 0xc1, 0x38, 0x1f, 0xcf, | ||||
| 	0x23, 0x46, 0x7e, 0x00, 0x72, 0x68, 0xcf, 0xe2, 0xa9, 0x51, 0x1e, 0x96, 0xa4, 0x07, 0xc6, 0xf9, | ||||
| 	0x49, 0x3c, 0x4d, 0xd7, 0x38, 0xa1, 0xcb, 0x8c, 0xca, 0xb0, 0xb4, 0x53, 0xc1, 0x35, 0x07, 0xa1, | ||||
| 	0xcb, 0xcc, 0x3f, 0x95, 0xa0, 0x76, 0x4a, 0xc5, 0x45, 0x4c, 0x08, 0x54, 0x79, 0x18, 0x0a, 0xbd, | ||||
| 	0x39, 0x8e, 0xc9, 0x0e, 0xf4, 0x93, 0x80, 0x26, 0xe2, 0x42, 0xde, 0xc8, 0xa1, 0x82, 0xb9, 0x46, | ||||
| 	0x19, 0xd5, 0xcb, 0x62, 0xf2, 0x1e, 0x74, 0xfd, 0xd0, 0xa1, 0xbe, 0x1d, 0x8b, 0x90, 0xd3, 0xa9, | ||||
| 	0xdc, 0x47, 0xda, 0x75, 0x50, 0x78, 0xa6, 0x64, 0xe4, 0x21, 0x6c, 0xc6, 0x8c, 0xfa, 0xf6, 0x2b, | ||||
| 	0x4e, 0xa3, 0xcc, 0xb0, 0xaa, 0x1c, 0x4a, 0xc5, 0x37, 0x9c, 0x46, 0xda, 0xd6, 0xfc, 0x7b, 0x1d, | ||||
| 	0x1a, 0x16, 0xfb, 0x43, 0xc2, 0x62, 0x41, 0x7a, 0x50, 0xf6, 0x5c, 0xbc, 0x6d, 0xcb, 0x2a, 0x7b, | ||||
| 	0x2e, 0x19, 0x01, 0xb1, 0x58, 0xe4, 0xcb, 0xad, 0xbd, 0x30, 0x38, 0xf0, 0x93, 0x58, 0x30, 0xae, | ||||
| 	0xef, 0xbc, 0x46, 0x43, 0xee, 0x40, 0x2b, 0x8c, 0x18, 0x47, 0x19, 0x06, 0xa0, 0x65, 0x2d, 0x04, | ||||
| 	0xf2, 0xe2, 0x11, 0x15, 0x17, 0x46, 0x15, 0x15, 0x38, 0x96, 0x32, 0x97, 0x0a, 0x6a, 0xd4, 0x94, | ||||
| 	0x4c, 0x8e, 0x89, 0x09, 0xf5, 0x98, 0x39, 0x9c, 0x09, 0xa3, 0x3e, 0x2c, 0xed, 0xb4, 0xf7, 0x60, | ||||
| 	0x14, 0x4d, 0x46, 0x67, 0x28, 0xb1, 0xb4, 0x86, 0xdc, 0x81, 0xaa, 0x8c, 0x8b, 0xd1, 0x40, 0x8b, | ||||
| 	0xa6, 0xb4, 0xd8, 0x4f, 0xc4, 0x85, 0x85, 0x52, 0xb2, 0x07, 0x0d, 0xf5, 0xa6, 0xb1, 0xd1, 0x1c, | ||||
| 	0x56, 0x76, 0xda, 0x7b, 0x86, 0x34, 0xd0, 0xb7, 0x1c, 0x29, 0x18, 0xc4, 0x87, 0x81, 0xe0, 0x73, | ||||
| 	0x2b, 0x35, 0x24, 0xef, 0x42, 0xc7, 0xf1, 0x3d, 0x16, 0x08, 0x5b, 0x84, 0x97, 0x2c, 0x30, 0x5a, | ||||
| 	0x78, 0xa2, 0xb6, 0x92, 0x8d, 0xa5, 0x88, 0xec, 0xc1, 0x5b, 0x79, 0x13, 0x9b, 0x3a, 0x0e, 0x8b, | ||||
| 	0xe3, 0x90, 0x1b, 0x80, 0xb6, 0xb7, 0x72, 0xb6, 0xfb, 0x5a, 0x25, 0xdd, 0xba, 0x5e, 0x1c, 0xf9, | ||||
| 	0x74, 0x6e, 0x07, 0x74, 0xc6, 0x8c, 0xb6, 0x72, 0xab, 0x65, 0x5f, 0xd1, 0x19, 0x23, 0x77, 0xa1, | ||||
| 	0x3d, 0x0b, 0x93, 0x40, 0xd8, 0x51, 0xe8, 0x05, 0xc2, 0xe8, 0xa0, 0x05, 0xa0, 0xe8, 0x54, 0x4a, | ||||
| 	0xc8, 0x3b, 0xa0, 0x66, 0x0a, 0x8c, 0x5d, 0x15, 0x57, 0x94, 0x20, 0x1c, 0xef, 0x41, 0x4f, 0xa9, | ||||
| 	0xb3, 0xf3, 0xf4, 0xd0, 0xa4, 0x8b, 0xd2, 0xec, 0x24, 0x4f, 0xa0, 0x85, 0x78, 0xf0, 0x82, 0xf3, | ||||
| 	0xd0, 0xe8, 0x63, 0xdc, 0x6e, 0xe5, 0xc2, 0x22, 0x31, 0x71, 0x14, 0x9c, 0x87, 0x56, 0xf3, 0x95, | ||||
| 	0x1e, 0x91, 0x4f, 0xe0, 0xed, 0xc2, 0x7d, 0x39, 0x9b, 0x51, 0x2f, 0xf0, 0x82, 0xa9, 0x9d, 0xc4, | ||||
| 	0x2c, 0x36, 0x36, 0x10, 0xe1, 0x46, 0xee, 0xd6, 0x56, 0x6a, 0xf0, 0x32, 0x66, 0x31, 0x79, 0x1b, | ||||
| 	0x5a, 0x2a, 0x41, 0x6d, 0xcf, 0x35, 0x36, 0xf1, 0x48, 0x4d, 0x25, 0x38, 0x72, 0xc9, 0x03, 0xe8, | ||||
| 	0x47, 0xa1, 0xef, 0x39, 0x73, 0x3b, 0xbc, 0x62, 0x9c, 0x7b, 0x2e, 0x33, 0xc8, 0xb0, 0xb4, 0xd3, | ||||
| 	0xb4, 0x7a, 0x4a, 0xfc, 0xb5, 0x96, 0xae, 0x4b, 0x8d, 0x5b, 0x68, 0xb8, 0x92, 0x1a, 0x23, 0x00, | ||||
| 	0x27, 0x0c, 0x02, 0xe6, 0x20, 0xfc, 0xb6, 0xf0, 0x86, 0x3d, 0x79, 0xc3, 0x83, 0x4c, 0x6a, 0xe5, | ||||
| 	0x2c, 0x06, 0x9f, 0x43, 0x27, 0x0f, 0x05, 0xb2, 0x01, 0x95, 0x4b, 0x36, 0xd7, 0xf0, 0x97, 0x43, | ||||
| 	0x32, 0x84, 0xda, 0x15, 0xf5, 0x13, 0x86, 0x90, 0xd7, 0x40, 0x54, 0x4b, 0x2c, 0xa5, 0xf8, 0x59, | ||||
| 	0xf9, 0x59, 0xc9, 0xfc, 0x73, 0x0d, 0xaa, 0x12, 0x7c, 0xe4, 0x43, 0xe8, 0xfa, 0x8c, 0xc6, 0xcc, | ||||
| 	0x0e, 0x23, 0xb9, 0x41, 0x8c, 0xae, 0xda, 0x7b, 0x1b, 0x72, 0xd9, 0xb1, 0x54, 0x7c, 0xad, 0xe4, | ||||
| 	0x56, 0xc7, 0xcf, 0xcd, 0x64, 0x4a, 0x7b, 0x81, 0x60, 0x3c, 0xa0, 0xbe, 0x8d, 0xc9, 0xa0, 0x12, | ||||
| 	0xac, 0x93, 0x0a, 0x9f, 0xcb, 0xa4, 0x58, 0xc6, 0x51, 0x65, 0x15, 0x47, 0x03, 0x68, 0x62, 0xec, | ||||
| 	0x3c, 0x16, 0xeb, 0x64, 0xcf, 0xe6, 0x64, 0x0f, 0x9a, 0x33, 0x26, 0xa8, 0xce, 0x35, 0x99, 0x12, | ||||
| 	0xdb, 0x69, 0xce, 0x8c, 0x4e, 0xb4, 0x42, 0x25, 0x44, 0x66, 0xb7, 0x92, 0x11, 0xf5, 0xd5, 0x8c, | ||||
| 	0x18, 0x40, 0x33, 0x03, 0x5d, 0x43, 0xbd, 0x70, 0x3a, 0x97, 0x34, 0x1b, 0x31, 0xee, 0x85, 0xae, | ||||
| 	0xd1, 0x44, 0xa0, 0xe8, 0x99, 0x24, 0xc9, 0x20, 0x99, 0x29, 0x08, 0xb5, 0x14, 0x49, 0x06, 0xc9, | ||||
| 	0x6c, 0x15, 0x31, 0xb0, 0x84, 0x98, 0x1f, 0x41, 0x8d, 0xfa, 0x1e, 0x8d, 0x31, 0x85, 0xe4, 0xcb, | ||||
| 	0x6a, 0xbe, 0x1f, 0xed, 0x4b, 0xa9, 0xa5, 0x94, 0xe4, 0x03, 0xe8, 0x4e, 0x79, 0x98, 0x44, 0x36, | ||||
| 	0x4e, 0x59, 0x6c, 0x74, 0xf0, 0xb6, 0xcb, 0xd6, 0x1d, 0x34, 0xda, 0x57, 0x36, 0x32, 0x03, 0x27, | ||||
| 	0x61, 0x12, 0xb8, 0xb6, 0xe3, 0xb9, 0x3c, 0x36, 0xba, 0x18, 0x3c, 0x40, 0xd1, 0x81, 0x94, 0xc8, | ||||
| 	0x14, 0x53, 0x29, 0x90, 0x05, 0xb8, 0x87, 0x36, 0x5d, 0x94, 0x9e, 0xa6, 0x51, 0xfe, 0x31, 0x6c, | ||||
| 	0xa6, 0x45, 0x69, 0x61, 0xd9, 0x47, 0xcb, 0x8d, 0x54, 0x91, 0x19, 0xef, 0xc0, 0x06, 0xbb, 0x96, | ||||
| 	0x14, 0xea, 0x09, 0x7b, 0x46, 0xaf, 0x6d, 0x21, 0x7c, 0x9d, 0x52, 0xbd, 0x54, 0x7e, 0x42, 0xaf, | ||||
| 	0xc7, 0xc2, 0x1f, 0xfc, 0x1c, 0xba, 0x85, 0x37, 0x5a, 0x83, 0xd4, 0xad, 0x3c, 0x52, 0x5b, 0x79, | ||||
| 	0x74, 0xfe, 0xb5, 0x0a, 0x80, 0x8f, 0xa5, 0x96, 0x2e, 0x53, 0x7c, 0xfe, 0x05, 0xcb, 0x6b, 0x5e, | ||||
| 	0x90, 0x72, 0x16, 0x08, 0x8d, 0x36, 0x3d, 0xfb, 0x4e, 0xa0, 0xa5, 0x24, 0x5f, 0xcb, 0x91, 0xfc, | ||||
| 	0x23, 0xa8, 0x4a, 0x50, 0x19, 0xf5, 0x05, 0x17, 0x2f, 0x4e, 0x84, 0xf0, 0x53, 0xd0, 0x43, 0xab, | ||||
| 	0x15, 0xa4, 0x37, 0x56, 0x91, 0x9e, 0x87, 0x50, 0xb3, 0x08, 0xa1, 0xf7, 0xa0, 0xeb, 0x70, 0x86, | ||||
| 	0x05, 0xc7, 0x96, 0x9d, 0x83, 0x86, 0x58, 0x27, 0x15, 0x8e, 0xbd, 0x19, 0x93, 0xf1, 0x93, 0xd1, | ||||
| 	0x06, 0x54, 0xc9, 0xe1, 0xda, 0xc7, 0x68, 0xaf, 0x7b, 0x0c, 0x55, 0xbe, 0x7d, 0xa6, 0x69, 0x1a, | ||||
| 	0xc7, 0x39, 0xa8, 0x77, 0x0b, 0x50, 0x2f, 0xe0, 0xb9, 0xb7, 0x84, 0xe7, 0x25, 0xd0, 0xf5, 0x57, | ||||
| 	0x40, 0xf7, 0x2e, 0x74, 0x64, 0x00, 0xe2, 0x88, 0x3a, 0x4c, 0x3a, 0xd8, 0x50, 0x81, 0xc8, 0x64, | ||||
| 	0x47, 0x2e, 0xa6, 0x68, 0x32, 0x99, 0xcc, 0x2f, 0x42, 0x9f, 0x2d, 0x58, 0xb6, 0x9d, 0xc9, 0x8e, | ||||
| 	0xdc, 0xc1, 0x47, 0xd0, 0xca, 0x22, 0xfc, 0xbd, 0x80, 0xf3, 0x97, 0x12, 0x74, 0xf2, 0xac, 0x25, | ||||
| 	0x17, 0x8f, 0xc7, 0xc7, 0xb8, 0xb8, 0x62, 0xc9, 0xa1, 0xac, 0xf7, 0x9c, 0x05, 0xec, 0x15, 0x9d, | ||||
| 	0xf8, 0xca, 0x41, 0xd3, 0x5a, 0x08, 0xa4, 0xd6, 0x0b, 0x1c, 0xce, 0x66, 0x29, 0x82, 0x2a, 0xd6, | ||||
| 	0x42, 0x40, 0x3e, 0x06, 0xf0, 0xe2, 0x38, 0x61, 0xea, 0x95, 0xaa, 0x98, 0xd3, 0x83, 0x91, 0x6a, | ||||
| 	0xfe, 0x46, 0x69, 0xf3, 0x37, 0x1a, 0xa7, 0xcd, 0x9f, 0xd5, 0x42, 0x6b, 0x7c, 0xbe, 0x6d, 0xa8, | ||||
| 	0xcb, 0xc7, 0x18, 0x1f, 0x23, 0xca, 0x2a, 0x96, 0x9e, 0x99, 0x7f, 0x84, 0xba, 0x6a, 0x13, 0xfe, | ||||
| 	0xaf, 0x4c, 0x7c, 0x1b, 0x9a, 0xca, 0xb7, 0xe7, 0xea, 0xbc, 0x68, 0xe0, 0xfc, 0xc8, 0x35, 0xff, | ||||
| 	0x55, 0x82, 0xa6, 0xc5, 0xe2, 0x28, 0x0c, 0x62, 0x96, 0x6b, 0x63, 0x4a, 0xaf, 0x6d, 0x63, 0xca, | ||||
| 	0x6b, 0xdb, 0x98, 0xb4, 0x39, 0xaa, 0xe4, 0x9a, 0xa3, 0x01, 0x34, 0x39, 0x73, 0x3d, 0xce, 0x1c, | ||||
| 	0xa1, 0x1b, 0xa9, 0x6c, 0x2e, 0x75, 0xaf, 0x28, 0x97, 0xf5, 0x37, 0x46, 0x92, 0x6f, 0x59, 0xd9, | ||||
| 	0x9c, 0x3c, 0xcd, 0x57, 0x7f, 0xd5, 0x57, 0x6d, 0xa9, 0xea, 0xaf, 0x8e, 0xbb, 0x5a, 0xfe, 0xcd, | ||||
| 	0x7f, 0x96, 0x61, 0x63, 0x59, 0xbd, 0x06, 0x04, 0x5b, 0x50, 0x53, 0xf5, 0x41, 0x23, 0x48, 0xac, | ||||
| 	0x54, 0x86, 0xca, 0x12, 0xaf, 0xfc, 0x62, 0x39, 0x47, 0x5f, 0xff, 0xfa, 0xc5, 0xfc, 0x7d, 0x1f, | ||||
| 	0x36, 0xe4, 0x29, 0x23, 0xe6, 0x2e, 0x7a, 0x1e, 0x45, 0x38, 0x7d, 0x2d, 0xcf, 0xba, 0x9e, 0x87, | ||||
| 	0xb0, 0x99, 0x9a, 0x2e, 0x52, 0xb1, 0x5e, 0xb0, 0x3d, 0x4c, 0x33, 0x72, 0x1b, 0xea, 0xe7, 0x21, | ||||
| 	0x9f, 0x51, 0xa1, 0x39, 0x47, 0xcf, 0x0a, 0x9c, 0x82, 0xe4, 0xd6, 0x54, 0xb0, 0x48, 0x85, 0xb2, | ||||
| 	0xaf, 0x97, 0xb9, 0x9e, 0xf5, 0xdc, 0x48, 0x3a, 0x4d, 0xab, 0x99, 0xf6, 0xda, 0xe6, 0xaf, 0xa1, | ||||
| 	0xbf, 0xd4, 0x66, 0xad, 0x09, 0xe4, 0x62, 0xfb, 0x72, 0x61, 0xfb, 0x82, 0xe7, 0xca, 0x92, 0xe7, | ||||
| 	0xdf, 0xc0, 0xe6, 0x17, 0x34, 0x70, 0x7d, 0xa6, 0xfd, 0xef, 0xf3, 0x69, 0x2c, 0x1b, 0x46, 0xdd, | ||||
| 	0xf5, 0xdb, 0x9a, 0xec, 0xbb, 0x56, 0x4b, 0x4b, 0x8e, 0x5c, 0x72, 0x0f, 0x1a, 0x5c, 0x59, 0x6b, | ||||
| 	0xe0, 0xb5, 0x73, 0x7d, 0xa0, 0x95, 0xea, 0xcc, 0x6f, 0x81, 0x14, 0x5c, 0xcb, 0x86, 0x7f, 0x4e, | ||||
| 	0x76, 0x24, 0x00, 0x15, 0x28, 0x34, 0xb0, 0x3b, 0x79, 0x1c, 0x59, 0x99, 0x96, 0x0c, 0xa1, 0xc2, | ||||
| 	0x38, 0xd7, 0x5b, 0x60, 0x23, 0xb6, 0xf8, 0xbc, 0xb2, 0xa4, 0xca, 0xfc, 0x09, 0x6c, 0x9e, 0x45, | ||||
| 	0xcc, 0xf1, 0xa8, 0x8f, 0x9f, 0x46, 0x6a, 0x83, 0xbb, 0x50, 0x93, 0x41, 0x4e, 0x73, 0xb6, 0x85, | ||||
| 	0x0b, 0x51, 0xad, 0xe4, 0xe6, 0xb7, 0x60, 0xa8, 0x73, 0x1d, 0x5e, 0x7b, 0xb1, 0x60, 0x81, 0xc3, | ||||
| 	0x0e, 0x2e, 0x98, 0x73, 0xf9, 0x3f, 0xbc, 0xf9, 0x15, 0xdc, 0x5e, 0xb7, 0x43, 0x7a, 0xbe, 0xb6, | ||||
| 	0x23, 0x67, 0xf6, 0xb9, 0xa4, 0x6a, 0xdc, 0xa3, 0x69, 0x01, 0x8a, 0x3e, 0x97, 0x12, 0xf9, 0x8e, | ||||
| 	0x4c, 0xae, 0x8b, 0x35, 0x25, 0xea, 0x59, 0x1a, 0x8f, 0xca, 0xcd, 0xf1, 0xf8, 0x5b, 0x09, 0x5a, | ||||
| 	0x67, 0x4c, 0x24, 0x11, 0xde, 0xe5, 0x6d, 0x68, 0x4d, 0x78, 0x78, 0xc9, 0xf8, 0xe2, 0x2a, 0x4d, | ||||
| 	0x25, 0x38, 0x72, 0xc9, 0x53, 0xa8, 0x1f, 0x84, 0xc1, 0xb9, 0x37, 0xc5, 0x0f, 0xc5, 0xf6, 0xde, | ||||
| 	0x6d, 0xc5, 0x2e, 0x7a, 0xed, 0x48, 0xe9, 0x54, 0x59, 0xd5, 0x86, 0x64, 0x08, 0x6d, 0xfd, 0xb9, | ||||
| 	0xfd, 0xf2, 0xe5, 0xd1, 0xf3, 0xb4, 0x83, 0xcc, 0x89, 0x06, 0x1f, 0x43, 0x3b, 0xb7, 0xf0, 0x7b, | ||||
| 	0x55, 0x8b, 0x1f, 0x02, 0xe0, 0xee, 0x2a, 0x46, 0x1b, 0xea, 0xaa, 0x7a, 0xa5, 0xbc, 0xda, 0x5d, | ||||
| 	0x68, 0xc9, 0x8f, 0x15, 0xa5, 0x26, 0x50, 0xcd, 0x7d, 0x57, 0xe3, 0xd8, 0xbc, 0x07, 0x9b, 0x47, | ||||
| 	0xc1, 0x15, 0xf5, 0x3d, 0x97, 0x0a, 0xf6, 0x25, 0x9b, 0x63, 0x08, 0x56, 0x4e, 0x60, 0x9e, 0x41, | ||||
| 	0x47, 0x7f, 0xb9, 0xbe, 0xd1, 0x19, 0x3b, 0xfa, 0x8c, 0xdf, 0x9d, 0x44, 0xef, 0x43, 0x5f, 0x3b, | ||||
| 	0x3d, 0xf6, 0x74, 0x0a, 0xc9, 0x92, 0xce, 0xd9, 0xb9, 0x77, 0xad, 0x5d, 0xeb, 0x99, 0xf9, 0x0c, | ||||
| 	0x36, 0x72, 0xa6, 0xd9, 0x75, 0x2e, 0xd9, 0x3c, 0x4e, 0xbf, 0xe8, 0xe5, 0x38, 0x8d, 0x40, 0x79, | ||||
| 	0x11, 0x01, 0x13, 0x7a, 0x7a, 0xe5, 0x0b, 0x26, 0x6e, 0xb8, 0xdd, 0x97, 0xd9, 0x41, 0x5e, 0x30, | ||||
| 	0xed, 0xfc, 0x3e, 0xd4, 0x98, 0xbc, 0x69, 0xbe, 0x84, 0xe5, 0x23, 0x60, 0x29, 0xf5, 0x9a, 0x0d, | ||||
| 	0x9f, 0x65, 0x1b, 0x9e, 0x26, 0x6a, 0xc3, 0x37, 0xf4, 0x65, 0xbe, 0x97, 0x1d, 0xe3, 0x34, 0x11, | ||||
| 	0x37, 0xbd, 0xe8, 0x3d, 0xd8, 0xd4, 0x46, 0xcf, 0x99, 0xcf, 0x04, 0xbb, 0xe1, 0x4a, 0xf7, 0x81, | ||||
| 	0x14, 0xcc, 0x6e, 0x72, 0x77, 0x07, 0x9a, 0xe3, 0xf1, 0x71, 0xa6, 0x2d, 0x72, 0xa3, 0xf9, 0x09, | ||||
| 	0x6c, 0x9e, 0x25, 0x6e, 0x78, 0xca, 0xbd, 0x2b, 0xcf, 0x67, 0x53, 0xb5, 0x59, 0xda, 0x6b, 0x96, | ||||
| 	0x72, 0xbd, 0xe6, 0xda, 0x6a, 0x64, 0xee, 0x00, 0x29, 0x2c, 0xcf, 0xde, 0x2d, 0x4e, 0xdc, 0x50, | ||||
| 	0xa7, 0x30, 0x8e, 0xcd, 0x1d, 0xe8, 0x8c, 0xa9, 0xac, 0xf7, 0xae, 0xb2, 0x31, 0xa0, 0x21, 0xd4, | ||||
| 	0x5c, 0x9b, 0xa5, 0x53, 0x73, 0x0f, 0xb6, 0x0e, 0xa8, 0x73, 0xe1, 0x05, 0xd3, 0xe7, 0x5e, 0x2c, | ||||
| 	0x1b, 0x1e, 0xbd, 0x62, 0x00, 0x4d, 0x57, 0x0b, 0xf4, 0x92, 0x6c, 0x6e, 0x3e, 0x86, 0xb7, 0x72, | ||||
| 	0x3f, 0x9b, 0x9c, 0x09, 0x9a, 0xc6, 0x63, 0x0b, 0x6a, 0xb1, 0x9c, 0xe1, 0x8a, 0x9a, 0xa5, 0x26, | ||||
| 	0xe6, 0x57, 0xb0, 0x95, 0x2f, 0xc0, 0xb2, 0xfd, 0x48, 0x2f, 0x8e, 0x8d, 0x41, 0x29, 0xd7, 0x18, | ||||
| 	0xe8, 0x98, 0x95, 0x17, 0xf5, 0x64, 0x03, 0x2a, 0xbf, 0xfc, 0x66, 0xac, 0xc1, 0x2e, 0x87, 0xe6, | ||||
| 	0xef, 0xe5, 0xf6, 0x45, 0x7f, 0x6a, 0xfb, 0x42, 0x77, 0x50, 0x7a, 0x93, 0xee, 0x60, 0x0d, 0xde, | ||||
| 	0x1e, 0xc3, 0xe6, 0x89, 0x1f, 0x3a, 0x97, 0x87, 0x41, 0x2e, 0x1a, 0x06, 0x34, 0x58, 0x90, 0x0f, | ||||
| 	0x46, 0x3a, 0x35, 0x1f, 0x40, 0xff, 0x38, 0x74, 0xa8, 0x7f, 0x12, 0x26, 0x81, 0xc8, 0xa2, 0x80, | ||||
| 	0xbf, 0x63, 0x69, 0x53, 0x35, 0x31, 0x1f, 0x43, 0x4f, 0x97, 0xe8, 0xe0, 0x3c, 0x4c, 0x99, 0x71, | ||||
| 	0x51, 0xcc, 0x4b, 0xc5, 0xbe, 0xda, 0x3c, 0x86, 0xfe, 0xc2, 0x5c, 0xf9, 0x7d, 0x00, 0x75, 0xa5, | ||||
| 	0xd6, 0x77, 0xeb, 0x67, 0x5f, 0x83, 0xca, 0xd2, 0xd2, 0xea, 0x35, 0x97, 0x9a, 0x41, 0xef, 0x14, | ||||
| 	0x7f, 0x4f, 0x3c, 0x0c, 0xae, 0x94, 0xb3, 0x23, 0x20, 0xea, 0x17, 0x46, 0x9b, 0x05, 0x57, 0x1e, | ||||
| 	0x0f, 0x03, 0xec, 0x6f, 0x4b, 0xba, 0x85, 0x49, 0x1d, 0x67, 0x8b, 0x52, 0x0b, 0x6b, 0x33, 0x5a, | ||||
| 	0x16, 0xad, 0x8d, 0x21, 0x2c, 0x7e, 0xad, 0x90, 0xa5, 0x86, 0xb3, 0x59, 0x28, 0x98, 0x4d, 0x5d, | ||||
| 	0x37, 0xcd, 0x16, 0x50, 0xa2, 0x7d, 0xd7, 0xe5, 0x7b, 0xff, 0x29, 0x43, 0xe3, 0x33, 0x45, 0xe0, | ||||
| 	0xe4, 0x53, 0xe8, 0x16, 0xca, 0x35, 0x79, 0x0b, 0x7f, 0xae, 0x58, 0x6e, 0x0e, 0x06, 0xdb, 0x2b, | ||||
| 	0x62, 0x75, 0xaf, 0x27, 0xd0, 0xc9, 0x17, 0x63, 0x82, 0x85, 0x17, 0x7f, 0x3b, 0x1d, 0xa0, 0xa7, | ||||
| 	0xd5, 0x4a, 0x7d, 0x06, 0x5b, 0xeb, 0xca, 0x24, 0xb9, 0xb3, 0xd8, 0x61, 0xb5, 0x44, 0x0f, 0xde, | ||||
| 	0xb9, 0x49, 0x9b, 0x96, 0xd7, 0xc6, 0x81, 0xcf, 0x68, 0x90, 0x44, 0xf9, 0x13, 0x2c, 0x86, 0xe4, | ||||
| 	0x29, 0x74, 0x0b, 0x85, 0x42, 0xdd, 0x73, 0xa5, 0x76, 0xe4, 0x97, 0xdc, 0x87, 0x1a, 0x16, 0x27, | ||||
| 	0xd2, 0x2d, 0x54, 0xc9, 0x41, 0x2f, 0x9b, 0xaa, 0xbd, 0x87, 0x50, 0xc5, 0x5f, 0xd4, 0x72, 0x1b, | ||||
| 	0xe3, 0x8a, 0xac, 0x72, 0xed, 0xfd, 0xbb, 0x04, 0x8d, 0xf4, 0x57, 0xd6, 0xa7, 0x50, 0x95, 0x35, | ||||
| 	0x80, 0xdc, 0xca, 0xd1, 0x68, 0x5a, 0x3f, 0x06, 0x5b, 0x4b, 0x42, 0xb5, 0xc1, 0x08, 0x2a, 0x2f, | ||||
| 	0x98, 0x20, 0x24, 0xa7, 0xd4, 0xc5, 0x60, 0x70, 0xab, 0x28, 0xcb, 0xec, 0x4f, 0x93, 0xa2, 0xbd, | ||||
| 	0xe6, 0xf2, 0x82, 0x7d, 0xc6, 0xd2, 0x1f, 0x41, 0x5d, 0xb1, 0xac, 0x0a, 0xca, 0x0a, 0x3f, 0xab, | ||||
| 	0xc7, 0x5f, 0xe5, 0xe3, 0xbd, 0x7f, 0x54, 0x01, 0xce, 0xe6, 0xb1, 0x60, 0xb3, 0x5f, 0x79, 0xec, | ||||
| 	0x15, 0x79, 0x08, 0xfd, 0xe7, 0xec, 0x9c, 0x26, 0xbe, 0xc0, 0xaf, 0x25, 0xc9, 0x26, 0xb9, 0x98, | ||||
| 	0x60, 0xc3, 0x97, 0x91, 0xf5, 0x7d, 0x68, 0x9f, 0xd0, 0xeb, 0xd7, 0xdb, 0x7d, 0x0a, 0xdd, 0x02, | ||||
| 	0x07, 0xeb, 0x23, 0x2e, 0xb3, 0xba, 0x3e, 0xe2, 0x2a, 0x5b, 0xdf, 0x87, 0x86, 0x66, 0xe6, 0xfc, | ||||
| 	0x1e, 0x58, 0xc3, 0x0a, 0x8c, 0xfd, 0x53, 0xe8, 0x2f, 0xf1, 0x72, 0xde, 0x1e, 0x7f, 0x7d, 0x58, | ||||
| 	0xcb, 0xdb, 0xcf, 0xe4, 0xd7, 0x4e, 0x91, 0x9b, 0xf3, 0x0b, 0x6f, 0x2b, 0x3e, 0x5c, 0x47, 0xde, | ||||
| 	0x2f, 0x8a, 0xdf, 0x49, 0xf8, 0x95, 0x68, 0x2c, 0xd3, 0x67, 0x4a, 0xde, 0xa9, 0xa3, 0x75, 0x34, | ||||
| 	0xfc, 0x04, 0x3a, 0x79, 0x06, 0x5d, 0x49, 0xc1, 0x55, 0x7a, 0x7d, 0x04, 0xb0, 0x20, 0xd1, 0xbc, | ||||
| 	0x3d, 0xc2, 0x63, 0x99, 0x5f, 0x3f, 0x04, 0x58, 0x50, 0xa3, 0x42, 0x55, 0x91, 0x59, 0xd5, 0xb2, | ||||
| 	0x65, 0xfa, 0x7c, 0x08, 0xad, 0x8c, 0xce, 0xf2, 0x7b, 0xa0, 0x83, 0x22, 0x3b, 0x7e, 0x36, 0xfa, | ||||
| 	0xed, 0xa3, 0xa9, 0x27, 0x2e, 0x92, 0xc9, 0xc8, 0x09, 0x67, 0xbb, 0x17, 0x34, 0xbe, 0xf0, 0x9c, | ||||
| 	0x90, 0x47, 0xbb, 0x57, 0x12, 0x4c, 0xbb, 0x2b, 0x7f, 0x00, 0x4d, 0xea, 0xf8, 0xb1, 0xf7, 0xc1, | ||||
| 	0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x4e, 0xbe, 0xe0, 0xab, 0x1c, 0x1a, 0x00, 0x00, | ||||
| 	// 2483 bytes of a gzipped FileDescriptorProto | ||||
| 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xcd, 0x72, 0x1b, 0xc7, | ||||
| 	0x11, 0x2e, 0x00, 0xc4, 0x5f, 0xe3, 0x8f, 0x18, 0xd1, 0xcc, 0x0a, 0x96, 0x23, 0x78, 0x1d, 0x49, | ||||
| 	0xb4, 0x22, 0x81, 0x12, 0x1d, 0xc7, 0x72, 0x52, 0x76, 0x8a, 0xa6, 0x68, 0x99, 0x31, 0x69, 0xb3, | ||||
| 	0x96, 0x50, 0x9c, 0xbf, 0x2a, 0x78, 0xb0, 0x3b, 0x04, 0xb7, 0xb8, 0xd8, 0xdd, 0xcc, 0xce, 0x52, | ||||
| 	0xc4, 0x29, 0x6f, 0x91, 0xd7, 0xc8, 0x35, 0x95, 0x4b, 0x6e, 0xa9, 0x54, 0x72, 0xce, 0x6b, 0xe4, | ||||
| 	0x19, 0x52, 0xd3, 0x33, 0xfb, 0x07, 0x80, 0x96, 0x5c, 0x95, 0xdc, 0x66, 0xba, 0x7b, 0x7a, 0x66, | ||||
| 	0x7a, 0xbe, 0xfe, 0xba, 0x17, 0x80, 0xbb, 0x5e, 0x30, 0x73, 0x6d, 0xea, 0xed, 0x86, 0x5e, 0x3c, | ||||
| 	0x73, 0xfd, 0xdd, 0x70, 0xba, 0x3b, 0xa5, 0xf6, 0x25, 0xf3, 0x9d, 0x51, 0xc8, 0x03, 0x11, 0x90, | ||||
| 	0x72, 0x38, 0x1d, 0xdc, 0x9d, 0x05, 0xc1, 0xcc, 0x63, 0xbb, 0x28, 0x99, 0xc6, 0xe7, 0xbb, 0xc2, | ||||
| 	0x9d, 0xb3, 0x48, 0xd0, 0x79, 0xa8, 0x8c, 0x06, 0xdb, 0x89, 0x17, 0xd7, 0x61, 0xbe, 0x70, 0xc5, | ||||
| 	0x42, 0xcb, 0xb7, 0x8a, 0xde, 0x95, 0xd4, 0xac, 0x43, 0xf5, 0x70, 0x1e, 0x8a, 0x85, 0x39, 0x84, | ||||
| 	0xda, 0x17, 0x8c, 0x3a, 0x8c, 0x93, 0x6d, 0xa8, 0x5d, 0xe0, 0xc8, 0x28, 0x0d, 0x2b, 0x3b, 0x4d, | ||||
| 	0x4b, 0xcf, 0xcc, 0xdf, 0x01, 0x9c, 0xca, 0x35, 0x87, 0x9c, 0x07, 0x9c, 0xdc, 0x86, 0x06, 0xe3, | ||||
| 	0x7c, 0x22, 0x16, 0x21, 0x33, 0x4a, 0xc3, 0xd2, 0x4e, 0xc7, 0xaa, 0x33, 0xce, 0xc7, 0x8b, 0x90, | ||||
| 	0x91, 0x1f, 0x80, 0x1c, 0x4e, 0xe6, 0xd1, 0xcc, 0x28, 0x0f, 0x4b, 0xd2, 0x03, 0xe3, 0xfc, 0x24, | ||||
| 	0x9a, 0x25, 0x6b, 0xec, 0xc0, 0x61, 0x46, 0x65, 0x58, 0xda, 0xa9, 0xe0, 0x9a, 0x83, 0xc0, 0x61, | ||||
| 	0xe6, 0x9f, 0x4a, 0x50, 0x3d, 0xa5, 0xe2, 0x22, 0x22, 0x04, 0x36, 0x78, 0x10, 0x08, 0xbd, 0x39, | ||||
| 	0x8e, 0xc9, 0x0e, 0xf4, 0x62, 0x9f, 0xc6, 0xe2, 0x42, 0xde, 0xc8, 0xa6, 0x82, 0x39, 0x46, 0x19, | ||||
| 	0xd5, 0xcb, 0x62, 0xf2, 0x1e, 0x74, 0xbc, 0xc0, 0xa6, 0xde, 0x24, 0x12, 0x01, 0xa7, 0x33, 0xb9, | ||||
| 	0x8f, 0xb4, 0x6b, 0xa3, 0xf0, 0x4c, 0xc9, 0xc8, 0x43, 0xe8, 0x47, 0x8c, 0x7a, 0x93, 0x57, 0x9c, | ||||
| 	0x86, 0xa9, 0xe1, 0x86, 0x72, 0x28, 0x15, 0xdf, 0x70, 0x1a, 0x6a, 0x5b, 0xf3, 0x6f, 0x35, 0xa8, | ||||
| 	0x5b, 0xec, 0x0f, 0x31, 0x8b, 0x04, 0xe9, 0x42, 0xd9, 0x75, 0xf0, 0xb6, 0x4d, 0xab, 0xec, 0x3a, | ||||
| 	0x64, 0x04, 0xc4, 0x62, 0xa1, 0x27, 0xb7, 0x76, 0x03, 0xff, 0xc0, 0x8b, 0x23, 0xc1, 0xb8, 0xbe, | ||||
| 	0xf3, 0x1a, 0x0d, 0xb9, 0x03, 0xcd, 0x20, 0x64, 0x1c, 0x65, 0x18, 0x80, 0xa6, 0x95, 0x09, 0xe4, | ||||
| 	0xc5, 0x43, 0x2a, 0x2e, 0x8c, 0x0d, 0x54, 0xe0, 0x58, 0xca, 0x1c, 0x2a, 0xa8, 0x51, 0x55, 0x32, | ||||
| 	0x39, 0x26, 0x26, 0xd4, 0x22, 0x66, 0x73, 0x26, 0x8c, 0xda, 0xb0, 0xb4, 0xd3, 0xda, 0x83, 0x51, | ||||
| 	0x38, 0x1d, 0x9d, 0xa1, 0xc4, 0xd2, 0x1a, 0x72, 0x07, 0x36, 0x64, 0x5c, 0x8c, 0x3a, 0x5a, 0x34, | ||||
| 	0xa4, 0xc5, 0x7e, 0x2c, 0x2e, 0x2c, 0x94, 0x92, 0x3d, 0xa8, 0xab, 0x37, 0x8d, 0x8c, 0xc6, 0xb0, | ||||
| 	0xb2, 0xd3, 0xda, 0x33, 0xa4, 0x81, 0xbe, 0xe5, 0x48, 0xc1, 0x20, 0x3a, 0xf4, 0x05, 0x5f, 0x58, | ||||
| 	0x89, 0x21, 0x79, 0x17, 0xda, 0xb6, 0xe7, 0x32, 0x5f, 0x4c, 0x44, 0x70, 0xc9, 0x7c, 0xa3, 0x89, | ||||
| 	0x27, 0x6a, 0x29, 0xd9, 0x58, 0x8a, 0xc8, 0x1e, 0xbc, 0x95, 0x37, 0x99, 0x50, 0xdb, 0x66, 0x51, | ||||
| 	0x14, 0x70, 0x03, 0xd0, 0xf6, 0x56, 0xce, 0x76, 0x5f, 0xab, 0xa4, 0x5b, 0xc7, 0x8d, 0x42, 0x8f, | ||||
| 	0x2e, 0x26, 0x3e, 0x9d, 0x33, 0xa3, 0xa5, 0xdc, 0x6a, 0xd9, 0x57, 0x74, 0xce, 0xc8, 0x5d, 0x68, | ||||
| 	0xcd, 0x83, 0xd8, 0x17, 0x93, 0x30, 0x70, 0x7d, 0x61, 0xb4, 0xd1, 0x02, 0x50, 0x74, 0x2a, 0x25, | ||||
| 	0xe4, 0x1d, 0x50, 0x33, 0x05, 0xc6, 0x8e, 0x8a, 0x2b, 0x4a, 0x10, 0x8e, 0xf7, 0xa0, 0xab, 0xd4, | ||||
| 	0xe9, 0x79, 0xba, 0x68, 0xd2, 0x41, 0x69, 0x7a, 0x92, 0x27, 0xd0, 0x44, 0x3c, 0xb8, 0xfe, 0x79, | ||||
| 	0x60, 0xf4, 0x30, 0x6e, 0xb7, 0x72, 0x61, 0x91, 0x98, 0x38, 0xf2, 0xcf, 0x03, 0xab, 0xf1, 0x4a, | ||||
| 	0x8f, 0xc8, 0x27, 0xf0, 0x76, 0xe1, 0xbe, 0x9c, 0xcd, 0xa9, 0xeb, 0xbb, 0xfe, 0x6c, 0x12, 0x47, | ||||
| 	0x2c, 0x32, 0x36, 0x11, 0xe1, 0x46, 0xee, 0xd6, 0x56, 0x62, 0xf0, 0x32, 0x62, 0x11, 0x79, 0x1b, | ||||
| 	0x9a, 0x2a, 0x41, 0x27, 0xae, 0x63, 0xf4, 0xf1, 0x48, 0x0d, 0x25, 0x38, 0x72, 0xc8, 0x03, 0xe8, | ||||
| 	0x85, 0x81, 0xe7, 0xda, 0x8b, 0x49, 0x70, 0xc5, 0x38, 0x77, 0x1d, 0x66, 0x90, 0x61, 0x69, 0xa7, | ||||
| 	0x61, 0x75, 0x95, 0xf8, 0x6b, 0x2d, 0x5d, 0x97, 0x1a, 0xb7, 0xd0, 0x70, 0x25, 0x35, 0x46, 0x00, | ||||
| 	0x76, 0xe0, 0xfb, 0xcc, 0x46, 0xf8, 0x6d, 0xe1, 0x0d, 0xbb, 0xf2, 0x86, 0x07, 0xa9, 0xd4, 0xca, | ||||
| 	0x59, 0x0c, 0x3e, 0x87, 0x76, 0x1e, 0x0a, 0x64, 0x13, 0x2a, 0x97, 0x6c, 0xa1, 0xe1, 0x2f, 0x87, | ||||
| 	0x64, 0x08, 0xd5, 0x2b, 0xea, 0xc5, 0x0c, 0x21, 0xaf, 0x81, 0xa8, 0x96, 0x58, 0x4a, 0xf1, 0xb3, | ||||
| 	0xf2, 0xb3, 0x92, 0xf9, 0xd7, 0x2a, 0x6c, 0x48, 0xf0, 0x91, 0x0f, 0xa1, 0xe3, 0x31, 0x1a, 0xb1, | ||||
| 	0x49, 0x10, 0xca, 0x0d, 0x22, 0x74, 0xd5, 0xda, 0xdb, 0x94, 0xcb, 0x8e, 0xa5, 0xe2, 0x6b, 0x25, | ||||
| 	0xb7, 0xda, 0x5e, 0x6e, 0x26, 0x53, 0xda, 0xf5, 0x05, 0xe3, 0x3e, 0xf5, 0x26, 0x98, 0x0c, 0x2a, | ||||
| 	0xc1, 0xda, 0x89, 0xf0, 0xb9, 0x4c, 0x8a, 0x65, 0x1c, 0x55, 0x56, 0x71, 0x34, 0x80, 0x06, 0xc6, | ||||
| 	0xce, 0x65, 0x91, 0x4e, 0xf6, 0x74, 0x4e, 0xf6, 0xa0, 0x31, 0x67, 0x82, 0xea, 0x5c, 0x93, 0x29, | ||||
| 	0xb1, 0x9d, 0xe4, 0xcc, 0xe8, 0x44, 0x2b, 0x54, 0x42, 0xa4, 0x76, 0x2b, 0x19, 0x51, 0x5b, 0xcd, | ||||
| 	0x88, 0x01, 0x34, 0x52, 0xd0, 0xd5, 0xd5, 0x0b, 0x27, 0x73, 0x49, 0xb3, 0x21, 0xe3, 0x6e, 0xe0, | ||||
| 	0x18, 0x0d, 0x04, 0x8a, 0x9e, 0x49, 0x92, 0xf4, 0xe3, 0xb9, 0x82, 0x50, 0x53, 0x91, 0xa4, 0x1f, | ||||
| 	0xcf, 0x57, 0x11, 0x03, 0x4b, 0x88, 0xf9, 0x11, 0x54, 0xa9, 0xe7, 0xd2, 0x08, 0x53, 0x48, 0xbe, | ||||
| 	0xac, 0xe6, 0xfb, 0xd1, 0xbe, 0x94, 0x5a, 0x4a, 0x49, 0x3e, 0x80, 0xce, 0x8c, 0x07, 0x71, 0x38, | ||||
| 	0xc1, 0x29, 0x8b, 0x8c, 0x36, 0xde, 0x76, 0xd9, 0xba, 0x8d, 0x46, 0xfb, 0xca, 0x46, 0x66, 0xe0, | ||||
| 	0x34, 0x88, 0x7d, 0x67, 0x62, 0xbb, 0x0e, 0x8f, 0x8c, 0x0e, 0x06, 0x0f, 0x50, 0x74, 0x20, 0x25, | ||||
| 	0x32, 0xc5, 0x54, 0x0a, 0xa4, 0x01, 0xee, 0xa2, 0x4d, 0x07, 0xa5, 0xa7, 0x49, 0x94, 0x7f, 0x0c, | ||||
| 	0xfd, 0xa4, 0x28, 0x65, 0x96, 0x3d, 0xb4, 0xdc, 0x4c, 0x14, 0xa9, 0xf1, 0x0e, 0x6c, 0xb2, 0x6b, | ||||
| 	0x49, 0xa1, 0xae, 0x98, 0xcc, 0xe9, 0xf5, 0x44, 0x08, 0x4f, 0xa7, 0x54, 0x37, 0x91, 0x9f, 0xd0, | ||||
| 	0xeb, 0xb1, 0xf0, 0x64, 0xfe, 0xab, 0xdd, 0x31, 0xff, 0xfb, 0x58, 0x8c, 0x9a, 0x28, 0x91, 0xf9, | ||||
| 	0x3f, 0xf8, 0x39, 0x74, 0x0a, 0x4f, 0xb8, 0x06, 0xc8, 0x5b, 0x79, 0x20, 0x37, 0xf3, 0xe0, 0xfd, | ||||
| 	0xe7, 0x06, 0x00, 0xbe, 0xa5, 0x5a, 0xba, 0x5c, 0x01, 0xf2, 0x0f, 0x5c, 0x5e, 0xf3, 0xc0, 0x94, | ||||
| 	0x33, 0x5f, 0x68, 0x30, 0xea, 0xd9, 0x77, 0xe2, 0x30, 0xa9, 0x01, 0xd5, 0x5c, 0x0d, 0x78, 0x04, | ||||
| 	0x1b, 0x12, 0x73, 0x46, 0x2d, 0xa3, 0xea, 0xec, 0x44, 0x88, 0x4e, 0x85, 0x4c, 0xb4, 0x5a, 0x49, | ||||
| 	0x84, 0xfa, 0x6a, 0x22, 0xe4, 0x11, 0xd6, 0x28, 0x22, 0xec, 0x3d, 0xe8, 0xd8, 0x9c, 0x61, 0x3d, | ||||
| 	0x9a, 0xc8, 0xc6, 0x42, 0x23, 0xb0, 0x9d, 0x08, 0xc7, 0xee, 0x9c, 0xc9, 0xf8, 0xc9, 0xc7, 0x00, | ||||
| 	0x54, 0xc9, 0xe1, 0xda, 0xb7, 0x6a, 0xad, 0x7d, 0x2b, 0xac, 0xee, 0x1e, 0xd3, 0x2c, 0x8e, 0xe3, | ||||
| 	0x5c, 0x26, 0x74, 0x0a, 0x99, 0x50, 0x80, 0x7b, 0x77, 0x09, 0xee, 0x4b, 0x98, 0xec, 0xad, 0x60, | ||||
| 	0xf2, 0x5d, 0x68, 0xcb, 0x00, 0x44, 0x21, 0xb5, 0x99, 0x74, 0xb0, 0xa9, 0x02, 0x91, 0xca, 0x8e, | ||||
| 	0x1c, 0xcc, 0xe0, 0x78, 0x3a, 0x5d, 0x5c, 0x04, 0x1e, 0xcb, 0x48, 0xb8, 0x95, 0xca, 0x8e, 0x1c, | ||||
| 	0x79, 0x5e, 0x44, 0x15, 0x41, 0x54, 0xe1, 0x78, 0xf0, 0x11, 0x34, 0xd3, 0xa8, 0x7f, 0x2f, 0x30, | ||||
| 	0xfd, 0xb9, 0x04, 0xed, 0x3c, 0xd1, 0xc9, 0xc5, 0xe3, 0xf1, 0x31, 0x2e, 0xae, 0x58, 0x72, 0x28, | ||||
| 	0x5b, 0x04, 0xce, 0x7c, 0xf6, 0x8a, 0x4e, 0x3d, 0xe5, 0xa0, 0x61, 0x65, 0x02, 0xa9, 0x75, 0x7d, | ||||
| 	0x9b, 0xb3, 0x79, 0x82, 0xaa, 0x8a, 0x95, 0x09, 0xc8, 0xc7, 0x00, 0x6e, 0x14, 0xc5, 0x4c, 0xbd, | ||||
| 	0xdc, 0x06, 0xd2, 0xc0, 0x60, 0xa4, 0xfa, 0xc5, 0x51, 0xd2, 0x2f, 0x8e, 0xc6, 0x49, 0xbf, 0x68, | ||||
| 	0x35, 0xd1, 0x1a, 0x9f, 0x74, 0x1b, 0x6a, 0xf2, 0x81, 0xc6, 0xc7, 0x88, 0xbc, 0x8a, 0xa5, 0x67, | ||||
| 	0xe6, 0x1f, 0xa1, 0xa6, 0x3a, 0x8b, 0xff, 0x2b, 0x79, 0xdf, 0x86, 0x86, 0xf2, 0xed, 0x3a, 0x3a, | ||||
| 	0x57, 0xea, 0x38, 0x3f, 0x72, 0xcc, 0x7f, 0x95, 0xa0, 0x61, 0xb1, 0x28, 0x0c, 0xfc, 0x88, 0xe5, | ||||
| 	0x3a, 0x9f, 0xd2, 0x6b, 0x3b, 0x9f, 0xf2, 0xda, 0xce, 0x27, 0xe9, 0xa7, 0x2a, 0xb9, 0x7e, 0x6a, | ||||
| 	0x00, 0x0d, 0xce, 0x1c, 0x97, 0x33, 0x5b, 0xe8, 0xde, 0x2b, 0x9d, 0x4b, 0xdd, 0x2b, 0xca, 0x65, | ||||
| 	0xc9, 0x8e, 0xb0, 0x2e, 0x34, 0xad, 0x74, 0x4e, 0x9e, 0xe6, 0x1b, 0x06, 0xd5, 0x8a, 0x6d, 0xa9, | ||||
| 	0x86, 0x41, 0x1d, 0x77, 0xb5, 0x63, 0x30, 0xff, 0x51, 0x86, 0xcd, 0x65, 0xf5, 0x1a, 0x10, 0x6c, | ||||
| 	0x41, 0x55, 0x95, 0x14, 0x8d, 0x20, 0xb1, 0x52, 0x4c, 0x2a, 0x4b, 0x5c, 0xf3, 0x8b, 0xe5, 0xbc, | ||||
| 	0x7d, 0xfd, 0xeb, 0x17, 0x73, 0xfa, 0x7d, 0xd8, 0x94, 0xa7, 0x0c, 0x99, 0x93, 0xb5, 0x49, 0x8a, | ||||
| 	0x84, 0x7a, 0x5a, 0x9e, 0x36, 0x4a, 0x0f, 0xa1, 0x9f, 0x98, 0x66, 0xe9, 0x59, 0x2b, 0xd8, 0x1e, | ||||
| 	0x26, 0x59, 0xba, 0x0d, 0xb5, 0xf3, 0x80, 0xcf, 0xa9, 0xd0, 0x3c, 0xa4, 0x67, 0x05, 0x9e, 0x41, | ||||
| 	0xc2, 0x6b, 0x28, 0x58, 0x24, 0x42, 0xf9, 0x29, 0x20, 0xf3, 0x3f, 0x6d, 0xd3, 0x91, 0x88, 0x1a, | ||||
| 	0x56, 0x23, 0x69, 0xcf, 0xcd, 0x5f, 0x43, 0x6f, 0xa9, 0x33, 0x5b, 0x13, 0xc8, 0x6c, 0xfb, 0x72, | ||||
| 	0x61, 0xfb, 0x82, 0xe7, 0xca, 0x92, 0xe7, 0xdf, 0x40, 0xff, 0x0b, 0xea, 0x3b, 0x1e, 0xd3, 0xfe, | ||||
| 	0xf7, 0xf9, 0x2c, 0x92, 0x35, 0x46, 0x7f, 0x28, 0x4c, 0x74, 0x01, 0xe8, 0x58, 0x4d, 0x2d, 0x39, | ||||
| 	0x72, 0xc8, 0x3d, 0xa8, 0x73, 0x65, 0xad, 0x81, 0xd7, 0xca, 0xb5, 0x8e, 0x56, 0xa2, 0x33, 0xbf, | ||||
| 	0x05, 0x52, 0x70, 0x2d, 0xbf, 0x11, 0x16, 0x64, 0x47, 0x02, 0x50, 0x81, 0x42, 0x03, 0xbb, 0x9d, | ||||
| 	0xc7, 0x91, 0x95, 0x6a, 0xc9, 0x10, 0x2a, 0x8c, 0x73, 0xbd, 0x05, 0xf6, 0x6e, 0xd9, 0x17, 0x99, | ||||
| 	0x25, 0x55, 0xe6, 0x4f, 0xa0, 0x7f, 0x16, 0x32, 0xdb, 0xa5, 0x1e, 0x7e, 0x4d, 0xa9, 0x0d, 0xee, | ||||
| 	0x42, 0x55, 0x06, 0x39, 0xc9, 0xd9, 0x26, 0x2e, 0x44, 0xb5, 0x92, 0x9b, 0xdf, 0x82, 0xa1, 0xce, | ||||
| 	0x75, 0x78, 0xed, 0x46, 0x82, 0xf9, 0x36, 0x3b, 0xb8, 0x60, 0xf6, 0xe5, 0xff, 0xf0, 0xe6, 0x57, | ||||
| 	0x70, 0x7b, 0xdd, 0x0e, 0xc9, 0xf9, 0x5a, 0xb6, 0x9c, 0x4d, 0xce, 0x25, 0x7d, 0xe3, 0x1e, 0x0d, | ||||
| 	0x0b, 0x50, 0xf4, 0xb9, 0x94, 0xc8, 0x77, 0x64, 0x72, 0x5d, 0xa4, 0x29, 0x51, 0xcf, 0x92, 0x78, | ||||
| 	0x54, 0x6e, 0x8e, 0xc7, 0x5f, 0x4a, 0xd0, 0x3c, 0x63, 0x22, 0x0e, 0xf1, 0x2e, 0x6f, 0x43, 0x73, | ||||
| 	0xca, 0x83, 0x4b, 0xc6, 0xb3, 0xab, 0x34, 0x94, 0xe0, 0xc8, 0x21, 0x4f, 0xa1, 0x76, 0x10, 0xf8, | ||||
| 	0xe7, 0xee, 0x0c, 0xbf, 0x2d, 0x5b, 0x7b, 0xb7, 0x15, 0xbb, 0xe8, 0xb5, 0x23, 0xa5, 0x53, 0xa5, | ||||
| 	0x56, 0x1b, 0x92, 0x21, 0xb4, 0xf4, 0x17, 0xfa, 0xcb, 0x97, 0x47, 0xcf, 0x93, 0xa6, 0x33, 0x27, | ||||
| 	0x1a, 0x7c, 0x0c, 0xad, 0xdc, 0xc2, 0xef, 0x55, 0x2d, 0x7e, 0x08, 0x80, 0xbb, 0xab, 0x18, 0x6d, | ||||
| 	0xaa, 0xab, 0xea, 0x95, 0xf2, 0x6a, 0x77, 0xa1, 0x29, 0xfb, 0x1b, 0xa5, 0x4e, 0xea, 0x54, 0x29, | ||||
| 	0xab, 0x53, 0xe6, 0x3d, 0xe8, 0x1f, 0xf9, 0x57, 0xd4, 0x73, 0x1d, 0x2a, 0xd8, 0x97, 0x6c, 0x81, | ||||
| 	0x21, 0x58, 0x39, 0x81, 0x79, 0x06, 0x6d, 0xfd, 0xb1, 0xfb, 0x46, 0x67, 0x6c, 0xeb, 0x33, 0x7e, | ||||
| 	0x77, 0x12, 0xbd, 0x0f, 0x3d, 0xed, 0xf4, 0xd8, 0xd5, 0x29, 0x24, 0xcb, 0x3c, 0x67, 0xe7, 0xee, | ||||
| 	0xb5, 0x76, 0xad, 0x67, 0xe6, 0x33, 0xd8, 0xcc, 0x99, 0xa6, 0xd7, 0xb9, 0x64, 0x8b, 0x28, 0xf9, | ||||
| 	0x11, 0x40, 0x8e, 0x93, 0x08, 0x94, 0xb3, 0x08, 0x98, 0xd0, 0xd5, 0x2b, 0x5f, 0x30, 0x71, 0xc3, | ||||
| 	0xed, 0xbe, 0x4c, 0x0f, 0xf2, 0x82, 0x69, 0xe7, 0xf7, 0xa1, 0xca, 0xe4, 0x4d, 0xf3, 0x25, 0x2c, | ||||
| 	0x1f, 0x01, 0x4b, 0xa9, 0xd7, 0x6c, 0xf8, 0x2c, 0xdd, 0xf0, 0x34, 0x56, 0x1b, 0xbe, 0xa1, 0x2f, | ||||
| 	0xf3, 0xbd, 0xf4, 0x18, 0xa7, 0xb1, 0xb8, 0xe9, 0x45, 0xef, 0x41, 0x5f, 0x1b, 0x3d, 0x67, 0x1e, | ||||
| 	0x13, 0xec, 0x86, 0x2b, 0xdd, 0x07, 0x52, 0x30, 0xbb, 0xc9, 0xdd, 0x1d, 0x68, 0x8c, 0xc7, 0xc7, | ||||
| 	0xa9, 0xb6, 0xc8, 0x8d, 0xe6, 0x27, 0xd0, 0x3f, 0x8b, 0x9d, 0xe0, 0x94, 0xbb, 0x57, 0xae, 0xc7, | ||||
| 	0x66, 0x6a, 0xb3, 0xa4, 0xff, 0x2c, 0xe5, 0xfa, 0xcf, 0xb5, 0xd5, 0xc8, 0xdc, 0x01, 0x52, 0x58, | ||||
| 	0x9e, 0xbe, 0x5b, 0x14, 0x3b, 0x81, 0x4e, 0x61, 0x1c, 0x9b, 0x3b, 0xd0, 0x1e, 0x53, 0x59, 0xef, | ||||
| 	0x1d, 0x65, 0x63, 0x40, 0x5d, 0xa8, 0xb9, 0x36, 0x4b, 0xa6, 0xe6, 0x1e, 0x6c, 0x1d, 0x50, 0xfb, | ||||
| 	0xc2, 0xf5, 0x67, 0xcf, 0xdd, 0x48, 0x36, 0x3c, 0x7a, 0xc5, 0x00, 0x1a, 0x8e, 0x16, 0xe8, 0x25, | ||||
| 	0xe9, 0xdc, 0x7c, 0x0c, 0x6f, 0xe5, 0x7e, 0x69, 0x39, 0x13, 0x34, 0x89, 0xc7, 0x16, 0x54, 0x23, | ||||
| 	0x39, 0xc3, 0x15, 0x55, 0x4b, 0x4d, 0xcc, 0xaf, 0x60, 0x2b, 0x5f, 0x80, 0x65, 0xfb, 0x91, 0x5c, | ||||
| 	0x1c, 0x1b, 0x83, 0x52, 0xae, 0x31, 0xd0, 0x31, 0x2b, 0x67, 0xf5, 0x64, 0x13, 0x2a, 0xbf, 0xfc, | ||||
| 	0x66, 0xac, 0xc1, 0x2e, 0x87, 0xe6, 0xef, 0xe5, 0xf6, 0x45, 0x7f, 0x6a, 0xfb, 0x42, 0x77, 0x50, | ||||
| 	0x7a, 0x93, 0xee, 0x60, 0x0d, 0xde, 0x1e, 0x43, 0xff, 0xc4, 0x0b, 0xec, 0xcb, 0x43, 0x3f, 0x17, | ||||
| 	0x0d, 0x03, 0xea, 0xcc, 0xcf, 0x07, 0x23, 0x99, 0x9a, 0x0f, 0xa0, 0x77, 0x1c, 0xd8, 0xd4, 0x3b, | ||||
| 	0x09, 0x62, 0x5f, 0xa4, 0x51, 0xc0, 0x9f, 0xbe, 0xb4, 0xa9, 0x9a, 0x98, 0x8f, 0xa1, 0xab, 0x4b, | ||||
| 	0xb4, 0x7f, 0x1e, 0x24, 0xcc, 0x98, 0x15, 0xf3, 0x52, 0xb1, 0xd7, 0x36, 0x8f, 0xa1, 0x97, 0x99, | ||||
| 	0x2b, 0xbf, 0x0f, 0xa0, 0xa6, 0xd4, 0xfa, 0x6e, 0xbd, 0xf4, 0x03, 0x52, 0x59, 0x5a, 0x5a, 0xbd, | ||||
| 	0xe6, 0x52, 0x73, 0xe8, 0x9e, 0xe2, 0x4f, 0x90, 0x87, 0xfe, 0x95, 0x72, 0x76, 0x04, 0x44, 0xfd, | ||||
| 	0x28, 0x39, 0x61, 0xfe, 0x95, 0xcb, 0x03, 0x1f, 0xfb, 0xdb, 0x92, 0x6e, 0x61, 0x12, 0xc7, 0xe9, | ||||
| 	0xa2, 0xc4, 0xc2, 0xea, 0x87, 0xcb, 0xa2, 0xb5, 0x31, 0x84, 0xec, 0x07, 0x0e, 0x59, 0x6a, 0x38, | ||||
| 	0x9b, 0x07, 0x82, 0x4d, 0xa8, 0xe3, 0x24, 0xd9, 0x02, 0x4a, 0xb4, 0xef, 0x38, 0x7c, 0xef, 0x3f, | ||||
| 	0x65, 0xa8, 0x7f, 0xa6, 0x08, 0x9c, 0x7c, 0x0a, 0x9d, 0x42, 0xb9, 0x26, 0x6f, 0xe1, 0x2f, 0x1c, | ||||
| 	0xcb, 0xcd, 0xc1, 0x60, 0x7b, 0x45, 0xac, 0xee, 0xf5, 0x04, 0xda, 0xf9, 0x62, 0x4c, 0xb0, 0xf0, | ||||
| 	0xe2, 0xcf, 0xad, 0x03, 0xf4, 0xb4, 0x5a, 0xa9, 0xcf, 0x60, 0x6b, 0x5d, 0x99, 0x24, 0x77, 0xb2, | ||||
| 	0x1d, 0x56, 0x4b, 0xf4, 0xe0, 0x9d, 0x9b, 0xb4, 0x49, 0x79, 0xad, 0x1f, 0x78, 0x8c, 0xfa, 0x71, | ||||
| 	0x98, 0x3f, 0x41, 0x36, 0x24, 0x4f, 0xa1, 0x53, 0x28, 0x14, 0xea, 0x9e, 0x2b, 0xb5, 0x23, 0xbf, | ||||
| 	0xe4, 0x3e, 0x54, 0xb1, 0x38, 0x91, 0x4e, 0xa1, 0x4a, 0x0e, 0xba, 0xe9, 0x54, 0xed, 0x3d, 0x84, | ||||
| 	0x0d, 0xfc, 0x11, 0x2e, 0xb7, 0x31, 0xae, 0x48, 0x2b, 0xd7, 0xde, 0xbf, 0x4b, 0x50, 0x4f, 0x7e, | ||||
| 	0x98, 0x7d, 0x0a, 0x1b, 0xb2, 0x06, 0x90, 0x5b, 0x39, 0x1a, 0x4d, 0xea, 0xc7, 0x60, 0x6b, 0x49, | ||||
| 	0xa8, 0x36, 0x18, 0x41, 0xe5, 0x05, 0x13, 0x84, 0xe4, 0x94, 0xba, 0x18, 0x0c, 0x6e, 0x15, 0x65, | ||||
| 	0xa9, 0xfd, 0x69, 0x5c, 0xb4, 0xd7, 0x5c, 0x5e, 0xb0, 0x4f, 0x59, 0xfa, 0x23, 0xa8, 0x29, 0x96, | ||||
| 	0x55, 0x41, 0x59, 0xe1, 0x67, 0xf5, 0xf8, 0xab, 0x7c, 0xbc, 0xf7, 0xf7, 0x0d, 0x80, 0xb3, 0x45, | ||||
| 	0x24, 0xd8, 0xfc, 0x57, 0x2e, 0x7b, 0x45, 0x1e, 0x42, 0xef, 0x39, 0x3b, 0xa7, 0xb1, 0x27, 0xf0, | ||||
| 	0x6b, 0x49, 0xb2, 0x49, 0x2e, 0x26, 0xd8, 0xf0, 0xa5, 0x64, 0x7d, 0x1f, 0x5a, 0x27, 0xf4, 0xfa, | ||||
| 	0xf5, 0x76, 0x9f, 0x42, 0xa7, 0xc0, 0xc1, 0xfa, 0x88, 0xcb, 0xac, 0xae, 0x8f, 0xb8, 0xca, 0xd6, | ||||
| 	0xf7, 0xa1, 0xae, 0x99, 0x39, 0xbf, 0x07, 0xd6, 0xb0, 0x02, 0x63, 0xff, 0x14, 0x7a, 0x4b, 0xbc, | ||||
| 	0x9c, 0xb7, 0xc7, 0x5f, 0x24, 0xd6, 0xf2, 0xf6, 0x33, 0xf9, 0xb5, 0x53, 0xe4, 0xe6, 0xfc, 0xc2, | ||||
| 	0xdb, 0x8a, 0x0f, 0xd7, 0x91, 0xf7, 0x8b, 0xe2, 0x77, 0x12, 0x7e, 0x25, 0x1a, 0xcb, 0xf4, 0x99, | ||||
| 	0x90, 0x77, 0xe2, 0x68, 0x1d, 0x0d, 0x3f, 0x81, 0x76, 0x9e, 0x41, 0x57, 0x52, 0x70, 0x95, 0x5e, | ||||
| 	0x1f, 0x01, 0x64, 0x24, 0x9a, 0xb7, 0x47, 0x78, 0x2c, 0xf3, 0xeb, 0x87, 0x00, 0x19, 0x35, 0x2a, | ||||
| 	0x54, 0x15, 0x99, 0x55, 0x2d, 0x5b, 0xa6, 0xcf, 0x87, 0xd0, 0x4c, 0xe9, 0x2c, 0xbf, 0x07, 0x3a, | ||||
| 	0x28, 0xb2, 0xe3, 0x67, 0xa3, 0xdf, 0x3e, 0x9a, 0xb9, 0xe2, 0x22, 0x9e, 0x8e, 0xec, 0x60, 0xbe, | ||||
| 	0x7b, 0x41, 0xa3, 0x0b, 0xd7, 0x0e, 0x78, 0xb8, 0x7b, 0x25, 0xc1, 0xb4, 0xbb, 0xf2, 0x9f, 0xd1, | ||||
| 	0xb4, 0x86, 0x1f, 0x7b, 0x1f, 0xfc, 0x37, 0x00, 0x00, 0xff, 0xff, 0x93, 0x15, 0xb9, 0x42, 0x4f, | ||||
| 	0x1a, 0x00, 0x00, | ||||
| } | ||||
|   | ||||
| @@ -207,6 +207,9 @@ message Auth { | ||||
| 	// Explicit maximum lifetime for the token. Unlike normal TTLs, the maximum | ||||
| 	// TTL is a hard limit and cannot be exceeded, also counts for periodic tokens. | ||||
| 	int64 explicit_max_ttl = 16; | ||||
|  | ||||
| 	// TokenType is the type of token being requested | ||||
| 	uint32 token_type = 17; | ||||
| } | ||||
|  | ||||
| message TokenEntry { | ||||
| @@ -227,6 +230,7 @@ message TokenEntry { | ||||
| 	repeated string bound_cidrs = 15; | ||||
| 	string namespace_id = 16; | ||||
| 	string cubbyhole_id = 17; | ||||
| 	uint32 type = 18; | ||||
| } | ||||
|  | ||||
| message LeaseOptions { | ||||
|   | ||||
| @@ -486,6 +486,7 @@ func LogicalAuthToProtoAuth(a *logical.Auth) (*Auth, error) { | ||||
|  | ||||
| 	return &Auth{ | ||||
| 		LeaseOptions:     lo, | ||||
| 		TokenType:        uint32(a.TokenType), | ||||
| 		InternalData:     string(buf[:]), | ||||
| 		DisplayName:      a.DisplayName, | ||||
| 		Policies:         a.Policies, | ||||
| @@ -532,6 +533,7 @@ func ProtoAuthToLogicalAuth(a *Auth) (*logical.Auth, error) { | ||||
|  | ||||
| 	return &logical.Auth{ | ||||
| 		LeaseOptions:     lo, | ||||
| 		TokenType:        logical.TokenType(a.TokenType), | ||||
| 		InternalData:     data, | ||||
| 		DisplayName:      a.DisplayName, | ||||
| 		Policies:         a.Policies, | ||||
| @@ -578,6 +580,7 @@ func LogicalTokenEntryToProtoTokenEntry(t *logical.TokenEntry) *TokenEntry { | ||||
| 		BoundCIDRs:     boundCIDRs, | ||||
| 		NamespaceID:    t.NamespaceID, | ||||
| 		CubbyholeID:    t.CubbyholeID, | ||||
| 		Type:           uint32(t.Type), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -614,5 +617,6 @@ func ProtoTokenEntryToLogicalTokenEntry(t *TokenEntry) (*logical.TokenEntry, err | ||||
| 		BoundCIDRs:     boundCIDRs, | ||||
| 		NamespaceID:    t.NamespaceID, | ||||
| 		CubbyholeID:    t.CubbyholeID, | ||||
| 		Type:           logical.TokenType(t.Type), | ||||
| 	}, nil | ||||
| } | ||||
|   | ||||
| @@ -213,6 +213,20 @@ func Test(tt TestT, c TestCase) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	tokenInfo, err := client.Auth().Token().LookupSelf() | ||||
| 	if err != nil { | ||||
| 		tt.Fatal("error looking up token: ", err) | ||||
| 		return | ||||
| 	} | ||||
| 	var tokenPolicies []string | ||||
| 	if tokenPoliciesRaw, ok := tokenInfo.Data["policies"]; ok { | ||||
| 		if tokenPoliciesSliceRaw, ok := tokenPoliciesRaw.([]interface{}); ok { | ||||
| 			for _, p := range tokenPoliciesSliceRaw { | ||||
| 				tokenPolicies = append(tokenPolicies, p.(string)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Make requests | ||||
| 	var revoke []*logical.Request | ||||
| 	for i, s := range c.Steps { | ||||
| @@ -228,6 +242,12 @@ func Test(tt TestT, c TestCase) { | ||||
| 		} | ||||
| 		if !s.Unauthenticated { | ||||
| 			req.ClientToken = client.Token() | ||||
| 			req.SetTokenEntry(&logical.TokenEntry{ | ||||
| 				ID:          req.ClientToken, | ||||
| 				NamespaceID: namespace.RootNamespaceID, | ||||
| 				Policies:    tokenPolicies, | ||||
| 				DisplayName: tokenInfo.Data["display_name"].(string), | ||||
| 			}) | ||||
| 		} | ||||
| 		if s.RemoteAddr != "" { | ||||
| 			req.Connection = &logical.Connection{RemoteAddr: s.RemoteAddr} | ||||
|   | ||||
| @@ -6,8 +6,49 @@ import ( | ||||
| 	sockaddr "github.com/hashicorp/go-sockaddr" | ||||
| ) | ||||
|  | ||||
| type TokenType uint8 | ||||
|  | ||||
| const ( | ||||
| 	// TokenTypeDefault means "use the default, if any, that is currently set | ||||
| 	// on the mount". If not set, results in a Service token. | ||||
| 	TokenTypeDefault TokenType = iota | ||||
|  | ||||
| 	// TokenTypeService is a "normal" Vault token for long-lived services | ||||
| 	TokenTypeService | ||||
|  | ||||
| 	// TokenTypeBatch is a batch token | ||||
| 	TokenTypeBatch | ||||
|  | ||||
| 	// TokenTypeDefaultService, configured on a mount, means that if | ||||
| 	// TokenTypeDefault is sent back by the mount, create Service tokens | ||||
| 	TokenTypeDefaultService | ||||
|  | ||||
| 	// TokenTypeDefaultBatch, configured on a mount, means that if | ||||
| 	// TokenTypeDefault is sent back by the mount, create Batch tokens | ||||
| 	TokenTypeDefaultBatch | ||||
| ) | ||||
|  | ||||
| func (t TokenType) String() string { | ||||
| 	switch t { | ||||
| 	case TokenTypeDefault: | ||||
| 		return "default" | ||||
| 	case TokenTypeService: | ||||
| 		return "service" | ||||
| 	case TokenTypeBatch: | ||||
| 		return "batch" | ||||
| 	case TokenTypeDefaultService: | ||||
| 		return "default-service" | ||||
| 	case TokenTypeDefaultBatch: | ||||
| 		return "default-batch" | ||||
| 	default: | ||||
| 		panic("unreachable") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TokenEntry is used to represent a given token | ||||
| type TokenEntry struct { | ||||
| 	Type TokenType `json:"type" mapstructure:"type" structs:"type" sentinel:""` | ||||
|  | ||||
| 	// ID of this entry, generally a random UUID | ||||
| 	ID string `json:"id" mapstructure:"id" structs:"id" sentinel:""` | ||||
|  | ||||
| @@ -107,6 +148,9 @@ func (te *TokenEntry) SentinelGet(key string) (interface{}, error) { | ||||
|  | ||||
| 	case "meta", "metadata": | ||||
| 		return te.Meta, nil | ||||
|  | ||||
| 	case "type": | ||||
| 		return te.Type.String(), nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil | ||||
| @@ -124,5 +168,6 @@ func (te *TokenEntry) SentinelKeys() []string { | ||||
| 		"creation_time_unix", | ||||
| 		"meta", | ||||
| 		"metadata", | ||||
| 		"type", | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -36,6 +36,7 @@ func LogicalResponseToHTTPResponse(input *Response) *HTTPResponse { | ||||
| 			LeaseDuration:    int(input.Auth.TTL.Seconds()), | ||||
| 			Renewable:        input.Auth.Renewable, | ||||
| 			EntityID:         input.Auth.EntityID, | ||||
| 			TokenType:        input.Auth.TokenType.String(), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -68,6 +69,12 @@ func HTTPResponseToLogicalResponse(input *HTTPResponse) *Response { | ||||
| 		} | ||||
| 		logicalResp.Auth.Renewable = input.Auth.Renewable | ||||
| 		logicalResp.Auth.TTL = time.Second * time.Duration(input.Auth.LeaseDuration) | ||||
| 		switch input.Auth.TokenType { | ||||
| 		case "service": | ||||
| 			logicalResp.Auth.TokenType = TokenTypeService | ||||
| 		case "batch": | ||||
| 			logicalResp.Auth.TokenType = TokenTypeBatch | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return logicalResp | ||||
| @@ -94,6 +101,7 @@ type HTTPAuth struct { | ||||
| 	LeaseDuration    int               `json:"lease_duration"` | ||||
| 	Renewable        bool              `json:"renewable"` | ||||
| 	EntityID         string            `json:"entity_id"` | ||||
| 	TokenType        string            `json:"token_type"` | ||||
| } | ||||
|  | ||||
| type HTTPWrapInfo struct { | ||||
|   | ||||
| @@ -572,7 +572,7 @@ ssl_storage_port: 7001 | ||||
| # | ||||
| # Setting listen_address to 0.0.0.0 is always wrong. | ||||
| # | ||||
| listen_address: 172.17.0.2 | ||||
| listen_address: 172.17.0.3 | ||||
|  | ||||
| # Set listen_address OR listen_interface, not both. Interfaces must correspond | ||||
| # to a single address, IP aliasing is not supported. | ||||
|   | ||||
| @@ -661,6 +661,10 @@ func (c *Core) setupCredentials(ctx context.Context) error { | ||||
| 		if entry.Type == "token" { | ||||
| 			c.tokenStore = backend.(*TokenStore) | ||||
|  | ||||
| 			// At some point when this isn't beta we may persist this but for | ||||
| 			// now always set it on mount | ||||
| 			entry.Config.TokenType = logical.TokenTypeDefaultService | ||||
|  | ||||
| 			// this is loaded *after* the normal mounts, including cubbyhole | ||||
| 			c.router.tokenStoreSaltFunc = c.tokenStore.Salt | ||||
| 			if !c.IsDRSecondary() { | ||||
|   | ||||
| @@ -845,7 +845,11 @@ func (b *AESGCMBarrier) encrypt(path string, term uint32, gcm cipher.AEAD, plain | ||||
| 	case AESGCMVersion1: | ||||
| 		out = gcm.Seal(out, nonce, plain, nil) | ||||
| 	case AESGCMVersion2: | ||||
| 		out = gcm.Seal(out, nonce, plain, []byte(path)) | ||||
| 		aad := []byte(nil) | ||||
| 		if path != "" { | ||||
| 			aad = []byte(path) | ||||
| 		} | ||||
| 		out = gcm.Seal(out, nonce, plain, aad) | ||||
| 	default: | ||||
| 		panic("Unknown AESGCM version") | ||||
| 	} | ||||
| @@ -865,7 +869,11 @@ func (b *AESGCMBarrier) decrypt(path string, gcm cipher.AEAD, cipher []byte) ([] | ||||
| 	case AESGCMVersion1: | ||||
| 		return gcm.Open(out, nonce, raw, nil) | ||||
| 	case AESGCMVersion2: | ||||
| 		return gcm.Open(out, nonce, raw, []byte(path)) | ||||
| 		aad := []byte(nil) | ||||
| 		if path != "" { | ||||
| 			aad = []byte(path) | ||||
| 		} | ||||
| 		return gcm.Open(out, nonce, raw, aad) | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("version bytes mis-match") | ||||
| 	} | ||||
|   | ||||
| @@ -1047,6 +1047,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 	// Audit-log the request before going any further | ||||
| 	auth := &logical.Auth{ | ||||
| 		ClientToken: req.ClientToken, | ||||
| 		Accessor:    req.ClientTokenAccessor, | ||||
| 	} | ||||
| 	if te != nil { | ||||
| 		auth.IdentityPolicies = identityPolicies[te.NamespaceID] | ||||
| @@ -1057,6 +1058,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 		auth.Metadata = te.Meta | ||||
| 		auth.DisplayName = te.DisplayName | ||||
| 		auth.EntityID = te.EntityID | ||||
| 		auth.TokenType = te.Type | ||||
| 	} | ||||
|  | ||||
| 	logInput := &audit.LogInput{ | ||||
|   | ||||
| @@ -317,6 +317,7 @@ func TestCore_HandleRequest_Lease(t *testing.T) { | ||||
| 	// Read the key | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Data = nil | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = c.HandleRequest(ctx, req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -359,6 +360,7 @@ func TestCore_HandleRequest_Lease_MaxLength(t *testing.T) { | ||||
| 	// Read the key | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Data = nil | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = c.HandleRequest(ctx, req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -401,6 +403,7 @@ func TestCore_HandleRequest_Lease_DefaultLength(t *testing.T) { | ||||
| 	// Read the key | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Data = nil | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = c.HandleRequest(ctx, req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -409,7 +412,7 @@ func TestCore_HandleRequest_Lease_DefaultLength(t *testing.T) { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| 	if resp.Secret.TTL != c.defaultLeaseTTL { | ||||
| 		t.Fatalf("bad: %#v, %d", resp.Secret, c.defaultLeaseTTL) | ||||
| 		t.Fatalf("bad: %d, %d", resp.Secret.TTL/time.Second, c.defaultLeaseTTL/time.Second) | ||||
| 	} | ||||
| 	if resp.Secret.LeaseID == "" { | ||||
| 		t.Fatalf("bad: %#v", resp.Secret) | ||||
| @@ -481,7 +484,7 @@ func TestCore_HandleRequest_NoSlash(t *testing.T) { | ||||
| // Test a root path is denied if non-root | ||||
| func TestCore_HandleRequest_RootPath(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	testMakeTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Operation:   logical.ReadOperation, | ||||
| @@ -516,7 +519,7 @@ func TestCore_HandleRequest_RootPath_WithSudo(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	// Child token (non-root) but with 'test' policy should have access | ||||
| 	testMakeTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
| 	req = &logical.Request{ | ||||
| 		Operation:   logical.ReadOperation, | ||||
| 		Path:        "sys/policy", // root protected! | ||||
| @@ -534,7 +537,7 @@ func TestCore_HandleRequest_RootPath_WithSudo(t *testing.T) { | ||||
| // Check that standard permissions work | ||||
| func TestCore_HandleRequest_PermissionDenied(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	testMakeTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Operation: logical.UpdateOperation, | ||||
| @@ -554,7 +557,7 @@ func TestCore_HandleRequest_PermissionDenied(t *testing.T) { | ||||
| // Check that standard permissions work | ||||
| func TestCore_HandleRequest_PermissionAllowed(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	testMakeTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaCore(t, c, root, "child", "", []string{"test"}) | ||||
|  | ||||
| 	// Set the 'test' policy object to permit access to secret/ | ||||
| 	req := &logical.Request{ | ||||
| @@ -719,6 +722,7 @@ func TestCore_HandleLogin_Token(t *testing.T) { | ||||
| 		TTL:          time.Hour * 24, | ||||
| 		CreationTime: te.CreationTime, | ||||
| 		NamespaceID:  namespace.RootNamespaceID, | ||||
| 		Type:         logical.TokenTypeService, | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(te, expect) { | ||||
| @@ -884,6 +888,7 @@ func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) { | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	if _, err := c.HandleRequest(namespace.RootContext(nil), req); err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| @@ -1020,6 +1025,7 @@ func TestCore_HandleRequest_CreateToken_Lease(t *testing.T) { | ||||
| 		CreationTime: te.CreationTime, | ||||
| 		TTL:          time.Hour * 24 * 32, | ||||
| 		NamespaceID:  namespace.RootNamespaceID, | ||||
| 		Type:         logical.TokenTypeService, | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(te, expect) { | ||||
| 		t.Fatalf("Bad: %#v expect: %#v", te, expect) | ||||
| @@ -1066,6 +1072,7 @@ func TestCore_HandleRequest_CreateToken_NoDefaultPolicy(t *testing.T) { | ||||
| 		CreationTime: te.CreationTime, | ||||
| 		TTL:          time.Hour * 24 * 32, | ||||
| 		NamespaceID:  namespace.RootNamespaceID, | ||||
| 		Type:         logical.TokenTypeService, | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(te, expect) { | ||||
| 		t.Fatalf("Bad: %#v expect: %#v", te, expect) | ||||
| @@ -1837,6 +1844,7 @@ func TestCore_HandleRequest_InternalData(t *testing.T) { | ||||
| 		Path:        "foo/test", | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	lreq.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	lresp, err := c.HandleRequest(namespace.RootContext(nil), lreq) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1909,6 +1917,7 @@ func TestCore_RenewSameLease(t *testing.T) { | ||||
| 	// Read the key | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Data = nil | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = c.HandleRequest(namespace.RootContext(nil), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -2078,6 +2087,7 @@ path "secret/*" { | ||||
| 	// Read the key | ||||
| 	req.Operation = logical.ReadOperation | ||||
| 	req.Data = nil | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = c.HandleRequest(namespace.RootContext(nil), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
|   | ||||
| @@ -873,6 +873,26 @@ func (m *ExpirationManager) Renew(ctx context.Context, leaseID string, increment | ||||
| 	le.ExpireTime = resp.Secret.ExpirationTime() | ||||
| 	le.LastRenewalTime = time.Now() | ||||
|  | ||||
| 	// If the token it's associated with is a batch token, constrain lease | ||||
| 	// times | ||||
| 	if le.ClientTokenType == logical.TokenTypeBatch { | ||||
| 		te, err := m.tokenStore.Lookup(ctx, le.ClientToken) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if te == nil { | ||||
| 			return nil, errors.New("cannot renew lease, no valid associated token") | ||||
| 		} | ||||
| 		tokenLeaseTimes, err := m.FetchLeaseTimesByToken(ctx, te) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if le.ExpireTime.After(tokenLeaseTimes.ExpireTime) { | ||||
| 			resp.Secret.TTL = tokenLeaseTimes.ExpireTime.Sub(le.LastRenewalTime) | ||||
| 			le.ExpireTime = tokenLeaseTimes.ExpireTime | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		m.pendingLock.Lock() | ||||
| 		if err := m.persistEntry(ctx, le); err != nil { | ||||
| @@ -1012,7 +1032,8 @@ func (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request | ||||
| func (m *ExpirationManager) Register(ctx context.Context, req *logical.Request, resp *logical.Response) (id string, retErr error) { | ||||
| 	defer metrics.MeasureSince([]string{"expire", "register"}, time.Now()) | ||||
|  | ||||
| 	if req.ClientToken == "" { | ||||
| 	te := req.TokenEntry() | ||||
| 	if te == nil { | ||||
| 		return "", fmt.Errorf("cannot register a lease with an empty client token") | ||||
| 	} | ||||
|  | ||||
| @@ -1046,6 +1067,7 @@ func (m *ExpirationManager) Register(ctx context.Context, req *logical.Request, | ||||
| 	le := &leaseEntry{ | ||||
| 		LeaseID:         leaseID, | ||||
| 		ClientToken:     req.ClientToken, | ||||
| 		ClientTokenType: te.Type, | ||||
| 		Path:            req.Path, | ||||
| 		Data:            resp.Data, | ||||
| 		Secret:          resp.Secret, | ||||
| @@ -1078,15 +1100,36 @@ func (m *ExpirationManager) Register(ctx context.Context, req *logical.Request, | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// If the token is a batch token, we want to constrain the maximum lifetime | ||||
| 	// by the token's lifetime | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		tokenLeaseTimes, err := m.FetchLeaseTimesByToken(ctx, te) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if le.ExpireTime.After(tokenLeaseTimes.ExpireTime) { | ||||
| 			le.ExpireTime = tokenLeaseTimes.ExpireTime | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Encode the entry | ||||
| 	if err := m.persistEntry(ctx, le); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Maintain secondary index by token | ||||
| 	if err := m.createIndexByToken(ctx, le); err != nil { | ||||
| 	// Maintain secondary index by token, except for orphan batch tokens | ||||
| 	switch { | ||||
| 	case te.Type != logical.TokenTypeBatch: | ||||
| 		if err := m.createIndexByToken(ctx, le, le.ClientToken); err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	case te.Parent != "": | ||||
| 		// If it's a non-orphan batch token, assign the secondary index to its | ||||
| 		// parent | ||||
| 		if err := m.createIndexByToken(ctx, le, te.Parent); err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Setup revocation timer if there is a lease | ||||
| 	m.updatePending(le, resp.Secret.LeaseTotal()) | ||||
| @@ -1101,8 +1144,12 @@ func (m *ExpirationManager) Register(ctx context.Context, req *logical.Request, | ||||
| func (m *ExpirationManager) RegisterAuth(ctx context.Context, te *logical.TokenEntry, auth *logical.Auth) error { | ||||
| 	defer metrics.MeasureSince([]string{"expire", "register-auth"}, time.Now()) | ||||
|  | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		return errors.New("cannot register a lease for a batch token") | ||||
| 	} | ||||
|  | ||||
| 	if auth.ClientToken == "" { | ||||
| 		return fmt.Errorf("cannot register an auth lease with an empty token") | ||||
| 		return errors.New("cannot register an auth lease with an empty token") | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(te.Path, "..") { | ||||
| @@ -1152,9 +1199,24 @@ func (m *ExpirationManager) RegisterAuth(ctx context.Context, te *logical.TokenE | ||||
|  | ||||
| // FetchLeaseTimesByToken is a helper function to use token values to compute | ||||
| // the leaseID, rather than pushing that logic back into the token store. | ||||
| // As a special case, for a batch token it simply returns the information | ||||
| // encoded on it. | ||||
| func (m *ExpirationManager) FetchLeaseTimesByToken(ctx context.Context, te *logical.TokenEntry) (*leaseEntry, error) { | ||||
| 	defer metrics.MeasureSince([]string{"expire", "fetch-lease-times-by-token"}, time.Now()) | ||||
|  | ||||
| 	if te == nil { | ||||
| 		return nil, errors.New("cannot fetch lease times for nil token") | ||||
| 	} | ||||
|  | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		issueTime := time.Unix(te.CreationTime, 0) | ||||
| 		return &leaseEntry{ | ||||
| 			IssueTime:       issueTime, | ||||
| 			ExpireTime:      issueTime.Add(te.TTL), | ||||
| 			ClientTokenType: logical.TokenTypeBatch, | ||||
| 		}, nil | ||||
| 	} | ||||
|  | ||||
| 	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -1273,6 +1335,10 @@ func (m *ExpirationManager) revokeEntry(ctx context.Context, le *leaseEntry) err | ||||
| 	// Revocation of login tokens is special since we can by-pass the | ||||
| 	// backend and directly interact with the token store | ||||
| 	if le.Auth != nil { | ||||
| 		if le.ClientTokenType == logical.TokenTypeBatch { | ||||
| 			return errors.New("batch tokens cannot be revoked") | ||||
| 		} | ||||
|  | ||||
| 		if err := m.tokenStore.revokeTree(ctx, le); err != nil { | ||||
| 			return errwrap.Wrapf("failed to revoke token: {{err}}", err) | ||||
| 		} | ||||
| @@ -1319,6 +1385,10 @@ func (m *ExpirationManager) renewEntry(ctx context.Context, le *leaseEntry, incr | ||||
| // renewAuthEntry is used to attempt renew of an auth entry. Only the token | ||||
| // store should get the actual token ID intact. | ||||
| func (m *ExpirationManager) renewAuthEntry(ctx context.Context, req *logical.Request, le *leaseEntry, increment time.Duration) (*logical.Response, error) { | ||||
| 	if le.ClientTokenType == logical.TokenTypeBatch { | ||||
| 		return logical.ErrorResponse("batch tokens cannot be renewed"), nil | ||||
| 	} | ||||
|  | ||||
| 	auth := *le.Auth | ||||
| 	auth.IssueTime = le.IssueTime | ||||
| 	auth.Increment = increment | ||||
| @@ -1446,10 +1516,10 @@ func (m *ExpirationManager) deleteEntry(ctx context.Context, le *leaseEntry) err | ||||
| } | ||||
|  | ||||
| // createIndexByToken creates a secondary index from the token to a lease entry | ||||
| func (m *ExpirationManager) createIndexByToken(ctx context.Context, le *leaseEntry) error { | ||||
| func (m *ExpirationManager) createIndexByToken(ctx context.Context, le *leaseEntry, token string) error { | ||||
| 	tokenNS := namespace.RootNamespace | ||||
| 	saltCtx := namespace.ContextWithNamespace(ctx, namespace.RootNamespace) | ||||
| 	_, nsID := namespace.SplitIDFromString(le.ClientToken) | ||||
| 	_, nsID := namespace.SplitIDFromString(token) | ||||
| 	if nsID != "" { | ||||
| 		tokenNS, err := NamespaceByID(ctx, nsID, m.core) | ||||
| 		if err != nil { | ||||
| @@ -1460,7 +1530,7 @@ func (m *ExpirationManager) createIndexByToken(ctx context.Context, le *leaseEnt | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	saltedID, err := m.tokenStore.SaltID(saltCtx, le.ClientToken) | ||||
| 	saltedID, err := m.tokenStore.SaltID(saltCtx, token) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -1674,6 +1744,7 @@ func (m *ExpirationManager) emitMetrics() { | ||||
| type leaseEntry struct { | ||||
| 	LeaseID         string                 `json:"lease_id"` | ||||
| 	ClientToken     string                 `json:"client_token"` | ||||
| 	ClientTokenType logical.TokenType      `json:"token_type"` | ||||
| 	Path            string                 `json:"path"` | ||||
| 	Data            map[string]interface{} `json:"data"` | ||||
| 	Secret          *logical.Secret        `json:"secret"` | ||||
| @@ -1691,24 +1762,29 @@ func (le *leaseEntry) encode() ([]byte, error) { | ||||
| } | ||||
|  | ||||
| func (le *leaseEntry) renewable() (bool, error) { | ||||
| 	var err error | ||||
| 	switch { | ||||
| 	// If there is no entry, cannot review | ||||
| 	case le == nil || le.ExpireTime.IsZero(): | ||||
| 		err = fmt.Errorf("lease not found or lease is not renewable") | ||||
| 	// If there is no entry, cannot review to renew | ||||
| 	case le == nil: | ||||
| 		return false, fmt.Errorf("lease not found") | ||||
|  | ||||
| 	case le.ExpireTime.IsZero(): | ||||
| 		return false, fmt.Errorf("lease is not renewable") | ||||
|  | ||||
| 	case le.ClientTokenType == logical.TokenTypeBatch: | ||||
| 		return false, nil | ||||
|  | ||||
| 	// Determine if the lease is expired | ||||
| 	case le.ExpireTime.Before(time.Now()): | ||||
| 		err = fmt.Errorf("lease expired") | ||||
| 		return false, fmt.Errorf("lease expired") | ||||
|  | ||||
| 	// Determine if the lease is renewable | ||||
| 	case le.Secret != nil && !le.Secret.Renewable: | ||||
| 		err = fmt.Errorf("lease is not renewable") | ||||
| 		return false, fmt.Errorf("lease is not renewable") | ||||
|  | ||||
| 	case le.Auth != nil && !le.Auth.Renewable: | ||||
| 		err = fmt.Errorf("lease is not renewable") | ||||
| 		return false, fmt.Errorf("lease is not renewable") | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return true, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -180,6 +180,7 @@ func TestExpiration_Tidy(t *testing.T) { | ||||
| 			Path:        "invalid/lease/" + fmt.Sprintf("%d", i+1), | ||||
| 			ClientToken: "invalidtoken", | ||||
| 		} | ||||
| 		req.SetTokenEntry(&logical.TokenEntry{ID: "invalidtoken", NamespaceID: "root"}) | ||||
| 		resp := &logical.Response{ | ||||
| 			Secret: &logical.Secret{ | ||||
| 				LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -396,6 +397,7 @@ func TestExpiration_Restore(t *testing.T) { | ||||
| 			Path:        path, | ||||
| 			ClientToken: "foobar", | ||||
| 		} | ||||
| 		req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 		resp := &logical.Response{ | ||||
| 			Secret: &logical.Secret{ | ||||
| 				LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -452,6 +454,7 @@ func TestExpiration_Register(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -539,7 +542,7 @@ func TestExpiration_RegisterAuth_NoLease(t *testing.T) { | ||||
| 		NamespaceID: namespace.RootNamespaceID, | ||||
| 	} | ||||
| 	resp, err := exp.RenewToken(namespace.TestContext(), &logical.Request{}, te, 0) | ||||
| 	if err != nil && (err != logical.ErrInvalidRequest || (resp != nil && resp.IsError() && resp.Error().Error() != "lease not found or lease is not renewable")) { | ||||
| 	if err != nil && (err != logical.ErrInvalidRequest || (resp != nil && resp.IsError() && resp.Error().Error() != "lease is not renewable")) { | ||||
| 		t.Fatalf("bad: err:%v resp:%#v", err, resp) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| @@ -578,6 +581,7 @@ func TestExpiration_Revoke(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -624,6 +628,7 @@ func TestExpiration_RevokeOnExpire(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -687,6 +692,7 @@ func TestExpiration_RevokePrefix(t *testing.T) { | ||||
| 			Path:        path, | ||||
| 			ClientToken: "foobar", | ||||
| 		} | ||||
| 		req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 		resp := &logical.Response{ | ||||
| 			Secret: &logical.Secret{ | ||||
| 				LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -755,6 +761,7 @@ func TestExpiration_RevokeByToken(t *testing.T) { | ||||
| 			Path:        path, | ||||
| 			ClientToken: "foobarbaz", | ||||
| 		} | ||||
| 		req.SetTokenEntry(&logical.TokenEntry{ID: "foobarbaz", NamespaceID: "root"}) | ||||
| 		resp := &logical.Response{ | ||||
| 			Secret: &logical.Secret{ | ||||
| 				LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -843,6 +850,7 @@ func TestExpiration_RevokeByToken_Blocking(t *testing.T) { | ||||
| 			Path:        path, | ||||
| 			ClientToken: "foobarbaz", | ||||
| 		} | ||||
| 		req.SetTokenEntry(&logical.TokenEntry{ID: "foobarbaz", NamespaceID: "root"}) | ||||
| 		resp := &logical.Response{ | ||||
| 			Secret: &logical.Secret{ | ||||
| 				LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -1145,6 +1153,7 @@ func TestExpiration_Renew(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -1215,6 +1224,7 @@ func TestExpiration_Renew_NotRenewable(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -1265,6 +1275,7 @@ func TestExpiration_Renew_RevokeOnExpire(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: "foobar", | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: "foobar", NamespaceID: "root"}) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -1407,7 +1418,7 @@ func TestExpiration_revokeEntry_token(t *testing.T) { | ||||
| 	if err := exp.persistEntry(namespace.TestContext(), le); err != nil { | ||||
| 		t.Fatalf("error persisting entry: %v", err) | ||||
| 	} | ||||
| 	if err := exp.createIndexByToken(namespace.TestContext(), le); err != nil { | ||||
| 	if err := exp.createIndexByToken(namespace.TestContext(), le, le.ClientToken); err != nil { | ||||
| 		t.Fatalf("error creating secondary index: %v", err) | ||||
| 	} | ||||
| 	exp.updatePending(le, le.Secret.LeaseTotal()) | ||||
| @@ -1802,6 +1813,7 @@ func TestExpiration_RevokeForce(t *testing.T) { | ||||
| 		Path:        "badrenew/creds", | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
|  | ||||
| 	resp, err := core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| @@ -1850,6 +1862,7 @@ func TestExpiration_RevokeForceSingle(t *testing.T) { | ||||
| 		Path:        "badrenew/creds", | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
|  | ||||
| 	resp, err := core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
|   | ||||
							
								
								
									
										420
									
								
								vault/external_tests/token/batch_token_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								vault/external_tests/token/batch_token_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,420 @@ | ||||
| package token | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/api" | ||||
| 	"github.com/hashicorp/vault/builtin/credential/approle" | ||||
| 	vaulthttp "github.com/hashicorp/vault/http" | ||||
| 	"github.com/hashicorp/vault/logical" | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| ) | ||||
|  | ||||
| func TestBatchTokens(t *testing.T) { | ||||
| 	coreConfig := &vault.CoreConfig{ | ||||
| 		LogicalBackends: map[string]logical.Factory{ | ||||
| 			"kv": vault.LeasedPassthroughBackendFactory, | ||||
| 		}, | ||||
| 		CredentialBackends: map[string]logical.Factory{ | ||||
| 			"approle": approle.Factory, | ||||
| 		}, | ||||
| 	} | ||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ | ||||
| 		HandlerFunc: vaulthttp.Handler, | ||||
| 	}) | ||||
| 	cluster.Start() | ||||
| 	defer cluster.Cleanup() | ||||
|  | ||||
| 	core := cluster.Cores[0].Core | ||||
| 	vault.TestWaitActive(t, core) | ||||
| 	client := cluster.Cores[0].Client | ||||
| 	rootToken := client.Token() | ||||
| 	var err error | ||||
|  | ||||
| 	// Set up a KV path | ||||
| 	err = client.Sys().Mount("kv", &api.MountInput{ | ||||
| 		Type: "kv", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = client.Logical().Write("kv/foo", map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 		"ttl": "5m", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Write the test policy | ||||
| 	err = client.Sys().PutPolicy("test", ` | ||||
| path "kv/*" { | ||||
| 	capabilities = ["read"] | ||||
| }`) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Mount the auth backend | ||||
| 	err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{ | ||||
| 		Type: "approle", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Tune the mount | ||||
| 	if err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{ | ||||
| 		DefaultLeaseTTL: "5s", | ||||
| 		MaxLeaseTTL:     "5s", | ||||
| 	}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create role | ||||
| 	resp, err := client.Logical().Write("auth/approle/role/test", map[string]interface{}{ | ||||
| 		"policies": "test", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Get role_id | ||||
| 	resp, err = client.Logical().Read("auth/approle/role/test/role-id") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("expected a response for fetching the role-id") | ||||
| 	} | ||||
| 	roleID := resp.Data["role_id"] | ||||
|  | ||||
| 	// Get secret_id | ||||
| 	resp, err = client.Logical().Write("auth/approle/role/test/secret-id", map[string]interface{}{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("expected a response for fetching the secret-id") | ||||
| 	} | ||||
| 	secretID := resp.Data["secret_id"] | ||||
|  | ||||
| 	// Login | ||||
| 	testLogin := func(mountTuneType, roleType string, batch bool) string { | ||||
| 		t.Helper() | ||||
| 		if err = client.Sys().TuneMount("auth/approle", api.MountConfigInput{ | ||||
| 			TokenType: mountTuneType, | ||||
| 		}); err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		_, err = client.Logical().Write("auth/approle/role/test", map[string]interface{}{ | ||||
| 			"token_type": roleType, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		resp, err = client.Logical().Write("auth/approle/login", map[string]interface{}{ | ||||
| 			"role_id":   roleID, | ||||
| 			"secret_id": secretID, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		if resp == nil { | ||||
| 			t.Fatal("expected a response for login") | ||||
| 		} | ||||
| 		if resp.Auth == nil { | ||||
| 			t.Fatal("expected auth object from response") | ||||
| 		} | ||||
| 		if resp.Auth.ClientToken == "" { | ||||
| 			t.Fatal("expected a client token") | ||||
| 		} | ||||
| 		if batch && !strings.HasPrefix(resp.Auth.ClientToken, "b.") { | ||||
| 			t.Fatal("expected a batch token") | ||||
| 		} | ||||
| 		if !batch && strings.HasPrefix(resp.Auth.ClientToken, "b.") { | ||||
| 			t.Fatal("expected a non-batch token") | ||||
| 		} | ||||
| 		return resp.Auth.ClientToken | ||||
| 	} | ||||
| 	testLogin("service", "default", false) | ||||
| 	testLogin("service", "batch", false) | ||||
| 	testLogin("service", "service", false) | ||||
| 	testLogin("batch", "default", true) | ||||
| 	testLogin("batch", "batch", true) | ||||
| 	testLogin("batch", "service", true) | ||||
| 	testLogin("default-service", "default", false) | ||||
| 	testLogin("default-service", "batch", true) | ||||
| 	testLogin("default-service", "service", false) | ||||
| 	testLogin("default-batch", "default", true) | ||||
| 	testLogin("default-batch", "batch", true) | ||||
| 	testLogin("default-batch", "service", false) | ||||
|  | ||||
| 	finalToken := testLogin("batch", "batch", true) | ||||
|  | ||||
| 	client.SetToken(finalToken) | ||||
| 	resp, err = client.Logical().Read("kv/foo") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp.Data["foo"].(string) != "bar" { | ||||
| 		t.Fatal("bad") | ||||
| 	} | ||||
| 	if resp.LeaseID == "" { | ||||
| 		t.Fatal("expected lease") | ||||
| 	} | ||||
| 	if !resp.Renewable { | ||||
| 		t.Fatal("expected renewable") | ||||
| 	} | ||||
| 	if resp.LeaseDuration > 5 { | ||||
| 		t.Fatalf("lease duration too big: %d", resp.LeaseDuration) | ||||
| 	} | ||||
| 	leaseID := resp.LeaseID | ||||
|  | ||||
| 	lastDuration := resp.LeaseDuration | ||||
| 	for i := 0; i < 3; i++ { | ||||
| 		time.Sleep(time.Second) | ||||
| 		resp, err = client.Sys().Renew(leaseID, 0) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		if resp.LeaseDuration >= lastDuration { | ||||
| 			t.Fatal("expected duration to go down") | ||||
| 		} | ||||
| 		lastDuration = resp.LeaseDuration | ||||
| 	} | ||||
|  | ||||
| 	client.SetToken(rootToken) | ||||
| 	time.Sleep(2 * time.Second) | ||||
| 	resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{ | ||||
| 		"lease_id": leaseID, | ||||
| 	}) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestBatchToken_ParentLeaseRevoke(t *testing.T) { | ||||
| 	coreConfig := &vault.CoreConfig{ | ||||
| 		LogicalBackends: map[string]logical.Factory{ | ||||
| 			"kv": vault.LeasedPassthroughBackendFactory, | ||||
| 		}, | ||||
| 		CredentialBackends: map[string]logical.Factory{ | ||||
| 			"approle": approle.Factory, | ||||
| 		}, | ||||
| 	} | ||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ | ||||
| 		HandlerFunc: vaulthttp.Handler, | ||||
| 	}) | ||||
| 	cluster.Start() | ||||
| 	defer cluster.Cleanup() | ||||
|  | ||||
| 	core := cluster.Cores[0].Core | ||||
| 	vault.TestWaitActive(t, core) | ||||
| 	client := cluster.Cores[0].Client | ||||
| 	rootToken := client.Token() | ||||
| 	var err error | ||||
|  | ||||
| 	// Set up a KV path | ||||
| 	err = client.Sys().Mount("kv", &api.MountInput{ | ||||
| 		Type: "kv", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = client.Logical().Write("kv/foo", map[string]interface{}{ | ||||
| 		"foo": "bar", | ||||
| 		"ttl": "5m", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Write the test policy | ||||
| 	err = client.Sys().PutPolicy("test", ` | ||||
| path "kv/*" { | ||||
| 	capabilities = ["read"] | ||||
| }`) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Create a second root token | ||||
| 	secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ | ||||
| 		Policies: []string{"root"}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	rootToken2 := secret.Auth.ClientToken | ||||
|  | ||||
| 	// Use this new token to create a batch token | ||||
| 	client.SetToken(rootToken2) | ||||
| 	secret, err = client.Auth().Token().Create(&api.TokenCreateRequest{ | ||||
| 		Policies: []string{"test"}, | ||||
| 		Type:     "batch", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	batchToken := secret.Auth.ClientToken | ||||
| 	client.SetToken(batchToken) | ||||
| 	_, err = client.Auth().Token().LookupSelf() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if secret.Auth.ClientToken[0:2] != "b." { | ||||
| 		t.Fatal(secret.Auth.ClientToken) | ||||
| 	} | ||||
|  | ||||
| 	// Get a lease with the batch token | ||||
| 	resp, err := client.Logical().Read("kv/foo") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp.Data["foo"].(string) != "bar" { | ||||
| 		t.Fatal("bad") | ||||
| 	} | ||||
| 	if resp.LeaseID == "" { | ||||
| 		t.Fatal("expected lease") | ||||
| 	} | ||||
| 	leaseID := resp.LeaseID | ||||
|  | ||||
| 	// Check the lease | ||||
| 	resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{ | ||||
| 		"lease_id": leaseID, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Revoke the parent | ||||
| 	client.SetToken(rootToken2) | ||||
| 	err = client.Auth().Token().RevokeSelf("") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Verify the batch token is not usable anymore | ||||
| 	client.SetToken(rootToken) | ||||
| 	_, err = client.Auth().Token().Lookup(batchToken) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
|  | ||||
| 	// Verify the lease has been revoked | ||||
| 	resp, err = client.Logical().Write("sys/leases/lookup", map[string]interface{}{ | ||||
| 		"lease_id": leaseID, | ||||
| 	}) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_Roles_Batch(t *testing.T) { | ||||
| 	cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ | ||||
| 		HandlerFunc: vaulthttp.Handler, | ||||
| 	}) | ||||
| 	cluster.Start() | ||||
| 	defer cluster.Cleanup() | ||||
|  | ||||
| 	core := cluster.Cores[0].Core | ||||
| 	vault.TestWaitActive(t, core) | ||||
| 	client := cluster.Cores[0].Client | ||||
| 	rootToken := client.Token() | ||||
|  | ||||
| 	var err error | ||||
| 	var secret *api.Secret | ||||
|  | ||||
| 	_, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{ | ||||
| 		"bound_cidrs": []string{}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{ | ||||
| 		Policies: []string{"default"}, | ||||
| 		Type:     "batch", | ||||
| 	}, "testrole") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	client.SetToken(secret.Auth.ClientToken) | ||||
| 	_, err = client.Auth().Token().LookupSelf() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if secret.Auth.ClientToken[0:2] == "b." { | ||||
| 		t.Fatal(secret.Auth.ClientToken) | ||||
| 	} | ||||
|  | ||||
| 	// Test batch | ||||
| 	client.SetToken(rootToken) | ||||
| 	_, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{ | ||||
| 		"token_type": "batch", | ||||
| 	}) | ||||
| 	// Orphan not set so we should error | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| 	_, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{ | ||||
| 		"token_type": "batch", | ||||
| 		"orphan":     true, | ||||
| 	}) | ||||
| 	// Renewable set so we should error | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| 	_, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{ | ||||
| 		"token_type": "batch", | ||||
| 		"orphan":     true, | ||||
| 		"renewable":  false, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{ | ||||
| 		Policies: []string{"default"}, | ||||
| 		Type:     "service", | ||||
| 	}, "testrole") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	client.SetToken(secret.Auth.ClientToken) | ||||
| 	_, err = client.Auth().Token().LookupSelf() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if secret.Auth.ClientToken[0:2] != "b." { | ||||
| 		t.Fatal(secret.Auth.ClientToken) | ||||
| 	} | ||||
|  | ||||
| 	// Back to normal | ||||
| 	client.SetToken(rootToken) | ||||
| 	_, err = client.Logical().Write("auth/token/roles/testrole", map[string]interface{}{ | ||||
| 		"token_type": "service", | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	secret, err = client.Auth().Token().CreateWithRole(&api.TokenCreateRequest{ | ||||
| 		Policies: []string{"default"}, | ||||
| 		Type:     "batch", | ||||
| 	}, "testrole") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	client.SetToken(secret.Auth.ClientToken) | ||||
| 	_, err = client.Auth().Token().LookupSelf() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if secret.Auth.ClientToken[0:2] == "b." { | ||||
| 		t.Fatal(secret.Auth.ClientToken) | ||||
| 	} | ||||
| } | ||||
| @@ -223,6 +223,7 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 	// Audit-log the request before going any further | ||||
| 	auth := &logical.Auth{ | ||||
| 		ClientToken: req.ClientToken, | ||||
| 		Accessor:    req.ClientTokenAccessor, | ||||
| 	} | ||||
| 	if te != nil { | ||||
| 		auth.IdentityPolicies = identityPolicies[te.NamespaceID] | ||||
| @@ -233,6 +234,7 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 		auth.Metadata = te.Meta | ||||
| 		auth.DisplayName = te.DisplayName | ||||
| 		auth.EntityID = te.EntityID | ||||
| 		auth.TokenType = te.Type | ||||
| 	} | ||||
|  | ||||
| 	logInput := &audit.LogInput{ | ||||
|   | ||||
| @@ -601,6 +601,9 @@ func mountInfo(entry *MountEntry) map[string]interface{} { | ||||
| 	if rawVal, ok := entry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 		entryConfig["passthrough_request_headers"] = rawVal.([]string) | ||||
| 	} | ||||
| 	if entry.Table == credentialTableType { | ||||
| 		entryConfig["token_type"] = entry.Config.TokenType.String() | ||||
| 	} | ||||
|  | ||||
| 	info["config"] = entryConfig | ||||
|  | ||||
| @@ -951,6 +954,10 @@ func (b *SystemBackend) handleTuneReadCommon(ctx context.Context, path string) ( | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	if mountEntry.Table == credentialTableType { | ||||
| 		resp.Data["token_type"] = mountEntry.Config.TokenType.String() | ||||
| 	} | ||||
|  | ||||
| 	if rawVal, ok := mountEntry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok { | ||||
| 		resp.Data["audit_non_hmac_request_keys"] = rawVal.([]string) | ||||
| 	} | ||||
| @@ -1192,6 +1199,44 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if rawVal, ok := data.GetOk("token_type"); ok { | ||||
| 		if !strings.HasPrefix(path, "auth/") { | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("'token_type' can only be modified on auth mounts")), logical.ErrInvalidRequest | ||||
| 		} | ||||
| 		if mountEntry.Type == "token" || mountEntry.Type == "ns_token" { | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("'token_type' cannot be set for 'token' or 'ns_token' auth mounts")), logical.ErrInvalidRequest | ||||
| 		} | ||||
|  | ||||
| 		tokenType := logical.TokenTypeDefaultService | ||||
| 		ttString := rawVal.(string) | ||||
|  | ||||
| 		switch ttString { | ||||
| 		case "", "default-service": | ||||
| 		case "default-batch": | ||||
| 			tokenType = logical.TokenTypeDefaultBatch | ||||
| 		case "service": | ||||
| 			tokenType = logical.TokenTypeService | ||||
| 		case "batch": | ||||
| 			tokenType = logical.TokenTypeBatch | ||||
| 		default: | ||||
| 			return logical.ErrorResponse(fmt.Sprintf( | ||||
| 				"invalid value for 'token_type'")), logical.ErrInvalidRequest | ||||
| 		} | ||||
|  | ||||
| 		oldVal := mountEntry.Config.TokenType | ||||
| 		mountEntry.Config.TokenType = tokenType | ||||
|  | ||||
| 		// Update the mount table | ||||
| 		if err := b.Core.persistAuth(ctx, b.Core.auth, &mountEntry.Local); err != nil { | ||||
| 			mountEntry.Config.TokenType = oldVal | ||||
| 			return handleError(err) | ||||
| 		} | ||||
|  | ||||
| 		if b.Core.logger.IsInfo() { | ||||
| 			b.Core.logger.Info("mount tuning of token_type successful", "path", path, "token_type", ttString) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if rawVal, ok := data.GetOk("passthrough_request_headers"); ok { | ||||
| 		headers := rawVal.([]string) | ||||
|  | ||||
| @@ -1467,37 +1512,10 @@ func (b *SystemBackend) handleAuthTable(ctx context.Context, req *logical.Reques | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		info := map[string]interface{}{ | ||||
| 			"type":        entry.Type, | ||||
| 			"description": entry.Description, | ||||
| 			"accessor":    entry.Accessor, | ||||
| 			"local":       entry.Local, | ||||
| 			"seal_wrap":   entry.SealWrap, | ||||
| 			"options":     entry.Options, | ||||
| 		} | ||||
| 		entryConfig := map[string]interface{}{ | ||||
| 			"default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()), | ||||
| 			"max_lease_ttl":     int64(entry.Config.MaxLeaseTTL.Seconds()), | ||||
| 			"plugin_name":       entry.Config.PluginName, | ||||
| 		} | ||||
| 		if rawVal, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok { | ||||
| 			entryConfig["audit_non_hmac_request_keys"] = rawVal.([]string) | ||||
| 		} | ||||
| 		if rawVal, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok { | ||||
| 			entryConfig["audit_non_hmac_response_keys"] = rawVal.([]string) | ||||
| 		} | ||||
| 		// Even though empty value is valid for ListingVisibility, we can ignore | ||||
| 		// this case during mount since there's nothing to unset/hide. | ||||
| 		if len(entry.Config.ListingVisibility) > 0 { | ||||
| 			entryConfig["listing_visibility"] = entry.Config.ListingVisibility | ||||
| 		} | ||||
| 		if rawVal, ok := entry.synthesizedConfigCache.Load("passthrough_request_headers"); ok { | ||||
| 			entryConfig["passthrough_request_headers"] = rawVal.([]string) | ||||
| 		} | ||||
|  | ||||
| 		info["config"] = entryConfig | ||||
| 		info := mountInfo(entry) | ||||
| 		resp.Data[entry.Path] = info | ||||
| 	} | ||||
|  | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| @@ -1569,6 +1587,20 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque | ||||
| 			logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	switch apiConfig.TokenType { | ||||
| 	case "", "default-service": | ||||
| 		config.TokenType = logical.TokenTypeDefaultService | ||||
| 	case "default-batch": | ||||
| 		config.TokenType = logical.TokenTypeDefaultBatch | ||||
| 	case "service": | ||||
| 		config.TokenType = logical.TokenTypeService | ||||
| 	case "batch": | ||||
| 		config.TokenType = logical.TokenTypeBatch | ||||
| 	default: | ||||
| 		return logical.ErrorResponse(fmt.Sprintf( | ||||
| 			"invalid value for 'token_type'")), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	switch logicalType { | ||||
| 	case "": | ||||
| 		return logical.ErrorResponse( | ||||
| @@ -3581,6 +3613,10 @@ This path responds to the following HTTP methods. | ||||
| 		"A list of headers to whitelist and pass from the request to the backend.", | ||||
| 		"", | ||||
| 	}, | ||||
| 	"token_type": { | ||||
| 		"The type of token to issue (service or batch).", | ||||
| 		"", | ||||
| 	}, | ||||
| 	"raw": { | ||||
| 		"Write, Read, and Delete data directly in the Storage backend.", | ||||
| 		"", | ||||
|   | ||||
| @@ -1010,6 +1010,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path { | ||||
| 					Type:        framework.TypeCommaStringSlice, | ||||
| 					Description: strings.TrimSpace(sysHelp["passthrough_request_headers"][0]), | ||||
| 				}, | ||||
| 				"token_type": &framework.FieldSchema{ | ||||
| 					Type:        framework.TypeString, | ||||
| 					Description: strings.TrimSpace(sysHelp["token_type"][0]), | ||||
| 				}, | ||||
| 			}, | ||||
|  | ||||
| 			Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatih/structs" | ||||
| 	"github.com/go-test/deep" | ||||
| 	hclog "github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/vault/audit" | ||||
| 	"github.com/hashicorp/vault/helper/builtinplugins" | ||||
| @@ -447,7 +448,7 @@ func TestSystemBackend_PathCapabilities(t *testing.T) { | ||||
| 	rootCheckFunc(t, resp) | ||||
|  | ||||
| 	// Create a non-root token | ||||
| 	testMakeTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
|  | ||||
| 	nonRootCheckFunc := func(t *testing.T, resp *logical.Response) { | ||||
| 		expected1 := []string{"create", "sudo", "update"} | ||||
| @@ -549,7 +550,7 @@ func testCapabilities(t *testing.T, endpoint string) { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, endpoint) | ||||
| 	if endpoint == "capabilities-self" { | ||||
| 		req.ClientToken = "tokenid" | ||||
| @@ -605,7 +606,7 @@ func TestSystemBackend_CapabilitiesAccessor_BC(t *testing.T) { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
| 	testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"}) | ||||
|  | ||||
| 	te, err = core.tokenStore.Lookup(namespace.TestContext(), "tokenid") | ||||
| 	if err != nil { | ||||
| @@ -696,6 +697,7 @@ func TestSystemBackend_leases(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -742,6 +744,7 @@ func TestSystemBackend_leases_list(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -790,6 +793,7 @@ func TestSystemBackend_leases_list(t *testing.T) { | ||||
| 	// Generate multiple leases | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -800,6 +804,7 @@ func TestSystemBackend_leases_list(t *testing.T) { | ||||
|  | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -839,6 +844,7 @@ func TestSystemBackend_leases_list(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/bar") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -886,6 +892,7 @@ func TestSystemBackend_renew(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -922,6 +929,7 @@ func TestSystemBackend_renew(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -976,7 +984,7 @@ func TestSystemBackend_renew(t *testing.T) { | ||||
| 	if resp2.Data == nil { | ||||
| 		t.Fatal("nil data") | ||||
| 	} | ||||
| 	if resp.Secret.TTL != 180*time.Second { | ||||
| 	if resp.Secret.TTL != time.Second*180 { | ||||
| 		t.Fatalf("bad lease duration: %v", resp.Secret.TTL) | ||||
| 	} | ||||
| } | ||||
| @@ -990,7 +998,7 @@ func TestSystemBackend_renew_invalidID(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 	if resp.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	} | ||||
|  | ||||
| @@ -1001,7 +1009,7 @@ func TestSystemBackend_renew_invalidID(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 	if resp.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	} | ||||
| } | ||||
| @@ -1015,7 +1023,7 @@ func TestSystemBackend_renew_invalidID_origUrl(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 	if resp.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	} | ||||
|  | ||||
| @@ -1026,7 +1034,7 @@ func TestSystemBackend_renew_invalidID_origUrl(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 	if resp.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	} | ||||
| } | ||||
| @@ -1050,6 +1058,7 @@ func TestSystemBackend_revoke(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1074,13 +1083,14 @@ func TestSystemBackend_revoke(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp3.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	if resp3.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", *resp3) | ||||
| 	} | ||||
|  | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1103,6 +1113,7 @@ func TestSystemBackend_revoke(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1192,6 +1203,7 @@ func TestSystemBackend_revokePrefix(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1216,8 +1228,8 @@ func TestSystemBackend_revokePrefix(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp3.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	if resp3.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %v", *resp3) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1240,6 +1252,7 @@ func TestSystemBackend_revokePrefix_origUrl(t *testing.T) { | ||||
| 	// Read a key with a LeaseID | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "secret/foo") | ||||
| 	req.ClientToken = root | ||||
| 	req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -1264,8 +1277,8 @@ func TestSystemBackend_revokePrefix_origUrl(t *testing.T) { | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if resp3.Data["error"] != "lease not found or lease is not renewable" { | ||||
| 		t.Fatalf("bad: %v", resp) | ||||
| 	if resp3.Data["error"] != "lease not found" { | ||||
| 		t.Fatalf("bad: %#v", *resp3) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1415,14 +1428,16 @@ func TestSystemBackend_authTable(t *testing.T) { | ||||
| 				"default_lease_ttl": int64(0), | ||||
| 				"max_lease_ttl":     int64(0), | ||||
| 				"plugin_name":       "", | ||||
| 				"force_no_cache":    false, | ||||
| 				"token_type":        "default-service", | ||||
| 			}, | ||||
| 			"local":     false, | ||||
| 			"seal_wrap": false, | ||||
| 			"options":   map[string]string(nil), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(resp.Data, exp) { | ||||
| 		t.Fatalf("got: %#v expect: %#v", resp.Data, exp) | ||||
| 	if diff := deep.Equal(resp.Data, exp); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1466,7 +1481,9 @@ func TestSystemBackend_enableAuth(t *testing.T) { | ||||
| 			"config": map[string]interface{}{ | ||||
| 				"default_lease_ttl": int64(2100), | ||||
| 				"max_lease_ttl":     int64(2700), | ||||
| 				"force_no_cache":    false, | ||||
| 				"plugin_name":       "", | ||||
| 				"token_type":        "default-service", | ||||
| 			}, | ||||
| 			"local":     true, | ||||
| 			"seal_wrap": true, | ||||
| @@ -1480,14 +1497,16 @@ func TestSystemBackend_enableAuth(t *testing.T) { | ||||
| 				"default_lease_ttl": int64(0), | ||||
| 				"max_lease_ttl":     int64(0), | ||||
| 				"plugin_name":       "", | ||||
| 				"force_no_cache":    false, | ||||
| 				"token_type":        "default-service", | ||||
| 			}, | ||||
| 			"local":     false, | ||||
| 			"seal_wrap": false, | ||||
| 			"options":   map[string]string(nil), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(resp.Data, exp) { | ||||
| 		t.Fatalf("got: %#v expect: %#v", resp.Data, exp) | ||||
| 	if diff := deep.Equal(resp.Data, exp); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -2300,6 +2319,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { | ||||
| 					"max_lease_ttl":     int64(0), | ||||
| 					"force_no_cache":    false, | ||||
| 					"plugin_name":       "", | ||||
| 					"token_type":        "default-service", | ||||
| 				}, | ||||
| 				"type":        "token", | ||||
| 				"description": "token based credentials", | ||||
| @@ -2309,8 +2329,8 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) { | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(resp.Data, exp) { | ||||
| 		t.Fatalf("got: %#v \n\n expect: %#v", resp.Data, exp) | ||||
| 	if diff := deep.Equal(resp.Data, exp); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
|  | ||||
| 	// Mount-tune an auth mount | ||||
| @@ -2391,7 +2411,7 @@ func TestSystemBackend_InternalUIMount(t *testing.T) { | ||||
| 		t.Fatalf("Bad Response: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"}) | ||||
| 	testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"}) | ||||
|  | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv") | ||||
| 	req.ClientToken = "tokenid" | ||||
|   | ||||
| @@ -226,6 +226,7 @@ type MountConfig struct { | ||||
| 	AuditNonHMACResponseKeys  []string              `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         ListingVisibilityType `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string              `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| 	TokenType                 logical.TokenType     `json:"token_type" structs:"token_type" mapstructure:"token_type"` | ||||
| } | ||||
|  | ||||
| // APIMountConfig is an embedded struct of api.MountConfigInput | ||||
| @@ -238,6 +239,7 @@ type APIMountConfig struct { | ||||
| 	AuditNonHMACResponseKeys  []string              `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         ListingVisibilityType `json:"listing_visibility,omitempty" structs:"listing_visibility" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string              `json:"passthrough_request_headers,omitempty" structs:"passthrough_request_headers" mapstructure:"passthrough_request_headers"` | ||||
| 	TokenType                 string                `json:"token_type" structs:"token_type" mapstructure:"token_type"` | ||||
| } | ||||
|  | ||||
| // Clone returns a deep copy of the mount entry | ||||
|   | ||||
| @@ -295,6 +295,7 @@ func TestCore_Unmount_Cleanup(t *testing.T) { | ||||
| 		Path:        "test/foo", | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	r.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err := c.HandleRequest(namespace.TestContext(), r) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| @@ -415,6 +416,7 @@ func TestCore_Remount_Cleanup(t *testing.T) { | ||||
| 		Path:        "test/foo", | ||||
| 		ClientToken: root, | ||||
| 	} | ||||
| 	r.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}}) | ||||
| 	resp, err := c.HandleRequest(namespace.TestContext(), r) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
|   | ||||
| @@ -269,19 +269,22 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool | ||||
| 	// whether a particular resource exists. Then we can mark it as an update | ||||
| 	// or creation as appropriate. | ||||
| 	if req.Operation == logical.CreateOperation || req.Operation == logical.UpdateOperation { | ||||
| 		checkExists, resourceExists, err := c.router.RouteExistenceCheck(ctx, req) | ||||
| 		existsResp, checkExists, resourceExists, err := c.router.RouteExistenceCheck(ctx, req) | ||||
| 		switch err { | ||||
| 		case logical.ErrUnsupportedPath: | ||||
| 			// fail later via bad path to avoid confusing items in the log | ||||
| 			checkExists = false | ||||
| 		case nil: | ||||
| 			// Continue on | ||||
| 			if existsResp != nil && existsResp.IsError() { | ||||
| 				return nil, te, existsResp.Error() | ||||
| 			} | ||||
| 			// Otherwise, continue on | ||||
| 		default: | ||||
| 			c.logger.Error("failed to run existence check", "error", err) | ||||
| 			if _, ok := err.(errutil.UserError); ok { | ||||
| 				return nil, nil, err | ||||
| 				return nil, te, err | ||||
| 			} else { | ||||
| 				return nil, nil, ErrInternalError | ||||
| 				return nil, te, ErrInternalError | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -316,6 +319,7 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool | ||||
| 		auth.ExternalNamespacePolicies = identityPolicies | ||||
| 		// Store the entity ID in the request object | ||||
| 		req.EntityID = te.EntityID | ||||
| 		auth.TokenType = te.Type | ||||
| 	} | ||||
|  | ||||
| 	// Check the standard non-root ACLs. Return the token entry if it's not | ||||
| @@ -744,6 +748,19 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp | ||||
| 				return nil, auth, retErr | ||||
| 			} | ||||
| 			resp.Secret.LeaseID = leaseID | ||||
|  | ||||
| 			// Get the actual time of the lease | ||||
| 			le, err := c.expiration.FetchLeaseTimes(ctx, leaseID) | ||||
| 			if err != nil { | ||||
| 				c.logger.Error("failed to fetch updated lease time", "request_path", req.Path, "error", err) | ||||
| 				retErr = multierror.Append(retErr, ErrInternalError) | ||||
| 				return nil, auth, retErr | ||||
| 			} | ||||
| 			// We round here because the clock will have already started | ||||
| 			// ticking, so we'll end up always returning 299 instead of 300 or | ||||
| 			// 26399 instead of 26400, say, even if it's just a few | ||||
| 			// microseconds. This provides a nicer UX. | ||||
| 			resp.Secret.TTL = le.ExpireTime.Sub(time.Now()).Round(time.Second) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -777,6 +794,9 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp | ||||
| 		} | ||||
|  | ||||
| 		resp.Auth.TokenPolicies = policyutil.SanitizePolicies(resp.Auth.Policies, policyutil.DoNotAddDefaultPolicy) | ||||
| 		switch resp.Auth.TokenType { | ||||
| 		case logical.TokenTypeBatch: | ||||
| 		case logical.TokenTypeService: | ||||
| 			if err := c.expiration.RegisterAuth(ctx, &logical.TokenEntry{ | ||||
| 				Path:        resp.Auth.CreationPath, | ||||
| 				NamespaceID: ns.ID, | ||||
| @@ -786,6 +806,7 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp | ||||
| 				retErr = multierror.Append(retErr, ErrInternalError) | ||||
| 				return nil, auth, retErr | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// We do these later since it's not meaningful for backends/expmgr to | ||||
| 		// have what is purely a snapshot of current identity policies, and | ||||
| @@ -1031,7 +1052,14 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		registerFunc, funcGetErr := getAuthRegisterFunc(c) | ||||
| 		var registerFunc RegisterAuthFunc | ||||
| 		var funcGetErr error | ||||
| 		// Batch tokens should not be forwarded to perf standby | ||||
| 		if auth.TokenType == logical.TokenTypeBatch { | ||||
| 			registerFunc = c.RegisterAuth | ||||
| 		} else { | ||||
| 			registerFunc, funcGetErr = getAuthRegisterFunc(c) | ||||
| 		} | ||||
| 		if funcGetErr != nil { | ||||
| 			retErr = multierror.Append(retErr, funcGetErr) | ||||
| 			return nil, auth, retErr | ||||
| @@ -1083,6 +1111,7 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st | ||||
| 		Policies:       auth.TokenPolicies, | ||||
| 		NamespaceID:    ns.ID, | ||||
| 		ExplicitMaxTTL: auth.ExplicitMaxTTL, | ||||
| 		Type:           auth.TokenType, | ||||
| 	} | ||||
|  | ||||
| 	if err := c.tokenStore.create(ctx, &te); err != nil { | ||||
| @@ -1095,12 +1124,18 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st | ||||
| 	auth.Accessor = te.Accessor | ||||
| 	auth.TTL = te.TTL | ||||
|  | ||||
| 	switch auth.TokenType { | ||||
| 	case logical.TokenTypeBatch: | ||||
| 		// Ensure it's not marked renewable since it isn't | ||||
| 		auth.Renewable = false | ||||
| 	case logical.TokenTypeService: | ||||
| 		// Register with the expiration manager | ||||
| 		if err := c.expiration.RegisterAuth(ctx, &te, auth); err != nil { | ||||
| 			c.tokenStore.revokeOrphan(ctx, te.ID) | ||||
| 			c.logger.Error("failed to register token lease", "request_path", path, "error", err) | ||||
| 			return ErrInternalError | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -468,9 +468,9 @@ func (r *Router) Route(ctx context.Context, req *logical.Request) (*logical.Resp | ||||
| } | ||||
|  | ||||
| // RouteExistenceCheck is used to route a given existence check request | ||||
| func (r *Router) RouteExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) { | ||||
| 	_, ok, exists, err := r.routeCommon(ctx, req, true) | ||||
| 	return ok, exists, err | ||||
| func (r *Router) RouteExistenceCheck(ctx context.Context, req *logical.Request) (*logical.Response, bool, bool, error) { | ||||
| 	resp, ok, exists, err := r.routeCommon(ctx, req, true) | ||||
| 	return resp, ok, exists, err | ||||
| } | ||||
|  | ||||
| func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenceCheck bool) (*logical.Response, bool, bool, error) { | ||||
| @@ -547,11 +547,17 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | ||||
| 			return nil, false, false, nil | ||||
| 		} | ||||
|  | ||||
| 		if req.TokenEntry() == nil { | ||||
| 		te := req.TokenEntry() | ||||
|  | ||||
| 		if te == nil { | ||||
| 			return nil, false, false, fmt.Errorf("nil token entry") | ||||
| 		} | ||||
|  | ||||
| 		switch req.TokenEntry().NamespaceID { | ||||
| 		if te.Type != logical.TokenTypeService { | ||||
| 			return logical.ErrorResponse(`cubbyhole operations are only supported by "service" type tokens`), false, false, nil | ||||
| 		} | ||||
|  | ||||
| 		switch te.NamespaceID { | ||||
| 		case namespace.RootNamespaceID: | ||||
| 			// In order for the token store to revoke later, we need to have the same | ||||
| 			// salted ID, so we double-salt what's going to the cubbyhole backend | ||||
| @@ -562,10 +568,10 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | ||||
| 			req.ClientToken = re.SaltID(salt.SaltID(req.ClientToken)) | ||||
|  | ||||
| 		default: | ||||
| 			if req.TokenEntry().CubbyholeID == "" { | ||||
| 			if te.CubbyholeID == "" { | ||||
| 				return nil, false, false, fmt.Errorf("empty cubbyhole id") | ||||
| 			} | ||||
| 			req.ClientToken = req.TokenEntry().CubbyholeID | ||||
| 			req.ClientToken = te.CubbyholeID | ||||
| 		} | ||||
|  | ||||
| 	default: | ||||
| @@ -644,20 +650,20 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | ||||
| 		return nil, ok, exists, err | ||||
| 	} else { | ||||
| 		resp, err := re.backend.HandleRequest(ctx, req) | ||||
| 		// When a token gets renewed, the request hits this path and reaches | ||||
| 		// token store. Token store delegates the renewal to the expiration | ||||
| 		// manager. Expiration manager in-turn creates a different logical | ||||
| 		// request and forwards the request to the auth backend that had | ||||
| 		// initially authenticated the login request. The forwarding to auth | ||||
| 		// backend will make this code path hit for the second time for the | ||||
| 		// same renewal request. The accessors in the Alias structs should be | ||||
| 		// of the auth backend and not of the token store. Therefore, avoiding | ||||
| 		// the overwriting of accessors by having a check for path prefix | ||||
| 		// having "renew". This gets applied for "renew" and "renew-self" | ||||
| 		// requests. | ||||
| 		if resp != nil && | ||||
| 			resp.Auth != nil && | ||||
| 			!strings.HasPrefix(req.Path, "renew") { | ||||
| 			resp.Auth != nil { | ||||
| 			// When a token gets renewed, the request hits this path and | ||||
| 			// reaches token store. Token store delegates the renewal to the | ||||
| 			// expiration manager. Expiration manager in-turn creates a | ||||
| 			// different logical request and forwards the request to the auth | ||||
| 			// backend that had initially authenticated the login request. The | ||||
| 			// forwarding to auth backend will make this code path hit for the | ||||
| 			// second time for the same renewal request. The accessors in the | ||||
| 			// Alias structs should be of the auth backend and not of the token | ||||
| 			// store. Therefore, avoiding the overwriting of accessors by | ||||
| 			// having a check for path prefix having "renew". This gets applied | ||||
| 			// for "renew" and "renew-self" requests. | ||||
| 			if !strings.HasPrefix(req.Path, "renew") { | ||||
| 				if resp.Auth.Alias != nil { | ||||
| 					resp.Auth.Alias.MountAccessor = re.mountEntry.Accessor | ||||
| 				} | ||||
| @@ -666,6 +672,26 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			switch re.mountEntry.Type { | ||||
| 			case "token", "ns_token": | ||||
| 				// Nothing; we respect what the token store is telling us and | ||||
| 				// we don't allow tuning | ||||
| 			default: | ||||
| 				switch re.mountEntry.Config.TokenType { | ||||
| 				case logical.TokenTypeService, logical.TokenTypeBatch: | ||||
| 					resp.Auth.TokenType = re.mountEntry.Config.TokenType | ||||
| 				case logical.TokenTypeDefault, logical.TokenTypeDefaultService: | ||||
| 					if resp.Auth.TokenType == logical.TokenTypeDefault { | ||||
| 						resp.Auth.TokenType = logical.TokenTypeService | ||||
| 					} | ||||
| 				case logical.TokenTypeDefaultBatch: | ||||
| 					if resp.Auth.TokenType == logical.TokenTypeDefault { | ||||
| 						resp.Auth.TokenType = logical.TokenTypeBatch | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return resp, false, false, err | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package vault | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| @@ -13,6 +14,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	proto "github.com/golang/protobuf/proto" | ||||
| 	"github.com/hashicorp/errwrap" | ||||
| 	log "github.com/hashicorp/go-hclog" | ||||
| 	sockaddr "github.com/hashicorp/go-sockaddr" | ||||
| @@ -31,6 +33,7 @@ import ( | ||||
| 	"github.com/hashicorp/vault/helper/strutil" | ||||
| 	"github.com/hashicorp/vault/logical" | ||||
| 	"github.com/hashicorp/vault/logical/framework" | ||||
| 	"github.com/hashicorp/vault/logical/plugin/pb" | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| ) | ||||
|  | ||||
| @@ -183,6 +186,12 @@ func (ts *TokenStore) paths() []*framework.Path { | ||||
| 					Type:        framework.TypeCommaStringSlice, | ||||
| 					Description: `Comma separated string or JSON list of CIDR blocks. If set, specifies the blocks of IP addresses which are allowed to use the generated token.`, | ||||
| 				}, | ||||
|  | ||||
| 				"token_type": &framework.FieldSchema{ | ||||
| 					Type:        framework.TypeString, | ||||
| 					Default:     "service", | ||||
| 					Description: "The type of token to generate, service or batch", | ||||
| 				}, | ||||
| 			}, | ||||
|  | ||||
| 			Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| @@ -473,6 +482,8 @@ type TokenStore struct { | ||||
|  | ||||
| 	core *Core | ||||
|  | ||||
| 	batchTokenEncryptor BarrierEncryptor | ||||
|  | ||||
| 	baseBarrierView     *BarrierView | ||||
| 	idBarrierView       *BarrierView | ||||
| 	accessorBarrierView *BarrierView | ||||
| @@ -515,6 +526,7 @@ func NewTokenStore(ctx context.Context, logger log.Logger, core *Core, config *l | ||||
| 	t := &TokenStore{ | ||||
| 		activeContext:         ctx, | ||||
| 		core:                  core, | ||||
| 		batchTokenEncryptor:   core.barrier, | ||||
| 		baseBarrierView:       view, | ||||
| 		idBarrierView:         view.SubView(idPrefix), | ||||
| 		accessorBarrierView:   view.SubView(accessorPrefix), | ||||
| @@ -630,6 +642,9 @@ type tsRoleEntry struct { | ||||
|  | ||||
| 	// The set of CIDRs that tokens generated using this role will be bound to | ||||
| 	BoundCIDRs []*sockaddr.SockAddrMarshaler `json:"bound_cidrs"` | ||||
|  | ||||
| 	// The type of token this role should issue | ||||
| 	TokenType logical.TokenType `json:"token_type" mapstructure:"token_type"` | ||||
| } | ||||
|  | ||||
| type accessorEntry struct { | ||||
| @@ -673,6 +688,7 @@ func (ts *TokenStore) rootToken(ctx context.Context) (*logical.TokenEntry, error | ||||
| 		DisplayName:  "root", | ||||
| 		CreationTime: time.Now().Unix(), | ||||
| 		NamespaceID:  namespace.RootNamespaceID, | ||||
| 		Type:         logical.TokenTypeService, | ||||
| 	} | ||||
| 	if err := ts.create(ctx, te); err != nil { | ||||
| 		return nil, err | ||||
| @@ -771,14 +787,6 @@ func (ts *TokenStore) createAccessor(ctx context.Context, entry *logical.TokenEn | ||||
| // a newly generated ID if not provided. | ||||
| func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) error { | ||||
| 	defer metrics.MeasureSince([]string{"token", "create"}, time.Now()) | ||||
| 	// Generate an ID if necessary | ||||
| 	if entry.ID == "" { | ||||
| 		var err error | ||||
| 		entry.ID, err = base62.Random(TokenLength, true) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	tokenNS, err := NamespaceByID(ctx, entry.NamespaceID, ts.core) | ||||
| 	if err != nil { | ||||
| @@ -788,6 +796,24 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err | ||||
| 		return namespace.ErrNoNamespace | ||||
| 	} | ||||
|  | ||||
| 	entry.Policies = policyutil.SanitizePolicies(entry.Policies, policyutil.DoNotAddDefaultPolicy) | ||||
|  | ||||
| 	switch entry.Type { | ||||
| 	case logical.TokenTypeDefault, logical.TokenTypeService: | ||||
| 		// In case it was default, force to service | ||||
| 		entry.Type = logical.TokenTypeService | ||||
|  | ||||
| 		// Generate an ID if necessary | ||||
| 		userSelectedID := true | ||||
| 		if entry.ID == "" { | ||||
| 			userSelectedID = false | ||||
| 			var err error | ||||
| 			entry.ID, err = base62.Random(TokenLength, true) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if tokenNS.ID != namespace.RootNamespaceID { | ||||
| 			entry.ID = fmt.Sprintf("%s.%s", entry.ID, tokenNS.ID) | ||||
| 			if entry.CubbyholeID == "" { | ||||
| @@ -799,12 +825,14 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// If the user didn't specifically pick the ID, e.g. because they were | ||||
| 		// sudo/root, check for collision; otherwise trust the process | ||||
| 		if userSelectedID { | ||||
| 			exist, _ := ts.lookupInternal(ctx, entry.ID, false, true) | ||||
| 			if exist != nil { | ||||
| 				return fmt.Errorf("cannot create a token with a duplicate ID") | ||||
| 			} | ||||
|  | ||||
| 	entry.Policies = policyutil.SanitizePolicies(entry.Policies, policyutil.DoNotAddDefaultPolicy) | ||||
| 		} | ||||
|  | ||||
| 		err = ts.createAccessor(ctx, entry) | ||||
| 		if err != nil { | ||||
| @@ -812,6 +840,55 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err | ||||
| 		} | ||||
|  | ||||
| 		return ts.storeCommon(ctx, entry, true) | ||||
|  | ||||
| 	case logical.TokenTypeBatch: | ||||
| 		// Ensure fields we don't support/care about are nilled, proto marshal, | ||||
| 		// encrypt, skip persistence | ||||
| 		entry.ID = "" | ||||
| 		pEntry := &pb.TokenEntry{ | ||||
| 			Parent:       entry.Parent, | ||||
| 			Policies:     entry.Policies, | ||||
| 			Path:         entry.Path, | ||||
| 			Meta:         entry.Meta, | ||||
| 			DisplayName:  entry.DisplayName, | ||||
| 			CreationTime: entry.CreationTime, | ||||
| 			TTL:          int64(entry.TTL), | ||||
| 			Role:         entry.Role, | ||||
| 			EntityID:     entry.EntityID, | ||||
| 			NamespaceID:  entry.NamespaceID, | ||||
| 			Type:         uint32(entry.Type), | ||||
| 		} | ||||
|  | ||||
| 		boundCIDRs := make([]string, len(entry.BoundCIDRs)) | ||||
| 		for i, cidr := range entry.BoundCIDRs { | ||||
| 			boundCIDRs[i] = cidr.String() | ||||
| 		} | ||||
| 		pEntry.BoundCIDRs = boundCIDRs | ||||
|  | ||||
| 		mEntry, err := proto.Marshal(pEntry) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		eEntry, err := ts.batchTokenEncryptor.Encrypt(ctx, "", mEntry) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		bEntry := base64.RawURLEncoding.EncodeToString(eEntry) | ||||
| 		entry.ID = fmt.Sprintf("b.%s", bEntry) | ||||
|  | ||||
| 		if tokenNS.ID != namespace.RootNamespaceID { | ||||
| 			entry.ID = fmt.Sprintf("%s.%s", entry.ID, tokenNS.ID) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
|  | ||||
| 	default: | ||||
| 		return fmt.Errorf("cannot create a token of type %d", entry.Type) | ||||
| 	} | ||||
|  | ||||
| 	return errors.New("unreachable code") | ||||
| } | ||||
|  | ||||
| // Store is used to store an updated token entry without writing the | ||||
| @@ -975,6 +1052,11 @@ func (ts *TokenStore) Lookup(ctx context.Context, id string) (*logical.TokenEntr | ||||
| 		return nil, fmt.Errorf("cannot lookup blank token") | ||||
| 	} | ||||
|  | ||||
| 	// If it starts with "b-" it's a batch token | ||||
| 	if len(id) > 2 && id[0:2] == "b." { | ||||
| 		return ts.lookupBatchToken(ctx, id) | ||||
| 	} | ||||
|  | ||||
| 	lock := locksutil.LockForKey(ts.tokenLocks, id) | ||||
| 	lock.RLock() | ||||
| 	defer lock.RUnlock() | ||||
| @@ -982,8 +1064,8 @@ func (ts *TokenStore) Lookup(ctx context.Context, id string) (*logical.TokenEntr | ||||
| 	return ts.lookupInternal(ctx, id, false, false) | ||||
| } | ||||
|  | ||||
| // lookupTainted is used to find a token that may or maynot be tainted given its | ||||
| // ID. It acquires a read lock, then calls lookupInternal. | ||||
| // lookupTainted is used to find a token that may or may not be tainted given | ||||
| // its ID. It acquires a read lock, then calls lookupInternal. | ||||
| func (ts *TokenStore) lookupTainted(ctx context.Context, id string) (*logical.TokenEntry, error) { | ||||
| 	defer metrics.MeasureSince([]string{"token", "lookup"}, time.Now()) | ||||
| 	if id == "" { | ||||
| @@ -997,6 +1079,48 @@ func (ts *TokenStore) lookupTainted(ctx context.Context, id string) (*logical.To | ||||
| 	return ts.lookupInternal(ctx, id, false, true) | ||||
| } | ||||
|  | ||||
| func (ts *TokenStore) lookupBatchToken(ctx context.Context, id string) (*logical.TokenEntry, error) { | ||||
| 	// Strip the b. from the front and namespace ID from the back | ||||
| 	bEntry, _ := namespace.SplitIDFromString(id[2:]) | ||||
|  | ||||
| 	eEntry, err := base64.RawURLEncoding.DecodeString(bEntry) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	mEntry, err := ts.batchTokenEncryptor.Decrypt(ctx, "", eEntry) | ||||
| 	if err != nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	pEntry := new(pb.TokenEntry) | ||||
| 	if err := proto.Unmarshal(mEntry, pEntry); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	te, err := pb.ProtoTokenEntryToLogicalTokenEntry(pEntry) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if time.Now().After(time.Unix(te.CreationTime, 0).Add(te.TTL)) { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	if te.Parent != "" { | ||||
| 		pte, err := ts.Lookup(ctx, te.Parent) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if pte == nil { | ||||
| 			return nil, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	te.ID = id | ||||
| 	return te, nil | ||||
| } | ||||
|  | ||||
| // lookupInternal is used to find a token given its (possibly salted) ID. If | ||||
| // tainted is true, entries that are in some revocation state (currently, | ||||
| // indicated by num uses < 0), the entry will be returned anyways | ||||
| @@ -1006,6 +1130,11 @@ func (ts *TokenStore) lookupInternal(ctx context.Context, id string, salted, tai | ||||
| 		return nil, errwrap.Wrapf("failed to find namespace in context: {{err}}", err) | ||||
| 	} | ||||
|  | ||||
| 	// If it starts with "b." it's a batch token | ||||
| 	if len(id) > 2 && id[0:2] == "b." { | ||||
| 		return ts.lookupBatchToken(ctx, id) | ||||
| 	} | ||||
|  | ||||
| 	var raw *logical.StorageEntry | ||||
| 	lookupID := id | ||||
|  | ||||
| @@ -1063,6 +1192,11 @@ func (ts *TokenStore) lookupInternal(ctx context.Context, id string, salted, tai | ||||
| 		entry.NamespaceID = namespace.RootNamespaceID | ||||
| 	} | ||||
|  | ||||
| 	// This will be the upgrade case | ||||
| 	if entry.Type == logical.TokenTypeDefault { | ||||
| 		entry.Type = logical.TokenTypeService | ||||
| 	} | ||||
|  | ||||
| 	persistNeeded := false | ||||
|  | ||||
| 	// Upgrade the deprecated fields | ||||
| @@ -1490,6 +1624,15 @@ func (ts *TokenStore) revokeTreeInternal(ctx context.Context, id string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *Core) IsBatchTokenCreationRequest(ctx context.Context, path string) (bool, error) { | ||||
| 	name := strings.TrimPrefix(path, "auth/token/create/") | ||||
| 	roleEntry, err := c.tokenStore.tokenStoreRole(ctx, name) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return roleEntry.TokenType == logical.TokenTypeBatch, nil | ||||
| } | ||||
|  | ||||
| // handleCreateAgainstRole handles the auth/token/create path for a role | ||||
| func (ts *TokenStore) handleCreateAgainstRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	name := d.Get("role_name").(string) | ||||
| @@ -1924,6 +2067,9 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 	if parent == nil { | ||||
| 		return logical.ErrorResponse("parent token lookup failed: no parent found"), logical.ErrInvalidRequest | ||||
| 	} | ||||
| 	if parent.Type == logical.TokenTypeBatch { | ||||
| 		return logical.ErrorResponse("batch tokens cannot create more tokens"), nil | ||||
| 	} | ||||
|  | ||||
| 	// A token with a restricted number of uses cannot create a new token | ||||
| 	// otherwise it could escape the restriction count. | ||||
| @@ -1949,6 +2095,7 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		DisplayName     string `mapstructure:"display_name"` | ||||
| 		NumUses         int    `mapstructure:"num_uses"` | ||||
| 		Period          string | ||||
| 		Type            string `mapstructure:"type"` | ||||
| 	} | ||||
| 	if err := mapstructure.WeakDecode(req.Data, &data); err != nil { | ||||
| 		return logical.ErrorResponse(fmt.Sprintf( | ||||
| @@ -1982,6 +2129,56 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	renewable := true | ||||
| 	if data.Renewable != nil { | ||||
| 		renewable = *data.Renewable | ||||
| 	} | ||||
|  | ||||
| 	tokenType := logical.TokenTypeService | ||||
| 	tokenTypeStr := data.Type | ||||
| 	if role != nil { | ||||
| 		switch role.TokenType { | ||||
| 		case logical.TokenTypeDefault, logical.TokenTypeService: | ||||
| 			tokenTypeStr = logical.TokenTypeService.String() | ||||
| 		case logical.TokenTypeBatch: | ||||
| 			tokenTypeStr = logical.TokenTypeBatch.String() | ||||
| 		default: | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("role being used for token creation contains invalid token type %q", role.TokenType.String())), nil | ||||
| 		} | ||||
| 	} | ||||
| 	switch tokenTypeStr { | ||||
| 	case "", "service": | ||||
| 	case "batch": | ||||
| 		var badReason string | ||||
| 		switch { | ||||
| 		case data.ExplicitMaxTTL != "": | ||||
| 			dur, err := parseutil.ParseDurationSecond(data.ExplicitMaxTTL) | ||||
| 			if err != nil { | ||||
| 				return logical.ErrorResponse(`"explicit_max_ttl" value could not be parsed`), nil | ||||
| 			} | ||||
| 			if dur != 0 { | ||||
| 				badReason = "explicit_max_ttl" | ||||
| 			} | ||||
| 		case data.NumUses != 0: | ||||
| 			badReason = "num_uses" | ||||
| 		case data.Period != "": | ||||
| 			dur, err := parseutil.ParseDurationSecond(data.Period) | ||||
| 			if err != nil { | ||||
| 				return logical.ErrorResponse(`"period" value could not be parsed`), nil | ||||
| 			} | ||||
| 			if dur != 0 { | ||||
| 				badReason = "period" | ||||
| 			} | ||||
| 		} | ||||
| 		if badReason != "" { | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("batch tokens cannot have %q set", badReason)), nil | ||||
| 		} | ||||
| 		tokenType = logical.TokenTypeBatch | ||||
| 		renewable = false | ||||
| 	default: | ||||
| 		return logical.ErrorResponse("invalid 'token_type' value"), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	// Verify the number of uses is positive | ||||
| 	if data.NumUses < 0 { | ||||
| 		return logical.ErrorResponse("number of uses cannot be negative"), | ||||
| @@ -2002,11 +2199,7 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		NumUses:      data.NumUses, | ||||
| 		CreationTime: time.Now().Unix(), | ||||
| 		NamespaceID:  ns.ID, | ||||
| 	} | ||||
|  | ||||
| 	renewable := true | ||||
| 	if data.Renewable != nil { | ||||
| 		renewable = *data.Renewable | ||||
| 		Type:         tokenType, | ||||
| 	} | ||||
|  | ||||
| 	// If the role is not nil, we add the role name as part of the token's | ||||
| @@ -2169,12 +2362,19 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if strutil.StrListContains(te.Policies, "root") { | ||||
| 		// 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") { | ||||
| 		if !strutil.StrListContains(parent.Policies, "root") { | ||||
| 			return logical.ErrorResponse("root tokens may not be created without parent token being root"), logical.ErrInvalidRequest | ||||
| 		} | ||||
|  | ||||
| 		if te.Type == logical.TokenTypeBatch { | ||||
| 			// Batch tokens cannot be revoked so we should never have root batch tokens | ||||
| 			return logical.ErrorResponse("batch tokens cannot be root tokens"), nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// | ||||
| 	// 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. | ||||
| @@ -2208,7 +2408,8 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
|  | ||||
| 	// At this point, it is clear whether the token is going to be an orphan or | ||||
| 	// not. If the token is not going to be an orphan, inherit the parent's | ||||
| 	// entity identifier into the child token. | ||||
| 	// entity identifier into the child token. We must also verify that, if | ||||
| 	// it's not an orphan, the parent isn't a batch token. | ||||
| 	if te.Parent != "" { | ||||
| 		te.EntityID = parent.EntityID | ||||
| 	} | ||||
| @@ -2269,8 +2470,10 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		te.TTL = dur | ||||
| 	} | ||||
|  | ||||
| 	// Set the lesser period/explicit max TTL if defined both in arguments and in role | ||||
| 	if role != nil { | ||||
| 	// Set the lesser period/explicit max TTL if defined both in arguments and | ||||
| 	// in role. Batch tokens will error out if not set via role, but here we | ||||
| 	// need to explicitly check | ||||
| 	if role != nil && te.Type != logical.TokenTypeBatch { | ||||
| 		if role.ExplicitMaxTTL != 0 { | ||||
| 			switch { | ||||
| 			case explicitMaxTTLToUse == 0: | ||||
| @@ -2345,6 +2548,7 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| 		Period:         periodToUse, | ||||
| 		ExplicitMaxTTL: explicitMaxTTLToUse, | ||||
| 		CreationPath:   te.Path, | ||||
| 		TokenType:      te.Type, | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range te.Policies { | ||||
| @@ -2364,34 +2568,7 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque | ||||
| // 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(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||
| 	te, err := ts.Lookup(ctx, req.ClientToken) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if te == nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, ts.core) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if tokenNS == nil { | ||||
| 		return nil, namespace.ErrNoNamespace | ||||
| 	} | ||||
|  | ||||
| 	revokeCtx := namespace.ContextWithNamespace(ts.quitContext, tokenNS) | ||||
| 	leaseID, err := ts.expiration.CreateOrFetchRevocationLeaseByToken(revokeCtx, te) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = ts.expiration.Revoke(revokeCtx, leaseID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil | ||||
| 	return ts.revokeCommon(ctx, req, data, req.ClientToken) | ||||
| } | ||||
|  | ||||
| // handleRevokeTree handles the auth/token/revoke/id path for revocation of tokens | ||||
| @@ -2408,6 +2585,20 @@ func (ts *TokenStore) handleRevokeTree(ctx context.Context, req *logical.Request | ||||
| 		urltoken = true | ||||
| 	} | ||||
|  | ||||
| 	if resp, err := ts.revokeCommon(ctx, req, data, id); resp != nil || err != nil { | ||||
| 		return resp, err | ||||
| 	} | ||||
|  | ||||
| 	if urltoken { | ||||
| 		resp := &logical.Response{} | ||||
| 		resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`) | ||||
| 		return resp, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| func (ts *TokenStore) revokeCommon(ctx context.Context, req *logical.Request, data *framework.FieldData, id string) (*logical.Response, error) { | ||||
| 	te, err := ts.Lookup(ctx, id) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -2416,6 +2607,10 @@ func (ts *TokenStore) handleRevokeTree(ctx context.Context, req *logical.Request | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		return logical.ErrorResponse("batch tokens cannot be revoked"), nil | ||||
| 	} | ||||
|  | ||||
| 	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, ts.core) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -2435,12 +2630,6 @@ func (ts *TokenStore) handleRevokeTree(ctx context.Context, req *logical.Request | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if urltoken { | ||||
| 		resp := &logical.Response{} | ||||
| 		resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`) | ||||
| 		return resp, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| @@ -2477,6 +2666,10 @@ func (ts *TokenStore) handleRevokeOrphan(ctx context.Context, req *logical.Reque | ||||
| 		return logical.ErrorResponse("token to revoke not found"), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		return logical.ErrorResponse("batch tokens cannot be revoked"), nil | ||||
| 	} | ||||
|  | ||||
| 	// Revoke and orphan | ||||
| 	if err := ts.revokeOrphan(ctx, id); err != nil { | ||||
| 		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||||
| @@ -2545,6 +2738,7 @@ func (ts *TokenStore) handleLookup(ctx context.Context, req *logical.Request, da | ||||
| 			"ttl":              int64(0), | ||||
| 			"explicit_max_ttl": int64(out.ExplicitMaxTTL.Seconds()), | ||||
| 			"entity_id":        out.EntityID, | ||||
| 			"type":             out.Type.String(), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| @@ -2645,8 +2839,14 @@ func (ts *TokenStore) handleRenew(ctx context.Context, req *logical.Request, dat | ||||
| 		return logical.ErrorResponse("token not found"), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	var resp *logical.Response | ||||
|  | ||||
| 	if te.Type == logical.TokenTypeBatch { | ||||
| 		return logical.ErrorResponse("batch tokens cannot be renewed"), nil | ||||
| 	} | ||||
|  | ||||
| 	// Renew the token and its children | ||||
| 	resp, err := ts.expiration.RenewToken(ctx, req, te, increment) | ||||
| 	resp, err = ts.expiration.RenewToken(ctx, req, te, increment) | ||||
|  | ||||
| 	if urltoken { | ||||
| 		resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`) | ||||
| @@ -2761,6 +2961,7 @@ func (ts *TokenStore) tokenStoreRoleRead(ctx context.Context, req *logical.Reque | ||||
| 			"orphan":              role.Orphan, | ||||
| 			"path_suffix":         role.PathSuffix, | ||||
| 			"renewable":           role.Renewable, | ||||
| 			"token_type":          role.TokenType.String(), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| @@ -2898,6 +3099,38 @@ func (ts *TokenStore) tokenStoreRoleCreateUpdate(ctx context.Context, req *logic | ||||
| 		entry.DisallowedPolicies = strutil.RemoveDuplicates(data.Get("disallowed_policies").([]string), true) | ||||
| 	} | ||||
|  | ||||
| 	tokenType := entry.TokenType | ||||
| 	tokenTypeRaw, ok := data.GetOk("token_type") | ||||
| 	if ok { | ||||
| 		tokenTypeStr := tokenTypeRaw.(string) | ||||
| 		switch tokenTypeStr { | ||||
| 		case "", "service": | ||||
| 			tokenType = logical.TokenTypeService | ||||
| 		case "batch": | ||||
| 			tokenType = logical.TokenTypeBatch | ||||
| 		default: | ||||
| 			return logical.ErrorResponse(fmt.Sprintf("invalid 'token_type' value %q", tokenTypeStr)), nil | ||||
| 		} | ||||
| 	} else if req.Operation == logical.CreateOperation { | ||||
| 		tokenType = logical.TokenTypeService | ||||
| 	} | ||||
| 	entry.TokenType = tokenType | ||||
|  | ||||
| 	if entry.TokenType == logical.TokenTypeBatch { | ||||
| 		if !entry.Orphan { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate non-orphan tokens"), nil | ||||
| 		} | ||||
| 		if entry.Period != 0 { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate periodic tokens"), nil | ||||
| 		} | ||||
| 		if entry.Renewable { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate renewable tokens"), nil | ||||
| 		} | ||||
| 		if entry.ExplicitMaxTTL != 0 { | ||||
| 			return logical.ErrorResponse("'token_type' cannot be 'batch' when role is set to generate tokens with an explicit max TTL"), nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ns, err := namespace.FromContext(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|   | ||||
| @@ -13,6 +13,8 @@ import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-test/deep" | ||||
| 	"github.com/hashicorp/errwrap" | ||||
| 	hclog "github.com/hashicorp/go-hclog" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/locksutil" | ||||
| @@ -286,10 +288,22 @@ func getBackendConfig(c *Core) *logical.BackendConfig { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) { | ||||
| func testMakeBatchTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) { | ||||
| 	testMakeTokenViaBackend(t, ts, root, client, ttl, policy, true) | ||||
| } | ||||
|  | ||||
| func testMakeServiceTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string) { | ||||
| 	testMakeTokenViaBackend(t, ts, root, client, ttl, policy, false) | ||||
| } | ||||
|  | ||||
| func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl string, policy []string, batch bool) { | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = root | ||||
| 	if batch { | ||||
| 		req.Data["type"] = "batch" | ||||
| 	} else { | ||||
| 		req.Data["id"] = client | ||||
| 	} | ||||
| 	req.Data["policies"] = policy | ||||
| 	req.Data["ttl"] = ttl | ||||
| 	resp := testMakeTokenViaRequest(t, ts, req) | ||||
| @@ -301,22 +315,27 @@ func testMakeTokenViaBackend(t testing.TB, ts *TokenStore, root, client, ttl str | ||||
|  | ||||
| func testMakeTokenViaRequest(t testing.TB, ts *TokenStore, req *logical.Request) *logical.Response { | ||||
| 	resp, err := ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if resp == nil { | ||||
| 		t.Fatalf("got nil token from create call") | ||||
| 	} | ||||
| 	// Let the caller handle the error | ||||
| 	if resp.IsError() { | ||||
| 		return resp | ||||
| 	} | ||||
|  | ||||
| 	te := &logical.TokenEntry{ | ||||
| 		Path:        resp.Auth.CreationPath, | ||||
| 		NamespaceID: namespace.RootNamespaceID, | ||||
| 	} | ||||
|  | ||||
| 	if resp.Auth.TokenType != logical.TokenTypeBatch { | ||||
| 		if err := ts.expiration.RegisterAuth(namespace.TestContext(), te, resp.Auth); err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	te, err = ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if err != nil { | ||||
| @@ -333,9 +352,15 @@ func testMakeTokenDirectly(t testing.TB, ts *TokenStore, te *logical.TokenEntry) | ||||
| 	if te.NamespaceID == "" { | ||||
| 		te.NamespaceID = namespace.RootNamespaceID | ||||
| 	} | ||||
| 	if te.CreationTime == 0 { | ||||
| 		te.CreationTime = time.Now().Unix() | ||||
| 	} | ||||
| 	if err := ts.create(namespace.RootContext(nil), te); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if te.Type == logical.TokenTypeDefault { | ||||
| 		te.Type = logical.TokenTypeService | ||||
| 	} | ||||
| 	auth := &logical.Auth{ | ||||
| 		NumUses:     te.NumUses, | ||||
| 		DisplayName: te.DisplayName, | ||||
| @@ -351,16 +376,37 @@ func testMakeTokenDirectly(t testing.TB, ts *TokenStore, te *logical.TokenEntry) | ||||
| 		Period:         te.Period, | ||||
| 		ExplicitMaxTTL: te.ExplicitMaxTTL, | ||||
| 		CreationPath:   te.Path, | ||||
| 		TokenType:      te.Type, | ||||
| 	} | ||||
| 	if err := ts.expiration.RegisterAuth(namespace.TestContext(), te, auth); err != nil { | ||||
| 	err := ts.expiration.RegisterAuth(namespace.TestContext(), te, auth) | ||||
| 	switch err { | ||||
| 	case nil: | ||||
| 		if te.Type == logical.TokenTypeBatch { | ||||
| 			t.Fatal("expected error from trying to register auth with batch token") | ||||
| 		} | ||||
| 	default: | ||||
| 		if te.Type != logical.TokenTypeBatch { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) { | ||||
| func testMakeServiceTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) { | ||||
| 	testMakeTokenViaCore(t, c, root, client, ttl, policy, false, nil) | ||||
| } | ||||
|  | ||||
| func testMakeBatchTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string) { | ||||
| 	testMakeTokenViaCore(t, c, root, client, ttl, policy, true, nil) | ||||
| } | ||||
|  | ||||
| func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl string, policy []string, batch bool, outAuth *logical.Auth) { | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create") | ||||
| 	req.ClientToken = root | ||||
| 	if batch { | ||||
| 		req.Data["type"] = "batch" | ||||
| 	} else { | ||||
| 		req.Data["id"] = client | ||||
| 	} | ||||
| 	req.Data["policies"] = policy | ||||
| 	req.Data["ttl"] = ttl | ||||
|  | ||||
| @@ -368,9 +414,14 @@ func testMakeTokenViaCore(t testing.TB, c *Core, root, client, ttl string, polic | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	if !batch { | ||||
| 		if resp.Auth.ClientToken != client { | ||||
| 			t.Fatalf("bad: %#v", *resp) | ||||
| 		} | ||||
| 	} | ||||
| 	if outAuth != nil && resp != nil && resp.Auth != nil { | ||||
| 		*outAuth = *resp.Auth | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_AccessorIndex(t *testing.T) { | ||||
| @@ -404,13 +455,27 @@ func TestTokenStore_AccessorIndex(t *testing.T) { | ||||
| 	if aEntry.TokenID != ent.ID { | ||||
| 		t.Fatalf("bad: got\n%s\nexpected\n%s\n", aEntry.TokenID, ent.ID) | ||||
| 	} | ||||
|  | ||||
| 	// Make sure a batch token doesn't get an accessor | ||||
| 	ent.Type = logical.TokenTypeBatch | ||||
| 	testMakeTokenDirectly(t, ts, ent) | ||||
|  | ||||
| 	out, err = ts.Lookup(namespace.TestContext(), ent.ID) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Ensure that accessor is created | ||||
| 	if out == nil || out.Accessor != "" { | ||||
| 		t.Fatalf("bad: %#v", out) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_LookupAccessor(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
| 	out, err := ts.Lookup(namespace.TestContext(), "tokenid") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| @@ -448,7 +513,7 @@ func TestTokenStore_HandleRequest_ListAccessors(t *testing.T) { | ||||
|  | ||||
| 	testKeys := []string{"token1", "token2", "token3", "token4"} | ||||
| 	for _, key := range testKeys { | ||||
| 		testMakeTokenViaBackend(t, ts, root, key, "", []string{"foo"}) | ||||
| 		testMakeServiceTokenViaBackend(t, ts, root, key, "", []string{"foo"}) | ||||
| 	} | ||||
|  | ||||
| 	// Revoke root to make the number of accessors match | ||||
| @@ -534,7 +599,7 @@ func TestTokenStore_HandleRequest_RevokeAccessor(t *testing.T) { | ||||
| 	rootToken, err := ts.rootToken(namespace.TestContext()) | ||||
| 	root := rootToken.ID | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
|  | ||||
| 	auth := &logical.Auth{ | ||||
| 		ClientToken: "tokenid", | ||||
| @@ -587,7 +652,7 @@ func TestTokenStore_HandleRequest_RevokeAccessor(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	// Now test without registering the token through the expiration manager | ||||
| 	testMakeTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "tokenid", "", []string{"foo"}) | ||||
| 	out, err = ts.Lookup(namespace.TestContext(), "tokenid") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| @@ -639,6 +704,27 @@ func TestTokenStore_RootToken(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_NoRootBatch(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/create") | ||||
| 	req.ClientToken = root | ||||
| 	req.Data["type"] = "batch" | ||||
| 	req.Data["policies"] = "root" | ||||
| 	req.Data["ttl"] = "5m" | ||||
|  | ||||
| 	resp, err := c.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("expected response") | ||||
| 	} | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("expected error, got %#v", *resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_CreateLookup(t *testing.T) { | ||||
| 	c, _, _ := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| @@ -930,6 +1016,7 @@ func TestTokenStore_Revoke_Leases(t *testing.T) { | ||||
| 		Path:        "noop/foo", | ||||
| 		ClientToken: ent.ID, | ||||
| 	} | ||||
| 	req.SetTokenEntry(ent) | ||||
| 	resp := &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -1226,6 +1313,19 @@ func TestTokenStore_HandleRequest_NonAssignable(t *testing.T) { | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("expected error; response is %#v", *resp) | ||||
| 	} | ||||
|  | ||||
| 	// Batch tokens too | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatal("got a nil response") | ||||
| 	} | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("expected error; response is %#v", *resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) { | ||||
| @@ -1250,6 +1350,7 @@ func TestTokenStore_HandleRequest_CreateToken_DisplayName(t *testing.T) { | ||||
| 		Path:        "auth/token/create", | ||||
| 		DisplayName: "token-foo-bar-baz", | ||||
| 		TTL:         0, | ||||
| 		Type:        logical.TokenTypeService, | ||||
| 	} | ||||
| 	out, err := ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if err != nil { | ||||
| @@ -1269,7 +1370,15 @@ func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) { | ||||
| 	req.ClientToken = root | ||||
| 	req.Data["num_uses"] = "1" | ||||
|  | ||||
| 	// Make sure batch tokens can't do limited use counts | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp, err := ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if resp == nil || !resp.IsError() { | ||||
| 		t.Fatalf("expected error: resp: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	delete(req.Data, "type") | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| @@ -1284,6 +1393,7 @@ func TestTokenStore_HandleRequest_CreateToken_NumUses(t *testing.T) { | ||||
| 		DisplayName: "token", | ||||
| 		NumUses:     1, | ||||
| 		TTL:         0, | ||||
| 		Type:        logical.TokenTypeService, | ||||
| 	} | ||||
| 	out, err := ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if err != nil { | ||||
| @@ -1337,7 +1447,15 @@ func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) { | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = root | ||||
|  | ||||
| 	// Make sure batch tokens won't automatically assign root | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp, err := ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if resp == nil || !resp.IsError() { | ||||
| 		t.Fatalf("expected error: resp: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	delete(req.Data, "type") | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| @@ -1351,6 +1469,7 @@ func TestTokenStore_HandleRequest_CreateToken_NoPolicy(t *testing.T) { | ||||
| 		Path:        "auth/token/create", | ||||
| 		DisplayName: "token", | ||||
| 		TTL:         0, | ||||
| 		Type:        logical.TokenTypeService, | ||||
| 	} | ||||
| 	out, err := ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if err != nil { | ||||
| @@ -1408,16 +1527,26 @@ func TestTokenStore_HandleRequest_CreateToken_RootID(t *testing.T) { | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
|  | ||||
| 	if resp.Auth.ClientToken != "foobar" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	// Retry with batch; batch should not actually accept a custom ID | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	out, _ := ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if out.ID == "foobar" { | ||||
| 		t.Fatalf("bad: %#v", out) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = "client" | ||||
| @@ -1431,12 +1560,22 @@ func TestTokenStore_HandleRequest_CreateToken_NonRootID(t *testing.T) { | ||||
| 	if resp.Data["error"] != "root or sudo privileges required to specify token id" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	// Retry with batch | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != logical.ErrInvalidRequest { | ||||
| 		t.Fatalf("err: %v resp: %#v", err, resp) | ||||
| 	} | ||||
| 	if resp.Data["error"] != "root or sudo privileges required to specify token id" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = "client" | ||||
| @@ -1449,12 +1588,28 @@ func TestTokenStore_HandleRequest_CreateToken_NonRoot_Subset(t *testing.T) { | ||||
| 	if resp.Auth.ClientToken == "" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	ent := &logical.TokenEntry{ | ||||
| 		NamespaceID: namespace.RootNamespaceID, | ||||
| 		Path:        "test", | ||||
| 		Policies:    []string{"foo", "bar"}, | ||||
| 		TTL:         time.Hour, | ||||
| 	} | ||||
| 	testMakeTokenDirectly(t, ts, ent) | ||||
| 	req.ClientToken = ent.ID | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	if resp.Auth.ClientToken == "" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_CreateToken_NonRoot_InvalidSubset(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo", "bar"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = "client" | ||||
| @@ -1480,7 +1635,7 @@ func TestTokenStore_HandleRequest_CreateToken_NonRoot_RootChild(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, ts, root, "sudoClient", "", []string{"test1"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "sudoClient", "", []string{"test1"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = "sudoClient" | ||||
| @@ -1555,7 +1710,7 @@ func TestTokenStore_HandleRequest_CreateToken_Root_RootChild(t *testing.T) { | ||||
| func TestTokenStore_HandleRequest_CreateToken_NonRoot_NoParent(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "client", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "client", "", []string{"foo"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "create") | ||||
| 	req.ClientToken = "client" | ||||
| @@ -1633,6 +1788,18 @@ func TestTokenStore_HandleRequest_CreateToken_Metadata(t *testing.T) { | ||||
| 	if !reflect.DeepEqual(out.Meta, meta) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta) | ||||
| 	} | ||||
|  | ||||
| 	// Test with batch tokens | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if resp.Auth.ClientToken == "" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	out, _ = ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if !reflect.DeepEqual(out.Meta, meta) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", meta, out.Meta) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_CreateToken_Lease(t *testing.T) { | ||||
| @@ -1675,6 +1842,19 @@ func TestTokenStore_HandleRequest_CreateToken_TTL(t *testing.T) { | ||||
| 	if !resp.Auth.Renewable { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	// Test batch tokens | ||||
| 	req.Data["type"] = "batch" | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if resp.Auth.ClientToken == "" { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| 	if resp.Auth.TTL != time.Hour { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| 	if resp.Auth.Renewable { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_Revoke(t *testing.T) { | ||||
| @@ -1684,7 +1864,7 @@ func TestTokenStore_HandleRequest_Revoke(t *testing.T) { | ||||
| 	rootToken, err := ts.rootToken(namespace.TestContext()) | ||||
| 	root := rootToken.ID | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
|  | ||||
| 	te, err := ts.Lookup(namespace.TestContext(), "child") | ||||
| 	if err != nil { | ||||
| @@ -1706,7 +1886,7 @@ func TestTokenStore_HandleRequest_Revoke(t *testing.T) { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
|  | ||||
| 	te, err = ts.Lookup(namespace.TestContext(), "sub-child") | ||||
| 	if err != nil { | ||||
| @@ -1760,8 +1940,8 @@ func TestTokenStore_HandleRequest_Revoke(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	// Now test without registering the tokens through the expiration manager | ||||
| 	testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
| 	testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
|  | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "revoke") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| @@ -1798,8 +1978,8 @@ func TestTokenStore_HandleRequest_Revoke(t *testing.T) { | ||||
| func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
| 	testMakeTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "child", "", []string{"root", "foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, "child", "sub-child", "", []string{"foo"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "revoke-orphan") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| @@ -1850,7 +2030,7 @@ func TestTokenStore_HandleRequest_RevokeOrphan(t *testing.T) { | ||||
| func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaBackend(t, ts, root, "child", "", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaBackend(t, ts, root, "child", "", []string{"foo"}) | ||||
|  | ||||
| 	out, err := ts.Lookup(namespace.TestContext(), "child") | ||||
| 	if err != nil { | ||||
| @@ -1883,6 +2063,11 @@ func TestTokenStore_HandleRequest_RevokeOrphan_NonRoot(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 	testTokenStore_HandleRequest_Lookup(t, false) | ||||
| 	testTokenStore_HandleRequest_Lookup(t, true) | ||||
| } | ||||
|  | ||||
| func testTokenStore_HandleRequest_Lookup(t *testing.T, batch bool) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "lookup") | ||||
| @@ -1911,6 +2096,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 		"explicit_max_ttl": int64(0), | ||||
| 		"expire_time":      nil, | ||||
| 		"entity_id":        "", | ||||
| 		"type":             "service", | ||||
| 	} | ||||
|  | ||||
| 	if resp.Data["creation_time"].(int64) == 0 { | ||||
| @@ -1922,63 +2108,20 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data) | ||||
| 	} | ||||
|  | ||||
| 	testMakeTokenViaCore(t, c, root, "client", "3600s", []string{"foo"}) | ||||
| 	outAuth := new(logical.Auth) | ||||
| 	testMakeTokenViaCore(t, c, root, "client", "3600s", []string{"foo"}, batch, outAuth) | ||||
|  | ||||
| 	// Test via GET | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "lookup") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"token": "client", | ||||
| 	} | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	exp = map[string]interface{}{ | ||||
| 		"id":               "client", | ||||
| 		"accessor":         resp.Data["accessor"], | ||||
| 		"policies":         []string{"default", "foo"}, | ||||
| 		"path":             "auth/token/create", | ||||
| 		"meta":             map[string]string(nil), | ||||
| 		"display_name":     "token", | ||||
| 		"orphan":           false, | ||||
| 		"num_uses":         0, | ||||
| 		"creation_ttl":     int64(3600), | ||||
| 		"ttl":              int64(3600), | ||||
| 		"explicit_max_ttl": int64(0), | ||||
| 		"renewable":        true, | ||||
| 		"entity_id":        "", | ||||
| 	} | ||||
|  | ||||
| 	if resp.Data["creation_time"].(int64) == 0 { | ||||
| 		t.Fatalf("creation time was zero") | ||||
| 	} | ||||
| 	delete(resp.Data, "creation_time") | ||||
| 	if resp.Data["issue_time"].(time.Time).IsZero() { | ||||
| 		t.Fatal("issue time is default time") | ||||
| 	} | ||||
| 	delete(resp.Data, "issue_time") | ||||
| 	if resp.Data["expire_time"].(time.Time).IsZero() { | ||||
| 		t.Fatal("expire time is default time") | ||||
| 	} | ||||
| 	delete(resp.Data, "expire_time") | ||||
|  | ||||
| 	// Depending on timing of the test this may have ticked down, so accept 3599 | ||||
| 	if resp.Data["ttl"].(int64) == 3599 { | ||||
| 		resp.Data["ttl"] = int64(3600) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(resp.Data, exp) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data) | ||||
| 	tokenType := "service" | ||||
| 	expID := "client" | ||||
| 	if batch { | ||||
| 		tokenType = "batch" | ||||
| 		expID = outAuth.ClientToken | ||||
| 	} | ||||
|  | ||||
| 	// Test via POST | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "lookup") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"token": "client", | ||||
| 		"token": expID, | ||||
| 	} | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| @@ -1989,7 +2132,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	exp = map[string]interface{}{ | ||||
| 		"id":               "client", | ||||
| 		"id":               expID, | ||||
| 		"accessor":         resp.Data["accessor"], | ||||
| 		"policies":         []string{"default", "foo"}, | ||||
| 		"path":             "auth/token/create", | ||||
| @@ -2000,8 +2143,9 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 		"creation_ttl":     int64(3600), | ||||
| 		"ttl":              int64(3600), | ||||
| 		"explicit_max_ttl": int64(0), | ||||
| 		"renewable":        true, | ||||
| 		"renewable":        !batch, | ||||
| 		"entity_id":        "", | ||||
| 		"type":             tokenType, | ||||
| 	} | ||||
|  | ||||
| 	if resp.Data["creation_time"].(int64) == 0 { | ||||
| @@ -2022,28 +2166,80 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 		resp.Data["ttl"] = int64(3600) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(resp.Data, exp) { | ||||
| 		t.Fatalf("bad: expected:%#v\nactual:%#v", exp, resp.Data) | ||||
| 	if diff := deep.Equal(resp.Data, exp); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
|  | ||||
| 	// Test via POST | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "lookup") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"token": expID, | ||||
| 	} | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	exp = map[string]interface{}{ | ||||
| 		"id":               expID, | ||||
| 		"accessor":         resp.Data["accessor"], | ||||
| 		"policies":         []string{"default", "foo"}, | ||||
| 		"path":             "auth/token/create", | ||||
| 		"meta":             map[string]string(nil), | ||||
| 		"display_name":     "token", | ||||
| 		"orphan":           false, | ||||
| 		"num_uses":         0, | ||||
| 		"creation_ttl":     int64(3600), | ||||
| 		"ttl":              int64(3600), | ||||
| 		"explicit_max_ttl": int64(0), | ||||
| 		"renewable":        !batch, | ||||
| 		"entity_id":        "", | ||||
| 		"type":             tokenType, | ||||
| 	} | ||||
|  | ||||
| 	if resp.Data["creation_time"].(int64) == 0 { | ||||
| 		t.Fatalf("creation time was zero") | ||||
| 	} | ||||
| 	delete(resp.Data, "creation_time") | ||||
| 	if resp.Data["issue_time"].(time.Time).IsZero() { | ||||
| 		t.Fatal("issue time is default time") | ||||
| 	} | ||||
| 	delete(resp.Data, "issue_time") | ||||
| 	if resp.Data["expire_time"].(time.Time).IsZero() { | ||||
| 		t.Fatal("expire time is default time") | ||||
| 	} | ||||
| 	delete(resp.Data, "expire_time") | ||||
|  | ||||
| 	// Depending on timing of the test this may have ticked down, so accept 3599 | ||||
| 	if resp.Data["ttl"].(int64) == 3599 { | ||||
| 		resp.Data["ttl"] = int64(3600) | ||||
| 	} | ||||
|  | ||||
| 	if diff := deep.Equal(resp.Data, exp); diff != nil { | ||||
| 		t.Fatal(diff) | ||||
| 	} | ||||
|  | ||||
| 	// Test last_renewal_time functionality | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "renew") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"token": "client", | ||||
| 	} | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	req = logical.TestRequest(t, logical.UpdateOperation, "lookup") | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"token": "client", | ||||
| 		"token": expID, | ||||
| 	} | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
| 	if batch && !resp.IsError() || !batch && resp.IsError() { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
|  | ||||
| 	req.Path = "lookup" | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| 	} | ||||
| @@ -2051,15 +2247,19 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) { | ||||
| 		t.Fatalf("bad: %#v", resp) | ||||
| 	} | ||||
|  | ||||
| 	if !batch { | ||||
| 		if resp.Data["last_renewal_time"].(int64) == 0 { | ||||
| 			t.Fatalf("last_renewal_time was zero") | ||||
| 		} | ||||
| 	} else if _, ok := resp.Data["last_renewal_time"]; ok { | ||||
| 		t.Fatal("expected zero last renewal time") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
| 	testMakeTokenViaCore(t, c, root, "client", "3600s", []string{"foo"}) | ||||
| 	testMakeServiceTokenViaCore(t, c, root, "client", "3600s", []string{"foo"}) | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.ReadOperation, "lookup-self") | ||||
| 	req.ClientToken = "client" | ||||
| @@ -2085,6 +2285,7 @@ func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) { | ||||
| 		"ttl":              int64(3600), | ||||
| 		"explicit_max_ttl": int64(0), | ||||
| 		"entity_id":        "", | ||||
| 		"type":             "service", | ||||
| 	} | ||||
|  | ||||
| 	if resp.Data["creation_time"].(int64) == 0 { | ||||
| @@ -2257,6 +2458,7 @@ func TestTokenStore_RoleCRUD(t *testing.T) { | ||||
| 		"path_suffix":         "happenin", | ||||
| 		"explicit_max_ttl":    int64(0), | ||||
| 		"renewable":           true, | ||||
| 		"token_type":          "service", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(expected, resp.Data) { | ||||
| @@ -2302,6 +2504,7 @@ func TestTokenStore_RoleCRUD(t *testing.T) { | ||||
| 		"path_suffix":         "happenin", | ||||
| 		"explicit_max_ttl":    int64(0), | ||||
| 		"renewable":           false, | ||||
| 		"token_type":          "service", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(expected, resp.Data) { | ||||
| @@ -2339,6 +2542,7 @@ func TestTokenStore_RoleCRUD(t *testing.T) { | ||||
| 		"path_suffix":         "happenin", | ||||
| 		"period":              int64(0), | ||||
| 		"renewable":           false, | ||||
| 		"token_type":          "service", | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(expected, resp.Data) { | ||||
| @@ -2675,7 +2879,7 @@ func TestTokenStore_RolePathSuffix(t *testing.T) { | ||||
| 	c, _, root := TestCoreUnsealed(t) | ||||
| 	ts := c.tokenStore | ||||
|  | ||||
| 	req := logical.TestRequest(t, logical.UpdateOperation, "roles/test") | ||||
| 	req := logical.TestRequest(t, logical.CreateOperation, "roles/test") | ||||
| 	req.ClientToken = root | ||||
| 	req.Data = map[string]interface{}{ | ||||
| 		"path_suffix": "happenin", | ||||
| @@ -2690,6 +2894,7 @@ func TestTokenStore_RolePathSuffix(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	req.Path = "create/test" | ||||
| 	req.Operation = logical.UpdateOperation | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err: %v\nresp: %#v", err, resp) | ||||
| @@ -4138,6 +4343,8 @@ func TestTokenStore_TidyLeaseRevocation(t *testing.T) { | ||||
| 		Path:        "prod/aws/foo", | ||||
| 		ClientToken: tut, | ||||
| 	} | ||||
| 	req.SetTokenEntry(testTokenEntry) | ||||
|  | ||||
| 	resp = &logical.Response{ | ||||
| 		Secret: &logical.Secret{ | ||||
| 			LeaseOptions: logical.LeaseOptions{ | ||||
| @@ -4224,3 +4431,101 @@ func TestTokenStore_TidyLeaseRevocation(t *testing.T) { | ||||
| 		t.Fatal("found leases") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_Batch_CannotCreateChildren(t *testing.T) { | ||||
| 	var resp *logical.Response | ||||
|  | ||||
| 	core, _, root := TestCoreUnsealed(t) | ||||
| 	ts := core.tokenStore | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Path:        "create", | ||||
| 		ClientToken: root, | ||||
| 		Operation:   logical.UpdateOperation, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"policies": []string{"policy1"}, | ||||
| 			"type":     "batch", | ||||
| 		}, | ||||
| 	} | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) { | ||||
| 		t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies) | ||||
| 	} | ||||
|  | ||||
| 	req.ClientToken = resp.Auth.ClientToken | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("bad: expected error, got %#v", *resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_Batch_CannotRevoke(t *testing.T) { | ||||
| 	var resp *logical.Response | ||||
| 	var err error | ||||
|  | ||||
| 	core, _, root := TestCoreUnsealed(t) | ||||
| 	ts := core.tokenStore | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Path:        "create", | ||||
| 		ClientToken: root, | ||||
| 		Operation:   logical.UpdateOperation, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"policies": []string{"policy1"}, | ||||
| 			"type":     "batch", | ||||
| 		}, | ||||
| 	} | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) { | ||||
| 		t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies) | ||||
| 	} | ||||
|  | ||||
| 	req.Path = "revoke" | ||||
| 	req.Data["token"] = resp.Auth.ClientToken | ||||
| 	resp, err = ts.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("bad: expected error, got %#v", *resp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokenStore_Batch_NoCubbyhole(t *testing.T) { | ||||
| 	var resp *logical.Response | ||||
| 	var err error | ||||
|  | ||||
| 	core, _, root := TestCoreUnsealed(t) | ||||
| 	ts := core.tokenStore | ||||
|  | ||||
| 	req := &logical.Request{ | ||||
| 		Path:        "create", | ||||
| 		ClientToken: root, | ||||
| 		Operation:   logical.UpdateOperation, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"policies": []string{"policy1"}, | ||||
| 			"type":     "batch", | ||||
| 		}, | ||||
| 	} | ||||
| 	resp = testMakeTokenViaRequest(t, ts, req) | ||||
| 	if !reflect.DeepEqual(resp.Auth.Policies, []string{"default", "policy1"}) { | ||||
| 		t.Fatalf("bad: policies: expected: [policy, default]; actual: %s", resp.Auth.Policies) | ||||
| 	} | ||||
|  | ||||
| 	te, err := ts.Lookup(namespace.TestContext(), resp.Auth.ClientToken) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	req.Path = "cubbyhole/foo" | ||||
| 	req.Operation = logical.CreateOperation | ||||
| 	req.ClientToken = te.ID | ||||
| 	req.SetTokenEntry(te) | ||||
| 	resp, err = core.HandleRequest(namespace.TestContext(), req) | ||||
| 	if err != nil && !errwrap.Contains(err, logical.ErrInvalidRequest.Error()) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if !resp.IsError() { | ||||
| 		t.Fatalf("bad: expected error, got %#v", *resp) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,8 @@ in-memory). It is only made for development or experimentation. | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| The properties of the dev server: | ||||
| The properties of the dev server (some can be overridden with command line | ||||
| flags or by specifying a configuration file): | ||||
|  | ||||
|   * **Initialized and unsealed** - The server will be automatically initialized | ||||
|     and unsealed. You don't need to use `vault operator unseal`. It is ready | ||||
|   | ||||
| @@ -1,16 +1,15 @@ | ||||
| --- | ||||
| layout: "docs" | ||||
| page_title: "Basic Concepts" | ||||
| page_title: "Concepts" | ||||
| sidebar_current: "docs-concepts" | ||||
| description: |- | ||||
|   Basic concepts that are important to understand for Vault usage. | ||||
|   Concepts that are important to understand for Vault usage. | ||||
| --- | ||||
|  | ||||
| # Basic Concepts | ||||
| # Concepts | ||||
|  | ||||
| This section covers some high level basic concepts that are important | ||||
| to understand for day to day Vault usage and operation. Every page in | ||||
| this section is recommended reading for anyone consuming or operating | ||||
| Vault. | ||||
| This section covers some concepts that are important to understand for day to | ||||
| day Vault usage and operation. Every page in this section is recommended | ||||
| reading for anyone consuming or operating Vault. | ||||
|  | ||||
| Please use the navigation to the left to learn more about a topic. | ||||
|   | ||||
| @@ -8,12 +8,12 @@ description: |- | ||||
|  | ||||
| # Lease, Renew, and Revoke | ||||
|  | ||||
| With every dynamic secret and authentication token, Vault creates a _lease_: | ||||
| metadata containing information such as a time duration, renewability, and | ||||
| more. Vault promises that the data will be valid for the given duration, or | ||||
| Time To Live (TTL). Once the lease is expired, Vault can automatically revoke | ||||
| the data, and the consumer of the secret can no longer be certain that it is | ||||
| valid. | ||||
| With every dynamic secret and `service` type authentication token, Vault | ||||
| creates a _lease_: metadata containing information such as a time duration, | ||||
| renewability, and more. Vault promises that the data will be valid for the | ||||
| given duration, or Time To Live (TTL). Once the lease is expired, Vault can | ||||
| automatically revoke the data, and the consumer of the secret can no longer be | ||||
| certain that it is valid. | ||||
|  | ||||
| The benefit should be clear: consumers of secrets need to check in with | ||||
| Vault routinely to either renew the lease (if allowed) or request a | ||||
| @@ -35,7 +35,8 @@ or automatically by Vault. When a lease is expired, Vault will automatically | ||||
| revoke that lease. | ||||
|  | ||||
| **Note**: The [Key/Value Backend](/docs/secrets/kv/index.html) which stores | ||||
| arbitrary secrets does not issue leases. | ||||
| arbitrary secrets does not issue leases although it will sometimes return a | ||||
| lease duration; see the documentation for more information. | ||||
|  | ||||
| ## Lease IDs | ||||
|  | ||||
| @@ -61,14 +62,6 @@ so often. | ||||
| As a result, the return value of renewals should be carefully inspected to | ||||
| determine what the new lease is. | ||||
|  | ||||
| **Note**: Prior to version 0.3, Vault documentation and help text did not | ||||
| distinguish sufficiently between a _lease_ and a _lease duration_.  Starting | ||||
| with version 0.3, Vault will start migrating to the term _ttl_ to describe | ||||
| lease durations, at least for user-facing text. As _lease duration_ is still a | ||||
| legitimate (but more verbose) description, there are currently no plans to | ||||
| change the JSON key used in responses, in order to retain | ||||
| backwards-compatibility. | ||||
|  | ||||
| ## Prefix-based Revocation | ||||
|  | ||||
| In addition to revoking a single secret, operators with proper access control | ||||
|   | ||||
| @@ -31,9 +31,15 @@ renewal time, and more. | ||||
|  | ||||
| Read on for a deeper dive into token concepts. | ||||
|  | ||||
| ## Token Concepts | ||||
| ## Token Types | ||||
|  | ||||
| ### The Token Store | ||||
| As of Vault 1.0, there are two types of tokens: `service` tokens and `batch` | ||||
| tokens. A section near the bottom of this page contains detailed information | ||||
| about their differences, but it is useful to understand other token concepts | ||||
| first. The features in the following sections all apply to service tokens, and | ||||
| their applicability to batch tokens is discussed later. | ||||
|  | ||||
| ## The Token Store | ||||
|  | ||||
| Often in documentation or in help channels, the "token store" is referenced. | ||||
| This is the same as the [`token` authentication | ||||
| @@ -42,13 +48,13 @@ backend in that it is responsible for creating and storing tokens, and cannot | ||||
| be disabled. It is also the only auth method that has no login | ||||
| capability -- all actions require existing authenticated tokens. | ||||
|  | ||||
| ### Root Tokens | ||||
| ## Root Tokens | ||||
|  | ||||
| Root tokens are tokens that have the `root` policy attached to them. Root | ||||
| tokens can do anything in Vault. _Anything_. In addition, they are the only | ||||
| type of token within Vault that can be set to never expire without any renewal | ||||
| needed. As a result, it is purposefully hard to create root tokens; in fact, as | ||||
| of version 0.6.1, there are only three ways to create root tokens: | ||||
| needed. As a result, it is purposefully hard to create root tokens; in fact | ||||
| there are only three ways to create root tokens: | ||||
|  | ||||
| 1. The initial root token generated at `vault operator init` time -- this token has no | ||||
|    expiration | ||||
| @@ -70,7 +76,7 @@ whenever a root token is live. This way multiple people can verify as to the | ||||
| tasks performed with the root token, and that the token was revoked immediately | ||||
| after these tasks were completed. | ||||
|  | ||||
| ### Token Hierarchies and Orphan Tokens | ||||
| ## Token Hierarchies and Orphan Tokens | ||||
|  | ||||
| Normally, when a token holder creates new tokens, these tokens will be created | ||||
| as children of the original token; tokens they create will be children of them; | ||||
| @@ -93,7 +99,7 @@ endpoint, which revokes the given token but rather than revoke the rest of the | ||||
| tree, it instead sets the tokens' immediate children to be orphans. Use with | ||||
| caution! | ||||
|  | ||||
| ### Token Accessors | ||||
| ## Token Accessors | ||||
|  | ||||
| When tokens are created, a token accessor is also created and returned. This | ||||
| accessor is a value that acts as a reference to a token and can only be used to | ||||
| @@ -121,7 +127,7 @@ dangerous endpoint (since listing all of the accessors means that they can then | ||||
| be used to revoke all tokens), it also provides a way to audit and revoke the | ||||
| currently-active set of tokens. | ||||
|  | ||||
| ### Token Time-To-Live, Periodic Tokens, and Explicit Max TTLs | ||||
| ## Token Time-To-Live, Periodic Tokens, and Explicit Max TTLs | ||||
|  | ||||
| Every non-root token has a time-to-live (TTL) associated with it, which is a | ||||
| current period of validity since either the token's creation time or last | ||||
| @@ -137,7 +143,7 @@ token is a periodic token (available for creation by `root`/`sudo` users, token | ||||
| store roles, or some auth methods), has an explicit maximum TTL | ||||
| attached, or neither. | ||||
|  | ||||
| #### The General Case | ||||
| ### The General Case | ||||
|  | ||||
| In the general case, where there is neither a period nor explicit maximum TTL | ||||
| value set on the token, the token's lifetime since it was created will be | ||||
| @@ -165,7 +171,7 @@ current value and the client may want to reauthenticate and acquire a new | ||||
| token. However, outside of direct operator interaction, Vault will never revoke | ||||
| a token before the returned TTL has expired. | ||||
|  | ||||
| #### Explicit Max TTLs | ||||
| ### Explicit Max TTLs | ||||
|  | ||||
| Tokens can have an explicit max TTL set on them. This value becomes a hard | ||||
| limit on the token's lifetime -- no matter what the values in (1), (2), and (3) | ||||
| @@ -173,7 +179,7 @@ from the general case are, the token cannot live past this explicitly-set | ||||
| value. This has an effect even when using periodic tokens to escape the normal | ||||
| TTL mechanism. | ||||
|  | ||||
| #### Periodic Tokens | ||||
| ### Periodic Tokens | ||||
|  | ||||
| In some cases, having a token be revoked would be problematic -- for instance, | ||||
| if a long-running service needs to maintain its SQL connection pool over a long | ||||
| @@ -209,9 +215,65 @@ There are a few important things to know when using periodic tokens: | ||||
| * A token with both a period and an explicit max TTL will act like a periodic | ||||
|   token but will be revoked when the explicit max TTL is reached | ||||
|  | ||||
| ### CIDR-Bound Tokens | ||||
| ## CIDR-Bound Tokens | ||||
|  | ||||
| Some tokens are able to be bound to CIDR(s) that restrict the range of client | ||||
| IPs allowed to use them. These affect all tokens except for non-expiring root | ||||
| tokens (those with a TTL of zero). If a root token has an expiration, it also | ||||
| is affected by CIDR-binding. | ||||
|  | ||||
| ## Token Types in Detail | ||||
|  | ||||
| There are currently two types of tokens. | ||||
|  | ||||
| ### Service Tokens | ||||
|  | ||||
| Service tokens are what users will generally think of as "normal" Vault tokens. | ||||
| They support all features, such as renewal, revocation, creating child tokens, | ||||
| and more. The are correspondingly heavyweight to create and track. | ||||
|  | ||||
| ### Batch Tokens | ||||
|  | ||||
| Batch tokens are are encrypted blobs that carry enough information for them to | ||||
| be used for Vault actions, but they require no storage on disk to track them. | ||||
| As a result they are extremely lightweight and scalable, but lack most of the | ||||
| flexibility and features of service tokens. | ||||
|  | ||||
| ### Token Type Comparison | ||||
|  | ||||
| This reference chart describes the difference in behavior between service and | ||||
| batch tokens.  | ||||
|  | ||||
| |  | Service Tokens | Batch Tokens | | ||||
| |---|---:|---:| | ||||
| | Can Be Root Tokens | Yes | No | | ||||
| | Can Create Child Tokens | Yes | No | | ||||
| | Can be Renewable | Yes | No | | ||||
| | Can be Periodic | Yes | No | | ||||
| | Can have Explicit Max TTL | Yes | No (always uses a fixed TTL) | | ||||
| | Has Accessors | Yes | No | | ||||
| | Has Cubbyhole | Yes | No | | ||||
| | Revoked with Parent (if not orphan) | Yes | Stops Working | | ||||
| | Dynamic Secrets Lease Assignment | Self | Parent (if not orphan) | | ||||
| | Can be Used Across Performance Replication Clusters | No | Yes (if orphan) | | ||||
| | Creation Scales with Performance Standby Node Count | No | Yes | | ||||
| | Cost | Heavyweight; multiple storage writes per token creation | Lightweight; no storage cost for token creation | | ||||
|  | ||||
| ### Service vs. Batch Token Lease Handling | ||||
|  | ||||
| #### Service Tokens | ||||
|  | ||||
| Leases created by service tokens (including child tokens' leases) are tracked | ||||
| along with the service token and revoked when the token expires. | ||||
|  | ||||
| #### Batch Tokens | ||||
|  | ||||
| Leases created by batch tokens are constrained to the remaining TTL of the | ||||
| batch tokens and, if the batch token is not an orphan, are tracked by the | ||||
| parent. They are revoked when the batch token's TTL expires, or when the batch | ||||
| token's parent is revoked (at which point the batch token is also denied access | ||||
| to Vault). | ||||
|  | ||||
| As a corollary, batch tokens can be used across performance replication | ||||
| clusters, but only if they are orphan, since non-orphan tokens will not be able | ||||
| to ensure the validity of the parent token. | ||||
|   | ||||
| @@ -43,7 +43,7 @@ | ||||
|       </li> | ||||
|  | ||||
|       <li<%= sidebar_current("docs-concepts") %>> | ||||
|         <a href="/docs/concepts/index.html">Basic Concepts</a> | ||||
|         <a href="/docs/concepts/index.html">Concepts</a> | ||||
|         <ul class="nav"> | ||||
|           <li<%= sidebar_current("docs-concepts-devserver") %>> | ||||
|             <a href="/docs/concepts/dev-server.html">"Dev" Server</a> | ||||
| @@ -63,6 +63,32 @@ | ||||
|  | ||||
|           <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|             <a href="/docs/concepts/tokens.html">Tokens</a> | ||||
|             <ul class="nav"> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#token-types">Token Types</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#the-token-store">The Token Store</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#root-tokens">Root Tokens</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#token-hierarchies-and-orphan-tokens">Token Hierarchies and Orphan Tokens</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#token-accessors">Token Accessors</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#token-time-to-live-periodic-tokens-and-explicit-max-ttls">Token Time-To-Live, Periodic Tokens, and Explicit Max TTLs</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#cidr-bound-tokens">CIDR-Bound Tokens</a> | ||||
|               </li> | ||||
|               <li<%= sidebar_current("docs-concepts-tokens") %>> | ||||
|                 <a href="/docs/concepts/tokens.html#token-types-in-detail">Token Types in Detail</a> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </li> | ||||
|  | ||||
|           <li<%= sidebar_current("docs-concepts-response-wrapping") %>> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jeff Mitchell
					Jeff Mitchell