diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/runtime.go | 4 | ||||
-rw-r--r-- | pkg/adapter/runtime_remote.go | 6 | ||||
-rw-r--r-- | pkg/network/config.go | 1 | ||||
-rw-r--r-- | pkg/rootless/rootless.go | 3 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 2 | ||||
-rw-r--r-- | pkg/rootlessport/rootlessport_linux.go | 262 | ||||
-rw-r--r-- | pkg/spec/namespaces.go | 20 | ||||
-rw-r--r-- | pkg/spec/security.go | 6 | ||||
-rw-r--r-- | pkg/util/utils.go | 2 | ||||
-rw-r--r-- | pkg/varlinkapi/images.go | 12 | ||||
-rw-r--r-- | pkg/varlinkapi/virtwriter/virtwriter.go | 10 |
11 files changed, 321 insertions, 7 deletions
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index dd4f0f35f..8933e826f 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -84,7 +84,7 @@ func getRuntime(runtime *libpod.Runtime) (*LocalRuntime, error) { }, nil } -// GetFilterImages returns a slice of images in containerimages that are "filtered" +// GetFilteredImages returns a slice of images in containerimages that are "filtered" func (r *LocalRuntime) GetFilteredImages(filters []string, rwOnly bool) ([]*ContainerImage, error) { images, err := r.ImageRuntime().GetImagesWithFilters(filters) if err != nil { @@ -111,6 +111,8 @@ func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) { return r.ImagestoContainerImages(images, rwOnly) } +// ImagestoContainerImages converts the slice of *image.Image to a slice of +// *ContainerImage. ReadOnly images are skipped when rwOnly is set. func (r *LocalRuntime) ImagestoContainerImages(images []*image.Image, rwOnly bool) ([]*ContainerImage, error) { var containerImages []*ContainerImage for _, i := range images { diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index fe5cc4fef..9c10b31c0 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -413,6 +413,12 @@ func (ci *ContainerImage) TagImage(tag string) error { return err } +// UntagImage removes a single tag from an image +func (ci *ContainerImage) UntagImage(tag string) error { + _, err := iopodman.UntagImage().Call(ci.Runtime.Conn, ci.ID(), tag) + return err +} + // RemoveImage calls varlink to remove an image func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (*image.ImageDeleteResponse, error) { ir := image.ImageDeleteResponse{} diff --git a/pkg/network/config.go b/pkg/network/config.go index e47b16143..a41455f68 100644 --- a/pkg/network/config.go +++ b/pkg/network/config.go @@ -90,6 +90,7 @@ func (p PortMapConfig) Bytes() ([]byte, error) { return json.MarshalIndent(p, "", "\t") } +// IPAMDHCP describes the ipamdhcp config type IPAMDHCP struct { DHCP string `json:"type"` } diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go index 7e9fe9db6..d02721ea9 100644 --- a/pkg/rootless/rootless.go +++ b/pkg/rootless/rootless.go @@ -7,6 +7,9 @@ import ( "github.com/pkg/errors" ) +// TryJoinPauseProcess attempts to join the namespaces of the pause PID via +// TryJoinFromFilePaths. If joining fails, it attempts to delete the specified +// file. func TryJoinPauseProcess(pausePidPath string) (bool, int, error) { if _, err := os.Stat(pausePidPath); err != nil { return false, -1, nil diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 94c42f7d0..182a39f6b 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -514,6 +514,8 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st return joinUserAndMountNS(uint(pausePid), pausePidPath) } + +// ReadMappingsProc parses and returns the ID mappings at the specified path. func ReadMappingsProc(path string) ([]idtools.IDMap, error) { file, err := os.Open(path) if err != nil { diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go new file mode 100644 index 000000000..655d1a448 --- /dev/null +++ b/pkg/rootlessport/rootlessport_linux.go @@ -0,0 +1,262 @@ +// +build linux + +// Package rootlessport provides reexec for RootlessKit-based port forwarder. +// +// init() contains reexec.Register() for ReexecKey . +// +// The reexec requires Config to be provided via stdin. +// +// The reexec writes human-readable error message on stdout on error. +// +// Debug log is printed on stderr. +package rootlessport + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "syscall" + + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/storage/pkg/reexec" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + rkport "github.com/rootless-containers/rootlesskit/pkg/port" + rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" + rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" + "github.com/sirupsen/logrus" +) + +const ( + // ReexecKey is the reexec key for the parent process. + ReexecKey = "containers-rootlessport" + // reexecChildKey is used internally for the second reexec + reexecChildKey = "containers-rootlessport-child" + reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE" +) + +// Config needs to be provided to the process via stdin as a JSON string. +// stdin needs to be closed after the message has been written. +type Config struct { + Mappings []ocicni.PortMapping + NetNSPath string + ExitFD int + ReadyFD int +} + +func init() { + reexec.Register(ReexecKey, func() { + if err := parent(); err != nil { + fmt.Println(err) + os.Exit(1) + } + }) + reexec.Register(reexecChildKey, func() { + if err := child(); err != nil { + fmt.Println(err) + os.Exit(1) + } + }) + +} + +func loadConfig(r io.Reader) (*Config, io.ReadCloser, io.WriteCloser, error) { + stdin, err := ioutil.ReadAll(r) + if err != nil { + return nil, nil, nil, err + } + var cfg Config + if err := json.Unmarshal(stdin, &cfg); err != nil { + return nil, nil, nil, err + } + if cfg.NetNSPath == "" { + return nil, nil, nil, errors.New("missing NetNSPath") + } + if cfg.ExitFD <= 0 { + return nil, nil, nil, errors.New("missing ExitFD") + } + exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile") + if exitFile == nil { + return nil, nil, nil, errors.New("invalid ExitFD") + } + if cfg.ReadyFD <= 0 { + return nil, nil, nil, errors.New("missing ReadyFD") + } + readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile") + if readyFile == nil { + return nil, nil, nil, errors.New("invalid ReadyFD") + } + return &cfg, exitFile, readyFile, nil +} + +func parent() error { + // load config from stdin + cfg, exitR, readyW, err := loadConfig(os.Stdin) + if err != nil { + return err + } + + // create the parent driver + stateDir, err := ioutil.TempDir("", "rootlessport") + if err != nil { + return err + } + driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir) + if err != nil { + return err + } + initComplete := make(chan struct{}) + quit := make(chan struct{}) + errCh := make(chan error) + // start the parent driver. initComplete will be closed when the child connected to the parent. + logrus.Infof("starting parent driver") + go func() { + driverErr := driver.RunParentDriver(initComplete, quit, nil) + if driverErr != nil { + logrus.WithError(driverErr).Warn("parent driver exited") + } + errCh <- driverErr + }() + opaque := driver.OpaqueForChild() + logrus.Infof("opaque=%+v", opaque) + opaqueJSON, err := json.Marshal(opaque) + if err != nil { + return err + } + childQuitR, childQuitW, err := os.Pipe() + if err != nil { + return err + } + defer func() { + // stop the child + logrus.Info("stopping child driver") + if err := childQuitW.Close(); err != nil { + logrus.WithError(err).Warn("unable to close childQuitW") + } + }() + + // reexec the child process in the child netns + cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid())) + cmd.Args = []string{reexecChildKey} + cmd.Stdin = childQuitR + cmd.Stdout = &logrusWriter{prefix: "child"} + cmd.Stderr = cmd.Stdout + cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON)) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + } + childNS, err := ns.GetNS(cfg.NetNSPath) + if err != nil { + return err + } + if err := childNS.Do(func(_ ns.NetNS) error { + logrus.Infof("starting child driver in child netns (%q %v)", cmd.Path, cmd.Args) + return cmd.Start() + }); err != nil { + return err + } + + logrus.Info("waiting for initComplete") + // wait for the child to connect to the parent + select { + case <-initComplete: + logrus.Infof("initComplete is closed; parent and child established the communication channel") + case err := <-errCh: + return err + } + defer func() { + logrus.Info("stopping parent driver") + quit <- struct{}{} + if err := <-errCh; err != nil { + logrus.WithError(err).Warn("parent driver returned error on exit") + } + }() + + // let parent expose ports + logrus.Infof("exposing ports %v", cfg.Mappings) + if err := exposePorts(driver, cfg.Mappings); err != nil { + return err + } + + // write and close ReadyFD (convention is same as slirp4netns --ready-fd) + logrus.Info("ready") + if _, err := readyW.Write([]byte("1")); err != nil { + return err + } + if err := readyW.Close(); err != nil { + return err + } + + // wait for ExitFD to be closed + logrus.Info("waiting for exitfd to be closed") + if _, err := ioutil.ReadAll(exitR); err != nil { + return err + } + return nil +} + +func exposePorts(pm rkport.Manager, portMappings []ocicni.PortMapping) error { + ctx := context.TODO() + for _, i := range portMappings { + hostIP := i.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + spec := rkport.Spec{ + Proto: i.Protocol, + ParentIP: hostIP, + ParentPort: int(i.HostPort), + ChildPort: int(i.ContainerPort), + } + if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { + return err + } + if _, err := pm.AddPort(ctx, spec); err != nil { + return err + } + } + return nil +} + +func child() error { + // load the config from the parent + var opaque map[string]string + if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil { + return err + } + + // start the child driver + quit := make(chan struct{}) + errCh := make(chan error) + go func() { + d := rkbuiltin.NewChildDriver(os.Stderr) + dErr := d.RunChildDriver(opaque, quit) + errCh <- dErr + }() + defer func() { + logrus.Info("stopping child driver") + quit <- struct{}{} + if err := <-errCh; err != nil { + logrus.WithError(err).Warn("child driver returned error on exit") + } + }() + + // wait for stdin to be closed + if _, err := ioutil.ReadAll(os.Stdin); err != nil { + return err + } + return nil +} + +type logrusWriter struct { + prefix string +} + +func (w *logrusWriter) Write(p []byte) (int, error) { + logrus.Infof("%s%s", w.prefix, string(p)) + return len(p), nil +} diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go index a45137416..8e95a3ca0 100644 --- a/pkg/spec/namespaces.go +++ b/pkg/spec/namespaces.go @@ -17,6 +17,7 @@ import ( "github.com/sirupsen/logrus" ) +// ToCreateOptions converts the input to a slice of container create options. func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) { var portBindings []ocicni.PortMapping var err error @@ -97,6 +98,8 @@ func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserCon return options, nil } +// ConfigureGenerator configures the generator based according to the current +// state of the NetworkConfig. func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error { netMode := c.NetMode if netMode.IsHost() { @@ -183,6 +186,7 @@ func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { return portBindings, nil } +// ToCreateOptions converts the input to container create options. func (c *CgroupConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) if c.CgroupMode.IsNS() { @@ -213,6 +217,7 @@ func (c *CgroupConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCre return options, nil } +// ToCreateOptions converts the input to container create options. func (c *UserConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) if c.UsernsMode.IsNS() { @@ -241,6 +246,8 @@ func (c *UserConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreat return options, nil } +// ConfigureGenerator configures the generator according to the current state +// of the UserConfig. func (c *UserConfig) ConfigureGenerator(g *generate.Generator) error { if IsNS(string(c.UsernsMode)) { if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), NS(string(c.UsernsMode))); err != nil { @@ -271,11 +278,14 @@ func (c *UserConfig) getPostConfigureNetNS() bool { return postConfigureNetNS } +// InNS returns true if the UserConfig indicates to be in a dedicated user +// namespace. func (c *UserConfig) InNS(isRootless bool) bool { hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 return isRootless || (hasUserns && !c.UsernsMode.IsHost()) } +// ToCreateOptions converts the input to container create options. func (c *IpcConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) if c.IpcMode.IsHost() { @@ -293,6 +303,8 @@ func (c *IpcConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreate return options, nil } +// ConfigureGenerator configures the generator according to the current state +// of the IpcConfig. func (c *IpcConfig) ConfigureGenerator(g *generate.Generator) error { ipcMode := c.IpcMode if IsNS(string(ipcMode)) { @@ -308,6 +320,8 @@ func (c *IpcConfig) ConfigureGenerator(g *generate.Generator) error { return nil } +// ConfigureGenerator configures the generator according to the current state +// of the CgroupConfig. func (c *CgroupConfig) ConfigureGenerator(g *generate.Generator) error { cgroupMode := c.CgroupMode if cgroupMode.IsDefaultValue() { @@ -337,6 +351,7 @@ func (c *CgroupConfig) ConfigureGenerator(g *generate.Generator) error { return nil } +// ToCreateOptions converts the input to container create options. func (c *PidConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) if c.PidMode.IsContainer() { @@ -351,6 +366,8 @@ func (c *PidConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreate return options, nil } +// ConfigureGenerator configures the generator according to the current state +// of the PidConfig. func (c *PidConfig) ConfigureGenerator(g *generate.Generator) error { pidMode := c.PidMode if IsNS(string(pidMode)) { @@ -368,6 +385,7 @@ func (c *PidConfig) ConfigureGenerator(g *generate.Generator) error { return nil } +// ToCreateOptions converts the input to container create options. func (c *UtsConfig) ToCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) if IsPod(string(c.UtsMode)) { @@ -391,6 +409,8 @@ func (c *UtsConfig) ToCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([ return options, nil } +// ConfigureGenerator configures the generator according to the current state +// of the UtsConfig. func (c *UtsConfig) ConfigureGenerator(g *generate.Generator, net *NetworkConfig, runtime *libpod.Runtime) error { hostname := c.Hostname var err error diff --git a/pkg/spec/security.go b/pkg/spec/security.go index 05ed94e66..372fe87c6 100644 --- a/pkg/spec/security.go +++ b/pkg/spec/security.go @@ -11,6 +11,8 @@ import ( "github.com/pkg/errors" ) +// ToCreateOptions convert the SecurityConfig to a slice of container create +// options. func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { options := make([]libpod.CtrCreateOption, 0) options = append(options, libpod.WithSecLabels(c.LabelOpts)) @@ -18,6 +20,8 @@ func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { return options, nil } +// SetLabelOpts sets the label options of the SecurityConfig according to the +// input. func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error { if c.Privileged { c.LabelOpts = label.DisableSecOpt() @@ -57,6 +61,7 @@ func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidCon return nil } +// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.). func (c *SecurityConfig) SetSecurityOpts(runtime *libpod.Runtime, securityOpts []string) error { for _, opt := range securityOpts { if opt == "no-new-privileges" { @@ -91,6 +96,7 @@ func (c *SecurityConfig) SetSecurityOpts(runtime *libpod.Runtime, securityOpts [ return nil } +// ConfigureGenerator configures the generator according to the input. func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { // HANDLE CAPABILITIES // NOTE: Must happen before SECCOMP diff --git a/pkg/util/utils.go b/pkg/util/utils.go index f7d04c73b..c9d09b8b5 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -304,7 +304,7 @@ func GetImageConfig(changes []string) (ImageConfig, error) { return config, nil } -// Parse and validate a signal name or number +// ParseSignal parses and validates a signal name or number. func ParseSignal(rawSignal string) (syscall.Signal, error) { // Strip off leading dash, to allow -1 or -HUP basename := strings.TrimPrefix(rawSignal, "-") diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index ac92343d0..bc644f87c 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -450,6 +450,18 @@ func (i *LibpodAPI) TagImage(call iopodman.VarlinkCall, name, tag string) error return call.ReplyTagImage(newImage.ID()) } +// UntagImage accepts an image name and tag as strings and removes the tag from the local store. +func (i *LibpodAPI) UntagImage(call iopodman.VarlinkCall, name, tag string) error { + newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + return call.ReplyImageNotFound(name, err.Error()) + } + if err := newImage.UntagImage(tag); err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyUntagImage(newImage.ID()) +} + // RemoveImage accepts a image name or ID as a string and force bool to determine if it should // remove the image even if being used by stopped containers func (i *LibpodAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bool) error { diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go index dd171943f..d96e82a3f 100644 --- a/pkg/varlinkapi/virtwriter/virtwriter.go +++ b/pkg/varlinkapi/virtwriter/virtwriter.go @@ -27,13 +27,13 @@ const ( TerminalResize SocketDest = iota // Quit and detach Quit SocketDest = iota - // Quit from the client + // HangUpFromClient hangs up from the client HangUpFromClient SocketDest = iota ) -// ClientHangup signifies that the client wants to drop its -// connection from the server -var ClientHangup = errors.New("client hangup") +// ErrClientHangup signifies that the client wants to drop its connection from +// the server. +var ErrClientHangup = errors.New("client hangup") // IntToSocketDest returns a socketdest based on integer input func IntToSocketDest(i int) SocketDest { @@ -177,7 +177,7 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote // // reproducer: echo hello | (podman-remote run -i alpine cat) time.Sleep(1 * time.Second) - return ClientHangup + return ErrClientHangup default: // Something really went wrong return errors.New("unknown multiplex destination") |