Skip to content

Commit

Permalink
Pod Security Admission support for odo deploy (#6679)
Browse files Browse the repository at this point in the history
* Use GetPodTemplateSpec instead of deprecated GetContainers

* Specify pod admission policy when building Exec Job

* Integration test

* Update tests/helper/component_podman.go

Co-authored-by: Armel Soro <armel@rm3l.org>

* add test

---------

Co-authored-by: Armel Soro <armel@rm3l.org>
  • Loading branch information
feloy and rm3l authored Mar 28, 2023
1 parent d0edfb6 commit 163c164
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 23 deletions.
6 changes: 0 additions & 6 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,3 @@ issues:
# Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint
# See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331
text: 'SA1019: allComponents\[i\].RunningOn is deprecated'
- linters:
- staticcheck
# Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint
# See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331
# TODO(feloy) Remove when https://github.com/devfile/library/pull/167 is merged
text: 'SA1019: generator.GetContainers is deprecated: in favor of GetPodTemplateSpec'
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/Xuanwo/go-locale v1.1.0
github.com/blang/semver v3.5.1+incompatible
github.com/devfile/api/v2 v2.2.0
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f
github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273
github.com/devfile/registry-support/registry-library v0.0.0-20221201200738-19293ac0b8ab
github.com/fatih/color v1.14.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ github.com/devfile/library v1.2.1-0.20220308191614-f0f7e11b17de/go.mod h1:GSPfJa
github.com/devfile/library/v2 v2.0.1/go.mod h1:paJ0PARAVy0br13VpBEQ4fO3rZVDxWtooQ29+23PNBk=
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257 h1:BgnMyht1qUYqMRR1H2aAMIcEXb9ignawJbdSMrAZg8Y=
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0=
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f h1:DRWf62j2diJCEPPumsKUkypNlyMV2/P6e3q6zcDT+WM=
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0=
github.com/devfile/registry-support/index/generator v0.0.0-20220222194908-7a90a4214f3e/go.mod h1:iRPBxs+ZjfLEduVXpCCIOzdD2588Zv9OCs/CcXMcCCY=
github.com/devfile/registry-support/index/generator v0.0.0-20220527155645-8328a8a883be/go.mod h1:1fyDJL+fPHtcrYA6yjSVWeLmXmjCNth0d5Rq1rvtryc=
github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273 h1:DXENQSRTEDsk9com38njPg5511DD12HPIgzyFUErnpM=
Expand Down
30 changes: 17 additions & 13 deletions pkg/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/devfile/library/v2/pkg/devfile/parser"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"k8s.io/utils/pointer"
Expand Down Expand Up @@ -96,17 +95,28 @@ func (o *deployHandler) ApplyOpenShift(openshift v1alpha2.Component) error {

// Execute will deploy the listed information in the `exec` section of devfile.yaml
func (o *deployHandler) Execute(command v1alpha2.Command) error {
containerComps, err := generator.GetContainers(o.devfileObj, common.DevfileOptions{FilterByName: command.Exec.Component})
policy, err := o.kubeClient.GetCurrentNamespacePolicy()
if err != nil {
return err
}
if len(containerComps) != 1 {
podTemplateSpec, err := generator.GetPodTemplateSpec(o.devfileObj, generator.PodTemplateParams{
Options: common.DevfileOptions{
FilterByName: command.Exec.Component,
},
PodSecurityAdmissionPolicy: policy,
})
if err != nil {
return err
}
// Setting the restart policy to "never" so that pods are kept around after the job finishes execution; this is helpful in obtaining logs to debug.
podTemplateSpec.Spec.RestartPolicy = "Never"

if len(podTemplateSpec.Spec.Containers) != 1 {
return fmt.Errorf("could not find the component")
}

containerComp := containerComps[0]
containerComp.Command = []string{"/bin/sh"}
containerComp.Args = getCmdline(command)
podTemplateSpec.Spec.Containers[0].Command = []string{"/bin/sh"}
podTemplateSpec.Spec.Containers[0].Args = getCmdline(command)

// Create a Kubernetes Job and use the container image referenced by command.Exec.Component
// Get the component for the command with command.Exec.Component
Expand All @@ -123,13 +133,7 @@ func (o *deployHandler) Execute(command v1alpha2.Command) error {
ObjectMeta: metav1.ObjectMeta{
Name: getJobName(),
},
PodTemplateSpec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{containerComp},
// Setting the restart policy to "never" so that pods are kept around after the job finishes execution; this is helpful in obtaining logs to debug.
RestartPolicy: "Never",
},
},
PodTemplateSpec: *podTemplateSpec,
SpecParams: odogenerator.JobSpecParams{
CompletionMode: &completionMode,
TTLSecondsAfterFinished: pointer.Int32(60),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
commands:
- exec:
commandLine: npm install
component: runtime
group:
isDefault: true
kind: build
workingDir: /project
id: install
- exec:
commandLine: npm start
component: runtime
group:
isDefault: true
kind: run
workingDir: /project
id: run
- exec:
commandLine: npm run debug
component: runtime
group:
isDefault: true
kind: debug
workingDir: /project
id: debug
- exec:
commandLine: npm test
component: runtime
group:
isDefault: true
kind: test
workingDir: /project
id: test
- exec:
commandLine: sleep 20
component: runtime
id: deploy-exec
- id: deploy
composite:
commands:
- deploy-exec
group:
kind: deploy
isDefault: true
components:
- container:
endpoints:
- name: http-3000
targetPort: 3000
image: registry.access.redhat.com/ubi8/nodejs-14:latest
memoryLimit: 1024Mi
mountSources: true
sourceMapping: /project
name: runtime
metadata:
description: Stack with Node.js 14
displayName: Node.js Runtime
icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg
language: javascript
name: nodejs-prj1-api-abhz
projectType: nodejs
tags:
- NodeJS
- Express
- ubi8
version: 1.0.1
schemaVersion: 2.2.0
starterProjects:
- git:
remotes:
origin: https://github.com/odo-devfiles/nodejs-ex.git
name: nodejs-starter
variables:
CONTAINER_IMAGE: quay.io/unknown-account/myimage
14 changes: 14 additions & 0 deletions tests/helper/component_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

. "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"

"github.com/redhat-developer/odo/pkg/labels"
Expand Down Expand Up @@ -70,6 +71,19 @@ func (o *ClusterComponent) GetPodDef() *corev1.Pod {
return &podDef
}

func (o *ClusterComponent) GetJobDef() *batchv1.Job {
var jobDef batchv1.Job
var jobName string
Eventually(func() string {
jobName = o.cli.GetJobNameByComponent(o.name, o.namespace)
return jobName
}).Should(Not(BeEmpty()))
bufferOutput := o.cli.Run("get", "jobs", jobName, "-o", "json").Out.Contents()
err := json.Unmarshal(bufferOutput, &jobDef)
Expect(err).ToNot(HaveOccurred())
return &jobDef
}

func (o *ClusterComponent) GetPodLogs() string {
podName := o.cli.GetRunningPodNameByComponent(o.name, o.namespace)
return string(o.cli.Run("-n", o.namespace, "logs", podName).Out.Contents())
Expand Down
3 changes: 3 additions & 0 deletions tests/helper/component_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helper

import (
. "github.com/onsi/ginkgo/v2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
)

Expand All @@ -22,6 +23,8 @@ type Component interface {
GetLabels() map[string]string
// GetPodDef returns the definition of the pod
GetPodDef() *corev1.Pod
// GetJobDef returns the definition of the job
GetJobDef() *batchv1.Job
// GetPodLogs returns logs for the pod
GetPodLogs() string
}
Expand Down
6 changes: 6 additions & 0 deletions tests/helper/component_podman.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/kubectl/pkg/scheme"
Expand Down Expand Up @@ -110,6 +111,11 @@ func (o *PodmanComponent) GetPodDef() *corev1.Pod {
return &pod
}

func (o *PodmanComponent) GetJobDef() *batchv1.Job {
// Not implemented for Podman
panic("not implemented for Podman")
}

func (o *PodmanComponent) GetLabels() map[string]string {
podName := fmt.Sprintf("%s-%s", o.componentName, o.app)
cmd := exec.Command("podman", "pod", "inspect", podName, "--format", "json")
Expand Down
1 change: 1 addition & 0 deletions tests/helper/helper_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CliRunner interface {
Exec(podName string, projectName string, args []string, expectedSuccess *bool) (string, string)
CheckCmdOpInRemoteDevfilePod(podName string, containerName string, prjName string, cmd []string, checkOp func(cmdOp string, err error) bool) bool
GetRunningPodNameByComponent(compName string, namespace string) string
GetJobNameByComponent(compName string, namespace string) string
GetVolumeMountNamesandPathsFromContainer(deployName string, containerName, namespace string) string
WaitAndCheckForExistence(resourceType, namespace string, timeoutMinutes int) bool
GetServices(namespace string) string
Expand Down
5 changes: 5 additions & 0 deletions tests/helper/helper_cmd_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ func (cw *CmdWrapper) ShouldRun() *CmdWrapper {
return cw
}

func (cw *CmdWrapper) Should(f func(session *gexec.Session)) {
cw.Runner()
f(cw.session)
}

func (cw *CmdWrapper) WithTerminate(timeoutAfter time.Duration, stop chan bool) *CmdWrapper {
cw.timeout = timeoutAfter * time.Second
cw.stopChan = stop
Expand Down
8 changes: 8 additions & 0 deletions tests/helper/helper_kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
const (
ResourceTypeDeployment = "deployment"
ResourceTypePod = "pod"
ResourceTypeJob = "job"
ResourceTypePVC = "pvc"
ResourceTypeService = "service"
)
Expand Down Expand Up @@ -91,6 +92,13 @@ func (kubectl KubectlRunner) GetRunningPodNameByComponent(compName string, names
return strings.TrimSpace(stdOut)
}

// GetJobNameByComponent executes kubectl command and returns the running job name
func (kubectl KubectlRunner) GetJobNameByComponent(compName string, namespace string) string {
selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName)
stdOut := Cmd(kubectl.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
return strings.TrimSpace(stdOut)
}

// GetPVCSize executes kubectl command and returns the bound storage size
func (kubectl KubectlRunner) GetPVCSize(compName, storageName, namespace string) string {
selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName)
Expand Down
7 changes: 7 additions & 0 deletions tests/helper/helper_oc.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ func (oc OcRunner) GetRunningPodNameByComponent(compName string, namespace strin
return strings.TrimSpace(stdOut)
}

// GetJobNameByComponent executes kubectl command and returns the running job name
func (oc OcRunner) GetJobNameByComponent(compName string, namespace string) string {
selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName)
stdOut := Cmd(oc.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
return strings.TrimSpace(stdOut)
}

// GetPVCSize executes oc command and returns the bound storage size
func (oc OcRunner) GetPVCSize(compName, storageName, namespace string) string {
selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName)
Expand Down
45 changes: 45 additions & 0 deletions tests/integration/cmd_devfile_deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/redhat-developer/odo/pkg/labels"
segment "github.com/redhat-developer/odo/pkg/segment/context"
"github.com/redhat-developer/odo/tests/helper"
)
Expand Down Expand Up @@ -533,6 +535,18 @@ CMD ["npm", "start"]
})
})

It("should not set securitycontext for podsecurity admission on job's pod template", func() {
if os.Getenv("KUBERNETES") != "true" {
Skip("This is a Kubernetes specific scenario, skipping")
}
helper.Cmd("odo", "deploy").Should(func(session *gexec.Session) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDeployMode, commonVar.Project, commonVar.CliRunner)
jobDef := component.GetJobDef()
Expect(jobDef.Spec.Template.Spec.SecurityContext.RunAsNonRoot).To(BeNil())
Expect(jobDef.Spec.Template.Spec.SecurityContext.SeccompProfile).To(BeNil())
})
})

}

