Ctrl K
ComunidadGuías…

internal/mcpgateway/store_sealed_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package mcpgateway

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/enterpilot/gomodel/config"
	"github.com/enterpilot/gomodel/internal/encryption"
	"github.com/enterpilot/gomodel/internal/encryption/encryptiontest"
)

func sealedTestServer(name string, headers map[string]string) ManagedServer {
	return ManagedServer{
		Name:      name,
		URL:       "https://mcp.example.com/mcp",
		Transport: "http",
		Headers:   headers,
		Enabled:   true,
	}
}

func TestSealedStoreEncryptsHeaderValues(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, _ := encryptiontest.NewBox(t)
		store := sealStore(raw, box)

		headers := map[string]string{"Authorization": "Bearer secret", "X-Team": "alpha"}
		require.NoError(t, store.Upsert(ctx, sealedTestServer("github", headers)))
		assert.Equal(t, "Bearer secret", headers["Authorization"], "the caller's map is not overwritten with ciphertext")

		stored, err := raw.Get(ctx, "github")
		require.NoError(t, err)
		require.Len(t, stored.Headers, 2)
		for name, value := range stored.Headers {
			assert.True(t, encryption.IsSealed(value), "header %s is not sealed", name)
		}

		got, err := store.Get(ctx, "github")
		require.NoError(t, err)
		assert.Equal(t, headers, got.Headers)

		list, err := store.List(ctx)
		require.NoError(t, err)
		require.Len(t, list, 1)
		assert.Equal(t, headers, list[0].Headers)

		// A header value moved to another header name or server fails.
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("gitlab", map[string]string{"Authorization": stored.Headers["Authorization"]})))
		_, err = store.Get(ctx, "gitlab")
		require.Error(t, err)
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("github", map[string]string{"X-Team": stored.Headers["Authorization"]})))
		_, err = store.Get(ctx, "github")
		require.Error(t, err)
	})
}

func TestSealedStoreReadsPlaintextHeaders(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer legacy"})))

		box, _ := encryptiontest.NewBox(t)
		got, err := sealStore(raw, box).Get(ctx, "github")
		require.NoError(t, err)
		assert.Equal(t, "Bearer legacy", got.Headers["Authorization"])
	})
}

func TestReencryptHeaders(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, keys := encryptiontest.NewBox(t)
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("plain", map[string]string{"Authorization": "Bearer plain"})))
		require.NoError(t, sealStore(raw, box).Upsert(ctx, sealedTestServer("old-key", map[string]string{"Authorization": "Bearer old"})))
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("no-headers", nil)))

		sealed := &sealedStore{Store: raw, box: encryptiontest.Rotate(t, keys)}
		report, err := sealed.reencrypt(ctx, raw.(headerSwapper))
		require.NoError(t, err)
		assert.Equal(t, encryption.Report{Entity: "mcp_servers", Rows: 3, Reencrypted: 2}, report)

		for name, want := range map[string]string{"plain": "Bearer plain", "old-key": "Bearer old"} {
			stored, err := raw.Get(ctx, name)
			require.NoError(t, err)
			assert.True(t, sealed.box.IsCurrent(stored.Headers["Authorization"]))
			got, err := sealed.Get(ctx, name)
			require.NoError(t, err)
			assert.Equal(t, want, got.Headers["Authorization"])
		}

		again, err := sealed.reencrypt(ctx, raw.(headerSwapper))
		require.NoError(t, err)
		assert.Equal(t, 0, again.Reencrypted)
	})
}

func TestSwapHeadersIsConditional(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("github", map[string]string{"Authorization": "a", "X-Team": "t"})))
		current, err := raw.Get(ctx, "github")
		require.NoError(t, err)

		swap := raw.(headerSwapper)
		next := *current
		next.Headers = map[string]string{"Authorization": "b", "X-Team": "t2"}
		next.Description = "ignored"
		swapped, err := swap.swapHeaders(ctx, *current, next)
		require.NoError(t, err)
		assert.True(t, swapped)
		got, err := raw.Get(ctx, "github")
		require.NoError(t, err)
		assert.Equal(t, next.Headers, got.Headers)
		assert.Empty(t, got.Description)

		swapped, err = swap.swapHeaders(ctx, *current, next)
		require.NoError(t, err)
		assert.False(t, swapped, "a stale read does not match")
	})
}

type rotateDuringUpsert struct {
	Store
	rotate func()
}

func (s rotateDuringUpsert) Upsert(ctx context.Context, server ManagedServer) error {
	s.rotate()
	return s.Store.Upsert(ctx, server)
}

func (s rotateDuringUpsert) Update(ctx context.Context, server ManagedServer) error {
	s.rotate()
	return s.Store.Update(ctx, server)
}

func TestSealedStoreResealsSaveThatRacedARotation(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, keys := encryptiontest.NewBox(t)
		store := &sealedStore{
			Store: rotateDuringUpsert{Store: raw, rotate: func() { encryptiontest.Rotate(t, keys) }},
			box:   box,
			swap:  raw.(headerSwapper),
		}
		require.NoError(t, store.Upsert(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer late"})))

		stored, err := raw.Get(ctx, "github")
		require.NoError(t, err)
		assert.Regexp(t, `^enc:v1:2:`, stored.Headers["Authorization"])
	})
}

