aboutsummaryrefslogtreecommitdiff
path: root/test/kms/openbao_integration_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/kms/openbao_integration_test.go')
-rw-r--r--test/kms/openbao_integration_test.go598
1 files changed, 598 insertions, 0 deletions
diff --git a/test/kms/openbao_integration_test.go b/test/kms/openbao_integration_test.go
new file mode 100644
index 000000000..d4e62ed4d
--- /dev/null
+++ b/test/kms/openbao_integration_test.go
@@ -0,0 +1,598 @@
+package kms_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/hashicorp/vault/api"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/seaweedfs/seaweedfs/weed/glog"
+ "github.com/seaweedfs/seaweedfs/weed/kms"
+ _ "github.com/seaweedfs/seaweedfs/weed/kms/openbao"
+)
+
+const (
+ OpenBaoAddress = "http://127.0.0.1:8200"
+ OpenBaoToken = "root-token-for-testing"
+ TransitPath = "transit"
+)
+
+// Test configuration for OpenBao KMS provider
+type testConfig struct {
+ config map[string]interface{}
+}
+
+func (c *testConfig) GetString(key string) string {
+ if val, ok := c.config[key]; ok {
+ if str, ok := val.(string); ok {
+ return str
+ }
+ }
+ return ""
+}
+
+func (c *testConfig) GetBool(key string) bool {
+ if val, ok := c.config[key]; ok {
+ if b, ok := val.(bool); ok {
+ return b
+ }
+ }
+ return false
+}
+
+func (c *testConfig) GetInt(key string) int {
+ if val, ok := c.config[key]; ok {
+ if i, ok := val.(int); ok {
+ return i
+ }
+ if f, ok := val.(float64); ok {
+ return int(f)
+ }
+ }
+ return 0
+}
+
+func (c *testConfig) GetStringSlice(key string) []string {
+ if val, ok := c.config[key]; ok {
+ if slice, ok := val.([]string); ok {
+ return slice
+ }
+ }
+ return nil
+}
+
+func (c *testConfig) SetDefault(key string, value interface{}) {
+ if c.config == nil {
+ c.config = make(map[string]interface{})
+ }
+ if _, exists := c.config[key]; !exists {
+ c.config[key] = value
+ }
+}
+
+// setupOpenBao starts OpenBao in development mode for testing
+func setupOpenBao(t *testing.T) (*exec.Cmd, func()) {
+ // Check if OpenBao is running in Docker (via make dev-openbao)
+ client, err := api.NewClient(&api.Config{Address: OpenBaoAddress})
+ if err == nil {
+ client.SetToken(OpenBaoToken)
+ _, err = client.Sys().Health()
+ if err == nil {
+ glog.V(1).Infof("Using existing OpenBao server at %s", OpenBaoAddress)
+ // Return dummy command and cleanup function for existing server
+ return nil, func() {}
+ }
+ }
+
+ // Check if OpenBao binary is available for starting locally
+ _, err = exec.LookPath("bao")
+ if err != nil {
+ t.Skip("OpenBao not running and bao binary not found. Run 'cd test/kms && make dev-openbao' first")
+ }
+
+ // Start OpenBao in dev mode
+ cmd := exec.Command("bao", "server", "-dev", "-dev-root-token-id="+OpenBaoToken, "-dev-listen-address=127.0.0.1:8200")
+ cmd.Env = append(os.Environ(), "BAO_DEV_ROOT_TOKEN_ID="+OpenBaoToken)
+
+ // Capture output for debugging
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err = cmd.Start()
+ require.NoError(t, err, "Failed to start OpenBao server")
+
+ // Wait for OpenBao to be ready
+ client, err = api.NewClient(&api.Config{Address: OpenBaoAddress})
+ require.NoError(t, err)
+ client.SetToken(OpenBaoToken)
+
+ // Wait up to 30 seconds for OpenBao to be ready
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ for {
+ select {
+ case <-ctx.Done():
+ cmd.Process.Kill()
+ t.Fatal("Timeout waiting for OpenBao to start")
+ default:
+ // Try to check health
+ resp, err := client.Sys().Health()
+ if err == nil && resp.Initialized {
+ glog.V(1).Infof("OpenBao server ready")
+ goto ready
+ }
+ time.Sleep(500 * time.Millisecond)
+ }
+ }
+
+ready:
+ // Setup cleanup function
+ cleanup := func() {
+ if cmd != nil && cmd.Process != nil {
+ glog.V(1).Infof("Stopping OpenBao server")
+ cmd.Process.Kill()
+ cmd.Wait()
+ }
+ }
+
+ return cmd, cleanup
+}
+
+// setupTransitEngine enables and configures the transit secrets engine
+func setupTransitEngine(t *testing.T) {
+ client, err := api.NewClient(&api.Config{Address: OpenBaoAddress})
+ require.NoError(t, err)
+ client.SetToken(OpenBaoToken)
+
+ // Enable transit secrets engine
+ err = client.Sys().Mount(TransitPath, &api.MountInput{
+ Type: "transit",
+ Description: "Transit engine for KMS testing",
+ })
+ if err != nil && !strings.Contains(err.Error(), "path is already in use") {
+ require.NoError(t, err, "Failed to enable transit engine")
+ }
+
+ // Create test encryption keys
+ testKeys := []string{"test-key-1", "test-key-2", "seaweedfs-test-key"}
+
+ for _, keyName := range testKeys {
+ keyData := map[string]interface{}{
+ "type": "aes256-gcm96",
+ }
+
+ path := fmt.Sprintf("%s/keys/%s", TransitPath, keyName)
+ _, err = client.Logical().Write(path, keyData)
+ if err != nil && !strings.Contains(err.Error(), "key already exists") {
+ require.NoError(t, err, "Failed to create test key %s", keyName)
+ }
+
+ glog.V(2).Infof("Created/verified test key: %s", keyName)
+ }
+}
+
+func TestOpenBaoKMSProvider_Integration(t *testing.T) {
+ // Start OpenBao server
+ _, cleanup := setupOpenBao(t)
+ defer cleanup()
+
+ // Setup transit engine and keys
+ setupTransitEngine(t)
+
+ t.Run("CreateProvider", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ require.NotNil(t, provider)
+
+ defer provider.Close()
+ })
+
+ t.Run("ProviderRegistration", func(t *testing.T) {
+ // Test that the provider is registered
+ providers := kms.ListProviders()
+ assert.Contains(t, providers, "openbao")
+ assert.Contains(t, providers, "vault") // Compatibility alias
+ })
+
+ t.Run("GenerateDataKey", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ defer provider.Close()
+
+ ctx := context.Background()
+ req := &kms.GenerateDataKeyRequest{
+ KeyID: "test-key-1",
+ KeySpec: kms.KeySpecAES256,
+ EncryptionContext: map[string]string{
+ "test": "context",
+ "env": "integration",
+ },
+ }
+
+ resp, err := provider.GenerateDataKey(ctx, req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ assert.Equal(t, "test-key-1", resp.KeyID)
+ assert.Len(t, resp.Plaintext, 32) // 256 bits
+ assert.NotEmpty(t, resp.CiphertextBlob)
+
+ // Verify the response is in standardized envelope format
+ envelope, err := kms.ParseEnvelope(resp.CiphertextBlob)
+ assert.NoError(t, err)
+ assert.Equal(t, "openbao", envelope.Provider)
+ assert.Equal(t, "test-key-1", envelope.KeyID)
+ assert.True(t, strings.HasPrefix(envelope.Ciphertext, "vault:")) // Raw OpenBao format inside envelope
+ })
+
+ t.Run("DecryptDataKey", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ defer provider.Close()
+
+ ctx := context.Background()
+
+ // First generate a data key
+ genReq := &kms.GenerateDataKeyRequest{
+ KeyID: "test-key-1",
+ KeySpec: kms.KeySpecAES256,
+ EncryptionContext: map[string]string{
+ "test": "decrypt",
+ "env": "integration",
+ },
+ }
+
+ genResp, err := provider.GenerateDataKey(ctx, genReq)
+ require.NoError(t, err)
+
+ // Now decrypt it
+ decReq := &kms.DecryptRequest{
+ CiphertextBlob: genResp.CiphertextBlob,
+ EncryptionContext: map[string]string{
+ "openbao:key:name": "test-key-1",
+ "test": "decrypt",
+ "env": "integration",
+ },
+ }
+
+ decResp, err := provider.Decrypt(ctx, decReq)
+ require.NoError(t, err)
+ require.NotNil(t, decResp)
+
+ assert.Equal(t, "test-key-1", decResp.KeyID)
+ assert.Equal(t, genResp.Plaintext, decResp.Plaintext)
+ })
+
+ t.Run("DescribeKey", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ defer provider.Close()
+
+ ctx := context.Background()
+ req := &kms.DescribeKeyRequest{
+ KeyID: "test-key-1",
+ }
+
+ resp, err := provider.DescribeKey(ctx, req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ assert.Equal(t, "test-key-1", resp.KeyID)
+ assert.Contains(t, resp.ARN, "openbao:")
+ assert.Equal(t, kms.KeyStateEnabled, resp.KeyState)
+ assert.Equal(t, kms.KeyUsageEncryptDecrypt, resp.KeyUsage)
+ })
+
+ t.Run("NonExistentKey", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ defer provider.Close()
+
+ ctx := context.Background()
+ req := &kms.DescribeKeyRequest{
+ KeyID: "non-existent-key",
+ }
+
+ _, err = provider.DescribeKey(ctx, req)
+ require.Error(t, err)
+
+ kmsErr, ok := err.(*kms.KMSError)
+ require.True(t, ok)
+ assert.Equal(t, kms.ErrCodeNotFoundException, kmsErr.Code)
+ })
+
+ t.Run("MultipleKeys", func(t *testing.T) {
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err)
+ defer provider.Close()
+
+ ctx := context.Background()
+
+ // Test with multiple keys
+ testKeys := []string{"test-key-1", "test-key-2", "seaweedfs-test-key"}
+
+ for _, keyName := range testKeys {
+ t.Run(fmt.Sprintf("Key_%s", keyName), func(t *testing.T) {
+ // Generate data key
+ genReq := &kms.GenerateDataKeyRequest{
+ KeyID: keyName,
+ KeySpec: kms.KeySpecAES256,
+ EncryptionContext: map[string]string{
+ "key": keyName,
+ },
+ }
+
+ genResp, err := provider.GenerateDataKey(ctx, genReq)
+ require.NoError(t, err)
+ assert.Equal(t, keyName, genResp.KeyID)
+
+ // Decrypt data key
+ decReq := &kms.DecryptRequest{
+ CiphertextBlob: genResp.CiphertextBlob,
+ EncryptionContext: map[string]string{
+ "openbao:key:name": keyName,
+ "key": keyName,
+ },
+ }
+
+ decResp, err := provider.Decrypt(ctx, decReq)
+ require.NoError(t, err)
+ assert.Equal(t, genResp.Plaintext, decResp.Plaintext)
+ })
+ }
+ })
+}
+
+func TestOpenBaoKMSProvider_ErrorHandling(t *testing.T) {
+ // Start OpenBao server
+ _, cleanup := setupOpenBao(t)
+ defer cleanup()
+
+ setupTransitEngine(t)
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ t.Skip("Skipping invalid token test - OpenBao dev mode may be too permissive")
+
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": "invalid-token",
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ require.NoError(t, err) // Provider creation doesn't validate token
+ defer provider.Close()
+
+ ctx := context.Background()
+ req := &kms.GenerateDataKeyRequest{
+ KeyID: "test-key-1",
+ KeySpec: kms.KeySpecAES256,
+ }
+
+ _, err = provider.GenerateDataKey(ctx, req)
+ require.Error(t, err)
+
+ // Check that it's a KMS error (could be access denied or other auth error)
+ kmsErr, ok := err.(*kms.KMSError)
+ require.True(t, ok, "Expected KMSError but got: %T", err)
+ // OpenBao might return different error codes for invalid tokens
+ assert.Contains(t, []string{kms.ErrCodeAccessDenied, kms.ErrCodeKMSInternalFailure}, kmsErr.Code)
+ })
+
+}
+
+func TestKMSManager_WithOpenBao(t *testing.T) {
+ // Start OpenBao server
+ _, cleanup := setupOpenBao(t)
+ defer cleanup()
+
+ setupTransitEngine(t)
+
+ t.Run("KMSManagerIntegration", func(t *testing.T) {
+ manager := kms.InitializeKMSManager()
+
+ // Add OpenBao provider to manager
+ kmsConfig := &kms.KMSConfig{
+ Provider: "openbao",
+ Config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ CacheEnabled: true,
+ CacheTTL: time.Hour,
+ }
+
+ err := manager.AddKMSProvider("openbao-test", kmsConfig)
+ require.NoError(t, err)
+
+ // Set as default provider
+ err = manager.SetDefaultKMSProvider("openbao-test")
+ require.NoError(t, err)
+
+ // Test bucket-specific assignment
+ err = manager.SetBucketKMSProvider("test-bucket", "openbao-test")
+ require.NoError(t, err)
+
+ // Test key operations through manager
+ ctx := context.Background()
+ resp, err := manager.GenerateDataKeyForBucket(ctx, "test-bucket", "test-key-1", kms.KeySpecAES256, map[string]string{
+ "bucket": "test-bucket",
+ })
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ assert.Equal(t, "test-key-1", resp.KeyID)
+ assert.Len(t, resp.Plaintext, 32)
+
+ // Test decryption through manager
+ decResp, err := manager.DecryptForBucket(ctx, "test-bucket", resp.CiphertextBlob, map[string]string{
+ "bucket": "test-bucket",
+ })
+ require.NoError(t, err)
+ assert.Equal(t, resp.Plaintext, decResp.Plaintext)
+
+ // Test health check
+ health := manager.GetKMSHealth(ctx)
+ assert.Contains(t, health, "openbao-test")
+ assert.NoError(t, health["openbao-test"]) // Should be healthy
+
+ // Cleanup
+ manager.Close()
+ })
+}
+
+// Benchmark tests for performance
+func BenchmarkOpenBaoKMS_GenerateDataKey(b *testing.B) {
+ if testing.Short() {
+ b.Skip("Skipping benchmark in short mode")
+ }
+
+ // Start OpenBao server
+ _, cleanup := setupOpenBao(&testing.T{})
+ defer cleanup()
+
+ setupTransitEngine(&testing.T{})
+
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer provider.Close()
+
+ ctx := context.Background()
+ req := &kms.GenerateDataKeyRequest{
+ KeyID: "test-key-1",
+ KeySpec: kms.KeySpecAES256,
+ }
+
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ _, err := provider.GenerateDataKey(ctx, req)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+}
+
+func BenchmarkOpenBaoKMS_Decrypt(b *testing.B) {
+ if testing.Short() {
+ b.Skip("Skipping benchmark in short mode")
+ }
+
+ // Start OpenBao server
+ _, cleanup := setupOpenBao(&testing.T{})
+ defer cleanup()
+
+ setupTransitEngine(&testing.T{})
+
+ config := &testConfig{
+ config: map[string]interface{}{
+ "address": OpenBaoAddress,
+ "token": OpenBaoToken,
+ "transit_path": TransitPath,
+ },
+ }
+
+ provider, err := kms.GetProvider("openbao", config)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer provider.Close()
+
+ ctx := context.Background()
+
+ // Generate a data key for decryption testing
+ genResp, err := provider.GenerateDataKey(ctx, &kms.GenerateDataKeyRequest{
+ KeyID: "test-key-1",
+ KeySpec: kms.KeySpecAES256,
+ })
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ decReq := &kms.DecryptRequest{
+ CiphertextBlob: genResp.CiphertextBlob,
+ EncryptionContext: map[string]string{
+ "openbao:key:name": "test-key-1",
+ },
+ }
+
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ _, err := provider.Decrypt(ctx, decReq)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+}