When("using a devfile name with length more than 63", func() {
Expand Down Expand Up @@ -578,4 +592,35 @@ CMD ["npm", "start"]
})

})

Context("deploying devfile with long-running exec", func() {
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-deploy-exec-long.yaml"),
path.Join(commonVar.Context, "devfile.yaml"),
helper.DevfileMetadataNameSetter(cmpName))
})

When("pod security is enforced as restricted", func() {
BeforeEach(func() {
commonVar.CliRunner.SetLabelsOnNamespace(
commonVar.Project,
"pod-security.kubernetes.io/enforce=restricted",
"pod-security.kubernetes.io/enforce-version=latest",
)
})

It("should set securitycontext for podsecurity admission on job's pod template", func() {
if os.Getenv("KUBERNETES") != "true" {
Skip("This is a Kubernetes specific scenario, skipping")
}
helper.Cmd("odo", "deploy").Should(func(session *gexec.Session) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDeployMode, commonVar.Project, commonVar.CliRunner)
jobDef := component.GetJobDef()
Expect(*jobDef.Spec.Template.Spec.SecurityContext.RunAsNonRoot).To(BeTrue())
Expect(string(jobDef.Spec.Template.Spec.SecurityContext.SeccompProfile.Type)).To(Equal("RuntimeDefault"))
})
})
})
})
})

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ github.com/devfile/api/v2/pkg/utils/overriding
github.com/devfile/api/v2/pkg/utils/unions
github.com/devfile/api/v2/pkg/validation
github.com/devfile/api/v2/pkg/validation/variables
# github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257
# github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f
## explicit; go 1.15
github.com/devfile/library/v2/pkg/devfile
github.com/devfile/library/v2/pkg/devfile/generator
Expand Down

0 comments on commit 163c164

Please sign in to comment.