From 0e2fe6de689274a591c7ebaf8941893c7e77cf7e Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Tue, 7 Jan 2020 16:48:07 +0100 Subject: [PATCH] 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.podman.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 --- cmd/podman/common.go | 4 ++ cmd/podman/shared/create.go | 16 +++++++ cmd/podman/shared/intermediate.go | 1 + docs/source/markdown/podman-create.1.md | 6 +++ docs/source/markdown/podman-run.1.md | 6 +++ pkg/spec/config_linux_cgo.go | 12 +++++ pkg/spec/createconfig.go | 64 ++++++++++++++++++++----- 7 files changed, 98 insertions(+), 11 deletions(-) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 69365201ec3a..5c117f6b36cc 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 f0fb28eb63d4..abd9e4b001d8 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.podman.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 bc12bd2a56ea..55826625bdcb 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 85aa8155323f..814805e19f4d 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 e8744de35547..ceb97dbb7c14 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 c471564563af..bd3c4757cf17 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 deafult 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 244a8d1cdd95..bc30f547b764 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,60 @@ type NetworkConfig struct { PublishAll bool //publish-all } +// SeccompPolicy determines which seccomp profile gets applied to the container. +type SeccompPolicy int + +const ( + // If set use SecurityConfig.SeccompProfilePath, otherwise use the default + // profile. The SeccompProfilePath might be explicitly set by the user. + SeccompPolicyDefault SeccompPolicy = iota + // 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 { + 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