Add API functions for token helpers

This commit is contained in:
Seth Vargo 2017-09-02 18:48:48 -04:00
parent 0f2905fd7c
commit 8910915c86
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
4 changed files with 2010 additions and 32 deletions

View File

@ -1,59 +1,131 @@
package api_test package api_test
import ( import (
"context"
"database/sql" "database/sql"
"encoding/base64"
"fmt" "fmt"
"net"
"net/http"
"testing" "testing"
"time"
"github.com/hashicorp/vault/api" "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/pki"
"github.com/hashicorp/vault/builtin/logical/transit" "github.com/hashicorp/vault/builtin/logical/transit"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault" "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" vaulthttp "github.com/hashicorp/vault/http"
logxi "github.com/mgutz/logxi/v1" logxi "github.com/mgutz/logxi/v1"
dockertest "gopkg.in/ory-am/dockertest.v3" dockertest "gopkg.in/ory-am/dockertest.v3"
) )
var testVaultServerDefaultBackends = map[string]logical.Factory{ // testVaultServer creates a test vault cluster and returns a configured API
"transit": transit.Factory, // client and closer function.
"pki": pki.Factory,
}
func testVaultServer(t testing.TB) (*api.Client, func()) { 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()) { // testVaultServerUnseal creates a test vault cluster and returns a configured
coreConfig := &vault.CoreConfig{ // API client, list of unseal keys (as strings), and a closer function.
DisableMlock: true, func testVaultServerUnseal(t testing.TB) (*api.Client, []string, func()) {
DisableCache: true, t.Helper()
Logger: logxi.NullLog,
LogicalBackends: backends, 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{ cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler, HandlerFunc: vaulthttp.Handler,
}) })
cluster.Start() 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 core := cluster.Cores[0].Core
vault.TestWaitActive(t, core) vault.TestWaitActive(t, core)
// Get the client already setup for us!
client := cluster.Cores[0].Client client := cluster.Cores[0].Client
client.SetToken(cluster.RootToken) client.SetToken(cluster.RootToken)
// Sanity check // Convert the unseal keys to base64 encoded, since these are how the user
secret, err := client.Auth().Token().LookupSelf() // 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 { if err != nil {
t.Fatal(err) 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, // testPostgresDB creates a testing postgres database in a Docker container,

View File

@ -5,20 +5,12 @@ import (
"time" "time"
"github.com/hashicorp/vault/api" "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) { func TestRenewer_Renew(t *testing.T) {
t.Parallel() t.Parallel()
client, vaultDone := testVaultServerBackends(t, map[string]logical.Factory{ client, vaultDone := testVaultServer(t)
"database": database.Factory,
"pki": pki.Factory,
"transit": transit.Factory,
})
defer vaultDone() defer vaultDone()
pgURL, pgDone := testPostgresDB(t) pgURL, pgDone := testPostgresDB(t)

View File

@ -1,7 +1,9 @@
package api package api
import ( import (
"encoding/json"
"io" "io"
"strconv"
"time" "time"
"github.com/hashicorp/vault/helper/jsonutil" "github.com/hashicorp/vault/helper/jsonutil"
@ -35,6 +37,201 @@ type Secret struct {
WrapInfo *SecretWrapInfo `json:"wrap_info,omitempty"` 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 // SecretWrapInfo contains wrapping information if we have it. If what is
// contained is an authentication token, the accessor for the token will be // contained is an authentication token, the accessor for the token will be
// available in WrappedAccessor. // available in WrappedAccessor.

File diff suppressed because it is too large Load Diff