Repository: kubernetes-sigs/security-profiles-operator
Pull request #1603 (2023-04-05 14:13:44)

Wait a second before stopping `spoc run`



#### What type of PR is this?


/kind cleanup

#### What this PR does / why we need it:
This makes it possible to retrieve late syscalls from applications which are already terminated, but the message did not appear in audit log yet.
#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.

Fixes #

or

None
-->
Refers to #1482 
#### Does this PR have test?
None
<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
None
#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/1603#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/main/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/bundle/manifests/security-profiles-operator-profile_v1_configmap.yaml b/bundle/manifests/security-profiles-operator-profile_v1_configmap.yaml
index a59945d56..d125aa80d 100644
--- a/bundle/manifests/security-profiles-operator-profile_v1_configmap.yaml
+++ b/bundle/manifests/security-profiles-operator-profile_v1_configmap.yaml
@@ -98,6 +98,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/bundle/manifests/security-profiles-operator.clusterserviceversion.yaml b/bundle/manifests/security-profiles-operator.clusterserviceversion.yaml
index 256a2fe37..0836f582f 100644
--- a/bundle/manifests/security-profiles-operator.clusterserviceversion.yaml
+++ b/bundle/manifests/security-profiles-operator.clusterserviceversion.yaml
@@ -306,6 +306,14 @@ spec:
     spec:
       clusterPermissions:
       - rules:
+        - apiGroups:
+          - ""
+          resourceNames:
+          - security-profiles-operator-profile
+          resources:
+          - configmaps
+          verbs:
+          - get
         - apiGroups:
           - ""
           resources:
diff --git a/deploy/base/kustomization.yaml b/deploy/base/kustomization.yaml
index 3261fadf9..0ca49b41b 100644
--- a/deploy/base/kustomization.yaml
+++ b/deploy/base/kustomization.yaml
@@ -15,6 +15,7 @@ configMapGenerator:
   - profiles/security-profiles-operator.json
   - profiles/selinuxd.cil
   - profiles/selinuxrecording.cil
+  - profiles/selinuxd-image-mapping.json
   name: security-profiles-operator-profile
 
 generatorOptions:
diff --git a/deploy/base/profiles/selinuxd-image-mapping.json b/deploy/base/profiles/selinuxd-image-mapping.json
new file mode 100644
index 000000000..900be5241
--- /dev/null
+++ b/deploy/base/profiles/selinuxd-image-mapping.json
@@ -0,0 +1,10 @@
+[
+    {
+        "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+        "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+    },
+    {
+        "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+        "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+    }
+]
diff --git a/deploy/base/role.yaml b/deploy/base/role.yaml
index f26df8c1a..54d4a5bd0 100644
--- a/deploy/base/role.yaml
+++ b/deploy/base/role.yaml
@@ -5,6 +5,14 @@ metadata:
   creationTimestamp: null
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
diff --git a/deploy/helm/templates/static-resources.yaml b/deploy/helm/templates/static-resources.yaml
index d7c3ee9e7..b8d95f7ec 100644
--- a/deploy/helm/templates/static-resources.yaml
+++ b/deploy/helm/templates/static-resources.yaml
@@ -50,6 +50,14 @@ metadata:
     helm.sh/chart: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -966,6 +974,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/deploy/namespace-operator.yaml b/deploy/namespace-operator.yaml
index 30ec3d6d4..10ff10f5f 100644
--- a/deploy/namespace-operator.yaml
+++ b/deploy/namespace-operator.yaml
@@ -2082,6 +2082,14 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -2922,6 +2930,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/deploy/openshift-dev.yaml b/deploy/openshift-dev.yaml
index 0f9979f1b..5d61634cc 100644
--- a/deploy/openshift-dev.yaml
+++ b/deploy/openshift-dev.yaml
@@ -2154,6 +2154,14 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -2904,6 +2912,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/deploy/openshift-downstream.yaml b/deploy/openshift-downstream.yaml
index c2483ea3a..c56a8e663 100644
--- a/deploy/openshift-downstream.yaml
+++ b/deploy/openshift-downstream.yaml
@@ -2082,6 +2082,14 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -2935,6 +2943,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
index acae863fc..184fbf879 100644
--- a/deploy/operator.yaml
+++ b/deploy/operator.yaml
@@ -2082,6 +2082,14 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -2922,6 +2930,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/deploy/overlays/webhook/role.yaml b/deploy/overlays/webhook/role.yaml
index f5c1444a2..7833d90a2 100644
--- a/deploy/overlays/webhook/role.yaml
+++ b/deploy/overlays/webhook/role.yaml
@@ -1,3 +1,3 @@
 ---
