play kube: handle seccomp labels
Add handling of seccomp annotations to play kube at both container and pod levels. also add a test Signed-off-by: Peter Hunt <pehunt@redhat.com>
This commit is contained in:
parent
db32ed1ae8
commit
8d585ccfa8
|
|
@ -596,12 +596,17 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa
|
|||
volumes[volume.Name] = hostPath.Path
|
||||
}
|
||||
|
||||
seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, container := range podYAML.Spec.Containers {
|
||||
newImage, err := r.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageMissing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createConfig, err := kubeContainerToCreateConfig(ctx, container, r.Runtime, newImage, namespaces, volumes, pod.ID(), podInfraID)
|
||||
createConfig, err := kubeContainerToCreateConfig(ctx, container, r.Runtime, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -720,7 +725,7 @@ func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfi
|
|||
}
|
||||
|
||||
// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
|
||||
func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string) (*createconfig.CreateConfig, error) {
|
||||
func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
|
||||
var (
|
||||
containerConfig createconfig.CreateConfig
|
||||
pidConfig createconfig.PidConfig
|
||||
|
|
@ -752,11 +757,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
|
|||
|
||||
setupSecurityContext(&securityConfig, &userConfig, containerYAML)
|
||||
|
||||
var err error
|
||||
containerConfig.Security.SeccompProfilePath, err = libpod.DefaultSeccompPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name)
|
||||
|
||||
containerConfig.Command = []string{}
|
||||
if imageData != nil && imageData.Config != nil {
|
||||
|
|
@ -826,3 +827,80 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
|
|||
}
|
||||
return &containerConfig, nil
|
||||
}
|
||||
|
||||
// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
|
||||
// it holds both container and pod seccomp paths
|
||||
type kubeSeccompPaths struct {
|
||||
containerPaths map[string]string
|
||||
podPath string
|
||||
}
|
||||
|
||||
// findForContainer checks whether a container has a seccomp path configured for it
|
||||
// if not, it returns the podPath, which should always have a value
|
||||
func (k *kubeSeccompPaths) findForContainer(ctrName string) string {
|
||||
if path, ok := k.containerPaths[ctrName]; ok {
|
||||
return path
|
||||
}
|
||||
return k.podPath
|
||||
}
|
||||
|
||||
// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp
|
||||
// it parses both pod and container level
|
||||
func initializeSeccompPaths(annotations map[string]string) (*kubeSeccompPaths, error) {
|
||||
seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)}
|
||||
var err error
|
||||
if annotations != nil {
|
||||
for annKeyValue, seccomp := range annotations {
|
||||
// check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/
|
||||
prefixAndCtr := strings.Split(annKeyValue, "/")
|
||||
if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix {
|
||||
continue
|
||||
} else if len(prefixAndCtr) != 2 {
|
||||
// this could be caused by a user inputting either of
|
||||
// container.seccomp.security.alpha.kubernetes.io{,/}
|
||||
// both of which are invalid
|
||||
return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0])
|
||||
}
|
||||
|
||||
path, err := verifySeccompPath(seccomp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seccompPaths.containerPaths[prefixAndCtr[1]] = path
|
||||
}
|
||||
|
||||
podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey]
|
||||
if ok {
|
||||
seccompPaths.podPath, err = verifySeccompPath(podSeccomp)
|
||||
} else {
|
||||
seccompPaths.podPath, err = libpod.DefaultSeccompPath()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return seccompPaths, nil
|
||||
}
|
||||
|
||||
// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path
|
||||
// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp
|
||||
func verifySeccompPath(path string) (string, error) {
|
||||
switch path {
|
||||
case v1.DeprecatedSeccompProfileDockerDefault:
|
||||
fallthrough
|
||||
case v1.SeccompProfileRuntimeDefault:
|
||||
return libpod.DefaultSeccompPath()
|
||||
case "unconfined":
|
||||
return path, nil
|
||||
default:
|
||||
// TODO we have an inconsistency here
|
||||
// k8s parses `localhost/<path>` which is found at `<seccomp_root>`
|
||||
// we currently parse `localhost:<seccomp_root>/<path>
|
||||
// to fully conform, we need to find a good location for the seccomp root
|
||||
parts := strings.Split(path, ":")
|
||||
if parts[0] == "localhost" {
|
||||
return parts[1], nil
|
||||
}
|
||||
return "", errors.Errorf("invalid seccomp path: %s", path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -559,3 +559,12 @@ func (p *PodmanTestIntegration) RunHealthCheck(cid string) error {
|
|||
}
|
||||
return errors.Errorf("unable to detect %s as running", cid)
|
||||
}
|
||||
|
||||
func (p *PodmanTestIntegration) CreateSeccompJson(in []byte) (string, error) {
|
||||
jsonFile := filepath.Join(p.TempDir, "seccomp.json")
|
||||
err := WriteJsonFile(in, jsonFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsonFile, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
|
@ -20,6 +21,13 @@ metadata:
|
|||
labels:
|
||||
app: {{ .Name }}
|
||||
name: {{ .Name }}
|
||||
{{ with .Annotations }}
|
||||
annotations:
|
||||
{{ range $key, $value := . }}
|
||||
{{ $key }}: {{ $value }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
spec:
|
||||
hostname: {{ .Hostname }}
|
||||
containers:
|
||||
|
|
@ -72,6 +80,7 @@ var (
|
|||
defaultCtrCmd = []string{"top"}
|
||||
defaultCtrImage = ALPINE
|
||||
defaultPodName = "testPod"
|
||||
seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`)
|
||||
)
|
||||
|
||||
func generateKubeYaml(pod *Pod, fileName string) error {
|
||||
|
|
@ -95,16 +104,17 @@ func generateKubeYaml(pod *Pod, fileName string) error {
|
|||
|
||||
// Pod describes the options a kube yaml can be configured at pod level
|
||||
type Pod struct {
|
||||
Name string
|
||||
Hostname string
|
||||
Ctrs []*Ctr
|
||||
Name string
|
||||
Hostname string
|
||||
Ctrs []*Ctr
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
// getPod takes a list of podOptions and returns a pod with sane defaults
|
||||
// and the configured options
|
||||
// if no containers are added, it will add the default container
|
||||
func getPod(options ...podOption) *Pod {
|
||||
p := Pod{defaultPodName, "", make([]*Ctr, 0)}
|
||||
p := Pod{defaultPodName, "", make([]*Ctr, 0), make(map[string]string)}
|
||||
for _, option := range options {
|
||||
option(&p)
|
||||
}
|
||||
|
|
@ -128,6 +138,12 @@ func withCtr(c *Ctr) podOption {
|
|||
}
|
||||
}
|
||||
|
||||
func withAnnotation(k, v string) podOption {
|
||||
return func(pod *Pod) {
|
||||
pod.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Ctr describes the options a kube yaml can be configured at container level
|
||||
type Ctr struct {
|
||||
Name string
|
||||
|
|
@ -330,4 +346,51 @@ var _ = Describe("Podman generate kube", func() {
|
|||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman play kube seccomp container level", func() {
|
||||
// expect play kube is expected to set a seccomp label if it's applied as an annotation
|
||||
jsonFile, err := podmanTest.CreateSeccompJson(seccompPwdEPERM)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Skip("Failed to prepare seccomp.json for test.")
|
||||
}
|
||||
|
||||
ctrAnnotation := "container.seccomp.security.alpha.kubernetes.io/" + defaultCtrName
|
||||
ctr := getCtr(withCmd([]string{"pwd"}))
|
||||
|
||||
err = generateKubeYaml(getPod(withCtr(ctr), withAnnotation(ctrAnnotation, "localhost:"+jsonFile)), kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
logs := podmanTest.Podman([]string{"logs", defaultCtrName})
|
||||
logs.WaitWithDefaultTimeout()
|
||||
Expect(logs.ExitCode()).To(Equal(0))
|
||||
Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
|
||||
})
|
||||
|
||||
It("podman play kube seccomp pod level", func() {
|
||||
// expect play kube is expected to set a seccomp label if it's applied as an annotation
|
||||
jsonFile, err := podmanTest.CreateSeccompJson(seccompPwdEPERM)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Skip("Failed to prepare seccomp.json for test.")
|
||||
}
|
||||
|
||||
ctr := getCtr(withCmd([]string{"pwd"}))
|
||||
|
||||
err = generateKubeYaml(getPod(withCtr(ctr), withAnnotation("seccomp.security.alpha.kubernetes.io/pod", "localhost:"+jsonFile)), kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
logs := podmanTest.Podman([]string{"logs", defaultCtrName})
|
||||
logs.WaitWithDefaultTimeout()
|
||||
Expect(logs.ExitCode()).To(Equal(0))
|
||||
Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -160,9 +160,9 @@ var _ = Describe("Podman run", func() {
|
|||
})
|
||||
|
||||
It("podman run seccomp test", func() {
|
||||
jsonFile := filepath.Join(podmanTest.TempDir, "seccomp.json")
|
||||
|
||||
in := []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`)
|
||||
err := WriteJsonFile(in, jsonFile)
|
||||
jsonFile, err := podmanTest.CreateSeccompJson(in)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Skip("Failed to prepare seccomp.json for test.")
|
||||
|
|
|
|||
Loading…
Reference in New Issue