mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
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>
This commit is contained in:
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
|
70
test/e2e/run_seccomp.go
Normal file
70
test/e2e/run_seccomp.go
Normal file
@ -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))
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user