返回 DeepSeek-Reasonix
dial_hardening_test.go
根目录 / internal / remote / dial_hardening_test.go
1 package remote
2
3 import (
4 "context"
5 "errors"
6 "net"
7 "testing"
8 "time"
9 )
10
11 func TestClientUsesResolvedPerHopCredentials(t *testing.T) {
12 targetAuth := AuthOptions{Password: func() (string, error) { return "target", nil }}
13 hopAuth := AuthOptions{Password: func() (string, error) { return "jump", nil }}
14 c, err := New(Options{
15 Host: ResolvedHost{HostName: "target", Port: 22, User: "target-user", ProxyJump: []string{"bastion"}},
16 Auth: targetAuth,
17 JumpHosts: []JumpHostOptions{{Host: ResolvedHost{HostName: "10.0.0.8", Port: 2202, User: "jump-user"}, Auth: hopAuth}},
18 })
19 if err != nil {
20 t.Fatal(err)
21 }
22 hop, auth, err := c.resolveHop("bastion")
23 if err != nil {
24 t.Fatal(err)
25 }
26 if hop.Addr() != "10.0.0.8:2202" || hop.User != "jump-user" {
27 t.Fatalf("resolved hop = %+v", hop)
28 }
29 if auth.Password == nil {
30 t.Fatal("configured jump password was dropped")
31 }
32 jumpSecret, err := auth.Password()
33 if err != nil || jumpSecret != "jump" {
34 t.Fatalf("jump password = %q, %v", jumpSecret, err)
35 }
36 targetSecret, _ := targetAuth.Password()
37 if jumpSecret == targetSecret {
38 t.Fatal("jump auth reused the target credential")
39 }
40 }
41
42 func TestClientKeepsAliasCredentialsDistinctForSharedEndpoint(t *testing.T) {
43 password := func(value string) func() (string, error) {
44 return func() (string, error) { return value, nil }
45 }
46 endpoint := ResolvedHost{HostName: "10.0.0.8", Port: 22, User: "jump-user"}
47 c, err := New(Options{
48 Host: ResolvedHost{HostName: "target", Port: 22, User: "target-user", ProxyJump: []string{"primary", "backup"}},
49 JumpHosts: []JumpHostOptions{
50 {Host: endpoint, Auth: AuthOptions{Password: password("primary-secret")}},
51 {Host: endpoint, Auth: AuthOptions{Password: password("backup-secret")}},
52 },
53 })
54 if err != nil {
55 t.Fatal(err)
56 }
57 _, primary, err := c.resolveHop("primary")
58 if err != nil {
59 t.Fatal(err)
60 }
61 _, backup, err := c.resolveHop("backup")
62 if err != nil {
63 t.Fatal(err)
64 }
65 primarySecret, _ := primary.Password()
66 backupSecret, _ := backup.Password()
67 if primarySecret != "primary-secret" || backupSecret != "backup-secret" {
68 t.Fatalf("shared endpoint credentials collided: primary=%q backup=%q", primarySecret, backupSecret)
69 }
70 }
71
72 type noDeadlineConn struct {
73 net.Conn
74 deadlineSet chan struct{}
75 }
76
77 func (c noDeadlineConn) SetDeadline(time.Time) error {
78 select {
79 case c.deadlineSet <- struct{}{}:
80 default:
81 }
82 return errors.New("unsupported")
83 }
84 func (noDeadlineConn) RemoteAddr() net.Addr {
85 return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 22}
86 }
87 func (noDeadlineConn) SetReadDeadline(time.Time) error { return errors.New("unsupported") }
88 func (noDeadlineConn) SetWriteDeadline(time.Time) error { return errors.New("unsupported") }
89
90 func TestSSHHandshakeHonorsContextWhenDeadlinesUnsupported(t *testing.T) {
91 client, server := net.Pipe()
92 defer client.Close()
93 defer server.Close()
94 ctx, cancel := context.WithCancel(context.Background())
95 defer cancel()
96 done := make(chan error, 1)
97 deadlineSet := make(chan struct{}, 1)
98 go func() {
99 _, err := newSSHClient(ctx, noDeadlineConn{Conn: client, deadlineSet: deadlineSet},
100 ResolvedHost{HostName: "target", Port: 22, User: "u", IdentityFileNone: true},
101 &AuthOptions{DisableAgent: true}, &HostKeyPolicy{}, time.Second)
102 done <- err
103 }()
104 // SetDeadline runs after the cancellation watcher is installed and directly
105 // before the SSH handshake. Wait for that event instead of asserting that a
106 // loaded Windows runner schedules a 40 ms timer within a 500 ms wall clock.
107 select {
108 case <-deadlineSet:
109 case err := <-done:
110 t.Fatalf("handshake returned before installing its cancellation watcher: %v", err)
111 case <-time.After(5 * time.Second):
112 t.Fatal("handshake did not install its cancellation watcher")
113 }
114 cancel()
115 select {
116 case err := <-done:
117 if err == nil {
118 t.Fatal("banner-less handshake unexpectedly succeeded")
119 }
120 case <-time.After(5 * time.Second):
121 t.Fatal("handshake outlived its context on a ProxyJump-style connection")
122 }
123 }
124
124 lines GO