maidn-cli/internal/openbao/app_secret_test.go

123 lines
4.9 KiB
Go

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")
}
}