Skip to content

Commit 715cbb0

Browse files
committed
multi: add basic SSH tunnel support
1 parent 91623de commit 715cbb0

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

Diff for: Dockerfile

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ COPY . .
1414

1515
RUN go build -v -o ./btc-buf .
1616

17-
FROM alpine
17+
FROM debian:bullseye-slim
18+
RUN apt-get update && \
19+
apt-get install -y openssh-client && \
20+
rm -rf /var/lib/apt/lists/*
21+
22+
# Setup SSH directory
23+
RUN mkdir -p /root/.ssh && \
24+
chmod 700 /root/.ssh
1825

1926
COPY --from=builder /work/grpc-health-probe /usr/bin/grpc-health-probe
2027
COPY --from=builder /work/btc-buf /usr/bin/btc-buf

Diff for: config.go

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ type config struct {
1818
JsonLog bool `long:"logging.json" description:"log to JSON format (default human readable)"`
1919
LogLevel string `long:"logging.level" description:"log level" default:"debug"`
2020
Bitcoind bitcoindConfig `group:"bitcoind" namespace:"bitcoind"`
21+
SSH sshConfig `group:"ssh" namespace:"ssh"`
22+
}
23+
24+
type sshConfig struct {
25+
Host string `long:"host" description:"host to connect to"`
26+
LocalPort int `long:"local-port" description:"local port to connect to"`
27+
RemotePort int `long:"remote-port" description:"remote port to connect to"`
28+
KnownHosts []string `long:"known-hosts"`
2129
}
2230

2331
type bitcoindConfig struct {

Diff for: main.go

+71-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"os"
8+
"os/exec"
79
"os/signal"
810
"runtime/debug"
911
"time"
@@ -28,6 +30,17 @@ func realMain(cfg *config) error {
2830
cancel(fmt.Errorf("received %s signal", signal))
2931
}()
3032

33+
errs := make(chan error)
34+
if cfg.SSH.Host != "" {
35+
zerolog.Ctx(ctx).Info().
36+
Msgf("setting up SSH tunnel: %d:localhost:%d -> %s",
37+
cfg.SSH.LocalPort, cfg.SSH.RemotePort, cfg.SSH.Host,
38+
)
39+
if err := setupSSHTunnel(ctx, cfg.SSH, errs); err != nil {
40+
return fmt.Errorf("setup SSH tunnel: %w", err)
41+
}
42+
}
43+
3144
clientCtx, clientCancel := context.WithTimeout(ctx, time.Second*10)
3245
defer clientCancel()
3346

@@ -38,8 +51,6 @@ func realMain(cfg *config) error {
3851
return fmt.Errorf("new server: %w", err)
3952
}
4053

41-
errs := make(chan error)
42-
4354
go func() {
4455
if err := bitcoind.Listen(ctx, cfg.Listen); err != nil {
4556
errs <- err
@@ -90,3 +101,61 @@ func findSetting(key string, settings []debug.BuildSetting) string {
90101

91102
return "unknown"
92103
}
104+
105+
// setupSSHTunnel creates an SSH tunnel by running the ssh command
106+
func setupSSHTunnel(ctx context.Context, conf sshConfig, out chan<- error) error {
107+
args := []string{
108+
"-N",
109+
"-L", fmt.Sprintf("%d:localhost:%d", conf.LocalPort, conf.RemotePort),
110+
conf.Host,
111+
}
112+
if conf.KnownHosts != nil {
113+
tempFile, err := os.CreateTemp("", "")
114+
if err != nil {
115+
return fmt.Errorf("create temp file: %w", err)
116+
}
117+
118+
for _, host := range conf.KnownHosts {
119+
fmt.Fprintln(tempFile, host)
120+
}
121+
if err := tempFile.Close(); err != nil {
122+
return fmt.Errorf("close temp file: %w", err)
123+
}
124+
125+
args = append(args, "-o", "UserKnownHostsFile="+tempFile.Name())
126+
}
127+
// Build SSH command with port forwarding
128+
// -N: Don't execute remote command (forward only)
129+
// -L: Local port forwarding
130+
cmd := exec.CommandContext(ctx, "ssh", args...)
131+
132+
// Start the SSH tunnel
133+
if err := cmd.Start(); err != nil {
134+
return fmt.Errorf("starting SSH tunnel: %w", err)
135+
}
136+
137+
// Monitor the tunnel process in background
138+
go func() {
139+
if err := cmd.Wait(); err != nil {
140+
zerolog.Ctx(ctx).Error().
141+
Err(err).
142+
Msg("SSH tunnel exited unexpectedly")
143+
out <- fmt.Errorf("SSH tunnel exited unexpectedly: %w", err)
144+
}
145+
}()
146+
147+
// Wait for the tunnel to be established
148+
for i := 0; i < 10; i++ {
149+
if conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", conf.LocalPort)); err == nil {
150+
conn.Close()
151+
return nil
152+
}
153+
select {
154+
case <-ctx.Done():
155+
return fmt.Errorf("wait for SSH tunnel: %w", ctx.Err())
156+
case <-time.After(time.Second):
157+
}
158+
}
159+
160+
return fmt.Errorf("timeout waiting for SSH tunnel")
161+
}

0 commit comments

Comments
 (0)