Repository: kubernetes-sigs/security-profiles-operator
Pull request #536 (2021-07-12 13:32:53)

Use context for GRPC client connection
#### What type of PR is this?

/kind cleanup


#### What this PR does / why we need it:
We now use a new utility function to create a context with a
corresponding timeout for connections to the GRPC server.

#### 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
-->
None
#### 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/536#" 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":[]} -->
codecov-commenter: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/536?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#536](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/536?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (adadb01) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/c2d8c06e627462010ee126fa603e875948d6e2f6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (c2d8c06) will **decrease** coverage by `0.33%`.
> The diff coverage is `66.66%`.


```diff
@@            Coverage Diff             @@
##           master     #536      +/-   ##
==========================================
- Coverage   44.08%   43.75%   -0.34%     
==========================================
  Files          25       25              
  Lines        1311     1312       +1     
==========================================
- Hits          578      574       -4     
- Misses        696      699       +3     
- Partials       37       39       +2     
```


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 63ec08442..c7931ff6b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,7 +5,7 @@ on:
       - master
   pull_request:
 env:
-  GO_VERSION: '1.16'
+  GO_VERSION: '1.16.6'
 jobs:
   macos:
     runs-on: macos-latest
diff --git a/Makefile b/Makefile
index 0f7dcb482..2de251947 100644
--- a/Makefile
+++ b/Makefile
@@ -185,16 +185,24 @@ update-proto: $(BUILD_DIR)/protoc-gen-go $(BUILD_DIR)/protoc-gen-go-grpc ## Upda
 			--go-grpc_opt=paths=source_relative \
 			api/server/api.proto
 
-.PHONY: vagrant-up
-vagrant-up: ## Boot the vagrant based test VM
+define vagrant-up
 	if [ ! -f image.tar ]; then \
 		make image IMAGE=$(IMAGE) && \
 		$(CONTAINER_RUNTIME) save -o image.tar $(IMAGE); \
 	fi
-	ln -sf hack/ci/Vagrantfile .
+	ln -sf hack/ci/Vagrantfile-$(1) Vagrantfile
 	# Retry in case provisioning failed because of some temporarily unavailable
 	# remote resource (like the VM image)
 	vagrant up || vagrant up || vagrant up
+endef
+
+.PHONY: vagrant-up-fedora
+vagrant-up-fedora: ## Boot the Vagrant Fedora based test VM
+	$(call vagrant-up,fedora)
+
+.PHONY: vagrant-up-ubuntu
+vagrant-up-ubuntu: ## Boot the Vagrant Ubuntu based test VM
+	$(call vagrant-up,ubuntu)
 
 $(BUILD_DIR)/mdtoc: $(BUILD_DIR)
 	$(call go-build,./vendor/sigs.k8s.io/mdtoc)
diff --git a/dependencies.yaml b/dependencies.yaml
index e5d1ced1c..79ed4cd59 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -1,12 +1,14 @@
 dependencies:
   - name: go
-    version: 1.16
+    version: 1.16.6
     refPaths:
     - path: .github/workflows/build.yml
       match: GO_VERSION
     - path: .github/workflows/test.yml
       match: GO_VERSION
-    - path: hack/ci/Vagrantfile
+    - path: hack/ci/Vagrantfile-fedora
+      match: GO_VERSION
+    - path: hack/ci/Vagrantfile-ubuntu
       match: GO_VERSION
 
   - name: golangci-lint
@@ -62,9 +64,15 @@ dependencies:
   - name: e2e-fedora
     version: 33
     refPaths:
-    - path: hack/ci/Vagrantfile
+    - path: hack/ci/Vagrantfile-fedora
       match: fedora
 
+  - name: e2e-ubuntu
+    version: 2004
+    refPaths:
+    - path: hack/ci/Vagrantfile-ubuntu
+      match: ubuntu
+
   - name: debian-base
     version: 1.6.0
     refPaths:
diff --git a/hack/ci/Vagrantfile b/hack/ci/Vagrantfile-fedora
similarity index 98%
rename from hack/ci/Vagrantfile
rename to hack/ci/Vagrantfile-fedora
index 8601de244..f38b977be 100644
--- a/hack/ci/Vagrantfile
+++ b/hack/ci/Vagrantfile-fedora
@@ -21,7 +21,7 @@ Vagrant.configure("2") do |config|
     sh.inline = <<~SHELL
       set -euxo pipefail
 
