From e9920683b998e73b41d5085615b219781d835af6 Mon Sep 17 00:00:00 2001
From: Daniel Higuero <daniel@napptive.com>
Date: Tue, 2 Mar 2021 18:06:05 +0100
Subject: [PATCH] Add support for security context

Signed-off-by: Daniel Higuero <daniel@napptive.com>
---
 apis/core/v1alpha2/core_workload_types.go     | 53 ++++++++++++
 apis/core/v1alpha2/zz_generated.deepcopy.go   | 80 +++++++++++++++++++
 .../core.oam.dev_containerizedworkloads.yaml  | 40 ++++++++++
 .../core.oam.dev_containerizedworkloads.yaml  | 40 ++++++++++
 .../containerizedworkload/translate.go        | 32 ++++++++
 .../containerizedworkload/translate_test.go   | 47 +++++++++++
 6 files changed, 292 insertions(+)

diff --git a/apis/core/v1alpha2/core_workload_types.go b/apis/core/v1alpha2/core_workload_types.go
index d2acb84f..ea731621 100644
--- a/apis/core/v1alpha2/core_workload_types.go
+++ b/apis/core/v1alpha2/core_workload_types.go
@@ -302,6 +302,55 @@ type ContainerHealthProbe struct {
 	FailureThreshold *int32 `json:"failureThreshold,omitempty"`
 }
 
