summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rw-r--r--cmd/podman/common/completion.go2
-rwxr-xr-xcontrib/cirrus/logformatter3
-rwxr-xr-xcontrib/cirrus/runner.sh7
-rw-r--r--docs/source/markdown/podman-build.1.md6
-rw-r--r--docs/source/markdown/podman-create.1.md17
-rw-r--r--docs/source/markdown/podman-play-kube.1.md5
-rw-r--r--docs/source/markdown/podman-pod-create.1.md33
-rw-r--r--docs/source/markdown/podman-run.1.md22
-rw-r--r--go.mod4
-rw-r--r--go.sum8
-rw-r--r--libpod/container.go9
-rw-r--r--libpod/container_internal.go80
-rw-r--r--libpod/container_internal_linux.go228
-rw-r--r--libpod/container_internal_linux_test.go28
-rw-r--r--libpod/networking_linux.go70
-rw-r--r--pkg/domain/infra/runtime_libpod.go67
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/specgen/container_validate.go7
-rw-r--r--pkg/specgen/generate/namespaces.go26
-rw-r--r--pkg/specgen/namespaces.go53
-rw-r--r--pkg/util/utils.go105
-rw-r--r--test/apiv2/python/rest_api/fixtures/podman.py4
-rw-r--r--test/e2e/pod_infra_container_test.go16
-rw-r--r--test/e2e/run_dns_test.go3
-rw-r--r--test/e2e/run_networking_test.go12
-rw-r--r--test/e2e/run_userns_test.go13
-rw-r--r--test/e2e/toolbox_test.go9
-rw-r--r--test/system/030-run.bats13
-rw-r--r--test/system/065-cp.bats8
-rw-r--r--test/system/075-exec.bats1
-rw-r--r--test/system/160-volumes.bats13
-rw-r--r--test/system/170-run-userns.bats14
-rw-r--r--test/system/200-pod.bats2
-rw-r--r--test/system/500-networking.bats103
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go1
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go3
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/oci.go1
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/hosts.go339
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/ip.go91
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/util.go30
-rw-r--r--vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md17
-rw-r--r--vendor/github.com/fsnotify/fsnotify/README.md138
-rw-r--r--vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go36
-rw-r--r--vendor/github.com/fsnotify/fsnotify/go.mod2
-rw-r--r--vendor/github.com/fsnotify/fsnotify/inotify.go13
-rw-r--r--vendor/github.com/fsnotify/fsnotify/inotify_poller.go1
-rw-r--r--vendor/github.com/fsnotify/fsnotify/kqueue.go13
-rw-r--r--vendor/github.com/fsnotify/fsnotify/windows.go28
-rw-r--r--vendor/modules.txt5
50 files changed, 1121 insertions, 613 deletions
diff --git a/Makefile b/Makefile
index 502323906..982d7b1e5 100644
--- a/Makefile
+++ b/Makefile
@@ -616,18 +616,24 @@ remotesystem:
fi;\
exit $$rc
-.PHONY: localapiv2
-localapiv2:
- # Order is important running python tests first causes the bash tests to fail, see 12-imagesMore
- # FIXME order of tests should not matter
+.PHONY: localapiv2-bash
+localapiv2-bash:
env PODMAN=./bin/podman stdbuf -o0 -e0 ./test/apiv2/test-apiv2
+
+.PHONY: localapiv2-python
+localapiv2-python:
env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf PODMAN=./bin/podman \
- pytest --disable-warnings ./test/apiv2/python
+ pytest --verbose --disable-warnings ./test/apiv2/python
touch test/__init__.py
env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf PODMAN=./bin/podman \
- pytest --disable-warnings ./test/python/docker
+ pytest --verbose --disable-warnings ./test/python/docker
rm -f test/__init__.py
+# Order is important running python tests first causes the bash tests
+# to fail, see 12-imagesMore. FIXME order of tests should not matter
+.PHONY: localapiv2
+localapiv2: localapiv2-bash localapiv2-python
+
.PHONY: remoteapiv2
remoteapiv2:
true
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/contrib/cirrus/logformatter b/contrib/cirrus/logformatter
index 3c52e612b..05f05dc0b 100755
--- a/contrib/cirrus/logformatter
+++ b/contrib/cirrus/logformatter
@@ -20,6 +20,9 @@ use warnings;
our $VERSION = '0.1';
+# Autoflush stdout
+$| = 1;
+
# For debugging, show data structures using DumpTree($var)
#use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0;
diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh
index 8f956d7f5..aee9bcfbb 100755
--- a/contrib/cirrus/runner.sh
+++ b/contrib/cirrus/runner.sh
@@ -59,8 +59,11 @@ function _run_unit() {
function _run_apiv2() {
_bail_if_test_can_be_skipped test/apiv2
- source .venv/requests/bin/activate
- make localapiv2 |& logformatter
+ (
+ make localapiv2-bash
+ source .venv/requests/bin/activate
+ make localapiv2-python
+ ) |& logformatter
}
function _run_compose() {
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*
diff --git a/go.mod b/go.mod
index 141f59525..4fb9de397 100644
--- a/go.mod
+++ b/go.mod
@@ -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
@@ -29,7 +29,7 @@ require (
github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651
github.com/docker/go-units v0.4.0
github.com/dtylman/scp v0.0.0-20181017070807-f3000a34aef4
- github.com/fsnotify/fsnotify v1.5.2
+ github.com/fsnotify/fsnotify v1.5.3
github.com/ghodss/yaml v1.0.0
github.com/godbus/dbus/v5 v5.1.0
github.com/google/gofuzz v1.2.0
diff --git a/go.sum b/go.sum
index 8f32ac243..47ff1ec36 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
@@ -513,8 +513,8 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
-github.com/fsnotify/fsnotify v1.5.2 h1:M+aHumjFDOySyAjWICQHrDfuizRTP7nzkRHxXfyRP68=
-github.com/fsnotify/fsnotify v1.5.2/go.mod h1:VKyWoa5earkjWzuYFJOy3s0DLrlWgSh5nf5hjFuJcAw=
+github.com/fsnotify/fsnotify v1.5.3 h1:vNFpj2z7YIbwh2bw7x35sqYpp2wfuq+pivKbWG09B8c=
+github.com/fsnotify/fsnotify v1.5.3/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsouza/go-dockerclient v1.7.7/go.mod h1:njNCXvoZj3sLPjf3yO0DPHf1mdLdCPDYPc14GskKA4Y=
github.com/fsouza/go-dockerclient v1.7.10 h1:KIda66AP88BWQpyg+8ve9LQmn1ZZ/usCbmxeBoMth3U=
github.com/fsouza/go-dockerclient v1.7.10/go.mod h1:rdD3Eq3rHwMA8p/xrn+gLb+3ov7uRJGVkV1HsUFY39A=
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_internal.go b/libpod/container_internal.go
index 9449d31c7..5c6719bdf 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 b5f8ad8e8..ead58b373 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"
@@ -2079,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())
}
@@ -2123,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())
}
}
@@ -2142,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())
}
}
@@ -2528,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
+func getLocalhostHostEntry(c *Container) etchosts.HostEntries {
+ return etchosts.HostEntries{{IP: "127.0.0.1", Names: []string{c.Hostname(), c.config.Name}}}
}
-// 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
-}
-
-// 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/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/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/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/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 cef55abff..7a7ca2706 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/util/utils.go b/pkg/util/utils.go
index de1fdb6a8..9842a0f73 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -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/apiv2/python/rest_api/fixtures/podman.py b/test/apiv2/python/rest_api/fixtures/podman.py
index c700571b9..f2db6f498 100644
--- a/test/apiv2/python/rest_api/fixtures/podman.py
+++ b/test/apiv2/python/rest_api/fixtures/podman.py
@@ -20,10 +20,6 @@ class Podman:
cgroupfs = os.getenv("CGROUP_MANAGER", "systemd")
self.cmd.append(f"--cgroup-manager={cgroupfs}")
- if os.getenv("DEBUG"):
- self.cmd.append("--log-level=debug")
- self.cmd.append("--syslog=true")
-
self.anchor_directory = tempfile.mkdtemp(prefix="podman_restapi_")
self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio"))
self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run"))
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/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/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..958aa4493 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -83,7 +83,7 @@ load helpers
}
# Issue #5466 - port-forwarding doesn't work with this option and -d
-@test "podman networking: port with --userns=keep-id" {
+@test "podman networking: port with --userns=keep-id for rootless or --uidmap=* for rootfull" {
for cidr in "" "$(random_rfc1918_subnet).0/24"; do
myport=$(random_free_port 52000-52999)
if [[ -z $cidr ]]; then
@@ -105,7 +105,9 @@ load helpers
# remote IP is not 127.0.0.1 (podman PR #9052).
# We could get more parseable output by using $NCAT_REMOTE_ADDR,
# but busybox nc doesn't support that.
- run_podman run -d --userns=keep-id $network_arg -p 127.0.0.1:$myport:$myport \
+ userns="--userns=keep-id"
+ is_rootless || userns="--uidmap=0:1111111:65536 --gidmap=0:1111111:65536"
+ run_podman run -d ${userns} $network_arg -p 127.0.0.1:$myport:$myport \
$IMAGE nc -l -n -v -p $myport
cid="$output"
@@ -133,14 +135,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 +471,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 +497,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 +679,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/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/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
index 8a642563d..828a60b24 100644
--- a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
+++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
@@ -48,6 +48,18 @@ fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Win
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
+To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
+
+* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
+* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
+* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
+* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
+* When you're done, you will want to halt or destroy the Vagrant boxes.
+
+Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
+
+Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
+
### Maintainers
Help maintaining fsnotify is welcome. To be a maintainer:
@@ -55,6 +67,11 @@ Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
+To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
+
All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/).
+
+[hub]: https://github.com/github/hub
+[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md
index 7797745da..34488e621 100644
--- a/vendor/github.com/fsnotify/fsnotify/README.md
+++ b/vendor/github.com/fsnotify/fsnotify/README.md
@@ -1,122 +1,40 @@
-# File system notifications for Go
+# WARNING
-[![Go Reference](https://pkg.go.dev/badge/github.com/fsnotify/fsnotify.svg)](https://pkg.go.dev/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Maintainers Wanted](https://img.shields.io/badge/maintainers-wanted-red.svg)](https://github.com/fsnotify/fsnotify/issues/413)
+If you are reading this, you use `master` branch of this repository,
+which is wrong.
+This branch
+ - should not be used;
+ - is not maintained;
+ - is not supported;
+ - will be removed soon.
+You should switch to using the default branch instead.
-fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library.
+## Using git
-Cross platform: Windows, Linux, BSD and macOS.
+Here's how to switch your existing local copy of this repository from `master`
+to `main` (assuming the remote name is `origin`):
-| Adapter | OS | Status |
-| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
-| inotify | Linux 2.6.27 or later, Android\* | Supported |
-| kqueue | BSD, macOS, iOS\* | Supported |
-| ReadDirectoryChangesW | Windows | Supported |
-| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
-| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
-| fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
-| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
-| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
-
-\* Android and iOS are untested.
-
-Please see [the documentation](https://pkg.go.dev/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
-
-## API stability
-
-fsnotify is a fork of [howeyc/fsnotify](https://github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
-
-All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/).
-
-## Usage
-
-```go
-package main
-
-import (
- "log"
-
- "github.com/fsnotify/fsnotify"
-)
-
-func main() {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Fatal(err)
- }
- defer watcher.Close()
-
- done := make(chan bool)
- go func() {
- for {
- select {
- case event, ok := <-watcher.Events:
- if !ok {
- return
- }
- log.Println("event:", event)
- if event.Op&fsnotify.Write == fsnotify.Write {
- log.Println("modified file:", event.Name)
- }
- case err, ok := <-watcher.Errors:
- if !ok {
- return
- }
- log.Println("error:", err)
- }
- }
- }()
-
- err = watcher.Add("/tmp/foo")
- if err != nil {
- log.Fatal(err)
- }
- <-done
-}
+```
+git branch -m master main
+git fetch origin
+git branch -u origin/main main
+git remote set-head origin -a
```
-## Contributing
-
-Please refer to [CONTRIBUTING][] before opening an issue or pull request.
-
-## FAQ
-
-**When a file is moved to another directory is it still being watched?**
-
-No (it shouldn't be, unless you are watching where it was moved to).
-
-**When I watch a directory, are all subdirectories watched as well?**
-
-No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
-
-**Do I have to watch the Error and Event channels in a separate goroutine?**
-
-As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
-
-**Why am I receiving multiple events for the same file on OS X?**
-
-Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
-
-**How many files can be watched at once?**
-
-There are OS-specific limits as to how many watches can be created:
-* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
-* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
-
-**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
-
-fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
-
-[#62]: https://github.com/howeyc/fsnotify/issues/62
-[#18]: https://github.com/fsnotify/fsnotify/issues/18
-[#11]: https://github.com/fsnotify/fsnotify/issues/11
-[#7]: https://github.com/howeyc/fsnotify/issues/7
+In addition to the above, if you want to remove the leftover `origin/master`
+remote branch (NOTE this also removes all other remote branches that no longer
+exist in `origin`):
-[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
+```
+git remote prune origin
+```
-## Related Projects
+## Background
-* [notify](https://github.com/rjeczalik/notify)
-* [fsevents](https://github.com/fsnotify/fsevents)
+The `master` branch was renamed to `main`, causing an issue with
+Yocto/OpenEmbedded's meta-virtualization layer, which explicitly refers
+to `master` branch of this repository (see #426).
+This temporary branch is created to alleviate the Yocto/OE issue.
diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go b/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
deleted file mode 100644
index eb25cb407..000000000
--- a/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2022 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.
-
-//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !solaris && !windows
-// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!solaris,!windows
-
-package fsnotify
-
-import (
- "fmt"
- "runtime"
-)
-
-// Watcher watches a set of files, delivering events to a channel.
-type Watcher struct{}
-
-// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
-func NewWatcher() (*Watcher, error) {
- return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
-}
-
-// Close removes all watches and closes the events channel.
-func (w *Watcher) Close() error {
- return nil
-}
-
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
- return nil
-}
-
-// Remove stops watching the the named file or directory (non-recursively).
-func (w *Watcher) Remove(name string) error {
- return nil
-}
diff --git a/vendor/github.com/fsnotify/fsnotify/go.mod b/vendor/github.com/fsnotify/fsnotify/go.mod
index 8d1fc1295..54089e48b 100644
--- a/vendor/github.com/fsnotify/fsnotify/go.mod
+++ b/vendor/github.com/fsnotify/fsnotify/go.mod
@@ -1,6 +1,6 @@
module github.com/fsnotify/fsnotify
-go 1.16
+go 1.13
require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
diff --git a/vendor/github.com/fsnotify/fsnotify/inotify.go b/vendor/github.com/fsnotify/fsnotify/inotify.go
index a6d0e0ec8..eb87699b5 100644
--- a/vendor/github.com/fsnotify/fsnotify/inotify.go
+++ b/vendor/github.com/fsnotify/fsnotify/inotify.go
@@ -163,19 +163,6 @@ func (w *Watcher) Remove(name string) error {
return nil
}
-// WatchList returns the directories and files that are being monitered.
-func (w *Watcher) WatchList() []string {
- w.mu.Lock()
- defer w.mu.Unlock()
-
- entries := make([]string, 0, len(w.watches))
- for pathname := range w.watches {
- entries = append(entries, pathname)
- }
-
- return entries
-}
-
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go
index b572a37c3..e9ff9439f 100644
--- a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go
+++ b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go
@@ -38,6 +38,7 @@ func newFdPoller(fd int) (*fdPoller, error) {
poller.close()
}
}()
+ poller.fd = fd
// Create epoll fd
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
diff --git a/vendor/github.com/fsnotify/fsnotify/kqueue.go b/vendor/github.com/fsnotify/fsnotify/kqueue.go
index 6fb8d8532..368f5b790 100644
--- a/vendor/github.com/fsnotify/fsnotify/kqueue.go
+++ b/vendor/github.com/fsnotify/fsnotify/kqueue.go
@@ -148,19 +148,6 @@ func (w *Watcher) Remove(name string) error {
return nil
}
-// WatchList returns the directories and files that are being monitered.
-func (w *Watcher) WatchList() []string {
- w.mu.Lock()
- defer w.mu.Unlock()
-
- entries := make([]string, 0, len(w.watches))
- for pathname := range w.watches {
- entries = append(entries, pathname)
- }
-
- return entries
-}
-
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
diff --git a/vendor/github.com/fsnotify/fsnotify/windows.go b/vendor/github.com/fsnotify/fsnotify/windows.go
index ddc69ef87..c02b75f7c 100644
--- a/vendor/github.com/fsnotify/fsnotify/windows.go
+++ b/vendor/github.com/fsnotify/fsnotify/windows.go
@@ -12,7 +12,6 @@ import (
"fmt"
"os"
"path/filepath"
- "reflect"
"runtime"
"sync"
"syscall"
@@ -97,21 +96,6 @@ func (w *Watcher) Remove(name string) error {
return <-in.reply
}
-// WatchList returns the directories and files that are being monitered.
-func (w *Watcher) WatchList() []string {
- w.mu.Lock()
- w.mu.Unlock()
-
- entries := make([]string, 0, len(w.watches))
- for _, entry := range w.watches {
- for _, watchEntry := range entry {
- entries = append(entries, watchEntry.path)
- }
- }
-
- return entries
-}
-
const (
// Options for AddWatch
sysFSONESHOT = 0x80000000
@@ -468,16 +452,8 @@ func (w *Watcher) readEvents() {
// Point "raw" to the event in the buffer
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
- // TODO: Consider using unsafe.Slice that is available from go1.17
- // https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang
- // instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name
- size := int(raw.FileNameLength / 2)
- var buf []uint16
- sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
- sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
- sh.Len = size
- sh.Cap = size
- name := syscall.UTF16ToString(buf)
+ buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
+ name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
fullname := filepath.Join(watch.path, name)
var mask uint64
diff --git a/vendor/modules.txt b/vendor/modules.txt
index ca4f53bf6..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
@@ -386,7 +387,7 @@ github.com/docker/libnetwork/types
github.com/dtylman/scp
# github.com/felixge/httpsnoop v1.0.1
github.com/felixge/httpsnoop
-# github.com/fsnotify/fsnotify v1.5.2
+# github.com/fsnotify/fsnotify v1.5.3
## explicit
github.com/fsnotify/fsnotify
# github.com/fsouza/go-dockerclient v1.7.10