-      export GO_VERSION=1.16
+      GO_VERSION=1.16.6
       curl -sSfL -o- https://dl.google.com/go/go"$GO_VERSION".linux-amd64.tar.gz |
         tar xfz - -C /usr/local
 
diff --git a/hack/ci/Vagrantfile-ubuntu b/hack/ci/Vagrantfile-ubuntu
new file mode 100644
index 000000000..1529c2e80
--- /dev/null
+++ b/hack/ci/Vagrantfile-ubuntu
@@ -0,0 +1,60 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+  config.vm.box = "generic/ubuntu2004"
+  memory = 6144
+  cpus = 4
+
+  config.vm.provider :virtualbox do |v|
+    v.memory = memory
+    v.cpus = cpus
+  end
+
+  config.vm.provider :libvirt do |v|
+    v.memory = memory
+    v.cpus = cpus
+  end
+
+  config.vm.synced_folder ".", "/vagrant"
+  config.vm.provision "docker"
+
+  config.vm.provision "install-dependencies", type: "shell", run: "once" do |sh|
+    sh.inline = <<~SHELL
+      set -euxo pipefail
+
+      # golang
+      GO_VERSION=1.16.6
+      curl -sSfL -o- https://golang.org/dl/go$GO_VERSION.linux-amd64.tar.gz | tar xfz - -C /usr/local
+
+      # Kubernetes
+      curl -sSfL https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
+      echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
+      apt-get update
+      apt-get install -y build-essential kubelet kubeadm kubectl
+
+      # Disable kernel print rate limiting for syslog messaging
+      sysctl -w kernel.printk_ratelimit=0
+      sysctl -w kernel.printk_ratelimit_burst=0
+
+      # Setup cluster
+      IP=`ip route get 1.2.3.4 | cut -d ' ' -f7 | tr -d '[:space:]'`
+      NODENAME=$(hostname -s)
+      swapoff -a
+      kubeadm init --apiserver-cert-extra-sans=$IP  --node-name $NODENAME
+
+      # Load pre-built image
+      docker load -i /vagrant/image.tar
+
+      # Setup kubectl
+      mkdir /home/vagrant/.kube
+      cp /etc/kubernetes/admin.conf /home/vagrant/.kube/config
+      chown -R vagrant:vagrant /home/vagrant/.kube
+
+      # Configure cluster
+      export KUBECONFIG=/etc/kubernetes/admin.conf
+      kubectl taint nodes --all node-role.kubernetes.io/master-
+      kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
+    SHELL
+  end
+end
diff --git a/hack/ci/env.sh b/hack/ci/env-fedora.sh
similarity index 100%
rename from hack/ci/env.sh
rename to hack/ci/env-fedora.sh
diff --git a/hack/ci/env-ubuntu.sh b/hack/ci/env-ubuntu.sh
new file mode 100755
index 000000000..bce83cdab
--- /dev/null
+++ b/hack/ci/env-ubuntu.sh
@@ -0,0 +1,20 @@
+# 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.
+
+export PATH="/usr/local/go/bin:$PATH"
+export GOPATH="$HOME/go"
+export GOBIN="$GOPATH/bin"
+
+# Added for faster debugging with lower verbosity
+alias k=kubectl
diff --git a/hack/ci/install-kubernetes.sh b/hack/ci/install-kubernetes.sh
index b6943f917..282383150 100755
--- a/hack/ci/install-kubernetes.sh
+++ b/hack/ci/install-kubernetes.sh
@@ -15,7 +15,7 @@
 
 set -euo pipefail
 
-ENVFILE=$(dirname "${BASH_SOURCE[0]}")/env.sh
+ENVFILE=$(dirname "${BASH_SOURCE[0]}")/env-fedora.sh
 . "$ENVFILE"
 
 K8SPATH="$GOPATH/src/k8s.io"
