返回 DeepSeek-Reasonix
client_agent_unix_test.go
根目录 / internal / remote / client_agent_unix_test.go
1 //go:build !windows
2
3 package remote
4
5 import (
6 "context"
7 "net"
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "golang.org/x/crypto/ssh"
14 "golang.org/x/crypto/ssh/agent"
15
16 "reasonix/internal/remote/sshtest"
17 )
18
19 func TestClientFallsBackFromEmptyAgentToIdentityFile(t *testing.T) {
20 pemBytes, authorized, err := sshtest.GenerateKeyPEM()
21 if err != nil {
22 t.Fatal(err)
23 }
24 srv := sshtest.Start(t, sshtest.Options{AuthorizedKey: authorized})
25 keyPath := filepath.Join(t.TempDir(), "id_ed25519")
26 if err := writeFile0600(keyPath, pemBytes); err != nil {
27 t.Fatal(err)
28 }
29
30 // A running but empty agent reproduces the desktop failure: the first
31 // publickey source has no signers, so the explicit identity must be tried
32 // as a second publickey attempt.
33 agentDir, err := os.MkdirTemp("", "reasonix-agent-")
34 if err != nil {
35 t.Fatal(err)
36 }
37 t.Cleanup(func() { _ = os.RemoveAll(agentDir) })
38 sock := filepath.Join(agentDir, "agent.sock")
39 listener, err := net.Listen("unix", sock)
40 if err != nil {
41 t.Fatal(err)
42 }
43 t.Cleanup(func() { _ = listener.Close() })
44 go func() {
45 conn, acceptErr := listener.Accept()
46 if acceptErr != nil {
47 return
48 }
49 defer conn.Close()
50 _ = agent.ServeAgent(agent.NewKeyring(), conn)
51 }()
52 t.Setenv("SSH_AUTH_SOCK", sock)
53
54 c := newTestClient(t, srv, Options{})
55 c.opts.Host.IdentityFile = keyPath
56
57 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
58 defer cancel()
59 if err := c.Start(ctx); err != nil {
60 t.Fatalf("Start with empty agent and explicit identity: %v", err)
61 }
62 defer c.Close()
63 if c.Status().Status != StatusConnected {
64 t.Fatalf("status = %v, want connected", c.Status().Status)
65 }
66 }
67
68 func TestIdentitiesOnlyUsesOnlyConfiguredAgentIdentity(t *testing.T) {
69 correctPEM, authorized, err := sshtest.GenerateKeyPEM()
70 if err != nil {
71 t.Fatal(err)
72 }
73 wrongPEM, _, err := sshtest.GenerateKeyPEM()
74 if err != nil {
75 t.Fatal(err)
76 }
77 srv := sshtest.Start(t, sshtest.Options{AuthorizedKey: authorized})
78 keyPath := filepath.Join(t.TempDir(), "id_ed25519.pub")
79 if err := writeFile0600(keyPath, ssh.MarshalAuthorizedKey(authorized)); err != nil {
80 t.Fatal(err)
81 }
82 keyring := agent.NewKeyring()
83 for _, pemBytes := range [][]byte{wrongPEM, correctPEM} {
84 privateKey, err := ssh.ParseRawPrivateKey(pemBytes)
85 if err != nil {
86 t.Fatal(err)
87 }
88 if err := keyring.Add(agent.AddedKey{PrivateKey: privateKey}); err != nil {
89 t.Fatal(err)
90 }
91 }
92
93 agentDir, err := os.MkdirTemp("", "reasonix-identities-only-")
94 if err != nil {
95 t.Fatal(err)
96 }
97 t.Cleanup(func() { _ = os.RemoveAll(agentDir) })
98 sock := filepath.Join(agentDir, "agent.sock")
99 listener, err := net.Listen("unix", sock)
100 if err != nil {
101 t.Fatal(err)
102 }
103 t.Cleanup(func() { _ = listener.Close() })
104 go func() {
105 for {
106 conn, acceptErr := listener.Accept()
107 if acceptErr != nil {
108 return
109 }
110 go func() {
111 defer conn.Close()
112 _ = agent.ServeAgent(keyring, conn)
113 }()
114 }
115 }()
116 t.Setenv("SSH_AUTH_SOCK", sock)
117
118 c := newTestClient(t, srv, Options{})
119 c.opts.Host.IdentityFile = keyPath
120 c.opts.Host.IdentityFiles = []string{keyPath}
121 c.opts.Host.IdentitiesOnly = true
122 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
123 defer cancel()
124 if err := c.Start(ctx); err != nil {
125 t.Fatalf("Start with configured agent identity and IdentitiesOnly: %v", err)
126 }
127 defer c.Close()
128 if c.Status().Status != StatusConnected {
129 t.Fatalf("status = %v, want connected", c.Status().Status)
130 }
131 }
132
132 lines GO