Skip to content

Commit 163c164

Browse files
feloyrm3l
andauthored
Pod Security Admission support for odo deploy (#6679)
* 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 <[email protected]> * add test --------- Co-authored-by: Armel Soro <[email protected]>
1 parent d0edfb6 commit 163c164

File tree

15 files changed

+188
-23
lines changed

15 files changed

+188
-23
lines changed

.golangci.yaml

-6
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,3 @@ issues:
8888
# Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint
8989
# See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331
9090
text: 'SA1019: allComponents\[i\].RunningOn is deprecated'
91-
- linters:
92-
- staticcheck
93-
# Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint
94-
# See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331
95-
# TODO(feloy) Remove when https://github.com/devfile/library/pull/167 is merged
96-
text: 'SA1019: generator.GetContainers is deprecated: in favor of GetPodTemplateSpec'

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/Xuanwo/go-locale v1.1.0
1010
github.com/blang/semver v3.5.1+incompatible
1111
github.com/devfile/api/v2 v2.2.0
12-
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257
12+
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f
1313
github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273
1414
github.com/devfile/registry-support/registry-library v0.0.0-20221201200738-19293ac0b8ab
1515
github.com/fatih/color v1.14.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,8 @@ github.com/devfile/library v1.2.1-0.20220308191614-f0f7e11b17de/go.mod h1:GSPfJa
390390
github.com/devfile/library/v2 v2.0.1/go.mod h1:paJ0PARAVy0br13VpBEQ4fO3rZVDxWtooQ29+23PNBk=
391391
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257 h1:BgnMyht1qUYqMRR1H2aAMIcEXb9ignawJbdSMrAZg8Y=
392392
github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0=
393+
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f h1:DRWf62j2diJCEPPumsKUkypNlyMV2/P6e3q6zcDT+WM=
394+
github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0=
393395
github.com/devfile/registry-support/index/generator v0.0.0-20220222194908-7a90a4214f3e/go.mod h1:iRPBxs+ZjfLEduVXpCCIOzdD2588Zv9OCs/CcXMcCCY=
394396
github.com/devfile/registry-support/index/generator v0.0.0-20220527155645-8328a8a883be/go.mod h1:1fyDJL+fPHtcrYA6yjSVWeLmXmjCNth0d5Rq1rvtryc=
395397
github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273 h1:DXENQSRTEDsk9com38njPg5511DD12HPIgzyFUErnpM=

pkg/deploy/deploy.go

+17-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/devfile/library/v2/pkg/devfile/parser"
1313
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
1414
batchv1 "k8s.io/api/batch/v1"
15-
corev1 "k8s.io/api/core/v1"
1615
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1716
"k8s.io/klog/v2"
1817
"k8s.io/utils/pointer"
@@ -96,17 +95,28 @@ func (o *deployHandler) ApplyOpenShift(openshift v1alpha2.Component) error {
9695

9796
// Execute will deploy the listed information in the `exec` section of devfile.yaml
9897
func (o *deployHandler) Execute(command v1alpha2.Command) error {
99-
containerComps, err := generator.GetContainers(o.devfileObj, common.DevfileOptions{FilterByName: command.Exec.Component})
98+
policy, err := o.kubeClient.GetCurrentNamespacePolicy()
10099
if err != nil {
101100
return err
102101
}
103-
if len(containerComps) != 1 {
102+
podTemplateSpec, err := generator.GetPodTemplateSpec(o.devfileObj, generator.PodTemplateParams{
103+
Options: common.DevfileOptions{
104+
FilterByName: command.Exec.Component,
105+
},
106+
PodSecurityAdmissionPolicy: policy,
107+
})
108+
if err != nil {
109+
return err
110+
}
111+
// 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.
112+
podTemplateSpec.Spec.RestartPolicy = "Never"
113+
114+
if len(podTemplateSpec.Spec.Containers) != 1 {
104115
return fmt.Errorf("could not find the component")
105116
}
106117

107-
containerComp := containerComps[0]
108-
containerComp.Command = []string{"/bin/sh"}
109-
containerComp.Args = getCmdline(command)
118+
podTemplateSpec.Spec.Containers[0].Command = []string{"/bin/sh"}
119+
podTemplateSpec.Spec.Containers[0].Args = getCmdline(command)
110120

111121
// Create a Kubernetes Job and use the container image referenced by command.Exec.Component
112122
// Get the component for the command with command.Exec.Component
@@ -123,13 +133,7 @@ func (o *deployHandler) Execute(command v1alpha2.Command) error {
123133
ObjectMeta: metav1.ObjectMeta{
124134
Name: getJobName(),
125135
},
126-
PodTemplateSpec: corev1.PodTemplateSpec{
127-
Spec: corev1.PodSpec{
128-
Containers: []corev1.Container{containerComp},
129-
// 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.
130-
RestartPolicy: "Never",
131-
},
132-
},
136+
PodTemplateSpec: *podTemplateSpec,
133137
SpecParams: odogenerator.JobSpecParams{
134138
CompletionMode: &completionMode,
135139
TTLSecondsAfterFinished: pointer.Int32(60),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
commands:
2+
- exec:
3+
commandLine: npm install
4+
component: runtime
5+
group:
6+
isDefault: true
7+
kind: build
8+
workingDir: /project
9+
id: install
10+
- exec:
11+
commandLine: npm start
12+
component: runtime
13+
group:
14+
isDefault: true
15+
kind: run
16+
workingDir: /project
17+
id: run
18+
- exec:
19+
commandLine: npm run debug
20+
component: runtime
21+
group:
22+
isDefault: true
23+
kind: debug
24+
workingDir: /project
25+
id: debug
26+
- exec:
27+
commandLine: npm test
28+
component: runtime
29+
group:
30+
isDefault: true
31+
kind: test
32+
workingDir: /project
33+
id: test
34+
- exec:
35+
commandLine: sleep 20
36+
component: runtime
37+
id: deploy-exec
38+
- id: deploy
39+
composite:
40+
commands:
41+
- deploy-exec
42+
group:
43+
kind: deploy
44+
isDefault: true
45+
components:
46+
- container:
47+
endpoints:
48+
- name: http-3000
49+
targetPort: 3000
50+
image: registry.access.redhat.com/ubi8/nodejs-14:latest
51+
memoryLimit: 1024Mi
52+
mountSources: true
53+
sourceMapping: /project
54+
name: runtime
55+
metadata:
56+
description: Stack with Node.js 14
57+
displayName: Node.js Runtime
58+
icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg
59+
language: javascript
60+
name: nodejs-prj1-api-abhz
61+
projectType: nodejs
62+
tags:
63+
- NodeJS
64+
- Express
65+
- ubi8
66+
version: 1.0.1
67+
schemaVersion: 2.2.0
68+
starterProjects:
69+
- git:
70+
remotes:
71+
origin: https://github.com/odo-devfiles/nodejs-ex.git
72+
name: nodejs-starter
73+
variables:
74+
CONTAINER_IMAGE: quay.io/unknown-account/myimage

tests/helper/component_cluster.go

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
. "github.com/onsi/gomega"
8+
batchv1 "k8s.io/api/batch/v1"
89
corev1 "k8s.io/api/core/v1"
910

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

74+
func (o *ClusterComponent) GetJobDef() *batchv1.Job {
75+
var jobDef batchv1.Job
76+
var jobName string
77+
Eventually(func() string {
78+
jobName = o.cli.GetJobNameByComponent(o.name, o.namespace)
79+
return jobName
80+
}).Should(Not(BeEmpty()))
81+
bufferOutput := o.cli.Run("get", "jobs", jobName, "-o", "json").Out.Contents()
82+
err := json.Unmarshal(bufferOutput, &jobDef)
83+
Expect(err).ToNot(HaveOccurred())
84+
return &jobDef
85+
}
86+
7387
func (o *ClusterComponent) GetPodLogs() string {
7488
podName := o.cli.GetRunningPodNameByComponent(o.name, o.namespace)
7589
return string(o.cli.Run("-n", o.namespace, "logs", podName).Out.Contents())

tests/helper/component_interface.go

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package helper
22

33
import (
44
. "github.com/onsi/ginkgo/v2"
5+
batchv1 "k8s.io/api/batch/v1"
56
corev1 "k8s.io/api/core/v1"
67
)
78

@@ -22,6 +23,8 @@ type Component interface {
2223
GetLabels() map[string]string
2324
// GetPodDef returns the definition of the pod
2425
GetPodDef() *corev1.Pod
26+
// GetJobDef returns the definition of the job
27+
GetJobDef() *batchv1.Job
2528
// GetPodLogs returns logs for the pod
2629
GetPodLogs() string
2730
}

tests/helper/component_podman.go

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
. "github.com/onsi/ginkgo/v2"
1111
. "github.com/onsi/gomega"
12+
batchv1 "k8s.io/api/batch/v1"
1213
corev1 "k8s.io/api/core/v1"
1314
jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
1415
"k8s.io/kubectl/pkg/scheme"
@@ -110,6 +111,11 @@ func (o *PodmanComponent) GetPodDef() *corev1.Pod {
110111
return &pod
111112
}
112113

114+
func (o *PodmanComponent) GetJobDef() *batchv1.Job {
115+
// Not implemented for Podman
116+
panic("not implemented for Podman")
117+
}
118+
113119
func (o *PodmanComponent) GetLabels() map[string]string {
114120
podName := fmt.Sprintf("%s-%s", o.componentName, o.app)
115121
cmd := exec.Command("podman", "pod", "inspect", podName, "--format", "json")

tests/helper/helper_cli.go

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type CliRunner interface {
1414
Exec(podName string, projectName string, args []string, expectedSuccess *bool) (string, string)
1515
CheckCmdOpInRemoteDevfilePod(podName string, containerName string, prjName string, cmd []string, checkOp func(cmdOp string, err error) bool) bool
1616
GetRunningPodNameByComponent(compName string, namespace string) string
17+
GetJobNameByComponent(compName string, namespace string) string
1718
GetVolumeMountNamesandPathsFromContainer(deployName string, containerName, namespace string) string
1819
WaitAndCheckForExistence(resourceType, namespace string, timeoutMinutes int) bool
1920
GetServices(namespace string) string

tests/helper/helper_cmd_wrapper.go

+5
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ func (cw *CmdWrapper) ShouldRun() *CmdWrapper {
114114
return cw
115115
}
116116

117+
func (cw *CmdWrapper) Should(f func(session *gexec.Session)) {
118+
cw.Runner()
119+
f(cw.session)
120+
}
121+
117122
func (cw *CmdWrapper) WithTerminate(timeoutAfter time.Duration, stop chan bool) *CmdWrapper {
118123
cw.timeout = timeoutAfter * time.Second
119124
cw.stopChan = stop

tests/helper/helper_kubectl.go

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
const (
1919
ResourceTypeDeployment = "deployment"
2020
ResourceTypePod = "pod"
21+
ResourceTypeJob = "job"
2122
ResourceTypePVC = "pvc"
2223
ResourceTypeService = "service"
2324
)
@@ -91,6 +92,13 @@ func (kubectl KubectlRunner) GetRunningPodNameByComponent(compName string, names
9192
return strings.TrimSpace(stdOut)
9293
}
9394

95+
// GetJobNameByComponent executes kubectl command and returns the running job name
96+
func (kubectl KubectlRunner) GetJobNameByComponent(compName string, namespace string) string {
97+
selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName)
98+
stdOut := Cmd(kubectl.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
99+
return strings.TrimSpace(stdOut)
100+
}
101+
94102
// GetPVCSize executes kubectl command and returns the bound storage size
95103
func (kubectl KubectlRunner) GetPVCSize(compName, storageName, namespace string) string {
96104
selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName)

tests/helper/helper_oc.go

+7
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ func (oc OcRunner) GetRunningPodNameByComponent(compName string, namespace strin
164164
return strings.TrimSpace(stdOut)
165165
}
166166

167+
// GetJobNameByComponent executes kubectl command and returns the running job name
168+
func (oc OcRunner) GetJobNameByComponent(compName string, namespace string) string {
169+
selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName)
170+
stdOut := Cmd(oc.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
171+
return strings.TrimSpace(stdOut)
172+
}
173+
167174
// GetPVCSize executes oc command and returns the bound storage size
168175
func (oc OcRunner) GetPVCSize(compName, storageName, namespace string) string {
169176
selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName)

tests/integration/cmd_devfile_deploy_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111

1212
. "github.com/onsi/ginkgo/v2"
1313
. "github.com/onsi/gomega"
14+
"github.com/onsi/gomega/gexec"
15+
"github.com/redhat-developer/odo/pkg/labels"
1416
segment "github.com/redhat-developer/odo/pkg/segment/context"
1517
"github.com/redhat-developer/odo/tests/helper"
1618
)
@@ -533,6 +535,18 @@ CMD ["npm", "start"]
533535
})
534536
})
535537

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

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

580594
})
595+
596+
Context("deploying devfile with long-running exec", func() {
597+
BeforeEach(func() {
598+
helper.CopyExampleDevFile(
599+
filepath.Join("source", "devfiles", "nodejs", "devfile-deploy-exec-long.yaml"),
600+
path.Join(commonVar.Context, "devfile.yaml"),
601+
helper.DevfileMetadataNameSetter(cmpName))
602+
})
603+
604+
When("pod security is enforced as restricted", func() {
605+
BeforeEach(func() {
606+
commonVar.CliRunner.SetLabelsOnNamespace(
607+
commonVar.Project,
608+
"pod-security.kubernetes.io/enforce=restricted",
609+
"pod-security.kubernetes.io/enforce-version=latest",
610+
)
611+
})
612+
613+
It("should set securitycontext for podsecurity admission on job's pod template", func() {
614+
if os.Getenv("KUBERNETES") != "true" {
615+
Skip("This is a Kubernetes specific scenario, skipping")
616+
}
617+
helper.Cmd("odo", "deploy").Should(func(session *gexec.Session) {
618+
component := helper.NewComponent(cmpName, "app", labels.ComponentDeployMode, commonVar.Project, commonVar.CliRunner)
619+
jobDef := component.GetJobDef()
620+
Expect(*jobDef.Spec.Template.Spec.SecurityContext.RunAsNonRoot).To(BeTrue())
621+
Expect(string(jobDef.Spec.Template.Spec.SecurityContext.SeccompProfile.Type)).To(Equal("RuntimeDefault"))
622+
})
623+
})
624+
})
625+
})
581626
})

vendor/github.com/devfile/library/v2/pkg/devfile/generator/generators.go

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ github.com/devfile/api/v2/pkg/utils/overriding
146146
github.com/devfile/api/v2/pkg/utils/unions
147147
github.com/devfile/api/v2/pkg/validation
148148
github.com/devfile/api/v2/pkg/validation/variables
149-
# github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257
149+
# github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f
150150
## explicit; go 1.15
151151
github.com/devfile/library/v2/pkg/devfile
152152
github.com/devfile/library/v2/pkg/devfile/generator

0 commit comments

Comments
 (0)