diff --git a/hack/ci/run-fedora.sh b/hack/ci/run-fedora.sh
new file mode 100755
index 000000000..4927f2790
--- /dev/null
+++ b/hack/ci/run-fedora.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+# 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.
+
+set -euo pipefail
+
+exec vagrant ssh -- sudo "bash -c 'cd /vagrant && . hack/ci/env-fedora.sh && $*'"
diff --git a/hack/ci/run.sh b/hack/ci/run-ubuntu.sh
similarity index 89%
rename from hack/ci/run.sh
rename to hack/ci/run-ubuntu.sh
index 6967eb20b..75d702d81 100755
--- a/hack/ci/run.sh
+++ b/hack/ci/run-ubuntu.sh
@@ -15,4 +15,4 @@
 
 set -euo pipefail
 
-exec vagrant ssh -- sudo "bash -c 'cd /vagrant && . hack/ci/env.sh && $*'"
+exec vagrant ssh -- "bash -c 'cd /vagrant && . hack/ci/env-ubuntu.sh && $*'"
diff --git a/installation-usage.md b/installation-usage.md
index c57923cf9..b2e9d264c 100644
--- a/installation-usage.md
+++ b/installation-usage.md
@@ -651,7 +651,13 @@ use the log enrichment feature:
 - [auditd][auditd] needs to run and has to be configured to log into
   `/var/log/audit/audit.log`
 - [syslog][syslog] can be used as fallback to auditd and needs to log into
-  `/var/log/syslog`
+  `/var/log/syslog`. Depending on the system configuration, a printk rate limiting may be
+  in place which has direct influence on the log enrichment. To diable the rate
+  limiting, set the following sysctls:
+  ```
+  > sysctl -w kernel.printk_ratelimit=0
+  > sysctl -w kernel.printk_ratelimit_burst=0
+  ```
 
 [auditd]: https://man7.org/linux/man-pages/man8/auditd.8.html
 [syslog]: https://man7.org/linux/man-pages/man3/syslog.3.html