-- path: /rules/2
+- path: /rules/3
   op: remove
diff --git a/deploy/webhook-operator.yaml b/deploy/webhook-operator.yaml
index eecb690a1..cad32631d 100644
--- a/deploy/webhook-operator.yaml
+++ b/deploy/webhook-operator.yaml
@@ -2154,6 +2154,14 @@ metadata:
     app: security-profiles-operator
   name: security-profiles-operator
 rules:
+- apiGroups:
+  - ""
+  resourceNames:
+  - security-profiles-operator-profile
+  resources:
+  - configmaps
+  verbs:
+  - get
 - apiGroups:
   - ""
   resources:
@@ -2893,6 +2901,17 @@ data:
         }
       ]
     }
+  selinuxd-image-mapping.json: |
+    [
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el8:latest"
+        },
+        {
+            "regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+            "image":"quay.io/security-profiles-operator/selinuxd-el9:latest"
+        }
+    ]
   selinuxd.cil: |
     (block selinuxd
         (blockinherit container)
diff --git a/internal/pkg/cli/runner/runner.go b/internal/pkg/cli/runner/runner.go
index 276407eff..3606e9702 100644
--- a/internal/pkg/cli/runner/runner.go
+++ b/internal/pkg/cli/runner/runner.go
@@ -25,6 +25,7 @@ import (
 	"log"
 	"path/filepath"
 	"sync/atomic"
+	"time"
 
 	"github.com/nxadm/tail"
 	"github.com/opencontainers/runtime-spec/specs-go"
@@ -103,6 +104,9 @@ func (r *Runner) Run() error {
 		return fmt.Errorf("wait for command: %w", err)
 	}
 
+	// Wait for the late syscalls from the audit logs.
+	time.Sleep(time.Second)
+
 	return nil
 }
 
diff --git a/internal/pkg/manager/spod/setup.go b/internal/pkg/manager/spod/setup.go
index 9c09c1a6f..d8a9ee735 100644
--- a/internal/pkg/manager/spod/setup.go
+++ b/internal/pkg/manager/spod/setup.go
@@ -26,6 +26,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
@@ -120,15 +121,11 @@ func isStaticWebhook(ctx context.Context) bool {
 }
 
 func (r *ReconcileSPOd) getTunables(ctx context.Context) (*daemonTunables, error) {
+	var err error
+
 	dt := &daemonTunables{}
 	dt.watchNamespace = os.Getenv(config.RestrictNamespaceEnvKey)
 
-	selinuxdImage := os.Getenv(selinuxdImageKey)
-	if selinuxdImage == "" {
-		return dt, errors.New("invalid selinuxd image")
-	}
-	dt.selinuxdImage = selinuxdImage
-
 	rbacProxyImage := os.Getenv(rbacProxyImageKey)
 	if rbacProxyImage == "" {
 		return dt, errors.New("invalid rbac proxy image")
@@ -146,10 +143,45 @@ func (r *ReconcileSPOd) getTunables(ctx context.Context) (*daemonTunables, error
 	}
 	dt.seccompLocalhostProfile = util.GetSeccompLocalhostProfilePath(node)
 	dt.containerRuntime = util.GetContainerRuntime(node)
+	dt.selinuxdImage, err = r.getSelinuxdImage(ctx, node)
+	if err != nil {
+		return dt, fmt.Errorf("could not determine selinuxd image: %w", err)
+	}
 
 	return dt, nil
 }
 
+func (r *ReconcileSPOd) getSelinuxdImage(ctx context.Context, node *corev1.Node) (string, error) {
+	var operatorCm corev1.ConfigMap
+	operatorCmName := types.NamespacedName{
+		Namespace: config.GetOperatorNamespace(),
+		Name:      util.OperatorConfigMap,
+	}
+
+	if err := r.clientReader.Get(ctx, operatorCmName, &operatorCm); err != nil {
+		return "", err
+	}
+	selinuxdImageMapping := operatorCm.Data[util.SelinuxdImageMappingKey]
+
+	selinuxdImage, err := util.MatchSelinuxdImageJSONMapping(node, []byte(selinuxdImageMapping))
+	if err != nil {
+		return "", fmt.Errorf("matching selinuxd image: %w", err)
+	}
+
+	if selinuxdImage != "" {
+		r.log.Info("matched selinuxd image against nodeInfo", "image", selinuxdImage)
+		return selinuxdImage, nil
+	}
+
+	selinuxdImage = os.Getenv(selinuxdImageKey)
+	if selinuxdImage != "" {
+		r.log.Info("using selinuxd image from envVar", "image", selinuxdImage)
+		return selinuxdImage, nil
+	}
+
+	return "", errors.New("invalid selinuxd image")
+}
+
 func getEffectiveSPOd(dt *daemonTunables) *appsv1.DaemonSet {
 	refSPOd := bindata.Manifest.DeepCopy()
 	refSPOd.SetNamespace(config.GetOperatorNamespace())
diff --git a/internal/pkg/manager/spod/spod_controller.go b/internal/pkg/manager/spod/spod_controller.go
index 9197d4fa5..c6e88c2b9 100644
--- a/internal/pkg/manager/spod/spod_controller.go
+++ b/internal/pkg/manager/spod/spod_controller.go
@@ -127,6 +127,9 @@ func (r *ReconcileSPOd) Healthz(*http.Request) error {
 //
 // Needed to detect which runtime is active
 // +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;get
+//
+// Needed to detect the proper selinux image
+// +kubebuilder:rbac:groups="",resources=configmaps,resourceNames=security-profiles-operator-profile,verbs=get
 
 // Reconcile reads that state of the cluster for a SPOD object and makes changes based on the state read
 // and what is in the `ConfigMap.Spec`.
diff --git a/internal/pkg/util/constants.go b/internal/pkg/util/constants.go
index 53d20975d..6e920cc09 100644
--- a/internal/pkg/util/constants.go
+++ b/internal/pkg/util/constants.go
@@ -28,3 +28,11 @@ const (
 	EventTypeNormal  string = "Normal"
 	EventTypeWarning string = "Warning"
 )
+
+const (
+	// OperatorConfigMap corresponds to the configMap created from deploy/base/profiles.
+	OperatorConfigMap = "security-profiles-operator-profile"
+	// SelinuxdImageMappingKey is the key in the configMap that contains the mapping
+	// between the selinuxd image and the OS version.
+	SelinuxdImageMappingKey = "selinuxd-image-mapping.json"
+)
diff --git a/internal/pkg/util/kubernetes.go b/internal/pkg/util/kubernetes.go
index 757651781..722e9c3b0 100644
--- a/internal/pkg/util/kubernetes.go
+++ b/internal/pkg/util/kubernetes.go
@@ -18,9 +18,11 @@ package util
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"os"
 	"path"
+	"regexp"
 	"strings"
 
 	"golang.org/x/mod/semver"
@@ -92,3 +94,35 @@ func GetKubeletDirFromNodeLabel(ctx context.Context, c client.Reader) (string, e
 	}
 	return "", fmt.Errorf("no %s label found on node %s", config.KubeletDirNodeLabelKey, nodeName)
 }
+
+type selinuxdImageMap struct {
+	Regex string `json:"regex"`
+	Image string `json:"image"`
+}
+
+func MatchSelinuxdImageJSONMapping(node *corev1.Node, mappingObj []byte) (string, error) {
+	var mapping []selinuxdImageMap
+
+	if err := json.Unmarshal(mappingObj, &mapping); err != nil {
+		return "", err
+	}
+
+	return matchSelinuxdImage(node, mapping), nil
+}
+
+func matchSelinuxdImage(node *corev1.Node, mapping []selinuxdImageMap) string {
+	if node == nil {
+		return ""
+	}
+
+	for _, m := range mapping {
+		if m.Regex == "" {
+			continue
+		}
+		if matched, err := regexp.MatchString(m.Regex, node.Status.NodeInfo.OSImage); err == nil && matched {
+			return m.Image
+		}
+	}
+
+	return ""
+}


Test patch:
diff --git a/internal/pkg/util/kubernetes_test.go b/internal/pkg/util/kubernetes_test.go
index e383b3d23..697caadd2 100644
--- a/internal/pkg/util/kubernetes_test.go
+++ b/internal/pkg/util/kubernetes_test.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package util_test
+package util
 
 import (
 	"path"
@@ -25,7 +25,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod/bindata"
-	"sigs.k8s.io/security-profiles-operator/internal/pkg/util"
 )
 
 func TestGetSeccompLocalhostProfilePath(t *testing.T) {
@@ -86,7 +85,7 @@ func TestGetSeccompLocalhostProfilePath(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			got := util.GetSeccompLocalhostProfilePath(tt.node)
+			got := GetSeccompLocalhostProfilePath(tt.node)
 			require.Equal(t, tt.want, got)
 		})
 	}
@@ -131,7 +130,7 @@ func TestGetContainerRuntime(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			got := util.GetContainerRuntime(tt.node)
+			got := GetContainerRuntime(tt.node)
 			require.Equal(t, tt.want, got)
 		})
 	}
