Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pod Security Admission support for odo deploy #6679

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -381,6 +381,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))
})

rm3l marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -129,7 +129,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