summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-01-15 01:16:07 +0100
committerGitHub <noreply@github.com>2020-01-15 01:16:07 +0100
commit0aa9dba3e1009dbbdf59d47d9370db0de4679730 (patch)
treea2fad9f02f0ae24e958995615e4e5963b3f7f92a
parentad5137bc7b346ef2e28eb85c872728b6748bc629 (diff)
parentf3f4c54f2abc341cee1e7b83e9538d91a3c627e3 (diff)
downloadpodman-0aa9dba3e1009dbbdf59d47d9370db0de4679730.tar.gz
podman-0aa9dba3e1009dbbdf59d47d9370db0de4679730.tar.bz2
podman-0aa9dba3e1009dbbdf59d47d9370db0de4679730.zip
Merge pull request #4806 from vrothberg/seccomp
policy for seccomp-profile selection
-rw-r--r--cmd/podman/common.go4
-rw-r--r--cmd/podman/shared/create.go30
-rw-r--r--cmd/podman/shared/intermediate.go1
-rw-r--r--docs/source/markdown/podman-create.1.md6
-rw-r--r--docs/source/markdown/podman-run.1.md6
-rw-r--r--pkg/spec/config_linux_cgo.go12
-rw-r--r--pkg/spec/createconfig.go67
-rw-r--r--test/e2e/config.go9
-rw-r--r--test/e2e/run_seccomp.go70
9 files changed, 187 insertions, 18 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index dc7590590..9064ec219 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -538,6 +538,10 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
"workdir", "w", "",
"Working directory inside the container",
)
+ createFlags.String(
+ "seccomp-policy", "default",
+ "Policy for selecting a seccomp profile (experimental)",
+ )
}
func getFormat(c *cliconfig.PodmanCommand) (string, error) {
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index 05a3f5598..50a64b01c 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -31,6 +31,10 @@ import (
"github.com/sirupsen/logrus"
)
+// seccompAnnotationKey is the key of the image annotation embedding a seccomp
+// profile.
+const seccompAnnotationKey = "io.containers.seccomp.profile"
+
func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
var (
healthCheck *manifest.Schema2HealthConfig
@@ -67,7 +71,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
imageName := ""
- var data *inspect.ImageData = nil
+ var imageData *inspect.ImageData = nil
// Set the storage if there is no rootfs specified
if rootfs == "" {
@@ -99,17 +103,17 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
if err != nil {
return nil, nil, err
}
- data, err = newImage.Inspect(ctx)
+ imageData, err = newImage.Inspect(ctx)
if err != nil {
return nil, nil, err
}
- if overrideOS == "" && data.Os != goruntime.GOOS {
- return nil, nil, errors.Errorf("incompatible image OS %q on %q host", data.Os, goruntime.GOOS)
+ if overrideOS == "" && imageData.Os != goruntime.GOOS {
+ return nil, nil, errors.Errorf("incompatible image OS %q on %q host", imageData.Os, goruntime.GOOS)
}
- if overrideArch == "" && data.Architecture != goruntime.GOARCH {
- return nil, nil, errors.Errorf("incompatible image architecture %q on %q host", data.Architecture, goruntime.GOARCH)
+ if overrideArch == "" && imageData.Architecture != goruntime.GOARCH {
+ return nil, nil, errors.Errorf("incompatible image architecture %q on %q host", imageData.Architecture, goruntime.GOARCH)
}
names := newImage.Names()
@@ -171,7 +175,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
}
- createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, data)
+ createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, imageData)
if err != nil {
return nil, nil, err
}
@@ -712,6 +716,18 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
return nil, err
}
+ // SECCOMP
+ if data != nil {
+ if value, exists := data.Annotations[seccompAnnotationKey]; exists {
+ secConfig.SeccompProfileFromImage = value
+ }
+ }
+ if policy, err := cc.LookupSeccompPolicy(c.String("seccomp-policy")); err != nil {
+ return nil, err
+ } else {
+ secConfig.SeccompPolicy = policy
+ }
+
config := &cc.CreateConfig{
Annotations: annotations,
BuiltinImgVolumes: ImageVolumes,
diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go
index e985e4dc0..d1f0e602e 100644
--- a/cmd/podman/shared/intermediate.go
+++ b/cmd/podman/shared/intermediate.go
@@ -463,6 +463,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes
m["volume"] = newCRStringArray(c, "volume")
m["volumes-from"] = newCRStringSlice(c, "volumes-from")
m["workdir"] = newCRString(c, "workdir")
+ m["seccomp-policy"] = newCRString(c, "seccomp-policy")
// global flag
if !remote {
m["authfile"] = newCRString(c, "authfile")
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index abde899bf..81fae0b25 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -676,6 +676,12 @@ If specified, the first argument refers to an exploded container on the file sys
This is useful to run a container without requiring any image management, the rootfs
of the container is assumed to be managed externally.
+**--seccomp-policy**=*policy*
+
+Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" annotation in the container image and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below.
+
+Note that this feature is experimental and may change in the future.
+
**--security-opt**=*option*
Security Options
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 9484ad347..af5bb814d 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -697,6 +697,12 @@ of the container is assumed to be managed externally.
Note: On `SELinux` systems, the rootfs needs the correct label, which is by default
`unconfined_u:object_r:container_file_t`.
+**--seccomp-policy**=*policy*
+
+Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" annotation in the container image and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below.
+
+Note that this feature is experimental and may change in the future.
+
**--security-opt**=*option*
Security Options
diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go
index c47156456..ae83c9d52 100644
--- a/pkg/spec/config_linux_cgo.go
+++ b/pkg/spec/config_linux_cgo.go
@@ -8,13 +8,24 @@ import (
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
seccomp "github.com/seccomp/containers-golang"
+ "github.com/sirupsen/logrus"
)
func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
var seccompConfig *spec.LinuxSeccomp
var err error
+ if config.SeccompPolicy == SeccompPolicyImage && config.SeccompProfileFromImage != "" {
+ logrus.Debug("Loading seccomp profile from the security config")
+ seccompConfig, err = seccomp.LoadProfile(config.SeccompProfileFromImage, configSpec)
+ if err != nil {
+ return nil, errors.Wrap(err, "loading seccomp profile failed")
+ }
+ return seccompConfig, nil
+ }
+
if config.SeccompProfilePath != "" {
+ logrus.Debugf("Loading seccomp profile from %q", config.SeccompProfilePath)
seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
if err != nil {
return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
@@ -24,6 +35,7 @@ func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.Linu
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
}
} else {
+ logrus.Debug("Loading default seccomp profile")
seccompConfig, err = seccomp.GetDefaultProfile(configSpec)
if err != nil {
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 6d058229b..fb222083b 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -2,6 +2,7 @@ package createconfig
import (
"os"
+ "sort"
"strconv"
"strings"
"syscall"
@@ -106,19 +107,63 @@ type NetworkConfig struct {
PublishAll bool //publish-all
}
+// SeccompPolicy determines which seccomp profile gets applied to the container.
+type SeccompPolicy int
+
+const (
+ // SeccompPolicyDefault - if set use SecurityConfig.SeccompProfilePath,
+ // otherwise use the default profile. The SeccompProfilePath might be
+ // explicitly set by the user.
+ SeccompPolicyDefault SeccompPolicy = iota
+ // SeccompPolicyImage - if set use SecurityConfig.SeccompProfileFromImage,
+ // otherwise follow SeccompPolicyDefault.
+ SeccompPolicyImage
+)
+
+// Map for easy lookups of supported policies.
+var supportedSeccompPolicies = map[string]SeccompPolicy{
+ "": SeccompPolicyDefault,
+ "default": SeccompPolicyDefault,
+ "image": SeccompPolicyImage,
+}
+
+// LookupSeccompPolicy looksup the corresponding SeccompPolicy for the specified
+// string. If none is found, an errors is returned including the list of
+// supported policies.
+// Note that an empty string resolved to SeccompPolicyDefault.
+func LookupSeccompPolicy(s string) (SeccompPolicy, error) {
+ policy, exists := supportedSeccompPolicies[s]
+ if exists {
+ return policy, nil
+ }
+
+ // Sort the keys first as maps are non-deterministic.
+ keys := []string{}
+ for k := range supportedSeccompPolicies {
+ if k != "" {
+ keys = append(keys, k)
+ }
+ }
+ sort.Strings(keys)
+
+ return -1, errors.Errorf("invalid seccomp policy %q: valid policies are %+q", s, keys)
+}
+
// SecurityConfig configures the security features for the container
type SecurityConfig struct {
- CapAdd []string // cap-add
- CapDrop []string // cap-drop
- LabelOpts []string //SecurityOpts
- NoNewPrivs bool //SecurityOpts
- ApparmorProfile string //SecurityOpts
- SeccompProfilePath string //SecurityOpts
- SecurityOpts []string
- Privileged bool //privileged
- ReadOnlyRootfs bool //read-only
- ReadOnlyTmpfs bool //read-only-tmpfs
- Sysctl map[string]string //sysctl
+ CapAdd []string // cap-add
+ CapDrop []string // cap-drop
+ LabelOpts []string //SecurityOpts
+ NoNewPrivs bool //SecurityOpts
+ ApparmorProfile string //SecurityOpts
+ SeccompProfilePath string //SecurityOpts
+ SeccompProfileFromImage string // seccomp profile from the container image
+ SeccompPolicy SeccompPolicy
+ SecurityOpts []string
+ Privileged bool //privileged
+ ReadOnlyRootfs bool //read-only
+ ReadOnlyTmpfs bool //read-only-tmpfs
+ Sysctl map[string]string //sysctl
}
// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI
diff --git a/test/e2e/config.go b/test/e2e/config.go
index aeb7affee..12d0e545e 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -14,4 +14,13 @@ var (
BB = "docker.io/library/busybox:latest"
healthcheck = "docker.io/libpod/alpine_healthcheck:latest"
ImageCacheDir = "/tmp/podman/imagecachedir"
+
+ // This image has seccomp profiles that blocks all syscalls.
+ // The intention behind blocking all syscalls is to prevent
+ // regressions in the future. The required syscalls can vary
+ // depending on which runtime we're using.
+ alpineSeccomp = "docker.io/libpod/alpine-with-seccomp:latest"
+ // This image has a bogus/invalid seccomp profile which should
+ // yield a json error when being read.
+ alpineBogusSeccomp = "docker.io/libpod/alpine-with-bogus-seccomp:latest"
)
diff --git a/test/e2e/run_seccomp.go b/test/e2e/run_seccomp.go
new file mode 100644
index 000000000..dcf938ad6
--- /dev/null
+++ b/test/e2e/run_seccomp.go
@@ -0,0 +1,70 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman run", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman run --seccomp-policy default", func() {
+ session := podmanTest.Podman([]string{"run", "--seccomp-policy", "default", alpineSeccomp, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman run --seccomp-policy ''", func() {
+ // Empty string is interpreted as "default".
+ session := podmanTest.Podman([]string{"run", "--seccomp-policy", "", alpineSeccomp, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman run --seccomp-policy invalid", func() {
+ session := podmanTest.Podman([]string{"run", "--seccomp-policy", "invalid", alpineSeccomp, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("podman run --seccomp-policy image (block all syscalls)", func() {
+ session := podmanTest.Podman([]string{"run", "--seccomp-policy", "image", alpineSeccomp, "ls"})
+ session.WaitWithDefaultTimeout()
+ // TODO: we're getting a "cannot start a container that has
+ // stopped" error which seems surprising. Investigate
+ // why that is so.
+ Expect(session.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("podman run --seccomp-policy image (bogus profile)", func() {
+ session := podmanTest.Podman([]string{"run", "--seccomp-policy", "image", alpineBogusSeccomp, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
+})