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 <meln5674.5674@gmail.com>
This commit is contained in:
Andrew Melnick
2025-07-31 18:51:37 -06:00
parent a118fdf4e2
commit feb36e4fe6
116 changed files with 1848 additions and 616 deletions

View File

@@ -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:

View File

@@ -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")

View File

@@ -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]

View File

@@ -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",

View File

@@ -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,
})
}

View File

@@ -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 == "" {

View File

@@ -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)**

View File

@@ -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**

View File

@@ -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.

View File

@@ -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.

18
go.mod
View File

@@ -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

48
go.sum
View File

@@ -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=

View File

@@ -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)

View File

@@ -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

View File

@@ -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://<user>@<host>[: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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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)

32
pkg/util/tlsutil/tls.go Normal file
View File

@@ -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
}

View File

@@ -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 doesnt 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
}

View File

@@ -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",

View File

@@ -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() {

View File

@@ -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

View File

@@ -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)

View File

@@ -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,
},
},
})

View File

@@ -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" {

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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}}"

View File

@@ -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 <bad_scheme_uri> 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
}

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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."

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
###############################################################################

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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=<value>", 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

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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, `"`), `"`)

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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})
}
}

View File

@@ -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)
}

View File

@@ -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 != "" {

View File

@@ -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)

View File

@@ -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().

View File

@@ -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"
)

View File

@@ -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"

View File

@@ -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, ".")

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)
#

View File

@@ -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)
#

View File

@@ -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"}
)

View File

@@ -28,7 +28,3 @@ func getLibpodTmpDir() string {
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}
func getDefaultComposeProviders() []string {
return defaultUnixComposeProviders
}

View File

@@ -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",

View File

@@ -29,7 +29,3 @@ func getLibpodTmpDir() string {
func getDefaultMachineVolumes() []string {
return []string{"$HOME:$HOME"}
}
func getDefaultComposeProviders() []string {
return defaultUnixComposeProviders
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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}
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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...)

View File

@@ -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 {

View File

@@ -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
}

Some files were not shown because too many files have changed in this diff Show More