Skip to content

Commit

Permalink
Merge pull request #12429 from cdoern/scp
Browse files Browse the repository at this point in the history
podman image scp never enter podman user NS
  • Loading branch information
openshift-merge-robot authored Jan 5, 2022
2 parents 6902d9d + f6d00ea commit cbb2b68
Show file tree
Hide file tree
Showing 15 changed files with 491 additions and 269 deletions.
309 changes: 154 additions & 155 deletions cmd/podman/images/scp.go

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions cmd/podman/images/scp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package images

import (
"testing"

"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/stretchr/testify/assert"
)

func TestParseSCPArgs(t *testing.T) {
args := []string{"alpine", "root@localhost::"}
var source *entities.ImageScpOptions
var dest *entities.ImageScpOptions
var err error
source, _, err = parseImageSCPArg(args[0])
assert.Nil(t, err)
assert.Equal(t, source.Image, "alpine")

dest, _, err = parseImageSCPArg(args[1])
assert.Nil(t, err)
assert.Equal(t, dest.Image, "")
assert.Equal(t, dest.User, "root")

args = []string{"root@localhost::alpine"}
source, _, err = parseImageSCPArg(args[0])
assert.Nil(t, err)
assert.Equal(t, source.User, "root")
assert.Equal(t, source.Image, "alpine")

args = []string{"[email protected]::alpine", "[email protected]::"}
source, _, err = parseImageSCPArg(args[0])
assert.Nil(t, err)
assert.True(t, source.Remote)
assert.Equal(t, source.Image, "alpine")

dest, _, err = parseImageSCPArg(args[1])
assert.Nil(t, err)
assert.True(t, dest.Remote)
assert.Equal(t, dest.Image, "")

args = []string{"[email protected]::alpine"}
source, _, err = parseImageSCPArg(args[0])
assert.Nil(t, err)
assert.True(t, source.Remote)
assert.Equal(t, source.Image, "alpine")
}
87 changes: 87 additions & 0 deletions cmd/podman/images/scp_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package images

import (
"strings"

"github.com/containers/image/v5/docker/reference"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/pkg/errors"
)

// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user
// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable
func parseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
location := entities.ImageScpOptions{}
var err error
cliConnections := []string{}

switch {
case strings.Contains(arg, "@localhost"): // image transfer between users
location.User = strings.Split(arg, "@")[0]
location, err = validateImagePortion(location, arg)
if err != nil {
return nil, nil, err
}
case strings.Contains(arg, "::"):
location, err = validateImagePortion(location, arg)
if err != nil {
return nil, nil, err
}
location.Remote = true
cliConnections = append(cliConnections, arg)
default:
location.Image = arg
}
return &location, cliConnections, nil
}

// validateImagePortion is a helper function to validate the image name in an SCP argument
func validateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) {
if remoteArgLength(arg, 1) > 0 {
err := validateImageName(strings.Split(arg, "::")[1])
if err != nil {
return location, err
}
location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections
}
return location, nil
}

// validateSCPArgs takes the array of source and destination options and checks for common errors
func validateSCPArgs(locations []*entities.ImageScpOptions) (bool, error) {
if len(locations) > 2 {
return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments")
}
switch {
case len(locations[0].Image) > 0 && len(locations[1].Image) > 0:
return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
case len(locations[0].Image) == 0 && len(locations[1].Image) == 0:
return false, errors.Wrapf(define.ErrInvalidArg, "a source image must be specified")
case len(locations[0].Image) == 0 && len(locations[1].Image) != 0:
if locations[0].Remote && locations[1].Remote {
return true, nil // we need to flip the cliConnections array so the save/load connections are in the right place
}
}
return false, nil
}

// validateImageName makes sure that the image given is valid and no injections are occurring
// we simply use this for error checking, bot setting the image
func validateImageName(input string) error {
// ParseNormalizedNamed transforms a shortname image into its
// full name reference so busybox => docker.io/library/busybox
// we want to keep our shortnames, so only return an error if
// we cannot parse what the user has given us
_, err := reference.ParseNormalizedNamed(input)
return err
}

