Skip to content

Commit f226879

Browse files
committed
Implement optional host proxy for cache
Implements both host_proxy and https_proxy (with mitm cert) Does not actually cache anything, just verbose INFO logging. Signed-off-by: Anders F Björklund <[email protected]>
1 parent a8b2f4d commit f226879

File tree

12 files changed

+171
-2
lines changed

12 files changed

+171
-2
lines changed

Diff for: examples/default.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ hostResolver:
448448
# - 1.1.1.1
449449
# - 1.0.0.1
450450

451+
# The host proxy implements a HTTP and HTTPS proxy that can cache downloads on the host.
452+
hostProxy:
453+
# 🟢 Builtin default: false
454+
enabled: null
455+
451456
# Prefix to use for installing guest agent, and containerd with dependencies (if configured)
452457
# 🟢 Builtin default: /usr/local
453458
guestInstallPrefix: null

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/diskfs/go-diskfs v1.4.0
2121
github.com/docker/go-units v0.5.0
2222
github.com/elastic/go-libaudit/v2 v2.5.0
23+
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
2324
github.com/foxcpp/go-mockdns v1.1.0
2425
github.com/goccy/go-yaml v1.11.3
2526
github.com/google/go-cmp v0.6.0

Diff for: go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ github.com/elastic/go-libaudit/v2 v2.5.0 h1:5OK919QRnGtcjVBz3n/cs5F42im1mPlVTA9T
6060
github.com/elastic/go-libaudit/v2 v2.5.0/go.mod h1:AjlnhinP+kKQuUJoXLVrqxBM8uyhQmkzoV6jjsCFP4Q=
6161
github.com/elastic/go-licenser v0.4.1 h1:1xDURsc8pL5zYT9R29425J3vkHdt4RT5TNEMeRN48x4=
6262
github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
63+
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
64+
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
65+
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
66+
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
6367
github.com/elliotchance/orderedmap v1.5.1 h1:G1X4PYlljzimbdQ3RXmtIZiQ9d6aRQ3sH1nzjq5mECE=
6468
github.com/elliotchance/orderedmap v1.5.1/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
6569
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
@@ -235,6 +239,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
235239
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
236240
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
237241
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
242+
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
238243
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
239244
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
240245
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

Diff for: pkg/cidata/cidata.TEMPLATE.d/lima.env

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ LIMA_CIDATA_SLIRP_GATEWAY={{.SlirpGateway}}
3232
LIMA_CIDATA_SLIRP_IP_ADDRESS={{.SlirpIPAddress}}
3333
LIMA_CIDATA_UDP_DNS_LOCAL_PORT={{.UDPDNSLocalPort}}
3434
LIMA_CIDATA_TCP_DNS_LOCAL_PORT={{.TCPDNSLocalPort}}
35+
LIMA_CIDATA_HTTP_PROXY_LOCAL_PORT={{.HTTPProxyLocalPort}}
3536
LIMA_CIDATA_ROSETTA_ENABLED={{.RosettaEnabled}}
3637
LIMA_CIDATA_ROSETTA_BINFMT={{.RosettaBinFmt}}
3738
{{- if .SkipDefaultDependencyResolution}}

Diff for: pkg/cidata/cidata.TEMPLATE.d/proxy.crt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{ range $line := .HTTPProxyCACert.Lines -}}
2+
{{ $line }}
3+
{{ end -}}

Diff for: pkg/cidata/cidata.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func setupEnv(y *limayaml.LimaYAML, args TemplateArgs) (map[string]string, error
110110
return env, nil
111111
}
112112

113-
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string) error {
113+
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string, proxyPort int, proxyCert string) error {
114114
if err := limayaml.Validate(y, false); err != nil {
115115
return err
116116
}
@@ -286,6 +286,11 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
286286
}
287287
}
288288

289+
if *y.HostProxy.Enabled {
290+
args.HTTPProxyLocalPort = proxyPort
291+
args.HTTPProxyCACert = getCert(proxyCert)
292+
}
293+
289294
args.CACerts.RemoveDefaults = y.CACertificates.RemoveDefaults
290295

291296
for _, path := range y.CACertificates.Files {

Diff for: pkg/cidata/template.go

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type TemplateArgs struct {
7272
SlirpIPAddress string
7373
UDPDNSLocalPort int
7474
TCPDNSLocalPort int
75+
HTTPProxyLocalPort int
76+
HTTPProxyCACert Cert
7577
Env map[string]string
7678
DNSAddresses []string
7779
CACerts CACerts

Diff for: pkg/hostagent/hostagent.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
hostagentapi "github.com/lima-vm/lima/pkg/hostagent/api"
3131
"github.com/lima-vm/lima/pkg/hostagent/dns"
3232
"github.com/lima-vm/lima/pkg/hostagent/events"
33+
"github.com/lima-vm/lima/pkg/hostagent/proxy"
3334
"github.com/lima-vm/lima/pkg/limayaml"
3435
"github.com/lima-vm/lima/pkg/sshutil"
3536
"github.com/lima-vm/lima/pkg/store"
@@ -44,6 +45,7 @@ type HostAgent struct {
4445
sshLocalPort int
4546
udpDNSLocalPort int
4647
tcpDNSLocalPort int
48+
proxyLocalPort int
4749
instDir string
4850
instName string
4951
instSSHAddress string
@@ -120,6 +122,13 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
120122
return nil, err
121123
}
122124
}
125+
var proxyLocalPort int
126+
if *y.HostProxy.Enabled {
127+
proxyLocalPort, err = findFreeUDPLocalPort()
128+
if err != nil {
129+
return nil, err
130+
}
131+
}
123132

124133
vSockPort := 0
125134
virtioPort := ""
@@ -136,7 +145,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
136145
virtioPort = "" // filenames.VirtioPort
137146
}
138147

139-
if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort, virtioPort); err != nil {
148+
if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort, virtioPort, proxyLocalPort, proxy.CACert); err != nil {
140149
return nil, err
141150
}
142151

@@ -177,6 +186,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
177186
sshLocalPort: sshLocalPort,
178187
udpDNSLocalPort: udpDNSLocalPort,
179188
tcpDNSLocalPort: tcpDNSLocalPort,
189+
proxyLocalPort: proxyLocalPort,
180190
instDir: inst.Dir,
181191
instName: instName,
182192
instSSHAddress: inst.SSHAddress,
@@ -325,6 +335,17 @@ func (a *HostAgent) Run(ctx context.Context) error {
325335
defer dnsServer.Shutdown()
326336
}
327337

338+
if *a.y.HostProxy.Enabled {
339+
srvOpts := proxy.ServerOptions{
340+
TCPPort: a.proxyLocalPort,
341+
}
342+
proxyServer, err := proxy.Start(srvOpts)
343+
if err != nil {
344+
return fmt.Errorf("cannot start proxy server: %w", err)
345+
}
346+
defer proxyServer.Shutdown()
347+
}
348+
328349
errCh, err := a.driver.Start(ctx)
329350
if err != nil {
330351
return err

Diff for: pkg/hostagent/proxy/proxy.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// This file has been adapted from https://github.com/elazarl/goproxy/blob/7cc037d/examples/goproxy-eavesdropper/main.go
2+
3+
package proxy
4+
5+
import (
6+
"bufio"
7+
"context"
8+
"net"
9+
"net/http"
10+
"regexp"
11+
"strconv"
12+
"time"
13+
14+
"github.com/elazarl/goproxy"
15+
"github.com/sirupsen/logrus"
16+
)
17+
18+
// CACert has the CA certificate text
19+
var CACert = string(goproxy.CA_CERT)
20+
21+
type ServerOptions struct {
22+
Address string
23+
TCPPort int
24+
}
25+
26+
type Server struct {
27+
srv *http.Server
28+
}
29+
30+
func (s *Server) Shutdown() {
31+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
32+
defer cancel()
33+
if s.srv != nil {
34+
_ = s.srv.Shutdown(ctx)
35+
}
36+
}
37+
38+
func Start(opts ServerOptions) (*Server, error) {
39+
server := &Server{}
40+
if opts.TCPPort > 0 {
41+
srv, err := listenAndServe(opts)
42+
if err != nil {
43+
return nil, err
44+
}
45+
server.srv = srv
46+
}
47+
return server, nil
48+
}
49+
50+
func listenAndServe(opts ServerOptions) (*http.Server, error) {
51+
addr := net.JoinHostPort(opts.Address, strconv.Itoa(opts.TCPPort))
52+
proxy := goproxy.NewProxyHttpServer()
53+
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
54+
HandleConnect(goproxy.AlwaysMitm)
55+
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
56+
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
57+
defer func() {
58+
if e := recover(); e != nil {
59+
ctx.Logf("error connecting to remote: %v", e)
60+
_, _ = client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
61+
}
62+
client.Close()
63+
}()
64+
url := req.URL.String()
65+
ctx.Logf("URL: %s", url)
66+
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
67+
remote, err := net.Dial("tcp", req.URL.Host)
68+
if err != nil {
69+
ctx.Logf("%v", err)
70+
return
71+
}
72+
_, _ = client.Write([]byte("HTTP/1.1 200 Ok\r\n\r\n"))
73+
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
74+
for {
75+
req, err := http.ReadRequest(clientBuf.Reader)
76+
if err != nil {
77+
ctx.Logf("%v", err)
78+
return
79+
}
80+
_ = req.Write(remoteBuf)
81+
_ = remoteBuf.Flush()
82+
resp, err := http.ReadResponse(remoteBuf.Reader, req)
83+
if err != nil {
84+
ctx.Logf("%v", err)
85+
return
86+
}
87+
_ = resp.Write(clientBuf.Writer)
88+
_ = clientBuf.Flush()
89+
resp.Body.Close()
90+
}
91+
})
92+
proxy.Verbose = true
93+
s := &http.Server{Addr: addr, Handler: proxy}
94+
go func() {
95+
logrus.Debugf("Start HTTP proxy listening on: %v", addr)
96+
if e := s.ListenAndServe(); e != nil {
97+
panic(e)
98+
}
99+
}()
100+
101+
return s, nil
102+
}

