package vault import ( "context" "encoding/json" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" ) func TestIssuerLifetimeEncodingAndCleanup(t *testing.T) { for _, test := range []struct { name string identityTTL, databaseTTL int reject bool wantError bool }{ {"identity limits lease", 4, 20, false, false}, {"database limits lease", 20, 4, false, false}, {"invalid identity", 0, 20, false, true}, {"invalid database lifetime", 20, 0, false, true}, {"rejected issuance", 20, 20, true, true}, } { t.Run(test.name, func(t *testing.T) { var revoked atomic.Bool server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/auth/approle/login": _ = json.NewEncoder(w).Encode(map[string]any{"auth": map[string]any{"client_token": "private-token", "lease_duration": test.identityTTL}}) case "/v1/database/creds/bff": if test.reject { http.Error(w, "private-password", 403) return } _ = json.NewEncoder(w).Encode(map[string]any{"lease_id": "lease", "lease_duration": test.databaseTTL, "data": map[string]string{"username": "user@host", "password": "p@ss:/?#"}}) case "/v1/auth/token/revoke-self": if r.Header.Get("X-Vault-Token") == "private-token" { revoked.Store(true) } w.WriteHeader(204) default: w.WriteHeader(404) } })) defer server.Close() directory := t.TempDir() identity, secret := filepath.Join(directory, "identity"), filepath.Join(directory, "secret") require.NoError(t, os.WriteFile(identity, []byte("identity"), 0600)) require.NoError(t, os.WriteFile(secret, []byte("secret"), 0600)) issuer, err := New(Config{Address: server.URL, DatabaseRole: "bff", RoleIDFile: identity, SecretIDFile: secret}, "mongodb://localhost/db?authSource=admin") require.NoError(t, err) started := time.Now() lease, err := issuer.Issue(context.Background()) if test.wantError { require.Error(t, err) require.NotContains(t, err.Error(), "private-") require.True(t, revoked.Load()) return } require.NoError(t, err) require.WithinDuration(t, started.Add(4*time.Second), lease.ExpiresAt, time.Second) parsed, err := url.Parse(lease.URI) require.NoError(t, err) require.Equal(t, "user@host", parsed.User.Username()) password, _ := parsed.User.Password() require.Equal(t, "p@ss:/?#", password) require.False(t, revoked.Load()) require.NoError(t, lease.Revoke(context.Background())) require.True(t, revoked.Load()) }) } }