diff options
73 files changed, 1135 insertions, 549 deletions
diff --git a/.golangci.yml b/.golangci.yml index 956e528ef..8a922775a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,7 +32,6 @@ linters: - predeclared - thelper - ifshort - - staticcheck - forbidigo - exhaustive - unparam @@ -877,7 +877,7 @@ install.tools: .install.goimports .install.gitvalidation .install.md2man .instal .PHONY: .install.golangci-lint .install.golangci-lint: .gopathok - VERSION=1.45.0 GOBIN=$(GOBIN) ./hack/install_golangci.sh + VERSION=1.45.2 GOBIN=$(GOBIN) ./hack/install_golangci.sh .PHONY: .install.bats .install.bats: .gopathok diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3d36162ee..abb943942 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -756,7 +756,7 @@ func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) // -> same as AutocompleteNamespace with "auto", "keep-id" added func AutocompleteUserNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { results, directive := AutocompleteNamespace(cmd, args, toComplete) - results = append(results, "auto", "keep-id") + results = append(results, "auto", "keep-id", "nomap") return results, directive } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index bbc449a1e..b202e404f 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -286,8 +286,6 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra if !isInfra && c.Flag("entrypoint").Changed { val := c.Flag("entrypoint").Value.String() vals.Entrypoint = &val - } else if isInfra && c.Flag("infra-command").Changed { - } // Docker-compatibility: the "-h" flag for run/create is reserved for @@ -297,7 +295,7 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra } func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (string, error) { - pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) + pullPolicy, err := config.ParsePullPolicy(cliVals.Pull) if err != nil { return "", err } @@ -383,7 +381,7 @@ func createPodIfNecessary(cmd *cobra.Command, s *specgen.SpecGenerator, netOpts podSpec := entities.PodSpec{} podGen := specgen.NewPodSpecGenerator() podSpec.PodSpecGen = *podGen - podGen, err = entities.ToPodSpecGen(*&podSpec.PodSpecGen, &createOptions) + podGen, err = entities.ToPodSpecGen(podSpec.PodSpecGen, &createOptions) if err != nil { return nil, err } diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index 0fed94e86..681df93e0 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -11,7 +11,7 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var ( @@ -69,7 +69,7 @@ func init() { func export(cmd *cobra.Command, args []string) error { if len(exportOpts.Output) == 0 { file := os.Stdout - if terminal.IsTerminal(int(file.Fd())) { + if term.IsTerminal(int(file.Fd())) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } exportOpts.Output = "/dev/stdout" diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 1d98eb3b3..4ad8d3183 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -18,7 +18,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var ( @@ -112,7 +112,7 @@ func run(cmd *cobra.Command, args []string) error { var err error // TODO: Breaking change should be made fatal in next major Release - if cliVals.TTY && cliVals.Interactive && !terminal.IsTerminal(int(os.Stdin.Fd())) { + if cliVals.TTY && cliVals.Interactive && !term.IsTerminal(int(os.Stdin.Fd())) { logrus.Warnf("The input device is not a TTY. The --tty and --interactive flags might not work properly") } diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index 30f88b02b..6f85fb7e7 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -16,7 +16,7 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var ( @@ -90,7 +90,7 @@ func load(cmd *cobra.Command, args []string) error { return err } } else { - if terminal.IsTerminal(int(os.Stdin.Fd())) { + if term.IsTerminal(int(os.Stdin.Fd())) { return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") } outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index a9fe675e1..fb642bafd 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -14,7 +14,7 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var ( @@ -109,7 +109,7 @@ func save(cmd *cobra.Command, args []string) (finalErr error) { if len(saveOpts.Output) == 0 { saveOpts.Quiet = true fi := os.Stdout - if terminal.IsTerminal(int(fi.Fd())) { + if term.IsTerminal(int(fi.Fd())) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } pipePath, cleanup, err := setupPipe() diff --git a/cmd/podman/volumes/unmount.go b/cmd/podman/volumes/unmount.go index dd0cebc06..af79e49ef 100644 --- a/cmd/podman/volumes/unmount.go +++ b/cmd/podman/volumes/unmount.go @@ -37,7 +37,6 @@ func volumeUnmount(cmd *cobra.Command, args []string) error { return err } for _, r := range reports { - var errs utils.OutputErrors if r.Err == nil { fmt.Println(r.Id) } else { diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 5793ecae5..406dfcd89 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -439,9 +439,9 @@ with a new set of cached layers. #### **--no-hosts** Do not create _/etc/hosts_ for the container. - -By default, Buildah manages _/etc/hosts_, adding the container's own IP address. -**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. Conflicts with the --add-host option. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +This option conflicts with **--add-host**. #### **--os**=*string* diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index f6d028f4d..15ae28dc3 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -743,9 +743,9 @@ Disable any defined healthchecks for container. #### **--no-hosts** -Do not create /etc/hosts for the container. -By default, Podman will manage /etc/hosts, adding the container's own IP address and any hosts from **--add-host**. -#### **--no-hosts** disables this, and the image's **/etc/host** will be preserved unmodified. +Do not create _/etc/hosts_ for the container. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. #### **--oom-kill-disable** @@ -1225,6 +1225,15 @@ Without this argument the command will be run as root in the container. Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with the **--uidmap** and **--gidmap** options. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: **auto**[:_OPTIONS,..._]: automatically create a unique user namespace. @@ -1247,6 +1256,8 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat **keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + **ns:**_namespace_: run the container in the given existing user namespace. **private**: create a new namespace for the container. diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 471c17f91..8b56d109a 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -216,7 +216,10 @@ Valid _mode_ values are: #### **--no-hosts** -Do not create /etc/hosts within the pod's containers, instead use the version from the image +Do not create /etc/hosts for the pod. +By default, Podman will manage /etc/hosts, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's **/etc/host** will be preserved unmodified. +This option conflicts with host added in the Kubernetes YAML. #### **--quiet**, **-q** diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index 1bf14efdb..403a317ea 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -17,7 +17,11 @@ containers added to it. The pod id is printed to STDOUT. You can then use #### **--add-host**=_host_:_ip_ -Add a host to the /etc/hosts file shared between all containers in the pod. +Add a custom host-to-IP mapping (host:ip) + +Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** +option can be set multiple times. +The /etc/hosts file is shared between all containers in the pod. #### **--cgroup-parent**=*path* @@ -187,7 +191,10 @@ NOTE: A container will only have access to aliases on the first network that it #### **--no-hosts** -Disable creation of /etc/hosts for the pod. +Do not create _/etc/hosts_ for the pod. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +This option conflicts with **--add-host**. #### **--pid**=*pid* @@ -308,14 +315,30 @@ several times to map different ranges. Set the user namespace mode for all the containers in a pod. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: -- *auto[:*_OPTIONS,..._*]*: automatically create a namespace. It is possible to specify these options to `auto`: + - *auto[:*_OPTIONS,..._*]*: automatically create a namespace. It is possible to specify these options to `auto`: + - *gidmapping=*_CONTAINER_GID:HOST_GID:SIZE_ to force a GID mapping to be present in the user namespace. + - *size=*_SIZE_: to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` will estimate a size for the user namespace. + - *uidmapping=*_CONTAINER_UID:HOST_UID:SIZE_ to force a UID mapping to be present in the user namespace. -- *host*: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). -- *keep-id*: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. + + - *host*: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). + + - *keep-id*: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. + + - *nomap*: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. #### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 8f72d4f49..578acf379 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -85,8 +85,10 @@ and specified with a _tag_. ## OPTIONS #### **--add-host**=_host_:_ip_ -Add a line to container's _/etc/hosts_ for custom host-to-IP mapping. -This option can be set multiple times. +Add a custom host-to-IP mapping (host:ip) + +Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** +option can be set multiple times. #### **--annotation**=_key_=_value_ @@ -768,9 +770,8 @@ Disable any defined healthchecks for container. #### **--no-hosts** Do not create _/etc/hosts_ for the container. - By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. -#### **--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. #### **--oom-kill-disable** @@ -1290,6 +1291,15 @@ When a user namespace is not in use, the UID and GID used within the container a Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with the **--uidmap** and **--gidmap** options. +Rootless user --userns=Key mappings: + +Key | Host User | Container User +----------|---------------|--------------------- +"" |$UID |0 (Default User account mapped to root user in container.) +keep-id |$UID |$UID (Map user account to same UID within container.) +auto |$UID | nil (Host User UID is not mapped into container.) +nomap |$UID | nil (Host User UID is not mapped into container.) + Valid _mode_ values are: **auto**[:_OPTIONS,..._]: automatically create a unique user namespace. @@ -1299,6 +1309,7 @@ The `--userns=auto` flag, requires that the user name `containers` and a range o Example: `containers:2147483647:2147483648`. Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinate user ids. The size of the ranges is based on the number of UIDs required in the image. The number of UIDs and GIDs can be overridden with the `size` option. + The rootless option `--userns=keep-id` uses all the subuids and subgids of the user. Using `--userns=auto` when starting new containers will not work as long as any containers exist that were started with `--userns=keep-id`. Valid `auto` options: @@ -1313,10 +1324,11 @@ The rootless option `--userns=keep-id` uses all the subuids and subgids of the u **keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. +**nomap**: creates a user namespace where the current rootless user's UID:GID are not mapped into the container. This option is ignored for containers created by the root user. + **ns:**_namespace_: run the container in the given existing user namespace. **private**: create a new namespace for the container. - This option is incompatible with **--gidmap**, **--uidmap**, **--subuidname** and **--subgidname**. #### **--uts**=*mode* @@ -8,7 +8,7 @@ require ( github.com/buger/goterm v1.0.4 github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 github.com/checkpoint-restore/go-criu/v5 v5.3.0 - github.com/container-orchestrated-devices/container-device-interface v0.3.2 + github.com/container-orchestrated-devices/container-device-interface v0.4.0 github.com/containernetworking/cni v1.0.1 github.com/containernetworking/plugins v1.1.1 github.com/containers/buildah v1.25.2-0.20220406205807-5b8e79118057 @@ -68,6 +68,7 @@ require ( golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 google.golang.org/protobuf v1.28.0 gopkg.in/inf.v0 v0.9.1 @@ -245,8 +245,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/container-orchestrated-devices/container-device-interface v0.3.2 h1:vZVaQwmFFddi7Y9mJgQTLPFxTWg81+OIHEMu/Th1wuw= -github.com/container-orchestrated-devices/container-device-interface v0.3.2/go.mod h1:E1zcucIkq9P3eyNmY+68dBQsTcsXJh9cgRo2IVNScKQ= +github.com/container-orchestrated-devices/container-device-interface v0.4.0 h1:b/mROkfDr1W8fJ25T66iVheHFnWixgyxTOSbO8i7jp4= +github.com/container-orchestrated-devices/container-device-interface v0.4.0/go.mod h1:E1zcucIkq9P3eyNmY+68dBQsTcsXJh9cgRo2IVNScKQ= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= diff --git a/libpod/container.go b/libpod/container.go index bc3cab439..6e2b7f528 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -628,6 +628,15 @@ func (c *Container) RuntimeName() string { // Hostname gets the container's hostname func (c *Container) Hostname() string { + if c.config.UTSNsCtr != "" { + utsNsCtr, err := c.runtime.GetContainer(c.config.UTSNsCtr) + if err != nil { + // should we return an error here? + logrus.Errorf("unable to lookup uts namespace for container %s: %v", c.ID(), err) + return "" + } + return utsNsCtr.Hostname() + } if c.config.Spec.Hostname != "" { return c.config.Spec.Hostname } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 735790411..76e0e9e13 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -845,7 +845,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named // Do not include if privileged - assumed that all devices will be // included. var err error - hostConfig.Devices, err = c.GetDevices(*&hostConfig.Privileged, *ctrSpec, deviceNodes) + hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, deviceNodes) if err != nil { return nil, err } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b051b7f2d..6c0d51df3 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1,7 +1,6 @@ package libpod import ( - "bufio" "bytes" "context" "fmt" @@ -17,8 +16,10 @@ import ( "github.com/containers/buildah/copier" "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/chown" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/ctime" @@ -31,6 +32,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/mount" "github.com/coreos/go-systemd/v22/daemon" securejoin "github.com/cyphar/filepath-securejoin" @@ -1006,17 +1008,14 @@ func (c *Container) completeNetworkSetup() error { } } // check if we have a bindmount for /etc/hosts - if hostsBindMount, ok := state.BindMounts["/etc/hosts"]; ok && len(c.cniHosts()) > 0 { - ctrHostPath := filepath.Join(c.state.RunDir, "hosts") - if hostsBindMount == ctrHostPath { - // read the existing hosts - b, err := ioutil.ReadFile(hostsBindMount) - if err != nil { - return err - } - if err := ioutil.WriteFile(hostsBindMount, append(b, []byte(c.cniHosts())...), 0644); err != nil { - return err - } + if hostsBindMount, ok := state.BindMounts[config.DefaultHostsFile]; ok { + entries, err := c.getHostsEntries() + if err != nil { + return err + } + // add new container ips to the hosts file + if err := etchosts.Add(hostsBindMount, entries); err != nil { + return err } } @@ -1041,18 +1040,6 @@ func (c *Container) completeNetworkSetup() error { return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644) } -func (c *Container) cniHosts() string { - var hosts string - for _, status := range c.getNetworkStatus() { - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - hosts += fmt.Sprintf("%s\t%s %s\n", netAddress.IPNet.IP.String(), c.Hostname(), c.config.Name) - } - } - } - return hosts -} - // Initialize a container, creating it in the runtime func (c *Container) init(ctx context.Context, retainRetries bool) error { // Unconditionally remove conmon temporary files. @@ -1894,6 +1881,24 @@ func (c *Container) cleanup(ctx context.Context) error { lastError = errors.Wrapf(err, "error removing container %s network", c.ID()) } + // cleanup host entry if it is shared + if c.config.NetNsCtr != "" { + if hoststFile, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + if _, err := os.Stat(hoststFile); err == nil { + // we cannot use the dependency container lock due ABBA deadlocks + if lock, err := lockfile.GetLockfile(hoststFile); err == nil { + lock.Lock() + // make sure to ignore ENOENT error in case the netns container was cleanup before this one + if err := etchosts.Remove(hoststFile, getLocalhostHostEntry(c)); err != nil && !errors.Is(err, os.ErrNotExist) { + // this error is not fatal we still want to do proper cleanup + logrus.Errorf("failed to remove hosts entry from the netns containers /etc/hosts: %v", err) + } + lock.Unlock() + } + } + } + } + // Remove the container from the runtime, if necessary. // Do this *before* unmounting storage - some runtimes (e.g. Kata) // apparently object to having storage removed while the container still @@ -2030,33 +2035,6 @@ func (c *Container) writeStringToStaticDir(filename, contents string) (string, e return destFileName, nil } -// appendStringToRunDir appends the provided string to the runtimedir file -func (c *Container) appendStringToRunDir(destFile, output string) (string, error) { - destFileName := filepath.Join(c.state.RunDir, destFile) - - f, err := os.OpenFile(destFileName, os.O_APPEND|os.O_RDWR, 0600) - if err != nil { - return "", err - } - defer f.Close() - - compareStr := strings.TrimRight(output, "\n") - scanner := bufio.NewScanner(f) - scanner.Split(bufio.ScanLines) - - for scanner.Scan() { - if strings.Compare(scanner.Text(), compareStr) == 0 { - return filepath.Join(c.state.RunDir, destFile), nil - } - } - - if _, err := f.WriteString(output); err != nil { - return "", errors.Wrapf(err, "unable to write %s", destFileName) - } - - return filepath.Join(c.state.RunDir, destFile), nil -} - // saveSpec saves the OCI spec to disk, replacing any existing specs for the container func (c *Container) saveSpec(spec *spec.Spec) error { // If the OCI spec already exists, we need to replace it diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index f8d0a124c..9d0c8ff8c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -28,6 +28,7 @@ import ( "github.com/containers/buildah/pkg/chrootuser" "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/cgroups" @@ -49,6 +50,7 @@ import ( "github.com/containers/podman/v4/version" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/lockfile" securejoin "github.com/cyphar/filepath-securejoin" runcuser "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -399,6 +401,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + //nolint:staticcheck g := generate.NewFromSpec(c.config.Spec) // If network namespace was requested, add it now @@ -1520,6 +1525,10 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO func (c *Container) generateContainerSpec() error { // Make sure the newly created config.json exists on disk + + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + //nolint:staticcheck g := generate.NewFromSpec(c.config.Spec) if err := c.saveSpec(g.Config); err != nil { @@ -2072,23 +2081,25 @@ func (c *Container) makeBindMounts() error { // check if dependency container has an /etc/hosts file. // It may not have one, so only use it if it does. - hostsPath, exists := bindMounts["/etc/hosts"] + hostsPath, exists := bindMounts[config.DefaultHostsFile] if !c.config.UseImageHosts && exists { - depCtr.lock.Lock() - // generate a hosts file for the dependency container, - // based on either its old hosts file, or the default, - // and add the relevant information from the new container (hosts and IP) - hostsPath, err = depCtr.appendHosts(hostsPath, c) + // we cannot use the dependency container lock due ABBA deadlocks in cleanup() + lock, err := lockfile.GetLockfile(hostsPath) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + lock.Lock() + // add the newly added container to the hosts file + // we always use 127.0.0.1 as ip since they have the same netns + err = etchosts.Add(hostsPath, getLocalhostHostEntry(c)) + lock.Unlock() if err != nil { - depCtr.lock.Unlock() return errors.Wrapf(err, "error creating hosts file for container %s which depends on container %s", c.ID(), depCtr.ID()) } - depCtr.lock.Unlock() // finally, save it in the new container - err := c.mountIntoRootDirs("/etc/hosts", hostsPath) - + err = c.mountIntoRootDirs(config.DefaultHostsFile, hostsPath) if err != nil { return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) } @@ -2116,7 +2127,7 @@ func (c *Container) makeBindMounts() error { } if !c.config.UseImageHosts { - if err := c.updateHosts("/etc/hosts"); err != nil { + if err := c.createHosts(); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } } @@ -2135,7 +2146,7 @@ func (c *Container) makeBindMounts() error { } } else { if !c.config.UseImageHosts && c.state.BindMounts["/etc/hosts"] == "" { - if err := c.updateHosts("/etc/hosts"); err != nil { + if err := c.createHosts(); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } } @@ -2521,169 +2532,79 @@ func (c *Container) removeNameserver(ips []string) error { return nil } -// updateHosts updates the container's hosts file -func (c *Container) updateHosts(path string) error { - var hosts string - - orig, err := ioutil.ReadFile(path) - if err != nil { - // Ignore if the path does not exist - if !os.IsNotExist(err) { - return err - } - } else { - hosts = string(orig) - } - - hosts += c.getHosts() - hosts = c.appendLocalhost(hosts) - - newHosts, err := c.writeStringToRundir("hosts", hosts) - if err != nil { - return err - } - - if err = c.mountIntoRootDirs("/etc/hosts", newHosts); err != nil { - return err - } - - return nil -} - -// based on networking mode we may want to append the localhost -// if there isn't any record for it and also this should happen -// in slirp4netns and similar network modes. -func (c *Container) appendLocalhost(hosts string) string { - if !strings.Contains(hosts, "localhost") && - !c.config.NetMode.IsHost() { - hosts += "127.0.0.1\tlocalhost\n::1\tlocalhost\n" - } - - return hosts +func getLocalhostHostEntry(c *Container) etchosts.HostEntries { + return etchosts.HostEntries{{IP: "127.0.0.1", Names: []string{c.Hostname(), c.config.Name}}} } -// appendHosts appends a container's config and state pertaining to hosts to a container's -// local hosts file. netCtr is the container from which the netNS information is -// taken. -// path is the basis of the hosts file, into which netCtr's netNS information will be appended. -// FIXME. Path should be used by this function,but I am not sure what is correct; remove //lint -// once this is fixed -func (c *Container) appendHosts(path string, netCtr *Container) (string, error) { //nolint - return c.appendStringToRunDir("hosts", netCtr.getHosts()) -} - -// getHosts finds the pertinent information for a container's host file in its config and state -// and returns a string in a format that can be written to the host file -func (c *Container) getHosts() string { - var hosts string - - if len(c.config.HostAdd) > 0 { - for _, host := range c.config.HostAdd { - // the host format has already been verified at this point - fields := strings.SplitN(host, ":", 2) - hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) - } - } - - hosts += c.cniHosts() - - // Add hostname for slirp4netns - if c.Hostname() != "" { - if c.config.NetMode.IsSlirp4netns() { - // When using slirp4netns, the interface gets a static IP - slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) - if err != nil { - logrus.Warnf("Failed to determine slirp4netnsIP: %v", err.Error()) - } else { - hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name) - } +// getHostsEntries returns the container ip host entries for the correct netmode +func (c *Container) getHostsEntries() (etchosts.HostEntries, error) { + var entries etchosts.HostEntries + names := []string{c.Hostname(), c.config.Name} + switch { + case c.config.NetMode.IsBridge(): + entries = etchosts.GetNetworkHostEntries(c.state.NetworkStatus, names...) + case c.config.NetMode.IsSlirp4netns(): + ip, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) + if err != nil { + return nil, err } - - // Do we have a network namespace? - netNone := false - if c.config.NetNsCtr == "" && !c.config.CreateNetNS { + entries = etchosts.HostEntries{{IP: ip.String(), Names: names}} + default: + // check for net=none + if !c.config.CreateNetNS { for _, ns := range c.config.Spec.Linux.Namespaces { if ns.Type == spec.NetworkNamespace { if ns.Path == "" { - netNone = true + entries = etchosts.HostEntries{{IP: "127.0.0.1", Names: names}} } break } } } - // If we are net=none (have a network namespace, but not connected to - // anything) add the container's name and hostname to localhost. - if netNone { - hosts += fmt.Sprintf("127.0.0.1 %s %s\n", c.Hostname(), c.config.Name) - } } + return entries, nil +} - // Add gateway entry if we are not in a machine. If we use podman machine - // the gvproxy dns server will take care of host.containers.internal. - // https://github.com/containers/gvisor-tap-vsock/commit/1108ea45162281046d239047a6db9bc187e64b08 - if !c.runtime.config.Engine.MachineEnabled { - var depCtr *Container - netStatus := c.getNetworkStatus() - if c.config.NetNsCtr != "" { - // ignoring the error because there isn't anything to do - depCtr, _ = c.getRootNetNsDepCtr() - } else if len(netStatus) != 0 { - depCtr = c +func (c *Container) createHosts() error { + var containerIPsEntries etchosts.HostEntries + var err error + // if we configure the netns after the container create we should not add + // the hosts here since we have no information about the actual ips + // instead we will add them in c.completeNetworkSetup() + if !c.config.PostConfigureNetNS { + containerIPsEntries, err = c.getHostsEntries() + if err != nil { + return fmt.Errorf("failed to get container ip host entries: %w", err) } + } + baseHostFile, err := etchosts.GetBaseHostFile(c.runtime.config.Containers.BaseHostsFile, c.state.Mountpoint) + if err != nil { + return err + } - // getLocalIP returns the non loopback local IP of the host - getLocalIP := func() string { - addrs, err := net.InterfaceAddrs() - if err != nil { - return "" - } - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String() - } - } - } - return "" - } + targetFile := filepath.Join(c.state.RunDir, "hosts") + err = etchosts.New(&etchosts.Params{ + BaseFile: baseHostFile, + ExtraHosts: c.config.HostAdd, + ContainerIPs: containerIPsEntries, + HostContainersInternalIP: etchosts.GetHostContainersInternalIP(c.runtime.config, c.state.NetworkStatus, c.runtime.network), + TargetFile: targetFile, + }) + if err != nil { + return err + } - if depCtr != nil { - host := "" - outer: - for net, status := range depCtr.getNetworkStatus() { - network, err := c.runtime.network.NetworkInspect(net) - // only add the host entry for bridge networks - // ip/macvlan gateway is normally not on the host - if err != nil || network.Driver != types.BridgeNetworkDriver { - continue - } - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - if netAddress.Gateway != nil { - host = fmt.Sprintf("%s host.containers.internal\n", netAddress.Gateway.String()) - break outer - } - } - } - } - // if no bridge gw was found try to use a local ip - if host == "" { - if ip := getLocalIP(); ip != "" { - host = fmt.Sprintf("%s\t%s\n", ip, "host.containers.internal") - } - } - hosts += host - } else if c.config.NetMode.IsSlirp4netns() { - if ip := getLocalIP(); ip != "" { - hosts += fmt.Sprintf("%s\t%s\n", ip, "host.containers.internal") - } - } else { - logrus.Debug("Network configuration does not support host.containers.internal address") - } + if err := os.Chown(targetFile, c.RootUID(), c.RootGID()); err != nil { + return err + } + if err := label.Relabel(targetFile, c.MountLabel(), false); err != nil { + return err } - return hosts + if err = c.mountIntoRootDirs(config.DefaultHostsFile, targetFile); err != nil { + return err + } + return nil } // generateGroupEntry generates an entry or entries into /etc/group as diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go index a7dd0fc31..2c1193445 100644 --- a/libpod/container_internal_linux_test.go +++ b/libpod/container_internal_linux_test.go @@ -8,7 +8,6 @@ import ( "os" "testing" - "github.com/containers/podman/v4/pkg/namespaces" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/assert" ) @@ -70,30 +69,3 @@ func TestGenerateUserGroupEntry(t *testing.T) { } assert.Equal(t, group, "567:x:567:567\n") } - -func TestAppendLocalhost(t *testing.T) { - { - c := Container{ - config: &ContainerConfig{ - ContainerNetworkConfig: ContainerNetworkConfig{ - NetMode: namespaces.NetworkMode("slirp4netns"), - }, - }, - } - - assert.Equal(t, "127.0.0.1\tlocalhost\n::1\tlocalhost\n", c.appendLocalhost("")) - assert.Equal(t, "127.0.0.1\tlocalhost", c.appendLocalhost("127.0.0.1\tlocalhost")) - } - { - c := Container{ - config: &ContainerConfig{ - ContainerNetworkConfig: ContainerNetworkConfig{ - NetMode: namespaces.NetworkMode("host"), - }, - }, - } - - assert.Equal(t, "", c.appendLocalhost("")) - assert.Equal(t, "127.0.0.1\tlocalhost", c.appendLocalhost("127.0.0.1\tlocalhost")) - } -} diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go index 51def1927..45b3a0e41 100644 --- a/libpod/healthcheck_linux.go +++ b/libpod/healthcheck_linux.go @@ -56,7 +56,7 @@ func (c *Container) startTimer() error { return errors.Wrapf(err, "unable to get systemd connection to start healthchecks") } defer conn.Close() - _, err = conn.StartUnit(fmt.Sprintf("%s.service", c.ID()), "fail", nil) + _, err = conn.StartUnitContext(context.Background(), fmt.Sprintf("%s.service", c.ID()), "fail", nil) return err } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 71e29f18f..41beaf41d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -20,7 +20,9 @@ import ( "time" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/libnetwork/types" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/netns" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" @@ -1282,15 +1284,35 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro for _, ip := range oldStatus.DNSServerIPs { stringIPs = append(stringIPs, ip.String()) } - if len(stringIPs) == 0 { - return nil + if len(stringIPs) > 0 { + logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) + if err := c.removeNameserver(stringIPs); err != nil { + return err + } } - logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) - if err := c.removeNameserver(stringIPs); err != nil { - return err + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // sync the names with c.getHostsEntries() + names := []string{c.Hostname(), c.config.Name} + rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...) + if len(rm) > 0 { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockfile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + logrus.Debugf("Remove /etc/hosts entries %v", rm) + lock.Lock() + err = etchosts.Remove(file, rm) + lock.Unlock() + if err != nil { + return err + } + } } } - return nil } @@ -1361,6 +1383,13 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe return errors.New("when adding aliases, results must be of length 1") } + // we need to get the old host entries before we add the new one to the status + // if we do not add do it here we will get the wrong existing entries which will throw of the logic + // we could also copy the map but this does not seem worth it + // sync the hostNames with c.getHostsEntries() + hostNames := []string{c.Hostname(), c.config.Name} + oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...) + // update network status if networkStatus == nil { networkStatus = make(map[string]types.StatusBlock, 1) @@ -1394,12 +1423,31 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe } stringIPs = append(stringIPs, ip.String()) } - if len(stringIPs) == 0 { - return nil + if len(stringIPs) > 0 { + logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) + if err := c.addNameserver(stringIPs); err != nil { + return err + } } - logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) - if err := c.addNameserver(stringIPs); err != nil { - return err + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockfile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + new := etchosts.GetNetworkHostEntries(results, hostNames...) + logrus.Debugf("Add /etc/hosts entries %v", new) + // use special AddIfExists API to make sure we only add new entries if an old one exists + // see the AddIfExists() comment for more information + lock.Lock() + err = etchosts.AddIfExists(file, oldHostEntries, new) + lock.Unlock() + if err != nil { + return err + } } return nil diff --git a/libpod/options.go b/libpod/options.go index ffd0e6037..4e597201a 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -936,6 +936,9 @@ func WithUserNSFrom(nsCtr *Container) CtrCreateOption { if err := JSONDeepCopy(nsCtr.IDMappings(), &ctr.config.IDMappings); err != nil { return err } + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + //nolint:staticcheck g := generate.NewFromSpec(ctr.config.Spec) g.ClearLinuxUIDMappings() diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 7edd49fd1..a62c2b607 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -397,6 +397,10 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai if ctr.restoreFromCheckpoint { // Remove information about bind mount // for new container from imported checkpoint + + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + //nolint:staticcheck g := generate.NewFromSpec(ctr.config.Spec) g.RemoveMount("/dev/shm") ctr.config.ShmDir = "" diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index b823a56b6..15d4b9f89 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -100,10 +100,10 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { // gather all images for manifest list var images []string if len(query.Images) > 0 { - images = append(query.Images) + images = query.Images } if len(body.Images) > 0 { - images = append(body.Images) + images = body.Images } id, err := imageEngine.ManifestAdd(r.Context(), query.Name, images, body.ManifestAddOptions) @@ -153,10 +153,10 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, schema2List) } -// ManifestAdd remove digest from manifest list +// ManifestAddV3 remove digest from manifest list // -// Deprecated: As of 4.0.0 use ManifestModify instead -func ManifestAdd(w http.ResponseWriter, r *http.Request) { +// As of 4.0.0 use ManifestModify instead +func ManifestAddV3(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) // Wrapper to support 3.x with 4.x libpod @@ -206,10 +206,10 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID}) } -// ManifestRemoveDigest remove digest from manifest list +// ManifestRemoveDigestV3 remove digest from manifest list // -// Deprecated: As of 4.0.0 use ManifestModify instead -func ManifestRemoveDigest(w http.ResponseWriter, r *http.Request) { +// As of 4.0.0 use ManifestModify instead +func ManifestRemoveDigestV3(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { @@ -242,7 +242,7 @@ func ManifestRemoveDigest(w http.ResponseWriter, r *http.Request) { // ManifestPushV3 push image to registry // -// Deprecated: As of 4.0.0 use ManifestPush instead +// As of 4.0.0 use ManifestPush instead func ManifestPushV3(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go index 58def109e..50a49bc1e 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -167,7 +167,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // $ref: "#/responses/BadParamError" // 500: // $ref: "#/responses/InternalError" - v3.Handle("/{name:.*}/add", s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost) + v3.Handle("/{name:.*}/add", s.APIHandler(libpod.ManifestAddV3)).Methods(http.MethodPost) // swagger:operation DELETE /libpod/manifests/{name} manifests ManifestDeleteV3Libpod // --- // summary: Remove image from a manifest list @@ -197,7 +197,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchManifest" // 500: // $ref: "#/responses/InternalError" - v3.Handle("/{name:.*}", s.APIHandler(libpod.ManifestRemoveDigest)).Methods(http.MethodDelete) + v3.Handle("/{name:.*}", s.APIHandler(libpod.ManifestRemoveDigestV3)).Methods(http.MethodDelete) // swagger:operation DELETE /libpod/manifests/{name} manifests ManifestDeleteLibpod // --- // summary: Delete manifest list diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 80702ea98..d84b47052 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -20,7 +20,7 @@ import ( "github.com/moby/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh/terminal" + terminal "golang.org/x/term" ) // The CloseWriter interface is used to determine whether we can do a one-sided diff --git a/pkg/bindings/containers/term_unix.go b/pkg/bindings/containers/term_unix.go index 2c976393f..e14f50813 100644 --- a/pkg/bindings/containers/term_unix.go +++ b/pkg/bindings/containers/term_unix.go @@ -9,11 +9,11 @@ import ( "os/signal" sig "github.com/containers/podman/v4/pkg/signal" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) -func makeRawTerm(stdin *os.File) (*terminal.State, error) { - return terminal.MakeRaw(int(stdin.Fd())) +func makeRawTerm(stdin *os.File) (*term.State, error) { + return term.MakeRaw(int(stdin.Fd())) } func notifyWinChange(ctx context.Context, winChange chan os.Signal, stdin *os.File, stdout *os.File) { @@ -21,5 +21,5 @@ func notifyWinChange(ctx context.Context, winChange chan os.Signal, stdin *os.Fi } func getTermSize(stdin *os.File, stdout *os.File) (width, height int, err error) { - return terminal.GetSize(int(stdin.Fd())) + return term.GetSize(int(stdin.Fd())) } diff --git a/pkg/bindings/containers/term_windows.go b/pkg/bindings/containers/term_windows.go index 11d4bd50d..e710e2998 100644 --- a/pkg/bindings/containers/term_windows.go +++ b/pkg/bindings/containers/term_windows.go @@ -6,12 +6,12 @@ import ( "time" sig "github.com/containers/podman/v4/pkg/signal" - "golang.org/x/crypto/ssh/terminal" "golang.org/x/sys/windows" + "golang.org/x/term" ) -func makeRawTerm(stdin *os.File) (*terminal.State, error) { - state, err := terminal.MakeRaw(int(stdin.Fd())) +func makeRawTerm(stdin *os.File) (*term.State, error) { + state, err := term.MakeRaw(int(stdin.Fd())) if err != nil { return nil, err } @@ -51,7 +51,7 @@ func notifyWinChange(ctx context.Context, winChange chan os.Signal, stdin *os.Fi break } - w, h, err := terminal.GetSize(int(stdout.Fd())) + w, h, err := term.GetSize(int(stdout.Fd())) if err != nil { continue } @@ -65,5 +65,5 @@ func notifyWinChange(ctx context.Context, winChange chan os.Signal, stdin *os.Fi } func getTermSize(stdin *os.File, stdout *os.File) (width, height int, err error) { - return terminal.GetSize(int(stdout.Fd())) + return term.GetSize(int(stdout.Fd())) } diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index d667a2dee..a005be6ac 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -103,7 +103,7 @@ var _ = Describe("Podman images", func() { Expect(len(errs)).To(BeZero()) Expect(inspectData.ID).To(Equal(response.Deleted[0])) - inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil) + _, err = images.GetImage(bt.conn, busybox.shortName, nil) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) @@ -118,7 +118,7 @@ var _ = Describe("Podman images", func() { // try to remove the image "alpine". This should fail since we are not force // deleting hence image cannot be deleted until the container is deleted. - response, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil) + _, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil) code, _ = bindings.CheckResponseCode(errs[0]) // FIXME FIXME FIXME: #12441: another invalid error // FIXME FIXME FIXME: this time msg="Image used by SHA: ..." @@ -126,7 +126,7 @@ var _ = Describe("Podman images", func() { // Removing the image "alpine" where force = true options := new(images.RemoveOptions).WithForce(true) - response, errs = images.Remove(bt.conn, []string{alpine.shortName}, options) + _, errs = images.Remove(bt.conn, []string{alpine.shortName}, options) Expect(errs).To(Or(HaveLen(0), BeNil())) // To be extra sure, check if the previously created container // is gone as well. @@ -135,11 +135,11 @@ var _ = Describe("Podman images", func() { Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Now make sure both images are gone. - inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil) + _, err = images.GetImage(bt.conn, busybox.shortName, nil) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) - inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil) + _, err = images.GetImage(bt.conn, alpine.shortName, nil) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) }) @@ -230,8 +230,8 @@ var _ = Describe("Podman images", func() { Expect(err).ToNot(HaveOccurred()) Expect(exists).To(BeFalse()) f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) - defer f.Close() Expect(err).ToNot(HaveOccurred()) + defer f.Close() names, err := images.Load(bt.conn, f) Expect(err).ToNot(HaveOccurred()) Expect(names.Names[0]).To(Equal(alpine.name)) @@ -252,8 +252,6 @@ var _ = Describe("Podman images", func() { Expect(names.Names[0]).To(Equal(alpine.name)) // load with a bad repo name should trigger a 500 - f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) - Expect(err).ToNot(HaveOccurred()) _, errs = images.Remove(bt.conn, []string{alpine.name}, nil) Expect(len(errs)).To(BeZero()) exists, err = images.Exists(bt.conn, alpine.name, nil) @@ -265,8 +263,8 @@ var _ = Describe("Podman images", func() { // Export an image exportPath := filepath.Join(bt.tempDirPath, alpine.tarballName) w, err := os.Create(filepath.Join(bt.tempDirPath, alpine.tarballName)) - defer w.Close() Expect(err).ToNot(HaveOccurred()) + defer w.Close() err = images.Export(bt.conn, []string{alpine.name}, w, nil) Expect(err).ToNot(HaveOccurred()) _, err = os.Stat(exportPath) @@ -283,8 +281,8 @@ var _ = Describe("Podman images", func() { Expect(err).ToNot(HaveOccurred()) Expect(exists).To(BeFalse()) f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) - defer f.Close() Expect(err).ToNot(HaveOccurred()) + defer f.Close() changes := []string{"CMD /bin/foobar"} testMessage := "test_import" options := new(images.ImportOptions).WithMessage(testMessage).WithChanges(changes).WithReference(alpine.name) diff --git a/pkg/bindings/test/secrets_test.go b/pkg/bindings/test/secrets_test.go index 377f49b2f..b4595f025 100644 --- a/pkg/bindings/test/secrets_test.go +++ b/pkg/bindings/test/secrets_test.go @@ -64,7 +64,7 @@ var _ = Describe("Podman secrets", func() { Expect(data.Spec.Name).To(Equal(name)) // inspecting non-existent secret should fail - data, err = secrets.Inspect(connText, "notasecret", nil) + _, err = secrets.Inspect(connText, "notasecret", nil) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) }) diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 153b19fdb..62d36f28d 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -10,13 +10,13 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) // ExecAttachCtr execs and attaches to a container func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (int, error) { var resize chan define.TerminalSize - haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + haveTerminal := term.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize // events, and set the terminal to raw mode @@ -42,7 +42,7 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { //nolint: interfacer resize := make(chan define.TerminalSize) - haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + haveTerminal := term.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize // events, and set the terminal to raw mode diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index dffd90dbe..5fdc252e2 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -276,46 +276,47 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin if len(subUIDMap) > 0 || len(subGIDMap) > 0 { return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id") } - if rootless.IsRootless() { - min := func(a, b int) int { - if a < b { - return a - } - return b + if !rootless.IsRootless() { + return nil, errors.New("keep-id is only supported in rootless mode") + } + min := func(a, b int) int { + if a < b { + return a } + return b + } - uid := rootless.GetRootlessUID() - gid := rootless.GetRootlessGID() - - uids, gids, err := rootless.GetConfiguredMappings() - if err != nil { - return nil, errors.Wrapf(err, "cannot read mappings") - } - maxUID, maxGID := 0, 0 - for _, u := range uids { - maxUID += u.Size - } - for _, g := range gids { - maxGID += g.Size - } + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() - options.UIDMap, options.GIDMap = nil, nil + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, errors.Wrapf(err, "cannot read mappings") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) - if maxUID > uid { - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) - } + options.UIDMap, options.GIDMap = nil, nil - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) - if maxGID > gid { - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) - } + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } - options.HostUIDMapping = false - options.HostGIDMapping = false + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) } + + options.HostUIDMapping = false + options.HostGIDMapping = false // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op return &options, nil } diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 4f44e7e4a..2dbdfcf80 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -42,14 +42,14 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt return reports, nil } -func (ic *ContainerEngine) PodLogs(_ context.Context, nameOrIDs string, options entities.PodLogsOptions) error { +func (ic *ContainerEngine) PodLogs(ctx context.Context, nameOrIDs string, options entities.PodLogsOptions) error { // PodLogsOptions are similar but contains few extra fields like ctrName // So cast other values as is so we can re-use the code containerLogsOpts := entities.PodLogsOptionsToContainerLogsOptions(options) // interface only accepts slice, keep everything consistent name := []string{options.ContainerName} - return ic.ContainerLogs(nil, name, containerLogsOpts) + return ic.ContainerLogs(ctx, name, containerLogsOpts) } func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) { diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 6215ae08f..ad1be15c7 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -139,7 +139,7 @@ func getStreamURL(streamType string) url2.URL { // This should get Exported and stay put as it will apply to all fcos downloads // getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version -func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { +func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:staticcheck var ( fcosstable stream.Stream altMeta release.Release @@ -149,6 +149,8 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // This is being hard set to testing. Once podman4 is in the // fcos trees, we should remove it and re-release at least on // macs. + // TODO: remove when podman4.0 is in coreos + // nolint:staticcheck imageStream = "podman-testing" switch imageStream { diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index a264a5a0f..bdea7c310 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -96,6 +96,11 @@ func (n UsernsMode) IsKeepID() bool { return n == "keep-id" } +// IsNoMap indicates whether container uses a mapping where the (uid, gid) on the host is not present in the namespace. +func (n UsernsMode) IsNoMap() bool { + return n == "nomap" +} + // IsAuto indicates whether container uses the "auto" userns mode. func (n UsernsMode) IsAuto() bool { parts := strings.Split(string(n), ":") @@ -158,7 +163,7 @@ func (n UsernsMode) IsPrivate() bool { func (n UsernsMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { - case "", privateType, hostType, "keep-id", nsType, "auto": + case "", privateType, hostType, "keep-id", nsType, "auto", "nomap": case containerType: if len(parts) != 2 || parts[1] == "" { return false diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 42b70e334..e06cd9a29 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -38,6 +38,13 @@ func (s *SpecGenerator) Validate() error { if len(s.PortMappings) > 0 || s.PublishExposedPorts { return errors.Wrap(define.ErrNetworkOnPodContainer, "published or exposed ports must be defined when the pod is created") } + if len(s.HostAdd) > 0 { + return errors.Wrap(define.ErrNetworkOnPodContainer, "extra host entries must be specified on the pod") + } + } + + if s.NetNS.IsContainer() && len(s.HostAdd) > 0 { + return errors.Wrap(ErrInvalidSpecConfig, "cannot set extra host entries when the container is joined to another containers network namespace") } // diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index df751a780..51f9fa535 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -82,7 +82,7 @@ func ToPodOpt(ctx context.Context, podName string, p entities.PodCreateOptions, } // dns options if options := dnsConfig.Options; len(options) > 0 { - dnsOptions := make([]string, 0) + dnsOptions := make([]string, 0, len(options)) for _, opts := range options { d := opts.Name if opts.Value != nil { @@ -90,6 +90,7 @@ func ToPodOpt(ctx context.Context, podName string, p entities.PodCreateOptions, } dnsOptions = append(dnsOptions, d) } + p.Net.DNSOptions = dnsOptions } } return p, nil @@ -281,9 +282,6 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener annotations = opts.Annotations } if opts.PodInfraID != "" { - if annotations == nil { - - } annotations[ann.SandboxID] = opts.PodInfraID annotations[ann.ContainerType] = ann.ContainerTypeContainer } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 05c2d1741..d8d1ae652 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -165,21 +165,19 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. // User switch s.UserNS.NSMode { case specgen.KeepID: - if rootless.IsRootless() { - toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) - - // If user is not overridden, set user in the container - // to user running Podman. - if s.User == "" { - _, uid, gid, err := util.GetKeepIDMapping() - if err != nil { - return nil, err - } - toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) + if !rootless.IsRootless() { + return nil, errors.New("keep-id is only supported in rootless mode") + } + toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) + + // If user is not overridden, set user in the container + // to user running Podman. + if s.User == "" { + _, uid, gid, err := util.GetKeepIDMapping() + if err != nil { + return nil, err } - } else { - // keep-id as root doesn't need a user namespace - s.UserNS.NSMode = specgen.Host + toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) } case specgen.FromPod: if pod == nil || infraCtr == nil { diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 4412eff29..eaf2daad9 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -55,6 +55,10 @@ const ( // of the namespace itself. // Only used with the user namespace, invalid otherwise. KeepID NamespaceMode = "keep-id" + // NoMap indicates a user namespace to keep the owner uid out + // of the namespace itself. + // Only used with the user namespace, invalid otherwise. + NoMap NamespaceMode = "no-map" // Auto indicates to automatically create a user namespace. // Only used with the user namespace, invalid otherwise. Auto NamespaceMode = "auto" @@ -121,6 +125,11 @@ func (n *Namespace) IsKeepID() bool { return n.NSMode == KeepID } +// IsNoMap indicates the namespace is NoMap +func (n *Namespace) IsNoMap() bool { + return n.NSMode == NoMap +} + func (n *Namespace) String() string { if n.Value != "" { return fmt.Sprintf("%s:%s", n.NSMode, n.Value) @@ -133,7 +142,7 @@ func validateUserNS(n *Namespace) error { return nil } switch n.NSMode { - case Auto, KeepID: + case Auto, KeepID, NoMap: return nil } return n.validate() @@ -299,6 +308,9 @@ func ParseUserNamespace(ns string) (Namespace, error) { case ns == "keep-id": toReturn.NSMode = KeepID return toReturn, nil + case ns == "nomap": + toReturn.NSMode = NoMap + return toReturn, nil case ns == "": toReturn.NSMode = Host return toReturn, nil @@ -548,20 +560,41 @@ func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *gene g.SetProcessUID(uint32(uid)) g.SetProcessGID(uint32(gid)) user = fmt.Sprintf("%d:%d", uid, gid) - fallthrough - case Private: - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + if err := privateUserNamespace(idmappings, g); err != nil { return user, err } - if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { - return user, errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") + case NoMap: + mappings, uid, gid, err := util.GetNoMapMapping() + if err != nil { + return user, err } - for _, uidmap := range idmappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + idmappings = mappings + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + user = fmt.Sprintf("%d:%d", uid, gid) + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err } - for _, gidmap := range idmappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + case Private: + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err } } return user, nil } + +func privateUserNamespace(idmappings *storage.IDMappingOptions, g *generate.Generator) error { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { + return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") + } + for _, uidmap := range idmappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range idmappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + return nil +} diff --git a/pkg/systemd/dbus.go b/pkg/systemd/dbus.go index 44feb8308..b35f778ab 100644 --- a/pkg/systemd/dbus.go +++ b/pkg/systemd/dbus.go @@ -1,6 +1,7 @@ package systemd import ( + "context" "fmt" "os" "path/filepath" @@ -140,5 +141,5 @@ func ConnectToDBUS() (*dbus.Conn, error) { if rootless.IsRootless() { return newRootlessConnection() } - return dbus.NewSystemdConnection() + return dbus.NewSystemdConnectionContext(context.Background()) } diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go index 04e12f6b3..0f0968c30 100644 --- a/pkg/terminal/util.go +++ b/pkg/terminal/util.go @@ -14,7 +14,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var ( @@ -29,9 +29,9 @@ var ( // Additionally, all input after `<secret>/n` is queued to podman command. func ReadPassword(prompt string) (pw []byte, err error) { fd := int(os.Stdin.Fd()) - if terminal.IsTerminal(fd) { + if term.IsTerminal(fd) { fmt.Fprint(os.Stderr, prompt) - pw, err = terminal.ReadPassword(fd) + pw, err = term.ReadPassword(fd) fmt.Fprintln(os.Stderr) return } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index b89978601..9842a0f73 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -28,7 +28,7 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) var containerConfig *config.Config @@ -65,7 +65,7 @@ func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) { } if password == "" { fmt.Print("Password: ") - termPassword, err := terminal.ReadPassword(0) + termPassword, err := term.ReadPassword(0) if err != nil { return nil, errors.Wrapf(err, "could not read password from terminal") } @@ -347,55 +347,84 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) { // GetKeepIDMapping returns the mappings and the user to use when keep-id is used func GetKeepIDMapping() (*stypes.IDMappingOptions, int, int, error) { + if !rootless.IsRootless() { + return nil, -1, -1, errors.New("keep-id is only supported in rootless mode") + } options := stypes.IDMappingOptions{ - HostUIDMapping: true, - HostGIDMapping: true, + HostUIDMapping: false, + HostGIDMapping: false, } - uid, gid := 0, 0 - if rootless.IsRootless() { - min := func(a, b int) int { - if a < b { - return a - } - return b + min := func(a, b int) int { + if a < b { + return a } + return b + } - uid = rootless.GetRootlessUID() - gid = rootless.GetRootlessGID() + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() - uids, gids, err := rootless.GetConfiguredMappings() - if err != nil { - return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") - } - maxUID, maxGID := 0, 0 - for _, u := range uids { - maxUID += u.Size - } - for _, g := range gids { - maxGID += g.Size - } - - options.UIDMap, options.GIDMap = nil, nil + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") + } + if len(uids) == 0 || len(gids) == 0 { + return nil, -1, -1, errors.Wrapf(err, "keep-id requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) - if maxUID > uid { - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) - } + options.UIDMap, options.GIDMap = nil, nil - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) - if maxGID > gid { - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) - } + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } - options.HostUIDMapping = false - options.HostGIDMapping = false + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) } - // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + return &options, uid, gid, nil } +// GetNoMapMapping returns the mappings and the user to use when nomap is used +func GetNoMapMapping() (*stypes.IDMappingOptions, int, int, error) { + if !rootless.IsRootless() { + return nil, -1, -1, errors.New("nomap is only supported in rootless mode") + } + options := stypes.IDMappingOptions{ + HostUIDMapping: false, + HostGIDMapping: false, + } + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") + } + if len(uids) == 0 || len(gids) == 0 { + return nil, -1, -1, errors.Wrapf(err, "nomap requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") + } + options.UIDMap, options.GIDMap = nil, nil + uid, gid := 0, 0 + for _, u := range uids { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: uid + 1, Size: u.Size}) + uid += u.Size + } + for _, g := range gids { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: gid + 1, Size: g.Size}) + gid += g.Size + } + return &options, 0, 0, nil +} + // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) { options := stypes.IDMappingOptions{ @@ -415,7 +444,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin options.AutoUserNsOpts = *opts return &options, nil } - if mode.IsKeepID() { + if mode.IsKeepID() || mode.IsNoMap() { options.HostUIDMapping = false options.HostGIDMapping = false return &options, nil diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 7b2dd89c9..2dc99bdc9 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -1061,7 +1061,7 @@ var _ = Describe("Podman checkpoint", func() { // Open a network connection to the redis server via initial port mapping // This should fail - conn, err = net.DialTimeout("tcp4", fmt.Sprintf("localhost:%d", randomPort), time.Duration(3)*time.Second) + _, err = net.DialTimeout("tcp4", fmt.Sprintf("localhost:%d", randomPort), time.Duration(3)*time.Second) Expect(err).ToNot(BeNil()) Expect(err.Error()).To(ContainSubstring("connection refused")) // Open a network connection to the redis server via new port mapping diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 2f4146bba..f808eb2b6 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -880,11 +880,11 @@ func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache boo func writeConf(conf []byte, confPath string) { if _, err := os.Stat(filepath.Dir(confPath)); os.IsNotExist(err) { - if err := os.MkdirAll(filepath.Dir(confPath), 777); err != nil { + if err := os.MkdirAll(filepath.Dir(confPath), 0o777); err != nil { fmt.Println(err) } } - if err := ioutil.WriteFile(confPath, conf, 777); err != nil { + if err := ioutil.WriteFile(confPath, conf, 0o777); err != nil { fmt.Println(err) } } diff --git a/test/e2e/login_logout_test.go b/test/e2e/login_logout_test.go index 77549a9a8..001779cdf 100644 --- a/test/e2e/login_logout_test.go +++ b/test/e2e/login_logout_test.go @@ -188,6 +188,8 @@ var _ = Describe("Podman login and logout", func() { Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"logout", "--authfile", authFile, server}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) }) It("podman login and logout with --tls-verify", func() { diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index db366b612..6373b949a 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -377,21 +377,19 @@ var _ = Describe("Podman pod create", func() { Expect(result.OutputToString()).To(ContainSubstring(infraID)) }) - It("podman run --add-host in pod", func() { - session := podmanTest.Podman([]string{"pod", "create"}) + It("podman run --add-host in pod should fail", func() { + session := podmanTest.Podman([]string{"pod", "create", "--add-host", "host1:127.0.0.1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) podID := session.OutputToString() - // verify we can add a host to the infra's /etc/hosts - // N/B: Using alpine for ping, since BB ping throws - // permission denied error as of Fedora 33. - session = podmanTest.Podman([]string{"run", "--pod", podID, "--add-host", "foobar:127.0.0.1", ALPINE, "ping", "-c", "1", "foobar"}) + session = podmanTest.Podman([]string{"create", "--pod", podID, "--add-host", "foobar:127.0.0.1", ALPINE, "ping", "-c", "1", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) + Expect(session).Should(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring("extra host entries must be specified on the pod: network cannot be configured when it is shared with a pod")) - // verify we can see the other hosts of infra's /etc/hosts - session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "foobar"}) + // verify we can see the pods hosts + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "host1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) }) diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index a8a98fc07..3b571ab20 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -220,6 +220,7 @@ var _ = Describe("Podman push", func() { if setup.LineInOutputContains("Active: inactive") { setup = SystemExec("systemctl", []string{"start", "docker"}) + Expect(setup).Should(Exit(0)) defer func() { stop := SystemExec("systemctl", []string{"stop", "docker"}) Expect(stop).Should(Exit(0)) diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index a313e8d40..7561a2e85 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -78,8 +78,7 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--add-host=foobar:1.1.1.1", "--add-host=foobaz:2001:db8::68", ALPINE, "cat", "/etc/hosts"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("1.1.1.1 foobar"))) - Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("2001:db8::68 foobaz"))) + Expect(session.OutputToStringArray()).To(ContainElements(HavePrefix("1.1.1.1\tfoobar"), HavePrefix("2001:db8::68\tfoobaz"))) }) It("podman run add hostname", func() { diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 696668e52..49c387227 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -608,6 +608,18 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(ctr2).Should(Exit(0)) }) + It("podman run --net container: and --add-host should fail", func() { + ctrName := "ctrToJoin" + ctr1 := podmanTest.RunTopContainer(ctrName) + ctr1.WaitWithDefaultTimeout() + Expect(ctr1).Should(Exit(0)) + + ctr2 := podmanTest.Podman([]string{"run", "-d", "--net=container:" + ctrName, "--add-host", "host1:127.0.0.1", ALPINE, "true"}) + ctr2.WaitWithDefaultTimeout() + Expect(ctr2).Should(ExitWithError()) + Expect(ctr2.ErrorToString()).Should(ContainSubstring("cannot set extra host entries when the container is joined to another containers network namespace: invalid configuration")) + }) + It("podman run --net container: copies hosts and resolv", func() { ctrName := "ctr1" ctr1 := podmanTest.RunTopContainer(ctrName) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index 5a046b0a4..092621c27 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -78,12 +78,18 @@ var _ = Describe("Podman UserNS support", func() { It("podman --userns=keep-id", func() { session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-u"}) session.WaitWithDefaultTimeout() + if os.Geteuid() == 0 { + Expect(session).Should(Exit(125)) + return + } + Expect(session).Should(Exit(0)) uid := fmt.Sprintf("%d", os.Geteuid()) Expect(session.OutputToString()).To(ContainSubstring(uid)) }) It("podman --userns=keep-id check passwd", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-un"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -93,6 +99,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman --userns=keep-id root owns /usr", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -100,6 +107,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman --userns=keep-id --user root:root", func() { + SkipIfNotRootless("keep-id only works in rootless mode") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "alpine", "id", "-u"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) @@ -107,10 +115,7 @@ var _ = Describe("Podman UserNS support", func() { }) It("podman run --userns=keep-id can add users", func() { - if os.Geteuid() == 0 { - Skip("Test only runs without root") - } - + SkipIfNotRootless("keep-id only works in rootless mode") userName := os.Getenv("USER") if userName == "" { Skip("Can't complete test if no username available") diff --git a/test/e2e/systemd_activate_test.go b/test/e2e/systemd_activate_test.go index d5434868d..04acafe1b 100644 --- a/test/e2e/systemd_activate_test.go +++ b/test/e2e/systemd_activate_test.go @@ -97,7 +97,7 @@ var _ = Describe("Systemd activate", func() { // Emulate 'systemd stop podman.service' activateSession.Signal(syscall.SIGTERM) - time.Sleep(2) + time.Sleep(100 * time.Millisecond) Eventually(activateSession).Should(Exit(0)) abiSession := podman("inspect", "--format={{.State.Running}}", containerName) diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go index b34fd299c..1fc28a06d 100644 --- a/test/e2e/toolbox_test.go +++ b/test/e2e/toolbox_test.go @@ -160,6 +160,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id --user root:root - entrypoint - entrypoint is executed as root", func() { + SkipIfNotRootless("only meaningful when run rootless") session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", ALPINE, "id"}) session.WaitWithDefaultTimeout() @@ -168,6 +169,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id + podman exec - correct names of user and group", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var err error @@ -199,6 +201,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id - entrypoint - adding user with useradd and then removing their password", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var username string = "testuser" @@ -238,6 +241,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var groupName string = "testgroup" @@ -268,6 +272,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create --userns=keep-id - entrypoint - modifying existing user with usermod - add to new group, change home/shell/uid", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration var badHomeDir string = "/home/badtestuser" var badShell string = "/bin/sh" @@ -315,6 +320,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman run --privileged --userns=keep-id --user root:root - entrypoint - (bind)mounting", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration session = podmanTest.Podman([]string{"run", "--privileged", "--userns=keep-id", "--user", "root:root", ALPINE, @@ -329,6 +335,7 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman create + start - with all needed switches for create - sleep as entry-point", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration // These should be most of the switches that Toolbox uses to create a "toolbox" container @@ -365,8 +372,8 @@ var _ = Describe("Toolbox-specific testing", func() { }) It("podman run --userns=keep-id check $HOME", func() { + SkipIfNotRootless("only meaningful when run rootless") var session *PodmanSessionIntegration - currentUser, err := user.Current() Expect(err).To(BeNil()) diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 526003c2d..283c3aea9 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -272,9 +272,11 @@ echo $rand | 0 | $rand # symptom only manifests on a fedora container image -- we have no # reproducer on alpine. Checking directory ownership is good enough. @test "podman run : user namespace preserved root ownership" { + keep="--userns=keep-id" + is_rootless || keep="" for priv in "" "--privileged"; do for user in "--user=0" "--user=100"; do - for keepid in "" "--userns=keep-id"; do + for keepid in "" ${keep}; do opts="$priv $user $keepid" for dir in /etc /usr;do @@ -289,6 +291,7 @@ echo $rand | 0 | $rand # #6829 : add username to /etc/passwd inside container if --userns=keep-id @test "podman run : add username to /etc/passwd if --userns=keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" # Default: always run as root run_podman run --rm $IMAGE id -un is "$output" "root" "id -un on regular container" @@ -339,6 +342,7 @@ echo $rand | 0 | $rand # #6991 : /etc/passwd is modifiable @test "podman run : --userns=keep-id: passwd file is modifiable" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" run_podman run -d --userns=keep-id --cap-add=dac_override $IMAGE sh -c 'while ! test -e /tmp/stop; do sleep 0.1; done' cid="$output" @@ -782,7 +786,7 @@ EOF mv $hosts_tmp /etc/hosts assert "$status" = 0 \ "podman run without /etc/hosts file should work" - assert "$output" =~ "^1\.2\.3\.4 foo.com.*" \ + assert "$output" =~ "^1\.2\.3\.4[[:blank:]]foo\.com.*" \ "users can add hosts even without /etc/hosts" } @@ -827,6 +831,9 @@ EOF # CVE-2022-1227 : podman top joins container mount NS and uses nsenter from image @test "podman top does not use nsenter from image" { + keepid="--userns=keep-id" + is_rootless || keepid="" + tmpdir=$PODMAN_TMPDIR/build-test mkdir -p $tmpdir tmpbuilddir=$tmpdir/build @@ -841,7 +848,7 @@ EOF test_image="cve_2022_1227_test" run_podman build -t $test_image $tmpbuilddir - run_podman run -d --userns=keep-id $test_image top + run_podman run -d ${keepid} $test_image top ctr="$output" run_podman top $ctr huser,user run_podman rm -f -t0 $ctr diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index fe9292fd0..cfbeff3ae 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -119,7 +119,9 @@ load helpers echo "content" > $srcdir/hostfile userid=$(id -u) - run_podman run --user=$userid --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity + keepid="--userns=keep-id" + is_rootless || keepid="" + run_podman run --user=$userid ${keepid} -d --name cpcontainer $IMAGE sleep infinity run_podman cp $srcdir/hostfile cpcontainer:/tmp/hostfile run_podman exec cpcontainer stat -c "%u" /tmp/hostfile is "$output" "$userid" "copied file is chowned to the container user" @@ -138,7 +140,9 @@ load helpers userid=$(id -u) - run_podman run --user="$userid" --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity + keepid="--userns=keep-id" + is_rootless || keepid="" + run_podman run --user=$userid ${keepid} -d --name cpcontainer $IMAGE sleep infinity run_podman cp -a=false - cpcontainer:/tmp/ < "${tmpdir}/a.tar" run_podman exec cpcontainer stat -c "%u:%g" /tmp/a.txt is "$output" "1042:1043" "copied file retains uid/gid from the tar" diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index 42954e5ec..0a6048b7e 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -87,6 +87,7 @@ load helpers # #6829 : add username to /etc/passwd inside container if --userns=keep-id @test "podman exec - with keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" # Multiple --userns options confirm command-line override (last one wins) run_podman run -d --userns=private --userns=keep-id $IMAGE sh -c \ "echo READY;while [ ! -f /tmp/stop ]; do sleep 1; done" diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 571d8767e..5b0460723 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -182,13 +182,14 @@ EOF run_podman volume rm $myvol - # Autocreated volumes should also work with keep-id - # All we do here is check status; podman 1.9.1 would fail with EPERM - myvol=myvol$(random_string) - run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ + if is_rootless; then + # Autocreated volumes should also work with keep-id + # All we do here is check status; podman 1.9.1 would fail with EPERM + myvol=myvol$(random_string) + run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ touch /myvol/myfile - - run_podman volume rm $myvol + run_podman volume rm $myvol + fi } diff --git a/test/system/170-run-userns.bats b/test/system/170-run-userns.bats index c020a73ab..d754306b2 100644 --- a/test/system/170-run-userns.bats +++ b/test/system/170-run-userns.bats @@ -94,3 +94,17 @@ EOF is ${output} ${secret_content} "Secrets should work with user namespace" run_podman secret rm ${test_name} } + +@test "podman userns=nomap" { + skip_if_not_rootless "--userns=nomap only works in rootless mode" + ns_user=$(id -un) + baseuid=$(egrep "${ns_user}:" /etc/subuid | cut -f2 -d:) + test ! -z ${baseuid} || skip "no IDs allocated for user ${ns_user}" + + test_name="test_$(random_string 12)" + run_podman run -d --userns=nomap $IMAGE sleep 100 + cid=${output} + run_podman top ${cid} huser + is "${output}" "HUSER.*${baseuid}" "Container should start with baseuid from /etc/subuid not user UID" + run_podman rm -t 0 --force ${cid} +} diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 56449dcad..ef4bf1a6c 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -250,7 +250,7 @@ EOF is "$output" ".*invalid config provided: cannot set hostname when joining the pod UTS namespace: invalid configuration" "--hostname should not be allowed in share UTS pod" run_podman run --rm --pod $pod_id $IMAGE cat /etc/hosts - is "$output" ".*$add_host_ip $add_host_n" "--add-host was added" + is "$output" ".*$add_host_ip[[:blank:]]$add_host_n" "--add-host was added" is "$output" ".* $hostname" "--hostname is in /etc/hosts" # ^^^^ this must be a tab, not a space diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 3bfc58a07..0c3062a7e 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -84,6 +84,7 @@ load helpers # Issue #5466 - port-forwarding doesn't work with this option and -d @test "podman networking: port with --userns=keep-id" { + skip_if_not_rootless "--userns=keep-id only works in rootless mode" for cidr in "" "$(random_rfc1918_subnet).0/24"; do myport=$(random_free_port 52000-52999) if [[ -z $cidr ]]; then @@ -133,14 +134,50 @@ load helpers done } +@test "podman pod manages /etc/hosts correctly" { + local pod_name=pod-$(random_string 10) + local infra_name=infra-$(random_string 10) + local con1_name=con1-$(random_string 10) + local con2_name=con2-$(random_string 10) + run_podman pod create --name $pod_name --infra-name $infra_name + pid="$output" + run_podman run --pod $pod_name --name $con1_name $IMAGE cat /etc/hosts + is "$output" ".*\s$pod_name $infra_name.*" "Pod hostname in /etc/hosts" + is "$output" ".*127.0.0.1\s$con1_name.*" "Container1 name in /etc/hosts" + # get the length of the hosts file + old_lines=${#lines[@]} + + # since the first container should be cleaned up now we should only see the + # new host entry and the old one should be removed (lines check) + run_podman run --pod $pod_name --name $con2_name $IMAGE cat /etc/hosts + is "$output" ".*\s$pod_name $infra_name.*" "Pod hostname in /etc/hosts" + is "$output" ".*127.0.0.1\s$con2_name.*" "Container2 name in /etc/hosts" + is "${#lines[@]}" "$old_lines" "Number of hosts lines is equal" + + run_podman run --pod $pod_name $IMAGE sh -c "hostname && cat /etc/hostname" + is "${lines[0]}" "$pod_name" "hostname is the pod hostname" + is "${lines[1]}" "$pod_name" "/etc/hostname contains correct pod hostname" + + run_podman pod rm $pod_name + is "$output" "$pid" "Only ID in output (no extra errors)" +} + @test "podman run with slirp4ns assigns correct addresses to /etc/hosts" { CIDR="$(random_rfc1918_subnet)" IP=$(hostname -I | cut -f 1 -d " ") local conname=con-$(random_string 10) run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \ --name $conname --hostname $conname $IMAGE cat /etc/hosts - is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be the cidr+2 address" + is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be host address" is "$output" ".*${CIDR}.100 $conname $conname" "$conname should be the cidr+100 address" + + if is_rootless; then + # check the slirp ip also works correct with userns + run_podman run --rm --userns keep-id --network slirp4netns:cidr="${CIDR}.0/24" \ + --name $conname --hostname $conname $IMAGE cat /etc/hosts + is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be host address" + is "$output" ".*${CIDR}.100 $conname $conname" "$conname should be the cidr+100 address" + fi } @test "podman run with slirp4ns adds correct dns address to resolv.conf" { @@ -433,9 +470,17 @@ load helpers run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").Aliases}}" is "$output" "[${cid:0:12}]" "short container id in network aliases" + # check /etc/hosts for our entry + run_podman exec $cid cat /etc/hosts + is "$output" ".*$ip.*" "hosts contain expected ip" + run_podman network disconnect $netname $cid is "$output" "" "Output should be empty (no errors)" + # check /etc/hosts again, the entry should be gone now + run_podman exec $cid cat /etc/hosts + assert "$output" !~ "$ip" "IP ($ip) should no longer be in /etc/hosts" + # check that we cannot curl (timeout after 3 sec) run curl --max-time 3 -s $SERVER/index.txt assert $status -ne 0 \ @@ -451,13 +496,18 @@ load helpers # check that we have a new ip and mac # if the ip is still the same this whole test turns into a nop run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" - assert "$output" != "$ip" \ + new_ip="$output" + assert "$new_ip" != "$ip" \ "IP address did not change after podman network disconnect/connect" run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" assert "$output" != "$mac" \ "MAC address did not change after podman network disconnect/connect" + # check /etc/hosts for the new entry + run_podman exec $cid cat /etc/hosts + is "$output" ".*$new_ip.*" "hosts contain expected new ip" + # Disconnect/reconnect of a container *with no ports* should succeed quietly run_podman network disconnect $netname $background_cid is "$output" "" "disconnect of container with no open ports" @@ -628,4 +678,48 @@ EOF done } +@test "podman run CONTAINERS_CONF /etc/hosts options" { + skip_if_remote "CONTAINERS_CONF redirect does not work on remote" + + containersconf=$PODMAN_TMPDIR/containers.conf + basehost=$PODMAN_TMPDIR/host + + ip1="$(random_rfc1918_subnet).$((RANDOM % 256))" + name1=host1$(random_string) + ip2="$(random_rfc1918_subnet).$((RANDOM % 256))" + name2=host2$(random_string) + + cat >$basehost <<EOF +$ip1 $name1 +$ip2 $name2 #some comment +EOF + + containersinternal_ip="$(random_rfc1918_subnet).$((RANDOM % 256))" + cat >$containersconf <<EOF +[containers] + base_hosts_file = "$basehost" + host_containers_internal_ip = "$containersinternal_ip" +EOF + + ip3="$(random_rfc1918_subnet).$((RANDOM % 256))" + name3=host3$(random_string) + + CONTAINERS_CONF=$containersconf run_podman run --rm --add-host $name3:$ip3 $IMAGE cat /etc/hosts + is "$output" ".*$ip3[[:blank:]]$name3.*" "--add-host entry in /etc/host" + is "$output" ".*$ip1[[:blank:]]$name1.*" "first base entry in /etc/host" + is "$output" ".*$ip2[[:blank:]]$name2.*" "second base entry in /etc/host" + is "$output" ".*127.0.0.1[[:blank:]]localhost.*" "ipv4 localhost entry added" + is "$output" ".*::1[[:blank:]]localhost.*" "ipv6 localhost entry added" + is "$output" ".*$containersinternal_ip[[:blank:]]host\.containers\.internal.*" "host.containers.internal ip from config in /etc/host" + is "${#lines[@]}" "7" "expect 7 host entries in /etc/hosts" + + # now try again with container name and hostname == host entry name + # in this case podman should not add its own entry thus we only have 5 entries (-1 for the removed --add-host) + CONTAINERS_CONF=$containersconf run_podman run --rm --name $name1 --hostname $name1 $IMAGE cat /etc/hosts + is "$output" ".*$ip1[[:blank:]]$name1.*" "first base entry in /etc/host" + is "$output" ".*$ip2[[:blank:]]$name2.*" "second base entry in /etc/host" + is "$output" ".*$containersinternal_ip[[:blank:]]host\.containers\.internal.*" "host.containers.internal ip from config in /etc/host" + is "${#lines[@]}" "5" "expect 5 host entries in /etc/hosts" +} + # vim: filetype=sh diff --git a/test/utils/common_function_test.go b/test/utils/common_function_test.go index 6323b44eb..a73d75490 100644 --- a/test/utils/common_function_test.go +++ b/test/utils/common_function_test.go @@ -110,9 +110,8 @@ var _ = Describe("Common functions test", func() { Expect(err).To(BeNil(), "Failed to write JSON to file.") read, err := os.Open("/tmp/testJSON") - defer read.Close() - Expect(err).To(BeNil(), "Can not find the JSON file after we write it.") + defer read.Close() bytes, _ := ioutil.ReadAll(read) json.Unmarshal(bytes, compareData) diff --git a/test/utils/utils.go b/test/utils/utils.go index 39a0ac875..9695835e5 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -439,10 +439,10 @@ func tagOutputToMap(imagesOutput []string) map[string]map[string]bool { // GetHostDistributionInfo returns a struct with its distribution Name and version func GetHostDistributionInfo() HostOS { f, err := os.Open(OSReleasePath) - defer f.Close() if err != nil { return HostOS{} } + defer f.Close() l := bufio.NewScanner(f) host := HostOS{} diff --git a/test/version/main.go b/test/version/main.go index 8f2904405..2a68aa5fc 100644 --- a/test/version/main.go +++ b/test/version/main.go @@ -7,5 +7,5 @@ import ( ) func main() { - fmt.Printf(version.Version.String()) + fmt.Print(version.Version.String()) } diff --git a/utils/utils_supported.go b/utils/utils_supported.go index ab2de2ce1..493ea61ce 100644 --- a/utils/utils_supported.go +++ b/utils/utils_supported.go @@ -6,6 +6,7 @@ package utils import ( "bufio" "bytes" + "context" "fmt" "io/ioutil" "os" @@ -32,7 +33,7 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error { return err } } else { - conn, err = systemdDbus.New() + conn, err = systemdDbus.NewWithContext(context.Background()) if err != nil { return err } @@ -43,10 +44,10 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error { properties = append(properties, newProp("Delegate", true)) properties = append(properties, newProp("DefaultDependencies", false)) ch := make(chan string) - _, err = conn.StartTransientUnit(unitName, "replace", properties, ch) + _, err = conn.StartTransientUnitContext(context.Background(), unitName, "replace", properties, ch) if err != nil { // On errors check if the cgroup already exists, if it does move the process there - if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil { + if props, err := conn.GetUnitTypePropertiesContext(context.Background(), unitName, "Scope"); err == nil { if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" { if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err == nil { return nil diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go index 59f01acb7..46fca2dac 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go @@ -34,6 +34,7 @@ var ( "0.1.0": {}, "0.2.0": {}, "0.3.0": {}, + "0.4.0": {}, } // Externally set CDI Spec validation function. diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go index 090e30e43..e16174f9d 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go @@ -3,7 +3,7 @@ package specs import "os" // CurrentVersion is the current version of the Spec. -const CurrentVersion = "0.3.0" +const CurrentVersion = "0.4.0" // Spec is the base configuration for CDI type Spec struct { @@ -45,6 +45,7 @@ type Mount struct { HostPath string `json:"hostPath"` ContainerPath string `json:"containerPath"` Options []string `json:"options,omitempty"` + Type string `json:"type,omitempty"` } // Hook represents a hook that needs to be added to the OCI spec. diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go index 10bc9fa23..14a0f6a0b 100644 --- a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go +++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go @@ -95,6 +95,7 @@ func (m *Mount) ToOCI() spec.Mount { Source: m.HostPath, Destination: m.ContainerPath, Options: m.Options, + Type: m.Type, } } diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go new file mode 100644 index 000000000..ce248a181 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go @@ -0,0 +1,339 @@ +package etchosts + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/util" +) + +const ( + hostContainersInternal = "host.containers.internal" + localhost = "localhost" +) + +type HostEntries []HostEntry + +type HostEntry struct { + IP string + Names []string +} + +// Params for the New() function call +type Params struct { + // BaseFile is the file where we read entries from and add entries to + // the target hosts file. If the name is empty it will not read any entries. + BaseFile string + // ExtraHosts is a slice of entries in the "hostname:ip" format. + // Optional. + ExtraHosts []string + // ContainerIPs should contain the main container ipv4 and ipv6 if available + // with the container name and host name as names set. + // Optional. + ContainerIPs HostEntries + // HostContainersInternalIP is the IP for the host.containers.internal entry. + // Optional. + HostContainersInternalIP string + // TargetFile where the hosts are written to. + TargetFile string +} + +// New will create a new hosts file and write this to the target file. +// This function does not prevent any kind of concurrency problems, it is +// the callers responsibility to avoid concurrent writes to this file. +// The extraHosts are written first, then the hosts from the file baseFile and the +// containerIps. The container ip entry is only added when the name was not already +// added before. +func New(params *Params) error { + if err := new(params); err != nil { + return fmt.Errorf("failed to create new hosts file: %w", err) + } + return nil +} + +// Add adds the given entries to the hosts file, entries are only added if +// they are not already present. +// Add is not atomic because it will keep the current file inode. This is +// required to keep bind mounts for containers working. +func Add(file string, entries HostEntries) error { + if err := add(file, entries); err != nil { + return fmt.Errorf("failed to add entries to hosts file: %w", err) + } + return nil +} + +// AddIfExists will add the given entries only if one of the existsEntries +// is in the hosts file. This API is required for podman network connect. +// Since we want to add the same host name for each network ip we want to +// add duplicates and the normal Add() call prevents us from doing so. +// However since we also do not want to overwrite potential entries that +// were added by users manually we first have to check if there are the +// current expected entries in the file. Note that this will only check +// for one match not all. It will also only check that the ip and one of +// the hostnames match like Remove(). +func AddIfExists(file string, existsEntries, newEntries HostEntries) error { + if err := addIfExists(file, existsEntries, newEntries); err != nil { + return fmt.Errorf("failed to add entries to hosts file: %w", err) + } + return nil +} + +// Remove will remove the given entries from the file. An entry will be +// removed when the ip and at least one name matches. Not all names have +// to match. If the given entries are not present in the file no error is +// returned. +// Remove is not atomic because it will keep the current file inode. This is +// required to keep bind mounts for containers working. +func Remove(file string, entries HostEntries) error { + if err := remove(file, entries); err != nil { + return fmt.Errorf("failed to remove entries from hosts file: %w", err) + } + return nil +} + +// new see comment on New() +func new(params *Params) error { + entries, err := parseExtraHosts(params.ExtraHosts) + if err != nil { + return err + } + entries2, err := parseHostsFile(params.BaseFile) + if err != nil { + return err + } + entries = append(entries, entries2...) + + // preallocate the slice with enough space for the 3 special entries below + containerIPs := make(HostEntries, 0, len(params.ContainerIPs)+3) + + // if localhost was not added we add it + // https://github.com/containers/podman/issues/11411 + lh := []string{localhost} + l1 := HostEntry{IP: "127.0.0.1", Names: lh} + l2 := HostEntry{IP: "::1", Names: lh} + containerIPs = append(containerIPs, l1, l2) + if params.HostContainersInternalIP != "" { + e := HostEntry{IP: params.HostContainersInternalIP, Names: []string{hostContainersInternal}} + containerIPs = append(containerIPs, e) + } + containerIPs = append(containerIPs, params.ContainerIPs...) + + if err := writeHostFile(params.TargetFile, entries, containerIPs); err != nil { + return err + } + return nil +} + +// add see comment on Add() +func add(file string, entries HostEntries) error { + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + names := make(map[string]struct{}) + for _, entry := range currentEntries { + for _, name := range entry.Names { + names[name] = struct{}{} + } + } + + // open file in append mode since we only add, we do not have to write existing entries again + f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer f.Close() + + return addEntriesIfNotExists(f, entries, names) +} + +// addIfExists see comment on AddIfExists() +func addIfExists(file string, existsEntries, newEntries HostEntries) error { + // special case when there are no existing entries do a normal add + // this can happen when we connect a network which was not connected + // to any other networks before + if len(existsEntries) == 0 { + return add(file, newEntries) + } + + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + for _, entry := range currentEntries { + if !checkIfEntryExists(entry, existsEntries) { + // keep looking for existing entries + continue + } + // if we have a matching existing entry add the new entries + // open file in append mode since we only add, we do not have to write existing entries again + f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer f.Close() + + for _, e := range newEntries { + if _, err = f.WriteString(formatLine(e.IP, e.Names)); err != nil { + return err + } + } + return nil + } + // no match found is no error + return nil +} + +// remove see comment on Remove() +func remove(file string, entries HostEntries) error { + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + + for _, entry := range currentEntries { + if checkIfEntryExists(entry, entries) { + continue + } + if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil { + return err + } + } + return nil +} + +func checkIfEntryExists(current HostEntry, entries HostEntries) bool { + // check if the current entry equals one of the given entries + for _, rm := range entries { + if current.IP == rm.IP { + // it is enough if one of the names match, in this case we remove the full entry + for _, name := range current.Names { + if util.StringInSlice(name, rm.Names) { + return true + } + } + } + } + return false +} + +// parseExtraHosts converts a slice of "name:ip" string to entries. +// Because podman and buildah both store the extra hosts in this format +// we convert it here instead of having to this on the caller side. +func parseExtraHosts(extraHosts []string) (HostEntries, error) { + entries := make(HostEntries, 0, len(extraHosts)) + for _, entry := range extraHosts { + values := strings.SplitN(entry, ":", 2) + if len(values) != 2 { + return nil, fmt.Errorf("unable to parse host entry %q: incorrect format", entry) + } + if values[0] == "" { + return nil, fmt.Errorf("hostname in host entry %q is empty", entry) + } + if values[1] == "" { + return nil, fmt.Errorf("IP address in host entry %q is empty", entry) + } + e := HostEntry{IP: values[1], Names: []string{values[0]}} + entries = append(entries, e) + } + return entries, nil +} + +// parseHostsFile parses a given host file and returns all entries in it. +// Note that this will remove all comments and spaces. +func parseHostsFile(file string) (HostEntries, error) { + // empty file is valid, in this case we skip adding entries from the file + if file == "" { + return nil, nil + } + + f, err := os.Open(file) + if err != nil { + // do not error when the default hosts file does not exists + // https://github.com/containers/podman/issues/12667 + if errors.Is(err, os.ErrNotExist) && file == config.DefaultHostsFile { + return nil, nil + } + return nil, err + } + defer f.Close() + + entries := HostEntries{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + // split of the comments + line := scanner.Text() + if c := strings.IndexByte(line, '#'); c != -1 { + line = line[:c] + } + fields := strings.Fields(line) + // if we only have a ip without names we skip it + if len(fields) < 2 { + continue + } + + e := HostEntry{IP: fields[0], Names: fields[1:]} + entries = append(entries, e) + } + + return entries, scanner.Err() +} + +// writeHostFile write the entries to the given file +func writeHostFile(file string, userEntries, containerIPs HostEntries) error { + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + + names := make(map[string]struct{}) + for _, entry := range userEntries { + for _, name := range entry.Names { + names[name] = struct{}{} + } + if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil { + return err + } + } + + return addEntriesIfNotExists(f, containerIPs, names) +} + +// addEntriesIfNotExists only adds the entries for names that are not already +// in the hosts file, otherwise we start overwriting user entries +func addEntriesIfNotExists(f io.StringWriter, containerIPs HostEntries, names map[string]struct{}) error { + for _, entry := range containerIPs { + freeNames := make([]string, 0, len(entry.Names)) + for _, name := range entry.Names { + if _, ok := names[name]; !ok { + freeNames = append(freeNames, name) + } + } + if len(freeNames) > 0 { + if _, err := f.WriteString(formatLine(entry.IP, freeNames)); err != nil { + return err + } + } + } + return nil +} + +// formatLine converts the given ip and names to a valid hosts line. +// The returned string includes the newline. +func formatLine(ip string, names []string) string { + return ip + "\t" + strings.Join(names, " ") + "\n" +} diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/ip.go b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go new file mode 100644 index 000000000..3d14b7147 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go @@ -0,0 +1,91 @@ +package etchosts + +import ( + "net" + + "github.com/containers/common/libnetwork/types" + "github.com/containers/common/libnetwork/util" + "github.com/containers/common/pkg/config" + "github.com/containers/storage/pkg/unshare" +) + +// GetHostContainersInternalIP return the host.containers.internal ip +// if netStatus is not nil then networkInterface also must be non nil otherwise this function panics +func GetHostContainersInternalIP(conf *config.Config, netStatus map[string]types.StatusBlock, networkInterface types.ContainerNetwork) string { + switch conf.Containers.HostContainersInternalIP { + case "": + // if empty (default) we will automatically choose one below + // if machine we let the gvproxy dns server handle the dns name so do not add it + if conf.Engine.MachineEnabled { + return "" + } + case "none": + return "" + default: + return conf.Containers.HostContainersInternalIP + } + ip := "" + // Only use the bridge ip when root, as rootless the interfaces are created + // inside the special netns and not the host so we cannot use them. + if unshare.IsRootless() { + return getLocalIP() + } + for net, status := range netStatus { + network, err := networkInterface.NetworkInspect(net) + // only add the host entry for bridge networks + // ip/macvlan gateway is normally not on the host + if err != nil || network.Driver != types.BridgeNetworkDriver { + continue + } + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + if netAddress.Gateway != nil { + if util.IsIPv4(netAddress.Gateway) { + return netAddress.Gateway.String() + } + // ipv6 address but keep looking since we prefer to use ipv4 + ip = netAddress.Gateway.String() + } + } + } + } + if ip != "" { + return ip + } + return getLocalIP() +} + +// getLocalIP returns the non loopback local IP of the host +func getLocalIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + ip := "" + for _, address := range addrs { + // check the address type and if it is not a loopback the display it + if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsGlobalUnicast() { + if util.IsIPv4(ipnet.IP) { + return ipnet.IP.String() + } + // if ipv6 we keep looking for an ipv4 address + ip = ipnet.IP.String() + } + } + return ip +} + +// GetNetworkHostEntries returns HostEntries for all ips in the network status +// with the given hostnames. +func GetNetworkHostEntries(netStatus map[string]types.StatusBlock, names ...string) HostEntries { + hostEntries := make(HostEntries, 0, len(netStatus)) + for _, status := range netStatus { + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + e := HostEntry{IP: netAddress.IPNet.IP.String(), Names: names} + hostEntries = append(hostEntries, e) + } + } + } + return hostEntries +} diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/util.go b/vendor/github.com/containers/common/libnetwork/etchosts/util.go new file mode 100644 index 000000000..d78284594 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/util.go @@ -0,0 +1,30 @@ +package etchosts + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + securejoin "github.com/cyphar/filepath-securejoin" +) + +// GetBaseHostFile return the hosts file which should be used as base. +// The first param should be the config value config.Containers.BaseHostsFile +// The second param should be the root path to the mounted image. This is +// required when the user conf value is set to "image". +func GetBaseHostFile(confValue, imageRoot string) (string, error) { + switch confValue { + case "": + return config.DefaultHostsFile, nil + case "none": + return "", nil + case "image": + // use secure join to prevent problems with symlinks + path, err := securejoin.SecureJoin(imageRoot, config.DefaultHostsFile) + if err != nil { + return "", fmt.Errorf("failed to get /etc/hosts path in image: %w", err) + } + return path, nil + default: + return confValue, nil + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go deleted file mode 100644 index a4d1919a9..000000000 --- a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package terminal provides support functions for dealing with terminals, as -// commonly found on UNIX systems. -// -// Deprecated: this package moved to golang.org/x/term. -package terminal - -import ( - "io" - - "golang.org/x/term" -) - -// EscapeCodes contains escape sequences that can be written to the terminal in -// order to achieve different styles of text. -type EscapeCodes = term.EscapeCodes - -// Terminal contains the state for running a VT100 terminal that is capable of -// reading lines of input. -type Terminal = term.Terminal - -// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is -// a local terminal, that terminal must first have been put into raw mode. -// prompt is a string that is written at the start of each input line (i.e. -// "> "). -func NewTerminal(c io.ReadWriter, prompt string) *Terminal { - return term.NewTerminal(c, prompt) -} - -// ErrPasteIndicator may be returned from ReadLine as the error, in addition -// to valid line data. It indicates that bracketed paste mode is enabled and -// that the returned line consists only of pasted data. Programs may wish to -// interpret pasted data more literally than typed data. -var ErrPasteIndicator = term.ErrPasteIndicator - -// State contains the state of a terminal. -type State = term.State - -// IsTerminal returns whether the given file descriptor is a terminal. -func IsTerminal(fd int) bool { - return term.IsTerminal(fd) -} - -// ReadPassword reads a line of input from a terminal without local echo. This -// is commonly used for inputting passwords and other sensitive data. The slice -// returned does not include the \n. -func ReadPassword(fd int) ([]byte, error) { - return term.ReadPassword(fd) -} - -// MakeRaw puts the terminal connected to the given file descriptor into raw -// mode and returns the previous state of the terminal so that it can be -// restored. -func MakeRaw(fd int) (*State, error) { - return term.MakeRaw(fd) -} - -// Restore restores the terminal connected to the given file descriptor to a -// previous state. -func Restore(fd int, oldState *State) error { - return term.Restore(fd, oldState) -} - -// GetState returns the current state of a terminal which may be useful to -// restore the terminal after a signal. -func GetState(fd int) (*State, error) { - return term.GetState(fd) -} - -// GetSize returns the dimensions of the given terminal. -func GetSize(fd int) (width, height int, err error) { - return term.GetSize(fd) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index e95f0a618..b1e0e3a23 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -57,7 +57,7 @@ github.com/checkpoint-restore/go-criu/v5/rpc github.com/checkpoint-restore/go-criu/v5/stats # github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/readline -# github.com/container-orchestrated-devices/container-device-interface v0.3.2 +# github.com/container-orchestrated-devices/container-device-interface v0.4.0 ## explicit github.com/container-orchestrated-devices/container-device-interface/pkg/cdi github.com/container-orchestrated-devices/container-device-interface/specs-go @@ -114,6 +114,7 @@ github.com/containers/buildah/util github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/libnetwork/cni +github.com/containers/common/libnetwork/etchosts github.com/containers/common/libnetwork/internal/util github.com/containers/common/libnetwork/netavark github.com/containers/common/libnetwork/network @@ -718,7 +719,6 @@ golang.org/x/crypto/ssh golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/knownhosts -golang.org/x/crypto/ssh/terminal # golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/net/context golang.org/x/net/html @@ -744,6 +744,7 @@ golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry # golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 +## explicit golang.org/x/term # golang.org/x/text v0.3.7 ## explicit |