+// Capability represent POSIX capabilities type
+type Capability string
+
+// Capabilities to be added and removed from running containers.
+type Capabilities struct {
+	// Added capabilities
+	// +optional
+	Add []Capability `json:"add,omitempty" protobuf:"bytes,1,rep,name=add,casttype=Capability"`
+	// Removed capabilities
+	// +optional
+	Drop []Capability `json:"drop,omitempty" protobuf:"bytes,2,rep,name=drop,casttype=Capability"`
+}
+
+// SecurityContext holds security configuration that will be applied to a container.
+type SecurityContext struct {
+	// The capabilities to add/drop when running containers.
+	// Defaults to the default set of capabilities granted by the container runtime.
+	// +optional
+	Capabilities *Capabilities `json:"capabilities,omitempty" protobuf:"bytes,1,opt,name=capabilities"`
+	// Run container in privileged mode.
+	// Processes in privileged containers are essentially equivalent to root on the host.
+	// Defaults to false.
+	// +optional
+	Privileged *bool `json:"privileged,omitempty" protobuf:"varint,2,opt,name=privileged"`
+	// The UID to run the entrypoint of the container process.
+	// Defaults to user specified in image metadata if unspecified.
+	// +optional
+	RunAsUser *int64 `json:"runAsUser,omitempty" protobuf:"varint,4,opt,name=runAsUser"`
+	// The GID to run the entrypoint of the container process.
+	// Uses runtime default if unset.
+	// +optional
+	RunAsGroup *int64 `json:"runAsGroup,omitempty" protobuf:"varint,8,opt,name=runAsGroup"`
+	// Indicates that the container must run as a non-root user.
+	// +optional
+	RunAsNonRoot *bool `json:"runAsNonRoot,omitempty" protobuf:"varint,5,opt,name=runAsNonRoot"`
+	// Whether this container has a read-only root filesystem.
+	// Default is false.
+	// +optional
+	ReadOnlyRootFilesystem *bool `json:"readOnlyRootFilesystem,omitempty" protobuf:"varint,6,opt,name=readOnlyRootFilesystem"`
+	// AllowPrivilegeEscalation controls whether a process can gain more
+	// privileges than its parent process. This bool directly controls if
+	// the no_new_privs flag will be set on the container process.
+	// AllowPrivilegeEscalation is true always when the container is:
+	// 1) run as Privileged
+	// 2) has CAP_SYS_ADMIN
+	// +optional
+	AllowPrivilegeEscalation *bool `json:"allowPrivilegeEscalation,omitempty" protobuf:"varint,7,opt,name=allowPrivilegeEscalation"`
+}
+
 // A Container represents an Open Containers Initiative (OCI) container.
 type Container struct {
 	// Name of this container. Must be unique within its workload.
@@ -354,6 +403,10 @@ type Container struct {
 	// credentials required to pull this container's image can be loaded.
 	// +optional
 	ImagePullSecret *string `json:"imagePullSecret,omitempty"`
+
+	// Security options the container should run with.
+	// +optional
+	SecurityContext *SecurityContext `json:"securityContext,omitempty"`
 }
 
 // A ContainerizedWorkloadSpec defines the desired state of a
diff --git a/apis/core/v1alpha2/zz_generated.deepcopy.go b/apis/core/v1alpha2/zz_generated.deepcopy.go
index 53c2c314..400ed535 100644
--- a/apis/core/v1alpha2/zz_generated.deepcopy.go
+++ b/apis/core/v1alpha2/zz_generated.deepcopy.go
@@ -197,6 +197,31 @@ func (in *CPUResources) DeepCopy() *CPUResources {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Capabilities) DeepCopyInto(out *Capabilities) {
+	*out = *in
+	if in.Add != nil {
+		in, out := &in.Add, &out.Add
+		*out = make([]Capability, len(*in))
+		copy(*out, *in)
+	}
+	if in.Drop != nil {
+		in, out := &in.Drop, &out.Drop
+		*out = make([]Capability, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Capabilities.
+func (in *Capabilities) DeepCopy() *Capabilities {
+	if in == nil {
+		return nil
+	}
+	out := new(Capabilities)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ChildResourceKind) DeepCopyInto(out *ChildResourceKind) {
 	*out = *in
@@ -484,6 +509,11 @@ func (in *Container) DeepCopyInto(out *Container) {
 		*out = new(string)
 		**out = **in
 	}
+	if in.SecurityContext != nil {
+		in, out := &in.SecurityContext, &out.SecurityContext
+		*out = new(SecurityContext)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container.
@@ -1397,6 +1427,56 @@ func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SecurityContext) DeepCopyInto(out *SecurityContext) {
+	*out = *in
+	if in.Capabilities != nil {
+		in, out := &in.Capabilities, &out.Capabilities
+		*out = new(Capabilities)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Privileged != nil {
+		in, out := &in.Privileged, &out.Privileged
+		*out = new(bool)
+		**out = **in
+	}
+	if in.RunAsUser != nil {
+		in, out := &in.RunAsUser, &out.RunAsUser
+		*out = new(int64)
+		**out = **in
+	}
+	if in.RunAsGroup != nil {
+		in, out := &in.RunAsGroup, &out.RunAsGroup
+		*out = new(int64)
+		**out = **in
+	}
+	if in.RunAsNonRoot != nil {
+		in, out := &in.RunAsNonRoot, &out.RunAsNonRoot
+		*out = new(bool)
+		**out = **in
+	}
+	if in.ReadOnlyRootFilesystem != nil {
+		in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem
+		*out = new(bool)
+		**out = **in
+	}
+	if in.AllowPrivilegeEscalation != nil {
+		in, out := &in.AllowPrivilegeEscalation, &out.AllowPrivilegeEscalation
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityContext.
+func (in *SecurityContext) DeepCopy() *SecurityContext {
+	if in == nil {
+		return nil
+	}
+	out := new(SecurityContext)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StoreReference) DeepCopyInto(out *StoreReference) {
 	*out = *in
diff --git a/charts/oam-kubernetes-runtime/crds/core.oam.dev_containerizedworkloads.yaml b/charts/oam-kubernetes-runtime/crds/core.oam.dev_containerizedworkloads.yaml
index b174cb65..e0e72d79 100644
--- a/charts/oam-kubernetes-runtime/crds/core.oam.dev_containerizedworkloads.yaml
+++ b/charts/oam-kubernetes-runtime/crds/core.oam.dev_containerizedworkloads.yaml
@@ -391,6 +391,46 @@ spec:
                       - cpu
                       - memory
                       type: object
+                    securityContext:
+                      description: Security options the container should run with.
+                      properties:
+                        allowPrivilegeEscalation:
+                          description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN'
+                          type: boolean
+                        capabilities:
+                          description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.
+                          properties:
+                            add:
+                              description: Added capabilities
+                              items:
+                                description: Capability represent POSIX capabilities type
+                                type: string
+                              type: array
+                            drop:
+                              description: Removed capabilities
+                              items:
+                                description: Capability represent POSIX capabilities type
+                                type: string
+                              type: array
+                          type: object
+                        privileged:
+                          description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.
+                          type: boolean
+                        readOnlyRootFilesystem:
+                          description: Whether this container has a read-only root filesystem. Default is false.
+                          type: boolean
+                        runAsGroup:
+                          description: The GID to run the entrypoint of the container process. Uses runtime default if unset.
+                          format: int64
+                          type: integer
+                        runAsNonRoot:
+                          description: Indicates that the container must run as a non-root user.
+                          type: boolean
+                        runAsUser:
+                          description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified.
+                          format: int64
+                          type: integer
+                      type: object
                   required:
                   - image
                   - name
diff --git a/legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_containerizedworkloads.yaml b/legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_containerizedworkloads.yaml
index 14692ee1..7368b58b 100644
--- a/legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_containerizedworkloads.yaml
+++ b/legacy/charts/oam-kubernetes-runtime-legacy/crds/core.oam.dev_containerizedworkloads.yaml
@@ -391,6 +391,46 @@ spec:
                     - cpu
                     - memory
                     type: object
+                  securityContext:
+                    description: Security options the container should run with.
+                    properties:
+                      allowPrivilegeEscalation:
+                        description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN'
+                        type: boolean
+                      capabilities:
+                        description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime.
+                        properties:
+                          add:
+                            description: Added capabilities
+                            items:
+                              description: Capability represent POSIX capabilities type
+                              type: string
+                            type: array
+                          drop:
+                            description: Removed capabilities
+                            items:
+                              description: Capability represent POSIX capabilities type
+                              type: string
+                            type: array
+                        type: object
+                      privileged:
+                        description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.
+                        type: boolean
+                      readOnlyRootFilesystem:
+                        description: Whether this container has a read-only root filesystem. Default is false.
+                        type: boolean
+                      runAsGroup:
+                        description: The GID to run the entrypoint of the container process. Uses runtime default if unset.
+                        format: int64
+                        type: integer
+                      runAsNonRoot:
+                        description: Indicates that the container must run as a non-root user.
+                        type: boolean
+                      runAsUser:
+                        description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified.
+                        format: int64
+                        type: integer
+                    type: object
                 required:
                 - image
                 - name
diff --git a/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate.go b/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate.go
index 69962ff8..966b58a0 100644
--- a/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate.go
+++ b/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate.go
@@ -264,6 +264,10 @@ func TranslateContainerWorkload(ctx context.Context, w oam.Workload) ([]oam.Obje
 			d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v)
 		}
 
+		if container.SecurityContext != nil {
+			kubernetesContainer.SecurityContext = translateSecurityContext(container.SecurityContext)
+		}
+
 		d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, kubernetesContainer)
 	}
 
@@ -275,6 +279,34 @@ func TranslateContainerWorkload(ctx context.Context, w oam.Workload) ([]oam.Obje
 	return []oam.Object{d}, nil
 }
 
+// translateSecurityContext transforms a OAM security context into a Kubernetes one.
+func translateSecurityContext(secCtx *v1alpha2.SecurityContext) *corev1.SecurityContext {
+	result := &corev1.SecurityContext{
+		Privileged:               secCtx.Privileged,
+		RunAsUser:                secCtx.RunAsUser,
+		RunAsGroup:               secCtx.RunAsGroup,
+		RunAsNonRoot:             secCtx.RunAsNonRoot,
+		ReadOnlyRootFilesystem:   secCtx.ReadOnlyRootFilesystem,
+		AllowPrivilegeEscalation: secCtx.AllowPrivilegeEscalation,
+	}
+	if secCtx.Capabilities != nil {
+		add := make([]corev1.Capability, 0)
+		drop := make([]corev1.Capability, 0)
+		for _, toAdd := range secCtx.Capabilities.Add {
+			add = append(add, corev1.Capability(toAdd))
+		}
+		for _, toDrop := range secCtx.Capabilities.Drop {
+			drop = append(drop, corev1.Capability(toDrop))
+		}
+		cap := &corev1.Capabilities{
+			Add:  add,
+			Drop: drop,
+		}
+		result.Capabilities = cap
+	}
+	return result
+}
+
 func translateConfigFileToVolume(cf v1alpha2.ContainerConfigFile, wlName, containerName string) (v corev1.Volume, vm corev1.VolumeMount) {
 	mountPath, _ := path.Split(cf.Path)
 	// translate into ConfigMap Volume
diff --git a/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate_test.go b/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate_test.go
index 3ce003e5..8a746e36 100644
--- a/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate_test.go
+++ b/pkg/controller/v1alpha2/core/workloads/containerizedworkload/translate_test.go
@@ -177,6 +177,9 @@ func TestContainerizedWorkloadTranslator(t *testing.T) {
 		"dapr.io/enabled": "true",
 	}
 	dmAnnotation := cwAnnotation
+	flagEnabled := true
+	var secIDValue int64 = 1000
+
 	type args struct {
 		w oam.Workload
 	}
@@ -319,6 +322,50 @@ func TestContainerizedWorkloadTranslator(t *testing.T) {
 				},
 			}))}},
 		},
+		"SuccessfulWithSecurityContext": {
+			reason: "A ContainerizedWorkload with security context should be successfully translated into a deployment.",
+			args: args{
+				w: containerizedWorkload(cwWithContainer(v1alpha2.Container{
+					Name:      "cool-container",
+					Image:     "cool/image:latest",
+					Command:   []string{"run"},
+					Arguments: []string{"--coolflag"},
+					SecurityContext: &v1alpha2.SecurityContext{
+						Capabilities: &v1alpha2.Capabilities{
+							Add:  []v1alpha2.Capability{"ADD"},
+							Drop: []v1alpha2.Capability{"DROP"},
+						},
+						Privileged:               &flagEnabled,
+						RunAsUser:                &secIDValue,
+						RunAsGroup:               &secIDValue,
+						RunAsNonRoot:             &flagEnabled,
+						ReadOnlyRootFilesystem:   &flagEnabled,
+						AllowPrivilegeEscalation: &flagEnabled,
+					},
+				})),
+			},
+			want: want{result: []oam.Object{deployment(dmWithContainer(corev1.Container{
+				Name:    "cool-container",
+				Image:   "cool/image:latest",
+				Command: []string{"run"},
+				Args:    []string{"--coolflag"},
+				SecurityContext: &corev1.SecurityContext{
+					Capabilities: &corev1.Capabilities{
+						Add:  []corev1.Capability{"ADD"},
+						Drop: []corev1.Capability{"DROP"},
+					},
+					Privileged:               &flagEnabled,
+					SELinuxOptions:           nil,
+					WindowsOptions:           nil,
+					RunAsUser:                &secIDValue,
+					RunAsGroup:               &secIDValue,
+					RunAsNonRoot:             &flagEnabled,
+					ReadOnlyRootFilesystem:   &flagEnabled,
+					AllowPrivilegeEscalation: &flagEnabled,
+					ProcMount:                nil,
+				},
+			}))}},
+		},
 	}
 
 	for name, tc := range cases {