From b68744f94de916054abae4c650ec163e6fe2ecbb Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Thu, 16 Nov 2023 16:19:04 +0100 Subject: [PATCH] Add SecurityContext logic. (#133) * Don't display info error when there is NO error. * Add SecurityContext logic. * Missing file in previous commit. * Add missing file. * Use Capabilities to allow 1000 to build Arrange /deployment/tmp to /tmp/tmp.xml as /deployment is not writable. * oops typo. --- api/v1alpha1/webserver_types.go | 2 + .../crd/bases/web.servers.org_webservers.yaml | 167 ++++++++++++++++++ controllers/templates.go | 63 +++++-- controllers/webserver_controller.go | 4 +- 4 files changed, 225 insertions(+), 11 deletions(-) diff --git a/api/v1alpha1/webserver_types.go b/api/v1alpha1/webserver_types.go index 426a35c..3be9c6f 100644 --- a/api/v1alpha1/webserver_types.go +++ b/api/v1alpha1/webserver_types.go @@ -46,6 +46,8 @@ type WebServerSpec struct { VolumeName string `json:"volumeName,omitempty"` // StorageClass name of the storage class we want to use for the bound StorageClass string `json:"storageClass,omitempty"` + // SecurityContext defines the security capabilities required to run the application. + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` } // (Deployment method 1) Application image diff --git a/config/crd/bases/web.servers.org_webservers.yaml b/config/crd/bases/web.servers.org_webservers.yaml index 434733a..b377014 100644 --- a/config/crd/bases/web.servers.org_webservers.yaml +++ b/config/crd/bases/web.servers.org_webservers.yaml @@ -89,6 +89,170 @@ spec: routeHostname: description: Route behaviour:[tls]hostname/NONE or empty. type: string + securityContext: + description: SecurityContext defines the security capabilities required + to run the application. + 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 + Note that this field cannot be set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container + runtime. Note that this field cannot be set when spec.os.name + is windows. + 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. Note that this field cannot be set when spec.os.name + is windows. + 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. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. + Default is false. Note that this field cannot be set when spec.os.name + is windows. + 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. Note that this + field cannot be set when spec.os.name is windows. + 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. Note that this field cannot be set when spec.os.name + is windows. + 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. Note that this + field cannot be set when spec.os.name is windows. + 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. Note that this + field cannot be set when spec.os.name is windows. + 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. Note + that this field cannot be set when spec.os.name is linux. + 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 + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is alpha-level + and will only be honored by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature flag + will result in errors when validating the Pod. All of a + Pod's containers must have the same effective HostProcess + value (it is not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + 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 + storageClass: + description: StorageClass name of the storage class we want to use + for the bound + type: string tlsPassword: description: TLSPassword passphrase for the key in the client.key type: string @@ -100,6 +264,9 @@ spec: useSessionClustering: description: Use Session Clustering type: boolean + volumeName: + description: VolumeName is the name of pv we eant to bound + type: string webImage: description: (Deployment method 1) Application image properties: diff --git a/controllers/templates.go b/controllers/templates.go index 4b426b2..4a7a609 100644 --- a/controllers/templates.go +++ b/controllers/templates.go @@ -249,9 +249,17 @@ func (r *WebServerReconciler) generateBuildPod(webServer *webserversv1alpha1.Web serviceAccountName := "" var securityContext *corev1.SecurityContext if r.isOpenShift { + // RunAsUser must correspond to the USER in the docker image. serviceAccountName = "builder" securityContext = &corev1.SecurityContext{ RunAsUser: &[]int64{1000}[0], + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + // "CAP_SETGID", "CAP_SETUID", + // "SETGID", "SETUID", "SYS_ADMIN", "SYS_CHROOT", + "SYS_ADMIN", "SYS_CHROOT", + }, + }, } } else { securityContext = &corev1.SecurityContext{ @@ -268,6 +276,11 @@ func (r *WebServerReconciler) generateBuildPod(webServer *webserversv1alpha1.Web ServiceAccountName: serviceAccountName, /* secret to pull the image */ ImagePullSecrets: r.generateimagePullSecrets(webServer), + SecurityContext: &corev1.PodSecurityContext{ + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, Containers: []corev1.Container{ { Name: "war", @@ -347,6 +360,7 @@ func (r *WebServerReconciler) generateUpdatedDeployment(webServer *webserversv1a if webServer.Spec.WebImage.WebApp != nil { applicationimage = webServer.Spec.WebImage.WebApp.WebAppWarImage } + log.Info("generateUpdatedDeployment") podTemplateSpec := r.generatePodTemplate(webServer, applicationimage) deployment.ObjectMeta = objectMeta spec := kbappsv1.DeploymentSpec{ @@ -710,10 +724,16 @@ func (r *WebServerReconciler) generatePodTemplate(webServer *webserversv1alpha1. health = webServer.Spec.WebImageStream.WebServerHealthCheck } terminationGracePeriodSeconds := int64(60) + template := corev1.PodTemplateSpec{ ObjectMeta: objectMeta, Spec: corev1.PodSpec{ TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, + SecurityContext: &corev1.PodSecurityContext{ + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, Containers: []corev1.Container{{ Name: webServer.Spec.ApplicationName, Image: image, @@ -734,8 +754,9 @@ func (r *WebServerReconciler) generatePodTemplate(webServer *webserversv1alpha1. ContainerPort: 9404, Protocol: corev1.ProtocolTCP, }}, - Env: r.generateEnvVars(webServer), - VolumeMounts: r.generateVolumeMounts(webServer), + SecurityContext: generateSecurityContext(webServer.Spec.SecurityContext), + Env: r.generateEnvVars(webServer), + VolumeMounts: r.generateVolumeMounts(webServer), }}, Volumes: r.generateVolumes(webServer), // Add the imagePullSecret to imagePullSecrets @@ -1138,7 +1159,6 @@ func (r *WebServerReconciler) generateLivenessProbeScript(webServer *webserversv } // create the shell script to modify server.xml -// func (r *WebServerReconciler) generateCommandForServerXml(webServer *webserversv1alpha1.WebServer) map[string]string { cmd := make(map[string]string) connector := "" @@ -1176,7 +1196,7 @@ func (r *WebServerReconciler) generateCommandForServerXml(webServer *webserversv "elif [ ! -f \"/tls/server.crt\" -o ! -f \"/tls/server.key\" ] ; then \n" + "log_warning \"Partial HTTPS configuration, the https connector WILL NOT be configured.\" \n" + "fi \n" + - "sed \"/ /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + "sed \"/ /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" } cmd["test.sh"] = "FILE=`find /opt -name server.xml`\n" + @@ -1186,10 +1206,10 @@ func (r *WebServerReconciler) generateCommandForServerXml(webServer *webserversv "grep -q MembershipProvider ${FILE}\n" + "if [ $? -ne 0 ]; then\n" if r.getUseKUBEPing(webServer) { - cmd["test.sh"] = cmd["test.sh"] + " sed '/cluster.html/a \\n \\n \\n \\n \\n' ${FILE}> /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + + cmd["test.sh"] = cmd["test.sh"] + " sed '/cluster.html/a \\n \\n \\n \\n \\n' ${FILE}> /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" + "fi\n" + connector } else { - cmd["test.sh"] = cmd["test.sh"] + " sed '/cluster.html/a \\n \\n \\n \\n \\n' ${FILE}> /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + + cmd["test.sh"] = cmd["test.sh"] + " sed '/cluster.html/a \\n \\n \\n \\n \\n' ${FILE}> /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" + "fi\n" + connector } if webServer.Spec.EnableAccessLogs { @@ -1199,9 +1219,9 @@ func (r *WebServerReconciler) generateCommandForServerXml(webServer *webserversv "sed -i \"s|prefix=\\\"1\\\"|prefix=\\\"access-$HOSTNAME\\\"|g\" ${FILE}\n" + "sed -i 's|suffix=\"\"|suffix=\".log\"|g' ${FILE}\n" + "else\n" + - "sed 's|directory=\"logs\"|directory=\"/opt/tomcat_logs\"|g' ${FILE}> /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + - "sed \"s|prefix=\\\"localhost_access_log\\\"|prefix=\\\"access-$HOSTNAME\\\"|g\" ${FILE}> /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + - "sed 's|suffix=\".txt\"|suffix=\".log\"|g' ${FILE}> /deployments/tmp; cat /deployments/tmp > ${FILE}; rm /deployments/tmp\n" + + "sed 's|directory=\"logs\"|directory=\"/opt/tomcat_logs\"|g' ${FILE}> /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" + + "sed \"s|prefix=\\\"localhost_access_log\\\"|prefix=\\\"access-$HOSTNAME\\\"|g\" ${FILE}> /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" + + "sed 's|suffix=\".txt\"|suffix=\".log\"|g' ${FILE}> /tmp/tmp.xml; cat /tmp/tmp.xml > ${FILE}; rm /tmp/tmp.xml\n" + "fi\n" } cmd["test.sh"] = cmd["test.sh"] + "FILE=`find /opt -name catalina.sh`\n" + @@ -1215,7 +1235,6 @@ func (r *WebServerReconciler) generateCommandForServerXml(webServer *webserversv } // create the shell script to pod builder -// func (r *WebServerReconciler) generateCommandForBuider(script string) map[string]string { cmd := make(map[string]string) cmd["build.sh"] = script @@ -1257,3 +1276,27 @@ func generateResources(r *corev1.ResourceRequirements) corev1.ResourceRequiremen return rTemplate } + +// generateSecurityContext supplements a default SecurityContext and returns it. +func generateSecurityContext(s *corev1.SecurityContext) *corev1.SecurityContext { + allowPrivilegeEscalation := new(bool) + *allowPrivilegeEscalation = false + + runAsNonRoot := new(bool) + *runAsNonRoot = true + + sTemplate := &corev1.SecurityContext{ + AllowPrivilegeEscalation: allowPrivilegeEscalation, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + RunAsNonRoot: runAsNonRoot, + } + if s != nil { + return s + } + + return sTemplate +} diff --git a/controllers/webserver_controller.go b/controllers/webserver_controller.go index 95da536..d04e19f 100644 --- a/controllers/webserver_controller.go +++ b/controllers/webserver_controller.go @@ -356,7 +356,9 @@ func (r *WebServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( log.Info("WebServe createDeployment: " + deployment.Name + " in " + deployment.Namespace + " using: " + deployment.Spec.Template.Spec.Containers[0].Image) result, err = r.createDeployment(ctx, webServer, deployment, deployment.Name, deployment.Namespace) if err != nil || result != (ctrl.Result{}) { - log.Info("WebServer can't create deployment") + if err != nil { + log.Info("WebServer can't create deployment") + } return result, err }