diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 066970d377ce..df852a66e895 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -896,6 +896,10 @@ "description": "Script runs a portion of code against an interpreter", "$ref": "#/definitions/io.argoproj.workflow.v1alpha1.ScriptTemplate" }, + "securityContext": { + "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", + "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext" + }, "serviceAccountName": { "description": "ServiceAccountName to apply to workflow pods", "type": "string" @@ -1220,6 +1224,10 @@ "description": "Set scheduler name for all pods. Will be overridden if container/script template's scheduler name is set. Default scheduler will be used if neither specified.", "type": "string" }, + "securityContext": { + "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", + "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext" + }, "serviceAccountName": { "description": "ServiceAccountName is the name of the ServiceAccount to run all pods of the workflow as.", "type": "string" diff --git a/pkg/apis/workflow/v1alpha1/openapi_generated.go b/pkg/apis/workflow/v1alpha1/openapi_generated.go index 38b1d4415d37..bc3bd4d5f5b4 100644 --- a/pkg/apis/workflow/v1alpha1/openapi_generated.go +++ b/pkg/apis/workflow/v1alpha1/openapi_generated.go @@ -1723,12 +1723,18 @@ func schema_pkg_apis_workflow_v1alpha1_Template(ref common.ReferenceCallback) co }, }, }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", + Ref: ref("k8s.io/api/core/v1.PodSecurityContext"), + }, + }, }, Required: []string{"name"}, }, }, Dependencies: []string{ - "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ArtifactLocation", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.DAGTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Inputs", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Metadata", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Outputs", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ResourceTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.RetryStrategy", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ScriptTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.SuspendTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.UserContainer", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.WorkflowStep", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ArtifactLocation", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.DAGTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Inputs", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Metadata", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Outputs", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ResourceTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.RetryStrategy", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.ScriptTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.SuspendTemplate", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.UserContainer", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.WorkflowStep", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, } } @@ -2302,12 +2308,18 @@ func schema_pkg_apis_workflow_v1alpha1_WorkflowSpec(ref common.ReferenceCallback }, }, }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", + Ref: ref("k8s.io/api/core/v1.PodSecurityContext"), + }, + }, }, Required: []string{"templates", "entrypoint"}, }, }, Dependencies: []string{ - "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Arguments", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Template", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PersistentVolumeClaim", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Arguments", "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1.Template", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PersistentVolumeClaim", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, } } diff --git a/pkg/apis/workflow/v1alpha1/types.go b/pkg/apis/workflow/v1alpha1/types.go index 503ffe035bc8..ea8d64b9ba29 100644 --- a/pkg/apis/workflow/v1alpha1/types.go +++ b/pkg/apis/workflow/v1alpha1/types.go @@ -167,6 +167,11 @@ type WorkflowSpec struct { // HostAliases is an optional list of hosts and IPs that will be injected into the pod spec HostAliases []apiv1.HostAlias `json:"hostAliases,omitempty"` + + // SecurityContext holds pod-level security attributes and common container settings. + // Optional: Defaults to empty. See type description for default values of each field. + // +optional + SecurityContext *apiv1.PodSecurityContext `json:"securityContext,omitempty"` } // Template is a reusable and composable unit of execution in a workflow @@ -261,6 +266,11 @@ type Template struct { // HostAliases is an optional list of hosts and IPs that will be injected into the pod spec HostAliases []apiv1.HostAlias `json:"hostAliases,omitempty"` + + // SecurityContext holds pod-level security attributes and common container settings. + // Optional: Defaults to empty. See type description for default values of each field. + // +optional + SecurityContext *apiv1.PodSecurityContext `json:"securityContext,omitempty"` } // Inputs are the mechanism for passing parameters, artifacts, volumes from one template to another diff --git a/pkg/apis/workflow/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/workflow/v1alpha1/zz_generated.deepcopy.go index 7977fd1ec5ed..c59bbc6621cb 100644 --- a/pkg/apis/workflow/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/workflow/v1alpha1/zz_generated.deepcopy.go @@ -860,6 +860,11 @@ func (in *Template) DeepCopyInto(out *Template) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } return } @@ -1073,6 +1078,11 @@ func (in *WorkflowSpec) DeepCopyInto(out *WorkflowSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } return } diff --git a/workflow/controller/workflowpod.go b/workflow/controller/workflowpod.go index 8ca546dd65de..489f9b102ea2 100644 --- a/workflow/controller/workflowpod.go +++ b/workflow/controller/workflowpod.go @@ -515,6 +515,12 @@ func addSchedulingConstraints(pod *apiv1.Pod, wfSpec *wfv1.WorkflowSpec, tmpl *w pod.Spec.HostAliases = append(pod.Spec.HostAliases, wfSpec.HostAliases...) pod.Spec.HostAliases = append(pod.Spec.HostAliases, tmpl.HostAliases...) + // set pod security context + if tmpl.SecurityContext != nil { + pod.Spec.SecurityContext = tmpl.SecurityContext + } else if wfSpec.SecurityContext != nil { + pod.Spec.SecurityContext = wfSpec.SecurityContext + } } // addVolumeReferences adds any volumeMounts that a container/sidecar is referencing, to the pod.spec.volumes diff --git a/workflow/controller/workflowpod_test.go b/workflow/controller/workflowpod_test.go index 2629a79bf93d..b603f09cb727 100644 --- a/workflow/controller/workflowpod_test.go +++ b/workflow/controller/workflowpod_test.go @@ -3,9 +3,10 @@ package controller import ( "encoding/json" "fmt" - "github.com/argoproj/argo/workflow/config" "testing" + "github.com/argoproj/argo/workflow/config" + wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" "github.com/argoproj/argo/workflow/common" "github.com/ghodss/yaml" @@ -656,3 +657,33 @@ func TestTmplLevelHostAliases(t *testing.T) { assert.NotNil(t, pod.Spec.HostAliases) } + +// TestWFLevelSecurityContext verifies the ability to carry forward workflow level SecurityContext to Podspec +func TestWFLevelSecurityContext(t *testing.T) { + woc := newWoc() + runAsUser := int64(1234) + woc.wf.Spec.SecurityContext = &apiv1.PodSecurityContext{ + RunAsUser: &runAsUser, + } + woc.executeContainer(woc.wf.Spec.Entrypoint, &woc.wf.Spec.Templates[0], "") + podName := getPodName(woc.wf) + pod, err := woc.controller.kubeclientset.CoreV1().Pods("").Get(podName, metav1.GetOptions{}) + assert.Nil(t, err) + assert.NotNil(t, pod.Spec.SecurityContext) + assert.Equal(t, runAsUser, *pod.Spec.SecurityContext.RunAsUser) +} + +// TestTmplLevelSecurityContext verifies the ability to carry forward template level SecurityContext to Podspec +func TestTmplLevelSecurityContext(t *testing.T) { + woc := newWoc() + runAsUser := int64(1234) + woc.wf.Spec.Templates[0].SecurityContext = &apiv1.PodSecurityContext{ + RunAsUser: &runAsUser, + } + woc.executeContainer(woc.wf.Spec.Entrypoint, &woc.wf.Spec.Templates[0], "") + podName := getPodName(woc.wf) + pod, err := woc.controller.kubeclientset.CoreV1().Pods("").Get(podName, metav1.GetOptions{}) + assert.Nil(t, err) + assert.NotNil(t, pod.Spec.SecurityContext) + assert.Equal(t, runAsUser, *pod.Spec.SecurityContext.RunAsUser) +}