Skip to content

Commit 67ec203

Browse files
committed
Add support for configuring tls verification with machine init
This patch adds a new --tls-verify flag to the `podman machine init` sub command which matches many of our other commands. This allows the user to optionally control whether TLS verification is enabled or disabled for download of the machine image. The default remains to leave the TLS verification decision to the backend library which defaults to enabling it, this patch just allows the user to explicitly set it on the CLI. Fixes: #26517 Signed-off-by: Lewis Roy <[email protected]>
1 parent 0c4c9e4 commit 67ec203

File tree

10 files changed

+108
-34
lines changed

10 files changed

+108
-34
lines changed

cmd/podman/machine/init.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/containers/common/pkg/completion"
1111
"github.com/containers/common/pkg/strongunits"
12+
"github.com/containers/image/v5/types"
1213
"github.com/containers/podman/v5/cmd/podman/registry"
1314
ldefine "github.com/containers/podman/v5/libpod/define"
1415
"github.com/containers/podman/v5/libpod/events"
@@ -41,6 +42,7 @@ var (
4142
// Flags which have a meaning when unspecified that differs from the flag default
4243
type InitOptionalFlags struct {
4344
UserModeNetworking bool
45+
tlsVerify bool
4446
}
4547

4648
// maxMachineNameSize is set to thirty to limit huge machine names primarily
@@ -154,6 +156,9 @@ func init() {
154156
userModeNetFlagName := "user-mode-networking"
155157
flags.BoolVar(&initOptionalFlags.UserModeNetworking, userModeNetFlagName, false,
156158
"Whether this machine should use user-mode networking, routing traffic through a host user-space process")
159+
160+
flags.BoolVar(&initOptionalFlags.tlsVerify, "tls-verify", true,
161+
"Require HTTPS and verify certificates when contacting registries")
157162
}
158163

159164
func initMachine(cmd *cobra.Command, args []string) error {
@@ -219,6 +224,16 @@ func initMachine(cmd *cobra.Command, args []string) error {
219224
}
220225
}
221226

227+
// initOpts.SkipTlsVerify defaults to OptionalBoolUndefined, which means the backend library
228+
// decides whether to verify TLS. We only explicitly set it if the user specifies the
229+
// --tls-verify flag on the CLI.
230+
//
231+
// The flag value from initOptionalFlags.tlsVerify indicates whether TLS verification is desired.
232+
// Since we are converting tlsVerify -> SkipTlsVerify, we must invert the bool accordingly.
233+
if cmd.Flags().Changed("tls-verify") {
234+
initOpts.SkipTlsVerify = types.NewOptionalBool(!initOptionalFlags.tlsVerify)
235+
}
236+
222237
// TODO need to work this back in
223238
// if finished, err := vm.Init(initOpts); err != nil || !finished {
224239
// // Finished = true, err = nil - Success! Log a message with further instructions

docs/source/markdown/options/tls-verify.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
####> This option file is used in:
2-
####> podman artifact pull, artifact push, auto update, build, container runlabel, create, farm build, kube play, login, manifest add, manifest create, manifest inspect, manifest push, pull, push, run, search
2+
####> podman artifact pull, artifact push, auto update, build, container runlabel, create, farm build, kube play, login, machine init, manifest add, manifest create, manifest inspect, manifest push, pull, push, run, search
33
####> If file is edited, make sure the changes
44
####> are applicable to all of those.
55
#### **--tls-verify**

docs/source/markdown/podman-machine-init.1.md.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ means to use the timezone of the machine host.
119119
The timezone setting is not used with WSL. WSL automatically sets the timezone to the same
120120
as the host Windows operating system.
121121

122+
@@option tls-verify
123+
122124
#### **--usb**=*bus=number,devnum=number* or *vendor=hexadecimal,product=hexadecimal*
123125

124126
Assign a USB device from the host to the VM via USB passthrough.

pkg/machine/define/initopts.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package define
22

3-
import "net/url"
3+
import (
4+
"net/url"
5+
6+
"github.com/containers/image/v5/types"
7+
)
48

59
type InitOptions struct {
610
PlaybookPath string
@@ -21,4 +25,5 @@ type InitOptions struct {
2125
UID string // uid of the user that called machine
2226
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
2327
USBs []string
28+
SkipTlsVerify types.OptionalBool
2429
}

pkg/machine/e2e/basic_test.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,18 +298,39 @@ var _ = Describe("run basic podman commands", func() {
298298

299299
It("CVE-2025-6032 regression test - HTTP", func() {
300300
// ensure that trying to pull from a local HTTP server fails and the connection will be rejected
301-
testImagePullTLS(nil)
301+
// ensure that tlsVerify is true by default
302+
testImagePullTLS(nil, nil)
302303
})
303304

304305
It("CVE-2025-6032 regression test - HTTPS unknown cert", func() {
305-
// ensure that trying to pull from an local HTTPS server with invalid certs fails and the connection will be rejected
306+
// ensure that trying to pull from a local HTTPS server with invalid certs fails and the connection will be rejected
307+
// ensure that tlsVerify is true by default
306308
testImagePullTLS(&TLSConfig{
307309
// Key/Cert was generated with:
308310
// openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -days 3650 \
309311
// -nodes -keyout test-tls.key -out test-tls.crt -subj "/CN=test.podman.io" -addext "subjectAltName=IP:127.0.0.1"
310312
key: "test-tls.key",
311313
cert: "test-tls.crt",
312-
})
314+
}, nil)
315+
})
316+
317+
It("machine init should not fail on TLS validation with --tls-verfy=false - HTTP", func() {
318+
// ensure that trying to pull from a local HTTP server doesn't fail when --tls-verify=false is set
319+
tlsVerify := false
320+
testImagePullTLS(nil, &tlsVerify)
321+
})
322+
323+
It("machine init should not fail on TLS validation with --tls-verfy=false - HTTPS", func() {
324+
// ensure that trying to pull from a local HTTPS server with invalid certs
325+
// doesn't fail due to tls validation when --tls-verify=false is set
326+
tlsVerify := false
327+
testImagePullTLS(&TLSConfig{
328+
// Key/Cert was generated with:
329+
// openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 -days 3650 \
330+
// -nodes -keyout test-tls.key -out test-tls.crt -subj "/CN=test.podman.io" -addext "subjectAltName=IP:127.0.0.1"
331+
key: "test-tls.key",
332+
cert: "test-tls.crt",
333+
}, &tlsVerify)
313334
})
314335
})
315336

