mirror of
https://github.com/containers/podman.git
synced 2025-06-21 01:19:15 +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", "",
|
"workdir", "w", "",
|
||||||
"Working directory inside the container",
|
"Working directory inside the container",
|
||||||
)
|
)
|
||||||
|
createFlags.String(
|
||||||
|
"seccomp-policy", "default",
|
||||||
|
"Policy for selecting a seccomp profile (experimental)",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFormat(c *cliconfig.PodmanCommand) (string, error) {
|
func getFormat(c *cliconfig.PodmanCommand) (string, error) {
|
||||||
|
@ -31,6 +31,10 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"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) {
|
func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
|
||||||
var (
|
var (
|
||||||
healthCheck *manifest.Schema2HealthConfig
|
healthCheck *manifest.Schema2HealthConfig
|
||||||
@ -67,7 +71,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageName := ""
|
imageName := ""
|
||||||
var data *inspect.ImageData = nil
|
var imageData *inspect.ImageData = nil
|
||||||
|
|
||||||
// Set the storage if there is no rootfs specified
|
// Set the storage if there is no rootfs specified
|
||||||
if rootfs == "" {
|
if rootfs == "" {
|
||||||
@ -99,17 +103,17 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
data, err = newImage.Inspect(ctx)
|
imageData, err = newImage.Inspect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if overrideOS == "" && data.Os != goruntime.GOOS {
|
if overrideOS == "" && imageData.Os != goruntime.GOOS {
|
||||||
return nil, nil, errors.Errorf("incompatible image OS %q on %q host", data.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 {
|
if overrideArch == "" && imageData.Architecture != goruntime.GOARCH {
|
||||||
return nil, nil, errors.Errorf("incompatible image architecture %q on %q host", data.Architecture, goruntime.GOARCH)
|
return nil, nil, errors.Errorf("incompatible image architecture %q on %q host", imageData.Architecture, goruntime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
names := newImage.Names()
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -712,6 +716,18 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
|
|||||||
return nil, err
|
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{
|
config := &cc.CreateConfig{
|
||||||
Annotations: annotations,
|
Annotations: annotations,
|
||||||
BuiltinImgVolumes: ImageVolumes,
|
BuiltinImgVolumes: ImageVolumes,
|
||||||
|
@ -463,6 +463,7 @@ func NewIntermediateLayer(c *cliconfig.PodmanCommand, remote bool) GenericCLIRes
|
|||||||
m["volume"] = newCRStringArray(c, "volume")
|
m["volume"] = newCRStringArray(c, "volume")
|
||||||
m["volumes-from"] = newCRStringSlice(c, "volumes-from")
|
m["volumes-from"] = newCRStringSlice(c, "volumes-from")
|
||||||
m["workdir"] = newCRString(c, "workdir")
|
m["workdir"] = newCRString(c, "workdir")
|
||||||
|
m["seccomp-policy"] = newCRString(c, "seccomp-policy")
|
||||||
// global flag
|
// global flag
|
||||||
if !remote {
|
if !remote {
|
||||||
m["authfile"] = newCRString(c, "authfile")
|
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
|
This is useful to run a container without requiring any image management, the rootfs
|
||||||
of the container is assumed to be managed externally.
|
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-opt**=*option*
|
||||||
|
|
||||||
Security Options
|
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
|
Note: On `SELinux` systems, the rootfs needs the correct label, which is by default
|
||||||
`unconfined_u:object_r:container_file_t`.
|
`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-opt**=*option*
|
||||||
|
|
||||||
Security Options
|
Security Options
|
||||||
|
@ -8,13 +8,24 @@ import (
|
|||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
seccomp "github.com/seccomp/containers-golang"
|
seccomp "github.com/seccomp/containers-golang"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
|
func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
|
||||||
var seccompConfig *spec.LinuxSeccomp
|
var seccompConfig *spec.LinuxSeccomp
|
||||||
var err error
|
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 != "" {
|
if config.SeccompProfilePath != "" {
|
||||||
|
logrus.Debugf("Loading seccomp profile from %q", config.SeccompProfilePath)
|
||||||
seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
|
seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
|
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)
|
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
logrus.Debug("Loading default seccomp profile")
|
||||||
seccompConfig, err = seccomp.GetDefaultProfile(configSpec)
|
seccompConfig, err = seccomp.GetDefaultProfile(configSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
|
return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
|
||||||
|
@ -2,6 +2,7 @@ package createconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -106,19 +107,63 @@ type NetworkConfig struct {
|
|||||||
PublishAll bool //publish-all
|
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
|
// SecurityConfig configures the security features for the container
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
CapAdd []string // cap-add
|
CapAdd []string // cap-add
|
||||||
CapDrop []string // cap-drop
|
CapDrop []string // cap-drop
|
||||||
LabelOpts []string //SecurityOpts
|
LabelOpts []string //SecurityOpts
|
||||||
NoNewPrivs bool //SecurityOpts
|
NoNewPrivs bool //SecurityOpts
|
||||||
ApparmorProfile string //SecurityOpts
|
ApparmorProfile string //SecurityOpts
|
||||||
SeccompProfilePath string //SecurityOpts
|
SeccompProfilePath string //SecurityOpts
|
||||||
SecurityOpts []string
|
SeccompProfileFromImage string // seccomp profile from the container image
|
||||||
Privileged bool //privileged
|
SeccompPolicy SeccompPolicy
|
||||||
ReadOnlyRootfs bool //read-only
|
SecurityOpts []string
|
||||||
ReadOnlyTmpfs bool //read-only-tmpfs
|
Privileged bool //privileged
|
||||||
Sysctl map[string]string //sysctl
|
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
|
// 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"
|
BB = "docker.io/library/busybox:latest"
|
||||||
healthcheck = "docker.io/libpod/alpine_healthcheck:latest"
|
healthcheck = "docker.io/libpod/alpine_healthcheck:latest"
|
||||||
ImageCacheDir = "/tmp/podman/imagecachedir"
|
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