Merge pull request #4806 from vrothberg/seccomp

policy for seccomp-profile selection
This commit is contained in:
OpenShift Merge Robot
2020-01-15 01:16:07 +01:00
committed by GitHub
9 changed files with 187 additions and 18 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
View 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))
})
})