diff --git a/internal/pkg/daemon/enricher/enricher.go b/internal/pkg/daemon/enricher/enricher.go
index 236f2f5ef..889df9bc5 100644
--- a/internal/pkg/daemon/enricher/enricher.go
+++ b/internal/pkg/daemon/enricher/enricher.go
@@ -76,10 +76,11 @@ func (e *Enricher) Run() error {
 	e.logger.Info("Starting log-enricher on node: " + nodeName)
 
 	e.logger.Info("Connecting to local GRPC server")
-	conn, err := e.impl.Dial()
+	conn, cancel, err := e.impl.Dial()
 	if err != nil {
 		return errors.Wrap(err, "connecting to local GRPC server")
 	}
+	defer cancel()
 	defer e.impl.Close(conn)
 	client := api.NewSecurityProfilesOperatorClient(conn)
 
diff --git a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
index 59ad5f4b1..13df06fa3 100644
--- a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
+++ b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
@@ -18,6 +18,7 @@ limitations under the License.
 package enricherfakes
 
 import (
+	"context"
 	"os"
 	"sync"
 	"time"
@@ -43,17 +44,19 @@ type FakeImpl struct {
 	closeReturnsOnCall map[int]struct {
 		result1 error
 	}
-	DialStub        func() (*grpc.ClientConn, error)
+	DialStub        func() (*grpc.ClientConn, context.CancelFunc, error)
 	dialMutex       sync.RWMutex
 	dialArgsForCall []struct {
 	}
 	dialReturns struct {
 		result1 *grpc.ClientConn
-		result2 error
+		result2 context.CancelFunc
+		result3 error
 	}
 	dialReturnsOnCall map[int]struct {
 		result1 *grpc.ClientConn
-		result2 error
+		result2 context.CancelFunc
+		result3 error
 	}
 	GetenvStub        func(string) string
 	getenvMutex       sync.RWMutex
@@ -281,7 +284,7 @@ func (fake *FakeImpl) CloseReturnsOnCall(i int, result1 error) {
 	}{result1}
 }
 
-func (fake *FakeImpl) Dial() (*grpc.ClientConn, error) {
+func (fake *FakeImpl) Dial() (*grpc.ClientConn, context.CancelFunc, error) {
 	fake.dialMutex.Lock()
 	ret, specificReturn := fake.dialReturnsOnCall[len(fake.dialArgsForCall)]
 	fake.dialArgsForCall = append(fake.dialArgsForCall, struct {
@@ -294,9 +297,9 @@ func (fake *FakeImpl) Dial() (*grpc.ClientConn, error) {
 		return stub()
 	}
 	if specificReturn {
-		return ret.result1, ret.result2
+		return ret.result1, ret.result2, ret.result3
 	}
-	return fakeReturns.result1, fakeReturns.result2
+	return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
 }
 
 func (fake *FakeImpl) DialCallCount() int {
@@ -305,36 +308,39 @@ func (fake *FakeImpl) DialCallCount() int {
 	return len(fake.dialArgsForCall)
 }
 
-func (fake *FakeImpl) DialCalls(stub func() (*grpc.ClientConn, error)) {
+func (fake *FakeImpl) DialCalls(stub func() (*grpc.ClientConn, context.CancelFunc, error)) {
 	fake.dialMutex.Lock()
 	defer fake.dialMutex.Unlock()
 	fake.DialStub = stub
 }
 
-func (fake *FakeImpl) DialReturns(result1 *grpc.ClientConn, result2 error) {
+func (fake *FakeImpl) DialReturns(result1 *grpc.ClientConn, result2 context.CancelFunc, result3 error) {
 	fake.dialMutex.Lock()
 	defer fake.dialMutex.Unlock()
 	fake.DialStub = nil
 	fake.dialReturns = struct {
 		result1 *grpc.ClientConn
-		result2 error
-	}{result1, result2}
+		result2 context.CancelFunc
+		result3 error
+	}{result1, result2, result3}
 }
 
-func (fake *FakeImpl) DialReturnsOnCall(i int, result1 *grpc.ClientConn, result2 error) {
+func (fake *FakeImpl) DialReturnsOnCall(i int, result1 *grpc.ClientConn, result2 context.CancelFunc, result3 error) {
 	fake.dialMutex.Lock()
 	defer fake.dialMutex.Unlock()
 	fake.DialStub = nil
 	if fake.dialReturnsOnCall == nil {
 		fake.dialReturnsOnCall = make(map[int]struct {
 			result1 *grpc.ClientConn
-			result2 error
+			result2 context.CancelFunc
+			result3 error
 		})
 	}
 	fake.dialReturnsOnCall[i] = struct {
 		result1 *grpc.ClientConn
-		result2 error
-	}{result1, result2}
+		result2 context.CancelFunc
+		result3 error
+	}{result1, result2, result3}
 }
 
 func (fake *FakeImpl) Getenv(arg1 string) string {
diff --git a/internal/pkg/daemon/enricher/impl.go b/internal/pkg/daemon/enricher/impl.go
index c6f552163..f63bb580c 100644
--- a/internal/pkg/daemon/enricher/impl.go
+++ b/internal/pkg/daemon/enricher/impl.go
@@ -40,7 +40,7 @@ type defaultImpl struct{}
 type impl interface {
 	SetTTL(cache ttlcache.SimpleCache, ttl time.Duration) error
 	Getenv(key string) string
-	Dial() (*grpc.ClientConn, error)
+	Dial() (*grpc.ClientConn, context.CancelFunc, error)
 	Close(*grpc.ClientConn) error
 	TailFile(filename string, config tail.Config) (*tail.Tail, error)
 	Lines(tailFile *tail.Tail) chan *tail.Line
@@ -73,8 +73,8 @@ func (d *defaultImpl) Getenv(key string) string {
 	return os.Getenv(key)
 }
 
-func (d *defaultImpl) Dial() (*grpc.ClientConn, error) {
-	return grpc.Dial(server.Addr(), grpc.WithInsecure())
+func (d *defaultImpl) Dial() (*grpc.ClientConn, context.CancelFunc, error) {
+	return server.Dial()
 }
 
 func (d *defaultImpl) Close(conn *grpc.ClientConn) error {
diff --git a/internal/pkg/daemon/profilerecorder/profilerecorder.go b/internal/pkg/daemon/profilerecorder/profilerecorder.go
index ed4d7dbd9..b3732466d 100644
--- a/internal/pkg/daemon/profilerecorder/profilerecorder.go
+++ b/internal/pkg/daemon/profilerecorder/profilerecorder.go
@@ -33,7 +33,6 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/resource"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
-	"google.golang.org/grpc"
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -137,10 +136,11 @@ func (r *RecorderReconciler) Setup(
 	}
 
 	r.log.Info("Connecting to local GRPC server")
-	conn, err := grpc.Dial(server.Addr(), grpc.WithInsecure())
+	conn, cancel, err := server.Dial()
 	if err != nil {
 		return errors.Wrap(err, "connecting to local GRPC server")
 	}
+	defer cancel()
 
 	r.grpcClient = api.NewSecurityProfilesOperatorClient(conn)
 	r.client = mgr.GetClient()
diff --git a/internal/pkg/daemon/server/server.go b/internal/pkg/daemon/server/server.go
index 7f5446a5a..0850ef4ac 100644
--- a/internal/pkg/daemon/server/server.go
+++ b/internal/pkg/daemon/server/server.go
@@ -21,6 +21,7 @@ import (
 	"io"
 	"net"
 	"sync"
+	"time"
 
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
@@ -32,7 +33,8 @@ import (
 )
 
 const (
-	maxMsgSize = 16 * 1024 * 1024
+	defaultTimeout time.Duration = 20 * time.Second
+	maxMsgSize                   = 16 * 1024 * 1024
 )
 
 // Server is the main structure of this package.
@@ -53,7 +55,7 @@ func New(logger logr.Logger, met *metrics.Metrics) *Server {
 
 // Start runs the server in the background.
 func (s *Server) Start() error {
-	listener, err := net.Listen("tcp", Addr())
+	listener, err := net.Listen("tcp", addr())
 	if err != nil {
 		return errors.Wrap(err, "create listener")
 	}
@@ -74,8 +76,20 @@ func (s *Server) Start() error {
 	return nil
 }
 
-// Addr returns the default server listening address.
-func Addr() string {
+// Dial can be used to connect to the default GRPC server by creating a new
+// client.
+func Dial() (*grpc.ClientConn, context.CancelFunc, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
+	conn, err := grpc.DialContext(ctx, addr(), grpc.WithInsecure())
+	if err != nil {
+		cancel()
+		return nil, nil, errors.Wrap(err, "GRPC dial")
+	}
+	return conn, cancel, nil
+}
+
+// addr returns the default server listening address.
+func addr() string {
 	return net.JoinHostPort("localhost", "9111")
 }
 
diff --git a/internal/pkg/manager/spod/bindata/spod.go b/internal/pkg/manager/spod/bindata/spod.go
index da6bdbc48..0d2e5d22a 100644
--- a/internal/pkg/manager/spod/bindata/spod.go
+++ b/internal/pkg/manager/spod/bindata/spod.go
@@ -371,6 +371,8 @@ semodule -i /opt/spo-profiles/selinuxd.cil
 						SecurityContext: &v1.SecurityContext{
 							ReadOnlyRootFilesystem: &truly,
 							Privileged:             &truly,
+							RunAsUser:              &userRoot,
+							RunAsGroup:             &userRoot,
 							SELinuxOptions: &v1.SELinuxOptions{
 								// TODO(pjbgf): Use a more restricted selinux type
 								Type: "spc_t",


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 35f692640..e9ee5578e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,7 +5,7 @@ on:
       - master
   pull_request:
 env:
-  GO_VERSION: '1.16'
+  GO_VERSION: '1.16.6'
 jobs:
   analyze:
     runs-on: ubuntu-latest
@@ -46,21 +46,52 @@ jobs:
           name: image
           path: image.tar
 
-  e2e:
+  e2e-fedora:
     needs: image
     runs-on: macos-10.15
     timeout-minutes: 90
     env:
-      RUN: ./hack/ci/run.sh
+      RUN: ./hack/ci/run-fedora.sh
     steps:
       - uses: actions/checkout@v2
+      - uses: actions/cache@v2
+        with:
+          path: |
+            ~/.vagrant.d/boxes
+          key: e2e-fedora-${{ hashFiles('hack/ci/Vagrantfile-fedora') }}
+          restore-keys: e2e-fedora-
+      - uses: actions/download-artifact@v2
+        with:
+          name: image
+          path: .
+      - name: Boot Virtual Machine
+        run: make vagrant-up-fedora
+      - name: Show environment information
+        run: $RUN kubectl get nodes -o wide
+      - name: Run E2E tests
+        run: $RUN hack/ci/e2e-fedora.sh
+
+  e2e-ubuntu:
+    needs: image
+    runs-on: macos-10.15
+    timeout-minutes: 90
+    env:
+      RUN: ./hack/ci/run-ubuntu.sh
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/cache@v2
+        with:
+          path: |
+            ~/.vagrant.d/boxes
+          key: e2e-ubuntu-${{ hashFiles('hack/ci/Vagrantfile-ubuntu') }}
+          restore-keys: e2e-ubuntu-
       - uses: actions/download-artifact@v2
         with:
           name: image
           path: .
       - name: Boot Virtual Machine
-        run: make vagrant-up
+        run: make vagrant-up-ubuntu
       - name: Show environment information
         run: $RUN kubectl get nodes -o wide
       - name: Run E2E tests
-        run: $RUN hack/ci/e2e.sh
+        run: $RUN hack/ci/e2e-ubuntu.sh
diff --git a/hack/ci/e2e.sh b/hack/ci/e2e-fedora.sh
similarity index 100%
rename from hack/ci/e2e.sh
rename to hack/ci/e2e-fedora.sh
diff --git a/hack/ci/e2e-ubuntu.sh b/hack/ci/e2e-ubuntu.sh
new file mode 100755
index 000000000..580aa8ac6
--- /dev/null
+++ b/hack/ci/e2e-ubuntu.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# 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.
+
+set -euo pipefail
+
+export E2E_CLUSTER_TYPE=vanilla
+export E2E_TEST_LOG_ENRICHER=true
+export E2E_TEST_SECCOMP=false
+
+make test-e2e
diff --git a/internal/pkg/daemon/enricher/container_test.go b/internal/pkg/daemon/enricher/container_test.go
index 2e5b4ca7c..189f78a8c 100644
--- a/internal/pkg/daemon/enricher/container_test.go
+++ b/internal/pkg/daemon/enricher/container_test.go
@@ -30,19 +30,25 @@ func TestExtractContainerID(t *testing.T) {
 		want       string
 	}{
 		{
-			"Should extract crio ID",
-			`4:net_cls,net_prio:/kubepods/besteffort/pod26ba375c-2266-4ecc-bf2d-b626db8762af/` +
-				`crio-af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a`,
+			"Should extract CRI-O ID",
+			"4:net_cls,net_prio:/kubepods/besteffort/pod26ba375c-2266-4ecc-bf2d-b626db8762af/" +
+				"crio-af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a",
 			"af208fd68bf39a07a439ed0c9b6609b9ae63ecd8a5f1a2af3e0db48b945b320a",
 		},
+		{
+			"Should extract containerd/Docker ID",
+			"12:cpu,cpuacct:/kubepods/burstable/poda201f46d-151a-4701-8f24-314bea77df79/" +
+				"b469ca5b54e01e7724b7a990f01d54f571dd7669b87851a87bd8b849c438c580",
+			"b469ca5b54e01e7724b7a990f01d54f571dd7669b87851a87bd8b849c438c580",
+		},
 		{
 			"Should return empty when not found",
-			`0::/system.slice/crio.service`,
+			"0::/system.slice/crio.service",
 			"",
 		},
 		{
-			"Should extract crio ID ending with .scope",
-			`0::/system.slice/crio-conmon-5819a498721cf8bb7e334809c9e48aa310bfc98801eb8017034ad17fb0749920.scope`,
+			"Should extract CRI-O ID ending with .scope",
+			"0::/system.slice/crio-conmon-5819a498721cf8bb7e334809c9e48aa310bfc98801eb8017034ad17fb0749920.scope",
 			"5819a498721cf8bb7e334809c9e48aa310bfc98801eb8017034ad17fb0749920",
 		},
 	}
diff --git a/internal/pkg/daemon/enricher/enricher_test.go b/internal/pkg/daemon/enricher/enricher_test.go
index 4f20282a1..517ad8705 100644
--- a/internal/pkg/daemon/enricher/enricher_test.go
+++ b/internal/pkg/daemon/enricher/enricher_test.go
@@ -133,7 +133,7 @@ func TestRun(t *testing.T) {
 			runAsync: false,
 			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
 				mock.GetenvReturns(node)
-				mock.DialReturns(nil, errTest)
+				mock.DialReturns(nil, nil, errTest)
 			},
 			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
 				require.NotNil(t, err)
@@ -143,6 +143,7 @@ func TestRun(t *testing.T) {
 			runAsync: false,
 			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
 				mock.GetenvReturns(node)
+				mock.DialReturns(nil, func() {}, errTest)
 				mock.MetricsAuditIncReturns(nil, errTest)
 			},
 			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
@@ -153,6 +154,7 @@ func TestRun(t *testing.T) {
 			runAsync: false,
 			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
 				mock.GetenvReturns(node)
+				mock.DialReturns(nil, func() {}, errTest)
 				mock.TailFileReturns(nil, errTest)
 			},
 			assert: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line, err error) {
@@ -163,6 +165,7 @@ func TestRun(t *testing.T) {
 			runAsync: false,
 			prepare: func(mock *enricherfakes.FakeImpl, lineChan chan *tail.Line) {
 				mock.GetenvReturns(node)
+				mock.DialReturns(nil, func() {}, errTest)
 				close(lineChan)
 				mock.LinesReturns(lineChan)
 				mock.ReasonReturns(errTest)
diff --git a/test/e2e_test.go b/test/e2e_test.go
index ad6f48bb3..269475cd3 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -28,6 +28,7 @@ import (
 
 	"sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
 	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 )
 
 const (
@@ -123,13 +124,16 @@ func (e *e2e) TestSecurityProfilesOperator() {
 		e.testCaseProfileBinding(nodes)
 	})
 
-	e.Run("cluster-wide: Seccomp: Verify profile recording", func() {
+	e.Run("cluster-wide: Seccomp: Verify profile recording hook", func() {
 		e.testCaseProfileRecordingStaticPodHook()
-		e.testCaseProfileRecordingStaticPodLogs()
 		e.testCaseProfileRecordingKubectlRunHook()
 		e.testCaseProfileRecordingMultiContainerHook()
-		e.testCaseProfileRecordingMultiContainerLogs()
 		e.testCaseProfileRecordingDeploymentHook()
+	})
+
+	e.Run("cluster-wide: Seccomp: Verify profile recording logs", func() {
+		e.testCaseProfileRecordingStaticPodLogs()
+		e.testCaseProfileRecordingMultiContainerLogs()
 		e.testCaseProfileRecordingDeploymentLogs()
 	})
 
@@ -265,6 +269,15 @@ func (e *e2e) deployOperator(manifest string) {
 	time.Sleep(defaultWaitTime)
 	e.waitInOperatorNSFor("condition=initialized", "pod", "-l", "name=spod")
 	e.waitInOperatorNSFor("condition=ready", "pod", "-l", "name=spod")
+	// Wait for spod to be available
+	for {
+		if res, err := command.New(
+			e.kubectlPath, "-n", config.OperatorName, "get", "spod", "spod",
+		).Run(); err == nil && res.Success() {
+			break
+		}
+		time.Sleep(time.Second)
+	}
 }
 
 func (e *e2e) cleanupOperator(manifest string) {
diff --git a/test/tc_log_enricher_test.go b/test/tc_log_enricher_test.go
index 47d8e963c..d3b17c372 100644
--- a/test/tc_log_enricher_test.go
+++ b/test/tc_log_enricher_test.go
@@ -85,7 +85,7 @@ spec:
 	}
 
 	e.logf("Checking log enricher output")
-	output := e.kubectlOperatorNS("logs", "ds/spod", "log-enricher")
+	output := e.kubectlOperatorNS("logs", "-l", "name=spod", "-c", "log-enricher")
 
 	e.Contains(output, `"msg"="audit"`)
 	e.Contains(output, `"type"="seccomp"`)
@@ -93,20 +93,20 @@ spec:
 	e.Contains(output, fmt.Sprintf(`"pod"=%q`, podName))
 	e.Contains(output, fmt.Sprintf(`"container"=%q`, containerName))
 	e.Contains(output, fmt.Sprintf(`"namespace"=%q`, namespace))
-	e.Contains(output, `"node"="127.0.0.1"`)
+	e.Regexp(`(?mU)\s"node"=".*"\s`, output)
 	e.Contains(output, `"pid"`)
 	e.Contains(output, `"timestamp"`)
 	e.Contains(output, `"syscallName"="listen"`)
 	e.Contains(output, `"syscallID"=50`)
 
 	metrics := e.getSpodMetrics()
-	e.Contains(metrics, fmt.Sprintf(`security_profiles_operator_seccomp_profile_audit_total{`+
+	e.Regexp(fmt.Sprintf(`(?m)security_profiles_operator_seccomp_profile_audit_total{`+
 		`container="%s",`+
 		`executable="/usr/sbin/nginx",`+
 		`namespace="%s",`+
-		`node="127.0.0.1",`+
+		`node=".*",`+
 		`pod="%s",`+
 		`syscall="listen"} 4`,
 		containerName, namespace, podName,
-	))
+	), metrics)
 }
diff --git a/test/tc_profilebindings_test.go b/test/tc_profilebindings_test.go
index 59ce7032f..1f67e1f90 100644
--- a/test/tc_profilebindings_test.go
+++ b/test/tc_profilebindings_test.go
@@ -24,6 +24,8 @@ import (
 )
 
 func (e *e2e) testCaseProfileBinding([]string) {
+	e.seccompOnlyTestCase()
+
 	const exampleProfilePath = "examples/seccompprofile.yaml"
 	const testBinding = `
 apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
diff --git a/test/tc_profilerecordings_test.go b/test/tc_profilerecordings_test.go
index 9e22672a4..72cdeb3c5 100644
--- a/test/tc_profilerecordings_test.go
+++ b/test/tc_profilerecordings_test.go
@@ -59,7 +59,6 @@ func (e *e2e) testCaseProfileRecordingStaticPodHook() {
 }
 
 func (e *e2e) testCaseProfileRecordingStaticPodLogs() {
-	e.profileRecordingTestCase()
 	e.logEnricherOnlyTestCase()
 	e.profileRecordingStaticPod(
 		exampleRecordingLogsPath,
@@ -139,7 +138,6 @@ func (e *e2e) testCaseProfileRecordingMultiContainerHook() {
 }
 
 func (e *e2e) testCaseProfileRecordingMultiContainerLogs() {
-	e.profileRecordingTestCase()
 	e.logEnricherOnlyTestCase()
 	e.profileRecordingMultiContainer(
 		exampleRecordingLogsPath,
@@ -151,8 +149,6 @@ func (e *e2e) testCaseProfileRecordingMultiContainerLogs() {
 func (e *e2e) profileRecordingMultiContainer(
 	recording string, waitConditions ...*regexp.Regexp,
 ) {
-	e.profileRecordingTestCase()
-
 	e.logf("Creating recording for multi container test")
 	e.kubectl("create", "-f", recording)
 
@@ -212,7 +208,6 @@ func (e *e2e) testCaseProfileRecordingDeploymentHook() {
 }
 
 func (e *e2e) testCaseProfileRecordingDeploymentLogs() {
-	e.profileRecordingTestCase()
 	e.logEnricherOnlyTestCase()
 	e.profileRecordingDeployment(
 		exampleRecordingLogsPath,
@@ -225,8 +220,6 @@ func (e *e2e) testCaseProfileRecordingDeploymentLogs() {
 func (e *e2e) profileRecordingDeployment(
 	recording string, waitConditions ...*regexp.Regexp,
 ) {
-	e.profileRecordingTestCase()
-
 	e.logf("Creating recording for deployment test")
 	e.kubectl("create", "-f", recording)
 
diff --git a/test/tc_redeploy_operator_test.go b/test/tc_redeploy_operator_test.go
index 48f383752..b19b408d9 100644
--- a/test/tc_redeploy_operator_test.go
+++ b/test/tc_redeploy_operator_test.go
@@ -17,6 +17,8 @@ limitations under the License.
 package e2e_test
 
 func (e *e2e) testCaseReDeployOperator([]string) {
+	e.seccompOnlyTestCase()
+
 	// Clean up the operator
 	e.cleanupOperator(manifest)
 
@@ -25,6 +27,8 @@ func (e *e2e) testCaseReDeployOperator([]string) {
 }
 
 func (e *e2e) testCaseReDeployNamespaceOperator([]string) {
+	e.seccompOnlyTestCase()
+
 	// Clean up the operator
 	e.cleanupOperator(namespaceManifest)