You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/credential/vault/testing_test.go

543 lines
15 KiB

package vault
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"path"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/go-secure-stdlib/parseutil"
vault "github.com/hashicorp/vault/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_TestCredentialStores(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
count := 4
css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), count)
assert.Len(css, count)
for _, cs := range css {
assert.NotEmpty(cs.GetPublicId())
assert.NotNil(cs.Token())
assert.NotNil(cs.ClientCertificate())
}
}
func Test_TestCredentialLibraries(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
require.NotNil(prj)
assert.NotEmpty(prj.GetPublicId())
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
count := 4
libs := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), count)
assert.Len(libs, count)
for _, lib := range libs {
assert.NotEmpty(lib.GetPublicId())
}
}
func testLogVaultSecret(t *testing.T, v *vault.Secret) string {
t.Helper()
require := require.New(t)
require.NotNil(v)
b, err := json.MarshalIndent(v, "", " ")
require.NoError(err)
require.NotEmpty(b)
return string(b)
}
func TestTestVaultServer_CreateToken(t *testing.T) {
t.Parallel()
assertIsRenewable := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsRenewable"
return func(t *testing.T, s *vault.Secret) {
isRenewable, err := s.TokenIsRenewable()
assert.NoError(t, err, op)
assert.True(t, isRenewable, op)
assert.Equal(t, isRenewable, s.Auth.Renewable, op)
}
}
assertIsNotRenewable := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsNotRenewable"
return func(t *testing.T, s *vault.Secret) {
isRenewable, err := s.TokenIsRenewable()
assert.NoError(t, err, op)
assert.False(t, isRenewable, op)
assert.Equal(t, isRenewable, s.Auth.Renewable, op)
}
}
assertIsOrphan := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsOrphan"
return func(t *testing.T, s *vault.Secret) {
assert.True(t, s.Auth.Orphan, op)
}
}
assertIsNotOrphan := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsNotOrphan"
return func(t *testing.T, s *vault.Secret) {
assert.False(t, s.Auth.Orphan, op)
}
}
assertIsPeriodic := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsPeriodic"
return func(t *testing.T, s *vault.Secret) {
_, ok := s.Data["period"]
assert.True(t, ok, op)
}
}
assertIsNotPeriodic := func() func(t *testing.T, s *vault.Secret) {
const op = "assertIsNotPeriodic"
return func(t *testing.T, s *vault.Secret) {
_, ok := s.Data["period"]
assert.False(t, ok, op)
}
}
assertPeriod := func(want time.Duration) func(t *testing.T, s *vault.Secret) {
const op = "assertPeriod"
return func(t *testing.T, s *vault.Secret) {
period, ok := s.Data["period"]
if assert.True(t, ok, op) {
require.NotNil(t, period)
gotPeriod, err := parseutil.ParseDurationSecond(period)
require.NoError(t, err)
if assert.True(t, ok, op) {
delta := 1 * time.Minute
assert.InDelta(t, want.Seconds(), gotPeriod.Seconds(), delta.Seconds())
}
}
}
}
assertDefaultPeriod := func() func(t *testing.T, s *vault.Secret) {
defaultPeriod := 24 * time.Hour
if deadline, ok := t.Deadline(); ok {
defaultPeriod = time.Until(deadline)
}
return assertPeriod(defaultPeriod)
}
combine := func(fns ...func(*testing.T, *vault.Secret)) func(*testing.T, *vault.Secret) {
return func(t *testing.T, s *vault.Secret) {
for _, fn := range fns {
fn(t, s)
}
}
}
tests := []struct {
name string
opts []TestOption
tokenChkFn func(t *testing.T, token *vault.Secret)
lookupChkFn func(t *testing.T, lookup *vault.Secret)
}{
{
name: "DefaultOptions",
tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()),
lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()),
},
{
name: "NotPeriodic",
opts: []TestOption{TestPeriodicToken(false)},
tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()),
lookupChkFn: assertIsNotPeriodic(),
},
{
name: "NotOrphan",
opts: []TestOption{TestOrphanToken(false)},
tokenChkFn: combine(assertIsRenewable(), assertIsNotOrphan()),
lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()),
},
{
name: "NotRenewable",
opts: []TestOption{TestRenewableToken(false)},
tokenChkFn: combine(assertIsNotRenewable(), assertIsOrphan()),
lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()),
},
{
name: "TokenPeriod",
opts: []TestOption{WithTokenPeriod(3 * time.Hour)},
tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()),
lookupChkFn: combine(assertIsPeriodic(), assertPeriod(3*time.Hour)),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t)
require.NotNil(v)
secret, token := v.CreateToken(t, tt.opts...)
require.NotNil(secret)
require.NotEmpty(token)
require.Equal(token, secret.Auth.ClientToken)
t.Log(testLogVaultSecret(t, secret))
// token sanity check
t2, err := secret.TokenID()
require.NoError(err)
assert.NotEmpty(t2)
require.Equal(token, t2)
require.Equal(t2, secret.Auth.ClientToken)
if tt.tokenChkFn != nil {
tt.tokenChkFn(t, secret)
}
tokenLookup := v.LookupToken(t, token)
t.Log(testLogVaultSecret(t, tokenLookup))
if tt.lookupChkFn != nil {
tt.lookupChkFn(t, tokenLookup)
}
})
}
}
func TestNewVaultServer(t *testing.T) {
t.Parallel()
t.Run("TestNoTLS", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS))
require.NotNil(v)
assert.NotEmpty(v.RootToken)
assert.NotEmpty(v.Addr)
conf := &clientConfig{
Addr: v.Addr,
Token: TokenSecret(v.RootToken),
}
client, err := newClient(conf)
require.NoError(err)
require.NotNil(client)
require.NoError(client.ping())
})
t.Run("TestServerTLS", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS))
require.NotNil(v)
assert.NotEmpty(v.RootToken)
assert.NotEmpty(v.Addr)
assert.NotEmpty(v.CaCert)
conf := &clientConfig{
Addr: v.Addr,
Token: TokenSecret(v.RootToken),
CaCert: v.CaCert,
}
client, err := newClient(conf)
require.NoError(err)
require.NotNil(client)
require.NoError(client.ping())
})
t.Run("TestClientTLS", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS))
require.NotNil(v)
assert.NotEmpty(v.RootToken)
assert.NotEmpty(v.Addr)
assert.NotEmpty(v.CaCert)
assert.NotEmpty(v.ClientCert)
assert.NotEmpty(v.ClientKey)
conf := &clientConfig{
Addr: v.Addr,
Token: TokenSecret(v.RootToken),
CaCert: v.CaCert,
ClientCert: v.ClientCert,
ClientKey: v.ClientKey,
}
client, err := newClient(conf)
require.NoError(err)
require.NotNil(client)
require.NoError(client.ping())
})
t.Run("TestClientTLS-with-client-key", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(err)
v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS), WithClientKey(key))
require.NotNil(v)
assert.NotEmpty(v.RootToken)
assert.NotEmpty(v.Addr)
assert.NotEmpty(v.CaCert)
assert.NotEmpty(v.ClientCert)
assert.NotEmpty(v.ClientKey)
k, err := x509.MarshalECPrivateKey(key)
require.NoError(err)
assert.Equal(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: k}), v.ClientKey)
conf := &clientConfig{
Addr: v.Addr,
Token: TokenSecret(v.RootToken),
CaCert: v.CaCert,
ClientCert: v.ClientCert,
ClientKey: v.ClientKey,
}
client, err := newClient(conf)
require.NoError(err)
require.NotNil(client)
require.NoError(client.ping())
})
}
func TestTestVaultServer_MountPKI(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS))
require.NotNil(v)
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
beforeCount := len(mounts)
v.MountPKI(t)
mounts, err = vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
afterCount := len(mounts)
assert.Greater(afterCount, beforeCount)
_, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"}))
vc.SetToken(token)
certPath := path.Join("pki", "issue", "boundary")
certOptions := map[string]interface{}{
"common_name": "boundary.com",
}
certSecret, err := vc.Logical().Write(certPath, certOptions)
assert.NoError(err)
require.NotEmpty(certSecret)
})
t.Run("with-mount-path", func(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS))
require.NotNil(v)
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
beforeCount := len(mounts)
v.MountPKI(t, WithTestMountPath("gary"))
mounts, err = vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
afterCount := len(mounts)
assert.Greater(afterCount, beforeCount)
_, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"}))
vc.SetToken(token)
certPath := path.Join("gary", "issue", "boundary")
certOptions := map[string]interface{}{
"common_name": "boundary.com",
}
certSecret, err := vc.Logical().Write(certPath, certOptions)
assert.NoError(err)
require.NotEmpty(certSecret)
})
t.Run("with-role-name", func(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS))
require.NotNil(v)
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
beforeCount := len(mounts)
v.MountPKI(t, WithTestRoleName("gary"))
mounts, err = vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
afterCount := len(mounts)
assert.Greater(afterCount, beforeCount)
_, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"}))
vc.SetToken(token)
certPath := path.Join("pki", "issue", "gary")
certOptions := map[string]interface{}{
"common_name": "boundary.com",
}
certSecret, err := vc.Logical().Write(certPath, certOptions)
assert.NoError(err)
require.NotEmpty(certSecret)
})
}
func TestTestVaultServer_MountDatabase(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithDockerNetwork(true), WithTestVaultTLS(TestClientTLS))
vc := v.client(t).cl
mounts, err := vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
beforeCount := len(mounts)
testDatabase := v.MountDatabase(t)
mounts, err = vc.Sys().ListMounts()
assert.NoError(err)
require.NotEmpty(mounts)
afterCount := len(mounts)
assert.Greater(afterCount, beforeCount)
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
vc.SetToken(token)
dbSecret, err := vc.Logical().Read(path.Join("database", "creds", "opened"))
assert.NoError(err)
require.NotEmpty(dbSecret)
// verify the database credentials work
assert.NoError(testDatabase.ValidateCredential(t, dbSecret))
// revoke the database credentials
assert.NoError(vc.Sys().Revoke(dbSecret.LeaseID))
// verify the database credentials no longer work
assert.Error(testDatabase.ValidateCredential(t, dbSecret))
})
}
func TestTestVaultServer_LookupLease(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
v := NewTestVaultServer(t, WithDockerNetwork(true))
v.MountDatabase(t)
conf := &clientConfig{
Addr: v.Addr,
CaCert: v.CaCert,
ClientCert: v.ClientCert,
ClientKey: v.ClientKey,
Token: TokenSecret(v.RootToken),
}
client, err := newClient(conf)
require.NoError(err)
require.NotNil(client)
assert.NoError(client.ping())
// Create secret
credPath := path.Join("database", "creds", "opened")
cred, err := client.get(credPath)
require.NoError(err)
// Sleep to move ttl
time.Sleep(time.Second)
leaseLookup := v.LookupLease(t, cred.LeaseID)
require.NotNil(leaseLookup.Data)
id := leaseLookup.Data["id"]
require.NotEmpty(id)
assert.Equal(cred.LeaseID, id.(string))
ttl := leaseLookup.Data["ttl"]
require.NotEmpty(ttl)
newTtl, err := ttl.(json.Number).Int64()
require.NoError(err)
// New ttl should have moved and be lower than original lease duration
assert.True(cred.LeaseDuration > int(newTtl))
}
func TestTestVaultServer_VerifyTokenInvalid(t *testing.T) {
t.Parallel()
require := require.New(t)
v := NewTestVaultServer(t, WithDockerNetwork(true))
_, token := v.CreateToken(t)
client := v.clientUsingToken(t, token)
err := client.revokeToken()
require.NoError(err)
v.VerifyTokenInvalid(t, token)
// Verify fake token is not valid
v.VerifyTokenInvalid(t, "fake-token")
}
func Test_testClientCert(t *testing.T) {
assert, require := assert.New(t), require.New(t)
cert := testClientCert(t, testCaCert(t))
require.NotNil(cert)
assert.NotEmpty(cert.Cert.Cert)
assert.NotEmpty(cert.Cert.Key)
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
require.NoError(err)
cert1 := testClientCert(t, testCaCert(t), WithClientKey(key))
require.NotNil(cert1)
assert.NotEmpty(cert1.Cert.Cert)
assert.NotEmpty(cert1.Cert.Key)
// cert and cert1 should have different certs and keys
assert.NotEqual(cert1.Cert.Cert, cert.Cert.Cert)
assert.NotEqual(cert1.Cert.Key, cert.Cert.Key)
// Generate new cert with same key as cert1
cert2 := testClientCert(t, testCaCert(t), WithClientKey(key))
require.NotNil(cert2)
assert.NotEmpty(cert2.Cert.Cert)
assert.NotEmpty(cert2.Cert.Key)
// cert1 and cert2 should have different certs but the same key
assert.NotEqual(cert1.Cert.Cert, cert2.Cert.Cert)
assert.Equal(cert1.Cert.Key, cert2.Cert.Key)
}