Copilot commented on code in PR #875:
URL: https://github.com/apache/solr-operator/pull/875#discussion_r4409910227


##########
controllers/solrcloud_controller_test.go:
##########
@@ -769,13 +786,21 @@ var _ = FDescribe("SolrCloud controller - General", 
func() {
                                
g.Expect(customConfigVol.VolumeSource.ConfigMap, "solr-xml Volume should have a 
ConfigMap source")
                                
g.Expect(customConfigVol.VolumeSource.ConfigMap.Name).To(Equal(testCustomConfigMap),
 "solr-xml has the wrong configMap as its source")
 
+                               // The "solr-xml" volume is mounted twice in 
this case, once for the solr.xml (via a subPath)
+                               // and once for the directory containing the 
log4j2.xml.
                                var logXmlVolMount *corev1.VolumeMount = nil
+                               var solrXmlVolMount *corev1.VolumeMount = nil
                                for _, mount := range 
found.Spec.Template.Spec.Containers[0].VolumeMounts {
                                        if mount.Name == "solr-xml" {
-                                               logXmlVolMount = &mount
-                                               break
+                                               if mount.SubPath == 
util.SolrXmlFile {
+                                                       solrXmlVolMount = &mount
+                                               } else {
+                                                       logXmlVolMount = &mount
+                                               }
                                        }
                                }

Review Comment:
   This stores pointers to the range loop variable (`mount`), which is reused 
each iteration in Go; both `solrXmlVolMount` and `logXmlVolMount` can end up 
pointing at the same final value. Iterate by index (or take pointers to slice 
elements) so each pointer refers to the actual element you matched.



##########
controllers/util/security_context_util.go:
##########
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import (
+       solr "github.com/apache/solr-operator/api/v1beta1"
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/utils/ptr"
+)
+
+// Names of the containers and init containers that the Solr Operator 
generates.
+const (
+       VolumePrepInitContainer       = "volume-prep"
+       SetupZkInitContainer          = "setup-zk"
+       Pkcs12InitContainer           = "gen-pkcs12-keystore"
+       TLSWrapperScriptInitContainer = "create-tls-wrapper-script"
+       // InitdbInitContainer is defined in solr_tls_util.go
+)
+
+// operatorContainerOptions describes a container that the operator generates.
+type operatorContainerOptions struct {
+       // imageRunsAsRoot is true if the container's image has no non-root 
user of its own.
+       // The "restricted" profile requires runAsNonRoot, which the kubelet 
refuses to honor for such
+       // an image unless a runAsUser is also given, so these containers get 
one.
+       imageRunsAsRoot bool
+
+       // readOnlyRootFilesystem is true if the container is known not to 
write to its root filesystem.
+       // This is not part of the "restricted" profile and is not applied to 
containers that did not
+       // already have it, see 
https://github.com/apache/solr-operator/issues/801.
+       readOnlyRootFilesystem bool
+}
+
+// operatorContainers lists every container and init container that the 
operator generates, and is
+// therefore responsible for hardening, along with the options that container 
needs. Containers the
+// user supplies through podOptions are not listed: their security context is 
the user's to set.
+//
+// A container missing from this map gets no security context, which makes the 
pod fail "restricted"
+// evaluation. The unit tests evaluate the generated pods against the profile, 
so a new operator
+// container that is not listed here fails the build.
+//
+// A name in this map is not on its own proof that the operator generated the 
container, since a
+// user can give a container of their own the same name. 
setSecurityContextOnOperatorContainers
+// therefore excludes the user's containers by name before consulting this map.
+var operatorContainers = map[string]operatorContainerOptions{
+       SolrNodeContainer:               {},
+       SolrPrometheusExporterContainer: {},
+       SetupZkInitContainer:            {readOnlyRootFilesystem: true},
+       Pkcs12InitContainer:             {},
+       VolumePrepInitContainer:         {imageRunsAsRoot: true},
+       InitdbInitContainer:             {imageRunsAsRoot: true},
+       TLSWrapperScriptInitContainer:   {imageRunsAsRoot: true},
+}
+
+// defaultPodSecurityContext builds the pod-level security context, starting 
from the one the user
+// provided, if any, and filling in every field they left unset. User-provided 
fields always win.
+//
+// The "restricted" profile asks for a seccomp profile, which is set once here 
rather than on every
+// container. fsGroup is not part of the profile, it is a long-standing 
operator default.
+func defaultPodSecurityContext(userProvided *corev1.PodSecurityContext, 
defaultFSGroup int64) *corev1.PodSecurityContext {
+       securityContext := &corev1.PodSecurityContext{}
+       if userProvided != nil {
+               securityContext = userProvided.DeepCopy()
+       }
+
+       if securityContext.SeccompProfile == nil {
+               securityContext.SeccompProfile = &corev1.SeccompProfile{Type: 
corev1.SeccompProfileTypeRuntimeDefault}
+       }
+       if securityContext.FSGroup == nil {
+               securityContext.FSGroup = ptr.To(defaultFSGroup)
+       }
+
+       return securityContext
+}
+
+// defaultContainerSecurityContext builds the security context for one 
operator-generated container,
+// starting from the one the user provided, if any, and filling in every field 
they left unset.
+// User-provided fields always win, so a single field is enough to opt out of 
a single default.
+//
+// podRunAsUser is the pod-level runAsUser, which containers inherit when they 
do not set their own.
+func defaultContainerSecurityContext(userProvided *corev1.SecurityContext, 
options operatorContainerOptions, podRunAsUser *int64) *corev1.SecurityContext {
+       securityContext := &corev1.SecurityContext{}
+       if userProvided != nil {
+               securityContext = userProvided.DeepCopy()
+       }
+
+       // The user can ask for a container to run as root, which some setups 
need (see the backup
+       // repository preparation in generateSolrSetupInitContainers). Such a 
container cannot also run
+       // as non-root, and dropping all capabilities would leave it unable to 
do the work root was
+       // asked for, so neither default is applied to it.
+       effectiveRunAsUser := securityContext.RunAsUser
+       if effectiveRunAsUser == nil {
+               effectiveRunAsUser = podRunAsUser
+       }
+       runsAsRoot := effectiveRunAsUser != nil && *effectiveRunAsUser == 0
+
+       // Kubernetes rejects allowPrivilegeEscalation: false alongside 
privileged: true or an added
+       // CAP_SYS_ADMIN, so the default is not applied when the user asked for 
either of those. An
+       // explicitly provided value is always kept, even an invalid one, so 
the user sees the error.
+       if securityContext.AllowPrivilegeEscalation == nil && 
!securityContextRequiresPrivilegeEscalation(securityContext) {
+               securityContext.AllowPrivilegeEscalation = ptr.To(false)
+       }
+       if !runsAsRoot {
+               if securityContext.RunAsNonRoot == nil {
+                       securityContext.RunAsNonRoot = ptr.To(true)
+               }
+               if securityContext.Capabilities == nil {
+                       securityContext.Capabilities = 
&corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}
+               }
+               if options.imageRunsAsRoot && effectiveRunAsUser == nil {
+                       securityContext.RunAsUser = 
ptr.To(int64(DefaultSolrUser))
+               }
+       }

Review Comment:
   If a user supplies `capabilities: {}` (non-nil struct but no `add`/`drop`), 
this code won’t apply the default `drop: [\"ALL\"]`, and the pod will fail the 
restricted profile unexpectedly. Consider treating an empty `Capabilities` 
object as unset (or default `Drop` when it’s nil/empty and `Add` is empty) so 
the ‘merged over defaults’ behavior is consistent.



##########
helm/solr-operator/Chart.yaml:
##########
@@ -145,6 +145,23 @@ annotations:
           url: https://solr.apache.org/docs/9_11_0/changes/Changes.html
         - name: Solr Security News
           url: https://solr.apache.org/security-news.html
+    - kind: changed
+      description: The `solr.xml` is now mounted into the Solr home directory 
from the ConfigMap, via a subPath volumeMount, instead of being copied there by 
the `cp-solr-xml` initContainer. That initContainer is only created for 
`volume` backupRepositories now, renamed to `volume-prep`
+      links:
+        - name: Github Issue

Review Comment:
   Use the correct casing for the brand name in link titles: change `Github 
Issue` to `GitHub Issue`.



##########
helm/solr-operator/Chart.yaml:
##########
@@ -145,6 +145,23 @@ annotations:
           url: https://solr.apache.org/docs/9_11_0/changes/Changes.html
         - name: Solr Security News
           url: https://solr.apache.org/security-news.html
+    - kind: changed
+      description: The `solr.xml` is now mounted into the Solr home directory 
from the ConfigMap, via a subPath volumeMount, instead of being copied there by 
the `cp-solr-xml` initContainer. That initContainer is only created for 
`volume` backupRepositories now, renamed to `volume-prep`
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/161
+    - kind: security
+      description: Solr and Prometheus exporter pods now satisfy the 
Kubernetes Pod Security Standards "restricted" profile by default. The operator 
sets a seccompProfile on the pod and runAsNonRoot, allowPrivilegeEscalation 
false and capabilities.drop ALL on every container it generates. 
podSecurityContext, containerSecurityContext and 
defaultInitContainerSecurityContext are now merged over these defaults field by 
field
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/873
+        - name: Pod Security Standards
+          url: 
https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
+    - kind: security
+      description: The Solr Operator's own container now drops all 
capabilities and sets a seccompProfile, so the operator pod satisfies the 
"restricted" profile too
+      links:
+        - name: Github Issue

Review Comment:
   Use the correct casing for the brand name in link titles: change `Github 
Issue` to `GitHub Issue`.



##########
helm/solr/Chart.yaml:
##########
@@ -70,6 +70,13 @@ annotations:
       links:
         - name: Solr Security News
           url: https://solr.apache.org/security-news.html
+    - kind: security
+      description: Solr and Prometheus exporter pods now satisfy the 
Kubernetes Pod Security Standards "restricted" profile by default. Anything set 
in podOptions.podSecurityContext, podOptions.containerSecurityContext or 
podOptions.defaultInitContainerSecurityContext is merged over those defaults 
field by field. A volume backupRepository directory must now be writable by the 
Solr user, or set podOptions.defaultInitContainerSecurityContext.runAsUser to 0 
to have the operator take ownership of it
+      links:
+        - name: Github Issue

Review Comment:
   Use the correct casing for the brand name: change `Github Issue` to `GitHub 
Issue` (also matches the URL’s host casing elsewhere in the repo).



##########
helm/solr-operator/Chart.yaml:
##########
@@ -145,6 +145,23 @@ annotations:
           url: https://solr.apache.org/docs/9_11_0/changes/Changes.html
         - name: Solr Security News
           url: https://solr.apache.org/security-news.html
+    - kind: changed
+      description: The `solr.xml` is now mounted into the Solr home directory 
from the ConfigMap, via a subPath volumeMount, instead of being copied there by 
the `cp-solr-xml` initContainer. That initContainer is only created for 
`volume` backupRepositories now, renamed to `volume-prep`
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/161
+    - kind: security
+      description: Solr and Prometheus exporter pods now satisfy the 
Kubernetes Pod Security Standards "restricted" profile by default. The operator 
sets a seccompProfile on the pod and runAsNonRoot, allowPrivilegeEscalation 
false and capabilities.drop ALL on every container it generates. 
podSecurityContext, containerSecurityContext and 
defaultInitContainerSecurityContext are now merged over these defaults field by 
field
+      links:
+        - name: Github Issue

Review Comment:
   Use the correct casing for the brand name in link titles: change `Github 
Issue` to `GitHub Issue`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to