aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Rothberg <vrothberg@suse.com>2018-10-12 13:18:36 +0200
committerValentin Rothberg <vrothberg@suse.com>2018-10-24 14:02:43 +0200
commit606a5cec8fa177fe64cff4ccf7cac05900fbe86c (patch)
treeeaed8639a17fb03e9efb7b0c1d9d2d3be3ecd89b
parentcde41c0b84e55a4325937f9c577afa6ccc129b9a (diff)
downloadpodman-606a5cec8fa177fe64cff4ccf7cac05900fbe86c.tar.gz
podman-606a5cec8fa177fe64cff4ccf7cac05900fbe86c.tar.bz2
podman-606a5cec8fa177fe64cff4ccf7cac05900fbe86c.zip
runlabel: run any command
As discussed [1], the runlabel command should execute any command specified in a label. The reasoning behind is that we cannot restrict which options are passed to Podman which thereby has full access to the host (runlabels must be used with care). With the updated semantics, runlabel will substitute the commands with a basepath equal to "docker" or "podman" with "/proc/self/exe", and otherwise leave the command unchanged to execute any other command on the host. [1] https://github.com/containers/libpod/pull/1607#issuecomment-428321382 Signed-off-by: Valentin Rothberg <vrothberg@suse.com>
-rw-r--r--cmd/podman/runlabel.go5
-rw-r--r--cmd/podman/shared/funcs.go41
-rw-r--r--cmd/podman/shared/funcs_test.go87
-rw-r--r--test/e2e/runlabel_test.go68
4 files changed, 190 insertions, 11 deletions
diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go
index aa7411a5f..e1dee1fb2 100644
--- a/cmd/podman/runlabel.go
+++ b/cmd/podman/runlabel.go
@@ -196,7 +196,10 @@ func runlabelCmd(c *cli.Context) error {
runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " "))
}
- cmd := shared.GenerateCommand(runLabel, imageName, c.String("name"))
+ cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name"))
+ if err != nil {
+ return errors.Wrapf(err, "unable to generate command")
+ }
env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts)
env = append(env, "PODMAN_RUNLABEL_NESTED=1")
diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go
index 485944f29..a92e0d547 100644
--- a/cmd/podman/shared/funcs.go
+++ b/cmd/podman/shared/funcs.go
@@ -3,11 +3,39 @@ package shared
import (
"fmt"
"os"
+ "path/filepath"
"strings"
)
+func substituteCommand(cmd string) (string, error) {
+ // If cmd is an absolute or relative path, check if the file exists.
+ // Throw an error if it doesn't exist.
+ if strings.Contains(cmd, "/") || strings.HasPrefix(cmd, ".") {
+ res, err := filepath.Abs(cmd)
+ if err != nil {
+ return "", err
+ }
+ if _, err := os.Stat(res); !os.IsNotExist(err) {
+ return res, nil
+ } else if err != nil {
+ return "", err
+ }
+ }
+
+ // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being
+ // used. Otherwise, leave the command unchanged.
+ switch cmd {
+ case "podman":
+ fallthrough
+ case "docker":
+ return "/proc/self/exe", nil
+ default:
+ return cmd, nil
+ }
+}
+
// GenerateCommand takes a label (string) and converts it to an executable command
-func GenerateCommand(command, imageName, name string) []string {
+func GenerateCommand(command, imageName, name string) ([]string, error) {
var (
newCommand []string
)
@@ -15,8 +43,13 @@ func GenerateCommand(command, imageName, name string) []string {
name = imageName
}
cmd := strings.Split(command, " ")
- // Replace the first element of cmd with "/proc/self/exe"
- newCommand = append(newCommand, "/proc/self/exe")
+
+ prog, err := substituteCommand(cmd[0])
+ if err != nil {
+ return nil, err
+ }
+ newCommand = append(newCommand, prog)
+
for _, arg := range cmd[1:] {
var newArg string
switch arg {
@@ -37,7 +70,7 @@ func GenerateCommand(command, imageName, name string) []string {
}
newCommand = append(newCommand, newArg)
}
- return newCommand
+ return newCommand, nil
}
// GenerateRunEnvironment merges the current environment variables with optional
diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go
index 612be480b..596df84e8 100644
--- a/cmd/podman/shared/funcs_test.go
+++ b/cmd/podman/shared/funcs_test.go
@@ -1,6 +1,10 @@
package shared
import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"strings"
"testing"
@@ -16,35 +20,106 @@ var (
func TestGenerateCommand(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
+func TestGenerateCommandCheckSubstitution(t *testing.T) {
+ type subsTest struct {
+ input string
+ expected string
+ shouldFail bool
+ }
+
+ absTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestAbsolutePath")
+ assert.Nil(t, err, "error creating tempfile")
+ defer os.Remove(absTmpFile.Name())
+
+ relTmpFile, err := ioutil.TempFile("./", "podmanRunlabelTestRelativePath")
+ assert.Nil(t, err, "error creating tempfile")
+ defer os.Remove(relTmpFile.Name())
+ relTmpCmd, err := filepath.Abs(relTmpFile.Name())
+ assert.Nil(t, err, "error getting absolute path for relative tmpfile")
+
+ // this has a (low) potential of race conditions but no other way
+ removedTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestRemove")
+ assert.Nil(t, err, "error creating tempfile")
+ os.Remove(removedTmpFile.Name())
+
+ absTmpCmd := fmt.Sprintf("%s --flag1 --flag2 --args=foo", absTmpFile.Name())
+ tests := []subsTest{
+ {
+ input: "docker run -it alpine:latest",
+ expected: "/proc/self/exe run -it alpine:latest",
+ shouldFail: false,
+ },
+ {
+ input: "podman run -it alpine:latest",
+ expected: "/proc/self/exe run -it alpine:latest",
+ shouldFail: false,
+ },
+ {
+ input: absTmpCmd,
+ expected: absTmpCmd,
+ shouldFail: false,
+ },
+ {
+ input: "./" + relTmpFile.Name(),
+ expected: relTmpCmd,
+ shouldFail: false,
+ },
+ {
+ input: "ls -la",
+ expected: "ls -la",
+ shouldFail: false,
+ },
+ {
+ input: removedTmpFile.Name(),
+ expected: "",
+ shouldFail: true,
+ },
+ }
+
+ for _, test := range tests {
+ newCommand, err := GenerateCommand(test.input, "foo", "bar")
+ if test.shouldFail {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ }
+ assert.Equal(t, test.expected, strings.Join(newCommand, " "))
+ }
+}
+
func TestGenerateCommandPath(t *testing.T) {
- inputCommand := "/usr/bin/docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
+ inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, _ := GenerateCommand(inputCommand, "foo", "bar")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoSetName(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install"
- newCommand := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoName(t *testing.T) {
inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install"
- newCommand := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandAlreadyPodman(t *testing.T) {
inputCommand := "podman run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go
new file mode 100644
index 000000000..8d10d3c24
--- /dev/null
+++ b/test/e2e/runlabel_test.go
@@ -0,0 +1,68 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var PodmanDockerfile = `
+FROM alpine:latest
+LABEL RUN podman --version`
+
+var LsDockerfile = `
+FROM alpine:latest
+LABEL RUN ls -la`
+
+var _ = Describe("podman container runlabel", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+
+ })
+
+ It("podman container runlabel (podman --version)", func() {
+ image := "podman-runlabel-test:podman"
+ podmanTest.BuildImage(PodmanDockerfile, image, "false")
+
+ result := podmanTest.Podman([]string{"container", "runlabel", "RUN", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"rmi", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
+
+ It("podman container runlabel (ls -la)", func() {
+ image := "podman-runlabel-test:ls"
+ podmanTest.BuildImage(LsDockerfile, image, "false")
+
+ result := podmanTest.Podman([]string{"container", "runlabel", "RUN", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"rmi", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
+})