func TestReencryptHeadersWithDottedNames(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		headers := map[string]string{"X.Api.Key": "k", "$Odd": "o", "Authorization": "Bearer a"}
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("dotted", headers)))

		box, _ := encryptiontest.NewBox(t)
		report, err := (&sealedStore{Store: raw, box: box}).reencrypt(ctx, raw.(headerSwapper))
		require.NoError(t, err)
		assert.Equal(t, encryption.Report{Entity: "mcp_servers", Rows: 1, Reencrypted: 1}, report)

		stored, err := raw.Get(ctx, "dotted")
		require.NoError(t, err)
		for name, value := range stored.Headers {
			assert.True(t, box.IsCurrent(value), "header %s is sealed", name)
		}
		got, err := sealStore(raw, box).Get(ctx, "dotted")
		require.NoError(t, err)
		assert.Equal(t, headers, got.Headers)
	})
}

func TestSealedStoreReportsAnUnconfirmedDataKey(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, keys := encryptiontest.NewBox(t)
		store := &sealedStore{
			Store: rotateDuringUpsert{Store: raw, rotate: func() { keys.FailLists(3) }},
			box:   box,
			swap:  raw.(headerSwapper),
		}
		err := store.Upsert(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer unconfirmed"}))
		require.ErrorIs(t, err, encryption.ErrSealUnconfirmed)
		assert.NotContains(t, err.Error(), "Bearer unconfirmed")

		stored, err := raw.Get(ctx, "github")
		require.NoError(t, err)
		assert.True(t, encryption.IsSealed(stored.Headers["Authorization"]), "the row was saved sealed")
	})
}

func TestSealedStoreUpdateSealsHeaders(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, _ := encryptiontest.NewBox(t)
		store := sealStore(raw, box)
		require.ErrorIs(t, store.Update(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer new"})), ErrNotFound)

		require.NoError(t, store.Upsert(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer old"})))
		require.NoError(t, store.Update(ctx, sealedTestServer("github", map[string]string{"Authorization": "Bearer new"})))

		stored, err := raw.Get(ctx, "github")
		require.NoError(t, err)
		assert.True(t, encryption.IsSealed(stored.Headers["Authorization"]), "Update does not write headers in plaintext")
		got, err := store.Get(ctx, "github")
		require.NoError(t, err)
		assert.Equal(t, "Bearer new", got.Headers["Authorization"])
	})
}

func assertHeadersSealed(t *testing.T, raw Store, name string) {
	t.Helper()
	stored, err := raw.Get(context.Background(), name)
	require.NoError(t, err)
	require.NotEmpty(t, stored.Headers)
	for header, value := range stored.Headers {
		assert.True(t, encryption.IsSealed(value), "header %s of %s is stored in plaintext", header, name)
	}
}

func TestNoWritePathStoresPlaintextHeaders(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, keys := encryptiontest.NewBox(t)
		racing := &sealedStore{
			Store: rotateDuringUpsert{Store: raw, rotate: func() { encryptiontest.Rotate(t, keys) }},
			box:   box,
			swap:  raw.(headerSwapper),
		}
		store := sealStore(raw, box)
		headers := func(i int) map[string]string {
			return map[string]string{"Authorization": fmt.Sprintf("Bearer %d", i), "X.Dotted": "d"}
		}
		writes := map[string]func(name string) error{
			"upsert": func(name string) error { return store.Upsert(ctx, sealedTestServer(name, headers(1))) },
			"update": func(name string) error {
				require.NoError(t, raw.Upsert(ctx, sealedTestServer(name, map[string]string{"Authorization": "Bearer legacy"})))
				return store.Update(ctx, sealedTestServer(name, headers(2)))
			},
			"upsert-racing-rotation": func(name string) error { return racing.Upsert(ctx, sealedTestServer(name, headers(3))) },
			"update-racing-rotation": func(name string) error {
				require.NoError(t, raw.Upsert(ctx, sealedTestServer(name, nil)))
				return racing.Update(ctx, sealedTestServer(name, headers(4)))
			},
		}
		for name, write := range writes {
			require.NoError(t, write(name), name)
			stored, err := raw.Get(ctx, name)
			require.NoError(t, err)
			assert.True(t, box.IsCurrent(stored.Headers["Authorization"]), "%s ends up under the data key active after the save", name)
		}
		for name := range writes {
			assertHeadersSealed(t, raw, name)
		}
	})
}

func TestServiceEditOfVirtualNamedServerSealsHeaders(t *testing.T) {
	runStoreSuite(t, func(t *testing.T, raw Store) {
		ctx := context.Background()
		box, _ := encryptiontest.NewBox(t)
		// The row predates the virtual server, so the edit goes through Update.
		require.NoError(t, raw.Upsert(ctx, sealedTestServer("coding", map[string]string{"Authorization": "Bearer legacy"})))
		service, err := NewService(ctx, Options{
			Store:          sealStore(raw, box),
			VirtualServers: map[string]VirtualServerSpec{"coding": {Name: "coding", Servers: []string{"alpha"}}},
		})
		require.NoError(t, err)
		t.Cleanup(service.Close)

		server := sealedTestServer("coding", map[string]string{"Authorization": "Bearer edited"})
		server.Transport = config.MCPTransportHTTP
		require.NoError(t, service.Upsert(ctx, server))
		assertHeadersSealed(t, raw, "coding")
	})
}