proxy-pool/internal/platform/credentials/store_test.go

435 lines
14 KiB
Go

package credentials
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"testing"
"time"
)
func TestMemoryStorePutCreatesIndependentLeasesForUnchangedScope(t *testing.T) {
store, err := NewMemoryStore(2)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
value := Value{Username: "alice", Password: "secret-password"}
first, err := store.Put(context.Background(), "provider-a", value)
if err != nil {
t.Fatalf("Put(first): %v", err)
}
second, err := store.Put(context.Background(), "provider-a", value)
if err != nil {
t.Fatalf("Put(second): %v", err)
}
if first == second || first.SecretRef == second.SecretRef {
t.Fatalf("references = %#v and %#v, want independent leases", first, second)
}
if first.SecretRef == "" || first.CredentialVersion != "v1" || second.CredentialVersion != "v1" {
t.Fatalf("first reference = %#v, want opaque ref at v1", first)
}
for _, plaintext := range []string{"provider-a", value.Username, value.Password} {
if strings.Contains(first.SecretRef, plaintext) {
t.Fatalf("SecretRef %q contains plaintext %q", first.SecretRef, plaintext)
}
}
got, err := store.Resolve(context.Background(), first)
if err != nil {
t.Fatalf("Resolve(): %v", err)
}
if got != value {
t.Fatalf("Resolve() = %#v, want %#v", got, value)
}
if err := store.Release(context.Background(), first); err != nil {
t.Fatalf("Release(first lease): %v", err)
}
if got, err := store.Resolve(context.Background(), second); err != nil || got != value {
t.Fatalf("Resolve(second lease) = %#v, %v", got, err)
}
}
func TestCredentialFormattingRedactsSensitiveMaterial(t *testing.T) {
value := Value{Username: "alice", Password: "secret-password"}
for _, formatted := range []string{
fmt.Sprintf("%v", value),
fmt.Sprintf("%+v", value),
fmt.Sprintf("%#v", value),
fmt.Sprintf("%s", value),
fmt.Sprintf("%q", value),
} {
if strings.Contains(formatted, value.Password) {
t.Fatalf("formatted Value exposes password: %s", formatted)
}
}
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
reference, err := store.Put(context.Background(), "provider-secret-scope", value)
if err != nil {
t.Fatalf("Put(): %v", err)
}
for _, formatted := range []string{
fmt.Sprintf("%v", store),
fmt.Sprintf("%+v", store),
fmt.Sprintf("%#v", store),
fmt.Sprintf("%s", store),
fmt.Sprintf("%q", store),
} {
for _, secret := range []string{
"provider-secret-scope",
value.Username,
value.Password,
reference.SecretRef,
} {
if strings.Contains(formatted, secret) {
t.Fatalf("formatted MemoryStore exposes sensitive material: %s", formatted)
}
}
}
}
func TestMemoryStorePutIncrementsVersionWithoutRevokingActiveLease(t *testing.T) {
store, err := NewMemoryStore(2)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
oldReference, err := store.Put(context.Background(), "provider-a", Value{
Username: "alice",
Password: "old-password",
})
if err != nil {
t.Fatalf("Put(old): %v", err)
}
want := Value{Username: "alice", Password: "new-password"}
newReference, err := store.Put(context.Background(), "provider-a", want)
if err != nil {
t.Fatalf("Put(new): %v", err)
}
if newReference.SecretRef == oldReference.SecretRef {
t.Fatalf("new SecretRef reused an active lease")
}
if newReference.CredentialVersion != "v2" {
t.Fatalf("new CredentialVersion = %q, want v2", newReference.CredentialVersion)
}
old, err := store.Resolve(context.Background(), oldReference)
if err != nil || old.Password != "old-password" {
t.Fatalf("Resolve(active old lease) = %#v, %v", old, err)
}
got, err := store.Resolve(context.Background(), newReference)
if err != nil {
t.Fatalf("Resolve(new): %v", err)
}
if got != want {
t.Fatalf("Resolve(new) returned unexpected credentials")
}
}
func TestMemoryStoreEnforcesCapacityWithoutChangingExistingCredentials(t *testing.T) {
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
want := Value{Username: "alice", Password: "first-password"}
reference, err := store.Put(context.Background(), "provider-a", want)
if err != nil {
t.Fatalf("Put(first scope): %v", err)
}
if _, err := store.Put(context.Background(), "provider-b", Value{
Username: "bob",
Password: "second-password",
}); !errors.Is(err, ErrCapacityExceeded) {
t.Fatalf("Put(over capacity) error = %v, want ErrCapacityExceeded", err)
}
got, err := store.Resolve(context.Background(), reference)
if err != nil {
t.Fatalf("Resolve(existing): %v", err)
}
if got != want {
t.Fatalf("Resolve(existing) returned changed credentials")
}
if _, err := store.Put(context.Background(), "provider-a", want); !errors.Is(err, ErrCapacityExceeded) {
t.Fatalf("Put(second lease at capacity) error = %v, want ErrCapacityExceeded", err)
}
if err := store.Release(context.Background(), reference); err != nil {
t.Fatalf("Release(first lease): %v", err)
}
if _, err := store.Put(context.Background(), "provider-a", want); err != nil {
t.Fatalf("Put(after lease release): %v", err)
}
}
func TestMemoryStoreReleaseMakesCapacityReusable(t *testing.T) {
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
first, err := store.Put(context.Background(), "provider-a", Value{Password: "first-password"})
if err != nil {
t.Fatalf("Put(first): %v", err)
}
if err := store.Release(context.Background(), first); err != nil {
t.Fatalf("Release(first): %v", err)
}
if _, err := store.Resolve(context.Background(), first); !errors.Is(err, ErrCredentialMissing) {
t.Fatalf("Resolve(released) error = %v, want ErrCredentialMissing", err)
}
if _, err := store.Put(context.Background(), "provider-b", Value{Password: "second-password"}); err != nil {
t.Fatalf("Put(after release): %v", err)
}
if err := store.Release(context.Background(), first); err != nil {
t.Fatalf("Release(idempotent): %v", err)
}
}
func TestMemoryStoreReleaseOfStaleReferencePreservesCurrentVersion(t *testing.T) {
store, err := NewMemoryStore(2)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
stale, err := store.Put(context.Background(), "provider-a", Value{Password: "old-password"})
if err != nil {
t.Fatalf("Put(old): %v", err)
}
current, err := store.Put(context.Background(), "provider-a", Value{Password: "new-password"})
if err != nil {
t.Fatalf("Put(new): %v", err)
}
if err := store.Release(context.Background(), stale); err != nil {
t.Fatalf("Release(stale): %v", err)
}
if _, err := store.Resolve(context.Background(), current); err != nil {
t.Fatalf("Resolve(current): %v", err)
}
}
func TestMemoryStoreEnsureCapacityOnlyGrowsLimit(t *testing.T) {
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
if err := store.EnsureCapacity(context.Background(), 2); err != nil {
t.Fatalf("EnsureCapacity(2): %v", err)
}
if _, err := store.Put(context.Background(), "provider-a", Value{}); err != nil {
t.Fatalf("Put(provider-a): %v", err)
}
if _, err := store.Put(context.Background(), "provider-b", Value{}); err != nil {
t.Fatalf("Put(provider-b): %v", err)
}
if err := store.EnsureCapacity(context.Background(), 1); err != nil {
t.Fatalf("EnsureCapacity(shrink request): %v", err)
}
if _, err := store.Put(context.Background(), "provider-c", Value{}); !errors.Is(err, ErrCapacityExceeded) {
t.Fatalf("Put(provider-c) error = %v, want retained capacity 2", err)
}
}
func TestMemoryStoreStrictlyValidatesCredentialVersion(t *testing.T) {
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
reference, err := store.Put(context.Background(), "provider-a", Value{Password: "password"})
if err != nil {
t.Fatalf("Put(): %v", err)
}
for _, version := range []string{"", "v0", "v01", "1", "latest", "v-1"} {
invalid := reference
invalid.CredentialVersion = version
if _, err := store.Resolve(context.Background(), invalid); !errors.Is(err, ErrInvalidReference) {
t.Fatalf("Resolve(version %q) error = %v, want ErrInvalidReference", version, err)
}
}
if _, err := store.Put(context.Background(), " \t\n", Value{}); !errors.Is(err, ErrInvalidScope) {
t.Fatalf("Put(blank scope) error = %v, want ErrInvalidScope", err)
}
}
func TestMemoryStoreValidatesCapacityAndHonorsCancellation(t *testing.T) {
if _, err := NewMemoryStore(0); !errors.Is(err, ErrInvalidCapacity) {
t.Fatalf("NewMemoryStore(0) error = %v, want ErrInvalidCapacity", err)
}
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(1): %v", err)
}
canceled, cancel := context.WithCancel(context.Background())
cancel()
if _, err := store.Put(canceled, "provider-a", Value{}); !errors.Is(err, context.Canceled) {
t.Fatalf("Put(canceled) error = %v, want context.Canceled", err)
}
if _, err := store.Resolve(canceled, Reference{
SecretRef: "cred_000000000000000000000000000000000000000000000000",
CredentialVersion: "v1",
}); !errors.Is(err, context.Canceled) {
t.Fatalf("Resolve(canceled) error = %v, want context.Canceled", err)
}
}
func TestMemoryStoreRejectsNilAndZeroValueStores(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
for _, store := range []*MemoryStore{nil, {}} {
if _, err := store.Put(ctx, "provider-a", Value{}); !errors.Is(err, ErrInvalidStore) {
t.Fatalf("Put() error = %v, want ErrInvalidStore", err)
}
if _, err := store.Resolve(ctx, Reference{
SecretRef: "cred_000000000000000000000000000000000000000000000000",
CredentialVersion: "v1",
}); !errors.Is(err, ErrInvalidStore) {
t.Fatalf("Resolve() error = %v, want ErrInvalidStore", err)
}
}
}
func TestMemoryStoreCreatesIndependentConcurrentLeases(t *testing.T) {
const workers = 100
store, err := NewMemoryStore(workers)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
want := Value{Username: "alice", Password: "shared-password"}
references := make(chan Reference, workers)
errorsSeen := make(chan error, workers)
var wait sync.WaitGroup
for range workers {
wait.Add(1)
go func() {
defer wait.Done()
reference, err := store.Put(context.Background(), "provider-a", want)
if err != nil {
errorsSeen <- err
return
}
references <- reference
}()
}
wait.Wait()
close(references)
close(errorsSeen)
for err := range errorsSeen {
t.Errorf("concurrent Put(): %v", err)
}
secretRefs := make(map[string]struct{}, workers)
for reference := range references {
if reference.CredentialVersion != "v1" {
t.Errorf("concurrent version = %q, want v1", reference.CredentialVersion)
}
secretRefs[reference.SecretRef] = struct{}{}
}
if len(secretRefs) != workers {
t.Fatalf("unique concurrent leases = %d, want %d", len(secretRefs), workers)
}
for secretRef := range secretRefs {
got, err := store.Resolve(context.Background(), Reference{
SecretRef: secretRef, CredentialVersion: "v1",
})
if err != nil || got != want {
t.Fatalf("Resolve(concurrent lease) = %#v, %v", got, err)
}
}
}
func TestMemoryStoreSerializesConcurrentCredentialChanges(t *testing.T) {
const workers = 100
store, err := NewMemoryStore(workers)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
type result struct {
value Value
reference Reference
err error
}
results := make(chan result, workers)
var wait sync.WaitGroup
for index := range workers {
wait.Add(1)
go func() {
defer wait.Done()
value := Value{Username: "alice", Password: "password-" + strconv.Itoa(index)}
reference, err := store.Put(context.Background(), "provider-a", value)
results <- result{value: value, reference: reference, err: err}
}()
}
wait.Wait()
close(results)
versions := make(map[string]struct{}, workers)
secretRefs := make(map[string]struct{}, workers)
var latest result
for current := range results {
if current.err != nil {
t.Fatalf("concurrent Put(): %v", current.err)
}
secretRefs[current.reference.SecretRef] = struct{}{}
versions[current.reference.CredentialVersion] = struct{}{}
if current.reference.CredentialVersion == "v100" {
latest = current
}
}
if len(versions) != workers {
t.Fatalf("unique versions = %d, want %d", len(versions), workers)
}
if len(secretRefs) != workers {
t.Fatalf("unique leases = %d, want %d", len(secretRefs), workers)
}
if latest.reference == (Reference{}) {
t.Fatal("highest version v100 was not returned")
}
got, err := store.Resolve(context.Background(), latest.reference)
if err != nil {
t.Fatalf("Resolve(latest): %v", err)
}
if got != latest.value {
t.Fatal("Resolve(latest) returned a different concurrent write")
}
}
func TestMemoryStoreErrorsDoNotExposeInputs(t *testing.T) {
store, err := NewMemoryStore(1)
if err != nil {
t.Fatalf("NewMemoryStore(): %v", err)
}
value := Value{Username: "sensitive-user", Password: "sensitive-password"}
reference, err := store.Put(context.Background(), "sensitive-scope", value)
if err != nil {
t.Fatalf("Put(): %v", err)
}
_, capacityErr := store.Put(context.Background(), "other-sensitive-scope", value)
unknown := Reference{
SecretRef: "sensitive-secret-ref",
CredentialVersion: "v1",
}
_, missingErr := store.Resolve(context.Background(), unknown)
stale := reference
stale.CredentialVersion = "v2"
_, versionErr := store.Resolve(context.Background(), stale)
for _, operationErr := range []error{capacityErr, missingErr, versionErr} {
if operationErr == nil {
t.Fatal("operation unexpectedly succeeded")
}
for _, sensitive := range []string{
"sensitive-scope",
"other-sensitive-scope",
value.Username,
value.Password,
unknown.SecretRef,
reference.SecretRef,
} {
if strings.Contains(operationErr.Error(), sensitive) {
t.Fatalf("error exposes sensitive input: %v", operationErr)
}
}
}
}