summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Rothberg <rothberg@redhat.com>2020-01-07 16:48:07 +0100
committerValentin Rothberg <rothberg@redhat.com>2020-01-09 17:57:58 +0100
commitf3f4c54f2abc341cee1e7b83e9538d91a3c627e3 (patch)
tree9c7174550c1071cd1b5ba98484f32e6169cee1ed
parent7ccf412f7068acac16aa5aecf059df19c239f7cc (diff)
downloadpodman-f3f4c54f2abc341cee1e7b83e9538d91a3c627e3.tar.gz
podman-f3f4c54f2abc341cee1e7b83e9538d91a3c627e3.tar.bz2
podman-f3f4c54f2abc341cee1e7b83e9538d91a3c627e3.zip
policy for seccomp-profile selection
Implement a policy for selecting a seccomp profile. In addition to the default behaviour (default profile unless --security-opt seccomp is set) add a second policy doing a lookup in the image annotation. If the image has the "io.containers.seccomp.profile" set its value will be interpreted as a seccomp profile. The policy can be selected via the new --seccomp-policy CLI flag. Once the containers.conf support is merged into libpod, we can add an option there as well. Note that this feature is marked as experimental and may change in the future. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r--cmd/podman/common.go4
-rw-r--r--cmd/podman/shared/create.go16
-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, 180 insertions, 11 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 69365201e..5c117f6b3 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 f0fb28eb6..2aca53ac9 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
@@ -711,6 +715,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 bc12bd2a5..55826625b 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 85aa81553..814805e19 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -663,6 +663,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 e8744de35..ceb97dbb7 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -685,6 +685,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 244a8d1cd..827caacd6 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))
+ })
+})