Skip to content

Commit c77732c

Browse files
authored
fix: harden IPv6 host/port URL handling for remote builder and tunnel paths (#4792)
* fix: format remote builder API URLs with JoinHostPort * fix: harden host:port formatting for IPv6 endpoints
1 parent cd17860 commit c77732c

File tree

11 files changed

+111
-33
lines changed

11 files changed

+111
-33
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package imgsrc
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"net/url"
7+
)
8+
9+
func builderAPIURL(daemonHost, path string) (string, error) {
10+
parsed, err := url.Parse(daemonHost)
11+
if err != nil {
12+
return "", err
13+
}
14+
15+
hostname := parsed.Hostname()
16+
if hostname == "" {
17+
return "", fmt.Errorf("failed to parse daemon host %q: missing hostname", daemonHost)
18+
}
19+
20+
parsed.Host = net.JoinHostPort(hostname, "8080")
21+
parsed.Scheme = "http"
22+
parsed.Path = path
23+
24+
return parsed.String(), nil
25+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package imgsrc
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestBuilderAPIURL(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
daemonHost string
14+
path string
15+
want string
16+
}{
17+
{
18+
name: "ipv4",
19+
daemonHost: "tcp://192.168.1.10:2375",
20+
path: "/flyio/v1/extendDeadline",
21+
want: "http://192.168.1.10:8080/flyio/v1/extendDeadline",
22+
},
23+
{
24+
name: "ipv6",
25+
daemonHost: "tcp://[fdaa:49:2bd7::2]:2375",
26+
path: "/flyio/v1/extendDeadline",
27+
want: "http://[fdaa:49:2bd7::2]:8080/flyio/v1/extendDeadline",
28+
},
29+
{
30+
name: "overlaybd path",
31+
daemonHost: "tcp://[fdaa:49:2bd7::2]:2375",
32+
path: "/flyio/v1/buildOverlaybdImage",
33+
want: "http://[fdaa:49:2bd7::2]:8080/flyio/v1/buildOverlaybdImage",
34+
},
35+
}
36+
37+
for _, tc := range tests {
38+
t.Run(tc.name, func(t *testing.T) {
39+
got, err := builderAPIURL(tc.daemonHost, tc.path)
40+
require.NoError(t, err)
41+
assert.Equal(t, tc.want, got)
42+
})
43+
}
44+
}
45+
46+
func TestBuilderAPIURLError(t *testing.T) {
47+
tests := []struct {
48+
name string
49+
daemonHost string
50+
}{
51+
{
52+
name: "missing host",
53+
daemonHost: "unix:///var/run/docker.sock",
54+
},
55+
{
56+
name: "invalid url",
57+
daemonHost: "://bad-url",
58+
},
59+
}
60+
61+
for _, tc := range tests {
62+
t.Run(tc.name, func(t *testing.T) {
63+
_, err := builderAPIURL(tc.daemonHost, "/flyio/v1/extendDeadline")
64+
require.Error(t, err)
65+
})
66+
}
67+
}

internal/build/imgsrc/docker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ func buildWireguardlessClientOpts(ctx context.Context, host, appName string) ([]
576576
"Authorization": "Basic " + basicAuth(appName, config.Tokens(ctx).Docker()),
577577
}),
578578
dockerclient.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
579-
return tls.Dial("tcp", parsedHostUrl.Host+":443", &tls.Config{})
579+
return tls.Dial("tcp", net.JoinHostPort(parsedHostUrl.Hostname(), "443"), &tls.Config{})
580580
}),
581581
}
582582

