|
3 | 3 | This package enables users to: |
4 | 4 |
|
5 | 5 | - Establish a tunnel to a remote server via SSH |
6 | | -- Use the remote server as a SOCKS5 proxy via SSH |
7 | 6 | - Verify SSH host keys for improved security |
| 7 | +- Graceful shutdown with context cancellation |
| 8 | +- Configurable connection limits and timeouts |
| 9 | +- Improved performance with buffered data transfer |
| 10 | + |
| 11 | +## Usage |
| 12 | + |
| 13 | +The package provides a simple API for creating SSH tunnels: |
| 14 | + |
| 15 | +```go |
| 16 | +// Create tunnel configuration |
| 17 | +config := &sshts.TunnelConfig{ |
| 18 | + User: "username", |
| 19 | + AuthMethods: []ssh.AuthMethod{ |
| 20 | + ssh.PublicKeys(signer), |
| 21 | + }, |
| 22 | + HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Or use knownhosts for security |
| 23 | +} |
| 24 | + |
| 25 | +// Create tunnel |
| 26 | +tunnel := sshts.NewTunnel( |
| 27 | + "localhost:8080", // Local address to listen on |
| 28 | + "localhost:80", // Remote address to forward to |
| 29 | + config, |
| 30 | +) |
| 31 | + |
| 32 | +// Start tunnel with context |
| 33 | +ctx, cancel := context.WithCancel(context.Background()) |
| 34 | +tunnelCancel, err := tunnel.Start(ctx, "ssh-server.example.com:22") |
| 35 | +if err != nil { |
| 36 | + log.Fatal("Failed to start tunnel:", err) |
| 37 | +} |
| 38 | + |
| 39 | +// To stop the tunnel |
| 40 | +tunnelCancel() |
| 41 | +tunnel.Close() |
| 42 | +``` |
8 | 43 |
|
9 | 44 | ## Host Key Verification |
10 | 45 |
|
11 | | -The package now supports proper SSH host key verification, which is crucial for security. There are three ways to verify host keys: |
| 46 | +The package supports proper SSH host key verification for security: |
12 | 47 |
|
13 | 48 | 1. **Insecure method (default)**: Uses `ssh.InsecureIgnoreHostKey()` - not recommended for production |
14 | 49 | 2. **Known hosts file**: Verifies host keys against a `known_hosts` file (recommended) |
15 | | -3. **Custom callback**: Provide your own `ssh.HostKeyCallback` function |
16 | 50 |
|
17 | 51 | Example using known hosts verification: |
18 | 52 | ```go |
19 | | -sshC, err := sshts.NewWithKnownHosts("username", "/path/to/private/key", "server:port", "/path/to/known_hosts") |
| 53 | +hostKeyCallback, err := knownhosts.New("/path/to/known_hosts") |
| 54 | +if err != nil { |
| 55 | + log.Fatal("Failed to load known_hosts file:", err) |
| 56 | +} |
| 57 | + |
| 58 | +config := &sshts.TunnelConfig{ |
| 59 | + User: "username", |
| 60 | + AuthMethods: []ssh.AuthMethod{ |
| 61 | + ssh.PublicKeys(signer), |
| 62 | + }, |
| 63 | + HostKeyCallback: hostKeyCallback, // Secure host key verification |
| 64 | +} |
20 | 65 | ``` |
21 | 66 |
|
22 | 67 | To set up host key verification: |
23 | | - |
24 | 68 | 1. Create a `known_hosts` file (usually located at `~/.ssh/known_hosts`) |
25 | 69 | 2. Add the remote server's host key to this file |
26 | | -3. Use `NewWithKnownHosts()` instead of `New()` |
| 70 | +3. Use `knownhosts.New()` to create a host key callback |
27 | 71 |
|
28 | 72 | You can manually add entries to known_hosts or use ssh-keyscan: |
29 | 73 | ```bash |
30 | 74 | ssh-keyscan server.example.com >> ~/.ssh/known_hosts |
31 | 75 | ``` |
32 | | -Here's an example of how to use this package: |
33 | | -```go |
34 | | -package main |
35 | | - |
36 | | -import ( |
37 | | - "fmt" |
38 | | - "io/ioutil" |
39 | | - "log" |
40 | | - "net/http" |
41 | | - "net/url" |
42 | | - "os" |
43 | | - "os/signal" |
44 | | - "syscall" |
45 | | - |
46 | | - "golang.org/x/crypto/ssh" |
47 | | - "golang.org/x/crypto/ssh/knownhosts" |
48 | | - "github.com/kslamph/sshts" |
49 | | -) |
50 | | - |
51 | | -func main() { |
52 | | - tunnelexample() |
53 | | -} |
54 | 76 |
|
55 | | -/* tunnel exmaple: |
56 | | -there is a remote server 101.32.14.95 , we have ssh access to it; |
57 | | -there is a postgres sql server running on it and listen on localhost:5432; |
58 | | -and this 101.32.14.95:5432 is not accessible from outside. |
59 | | -
|
60 | | -we want to access the postgres server by localhost:15432 |
61 | | -
|
62 | | -this setup increased the security of the postgres server, because it is not accessible from outside, and we can only access it by ssh tunneling. |
63 | | -there are 3 advantages of this setup: |
64 | | -using key to access ssh server is a proven safe way to access remote server |
65 | | -ssh tunneling is encrypted, so the data is safe at transport layer |
66 | | -we dont have to expose the postgres server interfaces other than localhost, so it is more secure |
67 | | -*/ |
68 | | - |
69 | | -func tunnelexample() { |
70 | | - //address and port of ssh server to connect to |
71 | | - sshAddress := "101.32.14.95:22" |
72 | | - |
73 | | - //local port to listen on |
74 | | - localTunnel := "localhost:15432" |
75 | | - |
76 | | - //server to tunnel to on remote, usually localhost but can be any address |
77 | | - remoteTunnel := "localhost:5432" |
78 | | - |
79 | | - // Method 1: Using the default insecure method (not recommended for production) |
80 | | - // sshC, err := sshts.New("root", "/home/k/.ssh/id_rsa", sshAddress) |
81 | | - |
82 | | - // Method 2: Using a custom host key callback |
83 | | - // hostKeyCallback := ssh.FixedHostKey(theHostKey) // where theHostKey is of type ssh.PublicKey |
84 | | - // sshC, err := sshts.NewWithHostKeyCallback("root", "/home/k/.ssh/id_rsa", sshAddress, hostKeyCallback) |
85 | | - |
86 | | - // Method 3: Using known_hosts file verification (recommended) |
87 | | - sshC, err := sshts.NewWithKnownHosts("root", "/home/k/.ssh/id_rsa", sshAddress, "/home/k/.ssh/known_hosts") |
88 | | - if err != nil { |
89 | | - log.Fatal(err) |
90 | | - } |
91 | | - err = sshC.Connect() |
92 | | - if err != nil { |
93 | | - log.Fatal(err) |
94 | | - } |
95 | | - defer sshC.Close() |
96 | | - |
97 | | - go sshC.StartTunnel(localTunnel, remoteTunnel) |
98 | | - fmt.Println("press ctrl+c to exit") |
99 | | - ch := make(chan os.Signal) |
100 | | - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) |
101 | | - <-ch |
102 | | - fmt.Println("exiting") |
| 77 | +## Features |
103 | 78 |
|
104 | | -} |
| 79 | +### Graceful Shutdown |
| 80 | +The package supports context cancellation for graceful shutdowns. When the connection is closed, all goroutines are properly cancelled and resources are cleaned up. |
105 | 81 |
|
106 | | -/* socks5 exmaple: |
107 | | -there is a remote server 101.32.14.95 , we have ssh access to it, we want use it as a socks5 proxy |
108 | | -and let the socks5 proxy listen on localhost:1080 |
109 | | -
|
110 | | -this setup has some advantages over proxy: |
111 | | -1. it is encrypted, so the data is safe at transport layer |
112 | | -2. there is no proxy server for maintain and it is easy to setup |
113 | | -*/ |
114 | | - |
115 | | -func socks5example() { |
116 | | - //address and port of ssh server to connect to |
117 | | - sshAddress := "18.162.151.198:22" |
118 | | - |
119 | | - //address and listening port of socks5 server to start |
120 | | - socks5Address := "localhost:1080" |
121 | | - |
122 | | - //username, private key path, and address of ssh server to connect to |
123 | | - // Using known_hosts file verification (recommended) |
124 | | - sshC, err := sshts.NewWithKnownHosts("proxy", "/home/k/.ssh/proxy.key", sshAddress, "/home/k/.ssh/known_hosts") |
125 | | - if err != nil { |
126 | | - log.Fatal(err) |
127 | | - } |
128 | | - err = sshC.Connect() |
129 | | - if err != nil { |
130 | | - log.Fatal(err) |
131 | | - } |
132 | | - defer sshC.Close() |
133 | | - |
134 | | - go sshC.StartSocks5Server(socks5Address) |
135 | | - for sshC.GetStatus() < 2 { |
136 | | - } |
137 | | - |
138 | | - httpget() |
139 | | - |
140 | | - fmt.Println("press ctrl+c to exit") |
141 | | - ch := make(chan os.Signal) |
142 | | - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) |
143 | | - <-ch |
144 | | - fmt.Println("exiting") |
| 82 | +### Connection Limits and Timeouts |
| 83 | +You can configure connection limits and timeouts to prevent resource exhaustion: |
145 | 84 |
|
| 85 | +```go |
| 86 | +config := &sshts.TunnelConfig{ |
| 87 | + // ... other config |
| 88 | + MaxConnections: 100, |
| 89 | + DialTimeout: 30 * time.Second, |
| 90 | + SSHTimeout: 30 * time.Second, |
146 | 91 | } |
| 92 | +``` |
147 | 93 |
|
148 | | -func httpget() { |
149 | | - proxyURL, err := url.Parse("socks5://localhost:1080") |
150 | | - if err != nil { |
151 | | - log.Fatal(err) |
152 | | - } |
153 | | - transport := &http.Transport{ |
154 | | - Proxy: http.ProxyURL(proxyURL), |
155 | | - } |
156 | | - client := &http.Client{Transport: transport} |
157 | | - resp, err := client.Get("http://ifconfig.me") |
158 | | - if err != nil { |
159 | | - log.Fatal(err) |
160 | | - } |
161 | | - defer resp.Body.Close() |
162 | | - body, err := ioutil.ReadAll(resp.Body) |
163 | | - if err != nil { |
164 | | - log.Fatal(err) |
165 | | - } |
166 | | - fmt.Println(string(body)) |
| 94 | +### Performance |
| 95 | +Data transfer uses `io.CopyBuffer` with a shared buffer pool for better performance: |
| 96 | + |
| 97 | +```go |
| 98 | +config := &sshts.TunnelConfig{ |
| 99 | + // ... other config |
| 100 | + BufferSize: 64 * 1024, // 64KB buffers |
167 | 101 | } |
168 | | -``` |
| 102 | +``` |
0 commit comments