Repository: kubernetes-sigs/security-profiles-operator
Pull request #517 (2021-07-02 10:35:13)

Add streaming support for syscall recording


#### What type of PR is this?


/kind feature


#### What this PR does / why we need it:
We now use the GRPC streaming capabilities to safe performance on syscall recording.
#### Which issue(s) this PR fixes:

None
#### Does this PR have test?
None

#### Special notes for your reviewer:
Requires https://github.com/kubernetes-sigs/security-profiles-operator/pull/513

#### 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/517#" 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/master/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":[]} -->
saschagrunert: Works, PTAL @JAORMX @jhrozek 
saschagrunert: Rebased to resolve the conflicts.

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index d33d7a58f..e23ca68bb 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -66,7 +66,6 @@ linters:
     - varcheck
     - wastedassign
     - whitespace
-    # - wrapcheck
     # - cyclop
     # - exhaustivestruct
     # - forbidigo
@@ -76,6 +75,7 @@ linters:
     # - nlreturn
     # - revive
     # - testpackage
+    # - wrapcheck
     # - wsl
 linters-settings:
   nestif:
diff --git a/Makefile b/Makefile
index 22aaf30c7..0f7dcb482 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 
 GO ?= go
 
-GOLANGCI_LINT_VERSION = v1.40.1
+GOLANGCI_LINT_VERSION = v1.41.1
 REPO_INFRA_VERSION = v0.1.8
 KUSTOMIZE_VERSION = 4.2.0
 
