Skip to content

Commit

Permalink
Merge pull request #4806 from vrothberg/seccomp
Browse files Browse the repository at this point in the history
policy for seccomp-profile selection
  • Loading branch information
openshift-merge-robot committed Jan 15, 2020
2 parents ad5137b + f3f4c54 commit 0aa9dba
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 18 deletions.
4 changes: 4 additions & 0 deletions cmd/podman/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
30 changes: 23 additions & 7 deletions cmd/podman/shared/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions cmd/podman/shared/intermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-run.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions pkg/spec/config_linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
67 changes: 56 additions & 11 deletions pkg/spec/createconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package createconfig

import (
"os"
"sort"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 70 additions & 0 deletions test/e2e/run_seccomp.go
Original file line number Diff line number Diff line change
@@ -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))
})
})

0 comments on commit 0aa9dba

Please sign in to comment.