Skip to content

Commit 5a84451

Browse files
Merge pull request #21652 from Luap99/machine-http-proxy
machine: implement http proxy logic for all providers
2 parents 4990c6e + f218f84 commit 5a84451

14 files changed

Lines changed: 214 additions & 332 deletions

File tree

pkg/machine/e2e/proxy_test.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package e2e_test
22

33
import (
44
"os"
5+
"path/filepath"
56

67
"github.com/containers/podman/v5/pkg/machine/define"
78
. "github.com/onsi/ginkgo/v2"
@@ -23,29 +24,30 @@ var _ = Describe("podman machine proxy settings propagation", func() {
2324
})
2425

2526
It("ssh to running machine and check proxy settings", func() {
26-
// TODO the proxy test is currently failing on applehv. FIX ME
27-
skipIfVmtype(define.AppleHvVirt, "TODO: this test fails on applehv")
27+
defer func() {
28+
os.Unsetenv("HTTP_PROXY")
29+
os.Unsetenv("HTTPS_PROXY")
30+
os.Unsetenv("SSL_CERT_DIR")
31+
os.Unsetenv("SSL_CERT_FILE")
32+
}()
33+
34+
certFileDir := GinkgoT().TempDir()
35+
certDir := GinkgoT().TempDir()
36+
certFile := filepath.Join(certFileDir, "cert1")
37+
err := os.WriteFile(certFile, []byte("cert1 content\n"), os.ModePerm)
38+
Expect(err).ToNot(HaveOccurred())
39+
err = os.WriteFile(filepath.Join(certDir, "cert2"), []byte("cert2 content\n"), os.ModePerm)
40+
Expect(err).ToNot(HaveOccurred())
41+
42+
os.Setenv("SSL_CERT_FILE", certFile)
43+
os.Setenv("SSL_CERT_DIR", certDir)
2844

29-
// https://github.com/containers/podman/issues/20129
30-
if testProvider.VMType() == define.HyperVVirt {
31-
Skip("proxy settings not yet supported")
32-
}
3345
name := randomString()
3446
i := new(initMachine)
3547
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
3648
Expect(err).ToNot(HaveOccurred())
3749
Expect(session).To(Exit(0))
3850

39-
defer func() {
40-
httpProxyEnv := os.Getenv("HTTP_PROXY")
41-
httpsProxyEnv := os.Getenv("HTTPS_PROXY")
42-
if httpProxyEnv != "" {
43-
os.Unsetenv("HTTP_PROXY")
44-
}
45-
if httpsProxyEnv != "" {
46-
os.Unsetenv("HTTPS_PROXY")
47-
}
48-
}()
4951
proxyURL := "http://abcdefghijklmnopqrstuvwxyz-proxy"
5052
os.Setenv("HTTP_PROXY", proxyURL)
5153
os.Setenv("HTTPS_PROXY", proxyURL)
@@ -65,5 +67,52 @@ var _ = Describe("podman machine proxy settings propagation", func() {
6567
Expect(err).ToNot(HaveOccurred())
6668
Expect(sshSession).To(Exit(0))
6769
Expect(sshSession.outputToString()).To(ContainSubstring(proxyURL))
70+
71+
// SSL_CERT not implemented for WSL
72+
if !isVmtype(define.WSLVirt) {
73+
sshSession, err = mb.setName(name).setCmd(sshProxy.withSSHCommand([]string{"printenv", "SSL_CERT_DIR", "SSL_CERT_FILE"})).run()
74+
Expect(err).ToNot(HaveOccurred())
75+
Expect(sshSession).To(Exit(0))
76+
Expect(string(sshSession.Out.Contents())).To(Equal(define.UserCertsTargetPath + "\n" + define.UserCertsTargetPath + "/cert1" + "\n"))
77+
78+
sshSession, err = mb.setName(name).setCmd(sshProxy.withSSHCommand([]string{"cat", "$SSL_CERT_DIR/cert2", "$SSL_CERT_FILE"})).run()
79+
Expect(err).ToNot(HaveOccurred())
80+
Expect(sshSession).To(Exit(0))
81+
Expect(string(sshSession.Out.Contents())).To(Equal("cert2 content\ncert1 content\n"))
82+
}
83+
84+
stop := new(stopMachine)
85+
stopSession, err := mb.setName(name).setCmd(stop).run()
86+
Expect(err).ToNot(HaveOccurred())
87+
Expect(stopSession).To(Exit(0))
88+
89+
// Now update proxy env, lets use some special vars to make sure our scripts can handle it
90+
proxy1 := "http:// some special @;\" here"
91+
proxy2 := "https://abc :£$%6 : |\"\""
92+
os.Setenv("HTTP_PROXY", proxy1)
93+
os.Setenv("HTTPS_PROXY", proxy2)
94+
95+
// changing SSL_CERT vars should not have an effect
96+
os.Setenv("SSL_CERT_FILE", "/tmp/1")
97+
os.Setenv("SSL_CERT_DIR", "/tmp")
98+
99+
// start it again should update the proxies
100+
startSession, err = mb.setName(name).setCmd(s).run()
101+
Expect(err).ToNot(HaveOccurred())
102+
Expect(startSession).To(Exit(0))
103+
104+
sshSession, err = mb.setName(name).setCmd(sshProxy.withSSHCommand([]string{"printenv", "HTTP_PROXY", "HTTPS_PROXY"})).run()
105+
Expect(err).ToNot(HaveOccurred())
106+
Expect(sshSession).To(Exit(0))
107+
Expect(string(sshSession.Out.Contents())).To(Equal(proxy1 + "\n" + proxy2 + "\n"))
108+
109+
// SSL_CERT not implemented for WSL
110+
if !isVmtype(define.WSLVirt) {
111+
// SSL_CERT... must still be the same as before
112+
sshSession, err = mb.setName(name).setCmd(sshProxy.withSSHCommand([]string{"cat", "$SSL_CERT_DIR/cert2", "$SSL_CERT_FILE"})).run()
113+
Expect(err).ToNot(HaveOccurred())
114+
Expect(sshSession).To(Exit(0))
115+
Expect(string(sshSession.Out.Contents())).To(Equal("cert2 content\ncert1 content\n"))
116+
}
68117
})
69118
})

pkg/machine/ignition/ignition.go

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/fs"
99
"net/url"
1010
"os"
11+
"path"
1112
"path/filepath"
1213

1314
"github.com/containers/podman/v5/pkg/machine/define"
@@ -178,46 +179,6 @@ ExecStart=
178179
ExecStart=-/usr/sbin/agetty --autologin root --noclear %I $TERM
179180
`
180181

181-
// This service gets environment variables that are provided
182-
// through qemu fw_cfg and then sets them into systemd/system.conf.d,
183-
// profile.d and environment.d files
184-
//
185-
// Currently, it is used for propagating
186-
// proxy settings e.g. HTTP_PROXY and others, on a start avoiding
187-
// a need of re-creating/re-initiating a VM
188-
189-
envset := parser.NewUnitFile()
190-
envset.Add("Unit", "Description", "Environment setter from QEMU FW_CFG")
191-
192-
envset.Add("Service", "Type", "oneshot")
193-
envset.Add("Service", "RemainAfterExit", "yes")
194-
envset.Add("Service", "Environment", "FWCFGRAW=/sys/firmware/qemu_fw_cfg/by_name/opt/com.coreos/environment/raw")
195-
envset.Add("Service", "Environment", "SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf")
196-
envset.Add("Service", "Environment", "ENVD_CONF=/etc/environment.d/default-env.conf")
197-
envset.Add("Service", "Environment", "PROFILE_CONF=/etc/profile.d/default-env.sh")
198-
envset.Add("Service", "ExecStart", `/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} &&\
199-
echo "[Manager]\n#Got from QEMU FW_CFG\nDefaultEnvironment=$(/usr/bin/base64 -d ${FWCFGRAW} | sed -e "s+|+ +g")\n" > ${SYSTEMD_CONF} ||\
200-
echo "[Manager]\n#Got nothing from QEMU FW_CFG\n#DefaultEnvironment=\n" > ${SYSTEMD_CONF}'`)
201-
envset.Add("Service", "ExecStart", `/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
202-
echo "#Got from QEMU FW_CFG"> ${ENVD_CONF};\
203-
IFS="|";\
204-
for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
205-
echo "$iprxy" >> ${ENVD_CONF}; done ) || \
206-
echo "#Got nothing from QEMU FW_CFG"> ${ENVD_CONF}'`)
207-
envset.Add("Service", "ExecStart", `/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
208-
echo "#Got from QEMU FW_CFG"> ${PROFILE_CONF};\
209-
IFS="|";\
210-
for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
211-
echo "export $iprxy" >> ${PROFILE_CONF}; done ) || \
212-
echo "#Got nothing from QEMU FW_CFG"> ${PROFILE_CONF}'`)
213-
envset.Add("Service", "ExecStartPost", "/usr/bin/systemctl daemon-reload")
214-
215-
envset.Add("Install", "WantedBy", "sysinit.target")
216-
envsetFile, err := envset.ToString()
217-
if err != nil {
218-
return err
219-
}
220-
221182
ignSystemd := Systemd{
222183
Units: []Unit{
223184
{
@@ -261,16 +222,6 @@ ExecStart=-/usr/sbin/agetty --autologin root --noclear %I $TERM
261222
},
262223
}
263224

264-
// Only qemu has the qemu firmware environment setting
265-
if ign.VMType == define.QemuVirt {
266-
qemuUnit := Unit{
267-
Enabled: BoolToPtr(true),
268-
Name: "envset-fwcfg.service",
269-
Contents: &envsetFile,
270-
}
271-
ignSystemd.Units = append(ignSystemd.Units, qemuUnit)
272-
}
273-
274225
if ign.NetRecover {
275226
contents, err := GetNetRecoveryUnitFile().ToString()
276227
if err != nil {
@@ -582,23 +533,29 @@ Delegate=memory pids cpu io
582533
certFiles = getCerts(filepath.Join(userHome, ".config/docker/certs.d"), true)
583534
files = append(files, certFiles...)
584535

585-
if sslCertFile, ok := os.LookupEnv("SSL_CERT_FILE"); ok {
586-
if _, err := os.Stat(sslCertFile); err == nil {
587-
certFiles = getCerts(sslCertFile, false)
536+
sslCertFileName, ok := os.LookupEnv(sslCertFile)
537+
if ok {
538+
if _, err := os.Stat(sslCertFileName); err == nil {
539+
certFiles = getCerts(sslCertFileName, false)
588540
files = append(files, certFiles...)
589541
} else {
590-
logrus.Warnf("Invalid path in SSL_CERT_FILE: %q", err)
542+
logrus.Warnf("Invalid path in %s: %q", sslCertFile, err)
591543
}
592544
}
593545

594-
if sslCertDir, ok := os.LookupEnv("SSL_CERT_DIR"); ok {
595-
if _, err := os.Stat(sslCertDir); err == nil {
596-
certFiles = getCerts(sslCertDir, true)
546+
sslCertDirName, ok := os.LookupEnv(sslCertDir)
547+
if ok {
548+
if _, err := os.Stat(sslCertDirName); err == nil {
549+
certFiles = getCerts(sslCertDirName, true)
597550
files = append(files, certFiles...)
598551
} else {
599-
logrus.Warnf("Invalid path in SSL_CERT_DIR: %q", err)
552+
logrus.Warnf("Invalid path in %s: %q", sslCertDir, err)
600553
}
601554
}
555+
if sslCertFileName != "" || sslCertDirName != "" {
556+
// If we copied certs via env then also make the to set the env in the VM.
557+
files = append(files, getSSLEnvironmentFiles(sslCertFileName, sslCertDirName)...)
558+
}
602559

603560
files = append(files, File{
604561
Node: Node{
@@ -686,16 +643,18 @@ func getCerts(certsDir string, isDir bool) []File {
686643
return files
687644
}
688645

689-
func prepareCertFile(path string, name string) (File, error) {
690-
b, err := os.ReadFile(path)
646+
func prepareCertFile(fpath string, name string) (File, error) {
647+
b, err := os.ReadFile(fpath)
691648
if err != nil {
692649
logrus.Warnf("Unable to read cert file %v", err)
693650
return File{}, err
694651
}
695652

696-
targetPath := filepath.Join(define.UserCertsTargetPath, name)
653+
// Note path is required here as we always create a path for the linux VM
654+
// even when the client run on windows so we cannot use filepath.
655+
targetPath := path.Join(define.UserCertsTargetPath, name)
697656

698-
logrus.Debugf("Copying cert file from '%s' to '%s'.", path, targetPath)
657+
logrus.Debugf("Copying cert file from '%s' to '%s'.", fpath, targetPath)
699658

700659
file := File{
701660
Node: Node{
@@ -714,6 +673,57 @@ func prepareCertFile(path string, name string) (File, error) {
714673
return file, nil
715674
}
716675

676+
const (
677+
systemdSSLConf = "/etc/systemd/system.conf.d/podman-machine-ssl.conf"
678+
envdSSLConf = "/etc/environment.d/podman-machine-ssl.conf"
679+
profileSSLConf = "/etc/profile.d/podman-machine-ssl.sh"
680+
sslCertFile = "SSL_CERT_FILE"
681+
sslCertDir = "SSL_CERT_DIR"
682+
)
683+
684+
func getSSLEnvironmentFiles(sslFileName, sslDirName string) []File {
685+
systemdFileContent := "[Manager]\n"
686+
envdFileContent := ""
687+
profileFileContent := ""
688+
if sslFileName != "" {
689+
// certs are written to UserCertsTargetPath see prepareCertFile()
690+
// Note the mix of path/filepath is intentional and required, we want to get the name of
691+
// a path on the client (i.e. windows) but then join to linux path that will be used inside the VM.
692+
env := fmt.Sprintf("%s=%q\n", sslCertFile, path.Join(define.UserCertsTargetPath, filepath.Base(sslFileName)))
693+
systemdFileContent += "DefaultEnvironment=" + env
694+
envdFileContent += env
695+
profileFileContent += "export " + env
696+
}
697+
if sslDirName != "" {
698+
// certs are written to UserCertsTargetPath see prepareCertFile()
699+
env := fmt.Sprintf("%s=%q\n", sslCertDir, define.UserCertsTargetPath)
700+
systemdFileContent += "DefaultEnvironment=" + env
701+
envdFileContent += env
702+
profileFileContent += "export " + env
703+
}
704+
return []File{
705+
getSSLFile(systemdSSLConf, systemdFileContent),
706+
getSSLFile(envdSSLConf, envdFileContent),
707+
getSSLFile(profileSSLConf, profileFileContent),
708+
}
709+
}
710+
711+
func getSSLFile(path, content string) File {
712+
return File{
713+
Node: Node{
714+
Group: GetNodeGrp("root"),
715+
Path: path,
716+
User: GetNodeUsr("root"),
717+
},
718+
FileEmbedded1: FileEmbedded1{
719+
Contents: Resource{
720+
Source: EncodeDataURLPtr(content),
721+
},
722+
Mode: IntToPtr(0644),
723+
},
724+
}
725+
}
726+
717727
func getLinks(usrName string) []Link {
718728
return []Link{{
719729
Node: Node{

pkg/machine/proxyenv/env.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package proxyenv
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"strings"
8+
9+
"github.com/containers/common/libnetwork/etchosts"
10+
"github.com/containers/common/pkg/config"
11+
"github.com/containers/podman/v5/pkg/machine"
12+
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
13+
"github.com/sirupsen/logrus"
14+
)
15+
16+
const proxySetupScriptTemplate = `#!/bin/bash
17+
18+
SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf
19+
ENVD_CONF=/etc/environment.d/default-env.conf
20+
PROFILE_CONF=/etc/profile.d/default-env.sh
21+
22+
mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/
23+
rm -f $SYSTEMD_CONF $ENVD_CONF $PROFILE_CONF
24+
25+
echo "[Manager]" >> $SYSTEMD_CONF
26+
for proxy in %s; do
27+
printf "DefaultEnvironment=%%q\n" "$proxy" >> $SYSTEMD_CONF
28+
printf "%%q\n" "$proxy" >> $ENVD_CONF
29+
printf "export %%q\n" "$proxy" >> $PROFILE_CONF
30+
done
31+
32+
systemctl daemon-reload
33+
`
34+
35+
func getProxyScript(isWSL bool) io.Reader {
36+
var envs []string
37+
for _, key := range config.ProxyEnv {
38+
if value, ok := os.LookupEnv(key); ok {
39+
// WSL does not use host.containers.internal as valid name for the VM.
40+
if !isWSL {
41+
value = strings.ReplaceAll(value, "127.0.0.1", etchosts.HostContainersInternal)
42+
value = strings.ReplaceAll(value, "localhost", etchosts.HostContainersInternal)
43+
}
44+
// %q to quote the value correctly
45+
envs = append(envs, fmt.Sprintf("%q", key+"="+value))
46+
}
47+
}
48+
49+
script := fmt.Sprintf(proxySetupScriptTemplate, strings.Join(envs, " "))
50+
logrus.Tracef("Final environment variable setup script: %s", script)
51+
return strings.NewReader(script)
52+
}
53+
54+
func ApplyProxies(mc *vmconfigs.MachineConfig) error {
55+
return machine.CommonSSHWithStdin("root", mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, []string{"/usr/bin/bash"},
56+
getProxyScript(mc.WSLHypervisor != nil))
57+
}

pkg/machine/qemu/command/command.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,6 @@ func (q *QemuCmd) SetDisplay(display string) {
107107
*q = append(*q, "-display", display)
108108
}
109109

110-
// SetPropagatedHostEnvs adds options that propagate SSL and proxy settings
111-
func (q *QemuCmd) SetPropagatedHostEnvs() {
112-
*q = PropagateHostEnv(*q)
113-
}
114-
115110
func (q *QemuCmd) Build() []string {
116111
return *q
117112
}

0 commit comments

Comments
 (0)