open-nomad/client/vaultclient/vaultclient_test.go

357 lines
8.9 KiB
Go

package vaultclient
import (
"strings"
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/testutil"
vaultapi "github.com/hashicorp/vault/api"
vaultconsts "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVaultClient_TokenRenewals(t *testing.T) {
ci.Parallel(t)
require := require.New(t)
v := testutil.NewTestVault(t)
defer v.Stop()
logger := testlog.HCLogger(t)
v.Config.ConnectionRetryIntv = 100 * time.Millisecond
v.Config.TaskTokenTTL = "4s"
c, err := NewVaultClient(v.Config, logger, nil)
if err != nil {
t.Fatalf("failed to build vault client: %v", err)
}
c.Start()
defer c.Stop()
// Sleep a little while to ensure that the renewal loop is active
time.Sleep(time.Duration(testutil.TestMultiplier()) * time.Second)
tcr := &vaultapi.TokenCreateRequest{
Policies: []string{"foo", "bar"},
TTL: "2s",
DisplayName: "derived-for-task",
Renewable: new(bool),
}
*tcr.Renewable = true
num := 5
tokens := make([]string, num)
for i := 0; i < num; i++ {
c.client.SetToken(v.Config.Token)
if err := c.client.SetAddress(v.Config.Addr); err != nil {
t.Fatal(err)
}
secret, err := c.client.Auth().Token().Create(tcr)
if err != nil {
t.Fatalf("failed to create vault token: %v", err)
}
if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
t.Fatal("failed to derive a wrapped vault token")
}
tokens[i] = secret.Auth.ClientToken
errCh, err := c.RenewToken(tokens[i], secret.Auth.LeaseDuration)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
go func(errCh <-chan error) {
for {
select {
case err := <-errCh:
require.NoError(err, "unexpected error while renewing vault token")
}
}
}(errCh)
}
c.lock.Lock()
length := c.heap.Length()
c.lock.Unlock()
if length != num {
t.Fatalf("bad: heap length: expected: %d, actual: %d", num, length)
}
time.Sleep(time.Duration(testutil.TestMultiplier()) * time.Second)
for i := 0; i < num; i++ {
if err := c.StopRenewToken(tokens[i]); err != nil {
require.NoError(err)
}
}
c.lock.Lock()
length = c.heap.Length()
c.lock.Unlock()
if length != 0 {
t.Fatalf("bad: heap length: expected: 0, actual: %d", length)
}
}
// TestVaultClient_NamespaceSupport tests that the Vault namespace config, if present, will result in the
// namespace header being set on the created Vault client.
func TestVaultClient_NamespaceSupport(t *testing.T) {
ci.Parallel(t)
require := require.New(t)
tr := true
testNs := "test-namespace"
logger := testlog.HCLogger(t)
conf := config.DefaultConfig()
conf.VaultConfig.Enabled = &tr
conf.VaultConfig.Token = "testvaulttoken"
conf.VaultConfig.Namespace = testNs
c, err := NewVaultClient(conf.VaultConfig, logger, nil)
require.NoError(err)
require.Equal(testNs, c.client.Headers().Get(vaultconsts.NamespaceHeaderName))
}
func TestVaultClient_Heap(t *testing.T) {
ci.Parallel(t)
tr := true
conf := config.DefaultConfig()
conf.VaultConfig.Enabled = &tr
conf.VaultConfig.Token = "testvaulttoken"
conf.VaultConfig.TaskTokenTTL = "10s"
logger := testlog.HCLogger(t)
c, err := NewVaultClient(conf.VaultConfig, logger, nil)
if err != nil {
t.Fatal(err)
}
if c == nil {
t.Fatal("failed to create vault client")
}
now := time.Now()
renewalReq1 := &vaultClientRenewalRequest{
errCh: make(chan error, 1),
id: "id1",
increment: 10,
}
if err := c.heap.Push(renewalReq1, now.Add(50*time.Second)); err != nil {
t.Fatal(err)
}
if !c.isTracked("id1") {
t.Fatalf("id1 should have been tracked")
}
renewalReq2 := &vaultClientRenewalRequest{
errCh: make(chan error, 1),
id: "id2",
increment: 10,
}
if err := c.heap.Push(renewalReq2, now.Add(40*time.Second)); err != nil {
t.Fatal(err)
}
if !c.isTracked("id2") {
t.Fatalf("id2 should have been tracked")
}
renewalReq3 := &vaultClientRenewalRequest{
errCh: make(chan error, 1),
id: "id3",
increment: 10,
}
if err := c.heap.Push(renewalReq3, now.Add(60*time.Second)); err != nil {
t.Fatal(err)
}
if !c.isTracked("id3") {
t.Fatalf("id3 should have been tracked")
}
// Reading elements should yield id2, id1 and id3 in order
req, _ := c.nextRenewal()
if req != renewalReq2 {
t.Fatalf("bad: expected: %#v, actual: %#v", renewalReq2, req)
}
if err := c.heap.Update(req, now.Add(70*time.Second)); err != nil {
t.Fatal(err)
}
req, _ = c.nextRenewal()
if req != renewalReq1 {
t.Fatalf("bad: expected: %#v, actual: %#v", renewalReq1, req)
}
if err := c.heap.Update(req, now.Add(80*time.Second)); err != nil {
t.Fatal(err)
}
req, _ = c.nextRenewal()
if req != renewalReq3 {
t.Fatalf("bad: expected: %#v, actual: %#v", renewalReq3, req)
}
if err := c.heap.Update(req, now.Add(90*time.Second)); err != nil {
t.Fatal(err)
}
if err := c.StopRenewToken("id1"); err != nil {
t.Fatal(err)
}
if err := c.StopRenewToken("id2"); err != nil {
t.Fatal(err)
}
if err := c.StopRenewToken("id3"); err != nil {
t.Fatal(err)
}
if c.isTracked("id1") {
t.Fatalf("id1 should not have been tracked")
}
if c.isTracked("id1") {
t.Fatalf("id1 should not have been tracked")
}
if c.isTracked("id1") {
t.Fatalf("id1 should not have been tracked")
}
}
func TestVaultClient_RenewNonRenewableLease(t *testing.T) {
ci.Parallel(t)
v := testutil.NewTestVault(t)
defer v.Stop()
logger := testlog.HCLogger(t)
v.Config.ConnectionRetryIntv = 100 * time.Millisecond
v.Config.TaskTokenTTL = "4s"
c, err := NewVaultClient(v.Config, logger, nil)
if err != nil {
t.Fatalf("failed to build vault client: %v", err)
}
c.Start()
defer c.Stop()
// Sleep a little while to ensure that the renewal loop is active
time.Sleep(time.Duration(testutil.TestMultiplier()) * time.Second)
tcr := &vaultapi.TokenCreateRequest{
Policies: []string{"foo", "bar"},
TTL: "2s",
DisplayName: "derived-for-task",
Renewable: new(bool),
}
c.client.SetToken(v.Config.Token)
if err := c.client.SetAddress(v.Config.Addr); err != nil {
t.Fatal(err)
}
secret, err := c.client.Auth().Token().Create(tcr)
if err != nil {
t.Fatalf("failed to create vault token: %v", err)
}
if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" {
t.Fatal("failed to derive a wrapped vault token")
}
_, err = c.RenewToken(secret.Auth.ClientToken, secret.Auth.LeaseDuration)
if err == nil {
t.Fatalf("expected error, got nil")
} else if !strings.Contains(err.Error(), "lease is not renewable") {
t.Fatalf("expected \"%s\" in error message, got \"%v\"", "lease is not renewable", err)
}
}
func TestVaultClient_RenewNonexistentLease(t *testing.T) {
ci.Parallel(t)
v := testutil.NewTestVault(t)
defer v.Stop()
logger := testlog.HCLogger(t)
v.Config.ConnectionRetryIntv = 100 * time.Millisecond
v.Config.TaskTokenTTL = "4s"
c, err := NewVaultClient(v.Config, logger, nil)
if err != nil {
t.Fatalf("failed to build vault client: %v", err)
}
c.Start()
defer c.Stop()
// Sleep a little while to ensure that the renewal loop is active
time.Sleep(time.Duration(testutil.TestMultiplier()) * time.Second)
c.client.SetToken(v.Config.Token)
if err := c.client.SetAddress(v.Config.Addr); err != nil {
t.Fatal(err)
}
_, err = c.RenewToken(c.client.Token(), 10)
if err == nil {
t.Fatalf("expected error, got nil")
// The Vault error message changed between 0.10.2 and 1.0.1
} else if !strings.Contains(err.Error(), "lease not found") && !strings.Contains(err.Error(), "lease is not renewable") {
t.Fatalf("expected \"%s\" or \"%s\" in error message, got \"%v\"", "lease not found", "lease is not renewable", err.Error())
}
}
// TestVaultClient_RenewalTime_Long asserts that for leases over 1m the renewal
// time is jittered.
func TestVaultClient_RenewalTime_Long(t *testing.T) {
ci.Parallel(t)
// highRoller is a randIntn func that always returns the max value
highRoller := func(n int) int {
return n - 1
}
// lowRoller is a randIntn func that always returns the min value (0)
lowRoller := func(int) int {
return 0
}
assert.Equal(t, 39*time.Second, renewalTime(highRoller, 60))
assert.Equal(t, 20*time.Second, renewalTime(lowRoller, 60))
assert.Equal(t, 309*time.Second, renewalTime(highRoller, 600))
assert.Equal(t, 290*time.Second, renewalTime(lowRoller, 600))
const days3 = 60 * 60 * 24 * 3
assert.Equal(t, (days3/2+9)*time.Second, renewalTime(highRoller, days3))
assert.Equal(t, (days3/2-10)*time.Second, renewalTime(lowRoller, days3))
}
// TestVaultClient_RenewalTime_Short asserts that for leases under 1m the renewal
// time is lease/2.
func TestVaultClient_RenewalTime_Short(t *testing.T) {
ci.Parallel(t)
dice := func(int) int {
require.Fail(t, "dice should not have been called")
panic("unreachable")
}
assert.Equal(t, 29*time.Second, renewalTime(dice, 58))
assert.Equal(t, 15*time.Second, renewalTime(dice, 30))
assert.Equal(t, 1*time.Second, renewalTime(dice, 2))
}