// remoteArgLength is a helper function to simplify the extracting of host argument data
// returns an int which contains the length of a specified index in a host::image string
func remoteArgLength(input string, side int) int {
if strings.Contains(input, "::") {
return len((strings.Split(input, "::"))[side])
}
return -1
}
4 changes: 2 additions & 2 deletions cmd/podman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ func parseCommands() *cobra.Command {
// Command cannot be run rootless
_, found := c.Command.Annotations[registry.UnshareNSRequired]
if found {
if rootless.IsRootless() && os.Getuid() != 0 {
if rootless.IsRootless() && os.Getuid() != 0 && c.Command.Name() != "scp" {
c.Command.RunE = func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot run command %q in rootless mode, must execute `podman unshare` first", cmd.CommandPath())
}
}
} else {
_, found = c.Command.Annotations[registry.ParentNSRequired]
if rootless.IsRootless() && found {
if rootless.IsRootless() && found && c.Command.Name() != "scp" {
c.Command.RunE = func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot run command %q in rootless mode", cmd.CommandPath())
}
Expand Down
1 change: 1 addition & 0 deletions contrib/cirrus/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ setup_rootless() {
groupadd -g $rootless_gid $ROOTLESS_USER
useradd -g $rootless_gid -u $rootless_uid --no-user-group --create-home $ROOTLESS_USER
chown -R $ROOTLESS_USER:$ROOTLESS_USER "$GOPATH" "$GOSRC"
echo "$ROOTLESS_USER ALL=(root) NOPASSWD: ALL" > /etc/sudoers.d/ci-rootless

mkdir -p "$HOME/.ssh" "/home/$ROOTLESS_USER/.ssh"

Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/entities/engine_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type ImageEngine interface {
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
Transfer(ctx context.Context, scpOpts ImageScpOptions) error
Transfer(ctx context.Context, source ImageScpOptions, dest ImageScpOptions, parentFlags []string) error
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
Expand Down
36 changes: 17 additions & 19 deletions pkg/domain/entities/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,30 +311,28 @@ type ImageSaveOptions struct {
Quiet bool
}

// ImageScpOptions provide options for securely copying images to podman remote
// ImageScpOptions provide options for securely copying images to and from a remote host
type ImageScpOptions struct {
// SoureImageName is the image the user is providing to load on a remote machine
SourceImageName string
// Tag allows for a new image to be created under the given name
Tag string
// ToRemote specifies that we are loading to the remote host
ToRemote bool
// FromRemote specifies that we are loading from the remote host
FromRemote bool
// Remote determines if this entity is operating on a remote machine
Remote bool `json:"remote,omitempty"`
// File is the input/output file for the save and load Operation
File string `json:"file,omitempty"`
// Quiet Determines if the save and load operation will be done quietly
Quiet bool `json:"quiet,omitempty"`
// Image is the image the user is providing to save and load
Image string `json:"image,omitempty"`
// User is used in conjunction with Transfer to determine if a valid user was given to save from/load into
User string `json:"user,omitempty"`
}

// ImageScpConnections provides the ssh related information used in remote image transfer
type ImageScpConnections struct {
// Connections holds the raw string values for connections (ssh or unix)
Connections []string
// URI contains the ssh connection URLs to be used by the client
URI []*url.URL
// Iden contains ssh identity keys to be used by the client
Iden []string
// Save Options used for first half of the scp operation
Save ImageSaveOptions
// Load options used for the second half of the scp operation
Load ImageLoadOptions
// Rootless determines whether we are loading locally from root storage to rootless storage
Rootless bool
// User is used in conjunction with Rootless to determine which user to use to obtain the uid
User string
// Identities contains ssh identity keys to be used by the client
Identities []string
}

// ImageTreeOptions provides options for ImageEngine.Tree()
Expand Down
Loading

0 comments on commit cbb2b68

Please sign in to comment.