Diff for: pkg/limayaml/defaults.go

+10
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,16 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
493493
y.HostResolver.IPv6 = ptr.Of(false)
494494
}
495495

496+
if y.HostProxy.Enabled == nil {
497+
y.HostProxy.Enabled = d.HostProxy.Enabled
498+
}
499+
if o.HostProxy.Enabled != nil {
500+
y.HostProxy.Enabled = o.HostProxy.Enabled
501+
}
502+
if y.HostProxy.Enabled == nil {
503+
y.HostProxy.Enabled = ptr.Of(false)
504+
}
505+
496506
if y.PropagateProxyEnv == nil {
497507
y.PropagateProxyEnv = d.PropagateProxyEnv
498508
}

Diff for: pkg/limayaml/defaults_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ func TestFillDefault(t *testing.T) {
9797
Enabled: ptr.Of(true),
9898
IPv6: ptr.Of(false),
9999
},
100+
HostProxy: HostProxy{
101+
Enabled: ptr.Of(false),
102+
},
100103
PropagateProxyEnv: ptr.Of(true),
101104
CACertificates: CACertificates{
102105
RemoveDefaults: ptr.Of(false),
@@ -336,6 +339,9 @@ func TestFillDefault(t *testing.T) {
336339
"default": "localhost",
337340
},
338341
},
342+
HostProxy: HostProxy{
343+
Enabled: ptr.Of(true),
344+
},
339345
PropagateProxyEnv: ptr.Of(false),
340346

341347
Mounts: []Mount{
@@ -521,6 +527,9 @@ func TestFillDefault(t *testing.T) {
521527
"override.": "underflow",
522528
},
523529
},
530+
HostProxy: HostProxy{
531+
Enabled: ptr.Of(true),
532+
},
524533
PropagateProxyEnv: ptr.Of(false),
525534

526535
Mounts: []Mount{

Diff for: pkg/limayaml/limayaml.go

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type LimaYAML struct {
3636
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
3737
DNS []net.IP `yaml:"dns,omitempty" json:"dns,omitempty"`
3838
HostResolver HostResolver `yaml:"hostResolver,omitempty" json:"hostResolver,omitempty"`
39+
HostProxy HostProxy `yaml:"hostProxy,omitempty" json:"hostProxy,omitempty"`
3940
// `useHostResolver` was deprecated in Lima v0.8.1, removed in Lima v0.14.0. Use `hostResolver.enabled` instead.
4041
PropagateProxyEnv *bool `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty"`
4142
CACertificates CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"`
@@ -252,6 +253,10 @@ type HostResolver struct {
252253
Hosts map[string]string `yaml:"hosts,omitempty" json:"hosts,omitempty"`
253254
}
254255

256+
type HostProxy struct {
257+
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
258+
}
259+
255260
type CACertificates struct {
256261
RemoveDefaults *bool `yaml:"removeDefaults,omitempty" json:"removeDefaults,omitempty"` // default: false
257262
Files []string `yaml:"files,omitempty" json:"files,omitempty"`

0 commit comments

Comments
 (0)