@@ -160,9 +159,74 @@ func TestGetVersion(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			got := util.GetVersion(tt.node)
+			got := GetVersion(tt.node)
 			require.True(t, semver.IsValid(got), "should return a valid version")
 			require.Equal(t, tt.want, got)
 		})
 	}
 }
+
+func TestMatchSelinuxdImageVersion(t *testing.T) {
+	t.Parallel()
+
+	mappingJSON := `[
+		{
+			"regex":"(.*)(CoreOS).*([\\d+])\\.8[\\d+]\\.(.*)",
+			"image":"quay.io/security-profiles-operator/selinuxd:el8"
+		},
+		{
+			"regex":"(.*)(CoreOS).*([\\d+])\\.9[\\d+]\\.(.*)",
+			"image":"quay.io/security-profiles-operator/selinuxd:el9"
+		}
+	]`
+
+	tests := []struct {
+		name string
+		node *corev1.Node
+		want string
+	}{
+		{
+			name: "Should return el8",
+			node: &corev1.Node{
+				Status: corev1.NodeStatus{
+					NodeInfo: corev1.NodeSystemInfo{
+						OSImage: "Red Hat Enterprise Linux CoreOS 411.86.202212072103-0 (Ootpa)",
+					},
+				},
+			},
+			want: "quay.io/security-profiles-operator/selinuxd:el8",
+		},
+		{
+			name: "Should return el9",
+			node: &corev1.Node{
+				Status: corev1.NodeStatus{
+					NodeInfo: corev1.NodeSystemInfo{
+						OSImage: "CentOS Stream CoreOS 413.92.202303061740-0 (Plow)",
+					},
+				},
+			},
+			want: "quay.io/security-profiles-operator/selinuxd:el9",
+		},
+		{
+			name: "Does not match anything",
+			node: &corev1.Node{
+				Status: corev1.NodeStatus{
+					NodeInfo: corev1.NodeSystemInfo{
+						OSImage: "Some other OS",
+					},
+				},
+			},
+			want: "",
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := MatchSelinuxdImageJSONMapping(tt.node, []byte(mappingJSON))
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}