From 8910915c86013a2566d452cf63f3ecfddb28bce6 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Sat, 2 Sep 2017 18:48:48 -0400 Subject: [PATCH] Add API functions for token helpers --- api/api_integration_test.go | 110 +- api/renewer_integration_test.go | 10 +- api/secret.go | 197 ++++ api/secret_test.go | 1725 ++++++++++++++++++++++++++++++- 4 files changed, 2010 insertions(+), 32 deletions(-) diff --git a/api/api_integration_test.go b/api/api_integration_test.go index c4e1a1d80..a9e4409ae 100644 --- a/api/api_integration_test.go +++ b/api/api_integration_test.go @@ -1,59 +1,131 @@ package api_test import ( + "context" "database/sql" + "encoding/base64" "fmt" + "net" + "net/http" "testing" + "time" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/audit" + "github.com/hashicorp/vault/builtin/logical/database" "github.com/hashicorp/vault/builtin/logical/pki" "github.com/hashicorp/vault/builtin/logical/transit" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" + auditFile "github.com/hashicorp/vault/builtin/audit/file" + credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" vaulthttp "github.com/hashicorp/vault/http" logxi "github.com/mgutz/logxi/v1" dockertest "gopkg.in/ory-am/dockertest.v3" ) -var testVaultServerDefaultBackends = map[string]logical.Factory{ - "transit": transit.Factory, - "pki": pki.Factory, -} - +// testVaultServer creates a test vault cluster and returns a configured API +// client and closer function. func testVaultServer(t testing.TB) (*api.Client, func()) { - return testVaultServerBackends(t, testVaultServerDefaultBackends) + t.Helper() + + client, _, closer := testVaultServerUnseal(t) + return client, closer } -func testVaultServerBackends(t testing.TB, backends map[string]logical.Factory) (*api.Client, func()) { - coreConfig := &vault.CoreConfig{ - DisableMlock: true, - DisableCache: true, - Logger: logxi.NullLog, - LogicalBackends: backends, - } +// testVaultServerUnseal creates a test vault cluster and returns a configured +// API client, list of unseal keys (as strings), and a closer function. +func testVaultServerUnseal(t testing.TB) (*api.Client, []string, func()) { + t.Helper() + + return testVaultServerCoreConfig(t, &vault.CoreConfig{ + DisableMlock: true, + DisableCache: true, + Logger: logxi.NullLog, + CredentialBackends: map[string]logical.Factory{ + "userpass": credUserpass.Factory, + }, + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + LogicalBackends: map[string]logical.Factory{ + "database": database.Factory, + "generic-leased": vault.LeasedPassthroughBackendFactory, + "pki": pki.Factory, + "transit": transit.Factory, + }, + }) +} + +// testVaultServerCoreConfig creates a new vault cluster with the given core +// configuration. This is a lower-level test helper. +func testVaultServerCoreConfig(t testing.TB, coreConfig *vault.CoreConfig) (*api.Client, []string, func()) { + t.Helper() cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ HandlerFunc: vaulthttp.Handler, }) cluster.Start() - // make it easy to get access to the active + // Make it easy to get access to the active core := cluster.Cores[0].Core vault.TestWaitActive(t, core) + // Get the client already setup for us! client := cluster.Cores[0].Client client.SetToken(cluster.RootToken) - // Sanity check - secret, err := client.Auth().Token().LookupSelf() + // Convert the unseal keys to base64 encoded, since these are how the user + // will get them. + unsealKeys := make([]string, len(cluster.BarrierKeys)) + for i := range unsealKeys { + unsealKeys[i] = base64.StdEncoding.EncodeToString(cluster.BarrierKeys[i]) + } + + return client, unsealKeys, func() { defer cluster.Cleanup() } +} + +// testVaultServerBad creates an http server that returns a 500 on each request +// to simulate failures. +func testVaultServerBad(t testing.TB) (*api.Client, func()) { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } - if secret == nil || secret.Data["id"].(string) != cluster.RootToken { - t.Fatalf("token mismatch: %#v vs %q", secret, cluster.RootToken) + + server := &http.Server{ + Addr: "127.0.0.1:0", + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "500 internal server error", http.StatusInternalServerError) + }), + ReadTimeout: 1 * time.Second, + ReadHeaderTimeout: 1 * time.Second, + WriteTimeout: 1 * time.Second, + IdleTimeout: 1 * time.Second, + } + + go func() { + if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { + t.Fatal(err) + } + }() + + client, err := api.NewClient(&api.Config{ + Address: "http://" + listener.Addr().String(), + }) + if err != nil { + t.Fatal(err) + } + + return client, func() { + ctx, done := context.WithTimeout(context.Background(), 5*time.Second) + defer done() + + server.Shutdown(ctx) } - return client, func() { defer cluster.Cleanup() } } // testPostgresDB creates a testing postgres database in a Docker container, diff --git a/api/renewer_integration_test.go b/api/renewer_integration_test.go index 7011c7d10..50a775e12 100644 --- a/api/renewer_integration_test.go +++ b/api/renewer_integration_test.go @@ -5,20 +5,12 @@ import ( "time" "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/builtin/logical/database" - "github.com/hashicorp/vault/builtin/logical/pki" - "github.com/hashicorp/vault/builtin/logical/transit" - "github.com/hashicorp/vault/logical" ) func TestRenewer_Renew(t *testing.T) { t.Parallel() - client, vaultDone := testVaultServerBackends(t, map[string]logical.Factory{ - "database": database.Factory, - "pki": pki.Factory, - "transit": transit.Factory, - }) + client, vaultDone := testVaultServer(t) defer vaultDone() pgURL, pgDone := testPostgresDB(t) diff --git a/api/secret.go b/api/secret.go index 7478a0c54..86117029d 100644 --- a/api/secret.go +++ b/api/secret.go @@ -1,7 +1,9 @@ package api import ( + "encoding/json" "io" + "strconv" "time" "github.com/hashicorp/vault/helper/jsonutil" @@ -35,6 +37,201 @@ type Secret struct { WrapInfo *SecretWrapInfo `json:"wrap_info,omitempty"` } +// TokenID returns the standardized token ID (token) for the given secret. +func (s *Secret) TokenID() string { + if s == nil { + return "" + } + + if s.Auth != nil && len(s.Auth.ClientToken) > 0 { + return s.Auth.ClientToken + } + + if s.Data == nil || s.Data["id"] == nil { + return "" + } + + id, ok := s.Data["id"].(string) + if !ok { + return "" + } + + return id +} + +// TokenAccessor returns the standardized token accessor for the given secret. +// If the secret is nil or does not contain an accessor, this returns the empty +// string. +func (s *Secret) TokenAccessor() string { + if s == nil { + return "" + } + + if s.Auth != nil && len(s.Auth.Accessor) > 0 { + return s.Auth.Accessor + } + + if s.Data == nil || s.Data["accessor"] == nil { + return "" + } + + accessor, ok := s.Data["accessor"].(string) + if !ok { + return "" + } + + return accessor +} + +// TokenMeta returns the standardized token metadata for the given secret. +// If the secret is nil or does not contain an accessor, this returns the empty +// string. Metadata is usually modeled as an map[string]interface{}, but token +// metdata is always a map[string]string. This function handles the coercion. +func (s *Secret) TokenMeta() map[string]string { + if s == nil { + return nil + } + + if s.Auth != nil && len(s.Auth.Metadata) > 0 { + return s.Auth.Metadata + } + + if s.Data == nil || s.Data["meta"] == nil { + return nil + } + + metaRaw, ok := s.Data["meta"].(map[string]interface{}) + if !ok { + return nil + } + + meta := make(map[string]string, len(metaRaw)) + for k, v := range metaRaw { + m, ok := v.(string) + if !ok { + return nil + } + meta[k] = m + } + + return meta +} + +// TokenRemainingUses returns the standardized remaining uses for the given +// secret. If the secret is nil or does not contain the "num_uses", this returns +// 0.. +func (s *Secret) TokenRemainingUses() int { + if s == nil || s.Data == nil || s.Data["num_uses"] == nil { + return 0 + } + + usesStr, ok := s.Data["num_uses"].(json.Number) + if !ok { + return 0 + } + + if string(usesStr) == "" { + return 0 + } + + uses, err := strconv.ParseInt(string(usesStr), 10, 64) + if err != nil { + return 0 + } + + return int(uses) +} + +// TokenPolicies returns the standardized list of policies for the given secret. +// If the secret is nil or does not contain any policies, this returns nil. +// Policies are usually returned as []interface{}, but this function ensures +// they are []string. +func (s *Secret) TokenPolicies() []string { + if s == nil { + return nil + } + + if s.Auth != nil && len(s.Auth.Policies) > 0 { + return s.Auth.Policies + } + + if s.Data == nil || s.Data["policies"] == nil { + return nil + } + + list, ok := s.Data["policies"].([]interface{}) + if !ok { + return nil + } + + policies := make([]string, len(list)) + for i := range list { + p, ok := list[i].(string) + if !ok { + return nil + } + policies[i] = p + } + + return policies +} + +// TokenIsRenewable returns the standardized token renewability for the given +// secret. If the secret is nil or does not contain the "renewable" key, this +// returns false. +func (s *Secret) TokenIsRenewable() bool { + if s == nil { + return false + } + + if s.Auth != nil && s.Auth.Renewable { + return s.Auth.Renewable + } + + if s.Data == nil || s.Data["renewable"] == nil { + return false + } + + renewable, ok := s.Data["renewable"].(bool) + if !ok { + return false + } + + return renewable +} + +// TokenTTL returns the standardized remaining token TTL for the given secret. +// If the secret is nil or does not contain a TTL, this returns the 0. +func (s *Secret) TokenTTL() time.Duration { + if s == nil { + return 0 + } + + if s.Auth != nil && s.Auth.LeaseDuration > 0 { + return time.Duration(s.Auth.LeaseDuration) * time.Second + } + + if s.Data == nil || s.Data["ttl"] == nil { + return 0 + } + + ttlStr, ok := s.Data["ttl"].(json.Number) + if !ok { + return 0 + } + + if string(ttlStr) == "" { + return 0 + } + + ttl, err := time.ParseDuration(string(ttlStr) + "s") + if err != nil { + return 0 + } + + return ttl +} + // SecretWrapInfo contains wrapping information if we have it. If what is // contained is an authentication token, the accessor for the token will be // available in WrappedAccessor. diff --git a/api/secret_test.go b/api/secret_test.go index 3b6496677..4e4c8c3ae 100644 --- a/api/secret_test.go +++ b/api/secret_test.go @@ -1,10 +1,13 @@ -package api +package api_test import ( + "encoding/json" "reflect" "strings" "testing" "time" + + "github.com/hashicorp/vault/api" ) func TestParseSecret(t *testing.T) { @@ -29,12 +32,12 @@ func TestParseSecret(t *testing.T) { rawTime, _ := time.Parse(time.RFC3339, "2016-06-07T15:52:10-04:00") - secret, err := ParseSecret(strings.NewReader(raw)) + secret, err := api.ParseSecret(strings.NewReader(raw)) if err != nil { t.Fatalf("err: %s", err) } - expected := &Secret{ + expected := &api.Secret{ LeaseID: "foo", Renewable: true, LeaseDuration: 10, @@ -44,7 +47,7 @@ func TestParseSecret(t *testing.T) { Warnings: []string{ "a warning!", }, - WrapInfo: &SecretWrapInfo{ + WrapInfo: &api.SecretWrapInfo{ Token: "token", TTL: 60, CreationTime: rawTime, @@ -55,3 +58,1717 @@ func TestParseSecret(t *testing.T) { t.Fatalf("bad:\ngot\n%#v\nexpected\n%#v\n", secret, expected) } } + +func TestSecret_TokenID(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp string + }{ + { + "nil", + nil, + "", + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + "", + }, + { + "empty_auth_client_token", + &api.Secret{ + Auth: &api.SecretAuth{ + ClientToken: "", + }, + }, + "", + }, + { + "real_auth_client_token", + &api.Secret{ + Auth: &api.SecretAuth{ + ClientToken: "my-token", + }, + }, + "my-token", + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + "", + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + "", + }, + { + "data_not_string", + &api.Secret{ + Data: map[string]interface{}{ + "id": 123, + }, + }, + "", + }, + { + "data_string", + &api.Secret{ + Data: map[string]interface{}{ + "id": "my-token", + }, + }, + "my-token", + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenID() + if act != tc.exp { + t.Errorf("expected %q to be %q", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenID() != token { + t.Errorf("expected %q to be %q", secret.TokenID(), token) + } + }) +} + +func TestSecret_TokenAccessor(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp string + }{ + { + "nil", + nil, + "", + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + "", + }, + { + "empty_auth_accessor", + &api.Secret{ + Auth: &api.SecretAuth{ + Accessor: "", + }, + }, + "", + }, + { + "real_auth_accessor", + &api.Secret{ + Auth: &api.SecretAuth{ + Accessor: "my-accessor", + }, + }, + "my-accessor", + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + "", + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + "", + }, + { + "data_not_string", + &api.Secret{ + Data: map[string]interface{}{ + "accessor": 123, + }, + }, + "", + }, + { + "data_string", + &api.Secret{ + Data: map[string]interface{}{ + "accessor": "my-accessor", + }, + }, + "my-accessor", + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenAccessor() + if act != tc.exp { + t.Errorf("expected %q to be %q", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + _, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + _, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + token, accessor := secret.Auth.ClientToken, secret.Auth.Accessor + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenAccessor() != accessor { + t.Errorf("expected %q to be %q", secret.TokenAccessor(), accessor) + } + }) +} + +func TestSecret_TokenMeta(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp map[string]string + }{ + { + "nil", + nil, + nil, + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + nil, + }, + { + "nil_auth_metadata", + &api.Secret{ + Auth: &api.SecretAuth{ + Metadata: nil, + }, + }, + nil, + }, + { + "empty_auth_metadata", + &api.Secret{ + Auth: &api.SecretAuth{ + Metadata: map[string]string{}, + }, + }, + nil, + }, + { + "real_auth_metadata", + &api.Secret{ + Auth: &api.SecretAuth{ + Metadata: map[string]string{"foo": "bar"}, + }, + }, + map[string]string{"foo": "bar"}, + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + nil, + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + nil, + }, + { + "data_not_map", + &api.Secret{ + Data: map[string]interface{}{ + "meta": 123, + }, + }, + nil, + }, + { + "data_map", + &api.Secret{ + Data: map[string]interface{}{ + "meta": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + map[string]string{"foo": "bar"}, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenMeta() + if !reflect.DeepEqual(act, tc.exp) { + t.Errorf("expected %#v to be %#v", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + + meta := map[string]string{"username": "test"} + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + meta := map[string]string{"foo": "bar"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Metadata: meta, + }) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + meta := map[string]string{"foo": "bar"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Metadata: meta, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + meta := map[string]string{"foo": "bar"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Metadata: meta, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + meta := map[string]string{"foo": "bar"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Metadata: meta, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + meta := map[string]string{"foo": "bar"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Metadata: meta, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenMeta(), meta) { + t.Errorf("expected %#v to be %#v", secret.TokenMeta(), meta) + } + }) +} + +func TestSecret_TokenRemainingUses(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp int + }{ + { + "nil", + nil, + 0, + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + 0, + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + 0, + }, + { + "data_not_json_number", + &api.Secret{ + Data: map[string]interface{}{ + "num_uses": 123, + }, + }, + 0, + }, + { + "data_json_number", + &api.Secret{ + Data: map[string]interface{}{ + "num_uses": json.Number("123"), + }, + }, + 123, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenRemainingUses() + if act != tc.exp { + t.Errorf("expected %d to be %d", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + "num_uses": uses, + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + + // Remaining uses is not returned from this API + uses = 0 + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + NumUses: uses, + }) + if err != nil { + t.Fatal(err) + } + + // /auth/token/create does not return the number of uses + uses = 0 + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + NumUses: uses, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + NumUses: uses, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + uses = uses - 1 // we just used it + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + NumUses: uses, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + // /auth/token/renew does not return the number of uses + uses = 0 + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + uses := 5 + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + NumUses: uses, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + // /auth/token/renew-self does not return the number of uses + uses = 0 + if secret.TokenRemainingUses() != uses { + t.Errorf("expected %d to be %d", secret.TokenRemainingUses(), uses) + } + }) +} + +func TestSecret_TokenPolicies(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp []string + }{ + { + "nil", + nil, + nil, + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + nil, + }, + { + "nil_auth_policies", + &api.Secret{ + Auth: &api.SecretAuth{ + Policies: nil, + }, + }, + nil, + }, + { + "empty_auth_policies", + &api.Secret{ + Auth: &api.SecretAuth{ + Policies: []string{}, + }, + }, + nil, + }, + { + "real_auth_policies", + &api.Secret{ + Auth: &api.SecretAuth{ + Policies: []string{"foo"}, + }, + }, + []string{"foo"}, + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + nil, + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + nil, + }, + { + "data_not_slice", + &api.Secret{ + Data: map[string]interface{}{ + "policies": 123, + }, + }, + nil, + }, + { + "data_slice", + &api.Secret{ + Data: map[string]interface{}{ + "policies": []interface{}{"foo"}, + }, + }, + []string{"foo"}, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenPolicies() + if !reflect.DeepEqual(act, tc.exp) { + t.Errorf("expected %#v to be %#v", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": strings.Join(policies, ","), + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: policies, + }) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: policies, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: policies, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: policies, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + policies := []string{"bar", "default", "foo"} + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: policies, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(secret.TokenPolicies(), policies) { + t.Errorf("expected %#v to be %#v", secret.TokenPolicies(), policies) + } + }) +} + +func TestSecret_TokenIsRenewable(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp bool + }{ + { + "nil", + nil, + false, + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + false, + }, + { + "auth_renewable_false", + &api.Secret{ + Auth: &api.SecretAuth{ + Renewable: false, + }, + }, + false, + }, + { + "auth_renewable_true", + &api.Secret{ + Auth: &api.SecretAuth{ + Renewable: true, + }, + }, + true, + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + false, + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + false, + }, + { + "data_not_bool", + &api.Secret{ + Data: map[string]interface{}{ + "renewable": 123, + }, + }, + false, + }, + { + "data_bool_true", + &api.Secret{ + Data: map[string]interface{}{ + "renewable": true, + }, + }, + true, + }, + { + "data_bool_false", + &api.Secret{ + Data: map[string]interface{}{ + "renewable": true, + }, + }, + true, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenIsRenewable() + if act != tc.exp { + t.Errorf("expected %t to be %t", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Renewable: &renewable, + }) + if err != nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Renewable: &renewable, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Renewable: &renewable, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Renewable: &renewable, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + renewable := true + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + Renewable: &renewable, + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenIsRenewable() != renewable { + t.Errorf("expected %t to be %t", secret.TokenIsRenewable(), renewable) + } + }) +} + +func TestSecret_TokenTTL(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + secret *api.Secret + exp time.Duration + }{ + { + "nil", + nil, + 0, + }, + { + "nil_auth", + &api.Secret{ + Auth: nil, + }, + 0, + }, + { + "nil_auth_lease_duration", + &api.Secret{ + Auth: &api.SecretAuth{ + LeaseDuration: 0, + }, + }, + 0, + }, + { + "real_auth_lease_duration", + &api.Secret{ + Auth: &api.SecretAuth{ + LeaseDuration: 3600, + }, + }, + 1 * time.Hour, + }, + { + "nil_data", + &api.Secret{ + Data: nil, + }, + 0, + }, + { + "empty_data", + &api.Secret{ + Data: map[string]interface{}{}, + }, + 0, + }, + { + "data_not_json_number", + &api.Secret{ + Data: map[string]interface{}{ + "ttl": 123, + }, + }, + 0, + }, + { + "data_json_number", + &api.Secret{ + Data: map[string]interface{}{ + "ttl": json.Number("3600"), + }, + }, + 1 * time.Hour, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + act := tc.secret.TokenTTL() + if act != tc.exp { + t.Errorf("expected %q to be %q", act, tc.exp) + } + }) + } + + t.Run("auth", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + if err := client.Sys().EnableAuth("userpass", "userpass", ""); err != nil { + t.Fatal(err) + } + if _, err := client.Logical().Write("auth/userpass/users/test", map[string]interface{}{ + "password": "test", + "policies": "default", + "ttl": ttl.String(), + "explicit_max_ttl": ttl.String(), + }); err != nil { + t.Fatal(err) + } + + secret, err := client.Logical().Write("auth/userpass/login/test", map[string]interface{}{ + "password": "test", + }) + if err != nil || secret == nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) + + t.Run("token-create", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: ttl.String(), + ExplicitMaxTTL: ttl.String(), + }) + if err != nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) + + t.Run("token-lookup", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: ttl.String(), + ExplicitMaxTTL: ttl.String(), + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Lookup(token) + if err != nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) + + t.Run("token-lookup-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: ttl.String(), + ExplicitMaxTTL: ttl.String(), + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().LookupSelf() + if err != nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) + + t.Run("token-renew", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: ttl.String(), + ExplicitMaxTTL: ttl.String(), + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + secret, err = client.Auth().Token().Renew(token, 0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) + + t.Run("token-renew-self", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ttl := 30 * time.Minute + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: ttl.String(), + ExplicitMaxTTL: ttl.String(), + }) + if err != nil { + t.Fatal(err) + } + token := secret.Auth.ClientToken + + client.SetToken(token) + secret, err = client.Auth().Token().RenewSelf(0) + if err != nil { + t.Fatal(err) + } + + if secret.TokenTTL() == 0 || secret.TokenTTL() > ttl { + t.Errorf("expected %q to non-zero and less than %q", secret.TokenTTL(), ttl) + } + }) +}