VAULT-25987 de-flake Test_NoAutoAuthSelfHealing_BadPolicy (#26547)

* VAULT-25987 de-flake Test_NoAutoAuthSelfHealing_BadPolicy

* Send token to outputchannel too

* Remove initial sink checks
This commit is contained in:
Violet Hynes
2024-04-19 15:11:30 -04:00
committed by GitHub
parent 76d33bfce7
commit fa61a060b4
2 changed files with 87 additions and 106 deletions

View File

@@ -27,20 +27,6 @@ import (
"github.com/stretchr/testify/require"
)
const (
lookupSelfTemplateContents = `{{ with secret "auth/token/lookup-self" }}{{ .Data.id }}{{ end }}`
kvDataTemplateContents = `"{{ with secret "secret/data/otherapp" }}{{ .Data.data.username }}{{ end }}"`
kvAccessPolicy = `
path "/kv/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "/secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}`
)
// TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput tests that
// if the token is revoked, Auto Auth is re-triggered and a valid new token
// is written to a sink, and the template is correctly rendered with the new token
@@ -48,14 +34,6 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
// Unset the environment variable so that agent picks up the right test cluster address
t.Setenv(api.EnvVaultAddress, "")
tmpDir := t.TempDir()
pathLookupSelf := filepath.Join(tmpDir, "lookup-self")
pathVaultToken := filepath.Join(tmpDir, "vault-token")
pathTokenFile := filepath.Join(tmpDir, "token-file")
secretRenderInterval := 1 * time.Second
contextTimeout := 30 * time.Second
cluster := minimal.NewTestSoloCluster(t, nil)
logger := corehelpers.NewTestLogger(t)
serverClient := cluster.Cores[0].Client
@@ -68,17 +46,12 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
require.NotEmpty(t, secret.Auth.ClientToken)
token := secret.Auth.ClientToken
// Write token to vault-token file
tokenFile, err := os.Create(pathVaultToken)
require.NoError(t, err)
_, err = tokenFile.WriteString(token)
require.NoError(t, err)
err = tokenFile.Close()
require.NoError(t, err)
// Write token to the auto-auth token file
pathVaultToken := makeTempFile(t, "token-file", token)
// Give us some leeway of 3 errors 1 from each of: auth handler, sink server template server.
errCh := make(chan error, 3)
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// Create auth handler
am, err := tokenfile.NewTokenFileAuthMethod(&auth.AuthConfig{
@@ -89,6 +62,10 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
})
require.NoError(t, err)
// Create sink file
pathSinkFile := makeTempFile(t, "sink-file", "")
require.NoError(t, err)
ahConfig := &auth.AuthHandlerConfig{
Logger: logger.Named("auth.handler"),
Client: serverClient,
@@ -102,20 +79,14 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
errCh <- ah.Run(ctx, am)
}()
// Create sink file server
_, err = os.Create(pathTokenFile)
require.NoError(t, err)
config := &sink.SinkConfig{
Logger: logger.Named("sink.file"),
Config: map[string]interface{}{
"path": pathTokenFile,
"path": pathSinkFile,
},
}
fs, err := file.NewFileSink(config)
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
config.Sink = fs
ss := sink.NewSinkServer(&sink.SinkServerConfig{
@@ -135,14 +106,14 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
TLSSkipVerify: true,
},
TemplateConfig: &agentConfig.TemplateConfig{
StaticSecretRenderInt: secretRenderInterval,
StaticSecretRenderInt: 1 * time.Second,
},
AutoAuth: &agentConfig.AutoAuth{
Sinks: []*agentConfig.Sink{
{
Type: "file",
Config: map[string]interface{}{
"path": pathLookupSelf,
"path": pathSinkFile,
},
},
},
@@ -154,33 +125,26 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
ExitAfterAuth: false,
}
pathTemplateOutput := makeTempFile(t, "template-output", "")
originalTemplateFileInfo, err := os.Stat(pathSinkFile)
require.NoError(t, err)
templateTest := &ctconfig.TemplateConfig{
Contents: pointerutil.StringPtr(lookupSelfTemplateContents),
Destination: pointerutil.StringPtr(pathLookupSelf),
Contents: pointerutil.StringPtr(`{{ with secret "auth/token/lookup-self" }}{{ .Data.id }}{{ end }}`),
Destination: pointerutil.StringPtr(pathTemplateOutput),
}
templatesToRender := []*ctconfig.TemplateConfig{templateTest}
var server *template.Server
server = template.NewServer(sc)
server := template.NewServer(sc)
go func() {
errCh <- server.Run(ctx, ah.TemplateTokenCh, templatesToRender, ah.AuthInProgress, ah.InvalidToken)
}()
// Trigger template render (mark the time as being earlier, based on the render interval)
preTriggerTime := time.Now().Add(-secretRenderInterval)
// Send token to template channel
ah.TemplateTokenCh <- token
fileInfo, err := waitForFiles(t, pathTokenFile, preTriggerTime)
templateFileInfo, err := waitForFiles(t, pathTemplateOutput, originalTemplateFileInfo.ModTime())
require.NoError(t, err)
templateFileInfo, err := waitForFiles(t, pathLookupSelf, preTriggerTime)
require.NoError(t, err)
tokenInSink, err := os.ReadFile(pathTokenFile)
require.NoError(t, err)
require.Equal(t, token, string(tokenInSink))
// Revoke Token
t.Logf("revoking token")
err = serverClient.Auth().Token().RevokeOrphan(token)
require.NoError(t, err)
@@ -197,20 +161,20 @@ func TestAutoAuthSelfHealing_TokenFileAuth_SinkOutput(t *testing.T) {
require.NoError(t, err)
// Wait for auto-auth to complete
_, err = waitForFiles(t, pathTokenFile, fileInfo.ModTime())
_, err = waitForFiles(t, pathSinkFile, templateFileInfo.ModTime())
require.NoError(t, err)
// Verify the new token has been written to a file sink after re-authenticating using lookup-self
tokenInSink, err = os.ReadFile(pathTokenFile)
tokenInSink, err := os.ReadFile(pathSinkFile)
require.NoError(t, err)
require.Equal(t, newToken, string(tokenInSink))
// Wait for the template file to have re-rendered
_, err = waitForFiles(t, pathLookupSelf, templateFileInfo.ModTime())
_, err = waitForFiles(t, pathTemplateOutput, templateFileInfo.ModTime())
require.NoError(t, err)
// Verify the template has now been correctly rendered with the new token
templateContents, err := os.ReadFile(pathLookupSelf)
templateContents, err := os.ReadFile(pathTemplateOutput)
require.NoError(t, err)
require.Equal(t, newToken, string(templateContents))
@@ -238,46 +202,40 @@ func Test_NoAutoAuthSelfHealing_BadPolicy(t *testing.T) {
// Unset the environment variable so that agent picks up the right test cluster address
t.Setenv(api.EnvVaultAddress, "")
tmpDir := t.TempDir()
pathKVData := filepath.Join(tmpDir, "kvData")
pathVaultToken := filepath.Join(tmpDir, "vault-token")
pathTokenFile := filepath.Join(tmpDir, "token-file")
policyName := "kv-access"
secretRenderInterval := 1 * time.Second
contextTimeout := 30 * time.Second
cluster := minimal.NewTestSoloCluster(t, nil)
logger := corehelpers.NewTestLogger(t)
serverClient := cluster.Cores[0].Client
// Write a policy with correct access to the secrets
err := serverClient.Sys().PutPolicy(policyName, kvAccessPolicy)
err := serverClient.Sys().PutPolicy(policyName, `
path "/kv/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "/secret/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}`)
require.NoError(t, err)
// Create a token without enough policy access to the kv secrets
secret, err := serverClient.Auth().Token().Create(&api.TokenCreateRequest{
Policies: []string{"test-autoauth"},
Policies: []string{"default"},
})
require.NoError(t, err)
require.NotNil(t, secret)
require.NotNil(t, secret.Auth)
require.NotEmpty(t, secret.Auth.ClientToken)
require.Len(t, secret.Auth.Policies, 2)
require.Len(t, secret.Auth.Policies, 1)
require.Contains(t, secret.Auth.Policies, "default")
require.Contains(t, secret.Auth.Policies, "test-autoauth")
token := secret.Auth.ClientToken
// Write token to vault-token file
tokenFile, err := os.Create(pathVaultToken)
require.NoError(t, err)
_, err = tokenFile.WriteString(token)
require.NoError(t, err)
err = tokenFile.Close()
require.NoError(t, err)
pathVaultToken := makeTempFile(t, "vault-token", token)
// Give us some leeway of 3 errors 1 from each of: auth handler, sink server template server.
errCh := make(chan error, 3)
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
// Create auth handler
am, err := tokenfile.NewTokenFileAuthMethod(&auth.AuthConfig{
@@ -300,20 +258,19 @@ func Test_NoAutoAuthSelfHealing_BadPolicy(t *testing.T) {
errCh <- ah.Run(ctx, am)
}()
// Create sink file server
_, err = os.Create(pathTokenFile)
// Create sink file
pathSinkFile := makeTempFile(t, "sink-file", "")
fileInfo, err := os.Stat(pathSinkFile)
require.NoError(t, err)
config := &sink.SinkConfig{
Logger: logger.Named("sink.file"),
Config: map[string]interface{}{
"path": pathTokenFile,
"path": pathSinkFile,
},
}
fs, err := file.NewFileSink(config)
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
config.Sink = fs
ss := sink.NewSinkServer(&sink.SinkServerConfig{
@@ -333,15 +290,15 @@ func Test_NoAutoAuthSelfHealing_BadPolicy(t *testing.T) {
TLSSkipVerify: true,
},
TemplateConfig: &agentConfig.TemplateConfig{
StaticSecretRenderInt: secretRenderInterval,
StaticSecretRenderInt: 1 * time.Second,
},
// Need to crate at least one sink output so that it does not exit after rendering
// Need to create at least one sink output so that it does not exit after rendering
AutoAuth: &agentConfig.AutoAuth{
Sinks: []*agentConfig.Sink{
{
Type: "file",
Config: map[string]interface{}{
"path": pathKVData,
"path": pathSinkFile,
},
},
},
@@ -353,27 +310,23 @@ func Test_NoAutoAuthSelfHealing_BadPolicy(t *testing.T) {
ExitAfterAuth: false,
}
pathTemplateDestination := makeTempFile(t, "kv-data", "")
fileInfo, err = os.Stat(pathTemplateDestination)
require.NoError(t, err)
templateDestModTime := fileInfo.ModTime()
templateTest := &ctconfig.TemplateConfig{
Contents: pointerutil.StringPtr(kvDataTemplateContents),
Destination: pointerutil.StringPtr(pathKVData),
Contents: pointerutil.StringPtr(`"{{ with secret "secret/data/otherapp" }}{{ .Data.data.username }}{{ end }}"`),
Destination: pointerutil.StringPtr(pathTemplateDestination),
}
templatesToRender := []*ctconfig.TemplateConfig{templateTest}
var server *template.Server
server = template.NewServer(&sc)
server := template.NewServer(&sc)
go func() {
errCh <- server.Run(ctx, ah.TemplateTokenCh, templatesToRender, ah.AuthInProgress, ah.InvalidToken)
}()
// Trigger template render (mark the time as being earlier, based on the render interval)
preTriggerTime := time.Now().Add(-secretRenderInterval)
// Send token to the template channel
ah.TemplateTokenCh <- token
_, err = waitForFiles(t, pathTokenFile, preTriggerTime)
require.NoError(t, err)
tokenInSink, err := os.ReadFile(pathTokenFile)
require.NoError(t, err)
require.Equal(t, token, string(tokenInSink))
// Create new token with the correct policy access
tokenSecret, err := serverClient.Auth().Token().Create(&api.TokenCreateRequest{
@@ -388,20 +341,28 @@ func Test_NoAutoAuthSelfHealing_BadPolicy(t *testing.T) {
require.Contains(t, tokenSecret.Auth.Policies, policyName)
newToken := tokenSecret.Auth.ClientToken
// Write token to file
err = os.WriteFile(pathVaultToken, []byte(token), 0o600)
// Write new token to token file (where Agent would re-auto-auth from if
// it were triggered)
err = os.WriteFile(pathVaultToken, []byte(newToken), 0o600)
require.NoError(t, err)
// Wait for any potential *incorrect* re-triggers of auto auth
time.Sleep(secretRenderInterval * 3)
time.Sleep(time.Second * 3)
// Auto auth should not have been re-triggered because of just a permission denied error
// Verify that the new token has NOT been written to the token sink
tokenInSink, err = os.ReadFile(pathTokenFile)
tokenInSink, err := os.ReadFile(pathSinkFile)
require.NoError(t, err)
require.NotEqual(t, newToken, string(tokenInSink))
require.Equal(t, token, string(tokenInSink))
fileInfo, err = os.Stat(pathTemplateDestination)
require.NoError(t, err)
newTemplateDestModTime := fileInfo.ModTime()
// Verify that the template hasn't been rendered
// since we still have invalid permissions
require.Equal(t, templateDestModTime, newTemplateDestModTime)
cancel()
wrapUpTimeout := 5 * time.Second
for {
@@ -424,11 +385,11 @@ func waitForFiles(t *testing.T, filePath string, prevModTime time.Time) (os.File
var fileInfo os.FileInfo
tick := time.Tick(100 * time.Millisecond)
timeout := time.After(5 * time.Second)
// We need to wait for the templates to render...
// We need to wait for the files to be updated...
for {
select {
case <-timeout:
return nil, fmt.Errorf("timed out waiting for templates to render, last error: %w", err)
return nil, fmt.Errorf("timed out waiting for files, last error: %w", err)
case <-tick:
}
@@ -441,9 +402,29 @@ func waitForFiles(t *testing.T, filePath string, prevModTime time.Time) (os.File
}
// Keep waiting until the file has been updated since the previous mod time
if !fileInfo.ModTime().After(prevModTime) {
err = fmt.Errorf("file not yet updated, prevModTime+%s, currentModTime=%s", prevModTime, fileInfo.ModTime())
continue
}
return fileInfo, nil
}
}
// makeTempFile creates a temp file with the specified name, populates it with the
// supplied contents and closes it. The path to the file is returned, also the file
// will be automatically removed when the test which created it, finishes.
func makeTempFile(t *testing.T, name, contents string) string {
t.Helper()
f, err := os.Create(filepath.Join(t.TempDir(), name))
require.NoError(t, err)
path := f.Name()
_, err = f.WriteString(contents)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
return path
}

View File

@@ -1465,6 +1465,8 @@ func makeTempFile(t *testing.T, name, contents string) string {
return path
}
// populateTempFile creates a temp file with the specified name, populates it with the
// supplied contents and closes it. The file pointer is returned.
func populateTempFile(t *testing.T, name, contents string) *os.File {
t.Helper()
@@ -3436,9 +3438,7 @@ func generateListenerAddress(t *testing.T) string {
t.Helper()
ln1, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
listenAddr := ln1.Addr().String()
ln1.Close()
return listenAddr