package openbao import ( "encoding/base64" "errors" "os" "path/filepath" "strings" "testing" ) func restrictedTokenFile(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "openbao-token") if err := os.WriteFile(path, []byte("test-restricted-token\n"), 0600); err != nil { t.Fatal(err) } return path } func TestManagedSecretPathOnlyAllowsAppAndSharedNamespaces(t *testing.T) { for _, test := range []struct { shared bool owner string name string want string }{ {false, "orders-api", "publish", "apps/orders-api/publish"}, {true, "rabbitmq", "password", "shared/rabbitmq/password"}, } { got, err := ManagedSecretPath(test.shared, test.owner, test.name) if err != nil || got != test.want { t.Fatalf("ManagedSecretPath(%t, %q, %q) = %q, %v", test.shared, test.owner, test.name, got, err) } } if _, err := ManagedSecretPath(false, "orders-api", "../root"); err == nil { t.Fatal("ManagedSecretPath accepted an unsafe path") } } func TestStoreManagedSecretUsesRestrictedTokenStdinWithoutRecoveryInputs(t *testing.T) { originalDecrypt, originalExec := decryptRecovery, execInPodMutation t.Cleanup(func() { decryptRecovery, execInPodMutation = originalDecrypt, originalExec }) decryptRecovery = func(_, _ string) ([]byte, error) { t.Fatal("application secret CRUD must not read recovery material") return nil, nil } const value = "must-not-appear-in-command" execInPodMutation = func(_ string, input []byte, args ...string) ([]byte, error) { if string(input) != "test-restricted-token\n"+base64.StdEncoding.EncodeToString([]byte(value))+"\n" { t.Fatal("secret token and value were not framed on stdin") } command := strings.Join(args, " ") if !strings.Contains(command, "bao kv put secret/apps/orders-api/publish value=\"$value0\"") || strings.Contains(command, value) || strings.Contains(command, "test-restricted-token") { t.Fatal("secret command exposed a value or token") } return nil, nil } if err := StoreManagedSecret("kubeconfig", restrictedTokenFile(t), "apps/orders-api/publish", []byte(value)); err != nil { t.Fatal(err) } } func TestListAndStatusReadMetadataOnly(t *testing.T) { originalDecrypt, originalExec := decryptRecovery, execInPod t.Cleanup(func() { decryptRecovery, execInPod = originalDecrypt, originalExec }) decryptRecovery = func(_, _ string) ([]byte, error) { t.Fatal("application secret CRUD must not read recovery material") return nil, nil } calls := 0 execInPod = func(_ string, input []byte, args ...string) ([]byte, error) { calls++ if string(input) != "test-restricted-token\n" || strings.Contains(strings.Join(args, " "), " kv get ") || strings.Contains(strings.Join(args, " "), "test-restricted-token") { t.Fatal("metadata query used an unsafe secret-read boundary") } if strings.Contains(strings.Join(args, " "), "kv list") { return []byte(`{"data":{"keys":["publish"]}}`), nil } return []byte(`{"data":{"created_time":"2026-01-01T00:00:00Z"}}`), nil } tokenPath := restrictedTokenFile(t) values, err := ListManagedSecrets("kubeconfig", tokenPath, false, "orders-api") if err != nil || len(values) != 1 || values[0] != "publish" { t.Fatalf("ListManagedSecrets() = %q, %v", values, err) } present, err := ManagedSecretStatus("kubeconfig", tokenPath, "apps/orders-api/publish") if err != nil || !present || calls != 2 { t.Fatalf("ManagedSecretStatus() = %t, %v; calls = %d", present, err, calls) } } func TestDeleteManagedSecretUsesMetadataDelete(t *testing.T) { originalDecrypt, originalExec := decryptRecovery, execInPodMutation t.Cleanup(func() { decryptRecovery, execInPodMutation = originalDecrypt, originalExec }) decryptRecovery = func(_, _ string) ([]byte, error) { t.Fatal("application secret CRUD must not read recovery material") return nil, nil } execInPodMutation = func(_ string, input []byte, args ...string) ([]byte, error) { if string(input) != "test-restricted-token\n" || !strings.Contains(strings.Join(args, " "), "bao kv metadata delete secret/shared/rabbitmq/password") || strings.Contains(strings.Join(args, " "), "test-restricted-token") { t.Fatal("delete did not use a metadata delete with token on stdin") } return nil, nil } if err := DeleteManagedSecret("kubeconfig", restrictedTokenFile(t), "shared/rabbitmq/password"); err != nil { t.Fatal(err) } } func TestStoreManagedSecretRedactsTokenAndValueFromFailures(t *testing.T) { original := execInPodMutation t.Cleanup(func() { execInPodMutation = original }) const value = "must-not-leak-value" execInPodMutation = func(_ string, _ []byte, _ ...string) ([]byte, error) { return []byte("test-restricted-token " + value), errors.New("failed") } err := StoreManagedSecret("kubeconfig", restrictedTokenFile(t), "apps/orders-api/publish", []byte(value)) if err == nil || strings.Contains(err.Error(), "test-restricted-token") || strings.Contains(err.Error(), value) { t.Fatal("secret operation leaked a token or value") } }