diff --git a/api/server/api.pb.go b/api/server/api.pb.go
index 06f30cd44..5a5597b46 100644
--- a/api/server/api.pb.go
+++ b/api/server/api.pb.go
@@ -395,26 +395,26 @@ var file_api_server_api_proto_rawDesc = []byte{
 	0x10, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
 	0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x18, 0x01, 0x20,
 	0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x22, 0x0f, 0x0a,
-	0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x99,
+	0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x9b,
 	0x02, 0x0a, 0x18, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69,
 	0x6c, 0x65, 0x73, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0f, 0x4d,
 	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x41, 0x75, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x63, 0x12, 0x18,
 	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x41, 0x75, 0x64, 0x69,
 	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45,
 	0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01,
-	0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c,
+	0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c,
 	0x6c, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79,
 	0x73, 0x63, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61,
 	0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14,
-	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61,
-	0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a,
-	0x0d, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14,
-	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61,
-	0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73,
+	0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73,
+	0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x3b, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73,
+	0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70,
+	0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04,
+	0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/api/server/api.proto b/api/server/api.proto
index 8b1c64012..7e51b3648 100644
--- a/api/server/api.proto
+++ b/api/server/api.proto
@@ -21,7 +21,7 @@ option go_package = "/api";
 
 service SecurityProfilesOperator {
     rpc MetricsAuditInc(stream MetricsAuditRequest) returns (EmptyResponse) {}
-    rpc RecordSyscall(RecordSyscallRequest) returns (EmptyResponse) {}
+    rpc RecordSyscall(stream RecordSyscallRequest) returns (EmptyResponse) {}
     rpc Syscalls(SyscallsRequest) returns (SyscallsResponse) {}
     rpc ResetSyscalls(SyscallsRequest) returns (EmptyResponse) {}
 }
diff --git a/api/server/api_grpc.pb.go b/api/server/api_grpc.pb.go
index a4dbdd4f1..35e03c3b9 100644
--- a/api/server/api_grpc.pb.go
+++ b/api/server/api_grpc.pb.go
@@ -19,7 +19,7 @@ const _ = grpc.SupportPackageIsVersion7
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 type SecurityProfilesOperatorClient interface {
 	MetricsAuditInc(ctx context.Context, opts ...grpc.CallOption) (SecurityProfilesOperator_MetricsAuditIncClient, error)
-	RecordSyscall(ctx context.Context, in *RecordSyscallRequest, opts ...grpc.CallOption) (*EmptyResponse, error)
+	RecordSyscall(ctx context.Context, opts ...grpc.CallOption) (SecurityProfilesOperator_RecordSyscallClient, error)
 	Syscalls(ctx context.Context, in *SyscallsRequest, opts ...grpc.CallOption) (*SyscallsResponse, error)
 	ResetSyscalls(ctx context.Context, in *SyscallsRequest, opts ...grpc.CallOption) (*EmptyResponse, error)
 }
@@ -66,13 +66,38 @@ func (x *securityProfilesOperatorMetricsAuditIncClient) CloseAndRecv() (*EmptyRe
 	return m, nil
 }
 
-func (c *securityProfilesOperatorClient) RecordSyscall(ctx context.Context, in *RecordSyscallRequest, opts ...grpc.CallOption) (*EmptyResponse, error) {
-	out := new(EmptyResponse)
-	err := c.cc.Invoke(ctx, "/api.SecurityProfilesOperator/RecordSyscall", in, out, opts...)
+func (c *securityProfilesOperatorClient) RecordSyscall(ctx context.Context, opts ...grpc.CallOption) (SecurityProfilesOperator_RecordSyscallClient, error) {
+	stream, err := c.cc.NewStream(ctx, &SecurityProfilesOperator_ServiceDesc.Streams[1], "/api.SecurityProfilesOperator/RecordSyscall", opts...)
 	if err != nil {
 		return nil, err
 	}
-	return out, nil
+	x := &securityProfilesOperatorRecordSyscallClient{stream}
+	return x, nil
+}
+
+type SecurityProfilesOperator_RecordSyscallClient interface {
+	Send(*RecordSyscallRequest) error
+	CloseAndRecv() (*EmptyResponse, error)
+	grpc.ClientStream
+}
+
+type securityProfilesOperatorRecordSyscallClient struct {
+	grpc.ClientStream
+}
+
+func (x *securityProfilesOperatorRecordSyscallClient) Send(m *RecordSyscallRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *securityProfilesOperatorRecordSyscallClient) CloseAndRecv() (*EmptyResponse, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(EmptyResponse)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
 }
 
 func (c *securityProfilesOperatorClient) Syscalls(ctx context.Context, in *SyscallsRequest, opts ...grpc.CallOption) (*SyscallsResponse, error) {
@@ -98,7 +123,7 @@ func (c *securityProfilesOperatorClient) ResetSyscalls(ctx context.Context, in *
 // for forward compatibility
 type SecurityProfilesOperatorServer interface {
 	MetricsAuditInc(SecurityProfilesOperator_MetricsAuditIncServer) error
-	RecordSyscall(context.Context, *RecordSyscallRequest) (*EmptyResponse, error)
+	RecordSyscall(SecurityProfilesOperator_RecordSyscallServer) error
 	Syscalls(context.Context, *SyscallsRequest) (*SyscallsResponse, error)
 	ResetSyscalls(context.Context, *SyscallsRequest) (*EmptyResponse, error)
 	mustEmbedUnimplementedSecurityProfilesOperatorServer()
@@ -111,8 +136,8 @@ type UnimplementedSecurityProfilesOperatorServer struct {
 func (UnimplementedSecurityProfilesOperatorServer) MetricsAuditInc(SecurityProfilesOperator_MetricsAuditIncServer) error {
 	return status.Errorf(codes.Unimplemented, "method MetricsAuditInc not implemented")
 }
-func (UnimplementedSecurityProfilesOperatorServer) RecordSyscall(context.Context, *RecordSyscallRequest) (*EmptyResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method RecordSyscall not implemented")
+func (UnimplementedSecurityProfilesOperatorServer) RecordSyscall(SecurityProfilesOperator_RecordSyscallServer) error {
+	return status.Errorf(codes.Unimplemented, "method RecordSyscall not implemented")
 }
 func (UnimplementedSecurityProfilesOperatorServer) Syscalls(context.Context, *SyscallsRequest) (*SyscallsResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Syscalls not implemented")
@@ -160,22 +185,30 @@ func (x *securityProfilesOperatorMetricsAuditIncServer) Recv() (*MetricsAuditReq
 	return m, nil
 }
 
-func _SecurityProfilesOperator_RecordSyscall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(RecordSyscallRequest)
-	if err := dec(in); err != nil {
+func _SecurityProfilesOperator_RecordSyscall_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(SecurityProfilesOperatorServer).RecordSyscall(&securityProfilesOperatorRecordSyscallServer{stream})
+}
+
+type SecurityProfilesOperator_RecordSyscallServer interface {
+	SendAndClose(*EmptyResponse) error
+	Recv() (*RecordSyscallRequest, error)
+	grpc.ServerStream
+}
+
+type securityProfilesOperatorRecordSyscallServer struct {
+	grpc.ServerStream
+}
+
+func (x *securityProfilesOperatorRecordSyscallServer) SendAndClose(m *EmptyResponse) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *securityProfilesOperatorRecordSyscallServer) Recv() (*RecordSyscallRequest, error) {
+	m := new(RecordSyscallRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
 		return nil, err
 	}
-	if interceptor == nil {
-		return srv.(SecurityProfilesOperatorServer).RecordSyscall(ctx, in)
-	}
-	info := &grpc.UnaryServerInfo{
-		Server:     srv,
-		FullMethod: "/api.SecurityProfilesOperator/RecordSyscall",
-	}
-	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(SecurityProfilesOperatorServer).RecordSyscall(ctx, req.(*RecordSyscallRequest))
-	}
-	return interceptor(ctx, in, info, handler)
+	return m, nil
 }
 
 func _SecurityProfilesOperator_Syscalls_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
@@ -221,10 +254,6 @@ var SecurityProfilesOperator_ServiceDesc = grpc.ServiceDesc{
 	ServiceName: "api.SecurityProfilesOperator",
 	HandlerType: (*SecurityProfilesOperatorServer)(nil),
 	Methods: []grpc.MethodDesc{
-		{
-			MethodName: "RecordSyscall",
-			Handler:    _SecurityProfilesOperator_RecordSyscall_Handler,
-		},
 		{
 			MethodName: "Syscalls",
 			Handler:    _SecurityProfilesOperator_Syscalls_Handler,
@@ -240,6 +269,11 @@ var SecurityProfilesOperator_ServiceDesc = grpc.ServiceDesc{
 			Handler:       _SecurityProfilesOperator_MetricsAuditInc_Handler,
 			ClientStreams: true,
 		},
+		{
+			StreamName:    "RecordSyscall",
+			Handler:       _SecurityProfilesOperator_RecordSyscall_Handler,
+			ClientStreams: true,
+		},
 	},
 	Metadata: "api/server/api.proto",
 }
diff --git a/cmd/security-profiles-operator/main.go b/cmd/security-profiles-operator/main.go
index 879450ae8..80e9a24da 100644
--- a/cmd/security-profiles-operator/main.go
+++ b/cmd/security-profiles-operator/main.go
@@ -246,12 +246,10 @@ func setControllerOptionsForNamespaces(opts *ctrl.Options) {
 }
 
 func getEnabledControllers(ctx *cli.Context) []controller.Controller {
-	controllers := make([]controller.Controller, 0, 2)
-
-	controllers = append(controllers,
+	controllers := []controller.Controller{
 		seccompprofile.NewController(),
 		profilerecorder.NewController(),
-	)
+	}
 
 	if ctx.Bool(selinuxFlag) {
 		controllers = append(controllers, selinuxprofile.NewController())
diff --git a/dependencies.yaml b/dependencies.yaml
index 56aed5308..ff0a77917 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -10,7 +10,7 @@ dependencies:
       match: GO_VERSION
 
   - name: golangci-lint
-    version: 1.40.1
+    version: 1.41.1
     refPaths:
     - path: Makefile
       match: GOLANGCI_LINT_VERSION
diff --git a/go.mod b/go.mod
index 6e62681f6..f1fae41d2 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/maxbrunsfeld/counterfeiter/v6 v6.4.1
 	github.com/nxadm/tail v1.4.8
 	github.com/pkg/errors v0.9.1
-	github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.48.1
+	github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.49.0
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_model v0.2.0
 	github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
index 3bf1f8f6e..a8f44291b 100644
--- a/go.sum
+++ b/go.sum
@@ -783,8 +783,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.48.1 h1:OGC7+ktZ6h8xI99VB6i8iuiXecdhUmwto9vbGzoVMac=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.48.1/go.mod h1:3WYi4xqXxGGXWDdQIITnLNmuDzO5n6wYva9spVhR4fg=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.49.0 h1:caQO65I0GsYqrQsbZkIA7T941XVq2N6n3z2jIUPuzdo=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.49.0/go.mod h1:3WYi4xqXxGGXWDdQIITnLNmuDzO5n6wYva9spVhR4fg=
 github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
diff --git a/internal/pkg/daemon/enricher/audit.go b/internal/pkg/daemon/enricher/audit.go
index 9dee739ce..2dee449a8 100644
--- a/internal/pkg/daemon/enricher/audit.go
+++ b/internal/pkg/daemon/enricher/audit.go
@@ -25,8 +25,9 @@ import (
 
 // type IDs are defined at https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/audit.h
 var (
-	//nolint:lll
-	seccompLineRegex = regexp.MustCompile(`(type=SECCOMP|audit:.+type=1326).+audit\((.+)\).+pid=(\b\d+\b).+exe="(.+)".+syscall=(\b\d+\b).*`)
+	seccompLineRegex = regexp.MustCompile(
+		`(type=SECCOMP|audit:.+type=1326).+audit\((.+)\).+pid=(\b\d+\b).+exe="(.+)".+syscall=(\b\d+\b).*`,
+	)
 	selinuxLineRegex = regexp.MustCompile(`type=AVC.+audit\((.+)\).+pid=(\b\d+\b).*`)
 )
 
@@ -56,7 +57,7 @@ func extractAuditLine(logLine string) (*auditLine, error) {
 		return selinux, nil
 	}
 
-	return nil, errors.Wrap(errUnsupportedLogLine, logLine)
+	return nil, errors.Errorf("unsupported log line: %s", logLine)
 }
 
 func extractSeccompLine(logLine string) *auditLine {
diff --git a/internal/pkg/daemon/enricher/container.go b/internal/pkg/daemon/enricher/container.go
index 46bf70858..2651b9f0b 100644
--- a/internal/pkg/daemon/enricher/container.go
+++ b/internal/pkg/daemon/enricher/container.go
@@ -24,7 +24,6 @@ import (
 	"strconv"
 
 	"github.com/ReneKroon/ttlcache/v2"
-	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
@@ -37,7 +36,7 @@ import (
 // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;
 
 func (e *Enricher) getContainerInfo(
-	logger logr.Logger, nodeName, containerID string,
+	nodeName, containerID string,
 ) (*containerInfo, error) {
 	// Check the cache first
 	if info, err := e.infoCache.Get(
@@ -56,6 +55,7 @@ func (e *Enricher) getContainerInfo(
 		return nil, errors.Wrap(err, "load in-cluster config")
 	}
 
+	errContainerIDEmpty := errors.New("container ID is empty")
 	if err := util.Retry(
 		func() (retryErr error) {
 			pods, err := e.impl.ListPods(clientset, nodeName)
@@ -72,7 +72,7 @@ func (e *Enricher) getContainerInfo(
 					containerName := containerStatus.Name
 
 					if containerID == "" {
-						logger.Info(
+						e.logger.Info(
 							"container ID is still empty, retrying",
 							"podName", pod.Name,
 							"containerName", containerName,
@@ -82,7 +82,7 @@ func (e *Enricher) getContainerInfo(
 
 					rawContainerID := regexID.FindString(containerID)
 					if rawContainerID == "" {
-						logger.Error(
+						e.logger.Error(
 							err, "unable to get container ID",
 							"podName", pod.Name,
 							"containerName", containerName,
@@ -123,6 +123,9 @@ func (e *Enricher) getContainerInfo(
 	return nil, errors.New("no container info for container ID")
 }
 
+// We assume that a container ID has a length of 64.
+var regexID = regexp.MustCompile(`[0-9a-f]{64}`)
+
 func (e *Enricher) getContainerID(processID int) (string, error) {
 	// Check the cache first
 	if id, err := e.containerIDCache.Get(
@@ -131,11 +134,13 @@ func (e *Enricher) getContainerID(processID int) (string, error) {
 		return id.(string), nil
 	}
 
-	cgroupFile := fmt.Sprintf("/proc/%d/cgroup", processID)
-	file, err := e.impl.Open(filepath.Clean(cgroupFile))
+	cgroupPath := fmt.Sprintf("/proc/%d/cgroup", processID)
+
+	file, err := e.impl.Open(filepath.Clean(cgroupPath))
 	if err != nil {
-		return "", errors.Errorf("could not open cgroup path %s", cgroupFile)
+		return "", errors.Wrap(err, "could not open cgroup path")
 	}
+
 	defer func() {
 		cerr := file.Close()
 		if err == nil {
@@ -144,9 +149,8 @@ func (e *Enricher) getContainerID(processID int) (string, error) {
 	}()
 
 	scanner := bufio.NewScanner(file)
-	scanner.Split(bufio.ScanLines)
 	for scanner.Scan() {
-		if containerID := extractID(scanner.Text()); containerID != "" {
+		if containerID := regexID.FindString(scanner.Text()); containerID != "" {
 			// Update the cache
 			if err := e.containerIDCache.Set(
 				strconv.Itoa(processID), containerID,
@@ -160,10 +164,3 @@ func (e *Enricher) getContainerID(processID int) (string, error) {
 
 	return "", errors.New("unable to find container ID from cgroup path")
 }
-
-// We assume that a container ID has a length of 64.
-var regexID = regexp.MustCompile(`[0-9a-f]{64}`)
-
-func extractID(cgroup string) string {
-	return regexID.FindString(cgroup)
-}
diff --git a/internal/pkg/daemon/enricher/enricher.go b/internal/pkg/daemon/enricher/enricher.go
index 8371a6d65..99050afb1 100644
--- a/internal/pkg/daemon/enricher/enricher.go
+++ b/internal/pkg/daemon/enricher/enricher.go
@@ -87,6 +87,11 @@ func (e *Enricher) Run() error {
 		return errors.Wrap(err, "create metrics audit client")
 	}
 
+	recordSyscallClient, err := e.impl.RecordSyscall(client)
+	if err != nil {
+		return errors.Wrap(err, "create syscall recording client")
+	}
+
 	// If the file does not exist, then tail will wait for it to appear
 	tailFile, err := e.impl.TailFile(
 		config.AuditLogPath,
@@ -122,12 +127,19 @@ func (e *Enricher) Run() error {
 		}
 
 		cID, err := e.getContainerID(auditLine.processID)
+		if errors.Is(err, os.ErrNotExist) {
+			// We're probably in container creation or removal
+			continue
+		}
 		if err != nil {
-			e.logger.Error(err, "unable to get container ID", "processID", auditLine.processID)
+			e.logger.Error(
+				err, "unable to get container ID",
+				"processID", auditLine.processID,
+			)
 			continue
 		}
 
-		info, err := e.getContainerInfo(e.logger, nodeName, cID)
+		info, err := e.getContainerInfo(nodeName, cID)
 		if err != nil {
 			e.logger.Error(
 				err, "container ID not found in cluster",
@@ -171,8 +183,8 @@ func (e *Enricher) Run() error {
 		}
 
 		if info.recordProfile != "" {
-			if _, err := e.impl.RecordSyscall(
-				client,
+			if err := e.impl.SendSyscall(
+				recordSyscallClient,
 				&api.RecordSyscallRequest{
 					Profile: info.recordProfile,
 					Syscall: syscallName,
diff --git a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
index a1e8683a5..e00da7c3d 100644
--- a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
+++ b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
@@ -140,18 +140,17 @@ type FakeImpl struct {
 		result1 *os.File
 		result2 error
 	}
-	RecordSyscallStub        func(api.SecurityProfilesOperatorClient, *api.RecordSyscallRequest) (*api.EmptyResponse, error)
+	RecordSyscallStub        func(api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_RecordSyscallClient, error)
 	recordSyscallMutex       sync.RWMutex
 	recordSyscallArgsForCall []struct {
 		arg1 api.SecurityProfilesOperatorClient
-		arg2 *api.RecordSyscallRequest
 	}
 	recordSyscallReturns struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_RecordSyscallClient
 		result2 error
 	}
 	recordSyscallReturnsOnCall map[int]struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_RecordSyscallClient
 		result2 error
 	}
 	SendMetricStub        func(api.SecurityProfilesOperator_MetricsAuditIncClient, *api.MetricsAuditRequest) error
@@ -166,6 +165,18 @@ type FakeImpl struct {
 	sendMetricReturnsOnCall map[int]struct {
 		result1 error
 	}
+	SendSyscallStub        func(api.SecurityProfilesOperator_RecordSyscallClient, *api.RecordSyscallRequest) error
+	sendSyscallMutex       sync.RWMutex
+	sendSyscallArgsForCall []struct {
+		arg1 api.SecurityProfilesOperator_RecordSyscallClient
+		arg2 *api.RecordSyscallRequest
+	}
+	sendSyscallReturns struct {
+		result1 error
+	}
+	sendSyscallReturnsOnCall map[int]struct {
+		result1 error
+	}
 	TailFileStub        func(string, tail.Config) (*tail.Tail, error)
 	tailFileMutex       sync.RWMutex
 	tailFileArgsForCall []struct {
@@ -736,19 +747,18 @@ func (fake *FakeImpl) OpenReturnsOnCall(i int, result1 *os.File, result2 error)
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) RecordSyscall(arg1 api.SecurityProfilesOperatorClient, arg2 *api.RecordSyscallRequest) (*api.EmptyResponse, error) {
+func (fake *FakeImpl) RecordSyscall(arg1 api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_RecordSyscallClient, error) {
 	fake.recordSyscallMutex.Lock()
 	ret, specificReturn := fake.recordSyscallReturnsOnCall[len(fake.recordSyscallArgsForCall)]
 	fake.recordSyscallArgsForCall = append(fake.recordSyscallArgsForCall, struct {
 		arg1 api.SecurityProfilesOperatorClient
-		arg2 *api.RecordSyscallRequest
-	}{arg1, arg2})
+	}{arg1})
 	stub := fake.RecordSyscallStub
 	fakeReturns := fake.recordSyscallReturns
-	fake.recordInvocation("RecordSyscall", []interface{}{arg1, arg2})
+	fake.recordInvocation("RecordSyscall", []interface{}{arg1})
 	fake.recordSyscallMutex.Unlock()
 	if stub != nil {
-		return stub(arg1, arg2)
+		return stub(arg1)
 	}
 	if specificReturn {
 		return ret.result1, ret.result2
@@ -762,41 +772,41 @@ func (fake *FakeImpl) RecordSyscallCallCount() int {
 	return len(fake.recordSyscallArgsForCall)
 }
 
-func (fake *FakeImpl) RecordSyscallCalls(stub func(api.SecurityProfilesOperatorClient, *api.RecordSyscallRequest) (*api.EmptyResponse, error)) {
+func (fake *FakeImpl) RecordSyscallCalls(stub func(api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_RecordSyscallClient, error)) {
 	fake.recordSyscallMutex.Lock()
 	defer fake.recordSyscallMutex.Unlock()
 	fake.RecordSyscallStub = stub
 }
 
-func (fake *FakeImpl) RecordSyscallArgsForCall(i int) (api.SecurityProfilesOperatorClient, *api.RecordSyscallRequest) {
+func (fake *FakeImpl) RecordSyscallArgsForCall(i int) api.SecurityProfilesOperatorClient {
 	fake.recordSyscallMutex.RLock()
 	defer fake.recordSyscallMutex.RUnlock()
 	argsForCall := fake.recordSyscallArgsForCall[i]
-	return argsForCall.arg1, argsForCall.arg2
+	return argsForCall.arg1
 }
 
-func (fake *FakeImpl) RecordSyscallReturns(result1 *api.EmptyResponse, result2 error) {
+func (fake *FakeImpl) RecordSyscallReturns(result1 api.SecurityProfilesOperator_RecordSyscallClient, result2 error) {
 	fake.recordSyscallMutex.Lock()
 	defer fake.recordSyscallMutex.Unlock()
 	fake.RecordSyscallStub = nil
 	fake.recordSyscallReturns = struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_RecordSyscallClient
 		result2 error
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) RecordSyscallReturnsOnCall(i int, result1 *api.EmptyResponse, result2 error) {
+func (fake *FakeImpl) RecordSyscallReturnsOnCall(i int, result1 api.SecurityProfilesOperator_RecordSyscallClient, result2 error) {
 	fake.recordSyscallMutex.Lock()
 	defer fake.recordSyscallMutex.Unlock()
 	fake.RecordSyscallStub = nil
 	if fake.recordSyscallReturnsOnCall == nil {
 		fake.recordSyscallReturnsOnCall = make(map[int]struct {
-			result1 *api.EmptyResponse
+			result1 api.SecurityProfilesOperator_RecordSyscallClient
 			result2 error
 		})
 	}
 	fake.recordSyscallReturnsOnCall[i] = struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_RecordSyscallClient
 		result2 error
 	}{result1, result2}
 }
@@ -863,6 +873,68 @@ func (fake *FakeImpl) SendMetricReturnsOnCall(i int, result1 error) {
 	}{result1}
 }
 
+func (fake *FakeImpl) SendSyscall(arg1 api.SecurityProfilesOperator_RecordSyscallClient, arg2 *api.RecordSyscallRequest) error {
+	fake.sendSyscallMutex.Lock()
+	ret, specificReturn := fake.sendSyscallReturnsOnCall[len(fake.sendSyscallArgsForCall)]
+	fake.sendSyscallArgsForCall = append(fake.sendSyscallArgsForCall, struct {
+		arg1 api.SecurityProfilesOperator_RecordSyscallClient
+		arg2 *api.RecordSyscallRequest
+	}{arg1, arg2})
+	stub := fake.SendSyscallStub
+	fakeReturns := fake.sendSyscallReturns
+	fake.recordInvocation("SendSyscall", []interface{}{arg1, arg2})
+	fake.sendSyscallMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeImpl) SendSyscallCallCount() int {
+	fake.sendSyscallMutex.RLock()
+	defer fake.sendSyscallMutex.RUnlock()
+	return len(fake.sendSyscallArgsForCall)
+}
+
+func (fake *FakeImpl) SendSyscallCalls(stub func(api.SecurityProfilesOperator_RecordSyscallClient, *api.RecordSyscallRequest) error) {
+	fake.sendSyscallMutex.Lock()
+	defer fake.sendSyscallMutex.Unlock()
+	fake.SendSyscallStub = stub
+}
+
+func (fake *FakeImpl) SendSyscallArgsForCall(i int) (api.SecurityProfilesOperator_RecordSyscallClient, *api.RecordSyscallRequest) {
+	fake.sendSyscallMutex.RLock()
+	defer fake.sendSyscallMutex.RUnlock()
+	argsForCall := fake.sendSyscallArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeImpl) SendSyscallReturns(result1 error) {
+	fake.sendSyscallMutex.Lock()
+	defer fake.sendSyscallMutex.Unlock()
+	fake.SendSyscallStub = nil
+	fake.sendSyscallReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) SendSyscallReturnsOnCall(i int, result1 error) {
+	fake.sendSyscallMutex.Lock()
+	defer fake.sendSyscallMutex.Unlock()
+	fake.SendSyscallStub = nil
+	if fake.sendSyscallReturnsOnCall == nil {
+		fake.sendSyscallReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.sendSyscallReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
 func (fake *FakeImpl) TailFile(arg1 string, arg2 tail.Config) (*tail.Tail, error) {
 	fake.tailFileMutex.Lock()
 	ret, specificReturn := fake.tailFileReturnsOnCall[len(fake.tailFileArgsForCall)]
@@ -953,6 +1025,8 @@ func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	defer fake.recordSyscallMutex.RUnlock()
 	fake.sendMetricMutex.RLock()
 	defer fake.sendMetricMutex.RUnlock()
+	fake.sendSyscallMutex.RLock()
+	defer fake.sendSyscallMutex.RUnlock()
 	fake.tailFileMutex.RLock()
 	defer fake.tailFileMutex.RUnlock()
 	copiedInvocations := map[string][][]interface{}{}
diff --git a/internal/pkg/daemon/enricher/impl.go b/internal/pkg/daemon/enricher/impl.go
index 0a54cd978..bbd49f761 100644
--- a/internal/pkg/daemon/enricher/impl.go
+++ b/internal/pkg/daemon/enricher/impl.go
@@ -54,8 +54,11 @@ type impl interface {
 	) error
 	RecordSyscall(
 		client api.SecurityProfilesOperatorClient,
+	) (api.SecurityProfilesOperator_RecordSyscallClient, error)
+	SendSyscall(
+		client api.SecurityProfilesOperator_RecordSyscallClient,
 		in *api.RecordSyscallRequest,
-	) (*api.EmptyResponse, error)
+	) error
 }
 
 func (d *defaultImpl) Getenv(key string) string {
@@ -117,7 +120,13 @@ func (d *defaultImpl) SendMetric(
 
 func (d *defaultImpl) RecordSyscall(
 	client api.SecurityProfilesOperatorClient,
+) (api.SecurityProfilesOperator_RecordSyscallClient, error) {
+	return client.RecordSyscall(context.Background())
+}
+
+func (d *defaultImpl) SendSyscall(
+	client api.SecurityProfilesOperator_RecordSyscallClient,
 	in *api.RecordSyscallRequest,
-) (*api.EmptyResponse, error) {
-	return client.RecordSyscall(context.Background(), in)
+) error {
+	return client.Send(in)
 }
diff --git a/internal/pkg/daemon/enricher/types.go b/internal/pkg/daemon/enricher/types.go
index 43a8292d7..9ece93ab8 100644
--- a/internal/pkg/daemon/enricher/types.go
+++ b/internal/pkg/daemon/enricher/types.go
@@ -16,13 +16,6 @@ limitations under the License.
 
 package enricher
 
-import "errors"
-
-var (
-	errUnsupportedLogLine = errors.New("unsupported log line")
-	errContainerIDEmpty   = errors.New("container ID is empty")
-)
-
 const (
 	auditTypeSeccomp = "seccomp"
 	auditTypeSelinux = "selinux"
diff --git a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
index db16a20ab..305dd17ba 100644
--- a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
+++ b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
@@ -280,10 +280,11 @@ func (r *ReconcileSP) reconcilePolicyFile(sp *selinuxprofilev1alpha1.SelinuxProf
 // drop dir is private to this pod, but mostly just calling a single write is much easier codepath
 // than mucking around with seeks and truncates to account for all the corner cases.
 func writeFileIfDiffers(filePath string, contents []byte) error {
-	file, err := os.OpenFile(filePath, os.O_RDONLY, 0600)
+	const filePermissions = 0o600
+	file, err := os.OpenFile(filePath, os.O_RDONLY, filePermissions)
 	if os.IsNotExist(err) {
 		file.Close()
-		return ioutil.WriteFile(filePath, contents, 0600)
+		return ioutil.WriteFile(filePath, contents, filePermissions)
 	} else if err != nil {
 		return errors.Wrap(err, "could not open for reading"+filePath)
 	}
@@ -298,7 +299,7 @@ func writeFileIfDiffers(filePath string, contents []byte) error {
 		return nil
 	}
 
-	return ioutil.WriteFile(filePath, contents, 0600)
+	return ioutil.WriteFile(filePath, contents, filePermissions)
 }
 
 func (r *ReconcileSP) reconcileDeletePolicy(
diff --git a/internal/pkg/daemon/server/server.go b/internal/pkg/daemon/server/server.go
index 2f3b1e009..7f5446a5a 100644
--- a/internal/pkg/daemon/server/server.go
+++ b/internal/pkg/daemon/server/server.go
@@ -118,11 +118,20 @@ func (s *Server) MetricsAuditInc(
 
 // RecordSyscall traces a single syscall for a provided profile.
 func (s *Server) RecordSyscall(
-	ctx context.Context, r *api.RecordSyscallRequest,
-) (*api.EmptyResponse, error) {
-	syscalls, _ := s.syscalls.LoadOrStore(r.GetProfile(), sets.NewString())
-	syscalls.(sets.String).Insert(r.GetSyscall())
-	return &api.EmptyResponse{}, nil
+	stream api.SecurityProfilesOperator_RecordSyscallServer,
+) error {
+	for {
+		r, err := stream.Recv()
+		if errors.Is(err, io.EOF) {
+			return stream.SendAndClose(&api.EmptyResponse{})
+		}
+		if err != nil {
+			return errors.Wrap(err, "record syscalls")
+		}
+
+		syscalls, _ := s.syscalls.LoadOrStore(r.GetProfile(), sets.NewString())
+		syscalls.(sets.String).Insert(r.GetSyscall())
+	}
 }
 
 // Syscalls returns the syscalls for a provided profile.
diff --git a/internal/pkg/manager/nodestatus/nodestatus.go b/internal/pkg/manager/nodestatus/nodestatus.go
index 4e3cfe987..6df7e2ab5 100644
--- a/internal/pkg/manager/nodestatus/nodestatus.go
+++ b/internal/pkg/manager/nodestatus/nodestatus.go
@@ -178,7 +178,7 @@ func (r *StatusReconciler) Reconcile(_ context.Context, req reconcile.Request) (
 		return reconcile.Result{}, nil
 	}
 
-	var lowestCommonState statusv1alpha1.ProfileState = statusv1alpha1.LowestState
+	lowestCommonState := statusv1alpha1.LowestState
 	for i := range nodeStatusList.Items {
 		lowestCommonState = statusv1alpha1.LowerOfTwoStates(lowestCommonState, nodeStatusList.Items[i].Status)
 	}
diff --git a/internal/pkg/version/version.go b/internal/pkg/version/version.go
index efa58b998..39bca2ceb 100644
--- a/internal/pkg/version/version.go
+++ b/internal/pkg/version/version.go
@@ -58,7 +58,8 @@ func Get() *Info {
 // String returns the string representation of the version info.
 func (i *Info) String() string {
 	b := strings.Builder{}
-	w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0)
+	const padding = 2
+	w := tabwriter.NewWriter(&b, 0, 0, padding, ' ', 0)
 
 	fmt.Fprintf(w, "Version:\t%s\n", i.Version)
 	fmt.Fprintf(w, "GitCommit:\t%s\n", i.GitCommit)
diff --git a/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/types.go b/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/types.go
index 794595996..0b99ec3e4 100644
--- a/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/types.go
+++ b/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/types.go
@@ -15,6 +15,8 @@
 package v1
 
 import (
+	"fmt"
+
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -167,11 +169,11 @@ type PrometheusSpec struct {
 	LogLevel string `json:"logLevel,omitempty"`
 	// Log format for Prometheus to be configured with.
 	LogFormat string `json:"logFormat,omitempty"`
-	// Interval between consecutive scrapes.
+	// Interval between consecutive scrapes. Default: `1m`
 	ScrapeInterval string `json:"scrapeInterval,omitempty"`
 	// Number of seconds to wait for target to respond before erroring.
 	ScrapeTimeout string `json:"scrapeTimeout,omitempty"`
-	// Interval between consecutive evaluations.
+	// Interval between consecutive evaluations. Default: `1m`
 	EvaluationInterval string `json:"evaluationInterval,omitempty"`
 	// /--rules.*/ command-line arguments.
 	Rules Rules `json:"rules,omitempty"`
@@ -269,9 +271,12 @@ type PrometheusSpec struct {
 	// InitContainers allows adding initContainers to the pod definition. Those can be used to e.g.
 	// fetch secrets for injection into the Prometheus configuration from external sources. Any errors
 	// during the execution of an initContainer will lead to a restart of the Pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
-	// Using initContainers for any use case other then secret fetching is entirely outside the scope
-	// of what the maintainers will support and by doing so, you accept that this behaviour may break
-	// at any time without notice.
+	// InitContainers described here modify an operator
+	// generated init containers if they share the same name and modifications are
+	// done via a strategic merge patch. The current init container name is:
+	// `init-config-reloader`. Overriding init containers is entirely outside the
+	// scope of what the maintainers will support and by doing so, you accept that
+	// this behaviour may break at any time without notice.
 	InitContainers []v1.Container `json:"initContainers,omitempty"`
 	// AdditionalScrapeConfigs allows specifying a key of a Secret containing
 	// additional Prometheus scrape configurations. Scrape configurations
@@ -341,9 +346,15 @@ type PrometheusSpec struct {
 	// the podmonitor and servicemonitor configs, and they will only discover endpoints
 	// within their current namespace.  Defaults to false.
 	IgnoreNamespaceSelectors bool `json:"ignoreNamespaceSelectors,omitempty"`
-	// EnforcedNamespaceLabel enforces adding a namespace label of origin for each alert
-	// and metric that is user created. The label value will always be the namespace of the object that is
-	// being created.
+	// EnforcedNamespaceLabel If set, a label will be added to
+	//
+	// 1. all user-metrics (created by `ServiceMonitor`, `PodMonitor` and `ProbeConfig` object) and
+	// 2. in all `PrometheusRule` objects (except the ones excluded in `prometheusRulesExcludedFromEnforce`) to
+	//    * alerting & recording rules and
+	//    * the metrics used in their expressions (`expr`).
+	//
+	// Label name is this field's value.
+	// Label value is the namespace of the created object (mentioned above).
 	EnforcedNamespaceLabel string `json:"enforcedNamespaceLabel,omitempty"`
 	// PrometheusRulesExcludedFromEnforce - list of prometheus rules to be excluded from enforcing
 	// of adding namespace labels. Works only if enforcedNamespaceLabel set to true.
@@ -503,7 +514,77 @@ type QuerySpec struct {
 // +k8s:openapi-gen=true
 type WebSpec struct {
 	// The prometheus web page title
-	PageTitle *string `json:"pageTitle,omitempty"`
+	PageTitle *string       `json:"pageTitle,omitempty"`
+	TLSConfig *WebTLSConfig `json:"tlsConfig,omitempty"`
+}
+
+// WebTLSConfig defines the TLS parameters for HTTPS.
+// +k8s:openapi-gen=true
+type WebTLSConfig struct {
+	// Secret containing the TLS key for the server.
+	KeySecret v1.SecretKeySelector `json:"keySecret"`
+	// Contains the TLS certificate for the server.
+	Cert SecretOrConfigMap `json:"cert"`
+	// Server policy for client authentication. Maps to ClientAuth Policies.
+	// For more detail on clientAuth options:
+	// https://golang.org/pkg/crypto/tls/#ClientAuthType
+	ClientAuthType string `json:"clientAuthType,omitempty"`
+	// Contains the CA certificate for client certificate authentication to the server.
+	ClientCA SecretOrConfigMap `json:"client_ca,omitempty"`
+	// Minimum TLS version that is acceptable. Defaults to TLS12.
+	MinVersion string `json:"minVersion,omitempty"`
+	// Maximum TLS version that is acceptable. Defaults to TLS13.
+	MaxVersion string `json:"maxVersion,omitempty"`
+	// List of supported cipher suites for TLS versions up to TLS 1.2. If empty,
+	// Go default cipher suites are used. Available cipher suites are documented
+	// in the go documentation: https://golang.org/pkg/crypto/tls/#pkg-constants
+	CipherSuites []string `json:"cipherSuites,omitempty"`
+	// Controls whether the server selects the
+	// client's most preferred cipher suite, or the server's most preferred
+	// cipher suite. If true then the server's preference, as expressed in
+	// the order of elements in cipherSuites, is used.
+	PreferServerCipherSuites *bool `json:"preferServerCipherSuites,omitempty"`
+	// Elliptic curves that will be used in an ECDHE handshake, in preference
+	// order. Available curves are documented in the go documentation:
+	// https://golang.org/pkg/crypto/tls/#CurveID
+	CurvePreferences []string `json:"curvePreferences,omitempty"`
+}
+
+// WebTLSConfigError is returned by WebTLSConfig.Validate() on
+// semantically invalid configurations.
+// +k8s:openapi-gen=false
+type WebTLSConfigError struct {
+	err string
+}
+
+func (e *WebTLSConfigError) Error() string {
+	return e.err
+}
+
+func (c *WebTLSConfig) Validate() error {
+	if c == nil {
+		return nil
+	}
+
+	if c.ClientCA != (SecretOrConfigMap{}) {
+		if err := c.ClientCA.Validate(); err != nil {
+			msg := fmt.Sprintf("invalid web tls config: %s", err.Error())
+			return &WebTLSConfigError{msg}
+		}
+	}
+
+	if c.Cert == (SecretOrConfigMap{}) {
+		return &WebTLSConfigError{"invalid web tls config: cert must be defined"}
+	} else if err := c.Cert.Validate(); err != nil {
+		msg := fmt.Sprintf("invalid web tls config: %s", err.Error())
+		return &WebTLSConfigError{msg}
+	}
+
+	if c.KeySecret == (v1.SecretKeySelector{}) {
+		return &WebTLSConfigError{"invalid web tls config: key must be defined"}
+	}
+
+	return nil
 }
 
 // ThanosSpec defines parameters for a Prometheus server within a Thanos deployment.
@@ -558,6 +639,8 @@ type ThanosSpec struct {
 	LogFormat string `json:"logFormat,omitempty"`
 	// MinTime for Thanos sidecar to be configured with. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.
 	MinTime string `json:"minTime,omitempty"`
+	// ReadyTimeout is the maximum time Thanos sidecar will wait for Prometheus to start. Eg 10m
+	ReadyTimeout string `json:"readyTimeout,omitempty"`
 }
 
 // RemoteWriteSpec defines the remote_write configuration for prometheus.
@@ -727,17 +810,21 @@ type ServiceMonitor struct {
 // ServiceMonitorSpec contains specification parameters for a ServiceMonitor.
 // +k8s:openapi-gen=true
 type ServiceMonitorSpec struct {
-	// The label to use to retrieve the job name from.
+	// Chooses the label of the Kubernetes `Endpoints`.
+	// Its value will be used for the `job`-label's value of the created metrics.
+	//
+	// Default & fallback value: the name of the respective Kubernetes `Endpoint`.
 	JobLabel string `json:"jobLabel,omitempty"`
-	// TargetLabels transfers labels on the Kubernetes Service onto the target.
+	// TargetLabels transfers labels from the Kubernetes `Service` onto the created metrics.
+	// All labels set in `selector.matchLabels` are automatically transferred.
 	TargetLabels []string `json:"targetLabels,omitempty"`
-	// PodTargetLabels transfers labels on the Kubernetes Pod onto the target.
+	// PodTargetLabels transfers labels on the Kubernetes `Pod` onto the created metrics.
 	PodTargetLabels []string `json:"podTargetLabels,omitempty"`
 	// A list of endpoints allowed as part of this ServiceMonitor.
 	Endpoints []Endpoint `json:"endpoints"`
 	// Selector to select Endpoints objects.
 	Selector metav1.LabelSelector `json:"selector"`
-	// Selector to select which namespaces the Endpoints objects are discovered from.
+	// Selector to select which namespaces the Kubernetes Endpoints objects are discovered from.
 	NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"`
 	// SampleLimit defines per-scrape limit on number of scraped samples that will be accepted.
 	SampleLimit uint64 `json:"sampleLimit,omitempty"`
@@ -951,6 +1038,8 @@ type ProberSpec struct {
 	// Path to collect metrics from.
 	// Defaults to `/probe`.
 	Path string `json:"path,omitempty"`
+	// Optional ProxyURL.
+	ProxyURL string `json:"proxyUrl,omitempty"`
 }
 
 // BasicAuth allow an endpoint to authenticate over basic authentication
@@ -1170,7 +1259,8 @@ type RuleGroup struct {
 	PartialResponseStrategy string `json:"partial_response_strategy,omitempty"`
 }
 
-// Rule describes an alerting or recording rule.
+// Rule describes an alerting or recording rule
+// See Prometheus documentation: [alerting](https://www.prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) or [recording](https://www.prometheus.io/docs/prometheus/latest/configuration/recording_rules/#recording-rules) rule
 // +k8s:openapi-gen=true
 type Rule struct {
 	Record      string             `json:"record,omitempty"`
diff --git a/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/zz_generated.deepcopy.go b/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/zz_generated.deepcopy.go
index bbaae09bb..ba830c4b6 100644
--- a/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/zz_generated.deepcopy.go
+++ b/vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1/zz_generated.deepcopy.go
@@ -1962,6 +1962,11 @@ func (in *WebSpec) DeepCopyInto(out *WebSpec) {
 		*out = new(string)
 		**out = **in
 	}
+	if in.TLSConfig != nil {
+		in, out := &in.TLSConfig, &out.TLSConfig
+		*out = new(WebTLSConfig)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebSpec.
@@ -1973,3 +1978,51 @@ func (in *WebSpec) DeepCopy() *WebSpec {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WebTLSConfig) DeepCopyInto(out *WebTLSConfig) {
+	*out = *in
+	in.KeySecret.DeepCopyInto(&out.KeySecret)
+	in.Cert.DeepCopyInto(&out.Cert)
+	in.ClientCA.DeepCopyInto(&out.ClientCA)
+	if in.CipherSuites != nil {
+		in, out := &in.CipherSuites, &out.CipherSuites
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.PreferServerCipherSuites != nil {
+		in, out := &in.PreferServerCipherSuites, &out.PreferServerCipherSuites
+		*out = new(bool)
+		**out = **in
+	}
+	if in.CurvePreferences != nil {
+		in, out := &in.CurvePreferences, &out.CurvePreferences
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebTLSConfig.
+func (in *WebTLSConfig) DeepCopy() *WebTLSConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(WebTLSConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *WebTLSConfigError) DeepCopyInto(out *WebTLSConfigError) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebTLSConfigError.
+func (in *WebTLSConfigError) DeepCopy() *WebTLSConfigError {
+	if in == nil {
+		return nil
+	}
+	out := new(WebTLSConfigError)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 86cf90029..297fa6fd7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -231,7 +231,7 @@ github.com/opencontainers/runtime-spec/specs-go
 github.com/pkg/errors
 # github.com/pmezard/go-difflib v1.0.0
 github.com/pmezard/go-difflib/difflib
-# github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.48.1
+# github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.49.0
 ## explicit
 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring
 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1


Test patch:
diff --git a/internal/pkg/daemon/enricher/container_test.go b/internal/pkg/daemon/enricher/container_test.go
index ead15c507..2e5b4ca7c 100644
--- a/internal/pkg/daemon/enricher/container_test.go
+++ b/internal/pkg/daemon/enricher/container_test.go
@@ -31,8 +31,8 @@ func TestExtractContainerID(t *testing.T) {
 	}{
 		{
 			"Should extract crio ID",
-			//nolint:lll
-			`4:net_cls,net_prio:/kubepods/besteffort/pod26ba375c-2266-4ecc-bf2d-b626db8762af/crio-af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a`,
+			`4:net_cls,net_prio:/kubepods/besteffort/pod26ba375c-2266-4ecc-bf2d-b626db8762af/` +
+				`crio-af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a`,
 			"af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a",
 		},
 		{
@@ -50,7 +50,7 @@ func TestExtractContainerID(t *testing.T) {
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			got := extractID(tt.cgroupLine)
+			got := regexID.FindString(tt.cgroupLine)
 			require.Equal(t, tt.want, got)
 		})
 	}