diff --git a/command/agent/agent_auto_auth_self_heal_test.go b/command/agent/agent_auto_auth_self_heal_test.go index 02849ff06e..f45cbc631c 100644 --- a/command/agent/agent_auto_auth_self_heal_test.go +++ b/command/agent/agent_auto_auth_self_heal_test.go @@ -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 +} diff --git a/command/agent_test.go b/command/agent_test.go index 9845c2ca03..239128728a 100644 --- a/command/agent_test.go +++ b/command/agent_test.go @@ -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