Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions internal/runtime/proxied.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,6 @@ func sidecarCreate(name, slug string, proxyID *int64, image, network string) cli
}
init := true
pids := tunnelPidsLimit
mode := container.NetworkMode("bridge")
if network != "" {
mode = container.NetworkMode(network)
}
restart := container.RestartPolicy{Name: "unless-stopped"}
if proxyID == nil {
restart = container.RestartPolicy{Name: "no"}
Expand All @@ -420,6 +416,34 @@ func sidecarCreate(name, slug string, proxyID *int64, image, network string) cli
if len(hostname) > 63 {
hostname = hostname[:63]
}
host := &container.HostConfig{
NetworkMode: "bridge",
CapDrop: []string{"ALL"},
CapAdd: []string{"NET_ADMIN"},
Privileged: false,
SecurityOpt: []string{noNewPrivileges},
ReadonlyRootfs: true,
Tmpfs: map[string]string{"/run": "size=8m,mode=0755"},
Init: &init,
Sysctls: map[string]string{
"net.ipv6.conf.all.disable_ipv6": "1",
"net.ipv6.conf.default.disable_ipv6": "1",
// DNS sent to Docker's resolver is redirected into the tunnel and keeps a
// loopback source until it is rewritten; see cp-tunnel.
"net.ipv4.conf.all.route_localnet": "1",
},
RestartPolicy: restart,
// tun2proxy runs at warn, so this log holds errors, never the sites the
// earner reaches; the bound keeps even a noisy failure off the disk.
LogConfig: container.LogConfig{Type: "json-file", Config: map[string]string{"max-size": "1m", "max-file": "2"}},
Resources: container.Resources{
PidsLimit: &pids,
Devices: []container.DeviceMapping{{PathOnHost: "/dev/net/tun", PathInContainer: "/dev/net/tun", CgroupPermissions: "rwm"}},
},
}
// The fenced network and its resolvers, as every bridge earner gets them: the
// fence drops the daemon's LAN resolver, so the tunnel needs the public ones.
isolate(host, network)
return client.ContainerCreateOptions{
Name: name,
Config: &container.Config{
Expand All @@ -428,31 +452,7 @@ func sidecarCreate(name, slug string, proxyID *int64, image, network string) cli
Hostname: hostname,
Volumes: map[string]struct{}{tunnelSecretDir: {}},
},
HostConfig: &container.HostConfig{
NetworkMode: mode,
CapDrop: []string{"ALL"},
CapAdd: []string{"NET_ADMIN"},
Privileged: false,
SecurityOpt: []string{noNewPrivileges},
ReadonlyRootfs: true,
Tmpfs: map[string]string{"/run": "size=8m,mode=0755"},
Init: &init,
Sysctls: map[string]string{
"net.ipv6.conf.all.disable_ipv6": "1",
"net.ipv6.conf.default.disable_ipv6": "1",
// DNS sent to Docker's resolver is redirected into the tunnel and keeps a
// loopback source until it is rewritten; see cp-tunnel.
"net.ipv4.conf.all.route_localnet": "1",
},
RestartPolicy: restart,
// tun2proxy runs at warn, so this log holds errors, never the sites the
// earner reaches; the bound keeps even a noisy failure off the disk.
LogConfig: container.LogConfig{Type: "json-file", Config: map[string]string{"max-size": "1m", "max-file": "2"}},
Resources: container.Resources{
PidsLimit: &pids,
Devices: []container.DeviceMapping{{PathOnHost: "/dev/net/tun", PathInContainer: "/dev/net/tun", CgroupPermissions: "rwm"}},
},
},
HostConfig: host,
}
}

Expand Down
17 changes: 17 additions & 0 deletions internal/runtime/proxied_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,23 @@ func deployRequest() ProxiedDeploy {
return ProxiedDeploy{Slug: "earner", ProxyID: 3, Service: proxiedService, Env: map[string]string{"TOKEN": "tok"}, Proxy: testSpec(), OwnIP: "192.0.2.1"}
}

// A fenced tunnel gets the fence's resolvers, as every fenced earner does: the fence
// drops the daemon's LAN resolver, so without them the tunnel resolves nothing.
func TestFencedSidecarGetsTheFencesDNS(t *testing.T) {
id := int64(3)
fenced := sidecarCreate(tunnelName("earner", id), "earner", &id, "example/cashpilot-tunnel:1.66.0", FenceNetwork).HostConfig
if fenced.NetworkMode != FenceNetwork {
t.Fatalf("network mode %q", fenced.NetworkMode)
}
if !slices.Equal(fenced.DNS, fenceDNS) {
t.Fatalf("DNS %v, want the fence's %v", fenced.DNS, fenceDNS)
}
plain := sidecarCreate(tunnelName("earner", id), "earner", &id, "example/cashpilot-tunnel:1.66.0", "").HostConfig
if plain.NetworkMode != "bridge" || plain.DNS != nil {
t.Fatalf("the default bridge tunnel was changed: net=%q dns=%v", plain.NetworkMode, plain.DNS)
}
}

// The sidecar is the only container with any network power, so its create options
// are the security boundary: NET_ADMIN and /dev/net/tun and nothing else, never
// privileged, read-only, no Docker socket, no published ports.
Expand Down
Loading