Skip to content

Commit

Permalink
Implement optional host proxy for cache
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
afbjorklund committed May 27, 2024
1 parent a8b2f4d commit f226879
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 2 deletions.
5 changes: 5 additions & 0 deletions examples/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ hostResolver:
# - 1.1.1.1
# - 1.0.0.1

# The host proxy implements a HTTP and HTTPS proxy that can cache downloads on the host.
hostProxy:
# 🟢 Builtin default: false
enabled: null

# Prefix to use for installing guest agent, and containerd with dependencies (if configured)
# 🟢 Builtin default: /usr/local
guestInstallPrefix: null
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/diskfs/go-diskfs v1.4.0
github.com/docker/go-units v0.5.0
github.com/elastic/go-libaudit/v2 v2.5.0
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
github.com/foxcpp/go-mockdns v1.1.0
github.com/goccy/go-yaml v1.11.3
github.com/google/go-cmp v0.6.0
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ github.com/elastic/go-libaudit/v2 v2.5.0 h1:5OK919QRnGtcjVBz3n/cs5F42im1mPlVTA9T
github.com/elastic/go-libaudit/v2 v2.5.0/go.mod h1:AjlnhinP+kKQuUJoXLVrqxBM8uyhQmkzoV6jjsCFP4Q=
github.com/elastic/go-licenser v0.4.1 h1:1xDURsc8pL5zYT9R29425J3vkHdt4RT5TNEMeRN48x4=
github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/elliotchance/orderedmap v1.5.1 h1:G1X4PYlljzimbdQ3RXmtIZiQ9d6aRQ3sH1nzjq5mECE=
github.com/elliotchance/orderedmap v1.5.1/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
Expand Down Expand Up @@ -235,6 +239,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
Expand Down
1 change: 1 addition & 0 deletions pkg/cidata/cidata.TEMPLATE.d/lima.env
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ LIMA_CIDATA_SLIRP_GATEWAY={{.SlirpGateway}}
LIMA_CIDATA_SLIRP_IP_ADDRESS={{.SlirpIPAddress}}
LIMA_CIDATA_UDP_DNS_LOCAL_PORT={{.UDPDNSLocalPort}}
LIMA_CIDATA_TCP_DNS_LOCAL_PORT={{.TCPDNSLocalPort}}
LIMA_CIDATA_HTTP_PROXY_LOCAL_PORT={{.HTTPProxyLocalPort}}
LIMA_CIDATA_ROSETTA_ENABLED={{.RosettaEnabled}}
LIMA_CIDATA_ROSETTA_BINFMT={{.RosettaBinFmt}}
{{- if .SkipDefaultDependencyResolution}}
Expand Down
3 changes: 3 additions & 0 deletions pkg/cidata/cidata.TEMPLATE.d/proxy.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{ range $line := .HTTPProxyCACert.Lines -}}
{{ $line }}
{{ end -}}
7 changes: 6 additions & 1 deletion pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func setupEnv(y *limayaml.LimaYAML, args TemplateArgs) (map[string]string, error
return env, nil
}

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

if *y.HostProxy.Enabled {
args.HTTPProxyLocalPort = proxyPort
args.HTTPProxyCACert = getCert(proxyCert)
}

args.CACerts.RemoveDefaults = y.CACertificates.RemoveDefaults

for _, path := range y.CACertificates.Files {
Expand Down
2 changes: 2 additions & 0 deletions pkg/cidata/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ type TemplateArgs struct {
SlirpIPAddress string
UDPDNSLocalPort int
TCPDNSLocalPort int
HTTPProxyLocalPort int
HTTPProxyCACert Cert
Env map[string]string
DNSAddresses []string
CACerts CACerts
Expand Down
23 changes: 22 additions & 1 deletion pkg/hostagent/hostagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
hostagentapi "github.com/lima-vm/lima/pkg/hostagent/api"
"github.com/lima-vm/lima/pkg/hostagent/dns"
"github.com/lima-vm/lima/pkg/hostagent/events"
"github.com/lima-vm/lima/pkg/hostagent/proxy"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/sshutil"
"github.com/lima-vm/lima/pkg/store"
Expand All @@ -44,6 +45,7 @@ type HostAgent struct {
sshLocalPort int
udpDNSLocalPort int
tcpDNSLocalPort int
proxyLocalPort int
instDir string
instName string
instSSHAddress string
Expand Down Expand Up @@ -120,6 +122,13 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
return nil, err
}
}
var proxyLocalPort int
if *y.HostProxy.Enabled {
proxyLocalPort, err = findFreeUDPLocalPort()
if err != nil {
return nil, err
}
}

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

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

Expand Down Expand Up @@ -177,6 +186,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
sshLocalPort: sshLocalPort,
udpDNSLocalPort: udpDNSLocalPort,
tcpDNSLocalPort: tcpDNSLocalPort,
proxyLocalPort: proxyLocalPort,
instDir: inst.Dir,
instName: instName,
instSSHAddress: inst.SSHAddress,
Expand Down Expand Up @@ -325,6 +335,17 @@ func (a *HostAgent) Run(ctx context.Context) error {
defer dnsServer.Shutdown()
}

if *a.y.HostProxy.Enabled {
srvOpts := proxy.ServerOptions{
TCPPort: a.proxyLocalPort,
}
proxyServer, err := proxy.Start(srvOpts)
if err != nil {
return fmt.Errorf("cannot start proxy server: %w", err)
}
defer proxyServer.Shutdown()
}

errCh, err := a.driver.Start(ctx)
if err != nil {
return err
Expand Down
102 changes: 102 additions & 0 deletions pkg/hostagent/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// This file has been adapted from https://github.com/elazarl/goproxy/blob/7cc037d/examples/goproxy-eavesdropper/main.go

package proxy

import (
"bufio"
"context"
"net"
"net/http"
"regexp"
"strconv"
"time"

"github.com/elazarl/goproxy"
"github.com/sirupsen/logrus"
)

// CACert has the CA certificate text
var CACert = string(goproxy.CA_CERT)

type ServerOptions struct {
Address string
TCPPort int
}

type Server struct {
srv *http.Server
}

func (s *Server) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if s.srv != nil {
_ = s.srv.Shutdown(ctx)
}
}

func Start(opts ServerOptions) (*Server, error) {
server := &Server{}
if opts.TCPPort > 0 {
srv, err := listenAndServe(opts)
if err != nil {
return nil, err
}
server.srv = srv
}
return server, nil
}

func listenAndServe(opts ServerOptions) (*http.Server, error) {
addr := net.JoinHostPort(opts.Address, strconv.Itoa(opts.TCPPort))
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
defer func() {
if e := recover(); e != nil {
ctx.Logf("error connecting to remote: %v", e)
_, _ = client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
}
client.Close()
}()
url := req.URL.String()
ctx.Logf("URL: %s", url)
clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
remote, err := net.Dial("tcp", req.URL.Host)
if err != nil {
ctx.Logf("%v", err)
return
}
_, _ = client.Write([]byte("HTTP/1.1 200 Ok\r\n\r\n"))
remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
for {
req, err := http.ReadRequest(clientBuf.Reader)
if err != nil {
ctx.Logf("%v", err)
return
}
_ = req.Write(remoteBuf)
_ = remoteBuf.Flush()
resp, err := http.ReadResponse(remoteBuf.Reader, req)
if err != nil {
ctx.Logf("%v", err)
return
}
_ = resp.Write(clientBuf.Writer)
_ = clientBuf.Flush()
resp.Body.Close()
}
})
proxy.Verbose = true
s := &http.Server{Addr: addr, Handler: proxy}
go func() {
logrus.Debugf("Start HTTP proxy listening on: %v", addr)
if e := s.ListenAndServe(); e != nil {
panic(e)
}
}()

return s, nil
}
10 changes: 10 additions & 0 deletions pkg/limayaml/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,16 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
y.HostResolver.IPv6 = ptr.Of(false)
}

if y.HostProxy.Enabled == nil {
y.HostProxy.Enabled = d.HostProxy.Enabled
}
if o.HostProxy.Enabled != nil {
y.HostProxy.Enabled = o.HostProxy.Enabled
}
if y.HostProxy.Enabled == nil {
y.HostProxy.Enabled = ptr.Of(false)
}

if y.PropagateProxyEnv == nil {
y.PropagateProxyEnv = d.PropagateProxyEnv
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/limayaml/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ func TestFillDefault(t *testing.T) {
Enabled: ptr.Of(true),
IPv6: ptr.Of(false),
},
HostProxy: HostProxy{
Enabled: ptr.Of(false),
},
PropagateProxyEnv: ptr.Of(true),
CACertificates: CACertificates{
RemoveDefaults: ptr.Of(false),
Expand Down Expand Up @@ -336,6 +339,9 @@ func TestFillDefault(t *testing.T) {
"default": "localhost",
},
},
HostProxy: HostProxy{
Enabled: ptr.Of(true),
},
PropagateProxyEnv: ptr.Of(false),

Mounts: []Mount{
Expand Down Expand Up @@ -521,6 +527,9 @@ func TestFillDefault(t *testing.T) {
"override.": "underflow",
},
},
HostProxy: HostProxy{
Enabled: ptr.Of(true),
},
PropagateProxyEnv: ptr.Of(false),

Mounts: []Mount{
Expand Down
5 changes: 5 additions & 0 deletions pkg/limayaml/limayaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type LimaYAML struct {
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
DNS []net.IP `yaml:"dns,omitempty" json:"dns,omitempty"`
HostResolver HostResolver `yaml:"hostResolver,omitempty" json:"hostResolver,omitempty"`
HostProxy HostProxy `yaml:"hostProxy,omitempty" json:"hostProxy,omitempty"`
// `useHostResolver` was deprecated in Lima v0.8.1, removed in Lima v0.14.0. Use `hostResolver.enabled` instead.
PropagateProxyEnv *bool `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty"`
CACertificates CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"`
Expand Down Expand Up @@ -252,6 +253,10 @@ type HostResolver struct {
Hosts map[string]string `yaml:"hosts,omitempty" json:"hosts,omitempty"`
}

type HostProxy struct {
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
}

type CACertificates struct {
RemoveDefaults *bool `yaml:"removeDefaults,omitempty" json:"removeDefaults,omitempty"` // default: false
Files []string `yaml:"files,omitempty" json:"files,omitempty"`
Expand Down

0 comments on commit f226879

Please sign in to comment.