diff options
Diffstat (limited to 'pkg')
31 files changed, 447 insertions, 660 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index b9b7f6708..67ec52047 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -261,8 +261,13 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C } } - // netMode - nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{string(cc.HostConfig.NetworkMode)}) + // special case for NetworkMode, the podman default is slirp4netns for + // rootless but for better docker compat we want bridge. + netmode := string(cc.HostConfig.NetworkMode) + if netmode == "" || netmode == "default" { + netmode = "bridge" + } + nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{netmode}) if err != nil { return nil, nil, err } @@ -278,6 +283,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C Network: nsmode, PublishPorts: specPorts, NetworkOptions: netOpts, + NoHosts: rtc.Containers.NoHosts, } // network names @@ -438,7 +444,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C cliOpts.Volume = append(cliOpts.Volume, vol) // Extract the destination so we don't add duplicate mounts in // the volumes phase. - splitVol := strings.SplitN(vol, ":", 3) + splitVol := specgen.SplitVolumeString(vol) switch len(splitVol) { case 1: volDestinations[vol] = true diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index f47aa523e..fe17aa1d4 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -70,68 +70,69 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { }() query := struct { - AddHosts string `schema:"extrahosts"` - AdditionalCapabilities string `schema:"addcaps"` - AllPlatforms bool `schema:"allplatforms"` - Annotations string `schema:"annotations"` - AppArmor string `schema:"apparmor"` - BuildArgs string `schema:"buildargs"` - CacheFrom string `schema:"cachefrom"` - CgroupParent string `schema:"cgroupparent"` // nolint - Compression uint64 `schema:"compression"` - ConfigureNetwork string `schema:"networkmode"` - CPPFlags string `schema:"cppflags"` - CpuPeriod uint64 `schema:"cpuperiod"` // nolint - CpuQuota int64 `schema:"cpuquota"` // nolint - CpuSetCpus string `schema:"cpusetcpus"` // nolint - CpuSetMems string `schema:"cpusetmems"` // nolint - CpuShares uint64 `schema:"cpushares"` // nolint - DNSOptions string `schema:"dnsoptions"` - DNSSearch string `schema:"dnssearch"` - DNSServers string `schema:"dnsservers"` - Devices string `schema:"devices"` - Dockerfile string `schema:"dockerfile"` - DropCapabilities string `schema:"dropcaps"` - Envs []string `schema:"setenv"` - Excludes string `schema:"excludes"` - ForceRm bool `schema:"forcerm"` - From string `schema:"from"` - HTTPProxy bool `schema:"httpproxy"` - IdentityLabel bool `schema:"identitylabel"` - Ignore bool `schema:"ignore"` - Isolation string `schema:"isolation"` - Jobs int `schema:"jobs"` // nolint - LabelOpts string `schema:"labelopts"` - Labels string `schema:"labels"` - Layers bool `schema:"layers"` - LogRusage bool `schema:"rusage"` - Manifest string `schema:"manifest"` - MemSwap int64 `schema:"memswap"` - Memory int64 `schema:"memory"` - NamespaceOptions string `schema:"nsoptions"` - NoCache bool `schema:"nocache"` - OSFeatures []string `schema:"osfeature"` - OSVersion string `schema:"osversion"` - OutputFormat string `schema:"outputformat"` - Platform []string `schema:"platform"` - Pull bool `schema:"pull"` - PullPolicy string `schema:"pullpolicy"` - Quiet bool `schema:"q"` - Registry string `schema:"registry"` - Rm bool `schema:"rm"` - RusageLogFile string `schema:"rusagelogfile"` - Remote string `schema:"remote"` - Seccomp string `schema:"seccomp"` - Secrets string `schema:"secrets"` - SecurityOpt string `schema:"securityopt"` - ShmSize int `schema:"shmsize"` - Squash bool `schema:"squash"` - TLSVerify bool `schema:"tlsVerify"` - Tags []string `schema:"t"` - Target string `schema:"target"` - Timestamp int64 `schema:"timestamp"` - Ulimits string `schema:"ulimits"` - UnsetEnvs []string `schema:"unsetenv"` + AddHosts string `schema:"extrahosts"` + AdditionalCapabilities string `schema:"addcaps"` + AdditionalBuildContexts string `schema:"additionalbuildcontexts"` + AllPlatforms bool `schema:"allplatforms"` + Annotations string `schema:"annotations"` + AppArmor string `schema:"apparmor"` + BuildArgs string `schema:"buildargs"` + CacheFrom string `schema:"cachefrom"` + CgroupParent string `schema:"cgroupparent"` // nolint + Compression uint64 `schema:"compression"` + ConfigureNetwork string `schema:"networkmode"` + CPPFlags string `schema:"cppflags"` + CpuPeriod uint64 `schema:"cpuperiod"` // nolint + CpuQuota int64 `schema:"cpuquota"` // nolint + CpuSetCpus string `schema:"cpusetcpus"` // nolint + CpuSetMems string `schema:"cpusetmems"` // nolint + CpuShares uint64 `schema:"cpushares"` // nolint + DNSOptions string `schema:"dnsoptions"` + DNSSearch string `schema:"dnssearch"` + DNSServers string `schema:"dnsservers"` + Devices string `schema:"devices"` + Dockerfile string `schema:"dockerfile"` + DropCapabilities string `schema:"dropcaps"` + Envs []string `schema:"setenv"` + Excludes string `schema:"excludes"` + ForceRm bool `schema:"forcerm"` + From string `schema:"from"` + HTTPProxy bool `schema:"httpproxy"` + IdentityLabel bool `schema:"identitylabel"` + Ignore bool `schema:"ignore"` + Isolation string `schema:"isolation"` + Jobs int `schema:"jobs"` // nolint + LabelOpts string `schema:"labelopts"` + Labels string `schema:"labels"` + Layers bool `schema:"layers"` + LogRusage bool `schema:"rusage"` + Manifest string `schema:"manifest"` + MemSwap int64 `schema:"memswap"` + Memory int64 `schema:"memory"` + NamespaceOptions string `schema:"nsoptions"` + NoCache bool `schema:"nocache"` + OSFeatures []string `schema:"osfeature"` + OSVersion string `schema:"osversion"` + OutputFormat string `schema:"outputformat"` + Platform []string `schema:"platform"` + Pull bool `schema:"pull"` + PullPolicy string `schema:"pullpolicy"` + Quiet bool `schema:"q"` + Registry string `schema:"registry"` + Rm bool `schema:"rm"` + RusageLogFile string `schema:"rusagelogfile"` + Remote string `schema:"remote"` + Seccomp string `schema:"seccomp"` + Secrets string `schema:"secrets"` + SecurityOpt string `schema:"securityopt"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + TLSVerify bool `schema:"tlsVerify"` + Tags []string `schema:"t"` + Target string `schema:"target"` + Timestamp int64 `schema:"timestamp"` + Ulimits string `schema:"ulimits"` + UnsetEnvs []string `schema:"unsetenv"` }{ Dockerfile: "Dockerfile", IdentityLabel: true, @@ -375,6 +376,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { additionalTags = append(additionalTags, possiblyNormalizedTag) } + var additionalBuildContexts = map[string]*buildahDefine.AdditionalBuildContext{} + if _, found := r.URL.Query()["additionalbuildcontexts"]; found { + if err := json.Unmarshal([]byte(query.AdditionalBuildContexts), &additionalBuildContexts); err != nil { + utils.BadRequest(w, "additionalbuildcontexts", query.AdditionalBuildContexts, err) + return + } + } + var buildArgs = map[string]string{} if _, found := r.URL.Query()["buildargs"]; found { if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { @@ -562,12 +571,13 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) buildOptions := buildahDefine.BuildOptions{ - AddCapabilities: addCaps, - AdditionalTags: additionalTags, - Annotations: annotations, - CPPFlags: cppflags, - Args: buildArgs, - AllPlatforms: query.AllPlatforms, + AddCapabilities: addCaps, + AdditionalBuildContexts: additionalBuildContexts, + AdditionalTags: additionalTags, + Annotations: annotations, + CPPFlags: cppflags, + Args: buildArgs, + AllPlatforms: query.AllPlatforms, CommonBuildOpts: &buildah.CommonBuildOptions{ AddHost: addhosts, ApparmorProfile: apparmor, diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 65b9d6cb5..d9ed1c265 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -163,7 +163,6 @@ func ManifestAddV3(w http.ResponseWriter, r *http.Request) { // Wrapper to support 3.x with 4.x libpod query := struct { entities.ManifestAddOptions - Images []string TLSVerify bool `schema:"tlsVerify"` }{} if err := json.NewDecoder(r.Body).Decode(&query); err != nil { diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index b71afc28c..36e61c986 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -77,7 +77,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusInternalServerError, err) return } - query.LogDriver = config.Containers.LogDriver + logDriver = config.Containers.LogDriver } containerEngine := abi.ContainerEngine{Libpod: runtime} @@ -89,7 +89,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { Networks: query.Network, NoHosts: query.NoHosts, Quiet: true, - LogDriver: query.LogDriver, + LogDriver: logDriver, LogOptions: query.LogOptions, StaticIPs: staticIPs, StaticMACs: staticMACs, diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index b4b7c36f6..fe81dc662 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -81,6 +81,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO for _, tag := range options.AdditionalTags { params.Add("t", tag) } + if additionalBuildContexts := options.AdditionalBuildContexts; len(additionalBuildContexts) > 0 { + additionalBuildContextMap, err := jsoniter.Marshal(additionalBuildContexts) + if err != nil { + return nil, err + } + params.Set("additionalbuildcontexts", string(additionalBuildContextMap)) + } if buildArgs := options.Args; len(buildArgs) > 0 { bArgs, err := jsoniter.MarshalToString(buildArgs) if err != nil { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d2fafccb1..8bd84a310 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -616,6 +616,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st ImportPrevious: options.ImportPrevious, Pod: options.Pod, PrintStats: options.PrintStats, + FileLocks: options.FileLocks, } filterFuncs := []libpod.ContainerFilter{ diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 2ce190464..762f0d79a 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -328,7 +328,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } func (se *SystemEngine) Reset(ctx context.Context) error { - return se.Libpod.Reset(ctx) + return nil } func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error { diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index 39989c96b..7b5198d2f 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -53,7 +53,7 @@ func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) ( case entities.RenumberMode: r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts) case entities.ResetMode: - r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts) + r, err = GetRuntimeReset(context.Background(), facts.FlagSet, facts) case entities.MigrateMode: name, flagErr := facts.FlagSet.GetString("new-runtime") if flagErr != nil { diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index ac557e9de..03e7ffb5d 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -9,9 +9,9 @@ import ( "os" "os/signal" "sync" + "syscall" "github.com/containers/common/pkg/cgroups" - "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/namespaces" @@ -37,6 +37,7 @@ type engineOpts struct { migrate bool noStore bool withFDS bool + reset bool config *entities.PodmanConfig } @@ -48,6 +49,7 @@ func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg *entities.Podm migrate: true, noStore: false, withFDS: true, + reset: false, config: cfg, }) } @@ -59,6 +61,7 @@ func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg *entities.P migrate: false, noStore: false, withFDS: false, + reset: false, config: cfg, }) } @@ -70,6 +73,7 @@ func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.Pod migrate: false, noStore: false, withFDS: true, + reset: false, config: cfg, }) } @@ -82,6 +86,7 @@ func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanCo migrate: false, noStore: false, withFDS: true, + reset: false, config: cfg, }) }) @@ -95,6 +100,18 @@ func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.Podm migrate: false, noStore: true, withFDS: true, + reset: false, + config: cfg, + }) +} + +func GetRuntimeReset(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + renumber: false, + migrate: false, + noStore: false, + withFDS: true, + reset: true, config: cfg, }) } @@ -161,6 +178,10 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo } } + if opts.reset { + options = append(options, libpod.WithReset()) + } + if opts.renumber { options = append(options, libpod.WithRenumber()) } @@ -375,7 +396,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin func StartWatcher(rt *libpod.Runtime) { // Setup the signal notifier ch := make(chan os.Signal, 1) - signal.Notify(ch, utils.SIGHUP) + signal.Notify(ch, syscall.SIGHUP) go func() { for { diff --git a/pkg/machine/config.go b/pkg/machine/config.go index d34776714..fcc129338 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -42,7 +42,9 @@ const ( // Running indicates the qemu vm is running. Running Status = "running" // Stopped indicates the vm has stopped. - Stopped Status = "stopped" + Stopped Status = "stopped" + // Starting indicated the vm is in the process of starting + Starting Status = "starting" DefaultMachineName string = "podman-machine-default" ) @@ -62,7 +64,7 @@ var ( DefaultIgnitionUserName = "core" ErrNoSuchVM = errors.New("VM does not exist") ErrVMAlreadyExists = errors.New("VM already exists") - ErrVMAlreadyRunning = errors.New("VM already running") + ErrVMAlreadyRunning = errors.New("VM already running or starting") ErrMultipleActiveVM = errors.New("only one VM can be active at a time") ForwarderBinaryName = "gvproxy" ) @@ -88,6 +90,7 @@ type ListResponse struct { CreatedAt time.Time LastUp time.Time Running bool + Starting bool Stream string VMType string CPUs uint64 @@ -138,14 +141,15 @@ type DistributionDownload interface { Get() *Download } type InspectInfo struct { - ConfigPath VMFile - Created time.Time - Image ImageConfig - LastUp time.Time - Name string - Resources ResourceConfig - SSHConfig SSHConfig - State Status + ConfigPath VMFile + ConnectionInfo ConnectionConfig + Created time.Time + Image ImageConfig + LastUp time.Time + Name string + Resources ResourceConfig + SSHConfig SSHConfig + State Status } func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL { @@ -286,11 +290,11 @@ func NewMachineFile(path string, symlink *string) (*VMFile, error) { // makeSymlink for macOS creates a symlink in $HOME/.podman/ // for a machinefile like a socket func (m *VMFile) makeSymlink(symlink *string) error { - homedir, err := os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { return err } - sl := filepath.Join(homedir, ".podman", *symlink) + sl := filepath.Join(homeDir, ".podman", *symlink) // make the symlink dir and throw away if it already exists if err := os.MkdirAll(filepath.Dir(sl), 0700); err != nil && !errors2.Is(err, os.ErrNotExist) { return err @@ -335,3 +339,9 @@ type SSHConfig struct { // RemoteUsername of the vm user RemoteUsername string } + +// ConnectionConfig contains connections like sockets, etc. +type ConnectionConfig struct { + // PodmanSocket is the exported podman service socket + PodmanSocket *VMFile `json:"PodmanSocket"` +} diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config.go index c17b840d3..248a2f0ad 100644 --- a/pkg/machine/e2e/config.go +++ b/pkg/machine/e2e/config.go @@ -85,6 +85,14 @@ func (ms *machineSession) outputToString() string { return strings.Join(fields, " ") } +// errorToString returns the error output from a session in string form +func (ms *machineSession) errorToString() string { + if ms == nil || ms.Err == nil || ms.Err.Contents() == nil { + return "" + } + return string(ms.Err.Contents()) +} + // newMB constructor for machine test builders func newMB() (*machineTestBuilder, error) { mb := machineTestBuilder{ diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index 2c9de5664..cdf13bb1a 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -2,6 +2,7 @@ package e2e import ( "encoding/json" + "strings" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/pkg/machine/qemu" @@ -86,6 +87,7 @@ var _ = Describe("podman machine stop", func() { var inspectInfo []machine.InspectInfo err = jsoniter.Unmarshal(inspectSession.Bytes(), &inspectInfo) Expect(err).To(BeNil()) + Expect(strings.HasSuffix(inspectInfo[0].ConnectionInfo.PodmanSocket.GetPath(), "podman.sock")) inspect := new(inspectMachine) inspect = inspect.withFormat("{{.Name}}") diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go index 155d39a64..9ee31ac26 100644 --- a/pkg/machine/e2e/ssh_test.go +++ b/pkg/machine/e2e/ssh_test.go @@ -56,5 +56,12 @@ var _ = Describe("podman machine ssh", func() { Expect(err).To(BeNil()) Expect(sshSession).To(Exit(0)) Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS")) + + // keep exit code + sshSession, err = mb.setName(name).setCmd(ssh.withSSHComand([]string{"false"})).run() + Expect(err).To(BeNil()) + Expect(sshSession).To(Exit(1)) + Expect(sshSession.outputToString()).To(Equal("")) + Expect(sshSession.errorToString()).To(Equal("")) }) }) diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index df58b8a1e..77427139a 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -146,13 +146,6 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:s streamType string ) - // This is being hard set to testing. Once podman4 is in the - // fcos trees, we should remove it and re-release at least on - // macs. - // TODO: remove when podman4.0 is in coreos - - imageStream = "podman-testing" //nolint:staticcheck - switch imageStream { case "podman-testing": streamType = "podman-testing" diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index e3fb3b970..1b0d63986 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -831,8 +831,14 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() if err != nil { return "", nil, err } - if state == machine.Running && !opts.Force { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + if state == machine.Running { + if !opts.Force { + return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + } + err := v.Stop(v.Name, machine.StopOptions{}) + if err != nil { + return "", nil, err + } } // Collect all the files that need to be destroyed @@ -904,7 +910,7 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) { } // Check if we can dial it if v.Starting && !bypass { - return "", nil + return machine.Starting, nil } monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { @@ -952,7 +958,8 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { sshDestination := username + "@localhost" port := strconv.Itoa(v.Port) - args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"} + args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"} if len(opts.Args) > 0 { args = append(args, opts.Args...) } else { @@ -1074,8 +1081,11 @@ func getVMInfos() ([]*machine.ListResponse, error) { return err } } - if state == machine.Running { + switch state { + case machine.Running: listEntry.Running = true + case machine.Starting: + listEntry.Starting = true } listed = append(listed, listEntry) @@ -1108,7 +1118,7 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { return false, "", errors.Wrap(err, "error checking VM active") } for _, vm := range vms { - if vm.Running { + if vm.Running || vm.Starting { return true, vm.Name, nil } } @@ -1471,16 +1481,22 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { if err != nil { return nil, err } - + connInfo := new(machine.ConnectionConfig) + podmanSocket, err := v.forwardSocketPath() + if err != nil { + return nil, err + } + connInfo.PodmanSocket = podmanSocket return &machine.InspectInfo{ - ConfigPath: v.ConfigPath, - Created: v.Created, - Image: v.ImageConfig, - LastUp: v.LastUp, - Name: v.Name, - Resources: v.ResourceConfig, - SSHConfig: v.SSHConfig, - State: state, + ConfigPath: v.ConfigPath, + ConnectionInfo: *connInfo, + Created: v.Created, + Image: v.ImageConfig, + LastUp: v.LastUp, + Name: v.Name, + Resources: v.ResourceConfig, + SSHConfig: v.SSHConfig, + State: state, }, nil } diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 06020aded..075f42cb2 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -1312,6 +1312,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) { listEntry.RemoteUsername = vm.RemoteUsername listEntry.Port = vm.Port listEntry.IdentityPath = vm.IdentityPath + listEntry.Starting = false running := vm.isRunning() listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running) diff --git a/pkg/resolvconf/dns/resolvconf.go b/pkg/resolvconf/dns/resolvconf.go deleted file mode 100644 index cb4bd1033..000000000 --- a/pkg/resolvconf/dns/resolvconf.go +++ /dev/null @@ -1,28 +0,0 @@ -// Originally from github.com/docker/libnetwork/resolvconf/dns - -package dns - -import ( - "regexp" -) - -// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. -const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` - -// IPv4Localhost is a regex pattern for IPv4 localhost address range. -const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})` - -var localhostIPRegexp = regexp.MustCompile(IPLocalhost) -var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost) - -// IsLocalhost returns true if ip matches the localhost IP regular expression. -// Used for determining if nameserver settings are being passed which are -// localhost addresses -func IsLocalhost(ip string) bool { - return localhostIPRegexp.MatchString(ip) -} - -// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression. -func IsIPv4Localhost(ip string) bool { - return localhostIPv4Regexp.MatchString(ip) -} diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go deleted file mode 100644 index f23cd61b0..000000000 --- a/pkg/resolvconf/resolvconf.go +++ /dev/null @@ -1,250 +0,0 @@ -// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf. -// Originally from github.com/docker/libnetwork/resolvconf. -package resolvconf - -import ( - "bytes" - "io/ioutil" - "regexp" - "strings" - "sync" - - "github.com/containers/podman/v4/pkg/resolvconf/dns" - "github.com/containers/storage/pkg/ioutils" - "github.com/sirupsen/logrus" -) - -const ( - // DefaultResolvConf points to the default file used for dns configuration on a linux machine - DefaultResolvConf = "/etc/resolv.conf" -) - -var ( - // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS - defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"} - defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"} - ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)` - ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock - // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also - // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants - // -- e.g. other link-local types -- either won't work in containers or are unnecessary. - // For readability and sufficiency for Docker purposes this seemed more reasonable than a - // 1000+ character regexp with exact and complete IPv6 validation - ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\w+)?` - - localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`) - nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`) - nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`) - searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) - optionsRegexp = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`) -) - -var lastModified struct { - sync.Mutex - sha256 string - contents []byte -} - -// File contains the resolv.conf content and its hash -type File struct { - Content []byte - Hash string -} - -// Get returns the contents of /etc/resolv.conf and its hash -func Get() (*File, error) { - return GetSpecific(DefaultResolvConf) -} - -// GetSpecific returns the contents of the user specified resolv.conf file and its hash -func GetSpecific(path string) (*File, error) { - resolv, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - hash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - return &File{Content: resolv, Hash: hash}, nil -} - -// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash -// and, if modified since last check, returns the bytes and new hash. -// This feature is used by the resolv.conf updater for containers -func GetIfChanged() (*File, error) { - lastModified.Lock() - defer lastModified.Unlock() - - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, err - } - newHash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - if lastModified.sha256 != newHash { - lastModified.sha256 = newHash - lastModified.contents = resolv - return &File{Content: resolv, Hash: newHash}, nil - } - // nothing changed, so return no data - return nil, nil -} - -// GetLastModified retrieves the last used contents and hash of the host resolv.conf. -// Used by containers updating on restart -func GetLastModified() *File { - lastModified.Lock() - defer lastModified.Unlock() - - return &File{Content: lastModified.contents, Hash: lastModified.sha256} -} - -// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: -// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided -// resolv.conf, removing local nameserver entries, and, if the resulting -// cleaned config has no defined nameservers left, adds default DNS entries -// 2. Given the caller provides the enable/disable state of IPv6, the filter -// code will remove all IPv6 nameservers if it is not enabled for containers -// -func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) { - // If we're using the host netns, we have nothing to do besides hash the file. - if !netnsEnabled { - hash, err := ioutils.HashData(bytes.NewReader(resolvConf)) - if err != nil { - return nil, err - } - return &File{Content: resolvConf, Hash: hash}, nil - } - cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) - // if IPv6 is not enabled, also clean out any IPv6 address nameserver - if !ipv6Enabled { - cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{}) - } - // if the resulting resolvConf has no more nameservers defined, add appropriate - // default DNS servers for IPv4 and (optionally) IPv6 - if len(GetNameservers(cleanedResolvConf)) == 0 { - logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns) - dns := defaultIPv4Dns - if ipv6Enabled { - logrus.Infof("IPv6 enabled; Adding default IPv6 external servers: %v", defaultIPv6Dns) - dns = append(dns, defaultIPv6Dns...) - } - cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) - } - hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf)) - if err != nil { - return nil, err - } - return &File{Content: cleanedResolvConf, Hash: hash}, nil -} - -// getLines parses input into lines and strips away comments. -func getLines(input []byte, commentMarker []byte) [][]byte { - lines := bytes.Split(input, []byte("\n")) - var output [][]byte - for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) - if commentIndex == -1 { - output = append(output, currentLine) - } else { - output = append(output, currentLine[:commentIndex]) - } - } - return output -} - -// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf -func GetNameservers(resolvConf []byte) []string { - nameservers := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { - ns := nsRegexp.FindSubmatch(line) - if len(ns) > 0 { - nameservers = append(nameservers, string(ns[1])) - } - } - return nameservers -} - -// GetNameserversAsCIDR returns nameservers (if any) listed in -// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") -// This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { - nameservers := []string{} - for _, nameserver := range GetNameservers(resolvConf) { - var address string - // If IPv6, strip zone if present - if strings.Contains(nameserver, ":") { - address = strings.Split(nameserver, "%")[0] + "/128" - } else { - address = nameserver + "/32" - } - nameservers = append(nameservers, address) - } - return nameservers -} - -// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf -// If more than one search line is encountered, only the contents of the last -// one is returned. -func GetSearchDomains(resolvConf []byte) []string { - domains := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { - match := searchRegexp.FindSubmatch(line) - if match == nil { - continue - } - domains = strings.Fields(string(match[1])) - } - return domains -} - -// GetOptions returns options (if any) listed in /etc/resolv.conf -// If more than one options line is encountered, only the contents of the last -// one is returned. -func GetOptions(resolvConf []byte) []string { - options := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { - match := optionsRegexp.FindSubmatch(line) - if match == nil { - continue - } - options = strings.Fields(string(match[1])) - } - return options -} - -// Build writes a configuration file to path containing a "nameserver" entry -// for every element in dns, a "search" entry for every element in -// dnsSearch, and an "options" entry for every element in dnsOptions. -func Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) { - content := bytes.NewBuffer(nil) - if len(dnsSearch) > 0 { - if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { - if _, err := content.WriteString("search " + searchString + "\n"); err != nil { - return nil, err - } - } - } - for _, dns := range dns { - if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { - return nil, err - } - } - if len(dnsOptions) > 0 { - if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { - if _, err := content.WriteString("options " + optsString + "\n"); err != nil { - return nil, err - } - } - } - - hash, err := ioutils.HashData(bytes.NewReader(content.Bytes())) - if err != nil { - return nil, err - } - - return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644) -} diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 532a2094f..5616a4511 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -183,10 +183,12 @@ func (s *SpecGenerator) Validate() error { } // Set defaults if network info is not provided - if s.NetNS.NSMode == "" { - s.NetNS.NSMode = Bridge + // when we are rootless we default to slirp4netns + if s.NetNS.IsPrivate() || s.NetNS.IsDefault() { if rootless.IsRootless() { s.NetNS.NSMode = Slirp + } else { + s.NetNS.NSMode = Bridge } } if err := validateNetNS(&s.NetNS); err != nil { diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index ed2e5408d..4c3748e67 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -3,7 +3,6 @@ package generate import ( "fmt" "io/fs" - "io/ioutil" "os" "path" "path/filepath" @@ -11,6 +10,7 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/rootless" + "github.com/containers/podman/v4/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" @@ -18,56 +18,6 @@ import ( "golang.org/x/sys/unix" ) -var ( - errNotADevice = errors.New("not a device node") -) - -func addPrivilegedDevices(g *generate.Generator) error { - hostDevices, err := getDevices("/dev") - if err != nil { - return err - } - g.ClearLinuxDevices() - - if rootless.IsRootless() { - mounts := make(map[string]interface{}) - for _, m := range g.Mounts() { - mounts[m.Destination] = true - } - newMounts := []spec.Mount{} - for _, d := range hostDevices { - devMnt := spec.Mount{ - Destination: d.Path, - Type: define.TypeBind, - Source: d.Path, - Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, - } - if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { - continue - } - if _, found := mounts[d.Path]; found { - continue - } - newMounts = append(newMounts, devMnt) - } - g.Config.Mounts = append(newMounts, g.Config.Mounts...) - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - } else { - for _, d := range hostDevices { - g.AddDevice(d) - } - // Add resources device - need to clear the existing one first. - if g.Config.Linux.Resources != nil { - g.Config.Linux.Resources.Devices = nil - } - g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") - } - - return nil -} - // DevicesFromPath computes a list of devices func DevicesFromPath(g *generate.Generator, devicePath string) error { devs := strings.Split(devicePath, ":") @@ -174,60 +124,12 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask } } -// based on getDevices from runc (libcontainer/devices/devices.go) -func getDevices(path string) ([]spec.LinuxDevice, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - if rootless.IsRootless() && os.IsPermission(err) { - return nil, nil - } - return nil, err - } - out := []spec.LinuxDevice{} - for _, f := range files { - switch { - case f.IsDir(): - switch f.Name() { - // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 - case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": - continue - default: - sub, err := getDevices(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - if sub != nil { - out = append(out, sub...) - } - continue - } - case f.Name() == "console": - continue - case f.Mode()&os.ModeSymlink != 0: - continue - } - - device, err := deviceFromPath(filepath.Join(path, f.Name())) - if err != nil { - if err == errNotADevice { - continue - } - if os.IsNotExist(err) { - continue - } - return nil, err - } - out = append(out, *device) - } - return out, nil -} - func addDevice(g *generate.Generator, device string) error { src, dst, permissions, err := ParseDevice(device) if err != nil { return err } - dev, err := deviceFromPath(src) + dev, err := util.DeviceFromPath(src) if err != nil { return errors.Wrapf(err, "%s is not a valid device", src) } @@ -316,43 +218,6 @@ func IsValidDeviceMode(mode string) bool { return true } -// Copied from github.com/opencontainers/runc/libcontainer/devices -// Given the path to a device look up the information about a linux device -func deviceFromPath(path string) (*spec.LinuxDevice, error) { - var stat unix.Stat_t - err := unix.Lstat(path, &stat) - if err != nil { - return nil, err - } - var ( - devType string - mode = stat.Mode - devNumber = uint64(stat.Rdev) // nolint: unconvert - m = os.FileMode(mode) - ) - - switch { - case mode&unix.S_IFBLK == unix.S_IFBLK: - devType = "b" - case mode&unix.S_IFCHR == unix.S_IFCHR: - devType = "c" - case mode&unix.S_IFIFO == unix.S_IFIFO: - devType = "p" - default: - return nil, errNotADevice - } - - return &spec.LinuxDevice{ - Type: devType, - Path: path, - FileMode: &m, - UID: &stat.Uid, - GID: &stat.Gid, - Major: int64(unix.Major(devNumber)), - Minor: int64(unix.Minor(devNumber)), - }, nil -} - func supportAmbientCapabilities() bool { err := unix.Prctl(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, 0, 0, 0) return err == nil diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 04e24d625..7faf13465 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -278,6 +278,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithPasswdEntry(s.PasswdEntry)) } + if s.Privileged { + options = append(options, libpod.WithMountAllDevices()) + } + useSystemd := false switch s.Systemd { case "always": @@ -542,6 +546,16 @@ func Inherit(infra libpod.Container, s *specgen.SpecGenerator, rt *libpod.Runtim infraConf := infra.Config() infraSpec := infraConf.Spec + // need to set compatOptions to the currently filled specgenOptions so we do not overwrite + compatibleOptions.CapAdd = append(compatibleOptions.CapAdd, s.CapAdd...) + compatibleOptions.CapDrop = append(compatibleOptions.CapDrop, s.CapDrop...) + compatibleOptions.HostDeviceList = append(compatibleOptions.HostDeviceList, s.HostDeviceList...) + compatibleOptions.ImageVolumes = append(compatibleOptions.ImageVolumes, s.ImageVolumes...) + compatibleOptions.Mounts = append(compatibleOptions.Mounts, s.Mounts...) + compatibleOptions.OverlayVolumes = append(compatibleOptions.OverlayVolumes, s.OverlayVolumes...) + compatibleOptions.SelinuxOpts = append(compatibleOptions.SelinuxOpts, s.SelinuxOpts...) + compatibleOptions.Volumes = append(compatibleOptions.Volumes, s.Volumes...) + compatByte, err := json.Marshal(compatibleOptions) if err != nil { return nil, nil, nil, err diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 37d561ec2..4224d16ce 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -19,6 +19,8 @@ import ( "github.com/sirupsen/logrus" ) +const host = "host" + // Get the default namespace mode for any given namespace type. func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) { // The default for most is private @@ -33,16 +35,38 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) podMode := false switch { case nsType == "pid" && pod.SharesPID(): + if pod.NamespaceMode(spec.PIDNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "ipc" && pod.SharesIPC(): + if pod.NamespaceMode(spec.IPCNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "uts" && pod.SharesUTS(): + if pod.NamespaceMode(spec.UTSNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "user" && pod.SharesUser(): + // user does not need a special check for host, this is already validated on pod creation + // if --userns=host then pod.SharesUser == false podMode = true case nsType == "net" && pod.SharesNet(): + if pod.NetworkMode() == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true case nsType == "cgroup" && pod.SharesCgroup(): + if pod.NamespaceMode(spec.CgroupNamespace) == host { + toReturn.NSMode = specgen.Host + return toReturn, nil + } podMode = true } if podMode { @@ -236,10 +260,12 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod. toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode)) } - // Net - // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we - // are in bridge mode. postConfigureNetNS := !s.UserNS.IsHost() + // when we are rootless we default to slirp4netns + if rootless.IsRootless() && (s.NetNS.IsPrivate() || s.NetNS.IsDefault()) { + s.NetNS.NSMode = specgen.Slirp + } + switch s.NetNS.NSMode { case specgen.FromPod: if pod == nil || infraCtr == nil { @@ -262,9 +288,7 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod. val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) } toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil)) - case specgen.Private: - fallthrough - case specgen.Bridge: + case specgen.Bridge, specgen.Private, specgen.Default: portMappings, expose, err := createPortMappings(s, imageData) if err != nil { return nil, err @@ -488,10 +512,7 @@ func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOptio case "cgroup": options = append(options, libpod.WithPodCgroup()) case "net": - // share the netns setting with other containers in the pod only when it is not set to host - if !netnsIsHost { - options = append(options, libpod.WithPodNet()) - } + options = append(options, libpod.WithPodNet()) case "mnt": return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") case "pid": diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index dda2de6e4..716960024 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -337,14 +337,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } var userDevices []spec.LinuxDevice - if s.Privileged { - // If privileged, we need to add all the host devices to the - // spec. We do not add the user provided ones because we are - // already adding them all. - if err := addPrivilegedDevices(&g); err != nil { - return nil, err - } - } else { + + if !s.Privileged { // add default devices from containers.conf for _, device := range rtc.Containers.Devices { if err = DevicesFromPath(&g, device); err != nil { diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 5b7bb2b57..d4f281a11 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -141,6 +141,9 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) { case specgen.Bridge: p.InfraContainerSpec.NetNS.NSMode = specgen.Bridge logrus.Debugf("Pod using bridge network mode") + case specgen.Private: + p.InfraContainerSpec.NetNS.NSMode = specgen.Private + logrus.Debugf("Pod will use default network mode") case specgen.Host: logrus.Debugf("Pod will use host networking") if len(p.InfraContainerSpec.PortMappings) > 0 || @@ -151,15 +154,15 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) { p.InfraContainerSpec.NetNS.NSMode = specgen.Host case specgen.Slirp: logrus.Debugf("Pod will use slirp4netns") - if p.InfraContainerSpec.NetNS.NSMode != "host" { + if p.InfraContainerSpec.NetNS.NSMode != specgen.Host { p.InfraContainerSpec.NetworkOptions = p.NetworkOptions - p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns") + p.InfraContainerSpec.NetNS.NSMode = specgen.Slirp } case specgen.NoNetwork: logrus.Debugf("Pod will not use networking") if len(p.InfraContainerSpec.PortMappings) > 0 || len(p.InfraContainerSpec.Networks) > 0 || - p.InfraContainerSpec.NetNS.NSMode == "host" { + p.InfraContainerSpec.NetNS.NSMode == specgen.Host { return nil, errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified") } p.InfraContainerSpec.NetNS.NSMode = specgen.NoNetwork diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 5a3b94ca4..f1343f6e2 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -10,7 +10,6 @@ import ( "github.com/containers/common/pkg/cgroups" cutil "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -319,62 +318,6 @@ func ParseUserNamespace(ns string) (Namespace, error) { return ParseNamespace(ns) } -// ParseNetworkNamespace parses a network namespace specification in string -// form. -// Returns a namespace and (optionally) a list of CNI networks to join. -func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, map[string]types.PerNetworkOptions, error) { - toReturn := Namespace{} - networks := make(map[string]types.PerNetworkOptions) - // Net defaults to Slirp on rootless - switch { - case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): - toReturn.NSMode = Slirp - case ns == string(FromPod): - toReturn.NSMode = FromPod - case ns == "" || ns == string(Default) || ns == string(Private): - if rootless.IsRootless() { - if rootlessDefaultCNI { - toReturn.NSMode = Bridge - } else { - toReturn.NSMode = Slirp - } - } else { - toReturn.NSMode = Bridge - } - case ns == string(Bridge): - toReturn.NSMode = Bridge - case ns == string(NoNetwork): - toReturn.NSMode = NoNetwork - case ns == string(Host): - toReturn.NSMode = Host - case strings.HasPrefix(ns, "ns:"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying \"ns:\"") - } - toReturn.NSMode = Path - toReturn.Value = split[1] - case strings.HasPrefix(ns, string(FromContainer)+":"): - split := strings.SplitN(ns, ":", 2) - if len(split) != 2 { - return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying \"container:\"") - } - toReturn.NSMode = FromContainer - toReturn.Value = split[1] - default: - // Assume we have been given a list of CNI networks. - // Which only works in bridge mode, so set that. - networkList := strings.Split(ns, ",") - for _, net := range networkList { - networks[net] = types.PerNetworkOptions{} - } - - toReturn.NSMode = Bridge - } - - return toReturn, networks, nil -} - // ParseNetworkFlag parses a network string slice into the network options // If the input is nil or empty it will use the default setting from containers.conf func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) { @@ -400,13 +343,7 @@ func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetwork case ns == string(FromPod): toReturn.NSMode = FromPod case ns == "" || ns == string(Default) || ns == string(Private): - // Net defaults to Slirp on rootless - if rootless.IsRootless() { - toReturn.NSMode = Slirp - break - } - // if root we use bridge - fallthrough + toReturn.NSMode = Private case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"): toReturn.NSMode = Bridge parts := strings.SplitN(ns, ":", 2) diff --git a/pkg/specgen/namespaces_test.go b/pkg/specgen/namespaces_test.go index 368c92bd5..d03a6d032 100644 --- a/pkg/specgen/namespaces_test.go +++ b/pkg/specgen/namespaces_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/containers/common/libnetwork/types" - "github.com/containers/podman/v4/pkg/rootless" "github.com/stretchr/testify/assert" ) @@ -17,14 +16,6 @@ func parsMacNoErr(mac string) types.HardwareAddr { func TestParseNetworkFlag(t *testing.T) { // root and rootless have different defaults defaultNetName := "default" - defaultNetworks := map[string]types.PerNetworkOptions{ - defaultNetName: {}, - } - defaultNsMode := Namespace{NSMode: Bridge} - if rootless.IsRootless() { - defaultNsMode = Namespace{NSMode: Slirp} - defaultNetworks = map[string]types.PerNetworkOptions{} - } tests := []struct { name string @@ -37,26 +28,26 @@ func TestParseNetworkFlag(t *testing.T) { { name: "empty input", args: nil, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "empty string as input", args: []string{}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "default mode", args: []string{"default"}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "private mode", args: []string{"private"}, - nsmode: defaultNsMode, - networks: defaultNetworks, + nsmode: Namespace{NSMode: Private}, + networks: map[string]types.PerNetworkOptions{}, }, { name: "bridge mode", diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 603506241..777097ac5 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -4,6 +4,7 @@ import ( "net" "github.com/containers/common/libnetwork/types" + storageTypes "github.com/containers/storage/types" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -222,6 +223,10 @@ type PodResourceConfig struct { type PodSecurityConfig struct { SecurityOpt []string `json:"security_opt,omitempty"` + // IDMappings are UID and GID mappings that will be used by user + // namespaces. + // Required if UserNS is private. + IDMappings *storageTypes.IDMappingOptions `json:"idmappings,omitempty"` } // NewPodSpecGenerator creates a new pod spec diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index b26666df3..a7a1022b0 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -97,6 +97,8 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na // This is not a named volume overlayFlag := false chownFlag := false + upperDirFlag := false + workDirFlag := false for _, o := range options { if o == "O" { overlayFlag = true @@ -105,8 +107,16 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na if strings.Contains(joinedOpts, "U") { chownFlag = true } - - if len(options) > 2 || (len(options) == 2 && !chownFlag) { + if strings.Contains(joinedOpts, "upperdir") { + upperDirFlag = true + } + if strings.Contains(joinedOpts, "workdir") { + workDirFlag = true + } + if (workDirFlag && !upperDirFlag) || (!workDirFlag && upperDirFlag) { + return nil, nil, nil, errors.New("must set both `upperdir` and `workdir`") + } + if len(options) > 2 && !(len(options) == 3 && upperDirFlag && workDirFlag) || (len(options) == 2 && !chownFlag) { return nil, nil, nil, errors.New("can't use 'O' with other options") } } diff --git a/pkg/specgenutil/createparse.go b/pkg/specgenutil/createparse.go index fb5f9c351..132f93771 100644 --- a/pkg/specgenutil/createparse.go +++ b/pkg/specgenutil/createparse.go @@ -18,20 +18,5 @@ func validate(c *entities.ContainerCreateOptions) error { return err } - var imageVolType = map[string]string{ - "bind": "", - "tmpfs": "", - "ignore": "", - } - if _, ok := imageVolType[c.ImageVolume]; !ok { - switch { - case c.IsInfra: - c.ImageVolume = "bind" - case c.IsClone: // the image volume type will be deduced later from the container we are cloning - return nil - default: - return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume) - } - } - return nil + return config.ValidateImageVolumeMode(c.ImageVolume) } diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 9cb2f200b..6d70af106 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -229,9 +229,11 @@ func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) } func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error { - var ( - err error - ) + rtc, err := config.Default() + if err != nil { + return err + } + // validate flags as needed if err := validate(c); err != nil { return err @@ -479,8 +481,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 { s.HostUsers = c.HostUsers } - if len(s.ImageVolumeMode) == 0 || len(c.ImageVolume) != 0 { - s.ImageVolumeMode = c.ImageVolume + if len(c.ImageVolume) != 0 { + if len(s.ImageVolumeMode) == 0 { + s.ImageVolumeMode = c.ImageVolume + } + } + if len(s.ImageVolumeMode) == 0 { + s.ImageVolumeMode = rtc.Engine.ImageVolumeMode } if s.ImageVolumeMode == "bind" { s.ImageVolumeMode = "anonymous" @@ -550,11 +557,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions s.CgroupsMode = c.CgroupsMode } if s.CgroupsMode == "" { - rtc, err := config.Default() - if err != nil { - return err - } - s.CgroupsMode = rtc.Cgroups() } @@ -622,7 +624,14 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if opt == "no-new-privileges" { s.ContainerSecurityConfig.NoNewPrivileges = true } else { - con := strings.SplitN(opt, "=", 2) + // Docker deprecated the ":" syntax but still supports it, + // so we need to as well + var con []string + if strings.Contains(opt, "=") { + con = strings.SplitN(opt, "=", 2) + } else { + con = strings.SplitN(opt, ":", 2) + } if len(con) != 2 { return fmt.Errorf("invalid --security-opt 1: %q", opt) } @@ -650,6 +659,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions } case "unmask": s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...) + case "no-new-privileges": + noNewPrivileges, err := strconv.ParseBool(con[1]) + if err != nil { + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go index 0b21bf3c5..871303f64 100644 --- a/pkg/util/utils_linux.go +++ b/pkg/util/utils_linux.go @@ -3,13 +3,24 @@ package util import ( "fmt" "io/fs" + "io/ioutil" "os" "path/filepath" + "strings" "syscall" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/psgo" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +var ( + errNotADevice = errors.New("not a device node") ) // GetContainerPidInformationDescriptors returns a string slice of all supported @@ -59,3 +70,134 @@ func FindDeviceNodes() (map[string]string, error) { return nodes, nil } + +func AddPrivilegedDevices(g *generate.Generator) error { + hostDevices, err := getDevices("/dev") + if err != nil { + return err + } + g.ClearLinuxDevices() + + if rootless.IsRootless() { + mounts := make(map[string]interface{}) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: define.TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + } else { + for _, d := range hostDevices { + g.AddDevice(d) + } + // Add resources device - need to clear the existing one first. + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + } + + return nil +} + +// based on getDevices from runc (libcontainer/devices/devices.go) +func getDevices(path string) ([]spec.LinuxDevice, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + if rootless.IsRootless() && os.IsPermission(err) { + return nil, nil + } + return nil, err + } + out := []spec.LinuxDevice{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + if sub != nil { + out = append(out, sub...) + } + continue + } + case f.Name() == "console": + continue + case f.Mode()&os.ModeSymlink != 0: + continue + } + + device, err := DeviceFromPath(filepath.Join(path, f.Name())) + if err != nil { + if err == errNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + +// Copied from github.com/opencontainers/runc/libcontainer/devices +// Given the path to a device look up the information about a linux device +func DeviceFromPath(path string) (*spec.LinuxDevice, error) { + var stat unix.Stat_t + err := unix.Lstat(path, &stat) + if err != nil { + return nil, err + } + var ( + devType string + mode = stat.Mode + devNumber = uint64(stat.Rdev) // nolint: unconvert + m = os.FileMode(mode) + ) + + switch { + case mode&unix.S_IFBLK == unix.S_IFBLK: + devType = "b" + case mode&unix.S_IFCHR == unix.S_IFCHR: + devType = "c" + case mode&unix.S_IFIFO == unix.S_IFIFO: + devType = "p" + default: + return nil, errNotADevice + } + + return &spec.LinuxDevice{ + Type: devType, + Path: path, + FileMode: &m, + UID: &stat.Uid, + GID: &stat.Gid, + Major: int64(unix.Major(devNumber)), + Minor: int64(unix.Minor(devNumber)), + }, nil +} |