diff options
-rw-r--r-- | cmd/podman/common.go | 4 | ||||
-rw-r--r-- | cmd/podman/shared/create.go | 30 | ||||
-rw-r--r-- | cmd/podman/shared/intermediate.go | 1 | ||||
-rw-r--r-- | docs/source/markdown/podman-create.1.md | 6 | ||||
-rw-r--r-- | docs/source/markdown/podman-run.1.md | 6 | ||||
-rw-r--r-- | pkg/api/handlers/generic/swagger.go | 1 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 44 | ||||
-rw-r--r-- | pkg/api/server/register_pods.go | 27 | ||||
-rw-r--r-- | pkg/spec/config_linux_cgo.go | 12 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 67 | ||||
-rw-r--r-- | test/e2e/config.go | 9 | ||||
-rw-r--r-- | test/e2e/run_seccomp.go | 70 |
12 files changed, 225 insertions, 52 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index dc7590590..9064ec219 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -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) { diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 05a3f5598..50a64b01c 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -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, diff --git a/cmd/podman/shared/intermediate.go b/cmd/podman/shared/intermediate.go index e985e4dc0..d1f0e602e 100644 --- a/cmd/podman/shared/intermediate.go +++ b/cmd/podman/shared/intermediate.go @@ -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") diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index abde899bf..81fae0b25 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -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 diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 9484ad347..af5bb814d 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -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 diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go index 8df961b94..27e1fc18d 100644 --- a/pkg/api/handlers/generic/swagger.go +++ b/pkg/api/handlers/generic/swagger.go @@ -16,7 +16,6 @@ type swagCtrWaitResponse struct { Body struct { // container exit code StatusCode int - error message Error struct { Message string } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 7298f3c61..916534fc7 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -10,7 +10,7 @@ import ( ) func (s *APIServer) registerImagesHandlers(r *mux.Router) error { - // swagger:operation POST /images/create images createImage + // swagger:operation POST /images/create compat_images createImage // // --- // summary: Create an image from an image @@ -40,7 +40,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // schema: // $ref: '#/responses/GenericError' r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}") - // swagger:operation POST /images/create images createImage + // swagger:operation POST /images/create compat_images createImage // --- // summary: Create an image from Source // description: Create an image by either pulling it from a registry or importing it. @@ -69,7 +69,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // schema: // $ref: '#/responses/GenericError' r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}") - // swagger:operation GET /images/json images listImages + // swagger:operation GET /images/json compat_images listImages // --- // summary: List Images // description: Returns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image. @@ -85,7 +85,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet) - // swagger:operation POST /images/load images loadImage + // swagger:operation POST /images/load compat_images loadImage // // --- // summary: Import image @@ -107,7 +107,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost) - // swagger:operation POST /images/prune images pruneImages + // swagger:operation POST /images/prune compat_images pruneImages // --- // summary: Prune unused images // description: Remove images from local storage that are not being used by a container @@ -132,7 +132,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost) - // swagger:operation GET /images/search images searchImages + // swagger:operation GET /images/search compat_images searchImages // --- // summary: Search images // description: Search registries for an image @@ -161,7 +161,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) - // swagger:operation DELETE /images/{nameOrID} images removeImage + // swagger:operation DELETE /images/{nameOrID} compat_images removeImage // --- // summary: Remove Image // description: Delete an image from local storage @@ -173,20 +173,20 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - in: query // name: noprune // type: bool - // description: not supported + // description: not supported. will be logged as an invalid parameter if enabled // produces: // - application/json // responses: // '200': // $ref: "#/responses/DocsImageDeleteResponse" - // '404': + // '400': // $ref: '#/responses/BadParamError' // '409': // $ref: '#/responses/ConflictError' // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) - // swagger:operation GET /images/{nameOrID}/get images exportImage + // swagger:operation GET /images/{nameOrID}/get compat_images exportImage // --- // summary: Export an image // description: Export an image in tarball format @@ -200,10 +200,13 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // responses: // '200': // description: no error + // schema: + // type: string + // format: binary // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/{name:..*}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet) - // swagger:operation GET /images/{nameOrID}/history images imageHistory + // swagger:operation GET /images/{nameOrID}/history compat_images imageHistory // --- // summary: History of an image // description: Return parent layers of an image. @@ -222,7 +225,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/images/{name:..*}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) - // swagger:operation GET /images/{nameOrID}/json images inspectImage + // swagger:operation GET /images/{nameOrID}/json compat_images inspectImage // --- // summary: Inspect an image // description: Return low-level information about an image. @@ -241,7 +244,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage)) - // swagger:operation POST /images/{nameOrID}/tag images tagImage + // swagger:operation POST /images/{nameOrID}/tag compat_images tagImage // --- // summary: Tag an image // description: Tag an image so that it becomes part of a repository. @@ -272,7 +275,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) - // swagger:operation POST /commit/ commit commitContainer + // swagger:operation POST /commit/ compat_commit commitContainer // --- // summary: Create a new image from a container // parameters: @@ -377,7 +380,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // swagger:operation POST /libpod/images/load images libpodLoadImage // --- // summary: Import image - // description: tbd + // description: Load a set of images and tags into a repository. // parameters: // - in: query // name: quiet @@ -463,10 +466,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: force // type: bool // description: remove the image even if used by containers or has other tags - // - in: query - // name: noprune - // type: bool - // description: not supported // produces: // - application/json // responses: @@ -474,6 +473,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // schema: // items: // $ref: "#/responses/DocsIageDeleteResponse" + // '400': + // $ref: "#/responses/BadParamError" // '404': // $ref: '#/responses/NoSuchImage' // '409': @@ -503,6 +504,9 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // responses: // '200': // description: no error + // schema: + // type: string + // format: binary // '404': // $ref: '#/responses/NoSuchImage' // '500': @@ -521,8 +525,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - application/json // responses: // '200': - // schema: - // items: // $ref: "#/responses/DocsLibpodInspectImageResponse" // '404': // $ref: '#/responses/NoSuchImage' diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 1f09e8f0e..5069326b6 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -19,11 +19,14 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // descriptions: needs description and plumbing for filters // responses: // '200': - // $ref: "#/responses/ListPodsResponse" + // properties: + // items: + // $ref: "#/responses/ListPodsResponse" + // type: array // '400': // $ref: "#/responses/BadParamError" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/json"), APIHandler(s.Context, libpod.Pods)).Methods(http.MethodGet) r.Handle(VersionedPath("/libpod/pods/create"), APIHandler(s.Context, libpod.PodCreate)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/prune pods PrunePods @@ -43,7 +46,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '400': // $ref: "#/responses/BadParamError" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost) // swagger:operation DELETE /libpod/pods/{nameOrID} pods removePod // --- @@ -67,7 +70,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete) // swagger:operation GET /libpod/pods/{nameOrID}/json pods inspectPod // --- @@ -85,7 +88,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/json"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet) // swagger:operation GET /libpod/pods/{nameOrID}/exists pods podExists // --- @@ -104,7 +107,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet) // swagger:operation POST /libpod/pods/{nameOrID}/kill pods killPod // --- @@ -130,7 +133,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '409': // $ref: "#/responses/ConflictError" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{nameOrID}/pause pods pausePod // --- @@ -148,7 +151,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{nameOrID}/restart pods restartPod // --- @@ -166,7 +169,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{nameOrID}/start pods startPod // --- @@ -186,7 +189,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{nameOrID}/stop pods stopPod // --- @@ -212,7 +215,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost) // swagger:operation POST /libpod/pods/{nameOrID}/unpause pods unpausePod // --- @@ -230,7 +233,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name:..*}/unpause"), APIHandler(s.Context, libpod.PodUnpause)).Methods(http.MethodPost) return nil } diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go index c47156456..ae83c9d52 100644 --- a/pkg/spec/config_linux_cgo.go +++ b/pkg/spec/config_linux_cgo.go @@ -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) diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6d058229b..fb222083b 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -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 diff --git a/test/e2e/config.go b/test/e2e/config.go index aeb7affee..12d0e545e 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -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" ) diff --git a/test/e2e/run_seccomp.go b/test/e2e/run_seccomp.go new file mode 100644 index 000000000..dcf938ad6 --- /dev/null +++ b/test/e2e/run_seccomp.go @@ -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)) + }) +}) |