From c72bb4f8feb05a63c189fe6ce264ca652b399022 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 10 Aug 2020 15:19:02 +0200 Subject: generate systemd: fix error handling Fix a bug in the error handling which returned nil instead of an error and ultimately lead to nil dereferences in the client. To prevent future regressions, add a test and check for the error message. Fixes: #7271 Signed-off-by: Valentin Rothberg --- pkg/domain/infra/abi/generate.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'pkg') diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index 560be988b..cff09bf2d 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -20,9 +20,10 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, if ctrErr == nil { // Generate the unit for the container. s, err := generate.ContainerUnit(ctr, options) - if err == nil { - return &entities.GenerateSystemdReport{Output: s}, nil + if err != nil { + return nil, err } + return &entities.GenerateSystemdReport{Output: s}, nil } // If it's not a container, we either have a pod or garbage. -- cgit v1.2.3-54-g00ecf From dcf39f0abec6d8925b628904c96fa9d075669e29 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 4 Aug 2020 15:55:53 +0200 Subject: image list: speed up Listing images has shown increasing performance penalties with an increasing number of images. Unless `--all` is specified, Podman will filter intermediate images. Determining intermediate images has been done by finding (and comparing!) parent images which is expensive. We had to query the storage many times which turned it into a bottleneck. Instead, create a layer tree and assign one or more images to nodes that match the images' top layer. Determining the children of an image is now exponentially faster as we already know the child images from the layer graph and the images using the same top layer, which may also be considered child images based on their history. On my system with 510 images, a rootful image list drops from 6 secs down to 0.3 secs. Also use the tree to compute parent nodes, and to filter intermediate images for pruning. Signed-off-by: Valentin Rothberg --- libpod/image/filters.go | 20 ++++ libpod/image/image.go | 133 ++------------------- libpod/image/layer_tree.go | 222 ++++++++++++++++++++++++++++++++++++ libpod/image/prune.go | 11 +- pkg/api/handlers/utils/images.go | 20 ++-- pkg/domain/infra/abi/images_list.go | 17 ++- 6 files changed, 276 insertions(+), 147 deletions(-) create mode 100644 libpod/image/layer_tree.go (limited to 'pkg') diff --git a/libpod/image/filters.go b/libpod/image/filters.go index 11d081ec3..24e39a67b 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -29,6 +29,26 @@ func CreatedBeforeFilter(createTime time.Time) ResultFilter { } } +// IntermediateFilter returns filter for intermediate images (i.e., images +// with children and no tags). +func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) { + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + return func(i *Image) bool { + if len(i.Names()) > 0 { + return true + } + children, err := tree.children(ctx, i, false) + if err != nil { + logrus.Error(err.Error()) + return false + } + return len(children) == 0 + }, nil +} + // CreatedAfterFilter allows you to filter on images created after // the given time.Time func CreatedAfterFilter(createTime time.Time) ResultFilter { diff --git a/libpod/image/image.go b/libpod/image/image.go index 048ec825d..6a87380c0 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -855,26 +855,6 @@ func (i *Image) Dangling() bool { return len(i.Names()) == 0 } -// Intermediate returns true if the image is cache or intermediate image. -// Cache image has parent and child. -func (i *Image) Intermediate(ctx context.Context) (bool, error) { - parent, err := i.IsParent(ctx) - if err != nil { - return false, err - } - if !parent { - return false, nil - } - img, err := i.GetParent(ctx) - if err != nil { - return false, err - } - if img != nil { - return true, nil - } - return false, nil -} - // User returns the image's user func (i *Image) User(ctx context.Context) (string, error) { imgInspect, err := i.inspect(ctx, false) @@ -1213,7 +1193,7 @@ func splitString(input string) string { // the parent of any other layer in store. Double check that image with that // layer exists as well. func (i *Image) IsParent(ctx context.Context) (bool, error) { - children, err := i.getChildren(ctx, 1) + children, err := i.getChildren(ctx, false) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return false, nil @@ -1288,63 +1268,16 @@ func areParentAndChild(parent, child *imgspecv1.Image) bool { // GetParent returns the image ID of the parent. Return nil if a parent is not found. func (i *Image) GetParent(ctx context.Context) (*Image, error) { - var childLayer *storage.Layer - images, err := i.imageruntime.GetImages() + tree, err := i.imageruntime.layerTree() if err != nil { return nil, err } - if i.TopLayer() != "" { - if childLayer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil { - return nil, err - } - } - // fetch the configuration for the child image - child, err := i.ociv1Image(ctx) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return nil, nil - } - return nil, err - } - for _, img := range images { - if img.ID() == i.ID() { - continue - } - candidateLayer := img.TopLayer() - // as a child, our top layer, if we have one, is either the - // candidate parent's layer, or one that's derived from it, so - // skip over any candidate image where we know that isn't the - // case - if childLayer != nil { - // The child has at least one layer, so a parent would - // have a top layer that's either the same as the child's - // top layer or the top layer's recorded parent layer, - // which could be an empty value. - if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID { - continue - } - } else { - // The child has no layers, but the candidate does. - if candidateLayer != "" { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(candidate, child) { - return img, nil - } - } - return nil, nil + return tree.parent(ctx, i) } // GetChildren returns a list of the imageIDs that depend on the image func (i *Image) GetChildren(ctx context.Context) ([]string, error) { - children, err := i.getChildren(ctx, 0) + children, err := i.getChildren(ctx, true) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return nil, nil @@ -1354,62 +1287,15 @@ func (i *Image) GetChildren(ctx context.Context) ([]string, error) { return children, nil } -// getChildren returns a list of at most "max" imageIDs that depend on the image -func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) { - var children []string - - if _, err := i.toImageRef(ctx); err != nil { - return nil, nil - } - - images, err := i.imageruntime.GetImages() - if err != nil { - return nil, err - } - - // fetch the configuration for the parent image - parent, err := i.ociv1Image(ctx) +// getChildren returns a list of imageIDs that depend on the image. If all is +// false, only the first child image is returned. +func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) { + tree, err := i.imageruntime.layerTree() if err != nil { return nil, err } - parentLayer := i.TopLayer() - for _, img := range images { - if img.ID() == i.ID() { - continue - } - if img.TopLayer() == "" { - if parentLayer != "" { - // this image has no layers, but we do, so - // it can't be derived from this one - continue - } - } else { - candidateLayer, err := img.Layer() - if err != nil { - return nil, err - } - // if this image's top layer is not our top layer, and is not - // based on our top layer, we can skip it - if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(parent, candidate) { - children = append(children, img.ID()) - } - // if we're not building an exhaustive list, maybe we're done? - if max > 0 && len(children) >= max { - break - } - } - return children, nil + return tree.children(ctx, i, all) } // InputIsID returns a bool if the user input for an image @@ -1608,6 +1494,7 @@ type LayerInfo struct { // GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers. func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) { + // TODO: evaluate if we can reuse `layerTree` here. // Memory allocated to store map of layers with key LayerID. // Map will build dependency chain with ParentID and ChildID(s) diff --git a/libpod/image/layer_tree.go b/libpod/image/layer_tree.go new file mode 100644 index 000000000..3699655fd --- /dev/null +++ b/libpod/image/layer_tree.go @@ -0,0 +1,222 @@ +package image + +import ( + "context" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// layerTree is an internal representation of local layers. +type layerTree struct { + // nodes is the actual layer tree with layer IDs being keys. + nodes map[string]*layerNode + // ociCache is a cache for Image.ID -> OCI Image. Translations are done + // on-demand. + ociCache map[string]*ociv1.Image +} + +// node returns a layerNode for the specified layerID. +func (t *layerTree) node(layerID string) *layerNode { + node, exists := t.nodes[layerID] + if !exists { + node = &layerNode{} + t.nodes[layerID] = node + } + return node +} + +// toOCI returns an OCI image for the specified image. +func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) { + var err error + oci, exists := t.ociCache[i.ID()] + if !exists { + oci, err = i.ociv1Image(ctx) + t.ociCache[i.ID()] = oci + } + return oci, err +} + +// layerNode is a node in a layerTree. It's ID is the key in a layerTree. +type layerNode struct { + children []*layerNode + images []*Image + parent *layerNode +} + +// layerTree extracts a layerTree from the layers in the local storage and +// relates them to the specified images. +func (ir *Runtime) layerTree() (*layerTree, error) { + layers, err := ir.store.Layers() + if err != nil { + return nil, err + } + + images, err := ir.GetImages() + if err != nil { + return nil, err + } + + tree := layerTree{ + nodes: make(map[string]*layerNode), + ociCache: make(map[string]*ociv1.Image), + } + + // First build a tree purely based on layer information. + for _, layer := range layers { + node := tree.node(layer.ID) + if layer.Parent == "" { + continue + } + parent := tree.node(layer.Parent) + node.parent = parent + parent.children = append(parent.children, node) + } + + // Now assign the images to each (top) layer. + for i := range images { + img := images[i] // do not leak loop variable outside the scope + topLayer := img.TopLayer() + if topLayer == "" { + continue + } + node, exists := tree.nodes[topLayer] + if !exists { + return nil, errors.Errorf("top layer %s of image %s not found in layer tree", img.TopLayer(), img.ID()) + } + node.images = append(node.images, img) + } + + return &tree, nil +} + +// children returns the image IDs of children . Child images are images +// with either the same top layer as parent or parent being the true parent +// layer. Furthermore, the history of the parent and child images must match +// with the parent having one history item less. +// If all is true, all images are returned. Otherwise, the first image is +// returned. +func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) { + if parent.TopLayer() == "" { + return nil, nil + } + + var children []string + + parentNode, exists := t.nodes[parent.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", parent.TopLayer()) + } + + parentID := parent.ID() + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + + // checkParent returns true if child and parent are in such a relation. + checkParent := func(child *Image) (bool, error) { + if parentID == child.ID() { + return false, nil + } + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return false, err + } + // History check. + return areParentAndChild(parentOCI, childOCI), nil + } + + // addChildrenFrom adds child images of parent to children. Returns + // true if any image is a child of parent. + addChildrenFromNode := func(node *layerNode) (bool, error) { + foundChildren := false + for _, childImage := range node.images { + isChild, err := checkParent(childImage) + if err != nil { + return foundChildren, err + } + if isChild { + foundChildren = true + children = append(children, childImage.ID()) + if all { + return foundChildren, nil + } + } + } + return foundChildren, nil + } + + // First check images where parent's top layer is also the parent + // layer. + for _, childNode := range parentNode.children { + found, err := addChildrenFromNode(childNode) + if err != nil { + return nil, err + } + if found && all { + return children, nil + } + } + + // Now check images with the same top layer. + if _, err := addChildrenFromNode(parentNode); err != nil { + return nil, err + } + + return children, nil +} + +// parent returns the parent image or nil if no parent image could be found. +func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) { + if child.TopLayer() == "" { + return nil, nil + } + + node, exists := t.nodes[child.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", child.TopLayer()) + } + + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return nil, err + } + + // Check images from the parent node (i.e., parent layer) and images + // with the same layer (i.e., same top layer). + childID := child.ID() + images := node.images + if node.parent != nil { + images = append(images, node.parent.images...) + } + for _, parent := range images { + if parent.ID() == childID { + continue + } + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + // History check. + if areParentAndChild(parentOCI, childOCI) { + return parent, nil + } + } + + return nil, nil +} + +// hasChildrenAndParent returns true if the specified image has children and a +// parent. +func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) { + children, err := t.children(ctx, i, false) + if err != nil { + return false, err + } + if len(children) == 0 { + return false, nil + } + parent, err := t.parent(ctx, i) + return parent != nil, err +} diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 5ad7a9a5e..2448f7c87 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -66,6 +66,12 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I if err != nil { return nil, err } + + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + for _, i := range allImages { // filter the images based on this. for _, filterFunc := range filterFuncs { @@ -85,8 +91,9 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I } } - //skip the cache or intermediate images - intermediate, err := i.Intermediate(ctx) + // skip the cache (i.e., with parent) and intermediate (i.e., + // with children) images + intermediate, err := tree.hasChildrenAndParent(ctx, i) if err != nil { return nil, err } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 195e71b75..63b1b566b 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -93,20 +93,14 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { if query.All { return images, nil } - returnImages := []*image.Image{} - for _, img := range images { - if len(img.Names()) == 0 { - parent, err := img.IsParent(r.Context()) - if err != nil { - return nil, err - } - if parent { - continue - } - } - returnImages = append(returnImages, img) + + filter, err := runtime.ImageRuntime().IntermediateFilter(r.Context(), images) + if err != nil { + return nil, err } - return returnImages, nil + images = image.FilterImages(images, []image.ResultFilter{filter}) + + return images, nil } func GetImage(r *http.Request, name string) (*image.Image, error) { diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 06ef673c7..bb5775db5 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -13,6 +13,14 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) return nil, err } + if !opts.All { + filter, err := ir.Libpod.ImageRuntime().IntermediateFilter(ctx, images) + if err != nil { + return nil, err + } + images = libpodImage.FilterImages(images, []libpodImage.ResultFilter{filter}) + } + summaries := []*entities.ImageSummary{} for _, img := range images { var repoTags []string @@ -32,15 +40,6 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) if err != nil { return nil, err } - if len(img.Names()) == 0 { - parent, err := img.IsParent(ctx) - if err != nil { - return nil, err - } - if parent { - continue - } - } } digests := make([]string, len(img.Digests())) -- cgit v1.2.3-54-g00ecf From c5646acf4a06520f4fa65acdf8eebb4130c5e7d9 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 4 Aug 2020 12:52:45 -0500 Subject: podman-remote send name and tag when loading an image with podman-remote load, we need to send a name and a tag to the endpoint Fixes: #7124 Backported-by: Valentin Rothberg Signed-off-by: Brent Baude --- pkg/domain/infra/tunnel/images.go | 6 +++++- test/system/120-load.bats | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'pkg') diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index bfe5fbec3..2e30621c5 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -188,7 +188,11 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) return nil, err } defer f.Close() - return images.Load(ir.ClientCxt, f, &opts.Name) + ref := opts.Name + if len(opts.Tag) > 0 { + ref += ":" + opts.Tag + } + return images.Load(ir.ClientCxt, f, &ref) } func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { diff --git a/test/system/120-load.bats b/test/system/120-load.bats index f290c1888..c0ddbf4d6 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -74,7 +74,7 @@ verify_iid_and_name() { verify_iid_and_name $img_name } -@test "podman load - NAME and NAME:TAG arguments work (requires: #2674)" { +@test "podman load - NAME and NAME:TAG arguments work" { get_iid_and_name run_podman save $iid -o $archive run_podman rmi $iid -- cgit v1.2.3-54-g00ecf From a53b97beb3dfd3b95b205e35872344c5fadbb7c1 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Fri, 24 Jul 2020 14:09:12 -0400 Subject: fix bug podman sign storage path - fix the bud podman not using specified --directory as signature storage. - use manifest and image referce to set repo@digest. close #6994 close #6993 Backported-by: Valentin Rothberg Signed-off-by: Qi Wang --- go.mod | 5 +- go.sum | 8 +-- pkg/domain/infra/abi/images.go | 65 ++++++++++------------ pkg/trust/trust.go | 4 +- test/e2e/image_sign_test.go | 62 +++++++++++++++++++++ test/e2e/sign/secret-key.asc | 57 +++++++++++++++++++ .../k8s.io/apimachinery/pkg/apis/meta/v1/types.go | 3 + vendor/k8s.io/apimachinery/pkg/util/net/http.go | 2 +- vendor/modules.txt | 4 +- 9 files changed, 161 insertions(+), 49 deletions(-) create mode 100644 test/e2e/image_sign_test.go create mode 100644 test/e2e/sign/secret-key.asc (limited to 'pkg') diff --git a/go.mod b/go.mod index 227979afb..492b46032 100644 --- a/go.mod +++ b/go.mod @@ -63,8 +63,7 @@ require ( golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 - gopkg.in/yaml.v2 v2.3.0 - k8s.io/api v0.18.4 - k8s.io/apimachinery v0.18.4 + k8s.io/api v0.18.6 + k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab ) diff --git a/go.sum b/go.sum index 32601c7a6..3bb51cab8 100644 --- a/go.sum +++ b/go.sum @@ -630,11 +630,11 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A= -k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4= -k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= +k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= -k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA= -k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 9f594d728..5f19f416a 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/url" "os" + "path" "path/filepath" "strconv" "strings" @@ -564,10 +565,6 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { } func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: options.CertDir, - } - mech, err := signature.NewGPGSigningMechanism() if err != nil { return nil, errors.Wrap(err, "error initializing GPG") @@ -586,7 +583,6 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie } for _, signimage := range names { - var sigStoreDir string srcRef, err := alltransports.ParseImageName(signimage) if err != nil { return nil, errors.Wrapf(err, "error parsing image name") @@ -607,40 +603,38 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie if dockerReference == nil { return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) } - - // create the signstore file - rtc, err := ir.Libpod.GetConfig() - if err != nil { - return nil, err - } - newImage, err := ir.Libpod.ImageRuntime().New(ctx, signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: options.SignBy}, nil, util.PullImageMissing) - if err != nil { - return nil, errors.Wrapf(err, "error pulling image %s", signimage) + var sigStoreDir string + if options.Directory != "" { + sigStoreDir = options.Directory } if sigStoreDir == "" { if rootless.IsRootless() { sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore") } else { + var sigStoreURI string registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) if registryInfo != nil { - if sigStoreDir = registryInfo.SigStoreStaging; sigStoreDir == "" { - sigStoreDir = registryInfo.SigStore - + if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" { + sigStoreURI = registryInfo.SigStore } } + if sigStoreURI == "" { + return nil, errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String()) + + } + sigStoreDir, err = localPathFromURI(sigStoreURI) + if err != nil { + return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreURI) + } } } - sigStoreDir, err = isValidSigStoreDir(sigStoreDir) + manifestDigest, err := manifest.Digest(getManifest) if err != nil { - return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) - } - repos, err := newImage.RepoDigests() - if err != nil { - return nil, errors.Wrapf(err, "error calculating repo digests for %s", signimage) + return nil, err } - if len(repos) == 0 { - logrus.Errorf("no repodigests associated with the image %s", signimage) - continue + repo := reference.Path(dockerReference) + if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references + return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) } // create signature @@ -648,22 +642,21 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie if err != nil { return nil, errors.Wrapf(err, "error creating new signature") } - - trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) - sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) - if err := os.MkdirAll(sigStoreDir, 0751); err != nil { + // create the signstore file + signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex()) + if err := os.MkdirAll(signatureDir, 0751); err != nil { // The directory is allowed to exist if !os.IsExist(err) { - logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) + logrus.Errorf("error creating directory %s: %s", signatureDir, err) continue } } - sigFilename, err := getSigFilename(sigStoreDir) + sigFilename, err := getSigFilename(signatureDir) if err != nil { logrus.Errorf("error creating sigstore file: %v", err) continue } - err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) + err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) if err != nil { logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) continue @@ -691,14 +684,12 @@ func getSigFilename(sigStoreDirPath string) (string, error) { } } -func isValidSigStoreDir(sigStoreDir string) (string, error) { - writeURIs := map[string]bool{"file": true} +func localPathFromURI(sigStoreDir string) (string, error) { url, err := url.Parse(sigStoreDir) if err != nil { return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) } - _, exists := writeURIs[url.Scheme] - if !exists { + if url.Scheme != "file" { return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) } sigStoreDir = url.Path diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go index 60de099fa..2348bc410 100644 --- a/pkg/trust/trust.go +++ b/pkg/trust/trust.go @@ -12,9 +12,9 @@ import ( "strings" "github.com/containers/image/v5/types" + "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" ) // PolicyContent struct for policy.json file @@ -157,7 +157,7 @@ func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *Regi searchKey = searchKey[:strings.LastIndex(searchKey, "/")] } } - return nil + return registryConfigs.DefaultDocker } // CreateTmpFile creates a temp file under dir and writes the content into it diff --git a/test/e2e/image_sign_test.go b/test/e2e/image_sign_test.go new file mode 100644 index 000000000..d5d460b38 --- /dev/null +++ b/test/e2e/image_sign_test.go @@ -0,0 +1,62 @@ +// +build !remote + +package integration + +import ( + "os" + "os/exec" + "path/filepath" + + . "github.com/containers/libpod/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image sign", func() { + var ( + origGNUPGHOME string + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + + tempGNUPGHOME := filepath.Join(podmanTest.TempDir, "tmpGPG") + err := os.Mkdir(tempGNUPGHOME, os.ModePerm) + Expect(err).To(BeNil()) + + origGNUPGHOME = os.Getenv("GNUPGHOME") + err = os.Setenv("GNUPGHOME", tempGNUPGHOME) + Expect(err).To(BeNil()) + + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + os.Setenv("GNUPGHOME", origGNUPGHOME) + }) + + It("podman sign image", func() { + cmd := exec.Command("gpg", "--import", "sign/secret-key.asc") + err := cmd.Run() + Expect(err).To(BeNil()) + sigDir := filepath.Join(podmanTest.TempDir, "test-sign") + err = os.MkdirAll(sigDir, os.ModePerm) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"image", "sign", "--directory", sigDir, "--sign-by", "foo@bar.com", "docker://library/alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + _, err = os.Stat(filepath.Join(sigDir, "library")) + Expect(err).To(BeNil()) + }) +}) diff --git a/test/e2e/sign/secret-key.asc b/test/e2e/sign/secret-key.asc new file mode 100644 index 000000000..23c0d05c3 --- /dev/null +++ b/test/e2e/sign/secret-key.asc @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=6kcB +-----END PGP PRIVATE KEY BLOCK----- diff --git a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go index bf125b62a..e7aaead8c 100644 --- a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go +++ b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -873,6 +873,9 @@ const ( // FieldManagerConflict is used to report when another client claims to manage this field, // It should only be returned for a request using server-side apply. CauseTypeFieldManagerConflict CauseType = "FieldManagerConflict" + // CauseTypeResourceVersionTooLarge is used to report that the requested resource version + // is newer than the data observed by the API server, so the request cannot be served. + CauseTypeResourceVersionTooLarge CauseType = "ResourceVersionTooLarge" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/k8s.io/apimachinery/pkg/util/net/http.go b/vendor/k8s.io/apimachinery/pkg/util/net/http.go index 7449cbb0a..7b64e6815 100644 --- a/vendor/k8s.io/apimachinery/pkg/util/net/http.go +++ b/vendor/k8s.io/apimachinery/pkg/util/net/http.go @@ -446,7 +446,7 @@ redirectLoop: // Only follow redirects to the same host. Otherwise, propagate the redirect response back. if requireSameHostRedirects && location.Hostname() != originalLocation.Hostname() { - break redirectLoop + return nil, nil, fmt.Errorf("hostname mismatch: expected %s, found %s", originalLocation.Hostname(), location.Hostname()) } // Reset the connection. diff --git a/vendor/modules.txt b/vendor/modules.txt index bf2a14d4c..4d1121553 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -689,10 +689,10 @@ gopkg.in/tomb.v1 gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c gopkg.in/yaml.v3 -# k8s.io/api v0.18.4 +# k8s.io/api v0.18.6 k8s.io/api/apps/v1 k8s.io/api/core/v1 -# k8s.io/apimachinery v0.18.4 +# k8s.io/apimachinery v0.18.6 k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/apis/meta/v1 -- cgit v1.2.3-54-g00ecf From 760a9077db10f0837986ae6b7e2d4003c6ad9662 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Mon, 3 Aug 2020 09:38:10 +0200 Subject: rootless: system service joins immediately the namespaces when there is a pause process running, let the "system service" podman instance join immediately the existing namespaces. Closes: https://github.com/containers/podman/issues/7180 Closes: https://github.com/containers/podman/issues/6660 Signed-off-by: Giuseppe Scrivano --- pkg/rootless/rootless_linux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 716db81dc..2c6f7ae38 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -205,7 +205,7 @@ can_use_shortcut () if (strcmp (argv[argc], "mount") == 0 || strcmp (argv[argc], "search") == 0 - || strcmp (argv[argc], "system") == 0) + || (strcmp (argv[argc], "system") == 0 && argv[argc+1] && strcmp (argv[argc+1], "service") != 0)) { ret = false; break; -- cgit v1.2.3-54-g00ecf From 5301d40e81c5a0758a3c8302b037854629317d43 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 3 Aug 2020 14:19:21 -0500 Subject: docker-compose uses application/tar even though the official documentation suggests that application/x-tar should be used for tar files, it seems docker-compose uses application/tar. we now accept them and issue a warning. Fixes: #7185 Signed-off-by: Brent Baude --- pkg/api/handlers/compat/images_build.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 8ac5b80c1..3d9efe61e 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -20,6 +20,7 @@ import ( "github.com/containers/libpod/v2/pkg/api/handlers/utils" "github.com/containers/storage/pkg/archive" "github.com/gorilla/schema" + "github.com/sirupsen/logrus" ) func BuildImage(w http.ResponseWriter, r *http.Request) { @@ -33,7 +34,13 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { - if hdr[0] != "application/x-tar" { + contentType := hdr[0] + switch contentType { + case "application/tar": + logrus.Warnf("tar file content type is %s, should use \"application/x-tar\" content type", contentType) + case "application/x-tar": + break + default: utils.BadRequest(w, "Content-Type", hdr[0], fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) return -- cgit v1.2.3-54-g00ecf From 2ff8f485ea640b03a322df0f49ed79af92aa1f0b Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Mon, 3 Aug 2020 13:58:41 -0500 Subject: Missing return after early exit the exists code was plagued by a missing return statement meant to trigger an early exit. Fixes: #7197 Signed-off-by: Brent Baude --- pkg/api/handlers/libpod/containers.go | 1 + 1 file changed, 1 insertion(+) (limited to 'pkg') diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 21904e21f..2303ff17a 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -23,6 +23,7 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { if err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { utils.ContainerNotFound(w, name, err) + return } utils.InternalServerError(w, err) return -- cgit v1.2.3-54-g00ecf From 43527de53e40b737333502f6fbb2e1d73a3f3ec9 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 4 Aug 2020 13:41:44 -0500 Subject: correct go-binding key for volumes the go binding for remove container was using 'vols' for a key to remove volumes associated to the container. the correct key should be "v" and is documented as such. Fixes: #7128 Signed-off-by: Brent Baude --- pkg/bindings/containers/containers.go | 2 +- test/system/160-volumes.bats | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'pkg') diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index c690ea125..c479e5dcb 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -98,7 +98,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { params.Set("force", strconv.FormatBool(*force)) } if volumes != nil { - params.Set("vols", strconv.FormatBool(*volumes)) + params.Set("v", strconv.FormatBool(*volumes)) } response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) if err != nil { diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index ef38b2a68..e2aefed43 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -142,8 +142,6 @@ EOF # Anonymous temporary volumes, and persistent autocreated named ones @test "podman volume, implicit creation with run" { - skip_if_remote "FIXME: pending #7128" - # No hostdir arg: create anonymous container with random name rand=$(random_string) run_podman run -v /myvol $IMAGE sh -c "echo $rand >/myvol/myfile" -- cgit v1.2.3-54-g00ecf From 5b4952395bd7499f45f24a871009c235ba47ca0b Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 4 Aug 2020 16:24:34 -0400 Subject: Handle podman-remote run --rm We need to remove the container after it has exited for podman-remote run --rm commands. If we don't remove this container at this step, we open ourselves up to race conditions. Signed-off-by: Daniel J Walsh --- pkg/domain/infra/tunnel/containers.go | 14 +++++++++++--- test/system/030-run.bats | 2 -- test/system/070-build.bats | 1 - test/system/160-volumes.bats | 4 ---- test/system/200-pod.bats | 3 --- test/system/300-cli-parsing.bats | 2 -- test/system/400-unprivileged-access.bats | 2 -- test/system/410-selinux.bats | 1 - 8 files changed, 11 insertions(+), 18 deletions(-) (limited to 'pkg') diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 4ee709e37..8835248ca 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -500,9 +500,6 @@ func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.C } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { - if opts.Rm { - logrus.Info("the remote client does not support --rm yet") - } con, err := containers.CreateWithSpec(ic.ClientCxt, opts.Spec) if err != nil { return nil, err @@ -526,6 +523,17 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if err != nil { report.ExitCode = define.ExitCode(err) } + if opts.Rm { + if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr || + errors.Cause(err) == define.ErrCtrRemoved { + logrus.Warnf("Container %s does not exist: %v", con.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", con.ID, err) + } + } + } + return &report, err } diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 4f707dda3..12c82bc4c 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -84,8 +84,6 @@ echo $rand | 0 | $rand # Believe it or not, 'sh -c' resulted in different behavior run_podman 0 run --rm $IMAGE sh -c /bin/true run_podman 1 run --rm $IMAGE sh -c /bin/false - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } @test "podman run --name" { diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 6ec6b09d9..bdc05a172 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -91,7 +91,6 @@ ADD https://github.com/containers/libpod/blob/master/README.md /tmp/ EOF run_podman build -t add_url $tmpdir run_podman run --rm add_url stat /tmp/README.md - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman rmi -f add_url # Now test COPY. That should fail. diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index e2aefed43..3f50bd3c4 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -93,7 +93,6 @@ Labels.l | $mylabel is "$(<$mountpoint/myfile)" "$rand" "we see content created in container" # Clean up - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvolume } @@ -135,7 +134,6 @@ EOF is "$output" "got here -$rand-" "script in volume is runnable with default (exec)" # Clean up - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvolume } @@ -173,7 +171,6 @@ EOF run_podman run --rm -v $myvol:/myvol:z $IMAGE \ sh -c "cp /myvol/myfile /myvol/myfile2" - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvol # Autocreated volumes should also work with keep-id @@ -182,7 +179,6 @@ EOF run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ touch /myvol/myfile - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvol } diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 6680a896d..93a7d7b5e 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -93,7 +93,6 @@ function teardown() { is "$output" "$message" "message sent from one container to another" # Clean up. First the nc -l container... - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman rm $cid1 # ...then, from pause container, find the image ID of the pause image... @@ -104,7 +103,6 @@ function teardown() { pause_iid="$output" # ...then rm the pod, then rmi the pause image so we don't leave strays. - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman pod rm $podname run_podman rmi $pause_iid @@ -202,7 +200,6 @@ function random_ip() { is "$output" ".*options $dns_opt" "--dns-opt was added" # pod inspect - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman pod inspect --format '{{.Name}}: {{.ID}} : {{.NumContainers}} : {{.Labels}}' mypod is "$output" "mypod: $pod_id : 1 : map\[${labelname}:${labelvalue}]" \ "pod inspect --format ..." diff --git a/test/system/300-cli-parsing.bats b/test/system/300-cli-parsing.bats index 2abc01bb7..92c073102 100644 --- a/test/system/300-cli-parsing.bats +++ b/test/system/300-cli-parsing.bats @@ -10,8 +10,6 @@ load helpers # Error: invalid argument "true=\"false\"" for "-l, --label" \ # flag: parse error on line 1, column 5: bare " in non-quoted-field run_podman run --rm --label 'true="false"' $IMAGE true - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # vim: filetype=sh diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index d020bf46a..acf0f0ba2 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -163,8 +163,6 @@ EOF die "$path: Unknown file type '$type'" fi done - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # vim: filetype=sh diff --git a/test/system/410-selinux.bats b/test/system/410-selinux.bats index 1501f8554..3dca59641 100644 --- a/test/system/410-selinux.bats +++ b/test/system/410-selinux.bats @@ -16,7 +16,6 @@ function check_label() { # FIXME: it'd be nice to specify the command to run, e.g. 'ls -dZ /', # but alpine ls (from busybox) doesn't support -Z run_podman run --rm $args $IMAGE cat -v /proc/self/attr/current - if is_remote; then sleep 2;fi # FIXME: pending #7119 # FIXME: on some CI systems, 'run --privileged' emits a spurious # warning line about dup devices. Ignore it. -- cgit v1.2.3-54-g00ecf From 8931e99a18cc168ebee07bb72b62849815af7434 Mon Sep 17 00:00:00 2001 From: zhangguanzhang Date: Sun, 2 Aug 2020 21:08:13 +0800 Subject: API returns 500 in case network is not found instead of 404 Backported-by: Valentin Rothberg Signed-off-by: zhangguanzhang --- libpod/define/errors.go | 3 +++ pkg/api/handlers/compat/networks.go | 7 +++---- pkg/api/handlers/libpod/networks.go | 6 +++--- pkg/api/handlers/utils/errors.go | 9 +++++++++ pkg/network/config.go | 5 ----- pkg/network/files.go | 3 ++- pkg/network/network.go | 3 ++- test/apiv2/35-networks.at | 8 ++++++++ 8 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 test/apiv2/35-networks.at (limited to 'pkg') diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 1e9179353..5cadce396 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -23,6 +23,9 @@ var ( // ErrNoSuchVolume indicates the requested volume does not exist ErrNoSuchVolume = errors.New("no such volume") + // ErrNoSuchNetwork indicates the requested network does not exist + ErrNoSuchNetwork = errors.New("network not found") + // ErrNoSuchExecSession indicates that the requested exec session does // not exist. ErrNoSuchExecSession = errors.New("no such exec session") diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 2e11c0edb..ad15be270 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/libpod/v2/libpod" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/api/handlers/utils" "github.com/containers/libpod/v2/pkg/domain/entities" "github.com/containers/libpod/v2/pkg/domain/infra/abi" @@ -44,9 +45,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) _, err = network.InspectNetwork(config, name) if err != nil { - // TODO our network package does not distinguish between not finding a - // specific network vs not being able to read it - utils.InternalServerError(w, err) + utils.NetworkNotFound(w, name, err) return } report, err := getNetworkResourceByName(name, runtime) @@ -285,7 +284,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { return } if !exists { - utils.Error(w, "network not found", http.StatusNotFound, network.ErrNetworkNotFound) + utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork) return } if err := network.RemoveNetwork(config, name); err != nil { diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 12409bf50..0164d5d13 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -5,10 +5,10 @@ import ( "net/http" "github.com/containers/libpod/v2/libpod" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/api/handlers/utils" "github.com/containers/libpod/v2/pkg/domain/entities" "github.com/containers/libpod/v2/pkg/domain/infra/abi" - "github.com/containers/libpod/v2/pkg/network" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -78,7 +78,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { } if reports[0].Err != nil { // If the network cannot be found, we return a 404. - if errors.Cause(err) == network.ErrNetworkNotFound { + if errors.Cause(err) == define.ErrNoSuchNetwork { utils.Error(w, "Something went wrong", http.StatusNotFound, err) return } @@ -104,7 +104,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { reports, err := ic.NetworkInspect(r.Context(), []string{name}, options) if err != nil { // If the network cannot be found, we return a 404. - if errors.Cause(err) == network.ErrNetworkNotFound { + if errors.Cause(err) == define.ErrNoSuchNetwork { utils.Error(w, "Something went wrong", http.StatusNotFound, err) return } diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index 00d09ac11..5e77b4049 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -39,6 +39,7 @@ func VolumeNotFound(w http.ResponseWriter, name string, err error) { msg := fmt.Sprintf("No such volume: %s", name) Error(w, msg, http.StatusNotFound, err) } + func ContainerNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchCtr { InternalServerError(w, err) @@ -55,6 +56,14 @@ func ImageNotFound(w http.ResponseWriter, name string, err error) { Error(w, msg, http.StatusNotFound, err) } +func NetworkNotFound(w http.ResponseWriter, name string, err error) { + if errors.Cause(err) != define.ErrNoSuchNetwork { + InternalServerError(w, err) + } + msg := fmt.Sprintf("No such network: %s", name) + Error(w, msg, http.StatusNotFound, err) +} + func PodNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchPod { InternalServerError(w, err) diff --git a/pkg/network/config.go b/pkg/network/config.go index a504e0ad0..0115433e1 100644 --- a/pkg/network/config.go +++ b/pkg/network/config.go @@ -2,7 +2,6 @@ package network import ( "encoding/json" - "errors" "net" ) @@ -20,10 +19,6 @@ const ( DefaultPodmanDomainName = "dns.podman" ) -var ( - ErrNetworkNotFound = errors.New("network not found") -) - // GetDefaultPodmanNetwork outputs the default network for podman func GetDefaultPodmanNetwork() (*net.IPNet, error) { _, n, err := net.ParseCIDR("10.88.1.0/24") diff --git a/pkg/network/files.go b/pkg/network/files.go index beb3289f3..f174a762c 100644 --- a/pkg/network/files.go +++ b/pkg/network/files.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/libpod/define" "github.com/pkg/errors" ) @@ -55,7 +56,7 @@ func GetCNIConfigPathByName(config *config.Config, name string) (string, error) return confFile, nil } } - return "", errors.Wrap(ErrNetworkNotFound, fmt.Sprintf("unable to find network configuration for %s", name)) + return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name)) } // ReadRawCNIConfByName reads the raw CNI configuration for a CNI diff --git a/pkg/network/network.go b/pkg/network/network.go index cbebb0be8..37f3f721a 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -8,6 +8,7 @@ import ( "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -200,7 +201,7 @@ func InspectNetwork(config *config.Config, name string) (map[string]interface{}, func Exists(config *config.Config, name string) (bool, error) { _, err := ReadRawCNIConfByName(config, name) if err != nil { - if errors.Cause(err) == ErrNetworkNotFound { + if errors.Cause(err) == define.ErrNoSuchNetwork { return false, nil } return false, err diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at new file mode 100644 index 000000000..fff3f3b1f --- /dev/null +++ b/test/apiv2/35-networks.at @@ -0,0 +1,8 @@ +# -*- sh -*- +# +# network-related tests +# + +t GET /networks/non-existing-network 404 + +# vim: filetype=sh -- cgit v1.2.3-54-g00ecf From 1752851958145f65e50cef5fab3e114487fcb63a Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Thu, 30 Jul 2020 08:16:14 -0700 Subject: Add versioned _ping endpoint Fixes #7008 Signed-off-by: Jhon Honce --- pkg/api/server/register_ping.go | 9 ++++----- test/apiv2/01-basic.at | 8 +++++++- test/apiv2/rest_api/test_rest_v1_0_0.py | 21 ++++++++++++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-) (limited to 'pkg') diff --git a/pkg/api/server/register_ping.go b/pkg/api/server/register_ping.go index 70e88ee00..d4ae78e74 100644 --- a/pkg/api/server/register_ping.go +++ b/pkg/api/server/register_ping.go @@ -9,9 +9,8 @@ import ( func (s *APIServer) registerPingHandlers(r *mux.Router) error { - r.Handle("/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodGet) - r.Handle("/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodHead) - + r.Handle("/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodGet, http.MethodHead) + r.Handle(VersionedPath("/_ping"), s.APIHandler(compat.Ping)).Methods(http.MethodGet, http.MethodHead) // swagger:operation GET /libpod/_ping libpod libpodPingGet // --- // summary: Ping service @@ -62,7 +61,7 @@ func (s *APIServer) registerPingHandlers(r *mux.Router) error { // determine if talking to Podman engine or another engine // 500: // $ref: "#/responses/InternalError" - r.Handle("/libpod/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodGet) - r.Handle("/libpod/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodHead) + r.Handle("/libpod/_ping", s.APIHandler(compat.Ping)).Methods(http.MethodGet, http.MethodHead) + r.Handle(VersionedPath("/libpod/_ping"), s.APIHandler(compat.Ping)).Methods(http.MethodGet, http.MethodHead) return nil } diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index 79dac990a..96b6aef7c 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -5,9 +5,15 @@ # NOTE: paths with a leading slash will be interpreted as-is; # paths without will have '/v1.40/' prepended. -t GET /_ping 200 OK +t GET /_ping 200 OK t HEAD /_ping 200 t GET /libpod/_ping 200 OK +t HEAD /libpod/_ping 200 + +t GET _ping 200 OK +t HEAD _ping 200 +t GET libpod/_ping 200 OK +t HEAD libpod/_ping 200 for i in /version version; do t GET $i 200 \ diff --git a/test/apiv2/rest_api/test_rest_v1_0_0.py b/test/apiv2/rest_api/test_rest_v1_0_0.py index 7c53623cb..2e574e015 100644 --- a/test/apiv2/rest_api/test_rest_v1_0_0.py +++ b/test/apiv2/rest_api/test_rest_v1_0_0.py @@ -13,9 +13,11 @@ from multiprocessing import Process import requests from dateutil.parser import parse +PODMAN_URL = "http://localhost:8080" + def _url(path): - return "http://localhost:8080/v1.0.0/libpod" + path + return PODMAN_URL + "/v1.0.0/libpod" + path def podman(): @@ -205,7 +207,21 @@ class TestApi(unittest.TestCase): search.join(timeout=10) self.assertFalse(search.is_alive(), "/images/search took too long") - def validateObjectFields(self, buffer): + def test_ping(self): + r = requests.get(PODMAN_URL + "/_ping") + self.assertEqual(r.status_code, 200, r.text) + + r = requests.head(PODMAN_URL + "/_ping") + self.assertEqual(r.status_code, 200, r.text) + + r = requests.get(_url("/_ping")) + self.assertEqual(r.status_code, 200, r.text) + + r = requests.get(_url("/_ping")) + self.assertEqual(r.status_code, 200, r.text) + + +def validateObjectFields(self, buffer): objs = json.loads(buffer) if not isinstance(objs, dict): for o in objs: @@ -214,6 +230,5 @@ class TestApi(unittest.TestCase): _ = objs["Id"] return objs - if __name__ == '__main__': unittest.main() -- cgit v1.2.3-54-g00ecf From 895e0d0e2ea74cbba4a4a351e865c55f313bdf99 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 29 Jul 2020 21:58:46 +0200 Subject: fix pod creation with "new:" syntax When you execute podman create/run with the --pod new: syntax the pod was created but the namespaces where not shared and therefore containers could not communicate over localhost. Add the default namespaces and pass the network options to the pod create options. Signed-off-by: Paul Holzinger --- cmd/podman/containers/create.go | 13 ++++++------- cmd/podman/containers/run.go | 2 +- pkg/specgen/generate/namespaces.go | 4 ++++ test/e2e/run_test.go | 6 +++++- 4 files changed, 16 insertions(+), 9 deletions(-) (limited to 'pkg') diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 41e63da76..0cd56f540 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -124,7 +124,7 @@ func create(cmd *cobra.Command, args []string) error { return err } - if _, err := createPodIfNecessary(s); err != nil { + if _, err := createPodIfNecessary(s, cliVals.Net); err != nil { return err } @@ -279,7 +279,7 @@ func openCidFile(cidfile string) (*os.File, error) { // createPodIfNecessary automatically creates a pod when requested. if the pod name // has the form new:ID, the pod ID is created and the name in the spec generator is replaced // with ID. -func createPodIfNecessary(s *specgen.SpecGenerator) (*entities.PodCreateReport, error) { +func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions) (*entities.PodCreateReport, error) { if !strings.HasPrefix(s.Pod, "new:") { return nil, nil } @@ -288,11 +288,10 @@ func createPodIfNecessary(s *specgen.SpecGenerator) (*entities.PodCreateReport, return nil, errors.Errorf("new pod name must be at least one character") } createOptions := entities.PodCreateOptions{ - Name: podName, - Infra: true, - Net: &entities.NetOptions{ - PublishPorts: s.PortMappings, - }, + Name: podName, + Infra: true, + Net: netOpts, + CreateCommand: os.Args, } s.Pod = podName return registry.ContainerEngine().PodCreate(context.Background(), createOptions) diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 638b1c96e..d8ffa5725 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -173,7 +173,7 @@ func run(cmd *cobra.Command, args []string) error { } runOpts.Spec = s - if _, err := createPodIfNecessary(s); err != nil { + if _, err := createPodIfNecessary(s, cliVals.Net); err != nil { return err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 566830cd8..39a45398d 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -452,6 +452,10 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { var options []libpod.PodCreateOption var erroredOptions []libpod.PodCreateOption + if ns == nil { + //set the default namespaces + ns = strings.Split(specgen.DefaultKernelNamespaces, ",") + } for _, toShare := range ns { switch toShare { case "cgroup": diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index d2950ed43..95eecd042 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -797,7 +797,11 @@ USER mail` }) It("podman run --pod automatically", func() { - session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"}) + session := podmanTest.Podman([]string{"run", "-d", "--pod", "new:foobar", ALPINE, "nc", "-l", "-p", "8080"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--pod", "foobar", ALPINE, "/bin/sh", "-c", "echo test | nc -w 1 127.0.0.1 8080"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) -- cgit v1.2.3-54-g00ecf From 3dfd8630a51a37734ad8c51162c4d004b8ffffb2 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 30 Jun 2020 15:44:14 -0400 Subject: Add username to /etc/passwd inside of container if --userns keep-id If I enter a continer with --userns keep-id, my UID will be present inside of the container, but most likely my user will not be defined. This patch will take information about the user and stick it into the container. Signed-off-by: Daniel J Walsh --- cmd/podman/common/specgen.go | 2 ++ libpod/container.go | 8 ++++- libpod/container_internal_linux.go | 58 ++++++++++++++++++++++++++++----- libpod/container_internal_linux_test.go | 42 ++++++++++++++++++++++++ libpod/options.go | 14 ++++++++ pkg/specgen/generate/namespaces.go | 4 ++- test/e2e/run_userns_test.go | 10 ++++++ 7 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 libpod/container_internal_linux_test.go (limited to 'pkg') diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 7716fc150..2333f2f7e 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -260,6 +260,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // If some mappings are specified, assume a private user namespace if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) { s.UserNS.NSMode = specgen.Private + } else { + s.UserNS.NSMode = specgen.NamespaceMode(userNS) } s.Terminal = c.TTY diff --git a/libpod/container.go b/libpod/container.go index 6a87ad3a9..9ad938a5c 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -277,6 +277,9 @@ type ContainerConfig struct { User string `json:"user,omitempty"` // Additional groups to add Groups []string `json:"groups,omitempty"` + // AddCurrentUserPasswdEntry indicates that the current user passwd entry + // should be added to the /etc/passwd within the container + AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` // Namespace Config // IDs of container to share namespaces with @@ -774,7 +777,10 @@ func (c *Container) Hostname() string { // WorkingDir returns the containers working dir func (c *Container) WorkingDir() string { - return c.config.Spec.Process.Cwd + if c.config.Spec.Process != nil { + return c.config.Spec.Process.Cwd + } + return "/" } // State Accessors diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index cfcf9b823..f86903fd1 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "os/user" "path" "path/filepath" "strconv" @@ -33,7 +34,7 @@ import ( "github.com/containers/libpod/v2/pkg/util" "github.com/containers/storage/pkg/archive" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/opencontainers/runc/libcontainer/user" + User "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -1420,9 +1421,23 @@ func (c *Container) getHosts() string { return hosts } -// generatePasswd generates a container specific passwd file, -// iff g.config.User is a number -func (c *Container) generatePasswd() (string, error) { +// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user +// running the container engine +func (c *Container) generateCurrentUserPasswdEntry() (string, error) { + uid := rootless.GetRootlessUID() + if uid == 0 { + return "", nil + } + u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID())) + if err != nil { + return "", errors.Wrapf(err, "failed to get current user") + } + return fmt.Sprintf("%s:x:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, c.WorkingDir()), nil +} + +// generateUserPasswdEntry generates an /etc/passwd entry for the container user +// to run in the container. +func (c *Container) generateUserPasswdEntry() (string, error) { var ( groupspec string gid int @@ -1440,14 +1455,16 @@ func (c *Container) generatePasswd() (string, error) { if err != nil { return "", nil } + // Lookup the user to see if it exists in the container image _, err = lookup.GetUser(c.state.Mountpoint, userspec) - if err != nil && err != user.ErrNoPasswdEntries { + if err != nil && err != User.ErrNoPasswdEntries { return "", err } if err == nil { return "", nil } + if groupspec != "" { ugid, err := strconv.ParseUint(groupspec, 10, 32) if err == nil { @@ -1460,14 +1477,39 @@ func (c *Container) generatePasswd() (string, error) { gid = group.Gid } } + return fmt.Sprintf("%d:x:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), nil +} + +// generatePasswd generates a container specific passwd file, +// iff g.config.User is a number +func (c *Container) generatePasswd() (string, error) { + if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" { + return "", nil + } + pwd := "" + if c.config.User != "" { + entry, err := c.generateUserPasswdEntry() + if err != nil { + return "", err + } + pwd += entry + } + if c.config.AddCurrentUserPasswdEntry { + entry, err := c.generateCurrentUserPasswdEntry() + if err != nil { + return "", err + } + pwd += entry + } + if pwd == "" { + return "", nil + } originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd") orig, err := ioutil.ReadFile(originPasswdFile) if err != nil && !os.IsNotExist(err) { return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) } - - pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir()) - passwdFile, err := c.writeStringToRundir("passwd", pwd) + passwdFile, err := c.writeStringToRundir("passwd", string(orig)+pwd) if err != nil { return "", errors.Wrapf(err, "failed to create temporary passwd file") } diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go new file mode 100644 index 000000000..078cc53a7 --- /dev/null +++ b/libpod/container_internal_linux_test.go @@ -0,0 +1,42 @@ +// +build linux + +package libpod + +import ( + "io/ioutil" + "os" + "testing" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" +) + +func TestGenerateUserPasswdEntry(t *testing.T) { + dir, err := ioutil.TempDir("", "libpod_test_") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + c := Container{ + config: &ContainerConfig{ + User: "123:456", + Spec: &spec.Spec{}, + }, + state: &ContainerState{ + Mountpoint: "/does/not/exist/tmp/", + }, + } + user, err := c.generateUserPasswdEntry() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, user, "123:x:123:456:container user:/:/bin/sh\n") + + c.config.User = "567" + user, err = c.generateUserPasswdEntry() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, user, "567:x:567:0:container user:/:/bin/sh\n") +} diff --git a/libpod/options.go b/libpod/options.go index bff3f3c18..560b406e2 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -844,6 +844,20 @@ func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { } } +// WithAddCurrentUserPasswdEntry indicates that container should add current +// user entry to /etc/passwd, since the UID will be mapped into the container, +// via user namespace +func WithAddCurrentUserPasswdEntry() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.AddCurrentUserPasswdEntry = true + return nil + } +} + // WithUserNSFrom indicates the the container should join the user namespace of // the given container. // If the container has joined a pod, it can only join the namespaces of diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 39a45398d..22670ca61 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -153,7 +153,9 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. // User switch s.UserNS.NSMode { case specgen.KeepID: - if !rootless.IsRootless() { + if rootless.IsRootless() { + toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) + } else { // keep-id as root doesn't need a user namespace s.UserNS.NSMode = specgen.Host } diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index c0d98f7b1..24d3e42eb 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -89,6 +89,16 @@ var _ = Describe("Podman UserNS support", func() { Expect(ok).To(BeTrue()) }) + It("podman --userns=keep-id check passwd", func() { + session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "id", "-un"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + u, err := user.Current() + Expect(err).To(BeNil()) + ok, _ := session.GrepString(u.Name) + Expect(ok).To(BeTrue()) + }) + It("podman --userns=keep-id root owns /usr", func() { session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf