From feb36e4fe672f899303f8aeddf7663cf70d2b17b Mon Sep 17 00:00:00 2001 From: Andrew Melnick Date: Thu, 31 Jul 2025 18:51:37 -0600 Subject: [PATCH] Implement TLS API Support * Added flags to point to TLS PEM files to use for exposing and connecting to an encrypted remote API socket with server and client authentication. * Added TLS fields for system connection ls templates. * Added special "tls" format for system connection ls to list TLS fields in human-readable table format. * Updated remote integration and system tests to allow specifying a "transport" to run the full suite against a unix, tcp, tls, or mtls system service. * Added system tests to verify basic operation of unix, tcp, tls, and mtls services, clients, and connections. Signed-off-by: Andrew Melnick --- Makefile | 49 +--- cmd/podman/root.go | 24 ++ cmd/podman/system/connection/add.go | 46 ++- cmd/podman/system/connection/list.go | 22 +- cmd/podman/system/service.go | 43 ++- cmd/podman/system/service_abi.go | 6 +- .../podman-system-connection-add.1.md | 18 ++ .../podman-system-connection-list.1.md | 3 + .../markdown/podman-system-service.1.md | 18 +- docs/source/markdown/podman.1.md | 13 + go.mod | 18 +- go.sum | 48 +-- internal/domain/infra/runtime_abi.go | 9 +- pkg/api/server/server.go | 38 ++- pkg/bindings/connection.go | 81 +++++- pkg/bindings/containers/attach.go | 29 ++ pkg/domain/entities/engine.go | 3 + pkg/domain/entities/types/system.go | 11 +- pkg/domain/infra/runtime_abi.go | 18 +- pkg/domain/infra/runtime_tunnel.go | 15 +- pkg/util/tlsutil/tls.go | 32 ++ test/e2e/common_test.go | 188 +++++++++++- test/e2e/image_scp_test.go | 7 +- test/e2e/info_test.go | 9 +- test/e2e/libpod_suite_remote_test.go | 38 ++- test/e2e/libpod_suite_test.go | 10 +- test/e2e/system_connection_test.go | 113 ++++++-- test/system/001-basic.bats | 14 +- test/system/010-images.bats | 2 +- test/system/032-sig-proxy.bats | 6 +- test/system/035-logs.bats | 2 +- test/system/050-stop.bats | 2 +- test/system/065-cp.bats | 4 +- test/system/120-load.bats | 12 +- test/system/130-kill.bats | 4 +- test/system/160-volumes.bats | 2 +- test/system/200-pod.bats | 2 +- test/system/220-healthcheck.bats | 4 +- test/system/251-system-service.bats | 253 +++++++++++++++- test/system/271-tcp-cors-server.bats | 6 +- test/system/272-system-connection.bats | 273 ++++++++++++++---- test/system/273-remote-spot-check.bats | 150 ++++++++++ test/system/450-interactive.bats | 4 +- test/system/550-pause-process.bats | 4 +- test/system/700-play.bats | 6 +- test/system/850-compose.bats | 6 + test/system/helpers.bash | 168 ++++++++++- test/system/setup_suite.bash | 24 ++ test/utils/utils.go | 45 +-- .../github.com/google/pprof/profile/merge.go | 11 +- .../google/pprof/profile/profile.go | 20 +- .../github.com/google/pprof/profile/prune.go | 9 +- vendor/go.podman.io/common/libimage/copier.go | 11 +- .../common/libimage/filter/filter.go | 12 +- .../go.podman.io/common/libimage/filters.go | 10 +- vendor/go.podman.io/common/libimage/image.go | 17 +- .../common/libimage/image_config.go | 31 +- .../common/libimage/manifest_list.go | 6 +- .../common/libimage/manifests/manifests.go | 6 +- vendor/go.podman.io/common/libimage/pull.go | 6 +- vendor/go.podman.io/common/libimage/search.go | 28 +- .../common/libnetwork/cni/cni_conversion.go | 4 +- .../go.podman.io/common/libnetwork/cni/run.go | 6 +- .../common/libnetwork/etchosts/hosts.go | 13 +- .../common/libnetwork/pasta/pasta_linux.go | 3 +- .../libnetwork/resolvconf/resolvconf.go | 3 +- .../common/libnetwork/slirp4netns/const.go | 9 +- .../libnetwork/slirp4netns/const_linux.go | 11 + .../libnetwork/slirp4netns/slirp4netns.go | 8 +- .../common/pkg/apparmor/apparmor_linux.go | 8 +- vendor/go.podman.io/common/pkg/auth/auth.go | 3 +- .../common/pkg/cgroups/cgroups_linux.go | 19 +- .../common/pkg/cgroups/cgroups_unsupported.go | 8 - .../common/pkg/cgroups/systemd_linux.go | 2 +- .../common/pkg/cgroups/utils_linux.go | 2 +- .../pkg/cgroupv2/cgroups_unsupported.go | 2 +- .../common/pkg/completion/completion.go | 2 +- .../go.podman.io/common/pkg/config/config.go | 25 +- .../common/pkg/config/config_unsupported.go | 2 +- .../common/pkg/config/containers.conf | 17 +- .../common/pkg/config/containers.conf-freebsd | 7 + .../go.podman.io/common/pkg/config/default.go | 10 - .../common/pkg/config/default_bsd.go | 4 - .../common/pkg/config/default_darwin.go | 2 +- .../common/pkg/config/default_linux.go | 4 - .../pkg/config/default_unix_notdarwin.go | 17 ++ .../common/pkg/config/default_unsupported.go | 12 +- .../common/pkg/config/default_windows.go | 10 - .../go.podman.io/common/pkg/config/systemd.go | 13 +- .../common/pkg/manifests/manifests.go | 9 +- .../common/pkg/report/formatter.go | 9 +- .../common/pkg/report/template.go | 15 +- .../seccomp/{filter.go => filter_linux.go} | 0 .../common/pkg/seccomp/seccomp_unsupported.go | 6 +- .../{validate.go => validate_linux.go} | 0 .../pkg/secrets/shelldriver/shelldriver.go | 3 +- .../common/pkg/ssh/connection_golang.go | 3 +- .../common/pkg/ssh/connection_native.go | 8 +- .../common/pkg/subscriptions/subscriptions.go | 18 +- .../common/pkg/timetype/timestamp.go | 10 +- .../common/pkg/timezone/timezone.go | 6 +- .../common/pkg/timezone/timezone_freebsd.go | 7 + .../common/pkg/timezone/timezone_linux.go | 5 +- .../common/pkg/timezone/timezone_unix.go | 12 - .../common/pkg/timezone/timezone_windows.go | 5 - .../common/pkg/version/version.go | 11 +- vendor/go.podman.io/common/version/version.go | 2 +- .../api/annotations/annotations.pb.go | 2 +- .../googleapis/api/annotations/client.pb.go | 2 +- .../api/annotations/field_behavior.pb.go | 2 +- .../api/annotations/field_info.pb.go | 2 +- .../googleapis/api/annotations/http.pb.go | 2 +- .../googleapis/api/annotations/resource.pb.go | 2 +- .../googleapis/api/annotations/routing.pb.go | 2 +- .../googleapis/api/launch_stage.pb.go | 2 +- vendor/modules.txt | 24 +- 116 files changed, 1848 insertions(+), 616 deletions(-) create mode 100644 pkg/util/tlsutil/tls.go create mode 100644 test/system/273-remote-spot-check.bats create mode 100644 vendor/go.podman.io/common/libnetwork/slirp4netns/const_linux.go create mode 100644 vendor/go.podman.io/common/pkg/config/default_unix_notdarwin.go rename vendor/go.podman.io/common/pkg/seccomp/{filter.go => filter_linux.go} (100%) rename vendor/go.podman.io/common/pkg/seccomp/{validate.go => validate_linux.go} (100%) create mode 100644 vendor/go.podman.io/common/pkg/timezone/timezone_freebsd.go delete mode 100644 vendor/go.podman.io/common/pkg/timezone/timezone_unix.go delete mode 100644 vendor/go.podman.io/common/pkg/timezone/timezone_windows.go diff --git a/Makefile b/Makefile index 2ee6fee0a7..b28fa4cc16 100644 --- a/Makefile +++ b/Makefile @@ -150,12 +150,17 @@ GINKGO ?= ./bin/ginkgo GINKGO_FLAKE_ATTEMPTS ?= 0 GINKGO_NO_COLOR ?= y +# The type of transport to use for testing remote service. +# Must be one of unix, tcp, tls, mtls +export REMOTESYSTEM_TRANSPORT ?= unix +export REMOTEINTEGRATION_TRANSPORT ?= unix + # Conditional required to produce empty-output if binary not built yet. RELEASE_VERSION = $(shell if test -x test/version/version; then test/version/version; fi) RELEASE_NUMBER = $(shell echo "$(call err_if_empty,RELEASE_VERSION)" | sed -e 's/^v\(.*\)/\1/') -# If non-empty, logs all output from server during remote system testing -PODMAN_SERVER_LOG ?= +# Logs all output from server during remote system testing to this file +PODMAN_SERVER_LOG ?= /dev/null # Ensure GOBIN is not set so the default (`go env GOPATH`/bin) is used. override undefine GOBIN @@ -680,6 +685,7 @@ ginkgo-run: .install.ginkgo ginkgo: $(MAKE) ginkgo-run TAGS="$(BUILDTAGS)" + .PHONY: ginkgo-remote ginkgo-remote: $(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing" @@ -709,44 +715,15 @@ localsystem: PODMAN=$(CURDIR)/bin/podman QUADLET=$(CURDIR)/bin/quadlet bats -T --filter-tags '!ci:parallel' test/system/ PODMAN=$(CURDIR)/bin/podman QUADLET=$(CURDIR)/bin/quadlet bats -T --filter-tags ci:parallel -j $$(nproc) test/system/ + .PHONY: remotesystem remotesystem: # Wipe existing config, database, and cache: start with clean slate. $(RM) -rf ${HOME}/.local/share/containers ${HOME}/.config/containers - # . Make sure there's no active podman server - if there is, - # it's not us, and we have no way to know what it is. - # . Start server. Wait to make sure it comes up. - # . Run tests, pretty much the same as localsystem. - # . Stop server. - rc=0;\ - if timeout -v 1 true; then \ - if ./bin/podman-remote info; then \ - echo "Error: podman system service (not ours) is already running" >&2;\ - exit 1;\ - fi;\ - ./bin/podman system service --timeout=0 > $(if $(PODMAN_SERVER_LOG),$(PODMAN_SERVER_LOG),/dev/null) 2>&1 & \ - retry=5;\ - while [ $$retry -ge 0 ]; do\ - echo Waiting for server...;\ - sleep 1;\ - ./bin/podman-remote info >/dev/null 2>&1 && break;\ - retry=$$(expr $$retry - 1);\ - done;\ - if [ $$retry -lt 0 ]; then\ - echo "Error: ./bin/podman system service did not come up" >&2;\ - exit 1;\ - fi;\ - env PODMAN="$(CURDIR)/bin/podman-remote" bats -T --filter-tags '!ci:parallel' test/system/ ;\ - rc=$$?; \ - if [ $$rc -eq 0 ]; then \ - env PODMAN="$(CURDIR)/bin/podman-remote" bats -T --filter-tags ci:parallel -j $$(nproc) test/system/ ;\ - rc=$$?;\ - fi; \ - kill %1;\ - else \ - echo "Skipping $@: 'timeout -v' unavailable'";\ - fi;\ - exit $$rc + PODMAN=$(CURDIR)/bin/podman-remote QUADLET=$(CURDIR)/bin/quadlet \ + bats -T --filter-tags '!ci:parallel' test/system/ + PODMAN=$(CURDIR)/bin/podman-remote QUADLET=$(CURDIR)/bin/quadlet \ + bats -T --filter-tags ci:parallel -j $$(nproc) test/system/ .PHONY: localapiv2-bash localapiv2-bash: diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 7685b67c6f..e73f885186 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -167,6 +167,9 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity + podmanConfig.TLSCertFile = con.TLSCert + podmanConfig.TLSKeyFile = con.TLSKey + podmanConfig.TLSCAFile = con.TLSCA podmanConfig.MachineMode = con.IsMachine case url.Changed: podmanConfig.URI = url.Value.String() @@ -179,6 +182,9 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity + podmanConfig.TLSCertFile = con.TLSCert + podmanConfig.TLSKeyFile = con.TLSKey + podmanConfig.TLSCAFile = con.TLSCA podmanConfig.MachineMode = con.IsMachine } case host.Changed: @@ -213,6 +219,9 @@ func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string { } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity + podmanConfig.TLSCertFile = con.TLSCert + podmanConfig.TLSKeyFile = con.TLSKey + podmanConfig.TLSCAFile = con.TLSCA podmanConfig.MachineMode = con.IsMachine return con.Name case hostEnv != "": @@ -225,6 +234,9 @@ func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string { if err == nil { podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity + podmanConfig.TLSCertFile = con.TLSCert + podmanConfig.TLSKeyFile = con.TLSKey + podmanConfig.TLSCAFile = con.TLSCA podmanConfig.MachineMode = con.IsMachine return con.Name } @@ -521,6 +533,18 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) { lFlags.StringVar(&podmanConfig.Identity, identityFlagName, podmanConfig.Identity, "path to SSH identity file, (CONTAINER_SSHKEY)") _ = cmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault) + tlsCertFileFlagName := "tls-cert" + lFlags.StringVar(&podmanConfig.TLSCertFile, tlsCertFileFlagName, podmanConfig.TLSCertFile, "path to TLS client certificate PEM file for remote.") + _ = cmd.RegisterFlagCompletionFunc(tlsCertFileFlagName, completion.AutocompleteDefault) + + tlsKeyFileFlagName := "tls-key" + lFlags.StringVar(&podmanConfig.TLSKeyFile, tlsKeyFileFlagName, podmanConfig.TLSKeyFile, "path to TLS client certificate private key PEM file for remote.") + _ = cmd.RegisterFlagCompletionFunc(tlsKeyFileFlagName, completion.AutocompleteDefault) + + tlsCAFileFlagName := "tls-ca" + lFlags.StringVar(&podmanConfig.TLSCAFile, tlsCAFileFlagName, podmanConfig.TLSCAFile, "path to TLS certificate Authority PEM file for remote.") + _ = cmd.RegisterFlagCompletionFunc(tlsCAFileFlagName, completion.AutocompleteDefault) + // Flags that control or influence any kind of output. outFlagName := "out" lFlags.StringVar(&useStdout, outFlagName, "", "Send output (stdout) from podman to a file") diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 88611fea2e..c5bfbcf2f0 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -27,7 +27,7 @@ var ( "destination" is one of the form: [user@]hostname (will default to ssh) ssh://[user@]hostname[:port][/path] (will obtain socket path from service, if not given.) - tcp://hostname:port (not secured) + tcp://hostname:port (not secured without TLS enabled) unix://path (absolute path required) `, RunE: add, @@ -36,6 +36,7 @@ var ( podman system connection add --identity ~/.ssh/dev_rsa testing ssh://root@server.fubar.com:2222 podman system connection add --identity ~/.ssh/dev_rsa --port 22 production root@server.fubar.com podman system connection add debug tcp://localhost:8080 + podman system connection add production-tls --tls-ca=ca.crt --tls-cert=tls.crt --tls-key=tls.key tcp://localhost:8080 `, } @@ -51,11 +52,14 @@ var ( dockerPath string cOpts = struct { - Identity string - Port int - UDSPath string - Default bool - Farm string + Identity string + Port int + UDSPath string + Default bool + Farm string + TLSCertFile string + TLSKeyFile string + TLSCAFile string }{} ) @@ -74,6 +78,18 @@ func init() { flags.StringVar(&cOpts.Identity, identityFlagName, "", "path to SSH identity file") _ = addCmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault) + tlsCertFileFlagName := "tls-cert" + flags.StringVar(&cOpts.TLSCertFile, tlsCertFileFlagName, "", "path to TLS client certificate PEM file") + _ = addCmd.RegisterFlagCompletionFunc(tlsCertFileFlagName, completion.AutocompleteDefault) + + tlsKeyFileFlagName := "tls-key" + flags.StringVar(&cOpts.TLSKeyFile, tlsKeyFileFlagName, "", "path to TLS client certificate private key PEM file") + _ = addCmd.RegisterFlagCompletionFunc(tlsKeyFileFlagName, completion.AutocompleteDefault) + + tlsCAFileFlagName := "tls-ca" + flags.StringVar(&cOpts.TLSCAFile, tlsCAFileFlagName, "", "path to TLS certificate Authority PEM file") + _ = addCmd.RegisterFlagCompletionFunc(tlsCAFileFlagName, completion.AutocompleteDefault) + socketPathFlagName := "socket-path" flags.StringVar(&cOpts.UDSPath, socketPathFlagName, "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") _ = addCmd.RegisterFlagCompletionFunc(socketPathFlagName, completion.AutocompleteDefault) @@ -139,6 +155,17 @@ func add(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid ssh mode") } + if uri.Scheme != "tcp" { + if cmd.Flags().Changed("tls-cert") { + return fmt.Errorf("--tls-cert option not supported for %s scheme", uri.Scheme) + } + if cmd.Flags().Changed("tls-key") { + return fmt.Errorf("--tls-key option not supported for %s scheme", uri.Scheme) + } + if cmd.Flags().Changed("tls-ca") { + return fmt.Errorf("--tls-ca option not supported for %s scheme", uri.Scheme) + } + } switch uri.Scheme { case "ssh": return ssh.Create(entities, sshMode) @@ -146,7 +173,6 @@ func add(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("identity") { return errors.New("--identity option not supported for unix scheme") } - if cmd.Flags().Changed("socket-path") { uri.Path = cmd.Flag("socket-path").Value.String() } @@ -169,6 +195,9 @@ func add(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("identity") { return errors.New("--identity option not supported for tcp scheme") } + if cmd.Flags().Changed("tls-cert") != cmd.Flags().Changed("tls-key") { + return errors.New("--tls-cert and --tls-key options must be both provided if one is provided") + } if uri.Port() == "" { return errors.New("tcp scheme requires a port either via --port or in destination URL") } @@ -179,6 +208,9 @@ func add(cmd *cobra.Command, args []string) error { dst := config.Destination{ URI: uri.String(), Identity: cOpts.Identity, + TLSCert: cOpts.TLSCertFile, + TLSKey: cOpts.TLSKeyFile, + TLSCA: cOpts.TLSCAFile, } connection := args[0] diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go index 8adb55eb2a..e82438958a 100644 --- a/cmd/podman/system/connection/list.go +++ b/cmd/podman/system/connection/list.go @@ -24,8 +24,14 @@ var ( Short: "List destination for the Podman service(s)", Long: `List destination information for the Podman service(s) in podman configuration`, Example: `podman system connection list + # Format as table without TLS info podman system connection ls - podman system connection ls --format=json`, + # Format as table with TLS info + podman system connection ls --format=tls + # Format as JSON + podman system connection ls --format=json + # Format as custom go template + podman system connection ls --format='{{range .}}{{.Name}}{{ "\n" }}{{ end }}'`, ValidArgsFunction: completion.AutocompleteNone, RunE: list, TraverseChildren: false, @@ -114,12 +120,17 @@ func inspect(cmd *cobra.Command, args []string) error { return err } - if format != "" { - rpt, err = rpt.Parse(report.OriginUser, format) - } else { + switch format { + case "tls": + rpt, err = rpt.Parse(report.OriginPodman, + "{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.TLSCA}}\t{{.TLSCert}}\t{{.TLSKey}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}") + case "": rpt, err = rpt.Parse(report.OriginPodman, "{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}") + default: + rpt, err = rpt.Parse(report.OriginUser, format) } + if err != nil { return err } @@ -128,6 +139,9 @@ func inspect(cmd *cobra.Command, args []string) error { err = rpt.Execute([]map[string]string{{ "Default": "Default", "Identity": "Identity", + "TLSCA": "TLSCA", + "TLSCert": "TLSCert", + "TLSKey": "TLSKey", "Name": "Name", "URI": "URI", "ReadWrite": "ReadWrite", diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index c0c802ca12..800f3b928f 100644 --- a/cmd/podman/system/service.go +++ b/cmd/podman/system/service.go @@ -3,6 +3,7 @@ package system import ( + "fmt" "net/url" "os" "path/filepath" @@ -36,13 +37,19 @@ Enable a listening service for API access to Podman commands. RunE: service, ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman system service --time=0 unix:///tmp/podman.sock - podman system service --time=0 tcp://localhost:8888`, + podman system service --time=0 tcp://localhost:8888 + podman system service --time=0 --tls-cert=tls.crt --tls-key=tls.key tcp://localhost:8888 + podman system service --time=0 --tls-cert=tls.crt --tls-key=tls.key --tls-client-ca=ca.crt tcp://localhost:8888 + `, } srvArgs = struct { - CorsHeaders string - PProfAddr string - Timeout uint + CorsHeaders string + PProfAddr string + Timeout uint + TLSCertFile string + TLSKeyFile string + TLSClientCAFile string }{} ) @@ -67,6 +74,16 @@ func init() { flags.StringVarP(&srvArgs.PProfAddr, "pprof-address", "", "", "Binding network address for pprof profile endpoints, default: do not expose endpoints") _ = flags.MarkHidden("pprof-address") + + flags.StringVarP(&srvArgs.TLSCertFile, "tls-cert", "", "", + "PEM file containing TLS serving certificate.") + _ = srvCmd.RegisterFlagCompletionFunc("tls-cert", completion.AutocompleteDefault) + flags.StringVarP(&srvArgs.TLSKeyFile, "tls-key", "", "", + "PEM file containing TLS serving certificate private key") + _ = srvCmd.RegisterFlagCompletionFunc("tls-key", completion.AutocompleteDefault) + flags.StringVarP(&srvArgs.TLSClientCAFile, "tls-client-ca", "", "", + "Only trust client connections with certificates signed by this CA PEM file") + _ = srvCmd.RegisterFlagCompletionFunc("tls-client-ca", completion.AutocompleteDefault) } func aliasTimeoutFlag(_ *pflag.FlagSet, name string) pflag.NormalizedName { @@ -99,11 +116,21 @@ func service(cmd *cobra.Command, args []string) error { } } + if len(srvArgs.TLSCertFile) != 0 && len(srvArgs.TLSKeyFile) == 0 { + return fmt.Errorf("--tls-cert provided without --tls-key") + } + if len(srvArgs.TLSKeyFile) != 0 && len(srvArgs.TLSCertFile) == 0 { + return fmt.Errorf("--tls-key provided without --tls-cert") + } + return restService(cmd.Flags(), registry.PodmanConfig(), entities.ServiceOptions{ - CorsHeaders: srvArgs.CorsHeaders, - PProfAddr: srvArgs.PProfAddr, - Timeout: time.Duration(srvArgs.Timeout) * time.Second, - URI: apiURI, + CorsHeaders: srvArgs.CorsHeaders, + PProfAddr: srvArgs.PProfAddr, + Timeout: time.Duration(srvArgs.Timeout) * time.Second, + URI: apiURI, + TLSCertFile: srvArgs.TLSCertFile, + TLSKeyFile: srvArgs.TLSKeyFile, + TLSClientCAFile: srvArgs.TLSClientCAFile, }) } diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 43f4c5472f..c8b1cd5531 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -76,11 +76,13 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities } } case "tcp": - // We want to check if the user is requesting a TCP address. + // We want to check if the user is requesting a TCP address if TLS is not active. // If so, warn that this is insecure. // Ignore errors here, the actual backend code will handle them // better than we can here. - logrus.Warnf("Using the Podman API service with TCP sockets is not recommended, please see `podman system service` manpage for details") + if opts.TLSKeyFile == "" || opts.TLSCertFile == "" { + logrus.Warnf("Using the Podman API service with TCP sockets without TLS is not recommended, please see `podman system service` manpage for details") + } host := uri.Host if host == "" { diff --git a/docs/source/markdown/podman-system-connection-add.1.md b/docs/source/markdown/podman-system-connection-add.1.md index d5ed7ff934..4381c81a2c 100644 --- a/docs/source/markdown/podman-system-connection-add.1.md +++ b/docs/source/markdown/podman-system-connection-add.1.md @@ -35,6 +35,18 @@ Port for ssh destination. The default value is `22`. Path to the Podman service unix domain socket on the ssh destination host +#### **--tls-ca**=*path* + +Path to a PEM file containing the certificate authority bundle to verify the server's certificate against. + +#### **--tls-cert**=*path* + +Path to a PEM file containing the TLS client certificate to present to the server. `--tls-key` must also be provided. + +#### **--tls-key**=*path* + +Path to a PEM file containing the private key matching `--tls-cert`. `--tls-cert` must also be provided. + ## EXAMPLE Add a named system connection: @@ -55,6 +67,7 @@ $ podman system connection add testing unix:///run/podman/podman.sock Add a named system connection to local tcp socket: ``` $ podman system connection add debug tcp://localhost:8080 + ``` Add a connection with a custom port: ``` @@ -70,6 +83,11 @@ Add a connection and make it the default: ``` $ podman system connection add --default production root@prod.example.com ``` + +Add a named system connection to remote tcp socket secured via TLS: +``` +$ podman system connection add secure-debug --tls-cert=tls.crt --tls-key=tls.key --tls-ca=ca.crt tcp://podman.example.com:8443 +``` ## SEE ALSO **[podman(1)](podman.1.md)**, **[podman-system(1)](podman-system.1.md)**, **[podman-system-connection(1)](podman-system-connection.1.md)** diff --git a/docs/source/markdown/podman-system-connection-list.1.md b/docs/source/markdown/podman-system-connection-list.1.md index 116b314ce3..72482bf165 100644 --- a/docs/source/markdown/podman-system-connection-list.1.md +++ b/docs/source/markdown/podman-system-connection-list.1.md @@ -24,6 +24,9 @@ Valid placeholders for the Go template listed below: | .Identity | Path to file containing SSH identity | | .Name | Connection Name/Identifier | | .ReadWrite | Indicates if this connection can be modified using the system connection commands | +| .TLSCA | Path to a PEM file containing the certificate authority bundle to verify the server's certificate against. | +| .TLSCert | Path to a PEM file containing the certificate authority bundle to verify the server's certificate against. | +| .TLSKey | Path to a PEM file containing the private key matching `.TLSCA` | | .URI | URI to podman service. Valid schemes are ssh://[user@]*host*[:port]*Unix domain socket*[?secure=True], unix://*Unix domain socket*, and tcp://localhost[:*port*] | #### **--quiet**, **-q** diff --git a/docs/source/markdown/podman-system-service.1.md b/docs/source/markdown/podman-system-service.1.md index df49de5721..59558b790c 100644 --- a/docs/source/markdown/podman-system-service.1.md +++ b/docs/source/markdown/podman-system-service.1.md @@ -70,10 +70,11 @@ To access the API service inside a container: Please note that the API grants full access to all Podman functionality, and thus allows arbitrary code execution as the user running the API, with no ability to limit or audit this access. The API's security model is built upon access via a Unix socket with access restricted via standard file permissions, ensuring that only the user running the service will be able to access it. -We *strongly* recommend against making the API socket available via the network (IE, bindings the service to a *tcp* URL). +TLS can be used to secure this socket by requiring clients to present a certificate signed by a trusted certificate authority ("CA"), as well as to allow the client to verify the identity of the API. +We *strongly* recommend against making the API socket available via the network (IE, bindings the service to a *tcp* URL) without enabling mutual TLS to authenticate the client. Even access via Localhost carries risks - anyone with access to the system will be able to access the API. If remote access is required, we instead recommend forwarding the API socket via SSH, and limiting access on the remote machine to the greatest extent possible. -If a *tcp* URL must be used, using the *--cors* option is recommended to improve security. +If a *tcp* URL must be used without TLS, using the *--cors* option is recommended to improve security. ## OPTIONS @@ -90,6 +91,19 @@ Print usage statement. The time until the session expires in _seconds_. The default is 5 seconds. A value of `0` means no timeout, therefore the session does not expire. +#### **--tls-cert**=*path* + +Path to a PEM file containing the TLS certificate to present to clients. `--tls-key` must also be provided. + +#### **--tls-client-ca**=*path* + +Path to a PEM file containing the TLS certificate bundle to validate client connections against. +Connections that present no certificate or a certificate not signed by one of these certificates will be rejected. + +#### **--tls-key**=*path* + +Path to a PEM file containing the private key matching `--tls-cert`. `--tls-cert` must also be provided. + The default timeout can be changed via the `service_timeout=VALUE` field in containers.conf. See **[containers.conf(5)](https://github.com/containers/common/blob/main/docs/containers.conf.5.md)** for more information. diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index e74d9b42e1..492068c231 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -173,6 +173,19 @@ Output logging information to syslog as well as the console (default *false*). On remote clients, including Mac and Windows (excluding WSL2) machines, logging is directed to the file $HOME/.config/containers/podman.log. +#### **--tls-ca**=*path* + +Path to a PEM file containing the certificate authority bundle to verify the server's certificate against. + +#### **--tls-cert**=*path* + +Path to a PEM file containing the TLS client certificate to present to the server. `--tls-key` must also be provided. + +#### **--tls-key**=*path* + +Path to a PEM file containing the private key matching `--tls-cert`. `--tls-cert` must also be provided. + + #### **--tmpdir**=*path* Path to the tmp directory, for libpod runtime content. Defaults to `$XDG_RUNTIME_DIR/libpod/tmp` as rootless and `/run/libpod/tmp` as rootful. diff --git a/go.mod b/go.mod index e3572acef6..644d4662f2 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/containers/podman/v5 // Warning: if there is a "toolchain" directive anywhere in this file (and most of the // time there shouldn't be), its version must be an exact match to the "go" directive. -go 1.24.0 +go 1.24.2 require ( github.com/Microsoft/go-winio v0.6.2 github.com/blang/semver/v4 v4.0.0 github.com/checkpoint-restore/checkpointctl v1.4.0 github.com/checkpoint-restore/go-criu/v7 v7.2.0 - github.com/containernetworking/plugins v1.7.1 + github.com/containernetworking/plugins v1.8.0 github.com/containers/buildah v1.41.1-0.20250829135344-3367a9bc2c9f github.com/containers/conmon v2.0.20+incompatible github.com/containers/gvisor-tap-vsock v0.8.7 @@ -65,7 +65,7 @@ require ( github.com/vbauerster/mpb/v8 v8.10.2 github.com/vishvananda/netlink v1.3.1 go.etcd.io/bbolt v1.4.3 - go.podman.io/common v0.65.0 + go.podman.io/common v0.65.1-0.20250925174758-4cf0ff781bfc go.podman.io/image/v5 v5.37.0 go.podman.io/storage v1.60.0 golang.org/x/crypto v0.42.0 @@ -119,9 +119,9 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-containerregistry v0.20.3 // indirect + github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f // indirect github.com/google/go-intervals v0.0.2 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect @@ -145,7 +145,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/runc v1.3.0 // indirect + github.com/opencontainers/runc v1.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.9 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect @@ -156,7 +156,7 @@ require ( github.com/seccomp/libseccomp-golang v0.11.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect github.com/segmentio/ksuid v1.0.4 // indirect - github.com/sigstore/fulcio v1.6.6 // indirect + github.com/sigstore/fulcio v1.7.1 // indirect github.com/sigstore/protobuf-specs v0.4.1 // indirect github.com/sigstore/sigstore v1.9.5 // indirect github.com/skeema/knownhosts v1.3.1 // indirect @@ -186,8 +186,8 @@ require ( golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.36.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect google.golang.org/grpc v1.72.2 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect diff --git a/go.sum b/go.sum index e0b17043db..8cf1a7caf2 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= -github.com/containernetworking/plugins v1.7.1 h1:CNAR0jviDj6FS5Vg85NTgKWLDzZPfi/lj+VJfhMDTIs= -github.com/containernetworking/plugins v1.7.1/go.mod h1:xuMdjuio+a1oVQsHKjr/mgzuZ24leAsqUYRnzGoXHy0= +github.com/containernetworking/plugins v1.8.0 h1:WjGbV/0UQyo8A4qBsAh6GaDAtu1hevxVxsEuqtBqUFk= +github.com/containernetworking/plugins v1.8.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= github.com/containers/buildah v1.41.1-0.20250829135344-3367a9bc2c9f h1:t2zdi9mHtJoGmRMXa3i+oD/7xlYHIgoA+/Jtd0Ysf6c= github.com/containers/buildah v1.41.1-0.20250829135344-3367a9bc2c9f/go.mod h1:LtwfkfBed4dUOFTcBG+O+9Vcu5znw/PLYWDJ1mieHic= github.com/containers/common v0.62.2 h1:xO45OOoeq17EZMIDZoSyRqg7GXGcRHa9sXlrr75zH+U= @@ -102,8 +102,8 @@ github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWh github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= -github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY= +github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= @@ -156,15 +156,15 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= -github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= +github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f h1:q+kbH7LI4wK3gNCxyvy2rFldJqAAB+Gch79/xj9/+GU= +github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f/go.mod h1:UnXV0UkKqoHbzwn49vfozmwMcLMS8XLLsVKVuhv3cGc= github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4= github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -176,8 +176,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -292,8 +292,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runc v1.3.0 h1:cvP7xbEvD0QQAs0nZKLzkVog2OPZhI/V2w3WmTmUSXI= -github.com/opencontainers/runc v1.3.0/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs= +github.com/opencontainers/runc v1.3.1 h1:c/yY0oh2wK7tzDuD56REnSxyU8ubh8hoAIOLGLrm4SM= +github.com/opencontainers/runc v1.3.1/go.mod h1:9wbWt42gV+KRxKRVVugNP6D5+PQciRbenB4fLVsqGPs= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA= @@ -323,8 +323,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -345,12 +345,12 @@ github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= -github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw= -github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk= +github.com/sigstore/fulcio v1.7.1 h1:RcoW20Nz49IGeZyu3y9QYhyyV3ZKQ85T+FXPKkvE+aQ= +github.com/sigstore/fulcio v1.7.1/go.mod h1:7lYY+hsd8Dt+IvKQRC+KEhWpCZ/GlmNvwIa5JhypMS8= github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU= @@ -449,8 +449,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.podman.io/common v0.65.0 h1:8JNl25U4VpKDkFHSymSPm4te7ZQHJbfAB/l2FqtmYEg= -go.podman.io/common v0.65.0/go.mod h1:+lJu8KHeoDQsD9HDdiFaMaOUiqPLQnK406WuLnqM7Z0= +go.podman.io/common v0.65.1-0.20250925174758-4cf0ff781bfc h1:vLyukLe6FUCUxGDq9IN9WCpbzqHzMk8gQSuRh/z/pcs= +go.podman.io/common v0.65.1-0.20250925174758-4cf0ff781bfc/go.mod h1:DyOdwtkwzYA8lE0TueJnxRju4Lmsrx6ZAC/ATAkYYck= go.podman.io/image/v5 v5.37.0 h1:yzgQybwuWIIeK63hu+mQqna/wOh96XD5cpVc6j8Dg5M= go.podman.io/image/v5 v5.37.0/go.mod h1:+s2Sx5dia/jVeT8tI3r2NAPrARMiDdbEq3QPIQogx3I= go.podman.io/storage v1.60.0 h1:bWNSrR58nxg39VNFDSx3m0AswbvyzPGOo5XsUfomTao= @@ -578,10 +578,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= diff --git a/internal/domain/infra/runtime_abi.go b/internal/domain/infra/runtime_abi.go index 9dab3b190c..2af86eaa88 100644 --- a/internal/domain/infra/runtime_abi.go +++ b/internal/domain/infra/runtime_abi.go @@ -19,7 +19,14 @@ func NewTestingEngine(facts *entities.PodmanConfig) (ientities.TestingEngine, er r, err := NewLibpodTestingRuntime(facts.FlagSet, facts) return r, err case entities.TunnelMode: - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode) + ctx, err := bindings.NewConnectionWithOptions(context.Background(), bindings.Options{ + URI: facts.URI, + Identity: facts.Identity, + TLSCertFile: facts.TLSCertFile, + TLSKeyFile: facts.TLSKeyFile, + TLSCAFile: facts.TLSCAFile, + Machine: facts.MachineMode, + }) return &tunnel.TestingEngine{ClientCtx: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 8d6bc0b7af..da832045f4 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -4,6 +4,7 @@ package server import ( "context" + "crypto/tls" "fmt" "log" "net" @@ -22,6 +23,7 @@ import ( "github.com/containers/podman/v5/pkg/api/server/idle" "github.com/containers/podman/v5/pkg/api/types" "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/util/tlsutil" "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -38,6 +40,9 @@ type APIServer struct { CorsHeaders string // Inject Cross-Origin Resource Sharing (CORS) headers PProfAddr string // Binding network address for pprof profiles idleTracker *idle.Tracker // Track connections to support idle shutdown + tlsCertFile string // TLS serving certificate PEM file + tlsKeyFile string // TLS serving certificate private key PEM file + tlsClientCAFile string // TLS client certifiicate CA bundle PEM file } // Number of seconds to wait for next request, if exceeded shutdown server @@ -76,10 +81,13 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser Handler: router, IdleTimeout: opts.Timeout * 2, }, - CorsHeaders: opts.CorsHeaders, - Listener: listener, - PProfAddr: opts.PProfAddr, - idleTracker: tracker, + CorsHeaders: opts.CorsHeaders, + Listener: listener, + PProfAddr: opts.PProfAddr, + idleTracker: tracker, + tlsCertFile: opts.TLSCertFile, + tlsKeyFile: opts.TLSKeyFile, + tlsClientCAFile: opts.TLSClientCAFile, } server.BaseContext = func(l net.Listener) context.Context { @@ -90,6 +98,18 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser return ctx } + if opts.TLSClientCAFile != "" { + logrus.Debugf("will validate client certs against %s", opts.TLSClientCAFile) + pool, err := tlsutil.ReadCertBundle(opts.TLSClientCAFile) + if err != nil { + return nil, err + } + server.TLSConfig = &tls.Config{ + ClientCAs: pool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + } + // Capture panics and print stack traces for diagnostics, // additionally process X-Reference-Id Header to support event correlation router.Use(panicHandler(), referenceIDHandler()) @@ -217,7 +237,15 @@ func (s *APIServer) Serve() error { errChan := make(chan error, 1) s.setupSystemd() go func() { - err := s.Server.Serve(s.Listener) + var err error + if s.tlsClientCAFile != "" || (s.tlsCertFile != "" && s.tlsKeyFile != "") { + if s.tlsCertFile != "" && s.tlsKeyFile != "" { + logrus.Debugf("serving TLS with cert %s and key %s", s.tlsCertFile, s.tlsKeyFile) + } + err = s.Server.ServeTLS(s.Listener, s.tlsCertFile, s.tlsKeyFile) + } else { + err = s.Server.Serve(s.Listener) + } if err != nil && err != http.ErrServerClosed { errChan <- fmt.Errorf("failed to start API service: %w", err) return diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 00d03e0d59..f84838eb02 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -3,6 +3,7 @@ package bindings import ( "bytes" "context" + "crypto/tls" "errors" "fmt" "io" @@ -17,6 +18,7 @@ import ( "time" "github.com/blang/semver/v4" + "github.com/containers/podman/v5/pkg/util/tlsutil" "github.com/containers/podman/v5/version" "github.com/kevinburke/ssh_config" "github.com/sirupsen/logrus" @@ -33,6 +35,7 @@ type APIResponse struct { type Connection struct { URI *url.URL Client *http.Client + tls bool } type valueKey string @@ -89,7 +92,7 @@ func JoinURL(elements ...string) string { // NewConnection creates a new service connection without an identity func NewConnection(ctx context.Context, uri string) (context.Context, error) { - return NewConnectionWithIdentity(ctx, uri, "", false) + return NewConnectionWithOptions(ctx, Options{URI: uri}) } // NewConnectionWithIdentity takes a URI as a string and returns a context with the @@ -101,14 +104,31 @@ func NewConnection(ctx context.Context, uri string) (context.Context, error) { // or unix:///run/podman/podman.sock // or ssh://@[:port]/run/podman/podman.sock func NewConnectionWithIdentity(ctx context.Context, uri string, identity string, machine bool) (context.Context, error) { - var err error - if v, found := os.LookupEnv("CONTAINER_HOST"); found && uri == "" { - uri = v - } + return NewConnectionWithOptions(ctx, Options{URI: uri, Identity: identity, Machine: machine}) +} - if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identity) == 0 { - identity = v +type Options struct { + URI string + Identity string + TLSCertFile string + TLSKeyFile string + TLSCAFile string + Machine bool +} + +func orEnv(s string, env string) string { + if len(s) != 0 { + return s } + s, _ = os.LookupEnv(env) + return s +} + +func NewConnectionWithOptions(ctx context.Context, opts Options) (context.Context, error) { + var err error + + uri := orEnv(opts.URI, "CONTAINER_HOST") + identity := orEnv(opts.Identity, "CONTAINER_SSHKEY") _url, err := url.Parse(uri) if err != nil { @@ -119,7 +139,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string, var connection Connection switch _url.Scheme { case "ssh": - conn, err := sshClient(_url, uri, identity, machine) + conn, err := sshClient(_url, uri, identity, opts.Machine) if err != nil { return nil, err } @@ -135,7 +155,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string, if !strings.HasPrefix(uri, "tcp://") { return nil, errors.New("tcp URIs should begin with tcp://") } - conn, err := tcpClient(_url) + conn, err := tcpClient(_url, opts.TLSCertFile, opts.TLSKeyFile, opts.TLSCAFile) if err != nil { return nil, newConnectError(err) } @@ -151,7 +171,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string, } ctx = context.WithValue(ctx, versionKey, serviceVersion) - ctx = context.WithValue(ctx, machineModeKey, machine) + ctx = context.WithValue(ctx, machineModeKey, opts.Machine) return ctx, nil } @@ -288,7 +308,7 @@ func sshClient(_url *url.URL, uri string, identity string, machine bool) (Connec return connection, nil } -func tcpClient(_url *url.URL) (Connection, error) { +func tcpClient(_url *url.URL, tlsCertFile, tlsKeyFile, tlsCAFile string) (Connection, error) { connection := Connection{ URI: _url, } @@ -320,11 +340,34 @@ func tcpClient(_url *url.URL) (Connection, error) { } } } + transport := http.Transport{ + DialContext: dialContext, + DisableCompression: true, + } + if len(tlsCAFile) != 0 || len(tlsCertFile) != 0 || len(tlsKeyFile) != 0 { + logrus.Debugf("using TLS cert=%s key=%s ca=%s", tlsCertFile, tlsKeyFile, tlsCAFile) + transport.TLSClientConfig = &tls.Config{} + connection.tls = true + } + if len(tlsCAFile) != 0 { + pool, err := tlsutil.ReadCertBundle(tlsCAFile) + if err != nil { + return connection, fmt.Errorf("unable to read CA bundle: %w", err) + } + transport.TLSClientConfig.RootCAs = pool + } + if (len(tlsCertFile) == 0) != (len(tlsKeyFile) == 0) { + return connection, fmt.Errorf("TLS Key and Certificate must both or neither be provided") + } + if len(tlsCertFile) != 0 && len(tlsKeyFile) != 0 { + keyPair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) + if err != nil { + return connection, fmt.Errorf("unable to read TLS key pair: %w", err) + } + transport.TLSClientConfig.Certificates = append(transport.TLSClientConfig.Certificates, keyPair) + } connection.Client = &http.Client{ - Transport: &http.Transport{ - DialContext: dialContext, - DisableCompression: true, - }, + Transport: &transport, } return connection, nil } @@ -405,8 +448,14 @@ func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMeth baseURL := "http://d" if c.URI.Scheme == "tcp" { + var scheme string + if c.tls { + scheme = "https" + } else { + scheme = "http" + } // Allow path prefixes for tcp connections to match Docker behavior - baseURL = "http://" + c.URI.Host + c.URI.Path + baseURL = scheme + "://" + c.URI.Host + c.URI.Path } uri := fmt.Sprintf(baseURL+"/v%s/libpod"+endpoint, params...) logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri) diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index ff42c9928a..a5c100854b 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -3,6 +3,7 @@ package containers import ( "bytes" "context" + "crypto/tls" "encoding/binary" "errors" "fmt" @@ -548,9 +549,11 @@ func newUpgradeRequest(ctx context.Context, conn *bindings.Connection, body io.R "Upgrade": []string{"tcp"}, } + // FIXME: This is one giant race condition. Let's hope no-one uses this same client until we're done! var socket net.Conn socketSet := false dialContext := conn.Client.Transport.(*http.Transport).DialContext + tlsConfig := conn.Client.Transport.(*http.Transport).TLSClientConfig t := &http.Transport{ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { c, err := dialContext(ctx, network, address) @@ -563,7 +566,33 @@ func newUpgradeRequest(ctx context.Context, conn *bindings.Connection, body io.R } return c, err }, + DialTLSContext: func(ctx context.Context, network, address string) (net.Conn, error) { + c, err := dialContext(ctx, network, address) + if err != nil { + return nil, err + } + var cfg *tls.Config + if tlsConfig == nil { + cfg = new(tls.Config) + } else { + cfg = tlsConfig.Clone() + } + if cfg.ServerName == "" { + var firstTLSHost string + if firstTLSHost, _, err = net.SplitHostPort(address); err != nil { + return nil, err + } + cfg.ServerName = firstTLSHost + } + c = tls.Client(c, cfg) + if !socketSet { + socket = c + socketSet = true + } + return c, err + }, IdleConnTimeout: time.Duration(0), + TLSClientConfig: tlsConfig, } conn.Client.Transport = t response, err := conn.DoRequest(ctx, body, http.MethodPost, path, params, headers) diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index 8e17679791..35c8d5efd8 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -37,6 +37,9 @@ type PodmanConfig struct { HooksDir []string CdiSpecDirs []string Identity string // ssh identity for connecting to server + TLSCertFile string // tls client cert for connecting to server + TLSKeyFile string // tls client cert private key for connection to server + TLSCAFile string // tls certificate authority to verify server connection IsRenumber bool // Is this a system renumber command? If so, a number of checks will be relaxed IsReset bool // Is this a system reset command? If so, a number of checks will be skipped/omitted MaxWorks int // maximum number of parallel threads diff --git a/pkg/domain/entities/types/system.go b/pkg/domain/entities/types/system.go index 97310428ec..b2243b46ba 100644 --- a/pkg/domain/entities/types/system.go +++ b/pkg/domain/entities/types/system.go @@ -9,10 +9,13 @@ import ( // ServiceOptions provides the input for starting an API and sidecar pprof services type ServiceOptions struct { - CorsHeaders string // Cross-Origin Resource Sharing (CORS) headers - PProfAddr string // Network address to bind pprof profiles service - Timeout time.Duration // Duration of inactivity the service should wait before shutting down - URI string // Path to unix domain socket service should listen on + CorsHeaders string // Cross-Origin Resource Sharing (CORS) headers + PProfAddr string // Network address to bind pprof profiles service + Timeout time.Duration // Duration of inactivity the service should wait before shutting down + URI string // Path to unix domain socket service should listen on + TLSCertFile string // Path to serving certificate PEM file + TLSKeyFile string // Path to serving certificate key PEM file + TLSClientCAFile string // Path to client certificate authority } // SystemCheckOptions provides options for checking storage consistency. diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index 21704fa760..5f55db0de8 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -18,7 +18,14 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, r, err := NewLibpodRuntime(facts.FlagSet, facts) return r, err case entities.TunnelMode: - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode) + ctx, err := bindings.NewConnectionWithOptions(context.Background(), bindings.Options{ + URI: facts.URI, + Identity: facts.Identity, + TLSCertFile: facts.TLSCertFile, + TLSKeyFile: facts.TLSKeyFile, + TLSCAFile: facts.TLSCAFile, + Machine: facts.MachineMode, + }) return &tunnel.ContainerEngine{ClientCtx: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) @@ -32,7 +39,14 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) return r, err case entities.TunnelMode: // TODO: look at me! - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode) + ctx, err := bindings.NewConnectionWithOptions(context.Background(), bindings.Options{ + URI: facts.URI, + Identity: facts.Identity, + TLSCertFile: facts.TLSCertFile, + TLSKeyFile: facts.TLSKeyFile, + TLSCAFile: facts.TLSCAFile, + Machine: facts.MachineMode, + }) if err != nil { return nil, fmt.Errorf("%w: %s", err, facts.URI) } diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go index a28385890c..d1647b89c3 100644 --- a/pkg/domain/infra/runtime_tunnel.go +++ b/pkg/domain/infra/runtime_tunnel.go @@ -17,13 +17,20 @@ var ( connection *context.Context ) -func newConnection(uri string, identity, farmNodeName string, machine bool) (context.Context, error) { +func newConnection(uri string, identity, tlsCertFile, tlsKeyFile, tlsCAFile, farmNodeName string, machine bool) (context.Context, error) { connectionMutex.Lock() defer connectionMutex.Unlock() // if farmNodeName given, then create a connection with the node so that we can send builds there if connection == nil || farmNodeName != "" { - ctx, err := bindings.NewConnectionWithIdentity(context.Background(), uri, identity, machine) + ctx, err := bindings.NewConnectionWithOptions(context.Background(), bindings.Options{ + URI: uri, + Identity: identity, + TLSCertFile: tlsCertFile, + TLSKeyFile: tlsKeyFile, + TLSCAFile: tlsCAFile, + Machine: machine, + }) if err != nil { return ctx, err } @@ -37,7 +44,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, case entities.ABIMode: return nil, fmt.Errorf("direct runtime not supported") case entities.TunnelMode: - ctx, err := newConnection(facts.URI, facts.Identity, "", facts.MachineMode) + ctx, err := newConnection(facts.URI, facts.Identity, facts.TLSCertFile, facts.TLSKeyFile, facts.TLSCAFile, "", facts.MachineMode) return &tunnel.ContainerEngine{ClientCtx: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) @@ -49,7 +56,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) case entities.ABIMode: return nil, fmt.Errorf("direct image runtime not supported") case entities.TunnelMode: - ctx, err := newConnection(facts.URI, facts.Identity, facts.FarmNodeName, facts.MachineMode) + ctx, err := newConnection(facts.URI, facts.Identity, facts.TLSCertFile, facts.TLSKeyFile, facts.TLSCAFile, facts.FarmNodeName, facts.MachineMode) return &tunnel.ImageEngine{ClientCtx: ctx, FarmNode: tunnel.FarmNode{NodeName: facts.FarmNodeName}}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) diff --git a/pkg/util/tlsutil/tls.go b/pkg/util/tlsutil/tls.go new file mode 100644 index 0000000000..2e9552c231 --- /dev/null +++ b/pkg/util/tlsutil/tls.go @@ -0,0 +1,32 @@ +package tlsutil + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "os" +) + +func ReadCertBundle(path string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + caPEM, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading cert bundle %s: %w", path, err) + } + for ix := 0; len(caPEM) != 0; ix++ { + var caDER *pem.Block + caDER, caPEM = pem.Decode(caPEM) + if caDER == nil { + return nil, fmt.Errorf("reading cert bundle %s: non-PEM data found", path) + } + if caDER.Type != "CERTIFICATE" { + return nil, fmt.Errorf("reading cert bundle %s: non-certificate type `%s` PEM data found", path, caDER.Type) + } + caCert, err := x509.ParseCertificate(caDER.Bytes) + if err != nil { + return nil, fmt.Errorf("reading cert bundle %s: parsing item %d: %w", path, ix, err) + } + pool.AddCert(caCert) + } + return pool, nil +} diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 587b273284..872f6bd780 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -6,7 +6,11 @@ import ( "bufio" "bytes" crand "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" "io" @@ -68,8 +72,10 @@ type PodmanTestIntegration struct { TmpDir string } -var GlobalTmpDir string // Single top-level tmpdir for all tests -var LockTmpDir string +var ( + GlobalTmpDir string // Single top-level tmpdir for all tests + LockTmpDir string +) // PodmanSessionIntegration struct for command line session type PodmanSessionIntegration struct { @@ -278,8 +284,18 @@ func getPodmanBinary(cwd string) string { return podmanBinary } +type PodmanTestCreateUtilTarget string + +const ( + PodmanTestCreateUtilTargetLocal = "" + PodmanTestCreateUtilTargetUnix = "unix" + PodmanTestCreateUtilTargetTCP = "tcp" + PodmanTestCreateUtilTargetTLS = "tls" + PodmanTestCreateUtilTargetMTLS = "mtls" +) + // PodmanTestCreate creates a PodmanTestIntegration instance for the tests -func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { +func PodmanTestCreateUtil(tempDir string, target PodmanTestCreateUtilTarget) *PodmanTestIntegration { host := GetHostDistributionInfo() cwd, _ := os.Getwd() @@ -364,7 +380,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { PodmanBinary: podmanBinary, RemotePodmanBinary: podmanRemoteBinary, TempDir: tempDir, - RemoteTest: remote, + RemoteTest: target != PodmanTestCreateUtilTargetLocal, ImageCacheFS: storageFs, ImageCacheDir: ImageCacheDir, NetworkBackend: networkBackend, @@ -383,14 +399,20 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { Host: host, } - if remote { - var pathPrefix string + var pathPrefix string + switch target { + case PodmanTestCreateUtilTargetLocal: + default: if !isRootless() { pathPrefix = "/run/podman/podman" + Expect(os.MkdirAll(pathPrefix, 0o700)).To(Succeed()) } else { runtimeDir := os.Getenv("XDG_RUNTIME_DIR") pathPrefix = filepath.Join(runtimeDir, "podman") } + } + switch target { + case PodmanTestCreateUtilTargetUnix: // We want to avoid collisions in socket paths, but using the // socket directly for a collision check doesn’t work; bind(2) on AF_UNIX // creates the file, and we need to pass a unique path now before the bind(2) @@ -404,13 +426,153 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { lockFile.Close() p.RemoteSocketLock = lockPath p.RemoteSocket = fmt.Sprintf("unix://%s-%s.sock", pathPrefix, uuid) + p.RemoteSocketScheme = "unix" break } + GinkgoLogr.Error(err, "RemoteSocket collision") tries++ if tries >= 1000 { panic("Too many RemoteSocket collisions") } } + case PodmanTestCreateUtilTargetTCP, PodmanTestCreateUtilTargetTLS, PodmanTestCreateUtilTargetMTLS: + tries := 0 + for { + uuid := stringid.GenerateRandomID() + lockPath := fmt.Sprintf("%s-%s.sock-lock", pathPrefix, uuid) + lockFile, err := os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0700) + if err == nil { + lockFile.Close() + p.RemoteSocketLock = lockPath + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err == nil { + defer lis.Close() + p.RemoteSocket = fmt.Sprintf("tcp://%s", lis.Addr()) + p.RemoteSocketScheme = "tcp" + break + } + } + GinkgoLogr.Error(err, "RemoteSocket collision") + tries++ + if tries >= 1000 { + panic("Too many RemoteSocket collisions") + } + } + } + + caKeyPath := filepath.Join(p.TempDir, "tls.ca.key") + caCertPath := filepath.Join(p.TempDir, "tls.ca.crt") + srvCertPath := filepath.Join(p.TempDir, "tls.srv.crt") + srvKeyPath := filepath.Join(p.TempDir, "tls.srv.key") + clientCertPath := filepath.Join(p.TempDir, "tls.client.crt") + clientKeyPath := filepath.Join(p.TempDir, "tls.client.key") + switch target { + case PodmanTestCreateUtilTargetTLS, PodmanTestCreateUtilTargetMTLS: + GinkgoLogr.Info("Generating test TLS certs", "now", time.Now(), "tmpdir", p.TempDir) + now := time.Now() + caPriv, err := rsa.GenerateKey(crand.Reader, 2048) + Expect(err).ToNot(HaveOccurred()) + caTmpl := x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(5 * time.Minute), + IsCA: true, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + caCertDER, err := x509.CreateCertificate(crand.Reader, &caTmpl, &caTmpl, &caPriv.PublicKey, caPriv) + Expect(err).ToNot(HaveOccurred()) + caCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: caCertDER, + }) + caKeyDER, err := x509.MarshalPKCS8PrivateKey(caPriv) + Expect(err).ToNot(HaveOccurred()) + caKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: caKeyDER, + }) + err = os.WriteFile(caCertPath, caCertPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(caKeyPath, caKeyPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + + caCert, err := x509.ParseCertificate(caCertDER) + Expect(err).ToNot(HaveOccurred()) + + srvPriv, err := rsa.GenerateKey(crand.Reader, 2048) + Expect(err).ToNot(HaveOccurred()) + srvTmpl := x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(5 * time.Minute), + KeyUsage: x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + srvCertDER, err := x509.CreateCertificate(crand.Reader, &srvTmpl, caCert, &srvPriv.PublicKey, caPriv) + Expect(err).ToNot(HaveOccurred()) + srvCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: srvCertDER, + }) + srvKeyDER, err := x509.MarshalPKCS8PrivateKey(srvPriv) + Expect(err).ToNot(HaveOccurred()) + srvKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: srvKeyDER, + }) + err = os.WriteFile(srvCertPath, srvCertPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(srvKeyPath, srvKeyPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + + p.RemoteTLSServerCAFile = caCertPath + p.RemoteTLSServerCAPool = x509.NewCertPool() + p.RemoteTLSServerCAPool.AddCert(caCert) + p.RemoteTLSServerCertFile = srvCertPath + p.RemoteTLSServerKeyFile = srvKeyPath + if target == PodmanTestCreateUtilTargetMTLS { + clientPriv, err := rsa.GenerateKey(crand.Reader, 2048) + Expect(err).ToNot(HaveOccurred()) + clientTmpl := x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(5 * time.Minute), + KeyUsage: x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + } + clientCertDER, err := x509.CreateCertificate(crand.Reader, &clientTmpl, caCert, &clientPriv.PublicKey, caPriv) + Expect(err).ToNot(HaveOccurred()) + clientCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCertDER, + }) + clientKeyDER, err := x509.MarshalPKCS8PrivateKey(clientPriv) + Expect(err).ToNot(HaveOccurred()) + clientCert, err := x509.ParseCertificate(clientCertDER) + Expect(err).ToNot(HaveOccurred()) + clientKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: clientKeyDER, + }) + err = os.WriteFile(clientCertPath, clientCertPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(clientKeyPath, clientKeyPEM, 0o600) + Expect(err).ToNot(HaveOccurred()) + + p.RemoteTLSClientCAFile = caCertPath + p.RemoteTLSServerCAPool = x509.NewCertPool() + p.RemoteTLSServerCAPool.AddCert(caCert) + p.RemoteTLSClientCertFile = clientCertPath + p.RemoteTLSClientKeyFile = clientKeyPath + p.RemoteTLSClientCerts = []tls.Certificate{{ + Certificate: [][]byte{clientCertDER}, + PrivateKey: clientPriv, + Leaf: clientCert, + }} + } } // Set up registries.conf ENV variable @@ -1173,7 +1335,7 @@ func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionInteg } func PodmanTestSetup(tempDir string) *PodmanTestIntegration { - return PodmanTestCreateUtil(tempDir, false) + return PodmanTestCreateUtil(tempDir, PodmanTestCreateUtilTargetLocal) } // PodmanNoEvents calls the Podman command without an imagecache and without an @@ -1190,7 +1352,17 @@ func (p *PodmanTestIntegration) PodmanNoEvents(args []string) *PodmanSessionInte func (p *PodmanTestIntegration) makeOptions(args []string, options PodmanExecOptions) []string { if p.RemoteTest { if !slices.Contains(args, "--remote") { - return append([]string{"--remote", "--url", p.RemoteSocket}, args...) + remoteArgs := []string{"--remote", "--url", p.RemoteSocket} + if p.RemoteTLSServerCAFile != "" { + remoteArgs = append(remoteArgs, "--tls-ca", p.RemoteTLSServerCAFile) + } + if p.RemoteTLSClientCertFile != "" { + remoteArgs = append(remoteArgs, "--tls-cert", p.RemoteTLSClientCertFile) + } + if p.RemoteTLSClientKeyFile != "" { + remoteArgs = append(remoteArgs, "--tls-key", p.RemoteTLSClientKeyFile) + } + return append(remoteArgs, args...) } return args } diff --git a/test/e2e/image_scp_test.go b/test/e2e/image_scp_test.go index bb6359eb6b..71429b3ed5 100644 --- a/test/e2e/image_scp_test.go +++ b/test/e2e/image_scp_test.go @@ -1,4 +1,4 @@ -//go:build linux || freebsd +//go:build !remote_testing && (linux || freebsd) package integration @@ -26,6 +26,11 @@ var _ = Describe("podman image scp", func() { if _, err := os.Stat(filepath.Join(homedir.Get(), ".ssh", "known_hosts")); err != nil { Skip("known_hosts does not exist or is not accessible") } + + ensureImage := podmanTest.Podman([]string{"pull", "-q", ALPINE}) + ensureImage.WaitWithDefaultTimeout() + Expect(ensureImage).Should(ExitCleanly()) + cmd := []string{"system", "connection", "add", "--default", "QA", diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index bffc40a639..b9dcb6873d 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -18,7 +18,6 @@ import ( ) var _ = Describe("Podman Info", func() { - It("podman info --format json", func() { tests := []struct { input string @@ -108,7 +107,12 @@ var _ = Describe("Podman Info", func() { session := podmanTest.Podman([]string{"info", "--format", "{{.Host.RemoteSocket.Path}}"}) session.WaitWithDefaultTimeout() Expect(session).Should(ExitCleanly()) - Expect(session.OutputToString()).To(MatchRegexp("/run/.*podman.*sock")) + switch podmanTest.RemoteSocketScheme { + case "unix": + Expect(session.OutputToString()).To(MatchRegexp("/run/.*podman.*sock")) + case "tcp": + Expect(session.OutputToString()).To(MatchRegexp("tcp://127.0.0.1:.*")) + } session = podmanTest.Podman([]string{"info", "--format", "{{.Host.ServiceIsRemote}}"}) session.WaitWithDefaultTimeout() @@ -125,7 +129,6 @@ var _ = Describe("Podman Info", func() { Expect(session).Should(ExitCleanly()) Expect(session.OutputToString()).To(Equal("true")) } - }) It("Podman info must contain cgroupControllers with RelevantControllers", func() { diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go index 2034da8850..9d31c86aeb 100644 --- a/test/e2e/libpod_suite_remote_test.go +++ b/test/e2e/libpod_suite_remote_test.go @@ -18,6 +18,8 @@ import ( . "github.com/onsi/gomega" ) +var RemoteTestingTarget = PodmanTestCreateUtilTarget(os.Getenv("REMOTEINTEGRATION_TRANSPORT")) + func IsRemote() bool { return true } @@ -53,11 +55,6 @@ func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { func resetRegistriesConfigEnv() { os.Setenv("CONTAINERS_REGISTRIES_CONF", "") } -func PodmanTestCreate(tempDir string) *PodmanTestIntegration { - pti := PodmanTestCreateUtil(tempDir, true) - pti.StartRemoteService() - return pti -} func (p *PodmanTestIntegration) StartRemoteService() { if !isRootless() { @@ -70,10 +67,26 @@ func (p *PodmanTestIntegration) StartRemoteService() { args = append(args, "--log-level", "trace") } remoteSocket := p.RemoteSocket - args = append(args, "system", "service", "--time", "0", remoteSocket) + args = append(args, "system", "service", "--time", "0") + + if p.RemoteTLSClientCAFile != "" { + args = append(args, "--tls-client-ca", p.RemoteTLSClientCAFile) + } + if p.RemoteTLSServerCertFile != "" { + args = append(args, "--tls-cert", p.RemoteTLSServerCertFile) + } + if p.RemoteTLSServerKeyFile != "" { + args = append(args, "--tls-key", p.RemoteTLSServerKeyFile) + } + + args = append(args, remoteSocket) + podmanOptions := getRemoteOptions(p, args) - cacheOptions := []string{"--storage-opt", - fmt.Sprintf("%s.imagestore=%s", p.PodmanTest.ImageCacheFS, p.PodmanTest.ImageCacheDir)} + cacheOptions := []string{ + "--storage-opt", + fmt.Sprintf("%s.imagestore=%s", p.PodmanTest.ImageCacheFS, p.PodmanTest.ImageCacheDir), + } + podmanOptions = append(cacheOptions, podmanOptions...) command := exec.Command(p.PodmanBinary, podmanOptions...) command.Stdout = GinkgoWriter @@ -111,11 +124,18 @@ func getRemoteOptions(p *PodmanTestIntegration, args []string) []string { networkDir := p.NetworkConfigDir podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s --db-backend %s", p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, networkDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, "file", p.DatabaseBackend), " ") + podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...) podmanOptions = append(podmanOptions, args...) return podmanOptions } +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + pti := PodmanTestCreateUtil(tempDir, RemoteTestingTarget) + pti.StartRemoteService() + return pti +} + // RestoreArtifact puts the cached image into our test store func (p *PodmanTestIntegration) RestoreArtifact(image string) error { tarball := imageTarPath(image) @@ -139,7 +159,7 @@ func (p *PodmanTestIntegration) DelayForService() error { var err error var conn net.Conn for i := 0; i < 100; i++ { - conn, err = net.Dial("unix", strings.TrimPrefix(p.RemoteSocket, "unix:")) + conn, err = net.Dial(p.RemoteSocketScheme, strings.TrimPrefix(p.RemoteSocket, p.RemoteSocketScheme+"://")) if err == nil { conn.Close() return nil diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 41d4ebd1fa..77087b2262 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -26,6 +26,12 @@ func (p *PodmanTestIntegration) PodmanWithOptions(options PodmanExecOptions, arg return &PodmanSessionIntegration{podmanSession} } +func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + pti := PodmanTestCreateUtil(tempDir, PodmanTestCreateUtilTargetLocal) + pti.StartRemoteService() + return pti +} + func (p *PodmanTestIntegration) setDefaultRegistriesConfigEnv() { defaultFile := "registries.conf" if UsingCacheRegistry() { @@ -47,10 +53,6 @@ func resetRegistriesConfigEnv() { os.Setenv("CONTAINERS_REGISTRIES_CONF", "") } -func PodmanTestCreate(tempDir string) *PodmanTestIntegration { - return PodmanTestCreateUtil(tempDir, false) -} - // RestoreArtifact puts the cached image into our test store func (p *PodmanTestIntegration) RestoreArtifact(image string) error { tarball := imageTarPath(image) diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go index ec07cf2140..611d01a2f4 100644 --- a/test/e2e/system_connection_test.go +++ b/test/e2e/system_connection_test.go @@ -5,6 +5,7 @@ package integration import ( "bytes" "context" + "crypto/tls" "errors" "fmt" "net" @@ -37,16 +38,21 @@ func setupConnectionsConf() { os.Setenv("PODMAN_CONNECTIONS_CONF", file) } -var systemConnectionListCmd = []string{"system", "connection", "ls", "--format", "{{.Name}} {{.URI}} {{.Identity}} {{.Default}} {{.ReadWrite}}"} -var farmListCmd = []string{"farm", "ls", "--format", "{{.Name}} {{.Connections}} {{.Default}} {{.ReadWrite}}"} +var ( + systemConnectionListCmd = []string{"system", "connection", "ls", "--format", "{{.Name}} {{.URI}} {{.Identity}} {{.Default}} {{.ReadWrite}}"} + systemConnectionListTLSSpecialValueCmd = []string{"system", "connection", "ls", "--format", "tls"} + systemConnectionListTLSCmd = []string{"system", "connection", "ls", "--format", "{{.Name}} {{.URI}} {{.TLSCA}} {{.Default}} {{.ReadWrite}}"} + systemConnectionListmTLSCmd = []string{"system", "connection", "ls", "--format", "{{.Name}} {{.URI}} {{.TLSCA}} {{.TLSCert}} {{.TLSKey}} {{.Default}} {{.ReadWrite}}"} + farmListCmd = []string{"farm", "ls", "--format", "{{.Name}} {{.Connections}} {{.Default}} {{.ReadWrite}}"} +) var _ = Describe("podman system connection", func() { - BeforeEach(setupConnectionsConf) Context("without running API service", func() { It("add ssh://", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "QA", @@ -62,7 +68,8 @@ var _ = Describe("podman system connection", func() { Expect(session).Should(ExitCleanly()) Expect(session.OutputToString()).To(Equal("QA ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true")) - cmd = []string{"system", "connection", "rename", + cmd = []string{ + "system", "connection", "rename", "QA", "QE", } @@ -77,7 +84,8 @@ var _ = Describe("podman system connection", func() { }) It("add UDS", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "QA-UDS", "unix:///run/podman/podman.sock", } @@ -93,7 +101,8 @@ var _ = Describe("podman system connection", func() { Expect(session).Should(ExitCleanly()) Expect(session.OutputToString()).To(Equal("QA-UDS unix:///run/podman/podman.sock true true")) - cmd = []string{"system", "connection", "add", + cmd = []string{ + "system", "connection", "add", "QA-UDS1", "--socket-path", "/run/user/podman/podman.sock", "unix:///run/podman/podman.sock", @@ -112,7 +121,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true }) It("add tcp", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "QA-TCP", "tcp://localhost:8888", } @@ -127,8 +137,52 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true Expect(session.OutputToString()).To(Equal("QA-TCP tcp://localhost:8888 true true")) }) + It("add tcp w/ TLS", func() { + cmd := []string{ + "system", "connection", "add", + "QA-TCP-TLS", + "tcp://localhost:8888", + "--tls-ca", "ca.pem", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.Out.Contents()).Should(BeEmpty()) + + session = podmanTest.Podman(systemConnectionListTLSCmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(Equal("QA-TCP-TLS tcp://localhost:8888 ca.pem true true")) + }) + + It("add tcp w/ mTLS", func() { + cmd := []string{ + "system", "connection", "add", + "QA-TCP-MTLS", + "tcp://localhost:8888", + "--tls-ca", "ca.pem", + "--tls-cert", "tls.crt", + "--tls-key", "tls.key", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.Out.Contents()).Should(BeEmpty()) + + session = podmanTest.Podman(systemConnectionListmTLSCmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(Equal("QA-TCP-MTLS tcp://localhost:8888 ca.pem tls.crt tls.key true true")) + + session = podmanTest.Podman(systemConnectionListTLSSpecialValueCmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.Out).Should(Say("Name *URI *Identity *TLSCA *TLSCert *TLSKey *Default *ReadWrite\nQA-TCP-MTLS *tcp://localhost:8888 *ca.pem *tls.crt *tls.key *true *true")) + }) + It("add tcp to reverse proxy path", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "QA-TCP-RP", "tcp://localhost:8888/reverse/proxy/path/prefix", } @@ -144,7 +198,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true }) It("add to new farm", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "--farm", "farm1", @@ -174,7 +229,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true Expect(session).Should(ExitCleanly()) Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"empty-farm\" created")) - cmd = []string{"system", "connection", "add", + cmd = []string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "--farm", "empty-farm", @@ -197,7 +253,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true }) It("removing connection should remove from farm also", func() { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "--farm", "farm1", @@ -235,7 +292,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true }) It("remove", func() { - session := podmanTest.Podman([]string{"system", "connection", "add", + session := podmanTest.Podman([]string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "QA", @@ -259,7 +317,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true }) It("remove --all", func() { - session := podmanTest.Podman([]string{"system", "connection", "add", + session := podmanTest.Podman([]string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", "QA", @@ -282,7 +341,8 @@ QA-UDS1 unix:///run/user/podman/podman.sock false true It("default", func() { for _, name := range []string{"devl", "qe"} { - cmd := []string{"system", "connection", "add", + cmd := []string{ + "system", "connection", "add", "--default", "--identity", "~/.ssh/id_rsa", name, @@ -351,20 +411,39 @@ qe ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa false true proxy := http.NewServeMux() proxy.Handle(pathPrefix+"/", &httputil.ReverseProxy{ Rewrite: func(pr *httputil.ProxyRequest) { + defer GinkgoRecover() proxyGotUsed = true pr.Out.URL.Path = strings.TrimPrefix(pr.Out.URL.Path, pathPrefix) pr.Out.URL.RawPath = strings.TrimPrefix(pr.Out.URL.RawPath, pathPrefix) - baseURL, _ := url.Parse("http://d") + scheme := "http" + if podmanTest.RemoteTLSServerCAFile != "" { + scheme = "https" + } + baseURL, err := url.Parse(scheme + "://localhost") + Expect(err).ToNot(HaveOccurred()) pr.SetURL(baseURL) }, Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + defer GinkgoRecover() By("Proxying to " + podmanTest.RemoteSocket) url, err := url.Parse(podmanTest.RemoteSocket) if err != nil { return nil, err } - return (&net.Dialer{}).DialContext(ctx, "unix", url.Path) + switch podmanTest.RemoteSocketScheme { + case "unix": + return (&net.Dialer{}).DialContext(ctx, podmanTest.RemoteSocketScheme, url.Path) + case "tcp": + return (&net.Dialer{}).DialContext(ctx, podmanTest.RemoteSocketScheme, url.Host) + default: + Fail("Unexpected remote socket scheme") + panic("") + } + }, + TLSClientConfig: &tls.Config{ + RootCAs: podmanTest.RemoteTLSServerCAPool, + Certificates: podmanTest.RemoteTLSClientCerts, }, }, }) diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 33a8155dfd..2295331acf 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -60,14 +60,18 @@ function setup() { if ! is_remote; then skip "only applicable on podman-remote" fi + # All we care about here is that the command passes run_podman --context=default version - # This one must fail - PODMAN=${PODMAN%%--url*} run_podman 125 --context=swarm version - is "$output" \ - "Error: read cli flags: connection \"swarm\" not found" \ - "--context=swarm should fail" + ( + unset REMOTESYSTEM_TRANSPORT + # This one must fail + PODMAN=${PODMAN%%--url*} run_podman 125 --context=swarm version + is "$output" \ + "Error: read cli flags: connection \"swarm\" not found" \ + "--context=swarm should fail" + ) } @test "podman can pull an image" { diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 491860ec4d..ef25f345d7 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -434,7 +434,7 @@ EOF # that listing all images does not fail (see BZ 2216700). for i in $(seq --format '%02g' 1 $count); do timeout --foreground -v --kill=10 60 \ - $PODMAN rmi img-$i-$(safename) & + "${PODMAN_CMD[@]}" rmi img-$i-$(safename) & done tries=100 diff --git a/test/system/032-sig-proxy.bats b/test/system/032-sig-proxy.bats index 285703c97e..0e7747b421 100644 --- a/test/system/032-sig-proxy.bats +++ b/test/system/032-sig-proxy.bats @@ -6,7 +6,7 @@ load helpers.sig-proxy # Each of the tests below does some setup, then invokes the helper from helpers.sig-proxy.bash. @test "podman sigproxy test: run" { # We're forced to use $PODMAN because run_podman cannot be backgrounded - $PODMAN run -i --name c_run $IMAGE sh -c "$SLEEPLOOP" & + "${PODMAN_CMD[@]}" run -i --name c_run $IMAGE sh -c "$SLEEPLOOP" & local kidpid=$! _test_sigproxy c_run $kidpid @@ -16,7 +16,7 @@ load helpers.sig-proxy run_podman create --name c_start $IMAGE sh -c "$SLEEPLOOP" # See above comments regarding $PODMAN and backgrounding - $PODMAN start --attach c_start & + "${PODMAN_CMD[@]}" start --attach c_start & local kidpid=$! _test_sigproxy c_start $kidpid @@ -26,7 +26,7 @@ load helpers.sig-proxy run_podman run -d --name c_attach $IMAGE sh -c "$SLEEPLOOP" # See above comments regarding $PODMAN and backgrounding - $PODMAN attach c_attach & + "${PODMAN_CMD[@]}" attach c_attach & local kidpid=$! _test_sigproxy c_attach $kidpid diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index 4aedd4242d..c2fd7908be 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -143,7 +143,7 @@ function _log_test_restarted() { run_podman run --log-driver=$driver ${events_backend} --name $cname $IMAGE sh -c 'start=0; if test -s log; then start=`tail -n 1 log`; fi; seq `expr $start + 1` `expr $start + 10` | tee -a log' run_podman ${events_backend} start -a $cname logfile=$(mktemp -p ${PODMAN_TMPDIR} logfileXXXXXXXX) - $PODMAN $_PODMAN_TEST_OPTS ${events_backend} logs -f $cname > $logfile + "${PODMAN_CMD[@]}" $_PODMAN_TEST_OPTS ${events_backend} logs -f $cname > $logfile expected=$(mktemp -p ${PODMAN_TMPDIR} expectedXXXXXXXX) seq 1 20 > $expected diff -u ${expected} ${logfile} diff --git a/test/system/050-stop.bats b/test/system/050-stop.bats index 4d71ce1221..59c4576e1f 100644 --- a/test/system/050-stop.bats +++ b/test/system/050-stop.bats @@ -183,7 +183,7 @@ load helpers # Stop the container, but do so in the background so we can inspect # the container status while it's stopping. Use $PODMAN because we # don't want the overhead and error checks of run_podman. - $PODMAN stop -t 20 $ctrname & + "${PODMAN_CMD[@]}" stop -t 20 $ctrname & # Wait for container to acknowledge the signal. We can't use wait_for_output # because that aborts if .State.Running != true diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index dd2f9c0a5c..5a96d41439 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -933,7 +933,7 @@ load helpers # that podman's stdout is redirected cleanly with no artifacts. # Copy file. - $PODMAN cp $cpcontainer:/tmp/file.txt - > $srcdir/stdout.tar + "${PODMAN_CMD[@]}" cp $cpcontainer:/tmp/file.txt - > $srcdir/stdout.tar tar xvf $srcdir/stdout.tar -C $srcdir is "$(< $srcdir/file.txt)" "$rand_content" "File contents: file.txt" @@ -943,7 +943,7 @@ load helpers rm -f $srcdir/* # Copy directory. - $PODMAN cp $cpcontainer:/tmp - > $srcdir/stdout.tar + "${PODMAN_CMD[@]}" cp $cpcontainer:/tmp - > $srcdir/stdout.tar tar xvf $srcdir/stdout.tar -C $srcdir is "$(< $srcdir/tmp/file.txt)" "$rand_content" "file.txt contents" diff --git a/test/system/120-load.bats b/test/system/120-load.bats index f83fe7d4e3..001404385a 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -66,7 +66,7 @@ verify_iid_and_name() { # We can't use run_podman because that uses the BATS 'run' function # which redirects stdout and stderr. Here we need to guarantee # that podman's stdout is a pipe, not any other form of redirection - $PODMAN save --format oci-archive $fqin | cat >$archive + "${PODMAN_CMD[@]}" save --format oci-archive $fqin | cat >$archive assert "$?" -eq 0 "Command failed: podman save ... | cat" # Make sure we can reload it @@ -129,7 +129,7 @@ verify_iid_and_name() { is "$output" "Copying blob .*Copying config.*Writing manifest" # confirm that image was copied. FIXME: also try $PODMAN image inspect? - _sudo $PODMAN image exists $newname + _sudo "${PODMAN_CMD[@]}" image exists $newname # Copy it back, this time using -q run_podman untag $IMAGE $newname @@ -150,16 +150,16 @@ verify_iid_and_name() { assert "$output" =~ "$src_digest" "Digest of re-fetched image is in list of original image digests" # remove root img for transfer back with another name - _sudo $PODMAN image rm $newname + _sudo "${PODMAN_CMD[@]}" image rm $newname # get foobar's ID, for an ID transfer test run_podman image inspect --format '{{.ID}}' foobar:123 run_podman image scp $output ${notme}@localhost::foobartwo - _sudo $PODMAN image exists foobartwo + _sudo "${PODMAN_CMD[@]}" image exists foobartwo # Clean up - _sudo $PODMAN image rm foobartwo + _sudo "${PODMAN_CMD[@]}" image rm foobartwo run_podman untag $IMAGE $newname # Negative test for nonexistent image. @@ -293,7 +293,7 @@ verify_iid_and_name() { # We can't use run_podman because that uses the BATS 'run' function # which redirects stdout and stderr. Here we need to guarantee # that podman's stdout is a pipe, not any other form of redirection - $PODMAN save -m $img1 $img2 | cat >$archive + "${PODMAN_CMD[@]}" save -m $img1 $img2 | cat >$archive assert "$?" -eq 0 "Command failed: podman save ... | cat" run_podman rmi -f $img1 $img2 diff --git a/test/system/130-kill.bats b/test/system/130-kill.bats index dbb5e6d921..685eb80265 100644 --- a/test/system/130-kill.bats +++ b/test/system/130-kill.bats @@ -13,7 +13,7 @@ load helpers # Start a container that will handle all signals by emitting 'got: N' local -a signals=(1 2 3 4 5 6 8 10 12 13 14 15 16 20 21 22 23 24 25 26 64) - $PODMAN run --name $cname $IMAGE sh -c \ + "${PODMAN_CMD[@]}" run --name $cname $IMAGE sh -c \ "for i in ${signals[*]}; do trap \"echo got: \$i\" \$i; done; echo READY; while ! test -e /stop; do sleep 0.1; done; @@ -121,7 +121,7 @@ load helpers # 14761 - concurrent kill/stop must record the exit code cname=c-$(safename) run_podman run -d --replace --name=$cname $IMAGE sh -c "trap 'echo Received SIGTERM, ignoring' SIGTERM; echo READY; while :; do sleep 0.2; done" - $PODMAN stop -t 1 $cname & + "${PODMAN_CMD[@]}" stop -t 1 $cname & run_podman kill $cname run_podman wait $cname run_podman rm -f $cname diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 2c7148a035..d81b1f9e6d 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -275,7 +275,7 @@ EOF # The "-v" is only for debugging: tar will emit the filename to stderr. # If this test ever fails, that may give a clue. echo "$_LOG_PROMPT $PODMAN volume export $volname | tar -x ..." - tar_output="$($PODMAN volume export $volname | tar -x -v --to-stdout)" + tar_output="$("${PODMAN_CMD[@]}" volume export $volname | tar -x -v --to-stdout)" echo "$tar_output" assert "$tar_output" == "$content" "extracted content" diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 4acfd0a443..1887e68ec4 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -589,7 +589,7 @@ io.max | $lomajmin rbps=1048576 wbps=1048576 riops=max wiops=max done # and delete them - $PODMAN pod rm -a & + "${PODMAN_CMD[@]}" pod rm -a & # pod ps should not fail while pods are deleted run_podman pod ps -q diff --git a/test/system/220-healthcheck.bats b/test/system/220-healthcheck.bats index 6d02946623..32a6f5122f 100644 --- a/test/system/220-healthcheck.bats +++ b/test/system/220-healthcheck.bats @@ -174,7 +174,7 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\\\n\" # Wait for the container in the background and create the $wait_file to # signal the specified wait condition was met. - (timeout --foreground -v --kill=5 10 $PODMAN wait --condition=$condition $ctr && touch $wait_file) & + (timeout --foreground -v --kill=5 10 "${PODMAN_CMD[@]}" wait --condition=$condition $ctr && touch $wait_file) & # Sleep 1 second to make sure above commands are running sleep 1 @@ -428,7 +428,7 @@ function _check_health_log { $IMAGE /home/podman/pause timeout --foreground -v --kill=10 60 \ - $PODMAN healthcheck run $ctr &> $hcStatus & + "${PODMAN_CMD[@]}" healthcheck run $ctr &> $hcStatus & hc_pid=$! run_podman inspect $ctr --format "{{.State.Status}}" diff --git a/test/system/251-system-service.bats b/test/system/251-system-service.bats index 6048ed5353..1113bed7e0 100644 --- a/test/system/251-system-service.bats +++ b/test/system/251-system-service.bats @@ -15,7 +15,13 @@ function teardown() { basic_teardown } +function _podman_system_service { + systemd-run --unit=$SERVICE_NAME ${PODMAN%%-remote} system service "$@" +} + @test "podman system service returns error" { + unset REMOTESYSTEM_TRANSPORT + skip_if_remote "podman system service unavailable over remote" run_podman 125 system service localhost:9292 is "$output" "Error: API Service endpoint scheme \"localhost\" is not supported. Try tcp://localhost:9292 or unix://localhost:9292" @@ -25,10 +31,12 @@ function teardown() { } @test "podman system service unix: without two slashes still works" { + unset REMOTESYSTEM_TRANSPORT + skip_if_remote "podman system service unavailable over remote" URL=unix:$PODMAN_TMPDIR/myunix.sock - systemd-run --unit=$SERVICE_NAME $PODMAN system service $URL --time=0 + _podman_system_service $URL --time=0 wait_for_file $PODMAN_TMPDIR/myunix.sock run_podman --host $URL info --format '{{.Host.RemoteSocket.Path}}' @@ -39,6 +47,8 @@ function teardown() { } @test "podman-system-service containers survive service stop" { + unset REMOTESYSTEM_TRANSPORT + skip_if_remote "podman system service unavailable over remote" local runtime=$(podman_runtime) if [[ "$runtime" != "crun" ]]; then @@ -48,7 +58,7 @@ function teardown() { port=$(random_free_port) URL=tcp://127.0.0.1:$port - systemd-run --unit=$SERVICE_NAME $PODMAN system service $URL --time=0 + _podman_system_service $URL --time=0 wait_for_port 127.0.0.1 $port # Start a long-running container. @@ -69,16 +79,18 @@ function teardown() { # This doesn't actually test podman system service, but we require it, # so least-awful choice is to run from this test file. @test "podman --host / -H options" { + unset REMOTESYSTEM_TRANSPORT + port=$(random_free_port) URL=tcp://127.0.0.1:$port # %%-remote makes this run real podman even when testing podman-remote - systemd-run --unit=$SERVICE_NAME ${PODMAN%%-remote*} system service $URL --time=0 + _podman_system_service $URL --time=0 wait_for_port 127.0.0.1 $port for opt in --host -H; do - run_podman $opt $URL info --format '{{.Host.RemoteSocket.Path}}' - is "$output" "$URL" "RemoteSocket.Path using $opt" + run_podman $opt $URL info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using $opt" done systemctl stop $SERVICE_NAME @@ -86,12 +98,14 @@ function teardown() { # Regression test for https://github.com/containers/podman/issues/17749 @test "podman-system-service --log-level=trace should be able to hijack" { + unset REMOTESYSTEM_TRANSPORT + skip_if_remote "podman system service unavailable over remote" port=$(random_free_port) URL=tcp://127.0.0.1:$port - systemd-run --unit=$SERVICE_NAME $PODMAN --log-level=trace system service $URL --time=0 + _podman_system_service $URL --time=0 wait_for_port 127.0.0.1 $port out=o-$(random_string) @@ -102,3 +116,230 @@ function teardown() { run_podman --url $URL rm $cname systemctl stop $SERVICE_NAME } + +@test "podman-system-service --tls-cert without --tls-key fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "tcp://localhost:${port}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + is "$output" ".* --tls-cert provided without --tls-key" +} + +@test "podman-system-service --tls-key without --tls-cert fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "tcp://localhost:${port}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" + is "$output" ".* --tls-key provided without --tls-cert" +} + +@test "podman-system-service --tls-key=missing fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "tcp://localhost:${port}" --tls-key=no-such-file.pem --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + is "$output" ".* no-such-file.pem: no such file or directory" +} + +@test "podman-system-service --tls-cert=missing fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "tcp://localhost:${port}" --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" --tls-cert=no-such-file.pem + is "$output" ".* no-such-file.pem: no such file or directory" +} + +@test "podman-system-service --tls-client-ca=missing fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "tcp://localhost:${port}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca=no-such-file.pem + is "$output" ".* no-such-file.pem: no such file or directory" +} + +@test "podman-system-service --tls-key=malformed fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + echo 'not a cert' >"${PODMAN_TMPDIR}/not-a-cert.pem" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${PODMAN_TMPDIR}/not-a-cert.pem" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + is "$output" ".* failed to find any PEM data in key input" +} + +@test "podman-system-service --tls-cert=malformed fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + echo 'not a cert' >"${PODMAN_TMPDIR}/not-a-cert.pem" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${PODMAN_TMPDIR}/not-a-cert.pem" + is "$output" ".* failed to find any PEM data in certificate input" +} + +@test "podman-system-service --tls-client-ca=malformed fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + echo 'not a cert' >"${PODMAN_TMPDIR}/not-a-cert.pem" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca="${PODMAN_TMPDIR}/not-a-cert.pem" + is "$output" ".* ${PODMAN_TMPDIR}/not-a-cert.pem: non-PEM data found" +} + +@test "podman-system-service --tls-key=cert fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + is "$output" ".*found a certificate rather than a key.*" +} + +@test "podman-system-service --tls-cert=key fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_KEY}" + is "$output" ".* PEM inputs may have been switched" +} + +@test "podman-system-service --tls-client-ca=key fails to start" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + run_podman 125 system service "${URL}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_KEY}" + is "$output" ".* ${REMOTESYSTEM_TLS_CA_KEY}: non-certificate type \`.*\` PEM data found" +} + +@test "podman-system-service --tls-cert --tls-key refuses HTTP client" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + _podman_system_service $URL --time=0 \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}" + + wait_for_port 127.0.0.1 $port + + run_podman 125 --url $URL system info + is "$output" ".* ping response was 400" + systemctl stop $SERVICE_NAME +} + +@test "podman-system-service --tls-cert --tls-key --tls-client-ca refuses client without cert" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + _podman_system_service $URL --time=0 \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}" + + wait_for_port 127.0.0.1 $port + + run_podman 125 --url $URL --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" system info + is "$output" ".* remote error: tls: certificate required" + systemctl stop $SERVICE_NAME +} + +@test "podman-system-service --tls-cert --tls-key --tls-client-ca refuses client untrusted cert" { + unset REMOTESYSTEM_TRANSPORT + + skip_if_remote "podman system service unavailable over remote" + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + _podman_system_service $URL --time=0 \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}" + + wait_for_port 127.0.0.1 $port + + run_podman 125 \ + --url $URL \ + --tls-key="${REMOTESYSTEM_TLS_BOGUS_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_BOGUS_CRT}" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + system info + # This is not a copy-paste error from above, the Go HTTPS server provides the same error message for + # "you didn't provide a cert" + # and + # "you didn't provide a cert *that I trust*" + # This is allegedly to make it "more secure" + is "$output" ".* remote error: tls: certificate required" + systemctl stop $SERVICE_NAME +} diff --git a/test/system/271-tcp-cors-server.bats b/test/system/271-tcp-cors-server.bats index a0af5a2960..e6e3275b9a 100644 --- a/test/system/271-tcp-cors-server.bats +++ b/test/system/271-tcp-cors-server.bats @@ -19,7 +19,7 @@ SOCKET_FILE="$UNIT_DIR/$SERVICE_NAME.socket" PORT=$(random_free_port) log=${PODMAN_TMPDIR}/system-service.log - $PODMAN system service --cors="*" tcp:$SERVICE_TCP_HOST:$PORT -t 20 2> $log & + "${PODMAN_CMD[@]}" system service --cors="*" tcp:$SERVICE_TCP_HOST:$PORT -t 20 2> $log & podman_pid="$!" wait_for_port $SERVICE_TCP_HOST $PORT @@ -42,7 +42,7 @@ SOCKET_FILE="$UNIT_DIR/$SERVICE_NAME.socket" @test "podman system service - tcp without CORS" { skip_if_remote "system service tests are meaningless over remote" PORT=$(random_free_port) - $PODMAN system service tcp:$SERVICE_TCP_HOST:$PORT -t 20 & + "${PODMAN_CMD[@]}" system service tcp:$SERVICE_TCP_HOST:$PORT -t 20 & podman_pid="$!" wait_for_port $SERVICE_TCP_HOST $PORT @@ -65,7 +65,7 @@ SOCKET_FILE="$UNIT_DIR/$SERVICE_NAME.socket" PORT=$(random_free_port) run_podman 0+w system service --log-level="debug" --cors="*" -t 1 tcp:$SERVICE_TCP_HOST:$PORT is "$output" ".*CORS Headers were set to ..\*...*" "debug log confirms CORS headers set" - assert "$output" =~ "level=warning msg=\"Using the Podman API service with TCP sockets is not recommended" \ + assert "$output" =~ "level=warning msg=\"Using the Podman API service with TCP sockets without TLS is not recommended" \ "TCP socket warning" } diff --git a/test/system/272-system-connection.bats b/test/system/272-system-connection.bats index bc71cb14bb..e91e9d83ef 100644 --- a/test/system/272-system-connection.bats +++ b/test/system/272-system-connection.bats @@ -44,15 +44,21 @@ function teardown() { # # Needed because, in CI, PODMAN="/path/to/podman-remote --url /path/to/socket" # which of course overrides podman's detection and use of a connection. -function _run_podman_remote() { +function run_podman_remote() { PODMAN=${PODMAN%%--url*} run_podman "$@" } # Very basic test, does not actually connect at any time @test "podman system connection - basic add / ls / remove" { + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} + run_podman system connection ls is "$output" "Name URI Identity Default ReadWrite" \ "system connection ls: no connections" + run_podman system connection ls --format=tls + is "$output" "Name URI Identity TLSCA TLSCert TLSKey Default ReadWrite" \ + "system connection ls: no connections" + c1="c1_$(random_string 15)" c2="c2_$(random_string 15)" @@ -90,6 +96,8 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ # Test tcp socket; requires starting a local server @test "podman system connection - tcp" { + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} + # Start server _SERVICE_PORT=$(random_free_port 63000-64999) @@ -100,7 +108,7 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ # to "podman-remote --url sdfsdf". This of course overrides the default # podman-remote action. Our solution: strip off the "--url xyz" part # when invoking podman. - _run_podman_remote 125 info + run_podman_remote 125 info is "$output" \ "OS: .*provider:.*Cannot connect to Podman. Please verify.*dial tcp.*connection refused" \ "podman info, without active service" @@ -115,7 +123,7 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ wait_for_port 127.0.0.1 $_SERVICE_PORT local timeout=10 while [[ $timeout -gt 1 ]]; do - _run_podman_remote '?' info --format '{{.Host.RemoteSocket.Path}}' + run_podman_remote '?' info --format '{{.Host.RemoteSocket.Path}}' if [[ $status == 0 ]]; then break fi @@ -125,13 +133,139 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ is "$output" "tcp://localhost:$_SERVICE_PORT" \ "podman info works, and talks to the correct server" - _run_podman_remote info --format '{{.Store.GraphRoot}}' + run_podman_remote info --format '{{.Store.GraphRoot}}' is "$output" "${PODMAN_TMPDIR}/root" \ "podman info, talks to the right service" # Add another connection; make sure it does not get set as default - _run_podman_remote system connection add fakeconnect tcp://localhost:$(( _SERVICE_PORT + 1)) - _run_podman_remote info --format '{{.Store.GraphRoot}}' + run_podman_remote system connection add fakeconnect tcp://localhost:$(( _SERVICE_PORT + 1)) + run_podman_remote info --format '{{.Store.GraphRoot}}' + # (Don't bother checking output; we just care about exit status) + + # Stop server. Use 'run' to avoid failing on nonzero exit status + run kill $_SERVICE_PID + run wait $_SERVICE_PID + _SERVICE_PID= + + run_podman system connection rm fakeconnect + run_podman system connection rm myconnect +} + +# Test tcp socket with server authentication; requires starting a local server +@test "podman system connection - tls" { + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} + + # Start server + _SERVICE_PORT=$(random_free_port 63000-64999) + + # Add the connection, and run podman info *before* starting the service. + # This should fail. + run_podman system connection add myconnect tcp://localhost:$_SERVICE_PORT \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" + # IMPORTANT NOTE: in CI, podman-remote is tested by setting PODMAN + # to "podman-remote --url sdfsdf". This of course overrides the default + # podman-remote action. Our solution: strip off the "--url xyz" part + # when invoking podman. + run_podman_remote 125 info + is "$output" \ + "OS: .*provider:.*Cannot connect to Podman. Please verify.*dial tcp.*connection refused" \ + "podman info, without active service" + + # Start service. Now podman info should work fine. The %%-remote* + # converts "podman-remote --opts" to just "podman", which is what + # we need for the server. + ${PODMAN%%-remote*} $(podman_isolation_opts ${PODMAN_TMPDIR}) \ + system service -t 99 \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + tcp://localhost:$_SERVICE_PORT & + _SERVICE_PID=$! + # Wait for the port and the podman-service to be ready. + wait_for_port 127.0.0.1 $_SERVICE_PORT + local timeout=10 + while [[ $timeout -gt 1 ]]; do + run_podman_remote '?' info --format '{{.Host.RemoteSocket.Path}}' + if [[ $status == 0 ]]; then + break + fi + sleep 1 + let timeout=$timeout-1 + done + is "$output" "tcp://localhost:$_SERVICE_PORT" \ + "podman info works, and talks to the correct server" + + run_podman_remote info --format '{{.Store.GraphRoot}}' + is "$output" "${PODMAN_TMPDIR}/root" \ + "podman info, talks to the right service" + + # Add another connection; make sure it does not get set as default + run_podman_remote system connection add fakeconnect tcp://localhost:$(( _SERVICE_PORT + 1)) + run_podman_remote info --format '{{.Store.GraphRoot}}' + # (Don't bother checking output; we just care about exit status) + + # Stop server. Use 'run' to avoid failing on nonzero exit status + run kill $_SERVICE_PID + run wait $_SERVICE_PID + _SERVICE_PID= + + run_podman system connection rm fakeconnect + run_podman system connection rm myconnect +} + +# Test tcp socket with mutual authentication; requires starting a local server +@test "podman system connection - mtls" { + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} + + # Start server + _SERVICE_PORT=$(random_free_port 63000-64999) + + # Add the connection, and run podman info *before* starting the service. + # This should fail. + run_podman system connection add myconnect tcp://localhost:$_SERVICE_PORT \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + --tls-cert="${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + --tls-key="${REMOTESYSTEM_TLS_CLIENT_KEY}" + + # IMPORTANT NOTE: in CI, podman-remote is tested by setting PODMAN + # to "podman-remote --url sdfsdf". This of course overrides the default + # podman-remote action. Our solution: strip off the "--url xyz" part + # when invoking podman. + run_podman_remote 125 info + is "$output" \ + "OS: .*provider:.*Cannot connect to Podman. Please verify.*dial tcp.*connection refused" \ + "podman info, without active service" + + # Start service. Now podman info should work fine. The %%-remote* + # converts "podman-remote --opts" to just "podman", which is what + # we need for the server. + ${PODMAN%%-remote*} $(podman_isolation_opts ${PODMAN_TMPDIR}) \ + system service -t 99 \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + tcp://localhost:$_SERVICE_PORT & + _SERVICE_PID=$! + # Wait for the port and the podman-service to be ready. + wait_for_port 127.0.0.1 $_SERVICE_PORT + local timeout=10 + while [[ $timeout -gt 1 ]]; do + run_podman_remote '?' info --format '{{.Host.RemoteSocket.Path}}' + if [[ $status == 0 ]]; then + break + fi + sleep 1 + let timeout=$timeout-1 + done + is "$output" "tcp://localhost:$_SERVICE_PORT" \ + "podman info works, and talks to the correct server" + + run_podman_remote info --format '{{.Store.GraphRoot}}' + is "$output" "${PODMAN_TMPDIR}/root" \ + "podman info, talks to the right service" + + # Add another connection; make sure it does not get set as default + run_podman_remote system connection add fakeconnect tcp://localhost:$(( _SERVICE_PORT + 1)) + run_podman_remote info --format '{{.Store.GraphRoot}}' # (Don't bother checking output; we just care about exit status) # Stop server. Use 'run' to avoid failing on nonzero exit status @@ -145,6 +279,8 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ # If we have ssh access to localhost (unlikely in CI), test that. @test "podman system connection - ssh" { + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} + # system connection only really works if we have an agent run ssh-add -l test "$status" -eq 0 || skip "Not running under ssh-agent" @@ -172,7 +308,7 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ is "$output" "" "output from system connection add" # debug logs will confirm that we use ssh connection - _run_podman_remote --log-level=debug info --format '{{.Host.RemoteSocket.Path}}' + run_podman_remote --log-level=debug info --format '{{.Host.RemoteSocket.Path}}' is "$output" ".*msg=\"SSH Agent Key .*" "we are truly using ssh" # Clean up @@ -180,86 +316,99 @@ $c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \ } @test "podman-remote: non-default connection" { + # priority: # 1. cli flags (--connection ,--url ,--context ,--host) # 2. Env variables (CONTAINER_HOST and CONTAINER_CONNECTION) # 3. ActiveService from containers.conf # 4. RemoteURI - # Prerequisite check: there must be no defined system connections - run_podman system connection ls -q - assert "$output" = "" "This test requires an empty list of system connections" + ( + unset REMOTESYSTEM_TRANSPORT REMOTESYSTEM_TLS_{CLIENT,SERVER,CA}_{CRT,KEY} - # setup - run_podman 0+w system connection add defaultconnection unix:///run/user/defaultconnection/podman/podman.sock - run_podman 0+w system connection add env-override unix:///run/user/env-override/podman/podman.sock - run_podman 0+w system connection add cli-override unix:///run/user/cli-override/podman/podman.sock + # Prerequisite check: there must be no defined system connections + run_podman system connection ls -q + assert "$output" = "" "This test requires an empty list of system connections" - # Test priority of Env variables wrt cli flags - CONTAINER_CONNECTION=env-override _run_podman_remote 125 --connection=cli-override ps - assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt --connection cli flag" + # setup + run_podman 0+w system connection add defaultconnection unix:///run/user/defaultconnection/podman/podman.sock + run_podman 0+w system connection add env-override unix:///run/user/env-override/podman/podman.sock + run_podman 0+w system connection add cli-override unix:///run/user/cli-override/podman/podman.sock - CONTAINER_HOST=foo://124.com _run_podman_remote 125 --connection=cli-override ps - assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_HOST wrt --connection cli flag" + # Test priority of Env variables wrt cli flags + CONTAINER_CONNECTION=env-override run_podman_remote 125 --connection=cli-override ps + assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt --connection cli flag" - CONTAINER_CONNECTION=env-override _run_podman_remote 125 --url=tcp://localhost ps - assert "$output" =~ "localhost" "test env variable CONTAINER_CONNECTION wrt --url cli flag" + CONTAINER_HOST=foo://124.com run_podman_remote 125 --connection=cli-override ps + assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_HOST wrt --connection cli flag" - CONTAINER_HOST=foo://124.com _run_podman_remote 125 --url=tcp://localhost ps - assert "$output" =~ "localhost" "test env variable CONTAINER_HOST wrt --url cli flag" + CONTAINER_CONNECTION=env-override run_podman_remote 125 --url=tcp://localhost ps + assert "$output" =~ "localhost" "test env variable CONTAINER_CONNECTION wrt --url cli flag" - # Docker-compat - CONTAINER_CONNECTION=env-override _run_podman_remote 125 --context=cli-override ps - assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt --context cli flag" + CONTAINER_HOST=foo://124.com run_podman_remote 125 --url=tcp://localhost ps + assert "$output" =~ "localhost" "test env variable CONTAINER_HOST wrt --url cli flag" - CONTAINER_HOST=foo://124.com _run_podman_remote 125 --context=cli-override ps - assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_HOST wrt --context cli flag" + # Docker-compat + CONTAINER_CONNECTION=env-override run_podman_remote 125 --context=cli-override ps + assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt --context cli flag" - CONTAINER_CONNECTION=env-override _run_podman_remote 125 --host=tcp://localhost ps - assert "$output" =~ "localhost" "test env variable CONTAINER_CONNECTION wrt --host cli flag" + CONTAINER_HOST=foo://124.com run_podman_remote 125 --context=cli-override ps + assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "test env variable CONTAINER_HOST wrt --context cli flag" - CONTAINER_HOST=foo://124.com _run_podman_remote 125 --host=tcp://localhost ps - assert "$output" =~ "localhost" "test env variable CONTAINER_HOST wrt --host cli flag" + CONTAINER_CONNECTION=env-override run_podman_remote 125 --host=tcp://localhost ps + assert "$output" =~ "localhost" "test env variable CONTAINER_CONNECTION wrt --host cli flag" - _run_podman_remote 125 --remote ps - assert "$output" =~ "/run/user/defaultconnection/podman/podman.sock" "test default connection" + CONTAINER_HOST=foo://124.com run_podman_remote 125 --host=tcp://localhost ps + assert "$output" =~ "localhost" "test env variable CONTAINER_HOST wrt --host cli flag" - CONTAINER_CONNECTION=env-override _run_podman_remote 125 --remote ps - assert "$output" =~ "/run/user/env-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt config" + run_podman_remote 125 --remote ps + assert "$output" =~ "/run/user/defaultconnection/podman/podman.sock" "test default connection" - CONTAINER_HOST=foo://124.com _run_podman_remote 125 --remote ps - assert "$output" =~ "foo" "test env variable CONTAINER_HOST wrt config" + CONTAINER_CONNECTION=env-override run_podman_remote 125 --remote ps + assert "$output" =~ "/run/user/env-override/podman/podman.sock" "test env variable CONTAINER_CONNECTION wrt config" - # There was a bug where this would panic instead of returning a proper error (#22997) - CONTAINER_CONNECTION=invalid-env _run_podman_remote 125 --remote ps - assert "$output" =~ "read cli flags: connection \"invalid-env\" not found" "connection error from env" + CONTAINER_HOST=foo://124.com run_podman_remote 125 --remote ps + assert "$output" =~ "foo" "test env variable CONTAINER_HOST wrt config" - # Check again with cli overwrite to ensure correct connection name in error is reported - CONTAINER_CONNECTION=invalid-env _run_podman_remote 125 --connection=invalid-cli ps - assert "$output" =~ "read cli flags: connection \"invalid-cli\" not found" "connection error from --connection cli" + # There was a bug where this would panic instead of returning a proper error (#22997) + CONTAINER_CONNECTION=invalid-env run_podman_remote 125 --remote ps + assert "$output" =~ "read cli flags: connection \"invalid-env\" not found" "connection error from env" - # Invalid env is fine if valid connection is given via cli - CONTAINER_CONNECTION=invalid-env _run_podman_remote 125 --connection=cli-override ps - assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "no CONTAINER_CONNECTION connection error with valid --connection cli" + # Check again with cli overwrite to ensure correct connection name in error is reported + CONTAINER_CONNECTION=invalid-env run_podman_remote 125 --connection=invalid-cli ps + assert "$output" =~ "read cli flags: connection \"invalid-cli\" not found" "connection error from --connection cli" - # Clean up - run_podman system connection rm defaultconnection - run_podman system connection rm env-override - run_podman system connection rm cli-override + # Invalid env is fine if valid connection is given via cli + CONTAINER_CONNECTION=invalid-env run_podman_remote 125 --connection=cli-override ps + assert "$output" =~ "/run/user/cli-override/podman/podman.sock" "no CONTAINER_CONNECTION connection error with valid --connection cli" + + # Clean up + run_podman system connection rm defaultconnection + run_podman system connection rm env-override + run_podman system connection rm cli-override + ) # With all system connections removed, test the default connection. - # This only works in upstream CI, where we run with a nonstandard socket. - # In gating we use the default /run/... - run_podman info --format '{{.Host.RemoteSocket.Path}}' - local sock="$output" - if [[ "$sock" =~ //run/ ]]; then - _run_podman_remote --remote info --format '{{.Host.RemoteSocket.Path}}' - assert "$output" = "$sock" "podman-remote is using default socket path" + if [[ "${REMOTESYSTEM_TRANSPORT}" =~ tcp|tls|mtls ]]; then + run_podman_remote --remote info --format '{{.Host.RemoteSocket.Path}}' + assert "$output" =~ "tcp://localhost:${REMOTESYSTEM_TCP_PORT}" + elif [[ "${REMOTESYSTEM_TRANSPORT}" =~ unix ]]; then + run_podman_remote --remote info --format '{{.Host.RemoteSocket.Path}}' + assert "$output" =~ "unix://${REMOTESYSTEM_UNIX_SOCK}" else - # Nonstandard socket - _run_podman_remote 125 --remote ps - assert "$output" =~ "/run/[a-z0-9/]*podman/podman.sock"\ - "test absence of default connection" + # This only works in upstream CI, where we run with a nonstandard socket. + # In gating we use the default /run/... + run_podman info --format '{{.Host.RemoteSocket.Path}}' + local sock="$output" + if [[ "$sock" =~ //run/ ]]; then + run_podman_remote --remote info --format '{{.Host.RemoteSocket.Path}}' + assert "$output" = "$sock" "podman-remote is using default socket path" + else + # Nonstandard socket + run_podman_remote 125 --remote ps + assert "$output" =~ "/run/[a-z0-9/]*podman/podman.sock"\ + "test absence of default connection" + fi fi } diff --git a/test/system/273-remote-spot-check.bats b/test/system/273-remote-spot-check.bats new file mode 100644 index 0000000000..1797ca6663 --- /dev/null +++ b/test/system/273-remote-spot-check.bats @@ -0,0 +1,150 @@ +# +# Tests that spot check connectivity for each of the supported remote transports, +# unix, tcp, tls, mtls + +load helpers +load helpers.systemd +load helpers.network + +SERVICE_NAME="podman-service-$(random_string)" + +function setup() { + basic_setup +} + +function teardown() { + # Ignore exit status: this is just a backup stop in case tests failed + run systemctl-user stop "$SERVICE_NAME" + rm -f $PODMAN_TMPDIR/myunix.sock + + basic_teardown +} + +@test "unix remote" { + unset REMOTESYSTEM_TRANSPORT + + URL=unix:$PODMAN_TMPDIR/myunix.sock + + systemd-run-user --unit=$SERVICE_NAME ${PODMAN%%-remote*} system service $URL --time=0 + wait_for_file $PODMAN_TMPDIR/myunix.sock + + # Variable works + CONTAINER_HOST=$URL \ + run_podman \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Flag works + run_podman \ + --url="$URL" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Streaming command works + run_podman \ + --url="$URL" \ + run --rm -i $IMAGE /bin/sh -c 'echo -n foo; sleep 0.1; echo -n bar; sleep 0.1; echo -n baz' + is "$output" foobarbaz + + systemctl-user stop $SERVICE_NAME + rm -f $PODMAN_TMPDIR/myunix.sock +} + +@test "tcp remote" { + unset REMOTESYSTEM_TRANSPORT + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + systemd-run-user --unit=$SERVICE_NAME ${PODMAN%%-remote*} system service $URL --time=0 + wait_for_port 127.0.0.1 $port + + # Variable works + CONTAINER_HOST=$URL \ + run_podman \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Flag works + run_podman \ + --url="$URL" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Streaming command works + run_podman \ + --url="$URL" \ + run --rm -i $IMAGE /bin/sh -c 'echo -n foo; sleep 0.1; echo -n bar; sleep 0.1; echo -n baz' + is "$output" foobarbaz + + systemctl-user stop $SERVICE_NAME +} + +@test "tls remote" { + unset REMOTESYSTEM_TRANSPORT + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + systemd-run-user --unit=$SERVICE_NAME ${PODMAN%%-remote*} system service $URL --time=0 \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + wait_for_port 127.0.0.1 $port + + # Variable works + CONTAINER_HOST=$URL \ + run_podman \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Flag works + run_podman \ + --url="$URL" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Streaming command works + run_podman \ + --url="$URL" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + run --rm -i $IMAGE /bin/sh -c 'echo -n foo; sleep 0.1; echo -n bar; sleep 0.1; echo -n baz' + is "$output" foobarbaz + + systemctl-user stop $SERVICE_NAME +} + +@test "mtls remote" { + unset REMOTESYSTEM_TRANSPORT + + port=$(random_free_port) + URL=tcp://127.0.0.1:$port + + systemd-run-user --unit=$SERVICE_NAME ${PODMAN%%-remote*} system service $URL --time=0 \ + --tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + wait_for_port 127.0.0.1 $port + + # Variable works + CONTAINER_HOST=$URL \ + run_podman \ + --tls-key="${REMOTESYSTEM_TLS_CLIENT_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Flag works + run_podman \ + --url="$URL" \ + --tls-key="${REMOTESYSTEM_TLS_CLIENT_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + info --format '{{.Host.RemoteSocket.Path}}' + is "$output" "$URL" "RemoteSocket.Path using unix:" + # Streaming command works + run_podman \ + --url="$URL" \ + --tls-key="${REMOTESYSTEM_TLS_CLIENT_KEY}" \ + --tls-cert="${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + --tls-ca="${REMOTESYSTEM_TLS_CA_CRT}" \ + run --rm -i $IMAGE /bin/sh -c 'echo -n foo; sleep 0.1; echo -n bar; sleep 0.1; echo -n baz' + is "$output" foobarbaz + + systemctl-user stop $SERVICE_NAME +} diff --git a/test/system/450-interactive.bats b/test/system/450-interactive.bats index 3ca50dc67d..31f01f6424 100644 --- a/test/system/450-interactive.bats +++ b/test/system/450-interactive.bats @@ -129,12 +129,12 @@ function teardown() { run_podman run --rm -v testVol:/data $IMAGE sh -c "echo data > /data/file.txt" # Positive Case - $PODMAN volume export testVol --output=/dev/null >$PODMAN_TEST_PTY || + "${PODMAN_CMD[@]}" volume export testVol --output=/dev/null >$PODMAN_TEST_PTY || die "$PODMAN volume export testVol --output=/dev/null failed when connected to terminal." # Negative Case local rc=0 - $PODMAN volume export testVol >$PODMAN_TEST_PTY 2>$PODMAN_TMPDIR/out || rc=$? + "${PODMAN_CMD[@]}" volume export testVol >$PODMAN_TEST_PTY 2>$PODMAN_TMPDIR/out || rc=$? is "$rc" "125" "Exit code should be 125" is "$(<$PODMAN_TMPDIR/out)" "Error: cannot write to terminal, use command-line redirection or the --output flag" "Should refuse to export to terminal." diff --git a/test/system/550-pause-process.bats b/test/system/550-pause-process.bats index 7bd75a38a6..da657e77e1 100644 --- a/test/system/550-pause-process.bats +++ b/test/system/550-pause-process.bats @@ -84,7 +84,7 @@ function _check_pause_process() { # We're forced to use $PODMAN because run_podman cannot be backgrounded # Also special logic to set a different argv0 to make sure the reexec still works: # https://github.com/containers/podman/issues/22672 - bash -c "exec -a argv0-podman $PODMAN run -i --name c_run $IMAGE sh -c '$SLEEPLOOP'" & + bash -c "exec -a argv0-podman ${PODMAN_CMD[@]} run -i --name c_run $IMAGE sh -c '$SLEEPLOOP'" & local kidpid=$! _test_sigproxy c_run $kidpid @@ -112,7 +112,7 @@ function _check_pause_process() { # Now again directly start podman run and make sure it can forward signals # We're forced to use $PODMAN because run_podman cannot be backgrounded local cname2=c2_$(random_string) - $PODMAN run -i --name $cname2 $IMAGE sh -c "$SLEEPLOOP" & + "${PODMAN_CMD[@]}" run -i --name $cname2 $IMAGE sh -c "$SLEEPLOOP" & local kidpid=$! _test_sigproxy $cname2 $kidpid diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 7d2153a490..b7f42a6702 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -152,7 +152,7 @@ RELABEL="system_u:object_r:container_file_t:s0" # Run `play kube` in the background as it will wait for the service # container to exit. timeout --foreground -v --kill=10 60 \ - $PODMAN play kube --service-container=true --log-driver journald $TESTYAML &>/dev/null & + "${PODMAN_CMD[@]}" play kube --service-container=true --log-driver journald $TESTYAML &>/dev/null & # Wait for the container to be running container_a=$PODCTRNAME @@ -569,7 +569,7 @@ EOF # Run `play kube` in the background as it will wait for the service # container to exit. timeout --foreground -v --kill=10 60 \ - $PODMAN play kube --service-container=true --log-driver journald $TESTYAML &>/dev/null & + "${PODMAN_CMD[@]}" play kube --service-container=true --log-driver journald $TESTYAML &>/dev/null & # The name of the service container is predictable: the first 12 characters # of the hash of the YAML file followed by the "-service" suffix @@ -662,7 +662,7 @@ spec: # on a running container; signaling during initialization # results in undefined behavior. logfile=$PODMAN_TMPDIR/kube-play.log - $PODMAN kube play --wait $fname &> $logfile & + "${PODMAN_CMD[@]}" kube play --wait $fname &> $logfile & local kidpid=$! for try in {1..10}; do diff --git a/test/system/850-compose.bats b/test/system/850-compose.bats index 8d967ad2d8..066d87bbe4 100644 --- a/test/system/850-compose.bats +++ b/test/system/850-compose.bats @@ -63,6 +63,12 @@ EOF url="${PODMAN##*--url }" url="${url%% *}" op='=' + elif is_remote && [[ "${REMOTESYSTEM_TRANSPORT}" =~ tcp|tls|mtls ]]; then + url="tcp://localhost:${REMOTESYSTEM_TCP_PORT}" + op='=' + elif is_remote && [[ "${REMOTESYSTEM_TRANSPORT}" =~ unix ]]; then + url="unix://${REMOTESYSTEM_UNIX_SOCK}" + op='=' fi # podman-remote test might run with --url so unset this because the socket will be used otherwise CONTAINERS_CONF_OVERRIDE=$compose_conf run_podman compose env diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 5cd2975a4e..5abfe872d3 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -35,6 +35,46 @@ SYSTEMD_IMAGE=$PODMAN_SYSTEMD_IMAGE_FQN # Default timeout for a podman command. PODMAN_TIMEOUT=${PODMAN_TIMEOUT:-120} +function add_podman_args { + declare -n arrayptr=$1 + + if is_remote ; then + case "${REMOTESYSTEM_TRANSPORT}" in + tcp|tls|mtls) + arrayptr+=(--url="tcp://localhost:${REMOTESYSTEM_TCP_PORT}") + ;; + unix) + arrayptr+=(--url="unix://${REMOTESYSTEM_UNIX_SOCK}") + esac + case "${REMOTESYSTEM_TRANSPORT}" in + tls|mtls) + arrayptr+=(--tls-ca="${REMOTESYSTEM_TLS_CA_CRT}") + ;; + esac + case "${REMOTESYSTEM_TRANSPORT}" in + mtls) + arrayptr+=( + --tls-cert="${REMOTESYSTEM_TLS_CLIENT_CRT}" + --tls-key="${REMOTESYSTEM_TLS_CLIENT_KEY}" + ) + ;; + esac + fi +} + + +export REMOTESYSTEM_TLS_CA_CRT=${BATS_SUITE_TMPDIR}/remotesystem.ca.crt.pem +export REMOTESYSTEM_TLS_CA_KEY=${BATS_SUITE_TMPDIR}/remotesystem.ca.key.pem +export REMOTESYSTEM_TLS_SERVER_CRT=${BATS_SUITE_TMPDIR}/remotesystem.server.crt.pem +export REMOTESYSTEM_TLS_SERVER_KEY=${BATS_SUITE_TMPDIR}/remotesystem.server.key.pem +export REMOTESYSTEM_TLS_CLIENT_CRT=${BATS_SUITE_TMPDIR}/remotesystem.client.crt.pem +export REMOTESYSTEM_TLS_CLIENT_KEY=${BATS_SUITE_TMPDIR}/remotesystem.client.key.pem +export REMOTESYSTEM_TLS_BOGUS_CRT=${BATS_SUITE_TMPDIR}/remotesystem.bogus.crt.pem +export REMOTESYSTEM_TLS_BOGUS_KEY=${BATS_SUITE_TMPDIR}/remotesystem.bogus.key.pem + +# Full command to run podman, including remote flags. This is (re)set by basic_setup. +PODMAN_CMD=() + # Prompt to display when logging podman commands; distinguish root/rootless _LOG_PROMPT='$' if [ $(id -u) -eq 0 ]; then @@ -168,6 +208,9 @@ function basic_setup() { # idea being that a large number of failures can show patterns. ASSERTION_FAILURES= immediate-assertion-failures + + PODMAN_CMD=("${PODMAN}") + add_podman_args PODMAN_CMD } # bail-now is how we terminate a test upon assertion failure. @@ -291,7 +334,7 @@ function restore_image() { ####################### function _run_podman_quiet() { # This should be the same as what run_podman() does. - run timeout -v --foreground --kill=10 60 $PODMAN $_PODMAN_TEST_OPTS "$@" + run timeout -v --foreground --kill=10 60 ${PODMAN_CMD[@]} $_PODMAN_TEST_OPTS "$@" if [[ $status -ne 0 ]]; then echo "# Error running command: podman $*" echo "$output" @@ -394,7 +437,7 @@ function clean_setup() { # Special case for timeout: check for locks (#18514) if [[ $status -eq 124 ]]; then echo "# [teardown] $_LOG_PROMPT podman system locks" >&3 - run $PODMAN system locks + run "${PODMAN_CMD[@]}" system locks for line in "${lines[*]}"; do echo "# $line" >&3 done @@ -512,11 +555,16 @@ function run_podman() { silence127="!" fi + podman_args=() + add_podman_args podman_args + + # stdout is only emitted upon error; this printf is to help in debugging - printf "\n%s %s %s %s\n" "$(timestamp)" "$_LOG_PROMPT" "$PODMAN" "$*" + printf "\n%s %s %s %s\n" "$(timestamp)" "$_LOG_PROMPT" $PODMAN "${podman_args[@]}" "$*" + # BATS hangs if a subprocess remains and keeps FD 3 open; this happens # if podman crashes unexpectedly without cleaning up subprocesses. - run $silence127 timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN $_PODMAN_TEST_OPTS "$@" 3>/dev/null + run $silence127 timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN "${podman_args[@]}" $_PODMAN_TEST_OPTS "$@" 3>/dev/null # without "quotes", multiple lines are glommed together into one if [ -n "$output" ]; then echo "$(timestamp) $output" @@ -1374,6 +1422,118 @@ function wait_for_restart_count() { done } +function gen-cert-pair { + cn=$1 key=$2 cert=$3 + shift 3 + openssl req -x509 \ + -quiet \ + -nodes \ + -newkey rsa:4096 -keyout "${key}" \ + -out "${cert}" \ + -days 1 \ + -subj "/C=??/ST=System/L=Test/O=Containers/OU=Podman/CN=${cn}" \ + "$@" +} + +function gen-signed-cert-pair { + cn=$1 key=$2 cert=$3 ca_key=$4 ca_cert=$5 + shift 5 + gen-cert-pair "${cn}" \ + "${key}" "${cert}" \ + -CAkey "${ca_key}" -CA "${ca_cert}" \ + "$@" +} + +function gen-tls { + rm -f \ + "${REMOTESYSTEM_TLS_CA_KEY}" "${REMOTESYSTEM_TLS_CA_CRT}" \ + "${REMOTESYSTEM_TLS_CLIENT_KEY}" "${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + "${REMOTESYSTEM_TLS_SERVER_KEY}" "${REMOTESYSTEM_TLS_SERVER_CRT}" \ + "${REMOTESYSTEM_TLS_BOGUS_KEY}" "${REMOTESYSTEM_TLS_BOGUS_CRT}" + + # CA + gen-cert-pair "ca" \ + "${REMOTESYSTEM_TLS_CA_KEY}" "${REMOTESYSTEM_TLS_CA_CRT}" \ + -addext basicConstraints=critical,CA:TRUE,pathlen:1 + # Client, signed by CA + gen-signed-cert-pair "client" \ + "${REMOTESYSTEM_TLS_CLIENT_KEY}" "${REMOTESYSTEM_TLS_CLIENT_CRT}" \ + "${REMOTESYSTEM_TLS_CA_KEY}" "${REMOTESYSTEM_TLS_CA_CRT}" \ + + # Server, signed by CA, valid for localhost, 127.0.0.1 + # NOTE: Go refuses certs without SAN's + gen-signed-cert-pair "localhost" \ + "${REMOTESYSTEM_TLS_SERVER_KEY}" "${REMOTESYSTEM_TLS_SERVER_CRT}" \ + "${REMOTESYSTEM_TLS_CA_KEY}" "${REMOTESYSTEM_TLS_CA_CRT}" \ + -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" + + # Bogus, self-signed + gen-cert-pair "bogus" \ + "${REMOTESYSTEM_TLS_BOGUS_KEY}" "${REMOTESYSTEM_TLS_BOGUS_CRT}" \ + -addext basicConstraints=critical,CA:TRUE,pathlen:1 +} + + +function systemd-run-user { + args=() + if [ "${UID}" != '0' ]; then + args+=(--user) + fi + systemd-run "${args[@]}" "$@" +} + +function systemctl-user { + args=() + if [ "${UID}" != '0' ]; then + args+=(--user) + fi + systemctl "${args[@]}" "$@" +} + +SUITE_SERVICE_NAME="podman-service-$(random_string)" +SUITE_PIDFILE="${BATS_SUITE_TMPDIR}/podman-system-service.pid" +function start-suite-podman-system-service { + service_args=() + case "${REMOTESYSTEM_TRANSPORT}" in + tls|mtls) + service_args+=( + --tls-cert="${REMOTESYSTEM_TLS_SERVER_CRT}" + --tls-key="${REMOTESYSTEM_TLS_SERVER_KEY}" + ) + ;; + esac + case "${REMOTESYSTEM_TRANSPORT}" in + mtls) + service_args+=(--tls-client-ca="${REMOTESYSTEM_TLS_CA_CRT}") + ;; + esac + case "${REMOTESYSTEM_TRANSPORT}" in + tcp|tls|mtls) + service_args+=("tcp://localhost:${REMOTESYSTEM_TCP_PORT}") + ;; + unix) + rm "${REMOTESYSTEM_UNIX_SOCK}" + service_args+=("unix://${REMOTESYSTEM_UNIX_SOCK}") + esac + + # TODO: In the future, use systemd if possible + # systemd-run-user --unit=$SUITE_SERVICE_NAME ${PODMAN%%-remote*} system service "${service_args[@]}" --time=0 + ${PODMAN%%-remote*} system service "${service_args[@]}" --time=0 &> "${PODMAN_SERVER_LOG:-/dev/null}" & + echo $! > "${SUITE_PIDFILE}" + + retry=5 + while [ $retry -ge 0 ]; do + echo Waiting for system service... + sleep 1 + "${PODMAN_CMD[@]}" system info && break + retry=$(expr $retry - 1) + done + if [ $retry -lt 0 ]; then + echo "Error: ./bin/podman system service did not come up" >&2 + exit 1 + fi +} + # END miscellaneous tools ############################################################################### diff --git a/test/system/setup_suite.bash b/test/system/setup_suite.bash index 48a71c7fe4..5e17aca2f1 100644 --- a/test/system/setup_suite.bash +++ b/test/system/setup_suite.bash @@ -8,6 +8,7 @@ load helpers load helpers.network load helpers.registry + # Create common environment just in case we end up needing a registry. # These environment variables will be available to all tests. function setup_suite() { @@ -17,6 +18,20 @@ function setup_suite() { IFS=" " + # These are set/generated even for non-remote tests because some local tests still create their + # own system service. + export REMOTESYSTEM_UNIX_SOCK=$(mktemp ${BATS_SUITE_TMPDIR}/remotesystem.podman.XXXXXX.sock) + export REMOTESYSTEM_TCP_PORT=$(random_free_port 27000-27999) + gen-tls + + PODMAN_CMD=("${PODMAN}") + add_podman_args PODMAN_CMD + + if is_remote; then + echo "Running remote system tests with transport ${REMOTESYSTEM_TRANSPORT}" + start-suite-podman-system-service + fi + export PODMAN_LOGIN_WORKDIR="$BATS_SUITE_TMPDIR/podman-bats-registry" mkdir "$PODMAN_LOGIN_WORKDIR" @@ -64,6 +79,15 @@ function teardown_suite() { fi fi + if is_remote; then + # See function start-suite-podman-system-service + # run systemctl-user stop "$SUITE_SERVICE_NAME" + # journalctl -u "${SUITE_SERVICE_NAME}" | tee ${PODMAN_SERVER_LOG:-/dev/null} + local system_service_pid=$(cat "${SUITE_PIDFILE}") + kill -sINT "${system_service_pid}" + wait "${system_service_pid}" + fi + return $exit_code } diff --git a/test/utils/utils.go b/test/utils/utils.go index 6dad5fab09..816a647e78 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -2,7 +2,11 @@ package utils import ( "bufio" + "crypto/rsa" + "crypto/tls" + "crypto/x509" "encoding/json" + "encoding/pem" "fmt" "io" "math/rand" @@ -14,9 +18,6 @@ import ( "time" crypto_rand "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" "github.com/sirupsen/logrus" @@ -63,19 +64,29 @@ type PodmanTestCommon interface { // PodmanTest struct for command line options type PodmanTest struct { - ImageCacheDir string - ImageCacheFS string - NetworkBackend NetworkBackend - DatabaseBackend string - PodmanBinary string - PodmanMakeOptions func(args []string, options PodmanExecOptions) []string - RemoteCommand *exec.Cmd - RemotePodmanBinary string - RemoteSession *os.Process - RemoteSocket string - RemoteSocketLock string // If not "", should be removed _after_ RemoteSocket is removed - RemoteTest bool - TempDir string + ImageCacheDir string + ImageCacheFS string + NetworkBackend NetworkBackend + DatabaseBackend string + PodmanBinary string + PodmanMakeOptions func(args []string, options PodmanExecOptions) []string + RemoteCommand *exec.Cmd + RemotePodmanBinary string + RemoteSession *os.Process + RemoteSocket string + RemoteSocketScheme string + RemoteSocketLock string // If not "", should be removed _after_ RemoteSocket is removed + RemoteTLSClientCAFile string + RemoteTLSClientCAPool *x509.CertPool + RemoteTLSClientCerts []tls.Certificate + RemoteTLSServerCertFile string + RemoteTLSServerKeyFile string + RemoteTLSServerCAFile string + RemoteTLSServerCAPool *x509.CertPool + RemoteTLSClientCertFile string + RemoteTLSClientKeyFile string + RemoteTest bool + TempDir string } // PodmanSession wraps the gexec.session so we can extend it @@ -229,7 +240,7 @@ func (p *PodmanTest) NumberOfPods() int { // GetContainerStatus returns the containers state. // This function assumes only one container is active. func (p *PodmanTest) GetContainerStatus() string { - var podmanArgs = []string{"ps"} + podmanArgs := []string{"ps"} podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}") session := p.PodmanExecBaseWithOptions(podmanArgs, PodmanExecOptions{ NoCache: true, diff --git a/vendor/github.com/google/pprof/profile/merge.go b/vendor/github.com/google/pprof/profile/merge.go index ba4d746407..8a51690be4 100644 --- a/vendor/github.com/google/pprof/profile/merge.go +++ b/vendor/github.com/google/pprof/profile/merge.go @@ -17,6 +17,7 @@ package profile import ( "encoding/binary" "fmt" + "slices" "sort" "strconv" "strings" @@ -78,12 +79,10 @@ func Merge(srcs []*Profile) (*Profile, error) { } } - for _, s := range p.Sample { - if isZeroSample(s) { - // If there are any zero samples, re-merge the profile to GC - // them. - return Merge([]*Profile{p}) - } + if slices.ContainsFunc(p.Sample, isZeroSample) { + // If there are any zero samples, re-merge the profile to GC + // them. + return Merge([]*Profile{p}) } return p, nil diff --git a/vendor/github.com/google/pprof/profile/profile.go b/vendor/github.com/google/pprof/profile/profile.go index f47a243903..43f561d445 100644 --- a/vendor/github.com/google/pprof/profile/profile.go +++ b/vendor/github.com/google/pprof/profile/profile.go @@ -24,6 +24,7 @@ import ( "math" "path/filepath" "regexp" + "slices" "sort" "strings" "sync" @@ -734,12 +735,7 @@ func (p *Profile) RemoveLabel(key string) { // HasLabel returns true if a sample has a label with indicated key and value. func (s *Sample) HasLabel(key, value string) bool { - for _, v := range s.Label[key] { - if v == value { - return true - } - } - return false + return slices.Contains(s.Label[key], value) } // SetNumLabel sets the specified key to the specified value for all samples in the @@ -852,7 +848,17 @@ func (p *Profile) HasFileLines() bool { // "[vdso]", "[vsyscall]" and some others, see the code. func (m *Mapping) Unsymbolizable() bool { name := filepath.Base(m.File) - return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon" + switch { + case strings.HasPrefix(name, "["): + case strings.HasPrefix(name, "linux-vdso"): + case strings.HasPrefix(m.File, "/dev/dri/"): + case m.File == "//anon": + case m.File == "": + case strings.HasPrefix(m.File, "/memfd:"): + default: + return false + } + return true } // Copy makes a fully independent copy of a profile. diff --git a/vendor/github.com/google/pprof/profile/prune.go b/vendor/github.com/google/pprof/profile/prune.go index b2f9fd5466..7bba31e8ce 100644 --- a/vendor/github.com/google/pprof/profile/prune.go +++ b/vendor/github.com/google/pprof/profile/prune.go @@ -19,6 +19,7 @@ package profile import ( "fmt" "regexp" + "slices" "strings" ) @@ -40,13 +41,7 @@ func simplifyFunc(f string) string { // Account for unsimplified names -- try to remove the argument list by trimming // starting from the first '(', but skipping reserved names that have '('. for _, ind := range bracketRx.FindAllStringSubmatchIndex(funcName, -1) { - foundReserved := false - for _, res := range reservedNames { - if funcName[ind[0]:ind[1]] == res { - foundReserved = true - break - } - } + foundReserved := slices.Contains(reservedNames, funcName[ind[0]:ind[1]]) if !foundReserved { funcName = funcName[:ind[0]] break diff --git a/vendor/go.podman.io/common/libimage/copier.go b/vendor/go.podman.io/common/libimage/copier.go index e81dfaf0f8..3c7d538104 100644 --- a/vendor/go.podman.io/common/libimage/copier.go +++ b/vendor/go.podman.io/common/libimage/copier.go @@ -9,6 +9,7 @@ import ( "io" "net" "os" + "slices" "strings" "time" @@ -396,7 +397,7 @@ func (c *Copier) copyInternal(ctx context.Context, source, destination types.Ima // TimeoutStartSec=, the service manager will allow the service to continue to start, provided the // service repeats "EXTEND_TIMEOUT_USEC=..." within the interval specified until the service startup // status is finished by "READY=1"." - extendValue := []byte(fmt.Sprintf("EXTEND_TIMEOUT_USEC=%d", extension.Microseconds())) + extendValue := fmt.Appendf(nil, "EXTEND_TIMEOUT_USEC=%d", extension.Microseconds()) extendTimeout := func() { if _, err := conn.Write(extendValue); err != nil { logrus.Errorf("Increasing EXTEND_TIMEOUT_USEC failed: %v", err) @@ -555,11 +556,9 @@ func checkRegistrySourcesAllows(dest types.ImageReference) (insecure *bool, err return nil, fmt.Errorf("registry %q denied by policy: not in allowed registries list (%s)", reference.Domain(dref), registrySources) } - for _, insecureDomain := range sources.InsecureRegistries { - if insecureDomain == reference.Domain(dref) { - insecure := true - return &insecure, nil - } + if slices.Contains(sources.InsecureRegistries, reference.Domain(dref)) { + insecure := true + return &insecure, nil } return nil, nil diff --git a/vendor/go.podman.io/common/libimage/filter/filter.go b/vendor/go.podman.io/common/libimage/filter/filter.go index 2f3f03baa4..b3be95d57a 100644 --- a/vendor/go.podman.io/common/libimage/filter/filter.go +++ b/vendor/go.podman.io/common/libimage/filter/filter.go @@ -24,25 +24,25 @@ type SearchFilter struct { func ParseSearchFilter(filter []string) (*SearchFilter, error) { sFilter := new(SearchFilter) for _, f := range filter { - arr := strings.SplitN(f, "=", 2) - switch arr[0] { + keyword, value, ok := strings.Cut(f, "=") + switch keyword { case define.SearchFilterStars: - if len(arr) < 2 { + if !ok { return nil, fmt.Errorf("invalid filter %q, should be stars=", filter) } - stars, err := strconv.Atoi(arr[1]) + stars, err := strconv.Atoi(value) if err != nil { return nil, fmt.Errorf("incorrect value type for stars filter: %w", err) } sFilter.Stars = stars case define.SearchFilterAutomated: - if len(arr) == 2 && arr[1] == "false" { + if ok && value == "false" { sFilter.IsAutomated = types.OptionalBoolFalse } else { sFilter.IsAutomated = types.OptionalBoolTrue } case define.SearchFilterOfficial: - if len(arr) == 2 && arr[1] == "false" { + if ok && value == "false" { sFilter.IsOfficial = types.OptionalBoolFalse } else { sFilter.IsOfficial = types.OptionalBoolTrue diff --git a/vendor/go.podman.io/common/libimage/filters.go b/vendor/go.podman.io/common/libimage/filters.go index dd55713c8d..a40abfe070 100644 --- a/vendor/go.podman.io/common/libimage/filters.go +++ b/vendor/go.podman.io/common/libimage/filters.go @@ -89,18 +89,16 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp var key, value string var filter filterFunc negate := false - split := strings.SplitN(f, "!=", 2) - if len(split) == 2 { + key, value, ok := strings.Cut(f, "!=") + if ok { negate = true } else { - split = strings.SplitN(f, "=", 2) - if len(split) != 2 { + key, value, ok = strings.Cut(f, "=") + if !ok { return nil, false, fmt.Errorf(filterInvalidValue, f) } } - key = split[0] - value = split[1] switch key { case "after", "since": img, err := r.time(key, value) diff --git a/vendor/go.podman.io/common/libimage/image.go b/vendor/go.podman.io/common/libimage/image.go index d0eb1b4c0e..32995968e7 100644 --- a/vendor/go.podman.io/common/libimage/image.go +++ b/vendor/go.podman.io/common/libimage/image.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "path/filepath" + "slices" "sort" "strings" "time" @@ -173,12 +174,7 @@ func (i *Image) Digests() []digest.Digest { // hasDigest returns whether the specified value matches any digest of the // image. func (i *Image) hasDigest(wantedDigest digest.Digest) bool { - for _, d := range i.Digests() { - if d == wantedDigest { - return true - } - } - return false + return slices.Contains(i.Digests(), wantedDigest) } // containsDigestPrefix returns whether the specified value matches any digest of the @@ -638,16 +634,9 @@ func (i *Image) Untag(name string) error { name = ref.String() - foundName := false - for _, n := range i.Names() { - if n == name { - foundName = true - break - } - } // Return an error if the name is not found, the c/storage // RemoveNames() API does not create one if no match is found. - if !foundName { + if !slices.Contains(i.Names(), name) { return fmt.Errorf("%s: %w", name, errTagUnknown) } diff --git a/vendor/go.podman.io/common/libimage/image_config.go b/vendor/go.podman.io/common/libimage/image_config.go index 6c6bb063e6..a34bc3be92 100644 --- a/vendor/go.podman.io/common/libimage/image_config.go +++ b/vendor/go.podman.io/common/libimage/image_config.go @@ -40,17 +40,17 @@ func ImageConfigFromChanges(changes []string) (*ImageConfig, error) { // nolint: for _, change := range changes { // First, let's assume proper Dockerfile format - space // separator between instruction and value - split := strings.SplitN(change, " ", 2) + outerKey, value, ok := strings.Cut(change, " ") - if len(split) != 2 { - split = strings.SplitN(change, "=", 2) - if len(split) != 2 { + if !ok { + outerKey, value, ok = strings.Cut(change, "=") + if !ok { return nil, fmt.Errorf("invalid change %q - must be formatted as KEY VALUE", change) } } - outerKey := strings.ToUpper(strings.TrimSpace(split[0])) - value := strings.TrimSpace(split[1]) + outerKey = strings.ToUpper(strings.TrimSpace(outerKey)) + value = strings.TrimSpace(value) switch outerKey { case "USER": // Assume literal contents are the user. @@ -96,18 +96,11 @@ func ImageConfigFromChanges(changes []string) (*ImageConfig, error) { // nolint: // For now: we only support key=value // We will attempt to strip quotation marks if present. - var key, val string - - splitEnv := strings.SplitN(value, "=", 2) - key = splitEnv[0] + key, val, _ := strings.Cut(value, "=") // val is "" if there is no "=" // We do need a key if key == "" { return nil, fmt.Errorf("invalid change %q - ENV must have at least one argument", change) } - // Perfectly valid to not have a value - if len(splitEnv) == 2 { - val = splitEnv[1] - } if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) @@ -192,17 +185,11 @@ func ImageConfigFromChanges(changes []string) (*ImageConfig, error) { // nolint: // Potentially problematic: LABEL might theoretically // allow an = in the key? If people really do this, we // may need to investigate more advanced parsing. - var ( - key, val string - ) - - splitLabel := strings.SplitN(value, "=", 2) + key, val, ok := strings.Cut(value, "=") // Unlike ENV, LABEL must have a value - if len(splitLabel) != 2 { + if !ok { return nil, fmt.Errorf("invalid change %q - LABEL must be formatted key=value", change) } - key = splitLabel[0] - val = splitLabel[1] if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) diff --git a/vendor/go.podman.io/common/libimage/manifest_list.go b/vendor/go.podman.io/common/libimage/manifest_list.go index 1aa31b1d79..f084bbe55f 100644 --- a/vendor/go.podman.io/common/libimage/manifest_list.go +++ b/vendor/go.podman.io/common/libimage/manifest_list.go @@ -303,10 +303,8 @@ func (m *ManifestList) LookupInstance(ctx context.Context, architecture, os, var } for _, image := range allImages { - for _, imageDigest := range append(image.Digests(), image.Digest()) { - if imageDigest == instanceDigest { - return image, nil - } + if slices.Contains(image.Digests(), instanceDigest) || instanceDigest == image.Digest() { + return image, nil } } diff --git a/vendor/go.podman.io/common/libimage/manifests/manifests.go b/vendor/go.podman.io/common/libimage/manifests/manifests.go index b99d8fc2c2..3caff540dd 100644 --- a/vendor/go.podman.io/common/libimage/manifests/manifests.go +++ b/vendor/go.podman.io/common/libimage/manifests/manifests.go @@ -252,10 +252,8 @@ func (l *list) InstanceByFile(file string) (digest.Digest, error) { return "", err } for instanceDigest, files := range l.artifacts.Files { - for _, file := range files { - if file == abs { - return instanceDigest, nil - } + if slices.Contains(files, abs) { + return instanceDigest, nil } } return "", os.ErrNotExist diff --git a/vendor/go.podman.io/common/libimage/pull.go b/vendor/go.podman.io/common/libimage/pull.go index 32a391fea6..1183311f4c 100644 --- a/vendor/go.podman.io/common/libimage/pull.go +++ b/vendor/go.podman.io/common/libimage/pull.go @@ -253,8 +253,8 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, storageName = imageName case ociTransport.Transport.Name(): - split := strings.SplitN(ref.StringWithinTransport(), ":", 2) - if len(split) == 1 || split[1] == "" { + _, refName, ok := strings.Cut(ref.StringWithinTransport(), ":") + if !ok || refName == "" { // Same trick as for the dir transport: we cannot use // the path to a directory as the name. storageName, err = getImageID(ctx, ref, nil) @@ -263,7 +263,7 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, } imageName = "sha256:" + storageName[1:] } else { // If the OCI-reference includes an image reference, use it - storageName = split[1] + storageName = refName imageName = storageName } diff --git a/vendor/go.podman.io/common/libimage/search.go b/vendor/go.podman.io/common/libimage/search.go index 513852ec85..cda0930672 100644 --- a/vendor/go.podman.io/common/libimage/search.go +++ b/vendor/go.podman.io/common/libimage/search.go @@ -94,11 +94,11 @@ func (r *Runtime) Search(ctx context.Context, term string, options *SearchOption // that we cannot use the reference parser from the containers/image // library as the search term may container arbitrary input such as // wildcards. See bugzilla.redhat.com/show_bug.cgi?id=1846629. - spl := strings.SplitN(term, "/", 2) + perhapsRegistry, perhapsTerm, ok := strings.Cut(term, "/") switch { - case len(spl) > 1: - searchRegistries = []string{spl[0]} - term = spl[1] + case ok: + searchRegistries = []string{perhapsRegistry} + term = perhapsTerm case len(options.Registries) > 0: searchRegistries = options.Registries default: @@ -203,15 +203,9 @@ func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry stri // limit is the number of results to output // if the total number of results is less than the limit, output all // if the limit has been set by the user, output those number of queries - limit = searchMaxQueries - if len(results) < limit { - limit = len(results) - } + limit = min(len(results), searchMaxQueries) if options.Limit != 0 { - limit = len(results) - if options.Limit < len(results) { - limit = options.Limit - } + limit = min(len(results), options.Limit) } paramsArr := []SearchResult{} @@ -264,15 +258,9 @@ func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registr if err != nil { return nil, fmt.Errorf("getting repository tags: %v", err) } - limit := searchMaxQueries - if len(tags) < limit { - limit = len(tags) - } + limit := min(len(tags), searchMaxQueries) if options.Limit != 0 { - limit = len(tags) - if options.Limit < limit { - limit = options.Limit - } + limit = min(len(tags), options.Limit) } paramsArr := []SearchResult{} for i := range limit { diff --git a/vendor/go.podman.io/common/libnetwork/cni/cni_conversion.go b/vendor/go.podman.io/common/libnetwork/cni/cni_conversion.go index 7b5d4eab75..c89a867453 100644 --- a/vendor/go.podman.io/common/libnetwork/cni/cni_conversion.go +++ b/vendor/go.podman.io/common/libnetwork/cni/cni_conversion.go @@ -355,9 +355,7 @@ func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry if port.Protocol == "" { return nil, errors.New("port protocol should not be empty") } - protocols := strings.Split(port.Protocol, ",") - - for _, protocol := range protocols { + for protocol := range strings.SplitSeq(port.Protocol, ",") { if !slices.Contains([]string{"tcp", "udp", "sctp"}, protocol) { return nil, fmt.Errorf("unknown port protocol %s", protocol) } diff --git a/vendor/go.podman.io/common/libnetwork/cni/run.go b/vendor/go.podman.io/common/libnetwork/cni/run.go index ef2b158f10..7877891a9a 100644 --- a/vendor/go.podman.io/common/libnetwork/cni/run.go +++ b/vendor/go.podman.io/common/libnetwork/cni/run.go @@ -177,9 +177,9 @@ func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPort } // Propagate environment CNI_ARGS - for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") { - if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 { - rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]}) + for kvpairs := range strings.SplitSeq(os.Getenv("CNI_ARGS"), ";") { + if key, val, ok := strings.Cut(kvpairs, "="); ok { + rt.Args = append(rt.Args, [2]string{key, val}) } } diff --git a/vendor/go.podman.io/common/libnetwork/etchosts/hosts.go b/vendor/go.podman.io/common/libnetwork/etchosts/hosts.go index dbde190fe0..2e3ccaa27c 100644 --- a/vendor/go.podman.io/common/libnetwork/etchosts/hosts.go +++ b/vendor/go.podman.io/common/libnetwork/etchosts/hosts.go @@ -236,24 +236,23 @@ func checkIfEntryExists(current HostEntry, entries HostEntries) bool { func parseExtraHosts(extraHosts []string, hostContainersInternalIP string) (HostEntries, error) { entries := make(HostEntries, 0, len(extraHosts)) for _, entry := range extraHosts { - values := strings.SplitN(entry, ":", 2) - if len(values) != 2 { + namesString, ip, ok := strings.Cut(entry, ":") + if !ok { return nil, fmt.Errorf("unable to parse host entry %q: incorrect format", entry) } - if values[0] == "" { + if namesString == "" { return nil, fmt.Errorf("hostname in host entry %q is empty", entry) } - if values[1] == "" { + if ip == "" { return nil, fmt.Errorf("IP address in host entry %q is empty", entry) } - ip := values[1] - if values[1] == HostGateway { + if ip == HostGateway { if hostContainersInternalIP == "" { return nil, fmt.Errorf("unable to replace %q of host entry %q: host containers internal IP address is empty", HostGateway, entry) } ip = hostContainersInternalIP } - names := strings.Split(values[0], ";") + names := strings.Split(namesString, ";") e := HostEntry{IP: ip, Names: names} entries = append(entries, e) } diff --git a/vendor/go.podman.io/common/libnetwork/pasta/pasta_linux.go b/vendor/go.podman.io/common/libnetwork/pasta/pasta_linux.go index 33043e8275..a72e7adfde 100644 --- a/vendor/go.podman.io/common/libnetwork/pasta/pasta_linux.go +++ b/vendor/go.podman.io/common/libnetwork/pasta/pasta_linux.go @@ -212,8 +212,7 @@ func createPastaArgs(opts *SetupOptions) ([]string, []string, []string, error) { } for _, i := range opts.Ports { - protocols := strings.Split(i.Protocol, ",") - for _, protocol := range protocols { + for protocol := range strings.SplitSeq(i.Protocol, ",") { var addr string if i.HostIP != "" { diff --git a/vendor/go.podman.io/common/libnetwork/resolvconf/resolvconf.go b/vendor/go.podman.io/common/libnetwork/resolvconf/resolvconf.go index 5724dfcc2e..a3647528fc 100644 --- a/vendor/go.podman.io/common/libnetwork/resolvconf/resolvconf.go +++ b/vendor/go.podman.io/common/libnetwork/resolvconf/resolvconf.go @@ -76,9 +76,8 @@ func filterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) []b // getLines parses input into lines and strips away comments. func getLines(input []byte) [][]byte { - lines := bytes.Split(input, []byte("\n")) var output [][]byte - for _, currentLine := range lines { + for currentLine := range bytes.SplitSeq(input, []byte("\n")) { commentIndex := bytes.Index(currentLine, []byte("#")) if commentIndex == -1 { output = append(output, currentLine) diff --git a/vendor/go.podman.io/common/libnetwork/slirp4netns/const.go b/vendor/go.podman.io/common/libnetwork/slirp4netns/const.go index d75785025b..82f3bff3a0 100644 --- a/vendor/go.podman.io/common/libnetwork/slirp4netns/const.go +++ b/vendor/go.podman.io/common/libnetwork/slirp4netns/const.go @@ -3,14 +3,7 @@ package slirp4netns import "net" const ( - ipv6ConfDefaultAcceptDadSysctl = "/proc/sys/net/ipv6/conf/default/accept_dad" - BinaryName = "slirp4netns" - - // defaultMTU the default MTU override. - defaultMTU = 65520 - - // default slirp4ns subnet. - defaultSubnet = "10.0.2.0/24" + BinaryName = "slirp4netns" ) // SetupResult return type from Setup(). diff --git a/vendor/go.podman.io/common/libnetwork/slirp4netns/const_linux.go b/vendor/go.podman.io/common/libnetwork/slirp4netns/const_linux.go new file mode 100644 index 0000000000..8e2742fe3f --- /dev/null +++ b/vendor/go.podman.io/common/libnetwork/slirp4netns/const_linux.go @@ -0,0 +1,11 @@ +package slirp4netns + +const ( + ipv6ConfDefaultAcceptDadSysctl = "/proc/sys/net/ipv6/conf/default/accept_dad" + + // defaultMTU the default MTU override. + defaultMTU = 65520 + + // default slirp4ns subnet. + defaultSubnet = "10.0.2.0/24" +) diff --git a/vendor/go.podman.io/common/libnetwork/slirp4netns/slirp4netns.go b/vendor/go.podman.io/common/libnetwork/slirp4netns/slirp4netns.go index c4020cae25..083a4e5fcc 100644 --- a/vendor/go.podman.io/common/libnetwork/slirp4netns/slirp4netns.go +++ b/vendor/go.podman.io/common/libnetwork/slirp4netns/slirp4netns.go @@ -124,11 +124,10 @@ func parseNetworkOptions(config *config.Config, extraOptions []string) (*network enableIPv6: true, } for _, o := range options { - parts := strings.SplitN(o, "=", 2) - if len(parts) < 2 { + option, value, ok := strings.Cut(o, "=") + if !ok { return nil, fmt.Errorf("unknown option for slirp4netns: %q", o) } - option, value := parts[0], parts[1] switch option { case "cidr": ipv4, _, err := net.ParseCIDR(value) @@ -639,8 +638,7 @@ func setupRootlessPortMappingViaSlirp(ports []types.PortMapping, cmd *exec.Cmd, // for each port we want to add we need to open a connection to the slirp4netns control socket // and send the add_hostfwd command. for _, port := range ports { - protocols := strings.Split(port.Protocol, ",") - for _, protocol := range protocols { + for protocol := range strings.SplitSeq(port.Protocol, ",") { hostIP := port.HostIP if hostIP == "" { hostIP = "0.0.0.0" diff --git a/vendor/go.podman.io/common/pkg/apparmor/apparmor_linux.go b/vendor/go.podman.io/common/pkg/apparmor/apparmor_linux.go index 9b0c766653..d677729d8c 100644 --- a/vendor/go.podman.io/common/pkg/apparmor/apparmor_linux.go +++ b/vendor/go.podman.io/common/pkg/apparmor/apparmor_linux.go @@ -208,14 +208,14 @@ func parseAAParserVersion(output string) (int, error) { // AppArmor parser version 2.9.1 // Copyright (C) 1999-2008 Novell Inc. // Copyright 2009-2012 Canonical Ltd. - lines := strings.SplitN(output, "\n", 2) - words := strings.Split(lines[0], " ") + firstLine, _, _ := strings.Cut(output, "\n") + words := strings.Split(firstLine, " ") version := words[len(words)-1] // trim "-beta1" suffix from version="3.0.0-beta1" if exists - version = strings.SplitN(version, "-", 2)[0] + version, _, _ = strings.Cut(version, "-") // also trim "~..." suffix used historically (https://gitlab.com/apparmor/apparmor/-/commit/bca67d3d27d219d11ce8c9cc70612bd637f88c10) - version = strings.SplitN(version, "~", 2)[0] + version, _, _ = strings.Cut(version, "~") // split by major minor version v := strings.Split(version, ".") diff --git a/vendor/go.podman.io/common/pkg/auth/auth.go b/vendor/go.podman.io/common/pkg/auth/auth.go index 8cb9f3a633..b6f7228546 100644 --- a/vendor/go.podman.io/common/pkg/auth/auth.go +++ b/vendor/go.podman.io/common/pkg/auth/auth.go @@ -233,8 +233,7 @@ func parseCredentialsKey(arg string, acceptRepositories bool) (key, registry str return "", "", err } - split := strings.Split(key, "/") - registry = split[0] + registry, _, _ = strings.Cut(key, "/") if !acceptRepositories { return registry, registry, nil diff --git a/vendor/go.podman.io/common/pkg/cgroups/cgroups_linux.go b/vendor/go.podman.io/common/pkg/cgroups/cgroups_linux.go index 322436a93e..1c66a8d9cc 100644 --- a/vendor/go.podman.io/common/pkg/cgroups/cgroups_linux.go +++ b/vendor/go.podman.io/common/pkg/cgroups/cgroups_linux.go @@ -111,7 +111,7 @@ func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) if err != nil { return nil, fmt.Errorf("failed while reading controllers for cgroup v2: %w", err) } - for _, controllerName := range strings.Fields(string(controllersFileBytes)) { + for controllerName := range strings.FieldsSeq(string(controllersFileBytes)) { c := controller{ name: controllerName, symlink: false, @@ -197,10 +197,9 @@ func getCgroupPathForCurrentProcess() (string, error) { s := bufio.NewScanner(f) for s.Scan() { text := s.Text() - procEntries := strings.SplitN(text, "::", 2) // set process cgroupPath only if entry is valid - if len(procEntries) > 1 { - cgroupPath = procEntries[1] + if _, p, ok := strings.Cut(text, "::"); ok { + cgroupPath = p } } if err := s.Err(); err != nil { @@ -278,10 +277,10 @@ func readFileByKeyAsUint64(path, key string) (uint64, error) { if err != nil { return 0, err } - for _, line := range strings.Split(string(content), "\n") { - fields := strings.SplitN(line, " ", 2) - if fields[0] == key { - v := cleanString(fields[1]) + for line := range strings.SplitSeq(string(content), "\n") { + k, v, _ := strings.Cut(line, " ") + if k == key { + v := cleanString(v) if v == "max" { return math.MaxUint64, nil } @@ -684,7 +683,7 @@ func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) { return nil, err } r := []uint64{} - for _, s := range strings.Split(string(data), " ") { + for s := range strings.SplitSeq(string(data), " ") { s = cleanString(s) if s == "" { break @@ -874,7 +873,7 @@ func rmDirRecursively(path string) error { } // kill all the processes that are still part of the cgroup if procs, err := os.ReadFile(filepath.Join(path, "cgroup.procs")); err == nil { - for _, pidS := range strings.Split(string(procs), "\n") { + for pidS := range strings.SplitSeq(string(procs), "\n") { if pid, err := strconv.Atoi(pidS); err == nil { _ = unix.Kill(pid, signal) } diff --git a/vendor/go.podman.io/common/pkg/cgroups/cgroups_unsupported.go b/vendor/go.podman.io/common/pkg/cgroups/cgroups_unsupported.go index 1602912122..5940dc82d9 100644 --- a/vendor/go.podman.io/common/pkg/cgroups/cgroups_unsupported.go +++ b/vendor/go.podman.io/common/pkg/cgroups/cgroups_unsupported.go @@ -2,10 +2,6 @@ package cgroups -import ( - "os" -) - // IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. func IsCgroup2UnifiedMode() (bool, error) { return false, nil @@ -16,7 +12,3 @@ func IsCgroup2UnifiedMode() (bool, error) { func UserOwnsCurrentSystemdCgroup() (bool, error) { return false, nil } - -func rmDirRecursively(path string) error { - return os.RemoveAll(path) -} diff --git a/vendor/go.podman.io/common/pkg/cgroups/systemd_linux.go b/vendor/go.podman.io/common/pkg/cgroups/systemd_linux.go index 4ae3c0af46..c0bc6d9d38 100644 --- a/vendor/go.podman.io/common/pkg/cgroups/systemd_linux.go +++ b/vendor/go.podman.io/common/pkg/cgroups/systemd_linux.go @@ -280,7 +280,7 @@ func resourcesToProps(res *cgroups.Resources, v2 bool) (map[string]uint64, map[s func rangeToBits(str string) ([]byte, error) { bits := new(big.Int) - for _, r := range strings.Split(str, ",") { + for r := range strings.SplitSeq(str, ",") { // allow extra spaces around r = strings.TrimSpace(r) // allow empty elements (extra commas) diff --git a/vendor/go.podman.io/common/pkg/cgroups/utils_linux.go b/vendor/go.podman.io/common/pkg/cgroups/utils_linux.go index 2143358e67..a1b18a9695 100644 --- a/vendor/go.podman.io/common/pkg/cgroups/utils_linux.go +++ b/vendor/go.podman.io/common/pkg/cgroups/utils_linux.go @@ -270,7 +270,7 @@ func MoveUnderCgroup(cgroup, subtree string, processes []uint32) error { if err != nil { return err } - for _, pid := range bytes.Split(processesData, []byte("\n")) { + for pid := range bytes.SplitSeq(processesData, []byte("\n")) { if len(pid) == 0 { continue } diff --git a/vendor/go.podman.io/common/pkg/cgroupv2/cgroups_unsupported.go b/vendor/go.podman.io/common/pkg/cgroupv2/cgroups_unsupported.go index 56269aa42d..8de8e60d80 100644 --- a/vendor/go.podman.io/common/pkg/cgroupv2/cgroups_unsupported.go +++ b/vendor/go.podman.io/common/pkg/cgroupv2/cgroups_unsupported.go @@ -2,7 +2,7 @@ package cgroupv2 -// Enabled returns whether we are running on cgroup v2 +// Enabled returns whether we are running on cgroup v2. func Enabled() (bool, error) { return false, nil } diff --git a/vendor/go.podman.io/common/pkg/completion/completion.go b/vendor/go.podman.io/common/pkg/completion/completion.go index fef95b7f35..f487d68630 100644 --- a/vendor/go.podman.io/common/pkg/completion/completion.go +++ b/vendor/go.podman.io/common/pkg/completion/completion.go @@ -72,7 +72,7 @@ func autocompleteSubIDName(filename string) ([]string, cobra.ShellCompDirective) var names []string scanner := bufio.NewScanner(file) for scanner.Scan() { - name := strings.SplitN(scanner.Text(), ":", 2)[0] + name, _, _ := strings.Cut(scanner.Text(), ":") names = append(names, name) } if err = scanner.Err(); err != nil { diff --git a/vendor/go.podman.io/common/pkg/config/config.go b/vendor/go.podman.io/common/pkg/config/config.go index 9d5a339375..8bc23deba1 100644 --- a/vendor/go.podman.io/common/pkg/config/config.go +++ b/vendor/go.podman.io/common/pkg/config/config.go @@ -707,6 +707,13 @@ type Destination struct { // Identity file with ssh key, optional Identity string `json:",omitempty" toml:"identity,omitempty"` + // Path to TLS client certificate PEM file, optional + TLSCert string `json:",omitempty" toml:"tls_cert,omitempty"` + // Path to TLS client certificate private key PEM file, optional + TLSKey string `json:",omitempty" toml:"tls_key,omitempty"` + // Path to TLS certificate authority PEM file, optional + TLSCA string `json:",omitempty" toml:"tls_ca,omitempty"` + // isMachine describes if the remote destination is a machine. IsMachine bool `json:",omitempty" toml:"is_machine,omitempty"` } @@ -761,9 +768,9 @@ func (c *Config) CheckCgroupsAndAdjustConfig() { } } } else { - for _, part := range strings.Split(session, ",") { - if strings.HasPrefix(part, "unix:path=") { - err := fileutils.Exists(strings.TrimPrefix(part, "unix:path=")) + for part := range strings.SplitSeq(session, ",") { + if path, ok := strings.CutPrefix(part, "unix:path="); ok { + err := fileutils.Exists(path) hasSession = err == nil break } @@ -1158,17 +1165,17 @@ func (c *Config) ImageCopyTmpDir() (string, error) { // setupEnv sets the environment variables for the engine. func (c *Config) setupEnv() error { for _, env := range c.Engine.Env.Get() { - splitEnv := strings.SplitN(env, "=", 2) - if len(splitEnv) != 2 { + key, value, ok := strings.Cut(env, "=") + if !ok { logrus.Warnf("invalid environment variable for engine %s, valid configuration is KEY=value pair", env) continue } // skip if the env is already defined - if _, ok := os.LookupEnv(splitEnv[0]); ok { - logrus.Debugf("environment variable %s is already defined, skip the settings from containers.conf", splitEnv[0]) + if _, ok := os.LookupEnv(key); ok { + logrus.Debugf("environment variable %s is already defined, skip the settings from containers.conf", key) continue } - if err := os.Setenv(splitEnv[0], splitEnv[1]); err != nil { + if err := os.Setenv(key, value); err != nil { return err } } @@ -1202,7 +1209,7 @@ func (e eventsLogMaxSize) MarshalText() ([]byte, error) { v := []byte{} return v, nil } - return []byte(fmt.Sprintf("%d", e)), nil + return fmt.Appendf(nil, "%d", e), nil } func ValidateImageVolumeMode(mode string) error { diff --git a/vendor/go.podman.io/common/pkg/config/config_unsupported.go b/vendor/go.podman.io/common/pkg/config/config_unsupported.go index 793a20ea8b..94938e243b 100644 --- a/vendor/go.podman.io/common/pkg/config/config_unsupported.go +++ b/vendor/go.podman.io/common/pkg/config/config_unsupported.go @@ -7,7 +7,7 @@ func selinuxEnabled() bool { } // Capabilities returns the capabilities parses the Add and Drop capability -// list from the default capabilities for the container +// list from the default capabilities for the container. func (c *Config) Capabilities(user string, addCapabilities, dropCapabilities []string) ([]string, error) { return nil, nil } diff --git a/vendor/go.podman.io/common/pkg/config/containers.conf b/vendor/go.podman.io/common/pkg/config/containers.conf index d0da362002..2e392d048e 100644 --- a/vendor/go.podman.io/common/pkg/config/containers.conf +++ b/vendor/go.podman.io/common/pkg/config/containers.conf @@ -317,11 +317,13 @@ default_sysctls = [ # #umask = "0022" -# Default way to to create a User namespace for the container +# Default way to create a USER namespace for the container. # Options are: -# `auto` Create unique User Namespace for the container. -# `host` Share host User Namespace with the container. -# +# `private` Create private USER Namespace for the container, without adding any UID mappings. +# `host` Share host USER Namespace with the container. Root in the container is mapped to the host user UID. +# `auto` Automatically create a USER namespace with a unique mapping. +# `keep-id` Like `private`, but container UIDs are mapped to the host user's subordinate UIDs listed in `/etc/subuid`, and the current user's `UID:GID` are mapped to the same values in the container. +# `no-map` Like `keep-id`, but the current user's `UID:GID` does not map to any `UID:GID` inside the container. #userns = "host" # Default way to to create a UTS namespace for the container @@ -777,10 +779,17 @@ default_sysctls = [ # rootful "unix:///run/podman/podman.sock (Default) # remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock # remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock +# tcp/tls remote tcp://10.10.1.136:9443 # # uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock" # Path to file containing ssh identity key # identity = "~/.ssh/id_rsa" +# Path to PEM file containing TLS client certificate +# tls_cert = "/path/to/certs/podman/tls.crt" +# Path to PEM file containing TLS client certificate private key +# tls_key = "/path/to/certs/podman/tls.key" +# Path to PEM file containing TLS certificate authority (CA) bundle +# tls_ca = "/path/to/certs/podman/ca.crt" # Directory for temporary files. Must be tmpfs (wiped after reboot) # diff --git a/vendor/go.podman.io/common/pkg/config/containers.conf-freebsd b/vendor/go.podman.io/common/pkg/config/containers.conf-freebsd index b57160b109..bd999c339c 100644 --- a/vendor/go.podman.io/common/pkg/config/containers.conf-freebsd +++ b/vendor/go.podman.io/common/pkg/config/containers.conf-freebsd @@ -598,10 +598,17 @@ default_sysctls = [ # rootful "unix:///run/podman/podman.sock (Default) # remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock # remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock +# tcp/tls remote tcp://10.10.1.136:9443 # # uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock" # Path to file containing ssh identity key # identity = "~/.ssh/id_rsa" +# Path to PEM file containing TLS client certificate +# tls_cert = "/path/to/certs/podman/tls.crt" +# Path to PEM file containing TLS client certificate private key +# tls_key = "/path/to/certs/podman/tls.key" +# Path to PEM file containing TLS certificate authority (CA) bundle +# tls_ca = "/path/to/certs/podman/ca.crt" # Directory for temporary files. Must be tmpfs (wiped after reboot) # diff --git a/vendor/go.podman.io/common/pkg/config/default.go b/vendor/go.podman.io/common/pkg/config/default.go index c48185646f..3bf0bc1692 100644 --- a/vendor/go.podman.io/common/pkg/config/default.go +++ b/vendor/go.podman.io/common/pkg/config/default.go @@ -145,16 +145,6 @@ var ( // helper binary in a different location. additionalHelperBinariesDir string - defaultUnixComposeProviders = []string{ - "$HOME/.docker/cli-plugins/docker-compose", - "/usr/local/lib/docker/cli-plugins/docker-compose", - "/usr/local/libexec/docker/cli-plugins/docker-compose", - "/usr/lib/docker/cli-plugins/docker-compose", - "/usr/libexec/docker/cli-plugins/docker-compose", - "docker-compose", - "podman-compose", - } - defaultContainerEnv = []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"} ) diff --git a/vendor/go.podman.io/common/pkg/config/default_bsd.go b/vendor/go.podman.io/common/pkg/config/default_bsd.go index 2e87dc8b0f..6d7d0d6dc6 100644 --- a/vendor/go.podman.io/common/pkg/config/default_bsd.go +++ b/vendor/go.podman.io/common/pkg/config/default_bsd.go @@ -28,7 +28,3 @@ func getLibpodTmpDir() string { func getDefaultMachineVolumes() []string { return []string{"$HOME:$HOME"} } - -func getDefaultComposeProviders() []string { - return defaultUnixComposeProviders -} diff --git a/vendor/go.podman.io/common/pkg/config/default_darwin.go b/vendor/go.podman.io/common/pkg/config/default_darwin.go index 86fa6d5087..03d12a2a30 100644 --- a/vendor/go.podman.io/common/pkg/config/default_darwin.go +++ b/vendor/go.podman.io/common/pkg/config/default_darwin.go @@ -12,7 +12,7 @@ func getLibpodTmpDir() string { return "/run/libpod" } -// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded) +// getDefaultMachineVolumes returns default mounted volumes (possibly with env vars, which will be expanded). func getDefaultMachineVolumes() []string { return []string{ "/Users:/Users", diff --git a/vendor/go.podman.io/common/pkg/config/default_linux.go b/vendor/go.podman.io/common/pkg/config/default_linux.go index ae9810fad7..fdcc6f3912 100644 --- a/vendor/go.podman.io/common/pkg/config/default_linux.go +++ b/vendor/go.podman.io/common/pkg/config/default_linux.go @@ -29,7 +29,3 @@ func getLibpodTmpDir() string { func getDefaultMachineVolumes() []string { return []string{"$HOME:$HOME"} } - -func getDefaultComposeProviders() []string { - return defaultUnixComposeProviders -} diff --git a/vendor/go.podman.io/common/pkg/config/default_unix_notdarwin.go b/vendor/go.podman.io/common/pkg/config/default_unix_notdarwin.go new file mode 100644 index 0000000000..913dd774e8 --- /dev/null +++ b/vendor/go.podman.io/common/pkg/config/default_unix_notdarwin.go @@ -0,0 +1,17 @@ +//go:build linux || freebsd || netbsd || openbsd + +package config + +var defaultUnixComposeProviders = []string{ + "$HOME/.docker/cli-plugins/docker-compose", + "/usr/local/lib/docker/cli-plugins/docker-compose", + "/usr/local/libexec/docker/cli-plugins/docker-compose", + "/usr/lib/docker/cli-plugins/docker-compose", + "/usr/libexec/docker/cli-plugins/docker-compose", + "docker-compose", + "podman-compose", +} + +func getDefaultComposeProviders() []string { + return defaultUnixComposeProviders +} diff --git a/vendor/go.podman.io/common/pkg/config/default_unsupported.go b/vendor/go.podman.io/common/pkg/config/default_unsupported.go index 46653e3996..6b0ed468f0 100644 --- a/vendor/go.podman.io/common/pkg/config/default_unsupported.go +++ b/vendor/go.podman.io/common/pkg/config/default_unsupported.go @@ -4,17 +4,7 @@ package config import "os" -// isCgroup2UnifiedMode returns whether we are running in cgroup2 mode. -func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) { - return false, nil -} - -// getDefaultProcessLimits returns the nofile and nproc for the current process in ulimits format -func getDefaultProcessLimits() []string { - return []string{} -} - -// getDefaultTmpDir for linux +// getDefaultTmpDir for linux. func getDefaultTmpDir() string { // first check the TMPDIR env var if path, found := os.LookupEnv("TMPDIR"); found { diff --git a/vendor/go.podman.io/common/pkg/config/default_windows.go b/vendor/go.podman.io/common/pkg/config/default_windows.go index d57e775b5a..c21fff76ab 100644 --- a/vendor/go.podman.io/common/pkg/config/default_windows.go +++ b/vendor/go.podman.io/common/pkg/config/default_windows.go @@ -9,16 +9,6 @@ import ( "go.podman.io/storage/pkg/homedir" ) -// isCgroup2UnifiedMode returns whether we are running in cgroup2 mode. -func isCgroup2UnifiedMode() (isUnified bool, isUnifiedErr error) { - return false, nil -} - -// getDefaultProcessLimits returns the nofile and nproc for the current process in ulimits format -func getDefaultProcessLimits() []string { - return []string{} -} - // getDefaultTmpDir for windows func getDefaultTmpDir() string { // first check the Temp env var diff --git a/vendor/go.podman.io/common/pkg/config/systemd.go b/vendor/go.podman.io/common/pkg/config/systemd.go index 5996302b72..e7c15b5909 100644 --- a/vendor/go.podman.io/common/pkg/config/systemd.go +++ b/vendor/go.podman.io/common/pkg/config/systemd.go @@ -5,16 +5,14 @@ package config import ( "os" "path/filepath" - "strings" "sync" "go.podman.io/common/pkg/cgroupv2" + "go.podman.io/common/pkg/systemd" "go.podman.io/storage/pkg/unshare" ) var ( - systemdOnce sync.Once - usesSystemd bool journaldOnce sync.Once usesJournald bool ) @@ -51,14 +49,7 @@ func defaultLogDriver() string { } func useSystemd() bool { - systemdOnce.Do(func() { - dat, err := os.ReadFile("/proc/1/comm") - if err == nil { - val := strings.TrimSuffix(string(dat), "\n") - usesSystemd = (val == "systemd") - } - }) - return usesSystemd + return systemd.RunsOnSystemd() } func useJournald() bool { diff --git a/vendor/go.podman.io/common/pkg/manifests/manifests.go b/vendor/go.podman.io/common/pkg/manifests/manifests.go index 5198fc0ae6..6884c13d04 100644 --- a/vendor/go.podman.io/common/pkg/manifests/manifests.go +++ b/vendor/go.podman.io/common/pkg/manifests/manifests.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "os" "slices" "strings" @@ -238,9 +239,7 @@ func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[str if *a == nil { (*a) = make(map[string]string) } - for k, v := range annotations { - (*a)[k] = v - } + maps.Copy((*a), annotations) if len(*a) == 0 { *a = nil } @@ -259,9 +258,7 @@ func (l *list) Annotations(instanceDigest *digest.Digest) (map[string]string, er a = oci.Annotations } annotations := make(map[string]string) - for k, v := range a { - annotations[k] = v - } + maps.Copy(annotations, a) return annotations, nil } diff --git a/vendor/go.podman.io/common/pkg/report/formatter.go b/vendor/go.podman.io/common/pkg/report/formatter.go index dcc09fdb16..e05e9762f4 100644 --- a/vendor/go.podman.io/common/pkg/report/formatter.go +++ b/vendor/go.podman.io/common/pkg/report/formatter.go @@ -2,6 +2,7 @@ package report import ( "io" + "maps" "strings" "text/tabwriter" "text/template" @@ -109,12 +110,8 @@ func (f *Formatter) Parse(origin Origin, text string) (*Formatter, error) { // A default template function will be replaced if there is a key collision. func (f *Formatter) Funcs(funcMap template.FuncMap) *Formatter { m := make(template.FuncMap, len(DefaultFuncs)+len(funcMap)) - for k, v := range DefaultFuncs { - m[k] = v - } - for k, v := range funcMap { - m[k] = v - } + maps.Copy(m, DefaultFuncs) + maps.Copy(m, funcMap) f.template = f.template.Funcs(funcMap) return f } diff --git a/vendor/go.podman.io/common/pkg/report/template.go b/vendor/go.podman.io/common/pkg/report/template.go index bb6ce7bea5..a1113d869c 100644 --- a/vendor/go.podman.io/common/pkg/report/template.go +++ b/vendor/go.podman.io/common/pkg/report/template.go @@ -3,6 +3,7 @@ package report import ( "bytes" "encoding/json" + "maps" "reflect" "strings" "text/template" @@ -95,7 +96,7 @@ func truncateWithLength(source string, length int) string { // 3) --format 'table {{.ID}}' # includes headers func Headers(object any, overrides map[string]string) []map[string]string { value := reflect.ValueOf(object) - if value.Kind() == reflect.Ptr { + if value.Kind() == reflect.Pointer { value = value.Elem() } @@ -106,9 +107,7 @@ func Headers(object any, overrides map[string]string) []map[string]string { // Recurse to find field names from promoted structs if field.Type.Kind() == reflect.Struct && field.Anonymous { h := Headers(reflect.New(field.Type).Interface(), nil) - for k, v := range h[0] { - headers[k] = v - } + maps.Copy(headers, h[0]) continue } name := strings.Join(camelcase.Split(field.Name), " ") @@ -146,12 +145,8 @@ func (t *Template) Parse(text string) (*Template, error) { // A default template function will be replace if there is a key collision. func (t *Template) Funcs(funcMap FuncMap) *Template { m := make(FuncMap) - for k, v := range DefaultFuncs { - m[k] = v - } - for k, v := range funcMap { - m[k] = v - } + maps.Copy(m, DefaultFuncs) + maps.Copy(m, funcMap) return &Template{Template: t.Template.Funcs(template.FuncMap(m)), isTable: t.isTable} } diff --git a/vendor/go.podman.io/common/pkg/seccomp/filter.go b/vendor/go.podman.io/common/pkg/seccomp/filter_linux.go similarity index 100% rename from vendor/go.podman.io/common/pkg/seccomp/filter.go rename to vendor/go.podman.io/common/pkg/seccomp/filter_linux.go diff --git a/vendor/go.podman.io/common/pkg/seccomp/seccomp_unsupported.go b/vendor/go.podman.io/common/pkg/seccomp/seccomp_unsupported.go index 1bf8155ddc..1311de0c75 100644 --- a/vendor/go.podman.io/common/pkg/seccomp/seccomp_unsupported.go +++ b/vendor/go.podman.io/common/pkg/seccomp/seccomp_unsupported.go @@ -14,12 +14,12 @@ import ( var errNotSupported = errors.New("seccomp not enabled in this build") -// LoadProfile returns an error on unsupported systems +// LoadProfile returns an error on unsupported systems. func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) { return nil, errNotSupported } -// GetDefaultProfile returns an error on unsupported systems +// GetDefaultProfile returns an error on unsupported systems. func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) { return nil, errNotSupported } @@ -29,7 +29,7 @@ func LoadProfileFromBytes(body []byte, rs *specs.Spec) (*specs.LinuxSeccomp, err return nil, errNotSupported } -// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp +// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp. func LoadProfileFromConfig(config *Seccomp, specgen *specs.Spec) (*specs.LinuxSeccomp, error) { return nil, errNotSupported } diff --git a/vendor/go.podman.io/common/pkg/seccomp/validate.go b/vendor/go.podman.io/common/pkg/seccomp/validate_linux.go similarity index 100% rename from vendor/go.podman.io/common/pkg/seccomp/validate.go rename to vendor/go.podman.io/common/pkg/seccomp/validate_linux.go diff --git a/vendor/go.podman.io/common/pkg/secrets/shelldriver/shelldriver.go b/vendor/go.podman.io/common/pkg/secrets/shelldriver/shelldriver.go index 5cf6aa48d2..c8b806d2a0 100644 --- a/vendor/go.podman.io/common/pkg/secrets/shelldriver/shelldriver.go +++ b/vendor/go.podman.io/common/pkg/secrets/shelldriver/shelldriver.go @@ -89,8 +89,7 @@ func (d *Driver) List() (secrets []string, err error) { return nil, err } - parts := bytes.Split(buf.Bytes(), []byte("\n")) - for _, part := range parts { + for part := range bytes.SplitSeq(buf.Bytes(), []byte("\n")) { id := strings.Trim(string(part), " \r\n") if len(id) > 0 { secrets = append(secrets, id) diff --git a/vendor/go.podman.io/common/pkg/ssh/connection_golang.go b/vendor/go.podman.io/common/pkg/ssh/connection_golang.go index 591e1a7575..c8e3c5bd85 100644 --- a/vendor/go.podman.io/common/pkg/ssh/connection_golang.go +++ b/vendor/go.podman.io/common/pkg/ssh/connection_golang.go @@ -166,8 +166,7 @@ func golangConnectionScp(options ConnectionScpOptions) (*ConnectionScpReport, er parent := filepath.Dir(remoteFile) path := string(filepath.Separator) - dirs := strings.Split(parent, path) - for _, dir := range dirs { + for dir := range strings.SplitSeq(parent, path) { path = filepath.Join(path, dir) // ignore errors due to most of the dirs already existing _ = sc.Mkdir(path) diff --git a/vendor/go.podman.io/common/pkg/ssh/connection_native.go b/vendor/go.podman.io/common/pkg/ssh/connection_native.go index 39127bbab4..a274db88ab 100644 --- a/vendor/go.podman.io/common/pkg/ssh/connection_native.go +++ b/vendor/go.podman.io/common/pkg/ssh/connection_native.go @@ -38,8 +38,8 @@ func nativeConnectionCreate(options ConnectionCreateOptions) error { return err } - if strings.Contains(uri.Host, "/run") { - uri.Host = strings.Split(uri.Host, "/run")[0] + if host, _, ok := strings.Cut(uri.Host, "/run"); ok { + uri.Host = host } conf, err := config.Default() if err != nil { @@ -114,8 +114,8 @@ func nativeConnectionExec(options ConnectionExecOptions, input io.Reader) (*Conn output := &bytes.Buffer{} errors := &bytes.Buffer{} - if strings.Contains(uri.Host, "/run") { - uri.Host = strings.Split(uri.Host, "/run")[0] + if host, _, ok := strings.Cut(uri.Host, "/run"); ok { + uri.Host = host } options.Args = append([]string{uri.User.String() + "@" + uri.Hostname()}, options.Args...) diff --git a/vendor/go.podman.io/common/pkg/subscriptions/subscriptions.go b/vendor/go.podman.io/common/pkg/subscriptions/subscriptions.go index a907ea6758..a8921941ba 100644 --- a/vendor/go.podman.io/common/pkg/subscriptions/subscriptions.go +++ b/vendor/go.podman.io/common/pkg/subscriptions/subscriptions.go @@ -144,15 +144,12 @@ func getMounts(filePath string) []string { } // getHostAndCtrDir separates the host:container paths. -func getMountsMap(path string) (string, string, error) { //nolint - arr := strings.SplitN(path, ":", 2) - switch len(arr) { - case 1: - return arr[0], arr[0], nil - case 2: - return arr[0], arr[1], nil +func getMountsMap(path string) (string, string) { + host, ctr, ok := strings.Cut(path, ":") + if !ok { + return path, path } - return "", "", fmt.Errorf("unable to get host and container dir from path: %s", path) + return host, ctr } // Return true iff the system is in FIPS mode as determined by reading @@ -238,10 +235,7 @@ func addSubscriptionsFromMountsFile(filePath, mountLabel, containerRunDir string defaultMountsPaths := getMounts(filePath) mounts := make([]rspec.Mount, 0, len(defaultMountsPaths)) for _, path := range defaultMountsPaths { - hostDirOrFile, ctrDirOrFile, err := getMountsMap(path) - if err != nil { - return nil, err - } + hostDirOrFile, ctrDirOrFile := getMountsMap(path) // skip if the hostDirOrFile path doesn't exist fileInfo, err := os.Stat(hostDirOrFile) if err != nil { diff --git a/vendor/go.podman.io/common/pkg/timetype/timestamp.go b/vendor/go.podman.io/common/pkg/timetype/timestamp.go index 2fc99d0e39..ca404c84f3 100644 --- a/vendor/go.podman.io/common/pkg/timetype/timestamp.go +++ b/vendor/go.podman.io/common/pkg/timetype/timestamp.go @@ -117,19 +117,19 @@ func ParseTimestamps(value string, def int64) (secs, nanoSecs int64, err error) } func parseTimestamp(value string) (int64, int64, error) { - sa := strings.SplitN(value, ".", 2) - s, err := strconv.ParseInt(sa[0], 10, 64) + secStr, nsStr, ok := strings.Cut(value, ".") + s, err := strconv.ParseInt(secStr, 10, 64) if err != nil { return s, 0, err } - if len(sa) != 2 { + if !ok { return s, 0, nil } - n, err := strconv.ParseInt(sa[1], 10, 64) + n, err := strconv.ParseInt(nsStr, 10, 64) if err != nil { return s, n, err } // should already be in nanoseconds but just in case convert n to nanoseconds - n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) + n = int64(float64(n) * math.Pow(float64(10), float64(9-len(nsStr)))) return s, n, nil } diff --git a/vendor/go.podman.io/common/pkg/timezone/timezone.go b/vendor/go.podman.io/common/pkg/timezone/timezone.go index 2dd55d8358..40333841e2 100644 --- a/vendor/go.podman.io/common/pkg/timezone/timezone.go +++ b/vendor/go.podman.io/common/pkg/timezone/timezone.go @@ -1,4 +1,4 @@ -//go:build !windows +//go:build linux || freebsd package timezone @@ -104,3 +104,7 @@ func copyTimezoneFile(containerRunDir, zonePath string) (string, error) { } return localtimeCopy, err } + +func openDirectory(path string) (fd int, err error) { + return unix.Open(path, unix.O_RDONLY|O_PATH|unix.O_CLOEXEC, 0) +} diff --git a/vendor/go.podman.io/common/pkg/timezone/timezone_freebsd.go b/vendor/go.podman.io/common/pkg/timezone/timezone_freebsd.go new file mode 100644 index 0000000000..008b474f72 --- /dev/null +++ b/vendor/go.podman.io/common/pkg/timezone/timezone_freebsd.go @@ -0,0 +1,7 @@ +//go:build !windows && !linux + +package timezone + +// O_PATH value on freebsd. We must define O_PATH ourselves +// until https://github.com/golang/go/issues/54355 is fixed. +const O_PATH = 0x00400000 //nolint:staticcheck // ST1003: should not use ALL_CAPS diff --git a/vendor/go.podman.io/common/pkg/timezone/timezone_linux.go b/vendor/go.podman.io/common/pkg/timezone/timezone_linux.go index ef096af59d..56e5eaa718 100644 --- a/vendor/go.podman.io/common/pkg/timezone/timezone_linux.go +++ b/vendor/go.podman.io/common/pkg/timezone/timezone_linux.go @@ -4,6 +4,5 @@ import ( "golang.org/x/sys/unix" ) -func openDirectory(path string) (fd int, err error) { - return unix.Open(path, unix.O_RDONLY|unix.O_PATH|unix.O_CLOEXEC, 0) -} +// O_PATH value on linux. +const O_PATH = unix.O_PATH //nolint:staticcheck // ST1003: should not use ALL_CAPS diff --git a/vendor/go.podman.io/common/pkg/timezone/timezone_unix.go b/vendor/go.podman.io/common/pkg/timezone/timezone_unix.go deleted file mode 100644 index bb57036f82..0000000000 --- a/vendor/go.podman.io/common/pkg/timezone/timezone_unix.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !windows && !linux - -package timezone - -import ( - "golang.org/x/sys/unix" -) - -func openDirectory(path string) (fd int, err error) { - const O_PATH = 0x00400000 - return unix.Open(path, unix.O_RDONLY|O_PATH|unix.O_CLOEXEC, 0) -} diff --git a/vendor/go.podman.io/common/pkg/timezone/timezone_windows.go b/vendor/go.podman.io/common/pkg/timezone/timezone_windows.go deleted file mode 100644 index d89090eeb9..0000000000 --- a/vendor/go.podman.io/common/pkg/timezone/timezone_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -package timezone - -func ConfigureContainerTimeZone(timezone, containerRunDir, mountPoint, etcPath, containerID string) (string, error) { - return "", nil -} diff --git a/vendor/go.podman.io/common/pkg/version/version.go b/vendor/go.podman.io/common/pkg/version/version.go index 9b13f484d1..80261c125e 100644 --- a/vendor/go.podman.io/common/pkg/version/version.go +++ b/vendor/go.podman.io/common/pkg/version/version.go @@ -28,10 +28,9 @@ func queryPackageVersion(cmdArg ...string) string { switch cmdArg[0] { case "/usr/bin/dlocate": // can return multiple matches - l := strings.Split(output, "\n") - output = l[0] - r := strings.Split(output, ": ") - regexpFormat := `^..\s` + r[0] + `\s` + output, _, _ := strings.Cut(output, "\n") + r, _, _ := strings.Cut(output, ": ") + regexpFormat := `^..\s` + r + `\s` cmd = exec.Command(cmdArg[0], "-P", regexpFormat, "-l") cmd.Env = []string{"COLUMNS=160"} // show entire value // dlocate always returns exit code 1 for list command @@ -46,9 +45,9 @@ func queryPackageVersion(cmdArg ...string) string { } } case "/usr/bin/dpkg": - r := strings.Split(output, ": ") + r, _, _ := strings.Cut(output, ": ") queryFormat := `${Package}_${Version}_${Architecture}` - cmd = exec.Command("/usr/bin/dpkg-query", "-f", queryFormat, "-W", r[0]) + cmd = exec.Command("/usr/bin/dpkg-query", "-f", queryFormat, "-W", r) if outp, err := cmd.Output(); err == nil { output = string(outp) } diff --git a/vendor/go.podman.io/common/version/version.go b/vendor/go.podman.io/common/version/version.go index e13dd10e59..bdfd10c645 100644 --- a/vendor/go.podman.io/common/version/version.go +++ b/vendor/go.podman.io/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.65.0" +const Version = "0.66.0-dev" diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go index 8b462f3dfe..0b789e2c5e 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/client.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/client.pb.go index db7806cb99..f840481726 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/client.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/client.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/field_behavior.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/field_behavior.pb.go index 08505ba3fe..5d583b8660 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/field_behavior.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/field_behavior.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/field_info.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/field_info.pb.go index a462e7d013..53e9dd1e99 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/field_info.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/field_info.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go index c93b4f5248..d30fcee4ce 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/resource.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/resource.pb.go index a1c543a948..175974a869 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/resource.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/resource.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/annotations/routing.pb.go b/vendor/google.golang.org/genproto/googleapis/api/annotations/routing.pb.go index 2b54db3045..b8c4aa71f2 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/annotations/routing.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/annotations/routing.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/google.golang.org/genproto/googleapis/api/launch_stage.pb.go b/vendor/google.golang.org/genproto/googleapis/api/launch_stage.pb.go index 498020e33c..a69c1d4734 100644 --- a/vendor/google.golang.org/genproto/googleapis/api/launch_stage.pb.go +++ b/vendor/google.golang.org/genproto/googleapis/api/launch_stage.pb.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/modules.txt b/vendor/modules.txt index cbf1019431..a119da9b56 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -75,8 +75,8 @@ github.com/containernetworking/cni/pkg/types/create github.com/containernetworking/cni/pkg/types/internal github.com/containernetworking/cni/pkg/utils github.com/containernetworking/cni/pkg/version -# github.com/containernetworking/plugins v1.7.1 -## explicit; go 1.23.0 +# github.com/containernetworking/plugins v1.8.0 +## explicit; go 1.24.2 github.com/containernetworking/plugins/pkg/ns # github.com/containers/buildah v1.41.1-0.20250829135344-3367a9bc2c9f ## explicit; go 1.23.3 @@ -312,7 +312,7 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/go-containerregistry v0.20.3 +# github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f ## explicit; go 1.23.0 github.com/google/go-containerregistry/pkg/name github.com/google/go-containerregistry/pkg/v1 @@ -320,8 +320,8 @@ github.com/google/go-containerregistry/pkg/v1/types # github.com/google/go-intervals v0.0.2 ## explicit; go 1.12 github.com/google/go-intervals/intervalset -# github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 -## explicit; go 1.23 +# github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 +## explicit; go 1.24.0 github.com/google/pprof/profile # github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 ## explicit; go 1.13 @@ -534,7 +534,7 @@ github.com/opencontainers/go-digest ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/opencontainers/runc v1.3.0 +# github.com/opencontainers/runc v1.3.1 ## explicit; go 1.23.0 github.com/opencontainers/runc/libcontainer/apparmor github.com/opencontainers/runc/libcontainer/devices @@ -614,8 +614,8 @@ github.com/shirou/gopsutil/v4/internal/common github.com/shirou/gopsutil/v4/mem github.com/shirou/gopsutil/v4/net github.com/shirou/gopsutil/v4/process -# github.com/sigstore/fulcio v1.6.6 -## explicit; go 1.23.3 +# github.com/sigstore/fulcio v1.7.1 +## explicit; go 1.24.0 github.com/sigstore/fulcio/pkg/api github.com/sigstore/fulcio/pkg/certificate # github.com/sigstore/protobuf-specs v0.4.1 @@ -742,8 +742,8 @@ go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded go.opentelemetry.io/otel/trace/internal/telemetry go.opentelemetry.io/otel/trace/noop -# go.podman.io/common v0.65.0 -## explicit; go 1.23.3 +# go.podman.io/common v0.65.1-0.20250925174758-4cf0ff781bfc +## explicit; go 1.24.2 go.podman.io/common/internal go.podman.io/common/internal/attributedstring go.podman.io/common/libimage @@ -1043,11 +1043,11 @@ golang.org/x/time/rate golang.org/x/tools/cover golang.org/x/tools/go/ast/edge golang.org/x/tools/go/ast/inspector -# google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb +# google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/api google.golang.org/genproto/googleapis/api/annotations -# google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/rpc/status # google.golang.org/grpc v1.72.2