Batch tokens (#755)

This commit is contained in:
Jeff Mitchell
2018-10-15 12:56:24 -04:00
committed by GitHub
parent 8db6aabee1
commit a58d313d2b
51 changed files with 2170 additions and 570 deletions

View File

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

View File

@@ -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

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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),
},
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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

View File

@@ -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 (

View File

@@ -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

View File

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

View File

@@ -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 {

View File

@@ -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,
})

View File

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

View File

@@ -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,
}

View File

@@ -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"]

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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,
}

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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() {

View File

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

View File

@@ -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{

View File

@@ -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)

View File

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

View File

@@ -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 {

View 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)
}
}

View File

@@ -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{

View File

@@ -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.",
"",

View File

@@ -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{

View File

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

View File

@@ -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

View File

@@ -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)

View File

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

View File

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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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") %>>