diff --git a/internal/pkg/manager/workloadannotator/workloadannotator.go b/internal/pkg/manager/workloadannotator/workloadannotator.go index ef3a35284c..64641e20d4 100644 --- a/internal/pkg/manager/workloadannotator/workloadannotator.go +++ b/internal/pkg/manager/workloadannotator/workloadannotator.go @@ -26,11 +26,13 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/scheme" + profilerecordingapi "sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1" seccompprofileapi "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1beta1" selinuxprofileapi "sigs.k8s.io/security-profiles-operator/api/selinuxprofile/v1alpha2" "sigs.k8s.io/security-profiles-operator/internal/pkg/controller" @@ -98,6 +100,7 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req reconcile.Request) (r if errors.IsNotFound(err) { // this is a pod deletion, so update all seccomp/selinux profiles that were using it seccompProfiles := &seccompprofileapi.SeccompProfileList{} selinuxProfiles := &selinuxprofileapi.SelinuxProfileList{} + profileRecordings := &profilerecordingapi.ProfileRecordingList{} if err = r.client.List(ctx, seccompProfiles, client.MatchingFields{linkedPodsKey: podID}); err != nil { return reconcile.Result{}, fmt.Errorf("listing SeccompProfiles for deleted pod: %w", err) @@ -107,6 +110,10 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req reconcile.Request) (r return reconcile.Result{}, fmt.Errorf("listing SelinuxProfiles for deleted pod: %w", err) } + if err = r.client.List(ctx, profileRecordings, client.MatchingFields{linkedPodsKey: podID}); err != nil { + return reconcile.Result{}, fmt.Errorf("listing ProfileRecordings for deleted pod: %w", err) + } + for i := range seccompProfiles.Items { if err = r.updatePodReferencesForSeccomp(ctx, &seccompProfiles.Items[i]); err != nil { return reconcile.Result{}, fmt.Errorf("updating SeccompProfile for deleted pod: %w", err) @@ -119,6 +126,12 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req reconcile.Request) (r } } + for k := range profileRecordings.Items { + if err = r.updatePodReferencesForProfileRecording(ctx, &profileRecordings.Items[k]); err != nil { + return reconcile.Result{}, fmt.Errorf("updating ProfileRecording for deleted pod: %w", err) + } + } + return reconcile.Result{}, nil } @@ -271,6 +284,60 @@ func (r *PodReconciler) updatePodReferencesForSelinux(ctx context.Context, se *s return nil } +// updatePodReferencesForProfileRecording updates a ProfileRecording with the identifiers of pods using it and ensures +// it has a finalizer indicating it is in use to prevent it from being deleted. +func (r *PodReconciler) updatePodReferencesForProfileRecording( + ctx context.Context, pr *profilerecordingapi.ProfileRecording, +) error { + // we list the pods and not the workloads, because the workloads are just strings + // and we can't query for them directly + selector, err := metav1.LabelSelectorAsSelector(&pr.Spec.PodSelector) + if err != nil { + return fmt.Errorf("creating selector: %w", err) + } + + linkedPods := &corev1.PodList{} + if err := r.client.List(ctx, linkedPods, client.InNamespace(pr.GetNamespace()), client.MatchingLabelsSelector{Selector: selector}); err != nil { + return fmt.Errorf("listing pods to update profileRecording: %w", err) + } + + podList := make([]string, len(linkedPods.Items)) + for i := range linkedPods.Items { + pod := linkedPods.Items[i] + podList[i] = pod.GetName() + } + + if err := util.Retry(func() error { + pr.Status.ActiveWorkloads = podList + updateErr := r.client.Status().Update(ctx, pr) + if updateErr != nil { + if err := r.client.Get(ctx, util.NamespacedName(pr.GetName(), pr.GetNamespace()), pr); err != nil { + return fmt.Errorf("retrieving profile: %w", err) + } + return fmt.Errorf("updating profile: %w", updateErr) + } + return nil + }, util.IsNotFoundOrConflict); err != nil { + return fmt.Errorf("updating ProfileRecording status: %w", err) + } + + if len(linkedPods.Items) > 0 { + if err := util.Retry(func() error { + return util.AddFinalizer(ctx, r.client, pr, util.HasActivePodsFinalizerString) + }, util.IsNotFoundOrConflict); err != nil { + return fmt.Errorf("adding finalizer: %w", err) + } + } else { + if err := util.Retry(func() error { + return util.RemoveFinalizer(ctx, r.client, pr, util.HasActivePodsFinalizerString) + }, util.IsNotFoundOrConflict); err != nil { + return fmt.Errorf("removing finalizer: %w", err) + } + } + + return nil +} + // getSeccompProfilesFromPod returns a slice of strings representing seccomp profiles required by the pod. // It looks first at the pod spec level, then in each container and init container, then in the annotations. func getSeccompProfilesFromPod(pod *corev1.Pod) []string {