package openbao import ( "encoding/base64" "errors" "os/exec" "path/filepath" "strings" "testing" "github.com/Pingu-Studio/MaidnCLI/internal/config" ) func TestEnsureRecoveryIdentity(t *testing.T) { if _, err := exec.LookPath("age-keygen"); err != nil { t.Skip("age-keygen is required for OpenBao recovery setup") } recipient, err := EnsureRecoveryIdentity(filepath.Join(t.TempDir(), "recovery-key.txt")) if err != nil { t.Fatal(err) } if !strings.HasPrefix(recipient, "age1") { t.Fatalf("invalid recovery recipient") } } func TestUnsealUsesPromptedStdinOnly(t *testing.T) { originalExec, originalStatus := execInPod, openBaoStatus t.Cleanup(func() { execInPod, openBaoStatus = originalExec, originalStatus }) calls := 0 execInPod = func(_ string, input []byte, args ...string) ([]byte, error) { if len(input) == 0 || len(args) != 3 || args[0] != "sh" || args[1] != "-ec" || args[2] != "read -r key; bao operator unseal \"$key\" >/dev/null" { t.Fatal("unseal share was not submitted through prompted stdin") } calls++ return nil, nil } openBaoStatus = func(string) (status, error) { return status{Initialized: true}, nil } if err := unseal("kubeconfig", RecoveryMaterial{UnsealKeysB64: []string{"share-1", "share-2", "share-3"}, UnsealThreshold: 2}); err != nil || calls != 3 { t.Fatalf("unseal calls:%d err:%v", calls, err) } } func TestUnsealFallsBackToControllerSecret(t *testing.T) { originalExec, originalStatus, originalController := execInPod, openBaoStatus, execInUnsealController t.Cleanup(func() { execInPod, openBaoStatus, execInUnsealController = originalExec, originalStatus, originalController }) shares := 0 execInPod = func(_ string, _ []byte, _ ...string) ([]byte, error) { shares++; return nil, nil } statusChecks := 0 openBaoStatus = func(string) (status, error) { statusChecks++ return status{Initialized: true, Sealed: statusChecks == 1}, nil } controllerCalled := false execInUnsealController = func(_ string, script string) ([]byte, error) { controllerCalled = strings.Contains(script, "/unseal/unseal-*") return nil, nil } if err := unseal("kubeconfig", RecoveryMaterial{UnsealKeysB64: []string{"share-1", "share-2", "share-3"}, UnsealThreshold: 2}); err != nil || shares != 3 || !controllerCalled { t.Fatalf("unseal fallback = shares:%d controller:%t err:%v", shares, controllerCalled, err) } } func TestWriteSecretFramesMultilineValues(t *testing.T) { original := execInPod t.Cleanup(func() { execInPod = original }) var input, script string execInPod = func(_ string, contents []byte, args ...string) ([]byte, error) { input, script = string(contents), args[len(args)-1] return nil, nil } value := "tunnel: tunnel\ningress:\n - service: http_status:404\n" if err := writeSecret("kubeconfig", "root", "platform/cloudflare-tunnel", map[string]string{"config": value}); err != nil || input != "root\n"+base64.StdEncoding.EncodeToString([]byte(value))+"\n" || !strings.Contains(script, "base64 -d") { t.Fatalf("multiline secret boundary was not framed safely: %v", err) } } func TestRefreshExternalSecretsIsReadyGatedAndScoped(t *testing.T) { original := kubectlOutput t.Cleanup(func() { kubectlOutput = original }) var calls []string kubectlOutput = func(_ string, args ...string) ([]byte, error) { calls = append(calls, strings.Join(args, " ")) if len(calls) == 1 { return []byte("True"), nil } if len(calls) == 3 { return []byte("externalsecret.external-secrets.io/forgejo-webhook"), nil } return nil, nil } if err := refreshExternalSecrets("kubeconfig"); err != nil || len(calls) != 4 || !strings.Contains(calls[0], "get deployment/external-secrets") || !strings.Contains(calls[1], "annotate clustersecretstore openbao") || !strings.Contains(calls[2], "get externalsecret forgejo-webhook") || !strings.Contains(calls[3], "annotate externalsecret forgejo-webhook") || strings.Contains(calls[1], "--all") || strings.Contains(calls[3], "--all") { t.Fatalf("ExternalSecret refresh was not readiness-gated and scoped: %q, %v", calls, err) } } func TestRefreshExternalSecretsSkipsWebhookBeforeTekton(t *testing.T) { original := kubectlOutput t.Cleanup(func() { kubectlOutput = original }) var calls []string kubectlOutput = func(_ string, args ...string) ([]byte, error) { calls = append(calls, strings.Join(args, " ")) if len(calls) == 1 { return []byte("True"), nil } return nil, nil } if err := refreshExternalSecrets("kubeconfig"); err != nil || len(calls) != 3 || !strings.Contains(calls[2], "--ignore-not-found") { t.Fatalf("missing webhook ExternalSecret was not safely skipped: %q, %v", calls, err) } } func TestConfigureSecretGrantsScopesApplicationAndSharedPaths(t *testing.T) { originalDecrypt, originalExec := decryptRecovery, execInPod t.Cleanup(func() { decryptRecovery, execInPod = originalDecrypt, originalExec }) decryptRecovery = func(_, _ string) ([]byte, error) { return []byte(`{"unseal_keys_b64":["share"],"unseal_threshold":1,"root_token":"root"}`), nil } var script string execInPod = func(_ string, input []byte, args ...string) ([]byte, error) { if string(input) != "root\n" || len(args) != 3 || args[0] != "sh" || args[1] != "-ec" { t.Fatal("secret grant did not use root token through stdin") } script = args[2] return nil, nil } grants := []config.SecretGrant{ {Application: "orders-api", Consumer: "publish", Shared: []string{"artifact-cache"}}, {Application: "orders-api", Consumer: "runtime", Environment: "production", Shared: []string{"rabbitmq"}}, } if err := ConfigureSecretGrants("kubeconfig", "identity", "bundle", grants); err != nil { t.Fatal(err) } for _, want := range []string{ `secret/data/apps/orders-api/publish/*`, `secret/data/apps/orders-api/runtime/production/*`, `secret/data/shared/artifact-cache/*`, `secret/data/shared/rabbitmq/*`, `bound_service_account_names=maidn-orders-api-publish`, `bound_service_account_namespaces=orders-api-production`, } { if !strings.Contains(script, want) { t.Fatalf("secret grant script missing %q: %s", want, script) } } if strings.Contains(script, `secret/data/*`) { t.Fatal("secret grant widened access to every OpenBao secret") } } func TestConfigureKubernetesAuthLimitsPlatformStore(t *testing.T) { original := execInPod t.Cleanup(func() { execInPod = original }) var script string execInPod = func(_ string, _ []byte, args ...string) ([]byte, error) { script = args[len(args)-1] return nil, nil } if err := configureKubernetesAuth("kubeconfig", "root", "reviewer"); err != nil { t.Fatal(err) } for _, want := range []string{`secret/data/platform/*`, `secret/data/cicd/*`} { if !strings.Contains(script, want) { t.Fatalf("platform policy missing %q", want) } } if strings.Contains(script, `secret/data/*`) { t.Fatal("platform External Secrets role can read every secret") } } func TestReadRecoveryMaterialDecryptsAndValidatesBundle(t *testing.T) { original := decryptRecovery t.Cleanup(func() { decryptRecovery = original }) called := false decryptRecovery = func(identityPath, bundlePath string) ([]byte, error) { called = identityPath == "recovery-identity" && bundlePath == "recovery-bundle" return []byte(`{"unseal_keys_b64":["test-share-1","test-share-2"],"unseal_threshold":2,"root_token":"test-root"}`), nil } material, err := ReadRecoveryMaterial("recovery-identity", "recovery-bundle") if err != nil || !called || material.UnsealThreshold != 2 || len(material.UnsealKeysB64) != 2 || material.RootToken == "" { t.Fatal("valid OpenBao recovery material was not read") } } func TestReadRecoveryMaterialRejectsMalformedOrInsufficientBundle(t *testing.T) { original := decryptRecovery t.Cleanup(func() { decryptRecovery = original }) for _, plaintext := range [][]byte{ []byte(`{"unseal_keys_b64":`), []byte(`{"unseal_keys_b64":["test-share"],"unseal_threshold":2,"root_token":"test-root"}`), } { decryptRecovery = func(string, string) ([]byte, error) { return plaintext, nil } if _, err := ReadRecoveryMaterial("recovery-identity", "recovery-bundle"); err == nil { t.Fatal("invalid OpenBao recovery material was accepted") } } decryptRecovery = func(string, string) ([]byte, error) { return nil, errors.New("unavailable") } if _, err := ReadRecoveryMaterial("recovery-identity", "recovery-bundle"); err == nil { t.Fatal("recovery decryption failure was accepted") } }