diff options
38 files changed, 592 insertions, 178 deletions
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8fd51b5e9..d6f92873e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'A friendly reminder that this issue had no activity for 30 days.' diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 4aca79770..de5b2995a 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/network" "github.com/containers/podman/v3/pkg/registries" "github.com/containers/podman/v3/pkg/rootless" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" @@ -243,7 +244,7 @@ func getRegistries() ([]string, cobra.ShellCompDirective) { return regs, cobra.ShellCompDirectiveNoFileComp } -func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { +func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]string, cobra.ShellCompDirective) { suggestions := []string{} networkListOptions := entities.NetworkListOptions{} @@ -259,7 +260,15 @@ func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCo } for _, n := range networks { - if strings.HasPrefix(n.Name, toComplete) { + id := network.GetNetworkID(n.Name) + // include ids in suggestions if cType == completeIDs or + // more then 2 chars are typed and cType == completeDefault + if ((len(toComplete) > 1 && cType == completeDefault) || + cType == completeIDs) && strings.HasPrefix(id, toComplete) { + suggestions = append(suggestions, id[0:12]) + } + // include name in suggestions + if cType != completeIDs && strings.HasPrefix(n.Name, toComplete) { suggestions = append(suggestions, n.Name) } } @@ -502,7 +511,7 @@ func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getNetworks(cmd, toComplete) + return getNetworks(cmd, toComplete, completeDefault) } // AutocompleteDefaultOneArg - Autocomplete path only for the first argument. @@ -588,7 +597,7 @@ func AutocompleteContainerOneArg(cmd *cobra.Command, args []string, toComplete s // AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args. func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return getNetworks(cmd, toComplete) + return getNetworks(cmd, toComplete, completeDefault) } if len(args) == 1 { return getContainers(cmd, toComplete, completeDefault) @@ -624,7 +633,7 @@ func AutocompleteInspect(cmd *cobra.Command, args []string, toComplete string) ( containers, _ := getContainers(cmd, toComplete, completeDefault) images, _ := getImages(cmd, toComplete) pods, _ := getPods(cmd, toComplete, completeDefault) - networks, _ := getNetworks(cmd, toComplete) + networks, _ := getNetworks(cmd, toComplete, completeDefault) volumes, _ := getVolumes(cmd, toComplete) suggestions := append(containers, images...) suggestions = append(suggestions, pods...) @@ -885,7 +894,7 @@ func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete strin }, } - networks, _ := getNetworks(cmd, toComplete) + networks, _ := getNetworks(cmd, toComplete, completeDefault) suggestions, dir := completeKeyValues(toComplete, kv) // add slirp4netns here it does not work correct if we add it to the kv map suggestions = append(suggestions, "slirp4netns") @@ -1039,7 +1048,10 @@ func AutocompleteNetworkDriver(cmd *cobra.Command, args []string, toComplete str // -> "ipc", "net", "pid", "user", "uts", "cgroup", "none" func AutocompletePodShareNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { namespaces := []string{"ipc", "net", "pid", "user", "uts", "cgroup", "none"} - return namespaces, cobra.ShellCompDirectiveNoFileComp + split := strings.Split(toComplete, ",") + split[len(split)-1] = "" + toComplete = strings.Join(split, ",") + return prefixSlice(toComplete, namespaces), cobra.ShellCompDirectiveNoFileComp } // AutocompletePodPsSort - Autocomplete images sort options. @@ -1115,7 +1127,7 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) return []string{define.HealthCheckHealthy, define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp }, - "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) }, "label=": nil, "exited=": nil, "until=": nil, @@ -1138,7 +1150,7 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri "ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) { return containerStatuses, cobra.ShellCompDirectiveNoFileComp }, - "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) }, "label=": nil, } return completeKeyValues(toComplete, kv) @@ -1158,11 +1170,28 @@ func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete stri return completeKeyValues(toComplete, kv) } +// AutocompletePruneFilters - Autocomplete container/image prune --filter options. +func AutocompletePruneFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "until=": nil, + "label=": nil, + } + return completeKeyValues(toComplete, kv) +} + // AutocompleteNetworkFilters - Autocomplete network ls --filter options. func AutocompleteNetworkFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { kv := keyValueCompletion{ - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, - "plugin=": nil, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeNames) }, + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeIDs) }, + "plugin=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"bridge", "portmap", + "firewall", "tuning", "dnsname", "macvlan"}, cobra.ShellCompDirectiveNoFileComp + }, + "label=": nil, + "driver=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"bridge"}, cobra.ShellCompDirectiveNoFileComp + }, } return completeKeyValues(toComplete, kv) } diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 5dc2ec864..2f45e559d 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -778,20 +778,30 @@ func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrot return td, nil } -func parseSecrets(secrets []string) ([]string, map[string]string, error) { +func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) { secretParseError := errors.New("error parsing secret") - var mount []string + var mount []specgen.Secret envs := make(map[string]string) for _, val := range secrets { + // mount only tells if user has set an option that can only be used with mount secret type + mountOnly := false source := "" secretType := "" target := "" + var uid, gid uint32 + // default mode 444 octal = 292 decimal + var mode uint32 = 292 split := strings.Split(val, ",") // --secret mysecret if len(split) == 1 { - source = val - mount = append(mount, source) + mountSecret := specgen.Secret{ + Source: val, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) continue } // --secret mysecret,opt=opt @@ -799,7 +809,7 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { source = split[0] split = split[1:] } - // TODO: implement other secret options + for _, val := range split { kv := strings.SplitN(val, "=", 2) if len(kv) < 2 { @@ -818,6 +828,28 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { secretType = kv[1] case "target": target = kv[1] + case "mode": + mountOnly = true + mode64, err := strconv.ParseUint(kv[1], 8, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "mode %s invalid", kv[1]) + } + mode = uint32(mode64) + case "uid", "UID": + mountOnly = true + uid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "UID %s invalid", kv[1]) + } + uid = uint32(uid64) + case "gid", "GID": + mountOnly = true + gid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "GID %s invalid", kv[1]) + } + gid = uint32(gid64) + default: return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val) } @@ -833,9 +865,18 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { if target != "" { return nil, nil, errors.Wrapf(secretParseError, "target option is invalid for mounted secrets") } - mount = append(mount, source) + mountSecret := specgen.Secret{ + Source: source, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) } if secretType == "env" { + if mountOnly { + return nil, nil, errors.Wrap(secretParseError, "UID, GID, Mode options cannot be set with secret type env") + } if target == "" { target = source } diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index 837d90f70..94da029b9 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -43,7 +43,7 @@ func init() { flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 8231e5c57..db645cc2e 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" "github.com/containers/podman/v3/cmd/podman/validate" @@ -15,10 +16,8 @@ import ( ) var ( - pruneDescription = `Removes all unnamed images from local storage. - - If an image is not being used by a container, it will be removed from the system.` - pruneCmd = &cobra.Command{ + pruneDescription = `Removes dangling or unused images from local storage.` + pruneCmd = &cobra.Command{ Use: "prune [options]", Args: validate.NoArgs, Short: "Remove unused images", @@ -41,13 +40,12 @@ func init() { }) flags := pruneCmd.Flags() - flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones") + flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all images not in use by containers, not just dangling ones") flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - //TODO: add completion for filters - _ = pruneCmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/networks/prune.go b/cmd/podman/networks/prune.go index bcc55f0f4..5f1cbda5f 100644 --- a/cmd/podman/networks/prune.go +++ b/cmd/podman/networks/prune.go @@ -6,7 +6,6 @@ import ( "os" "strings" - "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" @@ -39,7 +38,7 @@ func networkPruneFlags(cmd *cobra.Command, flags *pflag.FlagSet) { flags.BoolVarP(&force, "force", "f", false, "do not prompt for confirmation") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func init() { diff --git a/cmd/podman/networks/reload.go b/cmd/podman/networks/reload.go index 8f2fbf011..035e56a07 100644 --- a/cmd/podman/networks/reload.go +++ b/cmd/podman/networks/reload.go @@ -26,9 +26,6 @@ var ( Example: `podman network reload --latest podman network reload 3c13ef6dd843 podman network reload test1 test2`, - Annotations: map[string]string{ - registry.ParentNSRequired: "", - }, } ) diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index 3020a541b..0f1285564 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" @@ -50,7 +51,7 @@ func init() { flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes") filterFlagName := "filter" flags.StringArrayVar(&filters, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/docs/source/Introduction.rst b/docs/source/Introduction.rst index 3fa86f868..5c8713d27 100644 --- a/docs/source/Introduction.rst +++ b/docs/source/Introduction.rst @@ -2,7 +2,7 @@ Introduction ================================== -Containers_ simplify the consumption of applications with all of their dependencies and default configuration files. Users test drive or deploy a new application with one or two commands instead of following pages of installation instructions. Here's how to find your first `Container Image`_:: +Containers_ simplify the production, distribution, discoverability, and usage of applications with all of their dependencies and default configuration files. Users test drive or deploy a new application with one or two commands instead of following pages of installation instructions. Here's how to find your first `Container Image`_:: podman search busybox diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index d03d48506..2c51b312d 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -859,6 +859,9 @@ Secret Options - `type=mount|env` : How the secret will be exposed to the container. Default mount. - `target=target` : Target of secret. Defauts to secret name. +- `uid=0` : UID of secret. Defaults to 0. Mount secret type only. +- `gid=0` : GID of secret. Defaults to 0. Mount secret type only. +- `mode=0` : Mode of secret. Defaults to 0444. Mount secret type only. #### **--security-opt**=*option* diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md index 73024ffb8..bd08d18fc 100644 --- a/docs/source/markdown/podman-image-prune.1.md +++ b/docs/source/markdown/podman-image-prune.1.md @@ -8,8 +8,7 @@ podman-image-prune - Remove all unused images from the local store ## DESCRIPTION **podman image prune** removes all dangling images from local storage. With the `all` option, -you can delete all unused images. Unused images are dangling images as well as any image that -does not have any containers based on it. +you can delete all unused images (i.e., images not in use by any container). The image prune command does not prune cache images that only use layers that are necessary for other images. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index b9cfb01d1..46e15d62f 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -911,6 +911,9 @@ Secret Options - `type=mount|env` : How the secret will be exposed to the container. Default mount. - `target=target` : Target of secret. Defauts to secret name. +- `uid=0` : UID of secret. Defaults to 0. Mount secret type only. +- `gid=0` : GID of secret. Defaults to 0. Mount secret type only. +- `mode=0` : Mode of secret. Defaults to 0444. Mount secret type only. #### **--security-opt**=*option* diff --git a/docs/source/markdown/podman-volume-ls.1.md b/docs/source/markdown/podman-volume-ls.1.md index ab3813cca..489057446 100644 --- a/docs/source/markdown/podman-volume-ls.1.md +++ b/docs/source/markdown/podman-volume-ls.1.md @@ -16,7 +16,14 @@ flag. Use the **--quiet** flag to print only the volume names. #### **--filter**=*filter*, **-f** -Filter volume output. +Volumes can be filtered by the following attributes: + +- dangling +- driver +- label +- name +- opt +- scope #### **--format**=*format* diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md index 51dfa7564..850bf6681 100644 --- a/docs/tutorials/basic_networking.md +++ b/docs/tutorials/basic_networking.md @@ -87,12 +87,16 @@ network, and the one will be created as a bridge network. $ podman network create ``` -When rootless containers are run with a CNI networking configuration, a “side-car” -container for running CNI is also run. Do not remove this container while your rootless -containers are running. if you remove this container (e.g by accident) all attached -containers lose network connectivity. In order to restore the network connectivity -all containers with networks must be restarted. This will automatically recreate -the "side-car" container. For rootfull containers, there is no “side-car” container +When rootless containers are run with a CNI networking configuration, CNI operations +will be executed inside an extra network namespace. To join this namespace, use +`podman unshare --rootless-cni`. Podman version 3.1 and earlier use a special “side-car” +container called rootless-cni-infra. Do not remove this container while your rootless +containers are running. If you remove this container (e.g. by accident), all attached +containers lose network connectivity. In order to restore the network connectivity, all +containers with networks must be restarted. This will automatically recreate the "side-car" +container. When you are using version 3.2 or newer the “side-car” container can be +safely removed. Therefore, it is no longer used. +For rootfull containers, there is no extra namespace or “side-car” container as rootfull users have the permissions to create and modify network interfaces on the host. diff --git a/libpod/container.go b/libpod/container.go index c49d8feeb..591cf9bc5 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -126,6 +126,8 @@ type Container struct { // This is true if a container is restored from a checkpoint. restoreFromCheckpoint bool + + slirp4netnsSubnet *net.IPNet } // ContainerState contains the current state of the container @@ -235,6 +237,18 @@ type ContainerImageVolume struct { ReadWrite bool `json:"rw"` } +// ContainerSecret is a secret that is mounted in a container +type ContainerSecret struct { + // Secret is the secret + *secrets.Secret + // UID is tbe UID of the secret file + UID uint32 + // GID is the GID of the secret file + GID uint32 + // Mode is the mode of the secret file + Mode uint32 +} + // ContainerNetworkDescriptions describes the relationship between the CNI // network and the ethN where N is an integer type ContainerNetworkDescriptions map[string]int @@ -1124,7 +1138,7 @@ func (c *Container) Umask() string { } //Secrets return the secrets in the container -func (c *Container) Secrets() []*secrets.Secret { +func (c *Container) Secrets() []*ContainerSecret { return c.config.Secrets } diff --git a/libpod/container_config.go b/libpod/container_config.go index 904c03f9b..0de79fde3 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -148,7 +148,7 @@ type ContainerRootFSConfig struct { // default, but others do not. CreateWorkingDir bool `json:"createWorkingDir,omitempty"` // Secrets lists secrets to mount into the container - Secrets []*secrets.Secret `json:"secrets,omitempty"` + Secrets []*ContainerSecret `json:"secrets,omitempty"` // SecretPath is the secrets location in storage SecretsPath string `json:"secretsPath"` // Volatile specifies whether the container storage can be optimized diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5b2103c92..4210bc581 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -343,11 +343,13 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.CreateCommand = c.config.CreateCommand ctrConfig.Timezone = c.config.Timezone - for _, secret := range c.config.Secrets { newSec := define.InspectSecret{} newSec.Name = secret.Name newSec.ID = secret.ID + newSec.UID = secret.UID + newSec.GID = secret.GID + newSec.Mode = secret.Mode ctrConfig.Secrets = append(ctrConfig.Secrets, &newSec) } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 53b85a466..f77825efd 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -15,7 +15,7 @@ import ( metadata "github.com/checkpoint-restore/checkpointctl/lib" "github.com/containers/buildah/copier" - "github.com/containers/common/pkg/secrets" + butil "github.com/containers/buildah/util" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/cgroups" @@ -24,6 +24,7 @@ import ( "github.com/containers/podman/v3/pkg/hooks/exec" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/selinux" + "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" @@ -1530,6 +1531,16 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { }() } + // If /etc/mtab does not exist in container image, then we need to + // create it, so that mount command within the container will work. + mtab := filepath.Join(mountPoint, "/etc/mtab") + if err := os.MkdirAll(filepath.Dir(mtab), 0755); err != nil { + return "", errors.Wrap(err, "error creating mtab directory") + } + if err = os.Symlink("/proc/mounts", mtab); err != nil && !os.IsExist(err) { + return "", err + } + // Request a mount of all named volumes for _, v := range c.config.NamedVolumes { vol, err := c.mountNamedVolume(v, mountPoint) @@ -2235,21 +2246,31 @@ func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool { } // extractSecretToStorage copies a secret's data from the secrets manager to the container's static dir -func (c *Container) extractSecretToCtrStorage(name string) error { - manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir()) +func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error { + manager, err := c.runtime.SecretsManager() if err != nil { return err } - secr, data, err := manager.LookupSecretData(name) + _, data, err := manager.LookupSecretData(secr.Name) if err != nil { return err } secretFile := filepath.Join(c.config.SecretsPath, secr.Name) + hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), secr.UID, secr.GID) + if err != nil { + return errors.Wrap(err, "unable to extract secret") + } err = ioutil.WriteFile(secretFile, data, 0644) if err != nil { return errors.Wrapf(err, "unable to create %s", secretFile) } + if err := os.Lchown(secretFile, int(hostUID), int(hostGID)); err != nil { + return err + } + if err := os.Chmod(secretFile, os.FileMode(secr.Mode)); err != nil { + return err + } if err := label.Relabel(secretFile, c.config.MountLabel, false); err != nil { return err } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 7d57e8965..04340e6b2 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -29,7 +29,6 @@ import ( "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/chown" "github.com/containers/common/pkg/config" - "github.com/containers/common/pkg/secrets" "github.com/containers/common/pkg/subscriptions" "github.com/containers/common/pkg/umask" "github.com/containers/podman/v3/libpod/define" @@ -759,7 +758,10 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } if len(c.config.EnvSecrets) > 0 { - manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir()) + manager, err := c.runtime.SecretsManager() + if err != nil { + return nil, err + } if err != nil { return nil, err } @@ -1358,6 +1360,34 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return c.save() } +// Retrieves a container's "root" net namespace container dependency. +func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) { + containersVisited := map[string]int{c.config.ID: 1} + nextCtr := c.config.NetNsCtr + for nextCtr != "" { + // Make sure we aren't in a loop + if _, visited := containersVisited[nextCtr]; visited { + return nil, errors.New("loop encountered while determining net namespace container") + } + containersVisited[nextCtr] = 1 + + depCtr, err = c.runtime.state.Container(nextCtr) + if err != nil { + return nil, errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID()) + } + // This should never happen without an error + if depCtr == nil { + break + } + nextCtr = depCtr.config.NetNsCtr + } + + if depCtr == nil { + return nil, errors.New("unexpected error depCtr is nil without reported error from runtime state") + } + return depCtr, nil +} + // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { @@ -1396,24 +1426,9 @@ func (c *Container) makeBindMounts() error { // We want /etc/resolv.conf and /etc/hosts from the // other container. Unless we're not creating both of // them. - var ( - depCtr *Container - nextCtr string - ) - - // I don't like infinite loops, but I don't think there's - // a serious risk of looping dependencies - too many - // protections against that elsewhere. - nextCtr = c.config.NetNsCtr - for { - depCtr, err = c.runtime.state.Container(nextCtr) - if err != nil { - return errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID()) - } - nextCtr = depCtr.config.NetNsCtr - if nextCtr == "" { - break - } + depCtr, err := c.getRootNetNsDepCtr() + if err != nil { + return errors.Wrapf(err, "error fetching network namespace dependency container for container %s", c.ID()) } // We need that container's bind mounts @@ -1698,7 +1713,12 @@ func (c *Container) generateResolvConf() (string, error) { nameservers = resolvconf.GetNameservers(resolv.Content) // slirp4netns has a built in DNS server. if c.config.NetMode.IsSlirp4netns() { - nameservers = append([]string{slirp4netnsDNS}, nameservers...) + slirp4netnsDNS, err := GetSlirp4netnsDNS(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine Slirp4netns DNS: ", err.Error()) + } else { + nameservers = append([]string{slirp4netnsDNS.String()}, nameservers...) + } } } @@ -1779,7 +1799,12 @@ func (c *Container) getHosts() string { if c.Hostname() != "" { if c.config.NetMode.IsSlirp4netns() { // When using slirp4netns, the interface gets a static IP - hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP, c.Hostname(), c.config.Name) + slirp4netnsIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine slirp4netnsIP: ", err.Error()) + } else { + hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name) + } } else { hasNetNS := false netNone := false @@ -1802,6 +1827,36 @@ func (c *Container) getHosts() string { } } } + + // Add gateway entry + var depCtr *Container + if c.config.NetNsCtr != "" { + // ignoring the error because there isn't anything to do + depCtr, _ = c.getRootNetNsDepCtr() + } else if len(c.state.NetworkStatus) != 0 { + depCtr = c + } else { + depCtr = nil + } + + if depCtr != nil { + for _, pluginResultsRaw := range depCtr.state.NetworkStatus { + pluginResult, _ := cnitypes.GetResult(pluginResultsRaw) + for _, ip := range pluginResult.IPs { + hosts += fmt.Sprintf("%s host.containers.internal\n", ip.Gateway) + } + } + } else if c.config.NetMode.IsSlirp4netns() { + gatewayIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine gatewayIP: ", err.Error()) + } else { + hosts += fmt.Sprintf("%s host.containers.internal\n", gatewayIP.String()) + } + } else { + logrus.Debug("network configuration does not support host.containers.internal address") + } + return hosts } @@ -2339,7 +2394,7 @@ func (c *Container) createSecretMountDir() error { oldUmask := umask.Set(0) defer umask.Set(oldUmask) - if err := os.MkdirAll(src, 0644); err != nil { + if err := os.MkdirAll(src, 0755); err != nil { return err } if err := label.Relabel(src, c.config.MountLabel, false); err != nil { diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 5283946fa..af8ba6ecf 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -713,13 +713,16 @@ type DriverData struct { Data map[string]string `json:"Data"` } -// InspectHostPort provides information on a port on the host that a container's -// port is bound to. +// InspectSecret contains information on secrets mounted inside the container type InspectSecret struct { - // IP on the host we are bound to. "" if not specified (binding to all - // IPs). + // Name is the name of the secret Name string `json:"Name"` - // Port on the host we are bound to. No special formatting - just an - // integer stuffed into a string. + // ID is the ID of the secret ID string `json:"ID"` + // ID is the UID of the mounted secret file + UID uint32 `json:"UID"` + // ID is the GID of the mounted secret file + GID uint32 `json:"GID"` + // ID is the ID of the mode of the mounted secret file + Mode uint32 `json:"Mode"` } diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 64c652eec..81bf5f69c 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -179,6 +179,9 @@ var ( // ErrNoNetwork indicates that a container has no net namespace, like network=none ErrNoNetwork = errors.New("container has no network namespace") + // ErrNetworkModeInvalid indicates that a container has the wrong network mode for an operation + ErrNetworkModeInvalid = errors.New("invalid network mode") + // ErrSetSecurityAttribute indicates that a request to set a container's security attribute // was not possible. ErrSetSecurityAttribute = fmt.Errorf("%w: unable to assign security attribute", ErrOCIRuntime) diff --git a/libpod/diff.go b/libpod/diff.go index 6ce8d809a..c5a53478b 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" ) -var containerMounts = map[string]bool{ +var initInodes = map[string]bool{ "/dev": true, "/etc/hostname": true, "/etc/hosts": true, @@ -17,6 +17,7 @@ var containerMounts = map[string]bool{ "/run/.containerenv": true, "/run/secrets": true, "/sys": true, + "/etc/mtab": true, } // GetDiff returns the differences between the two images, layers, or containers @@ -36,7 +37,7 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) { changes, err := r.store.Changes(fromLayer, toLayer) if err == nil { for _, c := range changes { - if containerMounts[c.Path] { + if initInodes[c.Path] { continue } rchanges = append(rchanges, c) diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index cfed5a1f2..0e8a4f768 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -23,6 +23,7 @@ import ( "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/libpod/network" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/podman/v3/pkg/namespaces" "github.com/containers/podman/v3/pkg/netns" "github.com/containers/podman/v3/pkg/resolvconf" "github.com/containers/podman/v3/pkg/rootless" @@ -37,16 +38,12 @@ import ( ) const ( - // slirp4netnsIP is the IP used by slirp4netns to configure the tap device - // inside the network namespace. - slirp4netnsIP = "10.0.2.100" - - // slirp4netnsDNS is the IP for the built-in DNS server in the slirp network - slirp4netnsDNS = "10.0.2.3" - // slirp4netnsMTU the default MTU override slirp4netnsMTU = 65520 + // default slirp4ns subnet + defaultSlirp4netnsSubnet = "10.0.2.0/24" + // rootlessCNINSName is the file name for the rootless network namespace bind mount rootlessCNINSName = "rootless-cni-ns" ) @@ -360,15 +357,20 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { } // build a new resolv.conf file which uses the slirp4netns dns server address - resolveIP := slirp4netnsDNS + resolveIP, err := GetSlirp4netnsDNS(nil) + if err != nil { + return nil, errors.Wrap(err, "failed to determine default slirp4netns DNS address") + } + if netOptions.cidr != "" { _, cidr, err := net.ParseCIDR(netOptions.cidr) if err != nil { return nil, errors.Wrap(err, "failed to parse slirp4netns cidr") } - // the slirp dns ip is always the third ip in the subnet - cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 3 - resolveIP = cidr.IP.String() + resolveIP, err = GetSlirp4netnsDNS(cidr) + if err != nil { + return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String()) + } } conf, err := resolvconf.Get() if err != nil { @@ -377,7 +379,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { searchDomains := resolvconf.GetSearchDomains(conf.Content) dnsOptions := resolvconf.GetOptions(conf.Content) - _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP}, searchDomains, dnsOptions) + _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP.String()}, searchDomains, dnsOptions) if err != nil { return nil, errors.Wrap(err, "failed to create rootless cni resolv.conf") } @@ -577,7 +579,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { // set up port forwarder for CNI-in-slirp4netns netnsPath := ctr.state.NetNS.Path() // TODO: support slirp4netns port forwarder as well - return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, "") + return r.setupRootlessPortMappingViaRLK(ctr, netnsPath) } return nil } @@ -757,6 +759,15 @@ func getContainerNetNS(ctr *Container) (string, error) { return "", nil } +// isBridgeNetMode checks if the given network mode is bridge. +// It returns nil when it is set to bridge and an error otherwise. +func isBridgeNetMode(n namespaces.NetworkMode) error { + if !n.IsBridge() { + return errors.Wrapf(define.ErrNetworkModeInvalid, "%q is not supported", n) + } + return nil +} + // Reload only works with containers with a configured network. // It will tear down, and then reconfigure, the network of the container. // This is mainly used when a reload of firewall rules wipes out existing @@ -770,8 +781,8 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) ([]*cnitypes.Result, er if ctr.state.NetNS == nil { return nil, errors.Wrapf(define.ErrCtrStateInvalid, "container %s network is not configured, refusing to reload", ctr.ID()) } - if rootless.IsRootless() || ctr.config.NetMode.IsSlirp4netns() { - return nil, errors.Wrapf(define.ErrRootless, "network reload only supported for root containers") + if err := isBridgeNetMode(ctr.config.NetMode); err != nil { + return nil, err } logrus.Infof("Going to reload container %s network", ctr.ID()) @@ -1025,8 +1036,8 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) { // NetworkDisconnect removes a container from the network func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error { // only the bridge mode supports cni networks - if !c.config.NetMode.IsBridge() { - return errors.Errorf("network mode %q is not supported", c.config.NetMode) + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err } networks, err := c.networksByNameIndex() @@ -1086,8 +1097,8 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro // ConnectNetwork connects a container to a given network func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error { // only the bridge mode supports cni networks - if !c.config.NetMode.IsBridge() { - return errors.Errorf("network mode %q is not supported", c.config.NetMode) + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err } networks, err := c.networksByNameIndex() diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index c46dc6972..74d390d29 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -308,15 +308,89 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { return err } + // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself + _, ctr.slirp4netnsSubnet, _ = net.ParseCIDR(defaultSlirp4netnsSubnet) + + // Set slirp4netnsSubnet addresses now that we are pretty sure the command executed + if netOptions.cidr != "" { + ipv4, ipv4network, err := net.ParseCIDR(netOptions.cidr) + if err != nil || ipv4.To4() == nil { + return errors.Errorf("invalid cidr %q", netOptions.cidr) + } + ctr.slirp4netnsSubnet = ipv4network + } + if havePortMapping { if netOptions.isSlirpHostForward { return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket) } - return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, netOptions.cidr) + return r.setupRootlessPortMappingViaRLK(ctr, netnsPath) } + return nil } +// Get expected slirp ipv4 address based on subnet. If subnet is null use default subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsIP(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedIP, err := addToIP(slirpSubnet, uint32(100)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected ip for slirp4netns") + } + return expectedIP, nil +} + +// Get expected slirp Gateway ipv4 address based on subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsGateway(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedGatewayIP, err := addToIP(slirpSubnet, uint32(2)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected gateway ip for slirp4netns") + } + return expectedGatewayIP, nil +} + +// Get expected slirp DNS ipv4 address based on subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsDNS(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedDNSIP, err := addToIP(slirpSubnet, uint32(3)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected dns ip for slirp4netns") + } + return expectedDNSIP, nil +} + +// Helper function to calculate slirp ip address offsets +// Adapted from: https://github.com/signalsciences/ipv4/blob/master/int.go#L12-L24 +func addToIP(subnet *net.IPNet, offset uint32) (*net.IP, error) { + // I have no idea why I have to do this, but if I don't ip is 0 + ipFixed := subnet.IP.To4() + + ipInteger := uint32(ipFixed[3]) | uint32(ipFixed[2])<<8 | uint32(ipFixed[1])<<16 | uint32(ipFixed[0])<<24 + ipNewRaw := ipInteger + offset + // Avoid overflows + if ipNewRaw < ipInteger { + return nil, errors.Errorf("integer overflow while calculating ip address offset, %s + %d", ipFixed, offset) + } + ipNew := net.IPv4(byte(ipNewRaw>>24), byte(ipNewRaw>>16&0xFF), byte(ipNewRaw>>8)&0xFF, byte(ipNewRaw&0xFF)) + if !subnet.Contains(ipNew) { + return nil, errors.Errorf("calculated ip address %s is not within given subnet %s", ipNew.String(), subnet.String()) + } + return &ipNew, nil +} + func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error { prog := filepath.Base(cmd.Path) if len(cmd.Args) > 0 { @@ -363,7 +437,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t return nil } -func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slirp4CIDR string) error { +func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string) error { syncR, syncW, err := os.Pipe() if err != nil { return errors.Wrapf(err, "failed to open pipe") @@ -390,17 +464,11 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slir } } - childIP := slirp4netnsIP - // set the correct childIP when a custom cidr is set - if slirp4CIDR != "" { - _, cidr, err := net.ParseCIDR(slirp4CIDR) - if err != nil { - return errors.Wrap(err, "failed to parse slirp4netns cidr") - } - // the slirp container ip is always the hundredth ip in the subnet - cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 100 - childIP = cidr.IP.String() + slirp4netnsIP, err := GetSlirp4netnsIP(ctr.slirp4netnsSubnet) + if err != nil { + return errors.Wrapf(err, "failed to get slirp4ns ip") } + childIP := slirp4netnsIP.String() outer: for _, r := range ctr.state.NetworkStatus { for _, i := range r.IPs { diff --git a/libpod/options.go b/libpod/options.go index be26ced99..f942d264b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1695,23 +1695,12 @@ func WithUmask(umask string) CtrCreateOption { } // WithSecrets adds secrets to the container -func WithSecrets(secretNames []string) CtrCreateOption { +func WithSecrets(containerSecrets []*ContainerSecret) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return define.ErrCtrFinalized } - manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir()) - if err != nil { - return err - } - for _, name := range secretNames { - secr, err := manager.Lookup(name) - if err != nil { - return err - } - ctr.config.Secrets = append(ctr.config.Secrets, secr) - } - + ctr.config.Secrets = containerSecrets return nil } } @@ -1723,7 +1712,7 @@ func WithEnvSecrets(envSecrets map[string]string) CtrCreateOption { if ctr.valid { return define.ErrCtrFinalized } - manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir()) + manager, err := ctr.runtime.SecretsManager() if err != nil { return err } diff --git a/libpod/runtime.go b/libpod/runtime.go index 80fe92b54..d0bdeb574 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -16,6 +16,7 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/pkg/sysregistriesv2" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" @@ -103,6 +104,8 @@ type Runtime struct { // noStore indicates whether we need to interact with a store or not noStore bool + // secretsManager manages secrets + secretsManager *secrets.SecretsManager } // SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. @@ -1022,6 +1025,18 @@ func (r *Runtime) GetSecretsStorageDir() string { return filepath.Join(r.store.GraphRoot(), "secrets") } +// SecretsManager returns the directory that the secrets manager should take +func (r *Runtime) SecretsManager() (*secrets.SecretsManager, error) { + if r.secretsManager == nil { + manager, err := secrets.NewManager(r.GetSecretsStorageDir()) + if err != nil { + return nil, err + } + r.secretsManager = manager + } + return r.secretsManager, nil +} + func graphRootMounted() bool { f, err := os.OpenFile("/run/.containerenv", os.O_RDONLY, os.ModePerm) if err != nil { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 7d31e392f..4e4b2a8ab 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -366,7 +366,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, err } for _, secr := range ctr.config.Secrets { - err = ctr.extractSecretToCtrStorage(secr.Name) + err = ctr.extractSecretToCtrStorage(secr) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 0364b00a3..79e815490 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -40,25 +40,13 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { - // NOTE: the terms "dangling" and "intermediate" are not used - // consistently across our code base. In libimage, "dangling" means - // that an image has no tags. "intermediate" means that an image is - // dangling and that no other image depends on it (i.e., has no - // children). - // - // While pruning usually refers to "dangling" images, it has always - // removed "intermediate" ones. - defaultOptions := &libimage.RemoveImagesOptions{ - Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"), + pruneOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "containers=false", "readonly=false"), WithSize: true, } - // `image prune --all` means to *also* remove images which are not in - // use by any container. Since image filters are chained, we need to - // do two look ups since the default ones are a subset of all. - unusedOptions := &libimage.RemoveImagesOptions{ - Filters: append(opts.Filter, "containers=false", "readonly=false"), - WithSize: true, + if !opts.All { + pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true") } var pruneReports []*reports.PruneReport @@ -66,16 +54,12 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption // Now prune all images until we converge. numPreviouslyRemovedImages := 1 for { - removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions) - if rmErrors != nil { - return nil, errorhandling.JoinErrors(rmErrors) - } - removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions) + removedImages, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, pruneOptions) if rmErrors != nil { return nil, errorhandling.JoinErrors(rmErrors) } - for _, rmReport := range append(removedDefault, removedUnused...) { + for _, rmReport := range removedImages { r := *rmReport pruneReports = append(pruneReports, &reports.PruneReport{ Id: r.ID, @@ -83,7 +67,7 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption }) } - numRemovedImages := len(removedDefault) + len(removedUnused) + numRemovedImages := len(removedImages) if numRemovedImages+numPreviouslyRemovedImages == 0 { break } diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 1a833332c..33ab280e5 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -71,7 +71,9 @@ func (ic *ContainerEngine) NetworkReload(ctx context.Context, names []string, op report := new(entities.NetworkReloadReport) report.Id = ctr.ID() report.Err = ctr.ReloadNetwork() - if options.All && errors.Cause(report.Err) == define.ErrCtrStateInvalid { + // ignore errors for invalid ctr state and network mode when --all is used + if options.All && (errors.Cause(report.Err) == define.ErrCtrStateInvalid || + errors.Cause(report.Err) == define.ErrNetworkModeInvalid) { continue } reports = append(reports, report) diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index a94c5f5c5..0ac9b5d8d 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -12,7 +12,6 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" - "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" @@ -161,7 +160,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY ) // Create the secret manager before hand - secretsManager, err := secrets.NewManager(ic.Libpod.GetSecretsStorageDir()) + secretsManager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index 764f4a9dc..1e1cbc70f 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "path/filepath" - "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" ) @@ -14,7 +13,7 @@ import ( func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) { data, _ := ioutil.ReadAll(reader) secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } @@ -36,8 +35,7 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader } func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) { - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, nil, err } @@ -71,8 +69,7 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string } func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } @@ -105,8 +102,7 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt toRemove []string reports = []*entities.SecretRmReport{} ) - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 7682367b7..a0f5cc7e6 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -400,7 +400,24 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } if len(s.Secrets) != 0 { - options = append(options, libpod.WithSecrets(s.Secrets)) + manager, err := rt.SecretsManager() + if err != nil { + return nil, err + } + var secrs []*libpod.ContainerSecret + for _, s := range s.Secrets { + secr, err := manager.Lookup(s.Source) + if err != nil { + return nil, err + } + secrs = append(secrs, &libpod.ContainerSecret{ + Secret: secr, + UID: s.UID, + GID: s.GID, + Mode: s.Mode, + }) + } + options = append(options, libpod.WithSecrets(secrs)) } if len(s.EnvSecrets) != 0 { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 2e01d1535..2815bdebb 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -258,7 +258,7 @@ type ContainerStorageConfig struct { RootfsPropagation string `json:"rootfs_propagation,omitempty"` // Secrets are the secrets that will be added to the container // Optional. - Secrets []string `json:"secrets,omitempty"` + Secrets []Secret `json:"secrets,omitempty"` // Volatile specifies whether the container storage can be optimized // at the cost of not syncing all the dirty files in memory. Volatile bool `json:"volatile,omitempty"` @@ -521,6 +521,13 @@ type PortMapping struct { Protocol string `json:"protocol,omitempty"` } +type Secret struct { + Source string + UID uint32 + GID uint32 + Mode uint32 +} + var ( // ErrNoStaticIPRootless is used when a rootless user requests to assign a static IP address // to a pod or container diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index 6974c7614..c82aacbe4 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -66,7 +66,7 @@ var _ = Describe("Podman network connect and disconnect", func() { con := podmanTest.Podman([]string{"network", "disconnect", netName, "test"}) con.WaitWithDefaultTimeout() Expect(con.ExitCode()).ToNot(BeZero()) - Expect(con.ErrorToString()).To(ContainSubstring(`network mode "slirp4netns" is not supported`)) + Expect(con.ErrorToString()).To(ContainSubstring(`"slirp4netns" is not supported: invalid network mode`)) }) It("podman network disconnect", func() { @@ -132,7 +132,7 @@ var _ = Describe("Podman network connect and disconnect", func() { con := podmanTest.Podman([]string{"network", "connect", netName, "test"}) con.WaitWithDefaultTimeout() Expect(con.ExitCode()).ToNot(BeZero()) - Expect(con.ErrorToString()).To(ContainSubstring(`network mode "slirp4netns" is not supported`)) + Expect(con.ErrorToString()).To(ContainSubstring(`"slirp4netns" is not supported: invalid network mode`)) }) It("podman connect on a container that already is connected to the network should error", func() { diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 38f893a43..419748adb 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -88,6 +88,53 @@ var _ = Describe("Podman prune", func() { Expect(podmanTest.NumberOfContainers()).To(Equal(0)) }) + It("podman image prune - remove only dangling images", func() { + session := podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hasNone, _ := session.GrepString("<none>") + Expect(hasNone).To(BeFalse()) + numImages := len(session.OutputToStringArray()) + + // Since there's no dangling image, none should be removed. + session = podmanTest.Podman([]string{"image", "prune", "-f"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(0)) + + // Let's be extra sure that the same number of images is + // reported. + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(numImages)) + + // Now build a new image with dangling intermediate images. + podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") + + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hasNone, _ = session.GrepString("<none>") + Expect(hasNone).To(BeTrue()) // ! we have dangling ones + numImages = len(session.OutputToStringArray()) + + // Since there's at least one dangling image, prune should + // remove them. + session = podmanTest.Podman([]string{"image", "prune", "-f"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + numPrunedImages := len(session.OutputToStringArray()) + Expect(numPrunedImages >= 1).To(BeTrue()) + + // Now make sure that exactly the number of pruned images has + // been removed. + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(numImages - numPrunedImages)) + }) + It("podman image prune skip cache images", func() { podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index f27ded5d2..58538b689 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1669,6 +1669,49 @@ WORKDIR /madethis`, BB) Expect(session.OutputToString()).To(Equal(secretsString)) }) + It("podman run --secret mount with uid, gid, mode options", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // check default permissions + session = podmanTest.Podman([]string{"run", "--secret", "mysecret", "--name", "secr", ALPINE, "ls", "-l", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output := session.OutputToString() + Expect(output).To(ContainSubstring("-r--r--r--")) + Expect(output).To(ContainSubstring("root")) + + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=mount,uid=1000,gid=1001,mode=777", "--name", "secr2", ALPINE, "ls", "-ln", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output = session.OutputToString() + Expect(output).To(ContainSubstring("-rwxrwxrwx")) + Expect(output).To(ContainSubstring("1000")) + Expect(output).To(ContainSubstring("1001")) + }) + + It("podman run --secret with --user", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--secret", "mysecret", "--name", "nonroot", "--user", "200:200", ALPINE, "cat", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(secretsString)) + }) + It("podman run invalid secret option", func() { secretsString := "somesecretdata" secretFilePath := filepath.Join(podmanTest.TempDir, "secret") @@ -1694,6 +1737,11 @@ WORKDIR /madethis`, BB) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) + // mount option with env type + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env,uid=1000", "--name", "secr", ALPINE, "printenv", "mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + // No source given session = podmanTest.Podman([]string{"run", "--secret", "type=env", "--name", "secr", ALPINE, "printenv", "mysecret"}) session.WaitWithDefaultTimeout() diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 9a136ff13..e12c32ef5 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -690,4 +690,18 @@ json-file | f run_podman rm $cid } +@test "podman run no /etc/mtab " { + tmpdir=$PODMAN_TMPDIR/build-test + mkdir -p $tmpdir + + cat >$tmpdir/Dockerfile <<EOF +FROM $IMAGE +RUN rm /etc/mtab +EOF + expected="'/etc/mtab' -> '/proc/mounts'" + run_podman build -t nomtab $tmpdir + run_podman run --rm nomtab stat -c %N /etc/mtab + is "$output" "$expected" "/etc/mtab should be created" +} + # vim: filetype=sh diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 34220829a..1cec50827 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -162,6 +162,27 @@ load helpers done } +@test "podman run with slirp4ns assigns correct gateway address to host.containers.internal" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE grep 'host.containers.internal' /etc/hosts + is "$output" "${CIDR}.2 host.containers.internal" "host.containers.internal should be the cidr+2 address" +} + +@test "podman run with slirp4ns adds correct dns address to resolv.conf" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE grep "${CIDR}" /etc/resolv.conf + is "$output" "nameserver ${CIDR}.3" "resolv.conf should have slirp4netns cidr+3 as a nameserver" +} + +@test "podman run with slirp4ns assigns correct ip address container" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE sh -c "ip address | grep ${CIDR}" + is "$output" ".*inet ${CIDR}.100/24 \+" "container should have slirp4netns cidr+100 assigned to interface" +} + # "network create" now works rootless, with the help of a special container @test "podman network create" { myport=54322 @@ -215,7 +236,6 @@ load helpers @test "podman network reload" { skip_if_remote "podman network reload does not have remote support" - skip_if_rootless "podman network reload does not work rootless" random_1=$(random_string 30) HOST_PORT=12345 @@ -225,29 +245,42 @@ load helpers INDEX1=$PODMAN_TMPDIR/hello.txt echo $random_1 > $INDEX1 + # use default network for root + local netname=podman + # for rootless we have to create a custom network since there is no default network + if is_rootless; then + netname=testnet-$(random_string 10) + run_podman network create $netname + is "$output" ".*/cni/net.d/$netname.conflist" "output of 'network create'" + fi + # Bind-mount this file with a different name to a container running httpd run_podman run -d --name myweb -p "$HOST_PORT:80" \ - -v $INDEX1:/var/www/index.txt \ - -w /var/www \ - $IMAGE /bin/busybox-extras httpd -f -p 80 + --network $netname \ + -v $INDEX1:/var/www/index.txt \ + -w /var/www \ + $IMAGE /bin/busybox-extras httpd -f -p 80 cid=$output - run_podman inspect $cid --format "{{.NetworkSettings.IPAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" ip="$output" - run_podman inspect $cid --format "{{.NetworkSettings.MacAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" mac="$output" # Verify http contents: curl from localhost run curl -s $SERVER/index.txt is "$output" "$random_1" "curl 127.0.0.1:/index.txt" - # flush the CNI iptables here - run iptables -t nat -F CNI-HOSTPORT-DNAT + # rootless cannot modify iptables + if ! is_rootless; then + # flush the CNI iptables here + run iptables -t nat -F CNI-HOSTPORT-DNAT - # check that we cannot curl (timeout after 5 sec) - run timeout 5 curl -s $SERVER/index.txt - if [ "$status" -ne 124 ]; then - die "curl did not timeout, status code: $status" + # check that we cannot curl (timeout after 5 sec) + run timeout 5 curl -s $SERVER/index.txt + if [ "$status" -ne 124 ]; then + die "curl did not timeout, status code: $status" + fi fi # reload the network to recreate the iptables rules @@ -255,9 +288,9 @@ load helpers is "$output" "$cid" "Output does not match container ID" # check that we still have the same mac and ip - run_podman inspect $cid --format "{{.NetworkSettings.IPAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" is "$output" "$ip" "IP address changed after podman network reload" - run_podman inspect $cid --format "{{.NetworkSettings.MacAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" is "$output" "$mac" "MAC address changed after podman network reload" # check that we can still curl @@ -275,6 +308,10 @@ load helpers # cleanup the container run_podman rm -f $cid + + if is_rootless; then + run_podman network rm -f $netname + fi } @test "podman rootless cni adds /usr/sbin to PATH" { |