Repository: kubernetes-sigs/security-profiles-operator (c)

Issue:
Limit the `spo.x-k8s.io/profile-name` label content length or limit the profile name length to make them usable for labels
<!-- Please only use this template for submitting feature requests -->

#### What would you like to be added:
Limit the profile name length to 63 characters. Other ways of solving the problem described below are acceptable, too..

#### Why is this needed:
We added the `spo.x-k8s.io/profile-name` label (`StatusToProfLabel` in the code) as a label of the node statuses to be able to list node statuses per profile. This is useful, but the profile names are not limited and at the same time k8s labels are limited to 63 characters. If someone creates a profile with a longer name, the label wouldn't be assigned.

#### User story covered
As an application SRE, Rajesh needs to be able to see the state of the installed profile(s) and verify that it has indeed been installed on the system.

<!-- Please add a reference to the user story that this feature would cover

The existing user stories exist in docs/user-stores.md

If the user story doesn't exist yet, please add it to the main document
to help people understand what the feature is trying to solve and how it's
meant to help the target personas.

If this would help a target persona that's not currently listed, please add it
to the document in docs/personas.md
 -->


Pull request:
secprofnodestatus: Link the node profile status with profile using a custom label
<!--  Thanks for sending a pull request!  Here are some tips for you:

- If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
- Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
- If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
- If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?
/kind cleanup

<!--
Add one of the following kinds:
/kind bug
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

#### What this PR does / why we need it:
We were using the profile name as the value of the spo.x-k8s.io/profile-name
label to be able to list node profile statuses for a profile. This is
user-friendly but would fail in case the profile name was longer than 64
characters as that's the label size limit.

Instead, let's rename the label to spo.x-k8s.io/profile-id. In case the
profile name is shorter than the limit, let's still use the name because
it's user-friendly, but in case the name is longer, let's instead use a
sha1 hash of the name with the profile Kind as a prefix.

Using sha1 is OK in this case, because we're not using it for any crypto
purposes, just to shorten a name.


#### Which issue(s) this PR fixes:
Fixes #408

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.


or

None
-->

#### Does this PR have test?
Yes

<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
N/A

#### 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
The securityprofilenodestatus CR now links with the security profile its status
it represents using label spo.x-k8s.io/profile-id. If the profile name is less
than 64 characters long, then the label value is the profile name, otherwise it's
kind-sha1hashofthename.

This change supports profile names whose names are over 64 characters.

