From af920c1094cd0bed597896ba91bd080efbadf041 Mon Sep 17 00:00:00 2001 From: Daniel Palstra Date: Wed, 2 Nov 2022 14:39:26 +0100 Subject: [PATCH] Add support for securityContext on the container --- api/v1alpha1/wildflyserver_types.go | 2 + api/v1alpha1/zz_generated.deepcopy.go | 5 + api/v1alpha1/zz_generated.openapi.go | 8 +- .../crd/bases/wildfly.org_wildflyservers.yaml | 138 ++++++++++++++++++ controllers/wildflyserver_controller_test.go | 81 ++++++++++ doc/apis.adoc | 1 + pkg/resources/statefulsets/statefulset.go | 7 + 7 files changed, 241 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/wildflyserver_types.go b/api/v1alpha1/wildflyserver_types.go index 3f048444..758b7ef3 100644 --- a/api/v1alpha1/wildflyserver_types.go +++ b/api/v1alpha1/wildflyserver_types.go @@ -67,6 +67,8 @@ type WildFlyServerSpec struct { // ResourcesSpec defines the resources used by the WildFlyServer, ie CPU and memory, use limits and requests. // More info: https://pkg.go.dev/k8s.io/api@v0.18.14/core/v1#ResourceRequirements Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + // SecurityContext + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` } // StandaloneConfigMapSpec defines the desired configMap configuration to obtain the standalone configuration for WildFlyServer diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8c9f13ec..6367b145 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -178,6 +178,11 @@ func (in *WildFlyServerSpec) DeepCopyInto(out *WildFlyServerSpec) { *out = new(v1.ResourceRequirements) (*in).DeepCopyInto(*out) } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WildFlyServerSpec. diff --git a/api/v1alpha1/zz_generated.openapi.go b/api/v1alpha1/zz_generated.openapi.go index 9c454f50..d3f7b6fd 100644 --- a/api/v1alpha1/zz_generated.openapi.go +++ b/api/v1alpha1/zz_generated.openapi.go @@ -321,12 +321,18 @@ func schema__api_v1alpha1_WildFlyServerSpec(ref common.ReferenceCallback) common Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), }, }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext", + Ref: ref("k8s.io/api/core/v1.SecurityContext"), + }, + }, }, Required: []string{"applicationImage", "replicas"}, }, }, Dependencies: []string{ - "./api/v1alpha1.StandaloneConfigMapSpec", "./api/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.ResourceRequirements"}, + "./api/v1alpha1.StandaloneConfigMapSpec", "./api/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext"}, } } diff --git a/config/crd/bases/wildfly.org_wildflyservers.yaml b/config/crd/bases/wildfly.org_wildflyservers.yaml index e3617860..17cd09bf 100644 --- a/config/crd/bases/wildfly.org_wildflyservers.yaml +++ b/config/crd/bases/wildfly.org_wildflyservers.yaml @@ -250,6 +250,144 @@ spec: minItems: 1 type: array x-kubernetes-list-type: set + securityContext: + description: SecurityContext + 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 + procMount: + description: procMount denotes the type of proc mount to use for + the containers. The default is DefaultProcMount which uses the + container runtime defaults for readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + type: string + 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. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail to start + the container if it does. If unset or false, no such validation + will be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. + properties: + level: + description: Level is SELinux level label that applies to + the container. + type: string + role: + description: Role is a SELinux role label that applies to + the container. + type: string + type: + description: Type is a SELinux type label that applies to + the container. + type: string + user: + description: User is a SELinux user label that applies to + the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. If + seccomp options are provided at both the pod & container level, + the container options override the pod options. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must be + preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a profile + defined in a file on the node should be used. RuntimeDefault + - the container runtime default profile should be used. + Unconfined - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will + be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named by + the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA + credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in PodSecurityContext. + If set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: string + type: object + type: object serviceAccountName: type: string sessionAffinity: diff --git a/controllers/wildflyserver_controller_test.go b/controllers/wildflyserver_controller_test.go index d11787c3..60ff3854 100644 --- a/controllers/wildflyserver_controller_test.go +++ b/controllers/wildflyserver_controller_test.go @@ -494,3 +494,84 @@ func TestWildFlyServerWithResources(t *testing.T) { assert.Equal(limitCpu, statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU]) assert.Equal(limitMem, statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory]) } + +func TestWildFlyServerWithSecurityContext(t *testing.T) { + ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + assert := testifyAssert.New(t) + + allowPrivilegeEscalation := new(bool) + *allowPrivilegeEscalation = false + privileged := new(bool) + *privileged = false + readOnlyRootFilesystem := new(bool) + *readOnlyRootFilesystem = true + runAsNonRoot := new(bool) + *runAsNonRoot = true + + var ( + capabilities = &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + } + ) + + // A WildFlyServer resource with metadata and spec. + wildflyServer := &wildflyv1alpha1.WildFlyServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: wildflyv1alpha1.WildFlyServerSpec{ + ApplicationImage: applicationImage, + Replicas: replicas, + SecurityContext: &corev1.SecurityContext{ + AllowPrivilegeEscalation: allowPrivilegeEscalation, + Capabilities: capabilities, + Privileged: privileged, + ReadOnlyRootFilesystem: readOnlyRootFilesystem, + RunAsNonRoot: runAsNonRoot, + }, + }, + } + // Objects to track in the fake client. + objs := []runtime.Object{ + wildflyServer, + } + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(wildflyv1alpha1.GroupVersion, wildflyServer) + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + // Create a WildFlyServerReconciler object with the scheme and fake client. + r := &WildFlyServerReconciler{ + Client: cl, + Scheme: s, + Log: ctrl.Log.WithName("test").WithName("WildFlyServer"), + } + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + // statefulset will be created + _, err := r.Reconcile(context.TODO(), req) + require.NoError(t, err) + + // Check if stateful set has been created with the correct configuration. + statefulSet := &appsv1.StatefulSet{} + err = cl.Get(context.TODO(), req.NamespacedName, statefulSet) + require.NoError(t, err) + assert.Equal(replicas, *statefulSet.Spec.Replicas) + assert.Equal(applicationImage, statefulSet.Spec.Template.Spec.Containers[0].Image) + assert.Equal(allowPrivilegeEscalation, statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation) + assert.Equal(capabilities.Drop[0], statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities.Drop[0]) + assert.Equal(privileged, statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.Privileged) + assert.Equal(readOnlyRootFilesystem, statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem) + assert.Equal(runAsNonRoot, statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot) +} diff --git a/doc/apis.adoc b/doc/apis.adoc index de1ac10b..6ad6b4ef 100644 --- a/doc/apis.adoc +++ b/doc/apis.adoc @@ -45,6 +45,7 @@ It uses a `StatefulSet` with a pod spec that mounts the volume specified by `sto it defaults to false (application image is expected to use WildFly S2I Builder/Runtime images) | bool | false | `standaloneConfigMap` | spec to specify how standalone configuration can be read from a `ConfigMap` | *<> |false | `resources`| Resources spec to specify the request or limits of the Stateful Set. If omitted, the namespace defaults are used (https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/[more info]). | *<> | false +| `securityContext`| SecurityContext spec to define privilege and access control settings for the pod containers created by the Stateful set. If omitted default privileges are used (https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container[more info]) | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#securitycontext-v1-core[*corev1.SecurityContext] | false | `storage` | Storage spec to specify how storage should be used. If omitted, an `EmptyDir` is used (that will not persist data across pod restart) | *<> |false | `serviceAccountName` | Name of the ServiceAccount to use to run the WildFlyServer Pods | string | false | `envFrom` | List of environment variable present in the containers from source (either `ConfigMap` or `Secret`) | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envfromsource-v1-core[corev1.EnvFromSource] |false diff --git a/pkg/resources/statefulsets/statefulset.go b/pkg/resources/statefulsets/statefulset.go index 638cc89f..b2e4ddbd 100644 --- a/pkg/resources/statefulsets/statefulset.go +++ b/pkg/resources/statefulsets/statefulset.go @@ -102,6 +102,8 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, ReadinessProbe: createReadinessProbe(w), // Resources Resources: createResources(w.Spec.Resources), + // SecurityContext + SecurityContext: w.Spec.SecurityContext, }}, ServiceAccountName: w.Spec.ServiceAccountName, }, @@ -114,6 +116,11 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, statefulSet.Spec.Template.Spec.Containers[0].Resources = *w.Spec.Resources } + // if the user specified the securityContext directive propagate it to the container (required for HPA). + if w.Spec.SecurityContext != nil { + statefulSet.Spec.Template.Spec.Containers[0].SecurityContext = *&w.Spec.SecurityContext + } + if len(w.Spec.EnvFrom) > 0 { statefulSet.Spec.Template.Spec.Containers[0].EnvFrom = append(statefulSet.Spec.Template.Spec.Containers[0].EnvFrom, w.Spec.EnvFrom...) }