open-consul/agent/connect/ca/provider_vault_test.go
Connor f8fc317731
Fix leaked Vault LifetimeRenewers (#12607)
* Fix leaked Vault LifetimeRenewers

When the Vault CA Provider is reconfigured we do not stop the
LifetimeRenewers which can cause them to leak until the Consul processes
recycles. On Configure execute stopWatcher if it exists and is not nil
before starting a new renewal

* Add jitter before restarting the LifetimeWatcher

If we fail to login to Vault or our token is no longer valid we can
overwhelm a Vault instance with many requests very quickly by restarting
the LifetimeWatcher. Before restarting the LifetimeWatcher provide a
backoff time of 1 second or less.

* Use a retry.Waiter instead of RandomStagger

* changelog

* gofmt'd

* Swap out bool for atomic.Unit32 in test

* Provide some extra clarification in comment and changelog
2022-03-28 09:58:16 -05:00

855 lines
26 KiB
Go

package ca
import (
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"sync/atomic"
"testing"
"time"
"github.com/hashicorp/go-hclog"
vaultapi "github.com/hashicorp/vault/api"
vaultconst "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/sdk/testutil/retry"
)
func TestVaultCAProvider_ParseVaultCAConfig(t *testing.T) {
cases := map[string]struct {
rawConfig map[string]interface{}
expConfig *structs.VaultCAProviderConfig
expError string
}{
"no token and no auth method provided": {
rawConfig: map[string]interface{}{},
expError: "must provide a Vault token or configure a Vault auth method",
},
"both token and auth method provided": {
rawConfig: map[string]interface{}{"Token": "test", "AuthMethod": map[string]interface{}{"Type": "test"}},
expError: "only one of Vault token or Vault auth method can be provided, but not both",
},
"no root PKI path": {
rawConfig: map[string]interface{}{"Token": "test"},
expError: "must provide a valid path to a root PKI backend",
},
"no root intermediate path": {
rawConfig: map[string]interface{}{"Token": "test", "RootPKIPath": "test"},
expError: "must provide a valid path for the intermediate PKI backend",
},
"adds a slash to RootPKIPath and IntermediatePKIPath": {
rawConfig: map[string]interface{}{"Token": "test", "RootPKIPath": "test", "IntermediatePKIPath": "test"},
expConfig: &structs.VaultCAProviderConfig{
CommonCAProviderConfig: defaultCommonConfig(),
Token: "test",
RootPKIPath: "test/",
IntermediatePKIPath: "test/",
},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
config, err := ParseVaultCAConfig(c.rawConfig)
if c.expError != "" {
require.EqualError(t, err, c.expError)
} else {
require.NoError(t, err)
require.Equal(t, c.expConfig, config)
}
})
}
}
func TestVaultCAProvider_configureVaultAuthMethod(t *testing.T) {
cases := map[string]struct {
expLoginPath string
params map[string]interface{}
expError string
}{
"alicloud": {expLoginPath: "auth/alicloud/login"},
"approle": {expLoginPath: "auth/approle/login"},
"aws": {expLoginPath: "auth/aws/login"},
"azure": {expLoginPath: "auth/azure/login"},
"cf": {expLoginPath: "auth/cf/login"},
"github": {expLoginPath: "auth/github/login"},
"gcp": {expLoginPath: "auth/gcp/login"},
"jwt": {expLoginPath: "auth/jwt/login"},
"kerberos": {expLoginPath: "auth/kerberos/login"},
"kubernetes": {expLoginPath: "auth/kubernetes/login", params: map[string]interface{}{"jwt": "fake"}},
"ldap": {expLoginPath: "auth/ldap/login/foo", params: map[string]interface{}{"username": "foo"}},
"oci": {expLoginPath: "auth/oci/login/foo", params: map[string]interface{}{"role": "foo"}},
"okta": {expLoginPath: "auth/okta/login/foo", params: map[string]interface{}{"username": "foo"}},
"radius": {expLoginPath: "auth/radius/login/foo", params: map[string]interface{}{"username": "foo"}},
"cert": {expLoginPath: "auth/cert/login"},
"token": {expError: "'token' auth method is not supported via auth method configuration; please provide the token with the 'token' parameter in the CA configuration"},
"userpass": {expLoginPath: "auth/userpass/login/foo", params: map[string]interface{}{"username": "foo"}},
"unsupported": {expError: "auth method \"unsupported\" is not supported"},
}
for authMethodType, c := range cases {
t.Run(authMethodType, func(t *testing.T) {
loginPath, err := configureVaultAuthMethod(&structs.VaultAuthMethod{
Type: authMethodType,
Params: c.params,
})
if c.expError == "" {
require.NoError(t, err)
require.Equal(t, c.expLoginPath, loginPath)
} else {
require.EqualError(t, err, c.expError)
}
})
}
}
func TestVaultCAProvider_VaultTLSConfig(t *testing.T) {
config := &structs.VaultCAProviderConfig{
CAFile: "/capath/ca.pem",
CAPath: "/capath/",
CertFile: "/certpath/cert.pem",
KeyFile: "/certpath/key.pem",
TLSServerName: "server.name",
TLSSkipVerify: true,
}
tlsConfig := vaultTLSConfig(config)
require.Equal(t, config.CAFile, tlsConfig.CACert)
require.Equal(t, config.CAPath, tlsConfig.CAPath)
require.Equal(t, config.CertFile, tlsConfig.ClientCert)
require.Equal(t, config.KeyFile, tlsConfig.ClientKey)
require.Equal(t, config.TLSServerName, tlsConfig.TLSServerName)
require.Equal(t, config.TLSSkipVerify, tlsConfig.Insecure)
}
func TestVaultCAProvider_Configure(t *testing.T) {
SkipIfVaultNotPresent(t)
testcases := []struct {
name string
rawConfig map[string]interface{}
expectedValue func(t *testing.T, v *VaultProvider)
}{
{
name: "DefaultConfig",
rawConfig: map[string]interface{}{},
expectedValue: func(t *testing.T, v *VaultProvider) {
headers := v.client.Headers()
require.Equal(t, "", headers.Get(vaultconst.NamespaceHeaderName))
require.Equal(t, "pki-root/", v.config.RootPKIPath)
require.Equal(t, "pki-intermediate/", v.config.IntermediatePKIPath)
},
},
{
name: "TestConfigWithNamespace",
rawConfig: map[string]interface{}{"namespace": "ns1"},
expectedValue: func(t *testing.T, v *VaultProvider) {
h := v.client.Headers()
require.Equal(t, "ns1", h.Get(vaultconst.NamespaceHeaderName))
},
},
}
for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
provider, _ := testVaultProviderWithConfig(t, true, testcase.rawConfig)
testcase.expectedValue(t, provider)
})
}
return
}
func TestVaultCAProvider_SecondaryActiveIntermediate(t *testing.T) {
SkipIfVaultNotPresent(t)
provider, testVault := testVaultProviderWithConfig(t, false, nil)
defer testVault.Stop()
cert, err := provider.ActiveIntermediate()
require.Empty(t, cert)
require.NoError(t, err)
}
func TestVaultCAProvider_RenewToken(t *testing.T) {
SkipIfVaultNotPresent(t)
testVault, err := runTestVault(t)
require.NoError(t, err)
testVault.WaitUntilReady(t)
// Create a token with a short TTL to be renewed by the provider.
ttl := 1 * time.Second
tcr := &vaultapi.TokenCreateRequest{
TTL: ttl.String(),
}
secret, err := testVault.client.Auth().Token().Create(tcr)
require.NoError(t, err)
providerToken := secret.Auth.ClientToken
_, err = createVaultProvider(t, true, testVault.Addr, providerToken, nil)
require.NoError(t, err)
// Check the last renewal time.
secret, err = testVault.client.Auth().Token().Lookup(providerToken)
require.NoError(t, err)
firstRenewal, err := secret.Data["last_renewal_time"].(json.Number).Int64()
require.NoError(t, err)
// Wait past the TTL and make sure the token has been renewed.
retry.Run(t, func(r *retry.R) {
secret, err = testVault.client.Auth().Token().Lookup(providerToken)
require.NoError(r, err)
lastRenewal, err := secret.Data["last_renewal_time"].(json.Number).Int64()
require.NoError(r, err)
require.Greater(r, lastRenewal, firstRenewal)
})
}
func TestVaultCAProvider_RenewTokenStopWatcherOnConfigure(t *testing.T) {
SkipIfVaultNotPresent(t)
testVault, err := runTestVault(t)
require.NoError(t, err)
testVault.WaitUntilReady(t)
// Create a token with a short TTL to be renewed by the provider.
ttl := 1 * time.Second
tcr := &vaultapi.TokenCreateRequest{
TTL: ttl.String(),
}
secret, err := testVault.client.Auth().Token().Create(tcr)
require.NoError(t, err)
providerToken := secret.Auth.ClientToken
provider, err := createVaultProvider(t, true, testVault.Addr, providerToken, nil)
require.NoError(t, err)
var gotStopped = uint32(0)
provider.stopWatcher = func() {
atomic.StoreUint32(&gotStopped, 1)
}
// Check the last renewal time.
secret, err = testVault.client.Auth().Token().Lookup(providerToken)
require.NoError(t, err)
firstRenewal, err := secret.Data["last_renewal_time"].(json.Number).Int64()
require.NoError(t, err)
// Wait past the TTL and make sure the token has been renewed.
retry.Run(t, func(r *retry.R) {
secret, err = testVault.client.Auth().Token().Lookup(providerToken)
require.NoError(r, err)
lastRenewal, err := secret.Data["last_renewal_time"].(json.Number).Int64()
require.NoError(r, err)
require.Greater(r, lastRenewal, firstRenewal)
})
providerConfig := vaultProviderConfig(t, testVault.Addr, providerToken, nil)
require.NoError(t, provider.Configure(providerConfig))
require.Equal(t, uint32(1), atomic.LoadUint32(&gotStopped))
}
func TestVaultCAProvider_Bootstrap(t *testing.T) {
SkipIfVaultNotPresent(t)
providerWDefaultRootCertTtl, testvault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
})
defer testvault1.Stop()
client1 := testvault1.client
providerCustomRootCertTtl, testvault2 := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
"RootCertTTL": "8761h",
})
defer testvault2.Stop()
client2 := testvault2.client
cases := []struct {
certFunc func() (string, error)
backendPath string
rootCaCreation bool
provider *VaultProvider
client *vaultapi.Client
expectedRootCertTTL string
}{
{
certFunc: func() (string, error) {
root, err := providerWDefaultRootCertTtl.GenerateRoot()
return root.PEM, err
},
backendPath: "pki-root/",
rootCaCreation: true,
client: client1,
provider: providerWDefaultRootCertTtl,
expectedRootCertTTL: structs.DefaultRootCertTTL,
},
{
certFunc: providerCustomRootCertTtl.ActiveIntermediate,
backendPath: "pki-intermediate/",
rootCaCreation: false,
provider: providerCustomRootCertTtl,
client: client2,
expectedRootCertTTL: "8761h",
},
}
// Verify the root and intermediate certs match the ones in the vault backends
for _, tc := range cases {
provider := tc.provider
client := tc.client
cert, err := tc.certFunc()
require.NoError(t, err)
req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem")
resp, err := client.RawRequest(req)
require.NoError(t, err)
bytes, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, cert, string(bytes)+"\n")
// Should be a valid CA cert
parsed, err := connect.ParseCert(cert)
require.NoError(t, err)
require.True(t, parsed.IsCA)
require.Len(t, parsed.URIs, 1)
require.Equal(t, fmt.Sprintf("spiffe://%s.consul", provider.clusterID), parsed.URIs[0].String())
// test that the root cert ttl as applied
if tc.rootCaCreation {
rootCertTTL, err := time.ParseDuration(tc.expectedRootCertTTL)
require.NoError(t, err)
expectedNotAfter := time.Now().Add(rootCertTTL).UTC()
require.WithinDuration(t, expectedNotAfter, parsed.NotAfter, 10*time.Minute, "expected parsed cert ttl to be the same as the value configured")
}
}
}
func assertCorrectKeyType(t *testing.T, want, certPEM string) {
t.Helper()
cert, err := connect.ParseCert(certPEM)
require.NoError(t, err)
switch want {
case "ec":
require.Equal(t, x509.ECDSA, cert.PublicKeyAlgorithm)
case "rsa":
require.Equal(t, x509.RSA, cert.PublicKeyAlgorithm)
default:
t.Fatal("test doesn't support key type")
}
}
func TestVaultCAProvider_SignLeaf(t *testing.T) {
SkipIfVaultNotPresent(t)
for _, tc := range KeyTestCases {
tc := tc
t.Run(tc.Desc, func(t *testing.T) {
provider, testVault := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
"PrivateKeyType": tc.KeyType,
"PrivateKeyBits": tc.KeyBits,
})
defer testVault.Stop()
spiffeService := &connect.SpiffeIDService{
Host: "node1",
Namespace: "default",
Datacenter: "dc1",
Service: "foo",
}
root, err := provider.GenerateRoot()
require.NoError(t, err)
rootPEM := root.PEM
assertCorrectKeyType(t, tc.KeyType, rootPEM)
intPEM, err := provider.ActiveIntermediate()
require.NoError(t, err)
assertCorrectKeyType(t, tc.KeyType, intPEM)
// Generate a leaf cert for the service.
var firstSerial uint64
{
raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw)
require.NoError(t, err)
cert, err := provider.Sign(csr)
require.NoError(t, err)
parsed, err := connect.ParseCert(cert)
require.NoError(t, err)
require.Equal(t, parsed.URIs[0], spiffeService.URI())
firstSerial = parsed.SerialNumber.Uint64()
// Ensure the cert is valid now and expires within the correct limit.
now := time.Now()
require.True(t, parsed.NotAfter.Sub(now) < time.Hour)
require.True(t, parsed.NotBefore.Before(now))
// Make sure we can validate the cert as expected.
require.NoError(t, connect.ValidateLeaf(rootPEM, cert, []string{intPEM}))
requireTrailingNewline(t, cert)
}
// Generate a new cert for another service and make sure
// the serial number is unique.
spiffeService.Service = "bar"
{
raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw)
require.NoError(t, err)
cert, err := provider.Sign(csr)
require.NoError(t, err)
parsed, err := connect.ParseCert(cert)
require.NoError(t, err)
require.Equal(t, parsed.URIs[0], spiffeService.URI())
require.NotEqual(t, firstSerial, parsed.SerialNumber.Uint64())
// Ensure the cert is valid now and expires within the correct limit.
require.True(t, time.Until(parsed.NotAfter) < time.Hour)
require.True(t, parsed.NotBefore.Before(time.Now()))
// Make sure we can validate the cert as expected.
require.NoError(t, connect.ValidateLeaf(rootPEM, cert, []string{intPEM}))
}
})
}
}
func TestVaultCAProvider_CrossSignCA(t *testing.T) {
SkipIfVaultNotPresent(t)
tests := CASigningKeyTypeCases()
for _, tc := range tests {
tc := tc
t.Run(tc.Desc, func(t *testing.T) {
if tc.SigningKeyType != tc.CSRKeyType {
// See https://github.com/hashicorp/vault/issues/7709
t.Skip("Vault doesn't support cross-signing different key types yet.")
}
provider1, testVault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
"PrivateKeyType": tc.SigningKeyType,
"PrivateKeyBits": tc.SigningKeyBits,
})
defer testVault1.Stop()
{
root, err := provider1.GenerateRoot()
require.NoError(t, err)
assertCorrectKeyType(t, tc.SigningKeyType, root.PEM)
intPEM, err := provider1.ActiveIntermediate()
require.NoError(t, err)
assertCorrectKeyType(t, tc.SigningKeyType, intPEM)
}
provider2, testVault2 := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
"PrivateKeyType": tc.CSRKeyType,
"PrivateKeyBits": tc.CSRKeyBits,
})
defer testVault2.Stop()
{
root, err := provider2.GenerateRoot()
require.NoError(t, err)
assertCorrectKeyType(t, tc.CSRKeyType, root.PEM)
intPEM, err := provider2.ActiveIntermediate()
require.NoError(t, err)
assertCorrectKeyType(t, tc.CSRKeyType, intPEM)
}
testCrossSignProviders(t, provider1, provider2)
})
}
}
func TestVaultProvider_SignIntermediate(t *testing.T) {
SkipIfVaultNotPresent(t)
tests := CASigningKeyTypeCases()
for _, tc := range tests {
tc := tc
t.Run(tc.Desc, func(t *testing.T) {
provider1, testVault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
"LeafCertTTL": "1h",
"PrivateKeyType": tc.SigningKeyType,
"PrivateKeyBits": tc.SigningKeyBits,
})
defer testVault1.Stop()
provider2, testVault2 := testVaultProviderWithConfig(t, false, map[string]interface{}{
"LeafCertTTL": "1h",
"PrivateKeyType": tc.CSRKeyType,
"PrivateKeyBits": tc.CSRKeyBits,
})
defer testVault2.Stop()
testSignIntermediateCrossDC(t, provider1, provider2)
})
}
}
func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
SkipIfVaultNotPresent(t)
// primary = Vault, secondary = Consul
t.Run("pri=vault,sec=consul", func(t *testing.T) {
provider1, testVault1 := testVaultProviderWithConfig(t, true, nil)
defer testVault1.Stop()
conf := testConsulCAConfig()
delegate := newMockDelegate(t, conf)
provider2 := TestConsulProvider(t, delegate)
cfg := testProviderConfig(conf)
cfg.IsPrimary = false
cfg.Datacenter = "dc2"
require.NoError(t, provider2.Configure(cfg))
testSignIntermediateCrossDC(t, provider1, provider2)
})
// primary = Consul, secondary = Vault
t.Run("pri=consul,sec=vault", func(t *testing.T) {
conf := testConsulCAConfig()
delegate := newMockDelegate(t, conf)
provider1 := TestConsulProvider(t, delegate)
require.NoError(t, provider1.Configure(testProviderConfig(conf)))
_, err := provider1.GenerateRoot()
require.NoError(t, err)
// Ensure that we don't configure vault to try and mint leafs that
// outlive their CA during the test (which hard fails in vault).
intermediateCertTTL := getIntermediateCertTTL(t, conf)
leafCertTTL := intermediateCertTTL - 4*time.Hour
overrideConf := map[string]interface{}{
"LeafCertTTL": []uint8(leafCertTTL.String()),
}
provider2, testVault2 := testVaultProviderWithConfig(t, false, overrideConf)
defer testVault2.Stop()
testSignIntermediateCrossDC(t, provider1, provider2)
})
}
func TestVaultProvider_Cleanup(t *testing.T) {
SkipIfVaultNotPresent(t)
testVault, err := runTestVault(t)
require.NoError(t, err)
testVault.WaitUntilReady(t)
t.Run("provider-change", func(t *testing.T) {
provider, err := createVaultProvider(t, true, testVault.Addr, testVault.RootToken, nil)
require.NoError(t, err)
// ensure that the intermediate PKI mount exists
mounts, err := provider.client.Sys().ListMounts()
require.NoError(t, err)
require.Contains(t, mounts, provider.config.IntermediatePKIPath)
// call cleanup with a provider change - this should cause removal of the mount
require.NoError(t, provider.Cleanup(true, nil))
// verify the mount was removed
mounts, err = provider.client.Sys().ListMounts()
require.NoError(t, err)
require.NotContains(t, mounts, provider.config.IntermediatePKIPath)
})
t.Run("pki-path-change", func(t *testing.T) {
provider, err := createVaultProvider(t, true, testVault.Addr, testVault.RootToken, nil)
require.NoError(t, err)
// ensure that the intermediate PKI mount exists
mounts, err := provider.client.Sys().ListMounts()
require.NoError(t, err)
require.Contains(t, mounts, provider.config.IntermediatePKIPath)
// call cleanup with an intermediate pki path change - this should cause removal of the mount
require.NoError(t, provider.Cleanup(false, map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
//
"IntermediatePKIPath": "pki-intermediate2/",
// Tests duration parsing after msgpack type mangling during raft apply.
"LeafCertTTL": []uint8("72h"),
}))
// verify the mount was removed
mounts, err = provider.client.Sys().ListMounts()
require.NoError(t, err)
require.NotContains(t, mounts, provider.config.IntermediatePKIPath)
})
t.Run("pki-path-unchanged", func(t *testing.T) {
provider, err := createVaultProvider(t, true, testVault.Addr, testVault.RootToken, nil)
require.NoError(t, err)
// ensure that the intermediate PKI mount exists
mounts, err := provider.client.Sys().ListMounts()
require.NoError(t, err)
require.Contains(t, mounts, provider.config.IntermediatePKIPath)
// call cleanup with no config changes - this should not cause removal of the intermediate pki path
require.NoError(t, provider.Cleanup(false, map[string]interface{}{
"Address": testVault.Addr,
"Token": testVault.RootToken,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
// Tests duration parsing after msgpack type mangling during raft apply.
"LeafCertTTL": []uint8("72h"),
}))
// verify the mount was NOT removed
mounts, err = provider.client.Sys().ListMounts()
require.NoError(t, err)
require.Contains(t, mounts, provider.config.IntermediatePKIPath)
})
}
func TestVaultProvider_ConfigureWithAuthMethod(t *testing.T) {
SkipIfVaultNotPresent(t)
cases := []struct {
authMethodType string
configureAuthMethodFunc func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{}
}{
{
authMethodType: "userpass",
configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} {
_, err := vaultClient.Logical().Write("/auth/userpass/users/test",
map[string]interface{}{"password": "foo", "policies": "admins"})
require.NoError(t, err)
return map[string]interface{}{
"Type": "userpass",
"Params": map[string]interface{}{
"username": "test",
"password": "foo",
},
}
},
},
{
authMethodType: "approle",
configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} {
_, err := vaultClient.Logical().Write("auth/approle/role/my-role", nil)
require.NoError(t, err)
resp, err := vaultClient.Logical().Read("auth/approle/role/my-role/role-id")
require.NoError(t, err)
roleID := resp.Data["role_id"]
resp, err = vaultClient.Logical().Write("auth/approle/role/my-role/secret-id", nil)
require.NoError(t, err)
secretID := resp.Data["secret_id"]
return map[string]interface{}{
"Type": "approle",
"Params": map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
}
},
},
}
for _, c := range cases {
t.Run(c.authMethodType, func(t *testing.T) {
testVault := NewTestVaultServer(t)
err := testVault.Client().Sys().EnableAuthWithOptions(c.authMethodType, &vaultapi.EnableAuthOptions{Type: c.authMethodType})
require.NoError(t, err)
authMethodConf := c.configureAuthMethodFunc(t, testVault.Client())
conf := map[string]interface{}{
"Address": testVault.Addr,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
"AuthMethod": authMethodConf,
}
provider := NewVaultProvider(hclog.New(nil))
cfg := ProviderConfig{
ClusterID: connect.TestClusterID,
Datacenter: "dc1",
IsPrimary: true,
RawConfig: conf,
}
t.Cleanup(provider.Stop)
err = provider.Configure(cfg)
require.NoError(t, err)
require.NotEmpty(t, provider.client.Token())
})
}
}
func TestVaultProvider_RotateAuthMethodToken(t *testing.T) {
SkipIfVaultNotPresent(t)
testVault := NewTestVaultServer(t)
err := testVault.Client().Sys().EnableAuthWithOptions("approle", &vaultapi.EnableAuthOptions{Type: "approle"})
require.NoError(t, err)
_, err = testVault.Client().Logical().Write("auth/approle/role/my-role",
map[string]interface{}{"token_ttl": "2s", "token_explicit_max_ttl": "2s"})
require.NoError(t, err)
resp, err := testVault.Client().Logical().Read("auth/approle/role/my-role/role-id")
require.NoError(t, err)
roleID := resp.Data["role_id"]
resp, err = testVault.Client().Logical().Write("auth/approle/role/my-role/secret-id", nil)
require.NoError(t, err)
secretID := resp.Data["secret_id"]
conf := map[string]interface{}{
"Address": testVault.Addr,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
"AuthMethod": map[string]interface{}{
"Type": "approle",
"Params": map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
},
},
}
provider := NewVaultProvider(hclog.New(nil))
cfg := ProviderConfig{
ClusterID: connect.TestClusterID,
Datacenter: "dc1",
IsPrimary: true,
RawConfig: conf,
}
t.Cleanup(provider.Stop)
err = provider.Configure(cfg)
require.NoError(t, err)
token := provider.client.Token()
require.NotEmpty(t, token)
// Check that the token is rotated after max_ttl time has passed.
require.Eventually(t, func() bool {
return provider.client.Token() != token
}, 10*time.Second, 100*time.Millisecond)
}
func getIntermediateCertTTL(t *testing.T, caConf *structs.CAConfiguration) time.Duration {
t.Helper()
require.NotNil(t, caConf)
require.NotNil(t, caConf.Config)
iface, ok := caConf.Config["IntermediateCertTTL"]
require.True(t, ok)
ttlBytes, ok := iface.([]uint8)
require.True(t, ok)
ttlString := string(ttlBytes)
dur, err := time.ParseDuration(ttlString)
require.NoError(t, err)
return dur
}
func testVaultProviderWithConfig(t *testing.T, isPrimary bool, rawConf map[string]interface{}) (*VaultProvider, *TestVaultServer) {
testVault, err := runTestVault(t)
if err != nil {
t.Fatalf("err: %v", err)
}
testVault.WaitUntilReady(t)
provider, err := createVaultProvider(t, isPrimary, testVault.Addr, testVault.RootToken, rawConf)
if err != nil {
testVault.Stop()
t.Fatalf("err: %v", err)
}
return provider, testVault
}
func createVaultProvider(t *testing.T, isPrimary bool, addr, token string, rawConf map[string]interface{}) (*VaultProvider, error) {
cfg := vaultProviderConfig(t, addr, token, rawConf)
provider := NewVaultProvider(hclog.New(nil))
if !isPrimary {
cfg.IsPrimary = false
cfg.Datacenter = "dc2"
}
t.Cleanup(provider.Stop)
require.NoError(t, provider.Configure(cfg))
if isPrimary {
_, err := provider.GenerateRoot()
require.NoError(t, err)
_, err = provider.GenerateIntermediate()
require.NoError(t, err)
}
return provider, nil
}
func vaultProviderConfig(t *testing.T, addr, token string, rawConf map[string]interface{}) ProviderConfig {
conf := map[string]interface{}{
"Address": addr,
"Token": token,
"RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/",
// Tests duration parsing after msgpack type mangling during raft apply.
"LeafCertTTL": []uint8("72h"),
}
for k, v := range rawConf {
conf[k] = v
}
cfg := ProviderConfig{
ClusterID: connect.TestClusterID,
Datacenter: "dc1",
IsPrimary: true,
RawConfig: conf,
}
return cfg
}