internal/build/imgsrc/dockerfile_builder.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"io"
99
"net"
1010
"net/http"
11-
"net/url"
1211
"os"
1312
"path/filepath"
1413
"strings"
@@ -328,17 +327,10 @@ func buildOverlaybdImage(ctx context.Context, appName string, docker *dockerclie
328327

329328
terminal.Info("Building lazy-loading image, please wait...")
330329

331-
daemonHost := docker.DaemonHost()
332-
parsed, err := url.Parse(daemonHost)
330+
rchabUrl, err := builderAPIURL(docker.DaemonHost(), "/flyio/v1/buildOverlaybdImage")
333331
if err != nil {
334332
return nil, fmt.Errorf("failed to parse daemon host: %w", err)
335333
}
336-
hostPort := parsed.Host
337-
host, _, _ := net.SplitHostPort(hostPort)
338-
parsed.Host = host + ":8080"
339-
parsed.Scheme = "http"
340-
parsed.Path = "/flyio/v1/buildOverlaybdImage"
341-
rchabUrl := parsed.String()
342334

343335
terminal.Debugf("rchab url: %s", rchabUrl)
344336

internal/build/imgsrc/resolver.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8-
"net"
98
"net/http"
10-
"net/url"
119
"strings"
1210
"sync"
1311
"time"
@@ -791,18 +789,7 @@ func (r *Resolver) StartHeartbeat(ctx context.Context) (*StopSignal, error) {
791789
}
792790

793791
func getHeartbeatUrl(dockerClient *dockerclient.Client) (string, error) {
794-
daemonHost := dockerClient.DaemonHost()
795-
parsed, err := url.Parse(daemonHost)
796-
if err != nil {
797-
return "", err
798-
}
799-
hostPort := parsed.Host
800-
host, _, _ := net.SplitHostPort(hostPort)
801-
parsed.Host = host + ":8080"
802-
parsed.Scheme = "http"
803-
parsed.Path = "/flyio/v1/extendDeadline"
804-
805-
return parsed.String(), nil
792+
return builderAPIURL(dockerClient.DaemonHost(), "/flyio/v1/extendDeadline")
806793
}
807794

808795
func (s *StopSignal) Stop() {

internal/build/imgsrc/resolver_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestDeploymentImage(t *testing.T) {
2626
}
2727

2828
func TestHeartbeat(t *testing.T) {
29-
dc, err := client.NewClientWithOpts()
29+
dc, err := client.NewClientWithOpts(client.WithHost("tcp://127.0.0.1:2375"))
3030
assert.NoError(t, err)
3131

3232
ctx := context.Background()
@@ -43,7 +43,7 @@ func TestStartHeartbeat(t *testing.T) {
4343
Tokens: &tokens.Tokens{},
4444
})
4545

46-
dc, err := client.NewClientWithOpts()
46+
dc, err := client.NewClientWithOpts(client.WithHost("tcp://127.0.0.1:2375"))
4747
assert.NoError(t, err)
4848

4949
resolver := Resolver{
@@ -72,7 +72,7 @@ func TestStartHeartbeatFirstRetry(t *testing.T) {
7272
Tokens: &tokens.Tokens{},
7373
})
7474

75-
dc, err := client.NewClientWithOpts()
75+
dc, err := client.NewClientWithOpts(client.WithHost("tcp://127.0.0.1:2375"))
7676
assert.NoError(t, err)
7777

7878
numCalls := 0
@@ -109,7 +109,7 @@ func TestStartHeartbeatNoEndpoint(t *testing.T) {
109109
Tokens: &tokens.Tokens{},
110110
})
111111

112-
dc, err := client.NewClientWithOpts()
112+
dc, err := client.NewClientWithOpts(client.WithHost("tcp://127.0.0.1:2375"))
113113
assert.NoError(t, err)
114114

115115
resolver := Resolver{

internal/command/doctor/app_checks.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net"
78
"os"
89
"path/filepath"
910
"strconv"
@@ -160,7 +161,7 @@ func (ac *AppChecker) checkDnsRecords(ipAddresses []fly.IPAddress) {
160161

161162
return
162163
}
163-
nsAddr := fmt.Sprintf("%s:53", strings.TrimSuffix(ns, "."))
164+
nsAddr := net.JoinHostPort(strings.TrimSuffix(ns, "."), "53")
164165

165166
if len(v4s) > 0 {
166167
ac.lprint(nil, "Checking A record for %s... ", appHostname)

internal/command/mcp/proxy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ func resolveProxy(ctx context.Context, originalUrl string) (string, *exec.Cmd, e
354354

355355
bindAddr := flag.GetBindAddr(ctx)
356356

357-
parsedURL.Host = fmt.Sprintf("%s:%d", bindAddr, localPort)
357+
parsedURL.Host = net.JoinHostPort(bindAddr, fmt.Sprintf("%d", localPort))
358358

359359
return parsedURL.String(), cmd, nil
360360
}

internal/command/mcp/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mcp
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"net/http"
78
"os"
89
"os/exec"
@@ -470,7 +471,7 @@ func runServer(ctx context.Context) error {
470471
start = sseServer.Start
471472
}
472473

473-
if err = start(fmt.Sprintf("%s:%d", flag.GetString(ctx, flagnames.BindAddr), port)); err != nil {
474+
if err = start(net.JoinHostPort(flag.GetString(ctx, flagnames.BindAddr), fmt.Sprintf("%d", port))); err != nil {
474475
return fmt.Errorf("Server error: %v", err)
475476
}
476477
} else {

wg/state.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (s *WireGuardState) TunnelConfig() *Config {
7474
LocalNetwork: &wgl,
7575
RemotePublicKey: pkey,
7676
RemoteNetwork: &wgr,
77-
Endpoint: s.Peer.Endpointip + ":51820",
77+
Endpoint: net.JoinHostPort(s.Peer.Endpointip, "51820"),
7878
DNS: dns,
7979
LogLevel: wgLogLevel,
8080
}

0 commit comments

Comments
 (0)