@@ -349,7 +370,7 @@ type TLSConfig struct {
349370

350371
// setup a local webserver in the test and then point podman machine init to it
351372
// to verify the connection details.
352-
func testImagePullTLS(tls *TLSConfig) {
373+
func testImagePullTLS(tls *TLSConfig, tlsVerify *bool) {
353374
listener, err := net.Listen("tcp4", "127.0.0.1:0")
354375
Expect(err).ToNot(HaveOccurred())
355376
serverAddr := listener.Addr().String()
@@ -377,17 +398,29 @@ func testImagePullTLS(tls *TLSConfig) {
377398
}
378399
}()
379400

380-
name := randomString()
381401
i := new(initMachine)
382-
session, err := mb.setName(name).setCmd(i.withImage("docker://" + serverAddr + "/testimage")).run()
402+
403+
i.withImage("docker://" + serverAddr + "/testimage")
404+
405+
if tlsVerify != nil {
406+
i.withTlsVerify(tlsVerify)
407+
}
408+
409+
name := randomString()
410+
session, err := mb.setName(name).setCmd(i).run()
411+
383412
Expect(err).ToNot(HaveOccurred())
384413
Expect(session).To(Exit(125))
385414

386415
// Note because we don't run a real registry the error you get when TLS is not checked is:
387416
// Error: wrong manifest type for disk artifact: text/plain
388417
// As such we match the errors strings exactly to ensure we have proper error messages that indicate the TLS error.
389418
expectedErr := "Error: pinging container registry " + serverAddr + ": Get \"https://" + serverAddr + "/v2/\": "
390-
if tls != nil {
419+
420+
switch {
421+
case tlsVerify != nil && *tlsVerify == false: // tls-verify explicitly disabled
422+
expectedErr = "Error: wrong manifest type for disk artifact: text/plain\n"
423+
case tls != nil:
391424
expectedErr += "tls: failed to verify certificate: x509: "
392425
if runtime.GOOS == "darwin" {
393426
// Apple doesn't like such long valid certs so the error is different but the purpose
@@ -397,13 +430,18 @@ func testImagePullTLS(tls *TLSConfig) {
397430
} else {
398431
expectedErr += "certificate signed by unknown authority\n"
399432
}
400-
} else {
433+
default:
434+
// With both tlsVerify and tls being nil, a HTTP server will be ran and machine init should
435+
// default to using tlsVerify
401436
expectedErr += "http: server gave HTTP response to HTTPS client\n"
402437
}
438+
403439
Expect(session.errorToString()).To(Equal(expectedErr))
404440

405441
// if the client enforces TLS verification then we should not have received any request
406-
Expect(loggedRequests).To(BeEmpty(), "the server should have not process any request from the client")
442+
if tlsVerify == nil || *tlsVerify == true {
443+
Expect(loggedRequests).To(BeEmpty(), "the server should have not process any request from the client")
444+
}
407445

408446
srv.Close()
409447
Expect(<-serverErr).To(Equal(http.ErrServerClosed))

pkg/machine/e2e/config_init_test.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ import (
1111

1212
type initMachine struct {
1313
/*
14-
--cpus uint Number of CPUs (default 1)
15-
--disk-size uint Disk size in GiB (default 100)
16-
--ignition-path string Path to ignition file
17-
--username string Username of the remote user (default "core" for FCOS, "user" for Fedora)
18-
--image-path string Path to bootable image (default "testing")
19-
-m, --memory uint Memory in MiB (default 2048)
20-
--now Start machine now
21-
--rootful Whether this machine should prefer rootful container execution
22-
--playbook string Run an ansible playbook after first boot
23-
--timezone string Set timezone (default "local")
24-
-v, --volume stringArray Volumes to mount, source:target
25-
--volume-driver string Optional volume driver
26-
14+
--cpus uint Number of CPUs (default 1)
15+
--disk-size uint Disk size in GiB (default 100)
16+
--ignition-path string Path to ignition file
17+
--username string Username of the remote user (default "core" for FCOS, "user" for Fedora)
18+
--image-path string Path to bootable image (default "testing")
19+
-m, --memory uint Memory in MiB (default 2048)
20+
--now Start machine now
21+
--rootful Whether this machine should prefer rootful container execution
22+
--playbook string Run an ansible playbook after first boot
23+
--tls-verify Require HTTPS and verify certificates when contacting registries
24+
--timezone string Set timezone (default "local")
25+
-v, --volume stringArray Volumes to mount, source:target
26+
--volume-driver string Optional volume driver
2727
*/
2828
playbook string
2929
cpus *uint
@@ -38,6 +38,7 @@ type initMachine struct {
3838
rootful bool
3939
volumes []string
4040
userModeNetworking bool
41+
tlsVerify *bool
4142

4243
cmd []string
4344
}
@@ -85,6 +86,9 @@ func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
8586
if i.swap != nil {
8687
cmd = append(cmd, "--swap", strconv.Itoa(int(*i.swap)))
8788
}
89+
if i.tlsVerify != nil {
90+
cmd = append(cmd, "--tls-verify="+strconv.FormatBool(*i.tlsVerify))
91+
}
8892
name := m.name
8993
cmd = append(cmd, name)
9094

@@ -172,6 +176,11 @@ func (i *initMachine) withRunPlaybook(p string) *initMachine {
172176
return i
173177
}
174178

179+
func (i *initMachine) withTlsVerify(tlsVerify *bool) *initMachine {
180+
i.tlsVerify = tlsVerify
181+
return i
182+
}
183+
175184
func (i *initMachine) withUserModeNetworking(r bool) *initMachine { //nolint:unused,nolintlint
176185
i.userModeNetworking = r
177186
return i

pkg/machine/e2e/machine_pull_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"runtime"
88
"strings"
99

10+
"github.com/containers/image/v5/types"
1011
"github.com/containers/podman/v5/pkg/machine/compression"
1112
"github.com/containers/podman/v5/pkg/machine/define"
1213
"github.com/containers/podman/v5/pkg/machine/ocipull"
@@ -22,7 +23,9 @@ func pullOCITestDisk(finalDir string, vmType define.VMType) error {
2223
return err
2324
}
2425
dirs := define.MachineDirs{ImageCacheDir: imageCacheDir}
25-
ociArtPull, err := ocipull.NewOCIArtifactPull(context.Background(), &dirs, "", "e2emachine", vmType, unusedFinalPath)
26+
27+
var skipTlsVerify types.OptionalBool
28+
ociArtPull, err := ocipull.NewOCIArtifactPull(context.Background(), &dirs, "", "e2emachine", vmType, unusedFinalPath, skipTlsVerify)
2629
if err != nil {
2730
return err
2831
}

pkg/machine/ocipull/ociartifact.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,8 @@ type DiskArtifactOpts struct {
7171
7272
*/
7373

74-
func NewOCIArtifactPull(ctx context.Context, dirs *define.MachineDirs, endpoint string, vmName string, vmType define.VMType, finalPath *define.VMFile) (*OCIArtifactDisk, error) {
75-
var (
76-
arch string
77-
)
74+
func NewOCIArtifactPull(ctx context.Context, dirs *define.MachineDirs, endpoint string, vmName string, vmType define.VMType, finalPath *define.VMFile, skipTlsVerify types.OptionalBool) (*OCIArtifactDisk, error) {
75+
var arch string
7876

7977
artifactVersion := getVersion()
8078
switch runtime.GOARCH {
@@ -108,8 +106,10 @@ func NewOCIArtifactPull(ctx context.Context, dirs *define.MachineDirs, endpoint
108106
imageEndpoint: endpoint,
109107
machineVersion: artifactVersion,
110108
name: vmName,
111-
pullOptions: &PullOptions{},
112-
vmType: vmType,
109+
pullOptions: &PullOptions{
110+
SkipTLSVerify: skipTlsVerify,
111+
},
112+
vmType: vmType,
113113
}
114114
return &ociDisk, nil
115115
}

pkg/machine/shim/diskpull/diskpull.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ import (
44
"context"
55
"strings"
66

7+
"github.com/containers/image/v5/types"
78
"github.com/containers/podman/v5/pkg/machine/define"
89
"github.com/containers/podman/v5/pkg/machine/ocipull"
910
"github.com/containers/podman/v5/pkg/machine/stdpull"
1011
)
1112

12-
func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.VMFile, vmType define.VMType, name string) error {
13+
func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.VMFile, vmType define.VMType, name string, skipTlsVerify types.OptionalBool) error {
1314
var (
1415
err error
1516
mydisk ocipull.Disker
1617
)
1718

1819
if userInputPath == "" || strings.HasPrefix(userInputPath, "docker://") {
19-
mydisk, err = ocipull.NewOCIArtifactPull(context.Background(), dirs, userInputPath, name, vmType, imagePath)
20+
mydisk, err = ocipull.NewOCIArtifactPull(context.Background(), dirs, userInputPath, name, vmType, imagePath, skipTlsVerify)
2021
} else {
2122
if strings.HasPrefix(userInputPath, "http") {
2223
// TODO probably should use tempdir instead of datadir
@@ -28,5 +29,6 @@ func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.V
2829
if err != nil {
2930
return err
3031
}
32+
3133
return mydisk.Get()
3234
}

pkg/machine/shim/host.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
172172
// "/path
173173
// "docker://quay.io/something/someManifest
174174

175-
if err := diskpull.GetDisk(opts.Image, dirs, mc.ImagePath, mp.VMType(), mc.Name); err != nil {
175+
if err := diskpull.GetDisk(opts.Image, dirs, mc.ImagePath, mp.VMType(), mc.Name, opts.SkipTlsVerify); err != nil {
176176
return err
177177
}
178178

0 commit comments

Comments
 (0)