summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/create.go16
-rw-r--r--cmd/podman/images/scp.go309
-rw-r--r--cmd/podman/images/scp_test.go46
-rw-r--r--cmd/podman/images/scp_utils.go87
-rw-r--r--cmd/podman/main.go4
-rw-r--r--contrib/cirrus/lib.sh1
-rw-r--r--docs/source/markdown/podman-pod-create.1.md34
-rw-r--r--libpod/container_config.go11
-rw-r--r--libpod/container_inspect.go36
-rw-r--r--libpod/define/pod_inspect.go2
-rw-r--r--libpod/options.go19
-rw-r--r--libpod/pod_api.go3
-rw-r--r--pkg/api/handlers/libpod/pods.go1
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/images.go36
-rw-r--r--pkg/domain/entities/pods.go4
-rw-r--r--pkg/domain/infra/abi/images.go179
-rw-r--r--pkg/domain/infra/tunnel/images.go2
-rw-r--r--pkg/rootless/rootless_linux.c2
-rw-r--r--pkg/specgen/generate/container_create.go111
-rw-r--r--pkg/specgen/generate/oci.go37
-rw-r--r--pkg/specgen/podspecgen.go5
-rw-r--r--test/e2e/common_test.go28
-rw-r--r--test/e2e/image_scp_test.go38
-rw-r--r--test/e2e/libpod_suite_remote_test.go6
-rw-r--r--test/e2e/pod_create_test.go62
-rw-r--r--test/system/120-load.bats29
-rw-r--r--test/system/520-checkpoint.bats2
-rw-r--r--test/system/helpers.bash14
-rw-r--r--test/utils/utils.go5
-rw-r--r--utils/utils.go9
31 files changed, 789 insertions, 351 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index f02c5713b..32d227e65 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -540,14 +540,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets)
- securityOptFlagName := "security-opt"
- createFlags.StringArrayVar(
- &cf.SecurityOpt,
- securityOptFlagName, []string{},
- "Security Options",
- )
- _ = cmd.RegisterFlagCompletionFunc(securityOptFlagName, AutocompleteSecurityOption)
-
shmSizeFlagName := "shm-size"
createFlags.String(
shmSizeFlagName, shmSize(),
@@ -720,6 +712,13 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
`If a container with the same name exists, replace it`,
)
}
+ securityOptFlagName := "security-opt"
+ createFlags.StringArrayVar(
+ &cf.SecurityOpt,
+ securityOptFlagName, []string{},
+ "Security Options",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(securityOptFlagName, AutocompleteSecurityOption)
subgidnameFlagName := "subgidname"
createFlags.StringVar(
@@ -890,6 +889,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
"Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
)
_ = cmd.RegisterFlagCompletionFunc(deviceReadBpsFlagName, completion.AutocompleteDefault)
+
volumesFromFlagName := "volumes-from"
createFlags.StringArrayVar(
&cf.VolumesFrom,
diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go
index 5c9cadc7a..f02a3c15e 100644
--- a/cmd/podman/images/scp.go
+++ b/cmd/podman/images/scp.go
@@ -6,18 +6,19 @@ import (
"io/ioutil"
urlP "net/url"
"os"
+ "os/exec"
+ "os/user"
"strconv"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/cmd/podman/common"
- "github.com/containers/podman/v3/cmd/podman/parse"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/system/connection"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/rootless"
- "github.com/docker/distribution/reference"
+ "github.com/containers/podman/v3/utils"
scpD "github.com/dtylman/scp"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -28,8 +29,12 @@ import (
var (
saveScpDescription = `Securely copy an image from one host to another.`
imageScpCommand = &cobra.Command{
- Use: "scp [options] IMAGE [HOST::]",
- Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
+ Use: "scp [options] IMAGE [HOST::]",
+ Annotations: map[string]string{
+ registry.UnshareNSRequired: "",
+ registry.ParentNSRequired: "",
+ registry.EngineMode: registry.ABIMode,
+ },
Long: saveScpDescription,
Short: "securely copy images",
RunE: scp,
@@ -40,7 +45,10 @@ var (
)
var (
- scpOpts entities.ImageScpOptions
+ parentFlags []string
+ source entities.ImageScpOptions
+ dest entities.ImageScpOptions
+ sshInfo entities.ImageScpConnections
)
func init() {
@@ -53,7 +61,7 @@ func init() {
func scpFlags(cmd *cobra.Command) {
flags := cmd.Flags()
- flags.BoolVarP(&scpOpts.Save.Quiet, "quiet", "q", false, "Suppress the output")
+ flags.BoolVarP(&source.Quiet, "quiet", "q", false, "Suppress the output")
}
func scp(cmd *cobra.Command, args []string) (finalErr error) {
@@ -61,24 +69,31 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
// TODO add tag support for images
err error
)
- if scpOpts.Save.Quiet { // set quiet for both load and save
- scpOpts.Load.Quiet = true
+ for i, val := range os.Args {
+ if val == "image" {
+ break
+ }
+ if i == 0 {
+ continue
+ }
+ if strings.Contains(val, "CIRRUS") { // need to skip CIRRUS flags for testing suite purposes
+ continue
+ }
+ parentFlags = append(parentFlags, val)
}
- f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
+ podman, err := os.Executable()
if err != nil {
return err
}
- defer os.Remove(f.Name())
-
- scpOpts.Save.Output = f.Name()
- scpOpts.Load.Input = scpOpts.Save.Output
- if err := parse.ValidateFileName(saveOpts.Output); err != nil {
+ f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
+ if err != nil {
return err
}
confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once
if err != nil {
return errors.Wrapf(err, "could not make config")
}
+
abiEng, err := registry.NewImageEngine(cmd, args) // abi native engine
if err != nil {
return err
@@ -88,77 +103,115 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
if err != nil {
return err
}
- serv, err := parseArgs(args, cfg) // parses connection data and "which way" we are loading and saving
+ locations := []*entities.ImageScpOptions{}
+ cliConnections := []string{}
+ flipConnections := false
+ for _, arg := range args {
+ loc, connect, err := parseImageSCPArg(arg)
+ if err != nil {
+ return err
+ }
+ locations = append(locations, loc)
+ cliConnections = append(cliConnections, connect...)
+ }
+ source = *locations[0]
+ switch {
+ case len(locations) > 1:
+ if flipConnections, err = validateSCPArgs(locations); err != nil {
+ return err
+ }
+ if flipConnections { // the order of cliConnections matters, we need to flip both arrays since the args are parsed separately sometimes.
+ connect := cliConnections[0]
+ cliConnections[0] = cliConnections[1]
+ cliConnections[1] = connect
+
+ loc := locations[0]
+ locations[0] = locations[1]
+ locations[1] = loc
+ }
+ dest = *locations[1]
+ case len(locations) == 1:
+ switch {
+ case len(locations[0].Image) == 0:
+ return errors.Wrapf(define.ErrInvalidArg, "no source image specified")
+ case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE
+ return errors.Wrapf(define.ErrInvalidArg, "must specify a destination")
+ }
+ }
+
+ source.File = f.Name() // after parsing the arguments, set the file for the save/load
+ dest.File = source.File
+ if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors
+ return err
+ }
+
+ var serv map[string]config.Destination
+ serv, err = GetServiceInformation(cliConnections, cfg)
if err != nil {
return err
}
+
// TODO: Add podman remote support
confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine
+ saveCmd, loadCmd := createCommands(podman)
switch {
- case scpOpts.FromRemote: // if we want to load FROM the remote
- err = saveToRemote(scpOpts.SourceImageName, scpOpts.Save.Output, "", scpOpts.URI[0], scpOpts.Iden[0])
+ case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case
+ err = saveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
if err != nil {
return err
}
- if scpOpts.ToRemote { // we want to load remote -> remote
- rep, err := loadToRemote(scpOpts.Save.Output, "", scpOpts.URI[1], scpOpts.Iden[1])
+ if dest.Remote { // we want to load remote -> remote, both source and dest are remote
+ rep, err := loadToRemote(dest.File, "", sshInfo.URI[1], sshInfo.Identities[1])
if err != nil {
return err
}
fmt.Println(rep)
break
}
- report, err := abiEng.Load(context.Background(), scpOpts.Load)
+ err = execPodman(podman, loadCmd)
if err != nil {
return err
}
- fmt.Println("Loaded image(s): " + strings.Join(report.Names, ","))
- case scpOpts.ToRemote: // remote host load
- scpOpts.Save.Format = "oci-archive"
- abiErr := abiEng.Save(context.Background(), scpOpts.SourceImageName, []string{}, scpOpts.Save) // save the image locally before loading it on remote, local, or ssh
- if abiErr != nil {
- errors.Wrapf(abiErr, "could not save image as specified")
- }
- rep, err := loadToRemote(scpOpts.Save.Output, "", scpOpts.URI[0], scpOpts.Iden[0])
+ case dest.Remote: // remote host load, implies source is local
+ err = execPodman(podman, saveCmd)
if err != nil {
return err
}
- fmt.Println(rep)
- // TODO: Add podman remote support
- default: // else native load
- scpOpts.Save.Format = "oci-archive"
- _, err := os.Open(scpOpts.Save.Output)
+ rep, err := loadToRemote(source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
if err != nil {
return err
}
- if scpOpts.Tag != "" {
- return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
- }
- scpOpts.Save.Format = "oci-archive"
- abiErr := abiEng.Save(context.Background(), scpOpts.SourceImageName, []string{}, scpOpts.Save) // save the image locally before loading it on remote, local, or ssh
- if abiErr != nil {
- return errors.Wrapf(abiErr, "could not save image as specified")
+ fmt.Println(rep)
+ if err = os.Remove(source.File); err != nil {
+ return err
}
- if !rootless.IsRootless() && scpOpts.Rootless {
- if scpOpts.User == "" {
- scpOpts.User = os.Getenv("SUDO_USER")
- if scpOpts.User == "" {
- return errors.New("could not obtain root user, make sure the environmental variable SUDO_USER is set, and that this command is being run as root")
+ // TODO: Add podman remote support
+ default: // else native load, both source and dest are local and transferring between users
+ if source.User == "" { // source user has to be set, destination does not
+ source.User = os.Getenv("USER")
+ if source.User == "" {
+ u, err := user.Current()
+ if err != nil {
+ return errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set")
}
+ source.User = u.Username
}
- err := abiEng.Transfer(context.Background(), scpOpts)
- if err != nil {
- return err
- }
- } else {
- rep, err := abiEng.Load(context.Background(), scpOpts.Load)
- if err != nil {
- return err
- }
- fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ","))
+ }
+ err := abiEng.Transfer(context.Background(), source, dest, parentFlags)
+ if err != nil {
+ return err
}
}
+ src, err := json.MarshalIndent(source, "", " ")
+ if err != nil {
+ return err
+ }
+ dst, err := json.MarshalIndent(dest, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Printf("SOURCE: %s\nDEST: %s\n", string(src), string(dst))
return nil
}
@@ -249,119 +302,28 @@ func createConnection(url *urlP.URL, iden string) (*ssh.Client, string, error) {
return dialAdd, file, 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 th euser 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 {
- return len((strings.Split(input, "::"))[side])
-}
-
-// parseArgs returns the valid connection data based off of the information provided by the user
-// args is an array of the command arguments and cfg is tooling configuration used to get service destinations
-// returned is serv and an error if applicable. serv is a map of service destinations with the connection name as the index
-// this connection name is intended to be used as EngineConfig.ServiceDestinations
-// this function modifies the global scpOpt entities: FromRemote, ToRemote, Connections, and SourceImageName
-func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination, error) {
- serv := map[string]config.Destination{}
- cliConnections := []string{}
- switch len(args) {
- case 1:
- if strings.Contains(args[0], "localhost") {
- if strings.Split(args[0], "@")[0] != "root" {
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot transfer images from any user besides root using sudo")
- }
- scpOpts.Rootless = true
- scpOpts.SourceImageName = strings.Split(args[0], "::")[1]
- } else if strings.Contains(args[0], "::") {
- scpOpts.FromRemote = true
- cliConnections = append(cliConnections, args[0])
- } else {
- err := validateImageName(args[0])
- if err != nil {
- return nil, err
- }
- scpOpts.SourceImageName = args[0]
- }
- case 2:
- if strings.Contains(args[0], "localhost") || strings.Contains(args[1], "localhost") { // only supporting root to local using sudo at the moment
- if strings.Split(args[0], "@")[0] != "root" {
- return nil, errors.Wrapf(define.ErrInvalidArg, "currently, transferring images to a user account is not supported")
- }
- if len(strings.Split(args[0], "::")) > 1 {
- scpOpts.Rootless = true
- scpOpts.User = strings.Split(args[1], "@")[0]
- scpOpts.SourceImageName = strings.Split(args[0], "::")[1]
- } else {
- return nil, errors.Wrapf(define.ErrInvalidArg, "currently, you cannot rename images during the transfer or transfer them to a user account")
- }
- } else if strings.Contains(args[0], "::") {
- if !(strings.Contains(args[1], "::")) && remoteArgLength(args[0], 1) == 0 { // if an image is specified, this mean we are loading to our client
- cliConnections = append(cliConnections, args[0])
- scpOpts.ToRemote = true
- scpOpts.SourceImageName = args[1]
- } else if strings.Contains(args[1], "::") { // both remote clients
- scpOpts.FromRemote = true
- scpOpts.ToRemote = true
- if remoteArgLength(args[0], 1) == 0 { // is save->load w/ one image name
- cliConnections = append(cliConnections, args[0])
- cliConnections = append(cliConnections, args[1])
- } else if remoteArgLength(args[0], 1) > 0 && remoteArgLength(args[1], 1) > 0 {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- } else { // else its a load save (order of args)
- cliConnections = append(cliConnections, args[1])
- cliConnections = append(cliConnections, args[0])
- }
- } else {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- } else if strings.Contains(args[1], "::") { // if we are given image host::
- if remoteArgLength(args[1], 1) > 0 {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- err := validateImageName(args[0])
- if err != nil {
- return nil, err
- }
- scpOpts.SourceImageName = args[0]
- scpOpts.ToRemote = true
- cliConnections = append(cliConnections, args[1])
- } else {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- }
+// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information
+func GetServiceInformation(cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) {
+ var serv map[string]config.Destination
var url string
var iden string
for i, val := range cliConnections {
splitEnv := strings.SplitN(val, "::", 2)
- scpOpts.Connections = append(scpOpts.Connections, splitEnv[0])
+ sshInfo.Connections = append(sshInfo.Connections, splitEnv[0])
if len(splitEnv[1]) != 0 {
err := validateImageName(splitEnv[1])
if err != nil {
return nil, err
}
- scpOpts.SourceImageName = splitEnv[1]
+ source.Image = splitEnv[1]
//TODO: actually use the new name given by the user
}
- conn, found := cfg.Engine.ServiceDestinations[scpOpts.Connections[i]]
+ conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
if found {
url = conn.URI
iden = conn.Identity
} else { // no match, warn user and do a manual connection.
- url = "ssh://" + scpOpts.Connections[i]
+ url = "ssh://" + sshInfo.Connections[i]
iden = ""
logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location")
}
@@ -374,8 +336,45 @@ func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination
return nil, err
}
}
- scpOpts.URI = append(scpOpts.URI, urlT)
- scpOpts.Iden = append(scpOpts.Iden, iden)
+ sshInfo.URI = append(sshInfo.URI, urlT)
+ sshInfo.Identities = append(sshInfo.Identities, iden)
}
return serv, nil
}
+
+// execPodman executes the podman save/load command given the podman binary
+func execPodman(podman string, command []string) error {
+ if rootless.IsRootless() {
+ cmd := exec.Command(podman)
+ utils.CreateSCPCommand(cmd, command[1:])
+ logrus.Debug("Executing podman command")
+ return cmd.Run()
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ cmd := exec.Command("su", "-l", "root", "--command")
+ cmd = utils.CreateSCPCommand(cmd, []string{strings.Join(command, " ")})
+ return cmd.Run()
+ }
+ cmd := exec.Command(machinectl, "shell", "-q", "root@.host")
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing load command machinectl")
+ return cmd.Run()
+}
+
+// createCommands forms the podman save and load commands used by SCP
+func createCommands(podman string) ([]string, []string) {
+ var parentString string
+ quiet := ""
+ if source.Quiet {
+ quiet = "-q "
+ }
+ if len(parentFlags) > 0 {
+ parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added
+ } else {
+ parentString = strings.Join(parentFlags, " ")
+ }
+ loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ")
+ saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ")
+ return saveCmd, loadCmd
+}
diff --git a/cmd/podman/images/scp_test.go b/cmd/podman/images/scp_test.go
new file mode 100644
index 000000000..d4d8f8e58
--- /dev/null
+++ b/cmd/podman/images/scp_test.go
@@ -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{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"}
+ 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{"charliedoern@192.168.68.126::alpine"}
+ source, _, err = parseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.True(t, source.Remote)
+ assert.Equal(t, source.Image, "alpine")
+}
diff --git a/cmd/podman/images/scp_utils.go b/cmd/podman/images/scp_utils.go
new file mode 100644
index 000000000..ebb874c1c
--- /dev/null
+++ b/cmd/podman/images/scp_utils.go
@@ -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
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index b7f5f1720..b38734617 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -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())
}
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index cff8f4b3f..c7352106a 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -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"
diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md
index 56c3e7d34..b2e16e051 100644
--- a/docs/source/markdown/podman-pod-create.1.md
+++ b/docs/source/markdown/podman-pod-create.1.md
@@ -231,6 +231,38 @@ NOTE: This cannot be modified once the pod is created.
If another pod with the same name already exists, replace and remove it. The default is **false**.
+#### **--security-opt**=*option*
+
+Security Options
+
+- `apparmor=unconfined` : Turn off apparmor confinement for the pod
+- `apparmor=your-profile` : Set the apparmor confinement profile for the pod
+
+- `label=user:USER` : Set the label user for the pod processes
+- `label=role:ROLE` : Set the label role for the pod processes
+- `label=type:TYPE` : Set the label process type for the pod processes
+- `label=level:LEVEL` : Set the label level for the pod processes
+- `label=filetype:TYPE` : Set the label file type for the pod files
+- `label=disable` : Turn off label separation for the pod
+
+Note: Labeling can be disabled for all pods/containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file.
+
+- `mask=/path/1:/path/2` : The paths to mask separated by a colon. A masked path
+ cannot be accessed inside the containers within the pod.
+
+- `no-new-privileges` : Disable container processes from gaining additional privileges
+
+- `seccomp=unconfined` : Turn off seccomp confinement for the pod
+- `seccomp=profile.json` : Whitelisted syscalls seccomp Json file to be used as a seccomp filter
+
+- `proc-opts=OPTIONS` : Comma-separated list of options to use for the /proc mount. More details for the
+ possible mount options are specified in the **proc(5)** man page.
+
+- **unmask**=_ALL_ or _/path/1:/path/2_, or shell expanded paths (/proc/*): Paths to unmask separated by a colon. If set to **ALL**, it will unmask all the paths that are masked or made read only by default.
+ The default masked paths are **/proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/sched_debug, /proc/scsi, /proc/timer_list, /proc/timer_stats, /sys/firmware, and /sys/fs/selinux.** The default paths that are read only are **/proc/asound, /proc/bus, /proc/fs, /proc/irq, /proc/sys, /proc/sysrq-trigger, /sys/fs/cgroup**.
+
+Note: Labeling can be disabled for all containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file.
+
#### **--share**=*namespace*
A comma-separated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, uts.
@@ -471,7 +503,7 @@ $ podman pod create --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10
```
## SEE ALSO
-**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**
+**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **containers.conf(1)**
## HISTORY
diff --git a/libpod/container_config.go b/libpod/container_config.go
index a43fd632b..288524dbd 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -400,3 +400,14 @@ type ContainerMiscConfig struct {
// and if so, what type: always or once are possible non-nil entries
InitContainerType string `json:"init_container_type,omitempty"`
}
+
+type InfraInherit struct {
+ InfraSecurity ContainerSecurityConfig
+ InfraLabels []string `json:"labelopts,omitempty"`
+ InfraVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"`
+ InfraOverlay []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"`
+ InfraImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"`
+ InfraUserVolumes []string `json:"userVolumes,omitempty"`
+ InfraResources *spec.LinuxResources `json:"resources,omitempty"`
+ InfraDevices []spec.LinuxDevice `json:"device_host_src,omitempty"`
+}
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index f72700ab6..792dfc58e 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -273,6 +273,27 @@ func (c *Container) GetInspectMounts(namedVolumes []*ContainerNamedVolume, image
return inspectMounts, nil
}
+// GetSecurityOptions retrives and returns the security related annotations and process information upon inspection
+func (c *Container) GetSecurityOptions() []string {
+ ctrSpec := c.config.Spec
+ SecurityOpt := []string{}
+ if ctrSpec.Process != nil {
+ if ctrSpec.Process.NoNewPrivileges {
+ SecurityOpt = append(SecurityOpt, "no-new-privileges")
+ }
+ }
+ if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok {
+ SecurityOpt = append(SecurityOpt, fmt.Sprintf("label=%s", label))
+ }
+ if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok {
+ SecurityOpt = append(SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp))
+ }
+ if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok {
+ SecurityOpt = append(SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor))
+ }
+ return SecurityOpt
+}
+
// Parse mount options so we can populate them in the mount structure.
// The mount passed in will be modified.
func parseMountOptionsForInspect(options []string, mount *define.InspectMount) {
@@ -422,16 +443,14 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
hostConfig.GroupAdd = make([]string, 0, len(c.config.Groups))
hostConfig.GroupAdd = append(hostConfig.GroupAdd, c.config.Groups...)
- hostConfig.SecurityOpt = []string{}
if ctrSpec.Process != nil {
if ctrSpec.Process.OOMScoreAdj != nil {
hostConfig.OomScoreAdj = *ctrSpec.Process.OOMScoreAdj
}
- if ctrSpec.Process.NoNewPrivileges {
- hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, "no-new-privileges")
- }
}
+ hostConfig.SecurityOpt = c.GetSecurityOptions()
+
hostConfig.ReadonlyRootfs = ctrSpec.Root.Readonly
hostConfig.ShmSize = c.config.ShmSize
hostConfig.Runtime = "oci"
@@ -456,15 +475,6 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue {
hostConfig.Init = true
}
- if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok {
- hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label))
- }
- if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok {
- hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp))
- }
- if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok {
- hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor))
- }
}
// Resource limits
diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go
index 97e7ffdfb..e7adc8700 100644
--- a/libpod/define/pod_inspect.go
+++ b/libpod/define/pod_inspect.go
@@ -65,6 +65,8 @@ type InspectPodData struct {
BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"device_read_bps,omitempty"`
// VolumesFrom contains the containers that the pod inherits mounts from
VolumesFrom []string `json:"volumes_from,omitempty"`
+ // SecurityOpt contains the specified security labels and related SELinux information
+ SecurityOpts []string `json:"security_opt,omitempty"`
}
// InspectPodInfraConfig contains the configuration of the pod's infra
diff --git a/libpod/options.go b/libpod/options.go
index 204f2a457..6edb9972b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1816,6 +1816,25 @@ func WithSelectedPasswordManagement(passwd *bool) CtrCreateOption {
}
}
+// WithInfraConfig allows for inheritance of compatible config entities from the infra container
+func WithInfraConfig(compatibleOptions InfraInherit) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ compatMarshal, err := json.Marshal(compatibleOptions)
+ if err != nil {
+ return errors.New("Could not marshal compatible options")
+ }
+
+ err = json.Unmarshal(compatMarshal, ctr.config)
+ if err != nil {
+ return errors.New("Could not unmarshal compatible options into contrainer config")
+ }
+ return nil
+ }
+}
+
// Pod Creation Options
// WithPodCreateCommand adds the full command plus arguments of the current
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 95a82721e..526e0c28b 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -586,6 +586,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
var inspectMounts []define.InspectMount
var devices []define.InspectDevice
var deviceLimits []define.InspectBlkioThrottleDevice
+ var infraSecurity []string
if p.state.InfraContainerID != "" {
infra, err := p.runtime.GetContainer(p.state.InfraContainerID)
if err != nil {
@@ -603,6 +604,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
infraConfig.UserNS = p.UserNSMode()
namedVolumes, mounts := infra.sortUserVolumes(infra.config.Spec)
inspectMounts, err = infra.GetInspectMounts(namedVolumes, infra.config.ImageVolumes, mounts)
+ infraSecurity = infra.GetSecurityOptions()
if err != nil {
return nil, err
}
@@ -678,6 +680,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
Devices: devices,
BlkioDeviceReadBps: deviceLimits,
VolumesFrom: p.VolumesFrom(),
+ SecurityOpts: infraSecurity,
}
return &inspectData, nil
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 3d18406a5..1b29831b4 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -42,6 +42,7 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
infraOptions := entities.NewInfraContainerCreateOptions() // options for pulling the image and FillOutSpec
infraOptions.Net = &entities.NetOptions{}
infraOptions.Devices = psg.Devices
+ infraOptions.SecurityOpt = psg.SecurityOpt
err = specgenutil.FillOutSpecGen(psg.InfraContainerSpec, &infraOptions, []string{}) // necessary for default values in many cases (userns, idmappings)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error filling out specgen"))
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index d72f64b5e..bec505163 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -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
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 8b0fd2b85..62e7f67c8 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -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()
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index f9850e5a8..1b5a1be51 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -138,6 +138,7 @@ type PodCreateOptions struct {
Userns specgen.Namespace `json:"-"`
Volume []string `json:"volume,omitempty"`
VolumesFrom []string `json:"volumes_from,omitempty"`
+ SecurityOpt []string `json:"security_opt,omitempty"`
}
// PodLogsOptions describes the options to extract pod logs.
@@ -230,7 +231,7 @@ type ContainerCreateOptions struct {
Rm bool
RootFS bool
Secrets []string
- SecurityOpt []string
+ SecurityOpt []string `json:"security_opt,omitempty"`
SdNotifyMode string
ShmSize string
SignaturePolicy string
@@ -312,6 +313,7 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod
s.Hostname = p.Hostname
s.Labels = p.Labels
s.Devices = p.Devices
+ s.SecurityOpt = p.SecurityOpt
s.NoInfra = !p.Infra
if p.InfraCommand != nil && len(*p.InfraCommand) > 0 {
s.InfraCommand = strings.Split(*p.InfraCommand, " ")
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 4346182d6..84c83ea8e 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -28,6 +28,7 @@ import (
domainUtils "github.com/containers/podman/v3/pkg/domain/utils"
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/rootless"
+ "github.com/containers/podman/v3/utils"
"github.com/containers/storage"
dockerRef "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
@@ -351,65 +352,19 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
return pushError
}
-// Transfer moves images from root to rootless storage so the user specified in the scp call can access and use the image modified by root
-func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
- if scpOpts.User == "" {
+// Transfer moves images between root and rootless storage so the user specified in the scp call can access and use the image modified by root
+func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
+ if source.User == "" {
return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
}
- var u *user.User
- scpOpts.User = strings.Split(scpOpts.User, ":")[0] // split in case provided with uid:gid
- _, err := strconv.Atoi(scpOpts.User)
- if err != nil {
- u, err = user.Lookup(scpOpts.User)
- if err != nil {
- return err
- }
- } else {
- u, err = user.LookupId(scpOpts.User)
- if err != nil {
- return err
- }
- }
- uid, err := strconv.Atoi(u.Uid)
- if err != nil {
- return err
- }
- gid, err := strconv.Atoi(u.Gid)
- if err != nil {
- return err
- }
- err = os.Chown(scpOpts.Save.Output, uid, gid) // chown the output because was created by root so we need to give th euser read access
- if err != nil {
- return err
- }
-
podman, err := os.Executable()
if err != nil {
return err
}
- machinectl, err := exec.LookPath("machinectl")
- if err != nil {
- logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
- cmd := exec.Command("su", "-l", u.Username, "--command", podman+" --log-level="+logrus.GetLevel().String()+" --cgroup-manager=cgroupfs load --input="+scpOpts.Save.Output) // load the new image to the rootless storage
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- logrus.Debug("Executing load command su")
- err = cmd.Run()
- if err != nil {
- return err
- }
- } else {
- cmd := exec.Command(machinectl, "shell", "-q", u.Username+"@.host", podman, "--log-level="+logrus.GetLevel().String(), "--cgroup-manager=cgroupfs", "load", "--input", scpOpts.Save.Output) // load the new image to the rootless storage
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- logrus.Debug("Executing load command machinectl")
- err = cmd.Run()
- if err != nil {
- return err
- }
+ if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
+ return transferRootless(source, dest, podman, parentFlags)
}
-
- return nil
+ return transferRootful(source, dest, podman, parentFlags)
}
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
@@ -786,3 +741,123 @@ func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStore
}
return nil
}
+
+// TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users
+func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
+ var cmdSave *exec.Cmd
+ saveCommand := parentFlags
+ saveCommand = append(saveCommand, []string{"save", "--output", source.File, source.Image}...)
+
+ loadCommand := parentFlags
+ loadCommand = append(loadCommand, []string{"load", "--input", dest.File}...)
+
+ if source.User == "root" {
+ cmdSave = exec.Command("sudo", podman)
+ } else {
+ cmdSave = exec.Command(podman)
+ }
+ cmdSave = utils.CreateSCPCommand(cmdSave, saveCommand)
+ logrus.Debug("Executing save command")
+ err := cmdSave.Run()
+ if err != nil {
+ return err
+ }
+
+ var cmdLoad *exec.Cmd
+ if source.User != "root" {
+ cmdLoad = exec.Command("sudo", podman)
+ } else {
+ cmdLoad = exec.Command(podman)
+ }
+ cmdLoad = utils.CreateSCPCommand(cmdLoad, loadCommand)
+ logrus.Debug("Executing load command")
+ err = cmdLoad.Run()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// TransferRootless creates new podman processes using exec.Command and su/machinectl, transferring images between the given source and destination users
+func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
+ basicCommand := []string{podman}
+ basicCommand = append(basicCommand, parentFlags...)
+ saveCommand := append(basicCommand, []string{"save", "--output", source.File, source.Image}...)
+ loadCommand := append(basicCommand, []string{"load", "--input", dest.File}...)
+ save := []string{strings.Join(saveCommand, " ")}
+ load := []string{strings.Join(loadCommand, " ")}
+
+ // if executing using sudo or transferring between two users, the TransferRootless approach will not work, default to using machinectl or su as necessary.
+ // the approach using sudo is preferable and more straightforward. There is no reason for using sudo in these situations
+ // since the feature is meant to transfer from root to rootless an vice versa without explicit sudo evocaiton.
+ var uSave *user.User
+ var uLoad *user.User
+ var err error
+ source.User = strings.Split(source.User, ":")[0] // split in case provided with uid:gid
+ dest.User = strings.Split(dest.User, ":")[0]
+ uSave, err = lookupUser(source.User)
+ if err != nil {
+ return err
+ }
+ switch {
+ case dest.User != "": // if we are given a destination user, check that first
+ uLoad, err = lookupUser(dest.User)
+ if err != nil {
+ return err
+ }
+ case uSave.Name != "root": // else if we have no destination user, and source is not root that means we should be root
+ uLoad, err = user.LookupId("0")
+ if err != nil {
+ return err
+ }
+ default: // else if we have no dest user, and source user IS root, we want to be the default user.
+ uString := os.Getenv("SUDO_USER")
+ if uString == "" {
+ return errors.New("$SUDO_USER must be defined to find the default rootless user")
+ }
+ uLoad, err = user.Lookup(uString)
+ if err != nil {
+ return err
+ }
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
+ err = execSu(uSave, save)
+ if err != nil {
+ return err
+ }
+ return execSu(uLoad, load)
+ }
+ err = execMachine(uSave, saveCommand, machinectl)
+ if err != nil {
+ return err
+ }
+ return execMachine(uLoad, loadCommand, machinectl)
+}
+
+func lookupUser(u string) (*user.User, error) {
+ if u, err := user.LookupId(u); err == nil {
+ return u, nil
+ }
+ return user.Lookup(u)
+}
+
+func execSu(execUser *user.User, command []string) error {
+ cmd := exec.Command("su", "-l", execUser.Username, "--command")
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing command su")
+ return cmd.Run()
+}
+
+func execMachine(execUser *user.User, command []string, machinectl string) error {
+ var cmd *exec.Cmd
+ if execUser.Uid == "0" {
+ cmd = exec.Command("sudo", machinectl, "shell", "-q", execUser.Username+"@.host")
+ } else {
+ cmd = exec.Command(machinectl, "shell", "-q", execUser.Username+"@.host")
+ }
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing command machinectl")
+ return cmd.Run()
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 2feb9d7ad..f26a489e6 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -123,7 +123,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.
return &entities.ImagePullReport{Images: pulledImages}, nil
}
-func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage")
}
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 92f331ce4..94bd40f86 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -244,7 +244,7 @@ can_use_shortcut ()
if (argv[argc+1] != NULL && (strcmp (argv[argc], "container") == 0 ||
strcmp (argv[argc], "image") == 0) &&
- strcmp (argv[argc+1], "mount") == 0)
+ (strcmp (argv[argc+1], "mount") == 0 || strcmp (argv[argc+1], "scp") == 0))
{
ret = false;
break;
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 7ab9d1b29..7d792b3b1 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -2,13 +2,14 @@ package generate
import (
"context"
- "fmt"
+ "encoding/json"
"path/filepath"
"strings"
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg"
"github.com/containers/common/libimage"
"github.com/containers/podman/v3/libpod"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/namespaces"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
@@ -29,43 +30,30 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
// If joining a pod, retrieve the pod for use, and its infra container
var pod *libpod.Pod
- var infraConfig *libpod.ContainerConfig
+ var infra *libpod.Container
if s.Pod != "" {
pod, err = rt.LookupPod(s.Pod)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
}
if pod.HasInfraContainer() {
- infra, err := pod.InfraContainer()
+ infra, err = pod.InfraContainer()
if err != nil {
return nil, nil, nil, err
}
- infraConfig = infra.Config()
}
}
- if infraConfig != nil && (len(infraConfig.NamedVolumes) > 0 || len(infraConfig.UserVolumes) > 0 || len(infraConfig.ImageVolumes) > 0 || len(infraConfig.OverlayVolumes) > 0) {
- s.VolumesFrom = append(s.VolumesFrom, infraConfig.ID)
- }
-
- if infraConfig != nil && len(infraConfig.Spec.Linux.Devices) > 0 {
- s.DevicesFrom = append(s.DevicesFrom, infraConfig.ID)
- }
- if infraConfig != nil && infraConfig.Spec.Linux.Resources != nil && infraConfig.Spec.Linux.Resources.BlockIO != nil && len(infraConfig.Spec.Linux.Resources.BlockIO.ThrottleReadBpsDevice) > 0 {
- tempDev := make(map[string]spec.LinuxThrottleDevice)
- for _, val := range infraConfig.Spec.Linux.Resources.BlockIO.ThrottleReadBpsDevice {
- nodes, err := util.FindDeviceNodes()
- if err != nil {
- return nil, nil, nil, err
- }
- key := fmt.Sprintf("%d:%d", val.Major, val.Minor)
- tempDev[nodes[key]] = spec.LinuxThrottleDevice{Rate: uint64(val.Rate)}
- }
- for i, dev := range s.ThrottleReadBpsDevice {
- tempDev[i] = dev
+ options := []libpod.CtrCreateOption{}
+ compatibleOptions := &libpod.InfraInherit{}
+ var infraSpec *spec.Spec
+ if infra != nil {
+ options, infraSpec, compatibleOptions, err = Inherit(*infra)
+ if err != nil {
+ return nil, nil, nil, err
}
- s.ThrottleReadBpsDevice = tempDev
}
+
if err := FinishThrottleDevices(s); err != nil {
return nil, nil, nil, err
}
@@ -119,8 +107,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
s.CgroupNS = defaultNS
}
- options := []libpod.CtrCreateOption{}
-
if s.ContainerCreateCommand != nil {
options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand))
}
@@ -165,7 +151,8 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err
}
- opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command)
+ infraVolumes := (len(compatibleOptions.InfraVolumes) > 0 || len(compatibleOptions.InfraUserVolumes) > 0 || len(compatibleOptions.InfraImageVolumes) > 0)
+ opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVolumes, *compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
@@ -178,27 +165,29 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
logrus.Debugf("setting container name %s", s.Name)
options = append(options, libpod.WithName(s.Name))
}
- if len(s.DevicesFrom) > 0 {
- for _, dev := range s.DevicesFrom {
- ctr, err := rt.GetContainer(dev)
- if err != nil {
- return nil, nil, nil, err
- }
- devices := ctr.DeviceHostSrc()
- s.Devices = append(s.Devices, devices...)
- }
- }
if len(s.Devices) > 0 {
- opts = extractCDIDevices(s)
+ opts = ExtractCDIDevices(s)
options = append(options, opts...)
}
- runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command)
+ runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command, compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
if len(s.HostDeviceList) > 0 {
options = append(options, libpod.WithHostDevice(s.HostDeviceList))
}
+ if infraSpec != nil && infraSpec.Linux != nil { // if we are inheriting Linux info from a pod...
+ // Pass Security annotations
+ if len(infraSpec.Annotations[define.InspectAnnotationLabel]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationLabel]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationLabel] = infraSpec.Annotations[define.InspectAnnotationLabel]
+ }
+ if len(infraSpec.Annotations[define.InspectAnnotationSeccomp]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationSeccomp]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationSeccomp] = infraSpec.Annotations[define.InspectAnnotationSeccomp]
+ }
+ if len(infraSpec.Annotations[define.InspectAnnotationApparmor]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationApparmor]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationApparmor] = infraSpec.Annotations[define.InspectAnnotationApparmor]
+ }
+ }
return runtimeSpec, s, options, err
}
func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Spec, s *specgen.SpecGenerator, infra bool, options ...libpod.CtrCreateOption) (*libpod.Container, error) {
@@ -210,7 +199,7 @@ func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Sp
return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr)
}
-func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
+func ExtractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
devs := make([]spec.LinuxDevice, 0, len(s.Devices))
var cdiDevs []string
var options []libpod.CtrCreateOption
@@ -224,19 +213,16 @@ func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
cdiDevs = append(cdiDevs, device.Path)
continue
}
-
devs = append(devs, device)
}
-
s.Devices = devs
if len(cdiDevs) > 0 {
options = append(options, libpod.WithCDI(cdiDevs))
}
-
return options
}
-func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -317,7 +303,10 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
for _, imageVolume := range s.ImageVolumes {
destinations = append(destinations, imageVolume.Destination)
}
- options = append(options, libpod.WithUserVolumes(destinations))
+
+ if len(destinations) > 0 || !infraVolumes {
+ options = append(options, libpod.WithUserVolumes(destinations))
+ }
if len(volumes) != 0 {
var vols []*libpod.ContainerNamedVolume
@@ -405,7 +394,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
} else {
- if pod != nil {
+ if pod != nil && len(compatibleOptions.InfraLabels) == 0 {
// duplicate the security options from the pod
processLabel, err := pod.ProcessLabel()
if err != nil {
@@ -498,3 +487,33 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
return options, nil
}
+
+func Inherit(infra libpod.Container) (opts []libpod.CtrCreateOption, infraS *spec.Spec, compat *libpod.InfraInherit, err error) {
+ options := []libpod.CtrCreateOption{}
+ compatibleOptions := &libpod.InfraInherit{}
+ infraConf := infra.Config()
+ infraSpec := infraConf.Spec
+
+ config, err := json.Marshal(infraConf)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ err = json.Unmarshal(config, compatibleOptions)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if infraSpec.Linux != nil && infraSpec.Linux.Resources != nil {
+ resources, err := json.Marshal(infraSpec.Linux.Resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ err = json.Unmarshal(resources, &compatibleOptions.InfraResources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ }
+ if compatibleOptions != nil {
+ options = append(options, libpod.WithInfraConfig(*compatibleOptions))
+ }
+ return options, infraSpec, compatibleOptions, nil
+}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index efac53104..ee3a990fc 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -2,6 +2,7 @@ package generate
import (
"context"
+ "encoding/json"
"path"
"strings"
@@ -174,7 +175,7 @@ func getCGroupPermissons(unmask []string) string {
}
// SpecGenToOCI returns the base configuration for the container.
-func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) {
+func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string, compatibleOptions *libpod.InfraInherit) (*spec.Spec, error) {
cgroupPerm := getCGroupPermissons(s.Unmask)
g, err := generate.New("linux")
@@ -299,9 +300,32 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddAnnotation(key, val)
}
- g.Config.Linux.Resources = s.ResourceLimits
+ if compatibleOptions.InfraResources == nil && s.ResourceLimits != nil {
+ g.Config.Linux.Resources = s.ResourceLimits
+ } else if s.ResourceLimits != nil { // if we have predefined resource limits we need to make sure we keep the infra and container limits
+ originalResources, err := json.Marshal(s.ResourceLimits)
+ if err != nil {
+ return nil, err
+ }
+ infraResources, err := json.Marshal(compatibleOptions.InfraResources)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(infraResources, s.ResourceLimits) // put infra's resource limits in the container
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(originalResources, s.ResourceLimits) // make sure we did not override anything
+ if err != nil {
+ return nil, err
+ }
+ g.Config.Linux.Resources = s.ResourceLimits
+ } else {
+ g.Config.Linux.Resources = compatibleOptions.InfraResources
+ }
// Devices
+ var userDevices []spec.LinuxDevice
if s.Privileged {
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
@@ -316,14 +340,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
return nil, err
}
}
+ if len(compatibleOptions.InfraDevices) > 0 && len(s.Devices) == 0 {
+ userDevices = compatibleOptions.InfraDevices
+ } else {
+ userDevices = s.Devices
+ }
// add default devices specified by caller
- for _, device := range s.Devices {
+ for _, device := range userDevices {
if err = DevicesFromPath(&g, device.Path); err != nil {
return nil, err
}
}
}
- s.HostDeviceList = s.Devices
+ s.HostDeviceList = userDevices
// set the devices cgroup when not running in a user namespace
if !inUserNS && !s.Privileged {
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index e59d11c0a..33e8422fd 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -196,6 +196,7 @@ type PodSpecGenerator struct {
PodCgroupConfig
PodResourceConfig
PodStorageConfig
+ PodSecurityConfig
InfraContainerSpec *SpecGenerator `json:"-"`
}
@@ -210,6 +211,10 @@ type PodResourceConfig struct {
ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"`
}
+type PodSecurityConfig struct {
+ SecurityOpt []string `json:"security_opt,omitempty"`
+}
+
// NewPodSpecGenerator creates a new pod spec
func NewPodSpecGenerator() *PodSpecGenerator {
return &PodSpecGenerator{}
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 6e1a62b99..bd744aa78 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -274,14 +274,32 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration {
}
if remote {
- uuid := stringid.GenerateNonCryptoID()
+ var pathPrefix string
if !rootless.IsRootless() {
- p.RemoteSocket = fmt.Sprintf("unix:/run/podman/podman-%s.sock", uuid)
+ pathPrefix = "/run/podman/podman"
} else {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
- socket := fmt.Sprintf("podman-%s.sock", uuid)
- fqpath := filepath.Join(runtimeDir, socket)
- p.RemoteSocket = fmt.Sprintf("unix:%s", fqpath)
+ pathPrefix = filepath.Join(runtimeDir, "podman")
+ }
+ // We want to avoid collisions in socket paths, but using the
+ // socket directly for a collision check doesn’t work; bind(2) on AF_UNIX
+ // creates the file, and we need to pass a unique path now before the bind(2)
+ // happens. So, use a podman-%s.sock-lock empty file as a marker.
+ tries := 0
+ for {
+ uuid := stringid.GenerateNonCryptoID()
+ lockPath := fmt.Sprintf("%s-%s.sock-lock", pathPrefix, uuid)
+ lockFile, err := os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0700)
+ if err == nil {
+ lockFile.Close()
+ p.RemoteSocketLock = lockPath
+ p.RemoteSocket = fmt.Sprintf("unix:%s-%s.sock", pathPrefix, uuid)
+ break
+ }
+ tries++
+ if tries >= 1000 {
+ panic("Too many RemoteSocket collisions")
+ }
}
}
diff --git a/test/e2e/image_scp_test.go b/test/e2e/image_scp_test.go
index 6651a04b5..767b355d9 100644
--- a/test/e2e/image_scp_test.go
+++ b/test/e2e/image_scp_test.go
@@ -29,7 +29,6 @@ var _ = Describe("podman image scp", func() {
panic(err)
}
os.Setenv("CONTAINERS_CONF", conf.Name())
-
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@@ -52,38 +51,6 @@ var _ = Describe("podman image scp", func() {
})
- It("podman image scp quiet flag", func() {
- if IsRemote() {
- Skip("this test is only for non-remote")
- }
- scp := podmanTest.Podman([]string{"image", "scp", "-q", ALPINE})
- scp.WaitWithDefaultTimeout()
- Expect(scp).To(Exit(0))
- })
-
- It("podman image scp root to rootless transfer", func() {
- SkipIfNotRootless("this is a rootless only test, transferring from root to rootless using PodmanAsUser")
- if IsRemote() {
- Skip("this test is only for non-remote")
- }
- env := os.Environ()
- img := podmanTest.PodmanAsUser([]string{"image", "pull", ALPINE}, 0, 0, "", env) // pull image to root
- img.WaitWithDefaultTimeout()
- Expect(img).To(Exit(0))
- scp := podmanTest.PodmanAsUser([]string{"image", "scp", "root@localhost::" + ALPINE, "1000:1000@localhost::"}, 0, 0, "", env) //transfer from root to rootless (us)
- scp.WaitWithDefaultTimeout()
- Expect(scp).To(Exit(0))
-
- list := podmanTest.Podman([]string{"image", "list"}) // our image should now contain alpine loaded in from root
- list.WaitWithDefaultTimeout()
- Expect(list).To(Exit(0))
- Expect(list.OutputToStringArray()).To(ContainElement(HavePrefix("quay.io/libpod/alpine")))
-
- scp = podmanTest.PodmanAsUser([]string{"image", "scp", "root@localhost::" + ALPINE}, 0, 0, "", env) //transfer from root to rootless (us)
- scp.WaitWithDefaultTimeout()
- Expect(scp).To(Exit(0))
- })
-
It("podman image scp bogus image", func() {
if IsRemote() {
Skip("this test is only for non-remote")
@@ -119,11 +86,8 @@ var _ = Describe("podman image scp", func() {
scp.Wait(45)
// exit with error because we cannot make an actual ssh connection
// This tests that the input we are given is validated and prepared correctly
- // Error: failed to connect: dial tcp: address foo: missing port in address
+ // The error given should either be a missing image (due to testing suite complications) or a i/o timeout on ssh
Expect(scp).To(ExitWithError())
- Expect(scp.ErrorToString()).To(ContainSubstring(
- "Error: failed to connect: dial tcp 66.151.147.142:2222: i/o timeout",
- ))
})
diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go
index d60383029..4644e3748 100644
--- a/test/e2e/libpod_suite_remote_test.go
+++ b/test/e2e/libpod_suite_remote_test.go
@@ -1,3 +1,4 @@
+//go:build remote
// +build remote
package integration
@@ -143,6 +144,11 @@ func (p *PodmanTestIntegration) StopRemoteService() {
if err := os.Remove(socket); err != nil {
fmt.Println(err)
}
+ if p.RemoteSocketLock != "" {
+ if err := os.Remove(p.RemoteSocketLock); err != nil {
+ fmt.Println(err)
+ }
+ }
}
//MakeOptions assembles all the podman main options
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 41a017a52..fab107af8 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -9,6 +9,8 @@ import (
"strconv"
"strings"
+ "github.com/containers/common/pkg/apparmor"
+ "github.com/containers/common/pkg/seccomp"
"github.com/containers/common/pkg/sysinfo"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
@@ -16,6 +18,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
+ "github.com/opencontainers/selinux/go-selinux"
)
var _ = Describe("Podman pod create", func() {
@@ -967,4 +970,63 @@ ENTRYPOINT ["sleep","99999"]
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).Should(Equal("host"))
})
+
+ It("podman pod create --security-opt", func() {
+ if !selinux.GetEnabled() {
+ Skip("SELinux not enabled")
+ }
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--security-opt", "label=type:spc_t", "--security-opt", "seccomp=unconfined"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate).Should(Exit(0))
+
+ ctrCreate := podmanTest.Podman([]string{"container", "create", "--pod", podCreate.OutputToString(), ALPINE})
+ ctrCreate.WaitWithDefaultTimeout()
+ Expect(ctrCreate).Should(Exit(0))
+
+ ctrInspect := podmanTest.InspectContainer(ctrCreate.OutputToString())
+ Expect(ctrInspect[0].HostConfig.SecurityOpt).To(Equal([]string{"label=type:spc_t", "seccomp=unconfined"}))
+
+ podCreate = podmanTest.Podman([]string{"pod", "create", "--security-opt", "label=disable"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate).Should(Exit(0))
+
+ ctrCreate = podmanTest.Podman([]string{"container", "run", "-it", "--pod", podCreate.OutputToString(), ALPINE, "cat", "/proc/self/attr/current"})
+ ctrCreate.WaitWithDefaultTimeout()
+ Expect(ctrCreate).Should(Exit(0))
+ match, _ := ctrCreate.GrepString("spc_t")
+ Expect(match).Should(BeTrue())
+ })
+
+ It("podman pod create --security-opt seccomp", func() {
+ if !seccomp.IsEnabled() {
+ Skip("seccomp is not enabled")
+ }
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--security-opt", "seccomp=unconfined"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate).Should(Exit(0))
+
+ ctrCreate := podmanTest.Podman([]string{"container", "create", "--pod", podCreate.OutputToString(), ALPINE})
+ ctrCreate.WaitWithDefaultTimeout()
+ Expect(ctrCreate).Should(Exit(0))
+
+ ctrInspect := podmanTest.InspectContainer(ctrCreate.OutputToString())
+ Expect(ctrInspect[0].HostConfig.SecurityOpt).To(Equal([]string{"seccomp=unconfined"}))
+ })
+
+ It("podman pod create --security-opt apparmor test", func() {
+ if !apparmor.IsEnabled() {
+ Skip("Apparmor is not enabled")
+ }
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--security-opt", fmt.Sprintf("apparmor=%s", apparmor.Profile)})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate).Should(Exit(0))
+
+ ctrCreate := podmanTest.Podman([]string{"container", "create", "--pod", podCreate.OutputToString(), ALPINE})
+ ctrCreate.WaitWithDefaultTimeout()
+ Expect(ctrCreate).Should(Exit(0))
+
+ inspect := podmanTest.InspectContainer(ctrCreate.OutputToString())
+ Expect(inspect[0].AppArmorProfile).To(Equal(apparmor.Profile))
+
+ })
})
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index a5508b2f4..541095764 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -78,6 +78,35 @@ verify_iid_and_name() {
run_podman rmi $fqin
}
+@test "podman image scp transfer" {
+ skip_if_root_ubuntu "cannot create a new user successfully on ubuntu"
+ get_iid_and_name
+ if ! is_remote; then
+ if is_rootless; then
+ whoami=$(id -un)
+ run_podman image scp $whoami@localhost::$iid root@localhost::
+ if [ "$status" -ne 0 ]; then
+ die "Command failed: podman image scp transfer"
+ fi
+ whoami=$(id -un)
+ run_podman image scp -q $whoami@localhost::$iid root@localhost::
+ if [ "$status" -ne 0 ]; then
+ die "Command failed: podman image scp quiet transfer failed"
+ fi
+ fi
+ if ! is_rootless; then
+ id -u 1000 &>/dev/null || useradd -u 1000 -g 1000 testingUsr
+ if [ "$status" -ne 0 ]; then
+ die "Command failed: useradd 1000"
+ fi
+ run_podman image scp root@localhost::$iid 1000:1000@localhost::
+ if [ "$status" -ne 0 ]; then
+ die "Command failed: podman image scp transfer"
+ fi
+ fi
+ fi
+}
+
@test "podman load - by image ID" {
# FIXME: how to build a simple archive instead?
diff --git a/test/system/520-checkpoint.bats b/test/system/520-checkpoint.bats
index 723a20cc4..046dfd126 100644
--- a/test/system/520-checkpoint.bats
+++ b/test/system/520-checkpoint.bats
@@ -11,7 +11,7 @@ function setup() {
# TL;DR they keep fixing it then breaking it again. There's a test we
# could run to see if it's fixed, but it's way too complicated. Since
# integration tests also skip checkpoint tests on Ubuntu, do the same here.
- if grep -qiw ubuntu /etc/os-release; then
+ if is_ubuntu; then
skip "FIXME: checkpointing broken in Ubuntu 2004, 2104, 2110, ..."
fi
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 8f6033309..dcf7cf0a7 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -318,6 +318,10 @@ function wait_for_port() {
# BEGIN miscellaneous tools
# Shortcuts for common needs:
+function is_ubuntu() {
+ grep -qiw ubuntu /etc/os-release
+}
+
function is_rootless() {
[ "$(id -u)" -ne 0 ]
}
@@ -459,6 +463,16 @@ function skip_if_journald_unavailable {
fi
}
+function skip_if_root_ubuntu {
+ if is_ubuntu; then
+ if ! is_remote; then
+ if ! is_rootless; then
+ skip "Cannot run this test on rootful ubuntu, usually due to user errors"
+ fi
+ fi
+ fi
+}
+
#########
# die # Abort with helpful message
#########
diff --git a/test/utils/utils.go b/test/utils/utils.go
index f41024072..1f5067950 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -41,6 +41,7 @@ type PodmanTest struct {
RemotePodmanBinary string
RemoteSession *os.Process
RemoteSocket string
+ RemoteSocketLock string // If not "", should be removed _after_ RemoteSocket is removed
RemoteCommand *exec.Cmd
ImageCacheDir string
ImageCacheFS string
@@ -469,10 +470,6 @@ func Containerized() bool {
return strings.Contains(string(b), "docker")
}
-func init() {
- rand.Seed(GinkgoRandomSeed())
-}
-
var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
// RandomString returns a string of given length composed of random characters
diff --git a/utils/utils.go b/utils/utils.go
index 45cec2c5f..4c04b939d 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -229,3 +229,12 @@ func MovePauseProcessToScope(pausePidPath string) {
}
}
}
+
+// CreateSCPCommand takes an existing command, appends the given arguments and returns a configured podman command for image scp
+func CreateSCPCommand(cmd *exec.Cmd, command []string) *exec.Cmd {
+ cmd.Args = append(cmd.Args, command...)
+ cmd.Env = os.Environ()
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ return cmd
+}