From e9920683b998e73b41d5085615b219781d835af6 Mon Sep 17 00:00:00 2001 From: Daniel Higuero Date: Tue, 2 Mar 2021 18:06:05 +0100 Subject: [PATCH] Add support for security context Signed-off-by: Daniel Higuero --- 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 {