Add API functions for token helpers
This commit is contained in:
parent
0f2905fd7c
commit
8910915c86
|
@ -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.
|
||||||
|
func testVaultServerUnseal(t testing.TB) (*api.Client, []string, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return testVaultServerCoreConfig(t, &vault.CoreConfig{
|
||||||
DisableMlock: true,
|
DisableMlock: true,
|
||||||
DisableCache: true,
|
DisableCache: true,
|
||||||
Logger: logxi.NullLog,
|
Logger: logxi.NullLog,
|
||||||
LogicalBackends: backends,
|
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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
197
api/secret.go
197
api/secret.go
|
@ -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.
|
||||||
|
|
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