mirror of
https://github.com/containers/podman.git
synced 2025-06-20 09:03:43 +08:00
Merge pull request #4806 from vrothberg/seccomp
policy for seccomp-profile selection
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
|
||||
@ -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,
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,6 +107,48 @@ 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
|
||||
@ -114,6 +157,8 @@ type SecurityConfig struct {
|
||||
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
|
||||
|
@ -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