Add API functions for token helpers
This commit is contained in:
parent
0f2905fd7c
commit
8910915c86
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
197
api/secret.go
197
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.
|
||||
|
|
1725
api/secret_test.go
1725
api/secret_test.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue