Skip to content

Commit f218f84

Browse files
committed
machine: implement http proxy logic for all providers
Copy all proxy envs into the VM on each start, this allows for updates without having to recrate the VM. This is implemented via shell script that is passed via ssh to the VM after it is started. With that we now use the same logic for all providers the old fw_cfg logic for qemu has been removed and the WSL code as well which keeps the behavior the same. There is a small risk now because we only update the env via ssh that processes started before will have the old incorrect env but it should really only effect core system processes which likely do not need them anyway. The podman system service should not be started at this point so it should be good enough. It also fixes the broken behavior with SSL_CERT_FILE/SSL_CERT_DIR which were updated on each start which is not correct as the files are only copied with ignition so these should not be updated and just set statically when the VM was created. e2e test has been added to ensure the behavior works as expected. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
1 parent 59b6f48 commit f218f84

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)