package api import ( "context" "errors" "net" "os" "testing" "git.julianfamily.org/keepassgo/internal/apitokens" "git.julianfamily.org/keepassgo/internal/grpcaddr" "git.julianfamily.org/keepassgo/internal/passwords" "git.julianfamily.org/keepassgo/internal/session" "git.julianfamily.org/keepassgo/internal/vault" keepassgov1 "git.julianfamily.org/keepassgo/proto/keepassgo/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func TestStartHostServesVaultLifecycleAndSyncsSessionState(t *testing.T) { t.Parallel() lifecycle := &session.Manager{} if err := lifecycle.Create(vault.Model{ Entries: []vault.Entry{ testAPITokenEntry(t, apitokens.PolicyRule{ Effect: apitokens.EffectAllow, Operation: apitokens.OperationManageVault, Resource: apitokens.Resource{ Kind: apitokens.ResourceGroup, Path: []string{"Root"}, }, }, ), {ID: "entry-1", Title: "Vault Console", Path: []string{"Root", "Internet"}}, }, }, vault.MasterKey{Password: "correct horse battery staple"}); err != nil { t.Fatalf("Create() error = %v", err) } host, err := StartHost("127.0.0.1:0", lifecycle, passwords.DefaultProfiles(), nil, func() bool { return true }) if err != nil { t.Fatalf("StartHost() error = %v", err) } defer func() { _ = host.Stop() }() network, endpoint, err := grpcaddr.Parse(host.Address()) if err != nil { t.Fatalf("Parse(host.Address()) error = %v", err) } conn, err := grpc.NewClient("passthrough:///"+host.Address(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return net.Dial(network, endpoint) }), ) if err != nil { t.Fatalf("grpc.NewClient() error = %v", err) } defer func() { _ = conn.Close() }() client := keepassgov1.NewVaultServiceClient(conn) statusResp, err := client.GetSessionStatus(tokenContext(defaultTestTokenSecret), &keepassgov1.GetSessionStatusRequest{}) if err != nil { t.Fatalf("GetSessionStatus() error = %v", err) } if statusResp.Locked { t.Fatal("GetSessionStatus().Locked = true, want false") } if !statusResp.Dirty { t.Fatal("GetSessionStatus().Dirty = false, want true from dirty provider") } if err := lifecycle.Lock(); err != nil { t.Fatalf("Lock() error = %v", err) } if err := host.SyncFromLifecycle(); err != nil { t.Fatalf("SyncFromLifecycle() after lock error = %v", err) } statusResp, err = client.GetSessionStatus(tokenContext(defaultTestTokenSecret), &keepassgov1.GetSessionStatusRequest{}) if err != nil { t.Fatalf("GetSessionStatus() after lock error = %v", err) } if !statusResp.Locked { t.Fatal("GetSessionStatus().Locked = false, want true after lifecycle lock") } } func TestStartHostServesOverUnixSocket(t *testing.T) { t.Parallel() socketDir := t.TempDir() socketPath := socketDir + "/keepassgo.sock" lifecycle := &session.Manager{} if err := lifecycle.Create(vault.Model{ Entries: []vault.Entry{ testAPITokenEntry(t, apitokens.PolicyRule{Effect: apitokens.EffectAllow, Operation: apitokens.OperationManageVault, Resource: apitokens.Resource{Kind: apitokens.ResourceGroup, Path: []string{"Root"}}}, ), }, }, vault.MasterKey{Password: "correct horse battery staple"}); err != nil { t.Fatalf("Create() error = %v", err) } host, err := StartHost("unix://"+socketPath, lifecycle, passwords.DefaultProfiles(), nil, func() bool { return false }) if err != nil { t.Fatalf("StartHost() error = %v", err) } if got := host.Address(); got != "unix://"+socketPath { t.Fatalf("host.Address() = %q, want %q", got, "unix://"+socketPath) } if _, err := os.Stat(socketPath); err != nil { t.Fatalf("Stat(socketPath) error = %v", err) } if err := host.Stop(); err != nil { t.Fatalf("Stop() error = %v", err) } if _, err := os.Stat(socketPath); !errors.Is(err, os.ErrNotExist) { t.Fatalf("socket exists after Stop(), err = %v, want not-exist", err) } }