123 lines
4.9 KiB
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")
|
|
}
|
|
}
|