```

Patch:
diff --git a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
index dcacacde8..99b91cd20 100644
--- a/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
+++ b/api/secprofnodestatus/v1alpha1/secprofnodestatus_types.go
@@ -41,8 +41,9 @@ const (
 
 // Common labels of the node status objects.
 const (
-	// Identifies the profile by name so that the admin can list all node statuses for a certain profile.
-	StatusToProfLabel = "spo.x-k8s.io/profile-name"
+	// StatusToProfLabel identifies the profile by name, or if the name is too long, by a hash so that
+	// the admin can list all node statuses for a certain profile.
+	StatusToProfLabel = "spo.x-k8s.io/profile-id"
 	// Identifies the node on which the profile is installed so that the admin can list profiles per node.
 	StatusToNodeLabel = "spo.x-k8s.io/node-name"
 	// Allows the admin to filter out node statuses with a certain state (e.g. show me all that failed).
diff --git a/go.mod b/go.mod
index 09616decc..3fba2c9e3 100644
--- a/go.mod
+++ b/go.mod
@@ -25,9 +25,9 @@ require (
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
-	k8s.io/api v0.22.3
-	k8s.io/apimachinery v0.22.3
-	k8s.io/client-go v0.22.3
+	k8s.io/api v0.22.4
+	k8s.io/apimachinery v0.22.4
+	k8s.io/client-go v0.22.4
 	k8s.io/klog/v2 v2.10.0
 	sigs.k8s.io/controller-runtime v0.10.3
 	sigs.k8s.io/controller-tools v0.7.0
diff --git a/go.sum b/go.sum
index 4e1ce461c..a8e569824 100644
--- a/go.sum
+++ b/go.sum
@@ -1603,8 +1603,9 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
 k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
 k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
 k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
-k8s.io/api v0.22.3 h1:wOoES2GoSkUsdped2RB4zYypPqWtvprGoKCENTOOjP4=
 k8s.io/api v0.22.3/go.mod h1:azgiXFiXqiWyLCfI62/eYBOu19rj2LKmIhFPP4+33fs=
+k8s.io/api v0.22.4 h1:UvyHW0ezB2oIgHAxlYoo6UJQObYXU7awuNarwoHEOjw=
+k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk=
 k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
 k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA=
 k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
@@ -1616,8 +1617,9 @@ k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp
 k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
 k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
 k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
-k8s.io/apimachinery v0.22.3 h1:mrvBG5CZnEfwgpVqWcrRKvdsYECTrhAR6cApAgdsflk=
 k8s.io/apimachinery v0.22.3/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
+k8s.io/apimachinery v0.22.4 h1:9uwcvPpukBw/Ri0EUmWz+49cnFtaoiyEhQTK+xOe7Ck=
+k8s.io/apimachinery v0.22.4/go.mod h1:yU6oA6Gnax9RrxGzVvPFFJ+mpnW6PBSqp0sx0I0HHW0=
 k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
 k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
 k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
@@ -1631,8 +1633,9 @@ k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
 k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
 k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
 k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
-k8s.io/client-go v0.22.3 h1:6onkOSc+YNdwq5zXE0wFXicq64rrym+mXwHu/CPVGO4=
 k8s.io/client-go v0.22.3/go.mod h1:ElDjYf8gvZsKDYexmsmnMQ0DYO8W9RwBjfQ1PI53yow=
+k8s.io/client-go v0.22.4 h1:aAQ1Wk+I3bjCNk35YWUqbaueqrIonkfDPJSPDDe8Kfg=
+k8s.io/client-go v0.22.4/go.mod h1:Yzw4e5e7h1LNHA4uqnMVrpEpUs1hJOiuBsJKIlRCHDA=
 k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
 k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U=
 k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
@@ -1667,8 +1670,9 @@ k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
 k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
-k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
 k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
+k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c h1:jvamsI1tn9V0S8jicyX82qaFC0H/NKxv2e5mbqsgR80=
+k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
diff --git a/internal/pkg/daemon/selinuxprofile/selinuxprofile.go b/internal/pkg/daemon/selinuxprofile/selinuxprofile.go
index a3d726c97..3b5018761 100644
--- a/internal/pkg/daemon/selinuxprofile/selinuxprofile.go
+++ b/internal/pkg/daemon/selinuxprofile/selinuxprofile.go
@@ -18,6 +18,7 @@ package selinuxprofile
 
 import (
 	"context"
+	"regexp"
 
 	"github.com/pkg/errors"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -32,7 +33,13 @@ import (
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/translator"
 )
 
-var ErrUnknownKindForEntry = errors.New("unknown inherit kind for entry")
+var (
+	ErrInvalidLabelKey         = errors.New("invalid label key")
+	ErrInvalidObjClass         = errors.New("invalid object class")
+	ErrInvalidPermission       = errors.New("invalid permission")
+	ErrSystemInheritNotAllowed = errors.New("system profile not allowed")
+	ErrUnknownKindForEntry     = errors.New("unknown inherit kind for entry")
+)
 
 // NewController returns a new empty controller instance.
 func NewController() controller.Controller {
@@ -52,10 +59,12 @@ func selinuxProfileControllerBuild(b *ctrl.Builder, r reconcile.Reconciler) erro
 var _ SelinuxObjectHandler = &selinuxProfileHandler{}
 
 type selinuxProfileHandler struct {
-	sp             *selxv1alpha2.SelinuxProfile
-	cli            client.Client
-	systemInherits []string
-	objInherits    []selxv1alpha2.SelinuxProfileObject
+	sp                *selxv1alpha2.SelinuxProfile
+	cli               client.Client
+	systemInherits    []string
+	objInherits       []selxv1alpha2.SelinuxProfileObject
+	labelRegex        *regexp.Regexp
+	objClassPermRegex *regexp.Regexp
 }
 
 func (sph *selinuxProfileHandler) Init(
@@ -67,8 +76,23 @@ func (sph *selinuxProfileHandler) Init(
 	if sph.cli == nil {
 		sph.cli = cli
 	}
-	err := sph.cli.Get(ctx, key, sph.sp)
-	return err
+	// initiate the SelinuxProfile object
+	if err := sph.cli.Get(ctx, key, sph.sp); err != nil {
+		return err
+	}
+
+	// Matches alpha numerical names in upper and lower-case, as well as
+	// dashes and underscores. @self is also allowed explicitly.
+	// Must be at least one character.
+	// The characters must match from beginning to end of the string
+	sph.labelRegex = regexp.MustCompile(`^([a-zA-Z0-9.\-_]+|@self)$`)
+
+	// Matches alpha numerical names in upper and lower-case, as well as
+	// dashes and underscores.
+	// Must be at least one character.
+	// The characters must match from beginning to end of the string
+	sph.objClassPermRegex = regexp.MustCompile(`^[a-zA-Z0-9.\-_]+$`)
+	return nil
 }
 
 func (sph *selinuxProfileHandler) GetProfileObject() selxv1alpha2.SelinuxProfileObject {
@@ -82,6 +106,22 @@ func (sph *selinuxProfileHandler) Validate() error {
 			return err
 		}
 	}
+
+	for key, classperms := range sph.sp.Spec.Allow {
+		if err := sph.validateLabelKey(key); err != nil {
+			return err
+		}
+		for objclass, perms := range classperms {
+			if err := sph.validateObjClass(objclass); err != nil {
+				return err
+			}
+			for _, perm := range perms {
+				if err := sph.validatePermission(perm); err != nil {
+					return err
+				}
+			}
+		}
+	}
 	return nil
 }
 
@@ -99,6 +139,33 @@ func (sph *selinuxProfileHandler) validateAndTrackInherit(
 	return errors.Wrapf(ErrUnknownKindForEntry, "%s/%s", ancestorRef.Kind, ancestorRef.Name)
 }
 
+func (sph *selinuxProfileHandler) validateLabelKey(
+	key selxv1alpha2.LabelKey,
+) error {
+	if !sph.labelRegex.MatchString(string(key)) {
+		return errors.Wrapf(ErrInvalidLabelKey, "'%s' didn't match expected characters", key)
+	}
+	return nil
+}
+
+func (sph *selinuxProfileHandler) validateObjClass(
+	key selxv1alpha2.ObjectClassKey,
+) error {
+	if !sph.objClassPermRegex.MatchString(string(key)) {
+		return errors.Wrapf(ErrInvalidObjClass, "'%s' didn't match expected characters", key)
+	}
+	return nil
+}
+
+func (sph *selinuxProfileHandler) validatePermission(
+	perm string,
+) error {
+	if !sph.objClassPermRegex.MatchString(perm) {
+		return errors.Wrapf(ErrInvalidPermission, "'%s' didn't match expected characters", perm)
+	}
+	return nil
+}
+
 func (sph *selinuxProfileHandler) handleInheritSPOPolicy(
 	ancestorRef selxv1alpha2.PolicyRef,
 	namespace string,
@@ -132,7 +199,7 @@ func (sph *selinuxProfileHandler) handleInheritSystemPolicy(
 			return nil
 		}
 	}
-	return errors.Wrapf(err, "system profile %s not in SecurityProfilesOperatorDaemon's allow list",
+	return errors.Wrapf(ErrSystemInheritNotAllowed, "system profile %s not in SecurityProfilesOperatorDaemon's allow list",
 		ancestorRef.Name)
 }
 
diff --git a/internal/pkg/manager/nodestatus/nodestatus.go b/internal/pkg/manager/nodestatus/nodestatus.go
index 3e74ae01d..285947e2e 100644
--- a/internal/pkg/manager/nodestatus/nodestatus.go
+++ b/internal/pkg/manager/nodestatus/nodestatus.go
@@ -133,16 +133,6 @@ func (r *StatusReconciler) Reconcile(_ context.Context, req reconcile.Request) (
 		"Profile.Kind", prof.GetObjectKind().GroupVersionKind(),
 	)
 
-	// get all the other statuses
-	profName := instance.Labels[statusv1alpha1.StatusToProfLabel]
-	if profName == "" {
-		return reconcile.Result{}, errors.New("Unlabeled node status")
-	}
-
-	if prof.GetName() != profName {
-		return reconcile.Result{}, errors.New("status doesn't match owner")
-	}
-
 	// Initialize status if it hasn't happened already
 	if prof.GetStatusBase().Status == "" {
 		lprof.Info("Initializing Profile status")
@@ -154,7 +144,17 @@ func (r *StatusReconciler) Reconcile(_ context.Context, req reconcile.Request) (
 		return r.reconcileStatus(ctx, prof, targetStatus, lprof)
 	}
 
-	nodeStatusList, err := listStatusesForProfile(ctx, r.client, profName)
+	// get all the other statuses
+	profLabel := instance.Labels[statusv1alpha1.StatusToProfLabel]
+	if profLabel == "" {
+		return reconcile.Result{}, errors.New("Unlabeled node status")
+	}
+
+	if util.KindBasedDNSLengthName(prof) != instance.Labels[statusv1alpha1.StatusToProfLabel] {
+		return reconcile.Result{}, errors.New("status doesn't match owner")
+	}
+
+	nodeStatusList, err := listStatusesForProfile(ctx, r.client, profLabel)
 	if err != nil {
 		return reconcile.Result{}, errors.Wrap(err, "cannot list the node statuses")
 	}
@@ -292,11 +292,11 @@ func daemonSetIsUpdating(ds *appsv1.DaemonSet) bool {
 }
 
 func listStatusesForProfile(
-	ctx context.Context, c client.Client, polName string,
+	ctx context.Context, c client.Client, labelVal string,
 ) (*statusv1alpha1.SecurityProfileNodeStatusList, error) {
 	statusList := statusv1alpha1.SecurityProfileNodeStatusList{}
 	allStatusesForProf := client.MatchingLabels{
-		statusv1alpha1.StatusToProfLabel: polName,
+		statusv1alpha1.StatusToProfLabel: labelVal,
 	}
 
 	if err := c.List(ctx, &statusList, allStatusesForProf); err != nil {
diff --git a/internal/pkg/nodestatus/nodestatus.go b/internal/pkg/nodestatus/nodestatus.go
index 163d032b6..739a1a72e 100644
--- a/internal/pkg/nodestatus/nodestatus.go
+++ b/internal/pkg/nodestatus/nodestatus.go
@@ -65,6 +65,10 @@ func (nsf *StatusClient) Create(ctx context.Context) error {
 		return errors.Wrapf(err, "cannot create finalizer for %s", nsf.pol.GetName())
 	}
 
+	if err := nsf.createPolLabel(ctx); err != nil {
+		return errors.Wrapf(err, "cannot create policy name label for %s", nsf.pol.GetName())
+	}
+
 	// if object does not exist, add it
 	if err := nsf.createNodeStatus(ctx); err != nil {
 		return errors.Wrapf(err, "cannot create node status for %s", nsf.pol.GetName())
@@ -78,6 +82,25 @@ func (nsf *StatusClient) createFinalizer(ctx context.Context) error {
 	}, util.IsNotFoundOrConflict)
 }
 
+func (nsf *StatusClient) createPolLabel(ctx context.Context) error {
+	return util.Retry(func() error {
+		labels := nsf.pol.GetLabels()
+		if labels == nil {
+			labels = make(map[string]string)
+		}
+
+		if _, ok := labels[secprofnodestatusv1alpha1.StatusToProfLabel]; ok {
+			// the label is already set, nothing to do
+			return nil
+		}
+
+		labels[secprofnodestatusv1alpha1.StatusToProfLabel] = util.KindBasedDNSLengthName(nsf.pol)
+		nsf.pol.SetLabels(labels)
+
+		return nsf.client.Update(ctx, nsf.pol)
+	}, util.IsNotFoundOrConflict)
+}
+
 func (nsf *StatusClient) statusObj(
 	polState secprofnodestatusv1alpha1.ProfileState,
 ) *secprofnodestatusv1alpha1.SecurityProfileNodeStatus {
@@ -86,7 +109,7 @@ func (nsf *StatusClient) statusObj(
 			Name:      nsf.perNodeStatusName(),
 			Namespace: nsf.pol.GetNamespace(),
 			Labels: map[string]string{
-				secprofnodestatusv1alpha1.StatusToProfLabel: nsf.pol.GetName(),
+				secprofnodestatusv1alpha1.StatusToProfLabel: util.KindBasedDNSLengthName(nsf.pol),
 				secprofnodestatusv1alpha1.StatusToNodeLabel: nsf.nodeName,
 				secprofnodestatusv1alpha1.StatusStateLabel:  string(polState),
 				secprofnodestatusv1alpha1.StatusKindLabel:   nsf.pol.GetObjectKind().GroupVersionKind().Kind,
diff --git a/internal/pkg/util/names.go b/internal/pkg/util/names.go
index 6e1acba8d..29df282c5 100644
--- a/internal/pkg/util/names.go
+++ b/internal/pkg/util/names.go
@@ -16,7 +16,15 @@ limitations under the License.
 
 package util
 
-import "k8s.io/apimachinery/pkg/types"
+import (
+	"crypto/sha256"
+	"fmt"
+	"io"
+
+	"github.com/pkg/errors"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
 
 func NamespacedName(name, namespace string) types.NamespacedName {
 	return types.NamespacedName{
@@ -34,3 +42,44 @@ func Contains(a []string, b string) bool {
 	}
 	return false
 }
+
+// LengthName creates a string of maximum defined length.
+func lengthName(maxLen int, hashPrefix, format string, a ...interface{}) (string, error) {
+	friendlyName := fmt.Sprintf(format, a...)
+	if len(friendlyName) < maxLen {
+		return friendlyName, nil
+	}
+
+	// If that's too long, just hash the name. It's not very user friendly, but whatever
+	hasher := sha256.New()
+	if _, err := io.WriteString(hasher, friendlyName); err != nil {
+		return "", errors.Wrap(err, "writing string")
+	}
+
+	hashStr := fmt.Sprintf("%x", hasher.Sum(nil))
+	hashUseLen := maxLen - len(hashPrefix) - 1 // -1 for the dash separator
+	if hashUseLen < maxLen {
+		hashStr = hashStr[:hashUseLen]
+	}
+	hashedName := fmt.Sprintf("%s-%s", hashPrefix, hashStr)
+
+	if len(hashedName) > maxLen {
+		return "", errors.New("shortening string")
+	}
+	return hashedName, nil
+}
+
+func dnsLengthName(hashPrefix, format string, a ...interface{}) string {
+	const maxDNSLen = 63
+
+	// (jhrozek): I think it makes sense to make the utility function return
+	// error, but here I think it's OK to just ignore
+	// nolint:errcheck
+	name, _ := lengthName(maxDNSLen, hashPrefix, format, a...)
+	return name
+}
+
+func KindBasedDNSLengthName(obj client.Object) string {
+	kind := obj.GetObjectKind().GroupVersionKind().Kind
+	return dnsLengthName(kind, "%s-%s", kind, obj.GetName())
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 5186783d1..eda726b37 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -481,7 +481,7 @@ gopkg.in/warnings.v0
 gopkg.in/yaml.v2
 # gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 gopkg.in/yaml.v3
-# k8s.io/api v0.22.3
+# k8s.io/api v0.22.4
 ## explicit
 k8s.io/api/admission/v1
 k8s.io/api/admission/v1beta1
@@ -532,7 +532,7 @@ k8s.io/api/storage/v1beta1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
-# k8s.io/apimachinery v0.22.3
+# k8s.io/apimachinery v0.22.4
 ## explicit
 k8s.io/apimachinery/pkg/api/equality
 k8s.io/apimachinery/pkg/api/errors
@@ -581,7 +581,7 @@ k8s.io/apimachinery/pkg/version
 k8s.io/apimachinery/pkg/watch
 k8s.io/apimachinery/third_party/forked/golang/json
 k8s.io/apimachinery/third_party/forked/golang/reflect
-# k8s.io/client-go v0.22.3
+# k8s.io/client-go v0.22.4
 ## explicit
 k8s.io/client-go/applyconfigurations/admissionregistration/v1
 k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1
@@ -711,7 +711,7 @@ k8s.io/component-base/config/v1alpha1
 ## explicit
 k8s.io/klog/v2
 k8s.io/klog/v2/klogr
-# k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
+# k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c
 k8s.io/kube-openapi/pkg/util/proto
 # k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a
 k8s.io/utils/buffer


Test patch:
diff --git a/internal/pkg/daemon/selinuxprofile/selinuxprofile_test.go b/internal/pkg/daemon/selinuxprofile/selinuxprofile_test.go
index 46b632aa8..521eb944c 100644
--- a/internal/pkg/daemon/selinuxprofile/selinuxprofile_test.go
+++ b/internal/pkg/daemon/selinuxprofile/selinuxprofile_test.go
@@ -103,6 +103,11 @@ func Test_selinuxProfileHandler(t *testing.T) {
 								"open",
 							},
 						},
+						"@self": {
+							"tcp_socket": []string{
+								"listen",
+							},
+						},
 					},
 				},
 			},
@@ -230,7 +235,7 @@ func Test_selinuxProfileHandler(t *testing.T) {
 			},
 			wantValidateErr: true,
 			wantErrMatches: []string{
-				"unknown inherit kind for entry: InvalidKind/foo",
+				"InvalidKind/foo: unknown inherit kind for entry",
 			},
 			existingObjs: []client.Object{
 				spodinstance.DeepCopy(),
@@ -265,6 +270,96 @@ func Test_selinuxProfileHandler(t *testing.T) {
 				"\"test-selinux-recording-nginx\" not found",
 			},
 		},
+		{
+			name: "Test validate injection through label key",
+			profile: &selxv1alpha2.SelinuxProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "bar",
+				},
+				Spec: selxv1alpha2.SelinuxProfileSpec{
+					Allow: selxv1alpha2.Allow{
+						"var_log_t) (typeattribute container_runtime_domain (process))": {
+							"dir": []string{
+								"open",
+								"read",
+								"getattr",
+								"lock",
+								"search",
+								"ioctl",
+								"add_name",
+								"remove_name",
+								"write",
+							},
+						},
+					},
+				},
+			},
+			wantValidateErr: true,
+			wantErrMatches: []string{
+				"didn't match expected characters: invalid label key",
+			},
+		},
+		{
+			name: "Test validate injection through object class key",
+			profile: &selxv1alpha2.SelinuxProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "bar",
+				},
+				Spec: selxv1alpha2.SelinuxProfileSpec{
+					Allow: selxv1alpha2.Allow{
+						"var_log_t": {
+							"dir) (typeattribute container_runtime_domain (process))": []string{
+								"open",
+								"read",
+								"getattr",
+								"lock",
+								"search",
+								"ioctl",
+								"add_name",
+								"remove_name",
+								"write",
+							},
+						},
+					},
+				},
+			},
+			wantValidateErr: true,
+			wantErrMatches: []string{
+				"didn't match expected characters: invalid object class",
+			},
+		},
+		{
+			name: "Test validate injection through object permission",
+			profile: &selxv1alpha2.SelinuxProfile{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "bar",
+				},
+				Spec: selxv1alpha2.SelinuxProfileSpec{
+					Allow: selxv1alpha2.Allow{
+						"var_log_t": {
+							"dir": []string{
+								"open",
+								"read",
+								"getattr",
+								"lock",
+								"search",
+								"ioctl",
+								"add_name",
+								"remove_name",
+								"write) (typeattribute container_runtime_domain (process))",
+							},
+						},
+					},
+				},
+			},
+			wantValidateErr: true,
+			wantErrMatches: []string{
+				"didn't match expected characters: invalid permission",
+			},
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
@@ -277,12 +372,10 @@ func Test_selinuxProfileHandler(t *testing.T) {
 			key := types.NamespacedName{Name: tt.profile.GetName(), Namespace: tt.profile.GetNamespace()}
 			sph, initerr := newSelinuxProfileHandler(context.TODO(), cli, key)
 
-			// There was an init error
 			if (initerr != nil) != tt.wantInitErr {
 				t.Errorf("newSelinuxProfileHandler() error = %v, wantErr %v", initerr, tt.wantInitErr)
 			}
-
-			// there's an expected error
+			// There was an expected error
 			if (initerr != nil) && tt.wantInitErr {
 				for _, wantMatch := range tt.wantErrMatches {
 					matched, matcherr := regexp.MatchString(wantMatch, initerr.Error())
@@ -292,24 +385,21 @@ func Test_selinuxProfileHandler(t *testing.T) {
 						t.Errorf("The error didn't match expectation.\nExpected match for: %s\nGot instead: %s", wantMatch, initerr)
 					}
 				}
+				return
 			}
 
-			// There's no init error
-			if initerr != nil {
-				valerr := sph.Validate()
-				if (valerr != nil) != tt.wantValidateErr {
-					t.Errorf("selinuxProfileHandler.Validate() error = %v, wantErr %v", valerr, tt.wantValidateErr)
-				}
-
-				// there's an expected error
-				if (valerr != nil) && tt.wantValidateErr {
-					for _, wantMatch := range tt.wantErrMatches {
-						matched, matcherr := regexp.MatchString(wantMatch, valerr.Error())
-						if matcherr != nil {
-							t.Errorf("failed matching the error to expected string: %s", matcherr)
-						} else if !matched {
-							t.Errorf("The error didn't match expectation.\nExpected match for: %s\nGot instead: %s", wantMatch, valerr)
-						}
+			valerr := sph.Validate()
+			if (valerr != nil) != tt.wantValidateErr {
+				t.Errorf("selinuxProfileHandler.Validate() error = %v, wantErr %v", valerr, tt.wantValidateErr)
+			}
+			// there's an expected error
+			if (valerr != nil) && tt.wantValidateErr {
+				for _, wantMatch := range tt.wantErrMatches {
+					matched, matcherr := regexp.MatchString(wantMatch, valerr.Error())
+					if matcherr != nil {
+						t.Errorf("failed matching the error to expected string: %s", matcherr)
+					} else if !matched {
+						t.Errorf("The error didn't match expectation.\nExpected match for: %s\nGot instead: %s", wantMatch, valerr)
 					}
 				}
 			}
diff --git a/internal/pkg/util/names_test.go b/internal/pkg/util/names_test.go
new file mode 100644
index 000000000..53f2bde2d
--- /dev/null
+++ b/internal/pkg/util/names_test.go
@@ -0,0 +1,79 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed 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 (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	seccompprofile "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1beta1"
+)
+
+func TestNameHashing(t *testing.T) {
+	t.Parallel()
+
+	cases := []struct {
+		name      string
+		prof      seccompprofile.SeccompProfile
+		labelName string
+	}{
+		{
+			name: "short name",
+			prof: seccompprofile.SeccompProfile{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "SeccompProfile",
+					APIVersion: "security-profiles-operator.x-k8s.io/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "shortname-profile",
+					Namespace: "security-profiles-operator",
+				},
+				Spec:   seccompprofile.SeccompProfileSpec{},
+				Status: seccompprofile.SeccompProfileStatus{},
+			},
+			labelName: "SeccompProfile-shortname-profile",
+		},
+		{
+			name: "long name",
+			prof: seccompprofile.SeccompProfile{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "SeccompProfile",
+					APIVersion: "security-profiles-operator.x-k8s.io/v1beta1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "this-is-a-very-long-name-surely-over-64-characters-omg-its-overflowing",
+					Namespace: "security-profiles-operator",
+				},
+				Spec:   seccompprofile.SeccompProfileSpec{},
+				Status: seccompprofile.SeccompProfileStatus{},
+			},
+			labelName: "SeccompProfile-9d42ecd8a72de861cc202ee69381e536088eec6dc43f8f8e",
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			name := KindBasedDNSLengthName(&tc.prof)
+			require.Equal(t, tc.labelName, name)
+		})
+	}
+}
diff --git a/test/e2e_test.go b/test/e2e_test.go
index b46ec3f5a..a1ecea9ab 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -114,6 +114,10 @@ func (e *e2e) TestSecurityProfilesOperator() {
 			"SPOD: Change verbosity",
 			e.testCaseVerbosityChange,
 		},
+		{
+			"Seccomp: make sure statuses for profiles with long names can be listed",
+			e.testCaseLongSeccompProfileName,
+		},
 	}
 	for _, testCase := range testCases {
 		tc := testCase
@@ -328,9 +332,9 @@ func (e *e2e) getSeccompProfile(name, namespace string) *seccompprofileapi.Secco
 }
 
 func (e *e2e) getSeccompProfileNodeStatus(
-	name, namespace, node string,
+	id, namespace, node string,
 ) *secprofnodestatusv1alpha1.SecurityProfileNodeStatus {
-	selector := fmt.Sprintf("spo.x-k8s.io/node-name=%s,spo.x-k8s.io/profile-name=%s", node, name)
+	selector := fmt.Sprintf("spo.x-k8s.io/node-name=%s,spo.x-k8s.io/profile-id=SeccompProfile-%s", node, id)
 	seccompProfileNodeStatusJSON := e.kubectl(
 		"-n", namespace, "get", "securityprofilenodestatus", "-l", selector, "-o", "json",
 	)
@@ -341,9 +345,9 @@ func (e *e2e) getSeccompProfileNodeStatus(
 }
 
 func (e *e2e) getAllSeccompProfileNodeStatuses(
-	name, namespace string,
+	id, namespace string,
 ) *secprofnodestatusv1alpha1.SecurityProfileNodeStatusList {
-	selector := fmt.Sprintf("spo.x-k8s.io/profile-name=%s", name)
+	selector := fmt.Sprintf("spo.x-k8s.io/profile-id=SeccompProfile-%s", id)
 	seccompProfileNodeStatusJSON := e.kubectl(
 		"-n", namespace, "get", "securityprofilenodestatus", "-l", selector, "-o", "json",
 	)
@@ -412,3 +416,8 @@ func (e *e2e) retryGet(args ...string) string {
 	e.Fail("Timed out to wait for resource")
 	return ""
 }
+
+func (e *e2e) getSeccompPolicyID(profile string) string {
+	ns := e.getCurrentContextNamespace(defaultNamespace)
+	return e.kubectl("get", "sp", "-n", ns, profile, "-o", "jsonpath={.metadata.labels.spo\\.x-k8s\\.io/profile-id}")
+}
diff --git a/test/tc_long_profile_name_test.go b/test/tc_long_profile_name_test.go
new file mode 100644
index 000000000..c0d9d6600
--- /dev/null
+++ b/test/tc_long_profile_name_test.go
@@ -0,0 +1,60 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed 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 e2e_test
+
+import (
+	"encoding/json"
+	"fmt"
+
+	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+)
+
+const (
+	policyName     = "this-is-a-very-long-name-surely-over-64-characters-omg-its-overflowing"
+	longNamePolicy = `
+    apiVersion: security-profiles-operator.x-k8s.io/v1beta1
+    kind: SeccompProfile
+    metadata:
+      name: this-is-a-very-long-name-surely-over-64-characters-omg-its-overflowing
+    spec:
+      defaultAction: "SCMP_ACT_ALLOW"
+`
+)
+
+func (e *e2e) testCaseLongSeccompProfileName(nodes []string) {
+	e.logf("List node statuses for a policy with a very long name")
+
+	e.logf("creating policy")
+	deleteFn := e.writeAndCreate(longNamePolicy, "longname-policy.yml")
+	defer deleteFn()
+
+	e.logf("Waiting for profile to be reconciled")
+	e.waitFor("condition=ready", "sp", policyName)
+
+	e.logf("List all node statuses for policy by ID")
+	id := e.getSeccompPolicyID(policyName)
+	namespace := e.getCurrentContextNamespace(defaultNamespace)
+	selector := fmt.Sprintf("spo.x-k8s.io/profile-id=%s", id)
+	seccompProfileNodeStatusJSON := e.kubectl(
+		"-n", namespace, "get", "securityprofilenodestatus", "-l", selector, "-o", "json",
+	)
+	secpolNodeStatusList := &secprofnodestatusv1alpha1.SecurityProfileNodeStatusList{}
+	e.Nil(json.Unmarshal([]byte(seccompProfileNodeStatusJSON), secpolNodeStatusList))
+	if len(nodes) > len(secpolNodeStatusList.Items) {
+		e.Fail("Couldn't list statuses for nodes")
+	}
+}