Repository: cilium/cilium
Pull request #10859 (2020-04-06 11:05:20)

CI monitor reading
We see flakes in tests that read from monitor. These are structured to search monitor output for specific substrings. Since monitor may take some time to start up, we can change how we read its output. The 3 tests now read this incrementally, and fail on a timeout. This should allow it to pass when monitor was simply slow to print.

I haven't seen the specific flake in a bunch of testing with this PR. Most test runs have failed, however, often in other tests. I'm fairly confident that I didn't break anything but I would prefer more clean passes. It does do better on GKE and that is where we see the flake the most, however.

@joestringer I think we once discussed this flake in a PR, so I pulled you in for review. Feel free to unassign yourself.


maintainer-s-little-helper[bot]: Please set the appropriate release note label.
maintainer-s-little-helper[bot]: Please set the appropriate release note label.
maintainer-s-little-helper[bot]: Please set the appropriate release note label.
maintainer-s-little-helper[bot]: Please set the appropriate release note label.
raybejjani: test-focus K8sDatapathConfig.*
coveralls: 
[![Coverage Status](https://coveralls.io/builds/29875910/badge)](https://coveralls.io/builds/29875910)

Coverage decreased (-0.01%) to 45.88% when pulling **4d209a723ec043ccbc70911460adc3e62e3ef886 on raybejjani:ci-monitor-reading** into **c3dee352f4ab5228b55cc22e23243f64d477f595 on cilium:master**.

raybejjani: test-focus K8sDatapathConfig.*
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-gke
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-gke
raybejjani: test-me-please
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-focus k8s.*
raybejjani: test-me-please
raybejjani: test-gke
raybejjani: test-focus K8sDatapathConfiguration.*
raybejjani: test-focus K8sDatapathConfiguration.*
raybejjani: test-focus K8sDatapathConfiguration.*
raybejjani: test-focus K8sDatapathConfiguration.*
raybejjani: test-focus K8sK8sDatapathConfig.*
raybejjani: test-focus K8sDatapathConfig.*
raybejjani: test-me-please
raybejjani: test-gke

Patch:
diff --git a/.dockerignore b/.dockerignore
index e21d69008b..0f2c34f66b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -38,7 +38,7 @@ coverage-all.html
 .idea/
 *.plist
 
-cilium/bash_autocomplete
+*_bash_completion
 *.swo
 outgoing
 
diff --git a/.gitignore b/.gitignore
index 0622bd019a..394b1d9d2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,7 +40,7 @@ coverage-all.html
 .idea/
 *.plist
 
-cilium/bash_autocomplete
+*_bash_completion
 *.swo
 outgoing
 
diff --git a/.travis.yml b/.travis.yml
index 9551df300d..dc5bba082e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@ dist: trusty
 sudo: required
 
 go:
-  - 1.14.1
+  - 1.14.2
 
 arch:
   - amd64
diff --git a/Dockerfile b/Dockerfile
index fc8cf93c8b..4f3f64c379 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,7 +13,7 @@ FROM quay.io/cilium/cilium-envoy:a3385205ad620550b35d3b0b651e40898386e6e3 as cil
 # versions to be built while allowing the new versions to make changes
 # that are not backwards compatible.
 #
-FROM quay.io/cilium/cilium-builder:2020-04-02 as builder
+FROM quay.io/cilium/cilium-builder:2020-04-16 as builder
 LABEL maintainer="maintainer@cilium.io"
 WORKDIR /go/src/github.com/cilium/cilium
 COPY . ./
@@ -37,7 +37,7 @@ RUN make LOCKDEBUG=$LOCKDEBUG PKG_BUILD=1 V=$V LIBNETWORK_PLUGIN=$LIBNETWORK_PLU
 # built while allowing the new versions to make changes that are not
 # backwards compatible.
 #
-FROM quay.io/cilium/cilium-runtime:2020-04-14
+FROM quay.io/cilium/cilium-runtime:2020-04-16
 LABEL maintainer="maintainer@cilium.io"
 COPY --from=builder /tmp/install /
 COPY --from=cilium-envoy / /
@@ -46,7 +46,7 @@ COPY plugins/cilium-cni/cni-uninstall.sh /cni-uninstall.sh
 COPY contrib/packaging/docker/init-container.sh /init-container.sh
 WORKDIR /home/cilium
 RUN groupadd -f cilium \
-    && echo ". /etc/profile.d/bash_completion.sh" >> /etc/bash.bashrc \
-    && sysctl -w kernel.core_pattern=/tmp/core.%e.%p.%t
+    && echo ". /etc/profile.d/bash_completion.sh" >> /etc/bash.bashrc
+
 ENV INITSYSTEM="SYSTEMD"
 CMD ["/usr/bin/cilium"]
diff --git a/Dockerfile.builder b/Dockerfile.builder
index 5fa2d3988f..d7495248f4 100644
--- a/Dockerfile.builder
+++ b/Dockerfile.builder
@@ -16,7 +16,7 @@ WORKDIR /go/src/github.com/cilium/cilium
 ENV GOROOT /usr/local/go
 ENV GOPATH /go
 ENV PATH "$GOROOT/bin:$GOPATH/bin:$PATH"
-ENV GO_VERSION 1.14.1
+ENV GO_VERSION 1.14.2
 
 #
 # Build dependencies
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 25f879de95..5f4f893df7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.quiet
 
 .PHONY: default clean builder-image cmdref epub latex html run-server stop-server
diff --git a/Documentation/install/upgrade.rst b/Documentation/install/upgrade.rst
index b9d0d5dec5..d53ccb5563 100644
--- a/Documentation/install/upgrade.rst
+++ b/Documentation/install/upgrade.rst
@@ -639,13 +639,13 @@ New ConfigMap Options
     will only automatically allow connectivity from the local node, thereby providing
     a better default security posture.
 
-    The option is enabled by
-    default for new deployments when generated via Helm, in order to gain the benefits
-    of improved security. The option can be disabled
-    in order to maintain full compatibility with Cilium 1.6.x policy enforcement.
-    **Be aware** that upgrading a cluster to 1.7.x by using helm to generate a new Cilium config
-    that leaves ``enable-remote-node-identity`` set as the default value
-    of ``true`` **can break network connectivity.**
+    The option is enabled by default for new deployments when generated via
+    Helm, in order to gain the benefits of improved security. The Helm option
+    is ``--set global.remoteNodeIdentity``. This option can be disabled in
+    order to maintain full compatibility with Cilium 1.6.x policy enforcement.
+    **Be aware** that upgrading a cluster to 1.7.x by using helm to generate a
+    new Cilium config that leaves ``enable-remote-node-identity`` set as the
+    default value of ``true`` **can break network connectivity.**
 
     The reason for this is that
     with Cilium 1.6.x, the source identity of ANY connection from a host-networking pod or from
@@ -663,16 +663,16 @@ New ConfigMap Options
     Cilium monitor with a source identity equal to 6 (the numeric value for the
     new ``remote-node`` identity.   For example:
 
+    ::
 
-    .. parsed-literal::
-
-      xx drop (Policy denied) flow 0x6d7b6dd0 to endpoint 1657, identity 6->51566: 172.16.9.243:47278 -> 172.16.8.21:9093 tcp SYN
-
+       xx drop (Policy denied) flow 0x6d7b6dd0 to endpoint 1657, identity 6->51566: 172.16.9.243:47278 -> 172.16.8.21:9093 tcp SYN
 
-    There are two ways to address this.  One can set ``enable-remote-node-identity=false``
-    to retain the Cilium 1.6.x behavior.  However, this is not ideal, as it means there is
-    no way to prevent communication between host-networking pods and Cilium-managed pods,
-    since all such connectivity is allowed automatically because it is from the ``host`` identity.
+    There are two ways to address this.  One can set
+    ``enable-remote-node-identity=false`` in the `ConfigMap` to retain the
+    Cilium 1.6.x behavior.  However, this is not ideal, as it means there is no
+    way to prevent communication between host-networking pods and
+    Cilium-managed pods, since all such connectivity is allowed automatically
+    because it is from the ``host`` identity.
 
     The other option is to keep ``enable-remote-node-identity=true`` and
     create policy rules that explicitly whitelist connections between
@@ -681,19 +681,21 @@ New ConfigMap Options
     such a rule is:
 
 
-    .. parsed-literal::
+    ::
+
+       apiVersion: "cilium.io/v2"
+       kind: CiliumNetworkPolicy
+       metadata:
+         name: "allow-from-remote-nodes"
+       spec:
+         endpointSelector:
+           matchLabels:
+             app: myapp
+         ingress:
+         - fromEntities:
+           - remote-node
 
-      apiVersion: "cilium.io/v2"
-      kind: CiliumNetworkPolicy
-      metadata:
-        name: "allow-from-remote-nodes"
-      spec:
-        endpointSelector:
-          matchLabels:
-            app: myapp
-        ingress:
-        - fromEntities:
-          - remote-node
+    See :ref:`policy-remote-node` for more examples of remote-node policies.
 
 
   * ``enable-well-known-identities`` has been added to control the
diff --git a/Documentation/kubernetes/requirements.rst b/Documentation/kubernetes/requirements.rst
index bf302bd133..5101c0abc6 100644
--- a/Documentation/kubernetes/requirements.rst
+++ b/Documentation/kubernetes/requirements.rst
@@ -45,6 +45,16 @@ the `Kubernets CNI network-plugins documentation <https://kubernetes.io/docs/con
 Mounted BPF filesystem
 ======================
 
+.. Note::
+
+        Some distributions mount the bpf filesystem automatically. Check if the
+        bpf filesystem is mounted by running the command.
+
+        .. code:: shell-session
+
+                  mount | grep /sys/fs/bpf
+                  # if present should output, e.g. "none on /sys/fs/bpf type bpf"...
+
 This step is **required for production** environments but optional for testing
 and development. It allows the ``cilium-agent`` to pin BPF resources to a
 persistent filesystem and make them persistent across restarts of the agent.
diff --git a/Documentation/policy/language.rst b/Documentation/policy/language.rst
index 8831872960..dc77cd99ae 100644
--- a/Documentation/policy/language.rst
+++ b/Documentation/policy/language.rst
@@ -327,7 +327,7 @@ Access to/from local host
 Allow all endpoints with the label ``env=dev`` to access the host that is
 serving the particular endpoint.
 
-.. note:: Kubernetes will automatically allow all communication from and to the
+.. note:: Kubernetes will automatically allow all communication from the
 	  local host of all local endpoints. You can run the agent with the
 	  option ``--allow-localhost=policy`` to disable this behavior which
 	  will give you control over this via policy.
@@ -346,6 +346,27 @@ serving the particular endpoint.
 
         .. literalinclude:: ../../examples/policies/l3/entities/host.json
 
+.. _policy-remote-node:
+
+Access to/from all nodes in the cluster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Allow all endpoints with the label ``env=dev`` to receive traffic from any host
+in the cluster that Cilium is running on.
+
+.. only:: html
+
+   .. tabs::
+     .. group-tab:: k8s YAML
+
+        .. literalinclude:: ../../examples/policies/l3/entities/nodes.yaml
+     .. group-tab:: JSON
+
+        .. literalinclude:: ../../examples/policies/l3/entities/nodes.json
+
+.. only:: epub or latex
+
+        .. literalinclude:: ../../examples/policies/l3/entities/nodes.json
 
 Access to/from outside cluster
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/GO_VERSION b/GO_VERSION
index 63e799cf45..a4cc55716f 100644
--- a/GO_VERSION
+++ b/GO_VERSION
@@ -1,1 +1,1 @@
-1.14.1
+1.14.2
diff --git a/Makefile b/Makefile
index 7953d0f705..64cb843105 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include Makefile.defs
 
 SUBDIRS_CILIUM_CONTAINER := proxylib envoy plugins/cilium-cni bpf cilium daemon cilium-health bugtool
@@ -5,9 +8,9 @@ ifdef LIBNETWORK_PLUGIN
 SUBDIRS_CILIUM_CONTAINER += plugins/cilium-docker
 endif
 SUBDIRS := $(SUBDIRS_CILIUM_CONTAINER) operator plugins tools hubble-proxy
-GOFILES_EVAL := $(subst _$(ROOT_DIR)/,,$(shell $(CGO_DISABLED) $(GOLIST) $(GO) list -e ./...))
+GOFILES_EVAL := $(subst _$(ROOT_DIR)/,,$(shell $(GO_LIST) -e ./...))
 GOFILES ?= $(GOFILES_EVAL)
-TESTPKGS_EVAL := $(subst github.com/cilium/cilium/,,$(shell $(CGO_DISABLED) $(GOLIST) $(GO) list -e ./... | grep -v '/api/v1\|/vendor\|/contrib' | grep -v -P 'test(?!/helpers/logutils)'))
+TESTPKGS_EVAL := $(subst github.com/cilium/cilium/,,$(shell $(GO_LIST) -e ./... | grep -v '/api/v1\|/vendor\|/contrib' | grep -v -P 'test(?!/helpers/logutils)'))
 TESTPKGS ?= $(TESTPKGS_EVAL)
 GOLANGVERSION := $(shell $(GO) version 2>/dev/null | grep -Eo '(go[0-9].[0-9])')
 GOLANG_SRCFILES := $(shell for pkg in $(subst github.com/cilium/cilium/,,$(GOFILES)); do find $$pkg -name *.go -print; done | grep -v vendor | sort | uniq)
@@ -89,7 +92,7 @@ tests-privileged:
 	# cilium-map-migrate is a dependency of some unit tests.
 	$(QUIET) $(MAKE) $(SUBMAKEOPTS) -C bpf cilium-map-migrate
 	$(QUIET)$(foreach pkg,$(PRIV_TEST_PKGS),\
-		$(GO) test $(GOFLAGS) $(TEST_LDFLAGS) github.com/cilium/cilium/$(pkg) $(GOTEST_PRIV_OPTS) || exit 1;)
+		$(GO_TEST) $(TEST_LDFLAGS) github.com/cilium/cilium/$(pkg) $(GOTEST_PRIV_OPTS) || exit 1;)
 
 start-kvstores:
 ifeq ($(SKIP_KVSTORES),"false")
@@ -151,7 +154,7 @@ endif
 	# hence will trigger an error of too many arguments. As a workaround, we
 	# have to process these packages in different subshells.
 	for pkg in $(patsubst %,github.com/cilium/cilium/%,$(TESTPKGS)); do \
-		$(_GO) test $(GOFLAGS) $(TEST_UNITTEST_LDFLAGS) $$pkg $(GOTEST_BASE) $(GOTEST_COVER_OPTS) || exit 1; \
+		$(GO_TEST) $(TEST_UNITTEST_LDFLAGS) $$pkg $(GOTEST_BASE) $(GOTEST_COVER_OPTS) || exit 1; \
 		tail -n +2 coverage.out >> coverage-all-tmp.out; \
 	done
 	$(MAKE) generate-cov
@@ -160,14 +163,14 @@ endif
 
 bench: start-kvstores
 	$(QUIET)$(foreach pkg,$(patsubst %,github.com/cilium/cilium/%,$(TESTPKGS)),\
-		$(GO) test $(GOFLAGS) $(TEST_UNITTEST_LDFLAGS) $(GOTEST_BASE) $(BENCHFLAGS) \
+		$(GO_TEST) $(TEST_UNITTEST_LDFLAGS) $(GOTEST_BASE) $(BENCHFLAGS) \
 			$(pkg) \
 		|| exit 1;)
 	$(MAKE) stop-kvstores
 
 bench-privileged:
 	$(QUIET)$(foreach pkg,$(patsubst %,github.com/cilium/cilium/%,$(TESTPKGS)),\
-		$(GO) test $(GOFLAGS) $(TEST_UNITTEST_LDFLAGS) $(GOTEST_BASE) $(BENCHFLAGS) \
+		$(GO_TEST) $(TEST_UNITTEST_LDFLAGS) $(GOTEST_BASE) $(BENCHFLAGS) \
 			-tags=privileged_tests $(pkg) \
 		|| exit 1;)
 
@@ -365,7 +368,7 @@ gofmt:
 
 govet:
 	@$(ECHO_CHECK) vetting all GOFILES...
-	$(QUIET) $(GOLIST) $(_GO) vet \
+	$(QUIET) $(GO_VET) \
     ./api/... \
     ./bugtool/... \
     ./cilium/... \
@@ -475,6 +478,8 @@ postcheck: build
 	$(QUIET)$(MAKE) $(SUBMAKEOPTS) -C Documentation update-cmdref check
 	@$(ECHO_CHECK) contrib/scripts/lock-check.sh
 	$(QUIET) contrib/scripts/lock-check.sh
+	@$(ECHO_CHECK) contrib/scripts/rand-check.sh
+	$(QUIET) contrib/scripts/rand-check.sh
 
 minikube:
 	$(QUIET) contrib/scripts/minikube.sh
diff --git a/Makefile.defs b/Makefile.defs
index 41ab42d518..181de58e11 100644
--- a/Makefile.defs
+++ b/Makefile.defs
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 SHELL := /bin/bash
 .SHELLFLAGS := -eu -o pipefail -c
 
@@ -14,16 +17,32 @@ LOCALSTATEDIR?=/var
 RUNDIR?=/var/run
 CONFDIR?=/etc
 
-CGO_DISABLED = CGO_ENABLED=0
-GOLIST ?= GO111MODULE=off
-ifeq ($(GO),)
-	GO = $(CGO_DISABLED) go
-	_GO = go
+INSTALL = install
+
+# Container engine
+export CONTAINER_ENGINE?=docker
+CONTAINER_ENGINE_FULL=$(QUIET)$(CONTAINER_ENGINE)
+
+# Set DOCKER_IMAGE_TAG with "latest" by default
+ifeq ($(DOCKER_IMAGE_TAG),)
+    DOCKER_IMAGE_TAG="latest"
+endif
+
+ifeq ($(shell uname -m),aarch64)
+    ETCD_IMAGE=quay.io/coreos/etcd:v3.2.17-arm64
 else
-	_GO = $(GO)
+    ETCD_IMAGE=quay.io/coreos/etcd:v3.2.17
 endif
 
-INSTALL = install
+GO ?= go
+
+# go build/test/clean flags
+# these are declared here so they are treated explicitly
+# as non-immediate variables
+GO_BUILD_FLAGS = 
+GO_TEST_FLAGS = 
+GO_CLEAN_FLAGS = 
+GO_BUILD_LDFLAGS = 
 
 VERSION = $(shell cat $(dir $(lastword $(MAKEFILE_LIST)))/VERSION)
 VERSION_MAJOR = $(shell cat $(dir $(lastword $(MAKEFILE_LIST)))/VERSION | cut -d. -f1)
@@ -33,8 +52,8 @@ ifneq ($(wildcard $(dir $(lastword $(MAKEFILE_LIST)))/.git),)
 else
 	GIT_VERSION = $(shell cat $(ROOT_DIR)/GIT_VERSION)
 endif
-BUILD = $(VERSION) $(GIT_VERSION) $(shell $(GO) version)
-GOLDFLAGS = -X "github.com/cilium/cilium/pkg/version.Version=$(BUILD)"
+FULL_BUILD_VERSION = $(VERSION) $(GIT_VERSION) $(shell $(GO) version)
+GO_BUILD_LDFLAGS += -X "github.com/cilium/cilium/pkg/version.Version=$(FULL_BUILD_VERSION)"
 
 ifeq ($(NOSTRIP),)
 	# Note: these options will not remove annotations needed for stack
@@ -42,23 +61,18 @@ ifeq ($(NOSTRIP),)
 	#
 	# -w: Omit the DWARF symbol table.
 	# -s: Omit the symbol table and debug information.
-	GOLDFLAGS += -s -w
+	GO_BUILD_LDFLAGS += -s -w
 endif
 
 CILIUM_ENVOY_SHA=$(shell grep -o "FROM.*cilium/cilium-envoy:[0-9a-fA-F]*" $(ROOT_DIR)/Dockerfile | cut -d : -f 2)
-GOLDFLAGS += -X "github.com/cilium/cilium/pkg/envoy.RequiredEnvoyVersionSHA=$(CILIUM_ENVOY_SHA)"
+GO_BUILD_LDFLAGS += -X "github.com/cilium/cilium/pkg/envoy.RequiredEnvoyVersionSHA=$(CILIUM_ENVOY_SHA)"
 
 BPF_FILES_EVAL := $(shell git ls-files $(ROOT_DIR)/bpf/ | grep -v .gitignore | tr "\n" ' ')
 BPF_FILES ?= $(BPF_FILES_EVAL)
 BPF_SRCFILES := $(subst ../,,$(BPF_FILES))
 
 CILIUM_DATAPATH_SHA=$(shell cat $(BPF_FILES) | sha1sum | awk '{print $$1}')
-GOLDFLAGS += -X "github.com/cilium/cilium/pkg/datapath/loader.DatapathSHA=$(CILIUM_DATAPATH_SHA)"
-
-# Set DOCKER_IMAGE_TAG with "latest" by default
-ifeq ($(DOCKER_IMAGE_TAG),)
-    DOCKER_IMAGE_TAG="latest"
-endif
+GO_BUILD_LDFLAGS += -X "github.com/cilium/cilium/pkg/datapath/loader.DatapathSHA=$(CILIUM_DATAPATH_SHA)"
 
 # Set -mod=vendor if running >= go 1.13 or if GO111MODULE is set.
 # A go build is being executed with go modules if:
@@ -66,37 +80,37 @@ endif
 # * The go command is invoked in a directory outside of the $GOPATH/src tree
 #   and the environment variable GO111MODULE unset (or explicitly set to 'auto').
 ifeq ($(GO111MODULE),on)
-    GOFLAGS ?= -mod=vendor
-    GOCLEAN ?= -mod=vendor
+    GO_BUILD_FLAGS += -mod=vendor
+    GO_TEST_FLAGS += -mod=vendor
+    GO_CLEAN_FLAGS += -mod=vendor
 endif
+
 # Compile with '-mod=vendor' if go >= 1.13
 GO_MAJOR_VERSION_GE_1 := $(shell expr `$(GO) version | grep -E 'go[0-9]{1}+' -o | sed 's/go//g'` \>= 1)
 ifeq ($(GO_MAJOR_VERSION_GE_1),1)
     GO_MINOR_VERSION_GE_13 := $(shell expr `$(GO) version | grep -E 'go[^ ]+' -o | sed 's/go1.//g'` \>= 13)
     ifeq ($(GO_MINOR_VERSION_GE_13),1)
-        GOFLAGS ?= -mod=vendor
-        GOCLEAN ?= -mod=vendor
+        GO_BUILD_FLAGS += -mod=vendor
+        GO_TEST_FLAGS += -mod=vendor
+        GO_CLEAN_FLAGS += -mod=vendor
     endif
 endif
 
-GOBUILD = $(GOFLAGS) -ldflags '$(GOLDFLAGS)' $(EXTRA_GOBUILD_FLAGS)
-
-# Uncomment to enable race detection
-#GOBUILD += -race
-
-# Uncomment to enable deadlock detection
-#GOBUILD += -tags lockdebug
+ifneq ($(RACE),)
+    GO_BUILD_FLAGS += -race
+    GO_TEST_FLAGS += -race
+endif
 
 ifneq ($(LOCKDEBUG),)
-    GOBUILD += -tags lockdebug
+    GO_BUILD_FLAGS += -tags lockdebug
 endif
 
-# Container engine
-export CONTAINER_ENGINE?=docker
-CONTAINER_ENGINE_FULL=$(QUIET)$(CONTAINER_ENGINE)
+GO_BUILD_FLAGS += -ldflags '$(GO_BUILD_LDFLAGS) $(EXTRA_GO_BUILD_LDFLAGS)' $(EXTRA_GO_BUILD_FLAGS)
 
-ifeq ($(shell uname -m),aarch64)
-    ETCD_IMAGE=quay.io/coreos/etcd:v3.2.17-arm64
-else
-    ETCD_IMAGE=quay.io/coreos/etcd:v3.2.17
-endif
+GO_BUILD = CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS)
+GO_BUILD_WITH_CGO = CGO_ENABLED=1 $(GO) build $(GO_BUILD_FLAGS)
+GO_TEST = $(GO) test $(GO_TEST_FLAGS)
+GO_CLEAN = $(GO) clean $(GO_TEST_FLAGS)
+# TODO: remove `GO111MODULE=off` once Go 1.13 is deprecated by Go maintainers
+GO_VET = GO111MODULE=off $(GO) vet
+GO_LIST = GO111MODULE=off $(GO) list
diff --git a/Makefile.quiet b/Makefile.quiet
index e606d68a23..1a78ba4ee8 100644
--- a/Makefile.quiet
+++ b/Makefile.quiet
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 ifeq ($(ROOT_DIR),)
 	ROOT_DIR ?= $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 	RELATIVE_DIR ?= $(shell echo $(realpath .) | sed "s;$(ROOT_DIR)[/]*;;")
diff --git a/api/v1/Makefile b/api/v1/Makefile
index 0c69aba610..89affb67d9 100644
--- a/api/v1/Makefile
+++ b/api/v1/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 PROTOC ?= protoc
 
 HUBBLE_PROTO_SOURCES := \
diff --git a/bpf/Makefile b/bpf/Makefile
index 1d57dd08e6..9cc943f7ae 100644
--- a/bpf/Makefile
+++ b/bpf/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
 .PHONY: all subdirs check preprocess assembly install clean
diff --git a/bpf/Makefile.bpf b/bpf/Makefile.bpf
index 1412030b2b..292d7e86c6 100644
--- a/bpf/Makefile.bpf
+++ b/bpf/Makefile.bpf
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 FLAGS := -I$(ROOT_DIR)/bpf/include -I$(ROOT_DIR)/bpf -D__NR_CPUS__=$(shell nproc) -O2 -g
 
 CLANG_FLAGS := ${FLAGS} -target bpf -emit-llvm
diff --git a/bpf/sockops/Makefile b/bpf/sockops/Makefile
index 19d9ad40e4..4697445884 100644
--- a/bpf/sockops/Makefile
+++ b/bpf/sockops/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
 .PHONY: all assembly check preprocess clean
diff --git a/bugtool/Makefile b/bugtool/Makefile
index 9f5fcc72c8..91508f97f1 100644
--- a/bugtool/Makefile
+++ b/bugtool/Makefile
@@ -1,31 +1,22 @@
 # Copyright 2017-2020 Authors of Cilium
-#
-# 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.
+# SPDX-License-Identifier: Apache-2.0
 
 include ../Makefile.defs
 
-TARGET=cilium-bugtool
-SOURCES := $(shell find ../common . -name '*.go')
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := cilium-bugtool
+
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
 	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f .cilium-bugtool.config $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
diff --git a/cilium-dev.Dockerfile b/cilium-dev.Dockerfile
index 5627af974e..6f77a8fff1 100644
--- a/cilium-dev.Dockerfile
+++ b/cilium-dev.Dockerfile
@@ -28,7 +28,6 @@ RUN for i in proxylib envoy plugins/cilium-cni bpf cilium daemon cilium-health b
             SKIP_DOCS=true DESTDIR= \
             make -C $i install; done
 RUN groupadd -f cilium \
-    && echo ". /etc/profile.d/bash_completion.sh" >> /etc/bash.bashrc \
-    && sysctl -w kernel.core_pattern=/tmp/core.%e.%p.%t
+    && echo ". /etc/profile.d/bash_completion.sh" >> /etc/bash.bashrc
 ENV INITSYSTEM="SYSTEMD"
 CMD ["/usr/bin/cilium"]
diff --git a/cilium-dev.Dockerfile.dockerignore b/cilium-dev.Dockerfile.dockerignore
index 76952e1e91..266b986614 100644
--- a/cilium-dev.Dockerfile.dockerignore
+++ b/cilium-dev.Dockerfile.dockerignore
@@ -45,7 +45,7 @@ coverage-all.html
 .idea/
 *.plist
 
-cilium/bash_autocomplete
+*_bash_completion
 *.swo
 outgoing
 
diff --git a/cilium-docker-plugin.Dockerfile b/cilium-docker-plugin.Dockerfile
index 4bd52ac76d..fa4a4455d0 100644
--- a/cilium-docker-plugin.Dockerfile
+++ b/cilium-docker-plugin.Dockerfile
@@ -1,10 +1,9 @@
-FROM docker.io/library/golang:1.14.1 as builder
+FROM docker.io/library/golang:1.14.2 as builder
 LABEL maintainer="maintainer@cilium.io"
 ADD . /go/src/github.com/cilium/cilium
 WORKDIR /go/src/github.com/cilium/cilium/plugins/cilium-docker
 ARG LOCKDEBUG
-ARG V
-RUN make CGO_ENABLED=0 GOOS=linux LOCKDEBUG=$LOCKDEBUG PKG_BUILD=1 EXTRA_GOBUILD_FLAGS="-a -installsuffix cgo"
+RUN make LOCKDEBUG=$LOCKDEBUG
 
 FROM scratch
 LABEL maintainer="maintainer@cilium.io"
diff --git a/cilium-health/Makefile b/cilium-health/Makefile
index 29287f617d..b1ffd5dbed 100644
--- a/cilium-health/Makefile
+++ b/cilium-health/Makefile
@@ -1,31 +1,33 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-SUBDIRS = responder
-TARGET = cilium-health
+TARGET := cilium-health
+SUBDIRS := responder
 
-.PHONY: all $(SUBDIRS) install clean
+.PHONY: all $(TARGET) $(SUBDIRS) clean install
 
 all: $(TARGET) $(SUBDIRS)
 
-SOURCES := $(shell find ../api/v1/health ../pkg/health cmd . \
-	\( -name '*.go' ! -name '*_test.go' $(foreach dir,$(SUBDIRS),! -path './$(dir)/*') \) )
-$(TARGET): $(SOURCES)
+$(TARGET):
 	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
-
-$(SUBDIRS): force
-	@ $(MAKE) $(SUBMAKEOPTS) -C $@ all
+	$(QUIET)$(GO_BUILD) -o $@
 
 clean:
 	@$(ECHO_CLEAN)
 	$(QUIET)for i in $(SUBDIRS); do $(MAKE) $(SUBMAKEOPTS) -C $$i clean; done
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)for i in $(SUBDIRS); do $(MAKE) $(SUBMAKEOPTS) -C $$i install; done
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(BINDIR)
 
+
+$(SUBDIRS): force
+	@ $(MAKE) $(SUBMAKEOPTS) -C $@ all
+
 .PHONY: force
 force :;
diff --git a/cilium-health/responder/Makefile b/cilium-health/responder/Makefile
index 050a883f59..29c25ab5cf 100644
--- a/cilium-health/responder/Makefile
+++ b/cilium-health/responder/Makefile
@@ -1,17 +1,22 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
-TARGET=cilium-health-responder
-SOURCES := $(shell find ../../pkg/health/probe/responder . \( -name '*.go' ! -name '*_test.go' \))
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := cilium-health-responder
+
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
 	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
diff --git a/cilium-operator.Dockerfile b/cilium-operator.Dockerfile
index ac5ae3acbb..4995b6a4ea 100644
--- a/cilium-operator.Dockerfile
+++ b/cilium-operator.Dockerfile
@@ -1,10 +1,9 @@
-FROM docker.io/library/golang:1.14.1 as builder
+FROM docker.io/library/golang:1.14.2 as builder
 LABEL maintainer="maintainer@cilium.io"
 ADD . /go/src/github.com/cilium/cilium
 WORKDIR /go/src/github.com/cilium/cilium/operator
 ARG LOCKDEBUG
-ARG V
-RUN make CGO_ENABLED=0 GOOS=linux LOCKDEBUG=$LOCKDEBUG PKG_BUILD=1 EXTRA_GOBUILD_FLAGS="-a -installsuffix cgo -tags operator_aws,operator_azure"
+RUN make LOCKDEBUG=$LOCKDEBUG EXTRA_GO_BUILD_FLAGS="-tags operator_aws,operator_azure"
 
 FROM docker.io/library/alpine:3.9.3 as certs
 RUN apk --update add ca-certificates
diff --git a/cilium/.gitignore b/cilium/.gitignore
index f3c6bfa6a3..700f27eb0f 100644
--- a/cilium/.gitignore
+++ b/cilium/.gitignore
@@ -1,2 +1,1 @@
 cilium
-bash_autocomplete
diff --git a/cilium/Makefile b/cilium/Makefile
index 845a21a0a6..918a453b45 100644
--- a/cilium/Makefile
+++ b/cilium/Makefile
@@ -1,23 +1,26 @@
-include ../Makefile.quiet
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-TARGET=cilium
-SOURCES := $(shell find ../api ../daemon ../common ../pkg cmd . \( -name '*.go' ! -name '*_test.go' \))
+TARGET := cilium
 
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
-	@$(ECHO_CLEAN) $(notdir $(shell pwd))
+	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(CONFDIR)/bash_completion.d
-	./$(TARGET) completion bash > bash_autocomplete
-	$(QUIET)$(INSTALL) -m 0644 -T bash_autocomplete $(DESTDIR)$(CONFDIR)/bash_completion.d/cilium
+	./$(TARGET) completion bash > $(TARGET)_bash_completion
+	$(QUIET)$(INSTALL) -m 0644 -T $(TARGET)_bash_completion $(DESTDIR)$(CONFDIR)/bash_completion.d/$(TARGET)
diff --git a/contrib/codegen/Makefile b/contrib/codegen/Makefile
index cca0cc9eab..a3b0a2059c 100644
--- a/contrib/codegen/Makefile
+++ b/contrib/codegen/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 CFLAGS=-O2 -Wall
 
 all: hexgen
diff --git a/contrib/packaging/deb/Makefile b/contrib/packaging/deb/Makefile
index b0b5020f0a..45e8c273a0 100644
--- a/contrib/packaging/deb/Makefile
+++ b/contrib/packaging/deb/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../../Makefile.defs
 
 BUILDDIR := "$(CURDIR)/cilium-$(VERSION)"
@@ -5,7 +8,6 @@ BASEDIR := "$(CURDIR)/../../../"
 BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
 
 build: clean
-
 	mkdir -p output
 	(cd $(BASEDIR) && git bundle create $(CURDIR)/version_$(VERSION) $(BRANCH) --tags)
 	(cd $(CURDIR) && git clone $(CURDIR)/version_$(VERSION) cilium -b $(BRANCH))
diff --git a/contrib/packaging/docker/Dockerfile.runtime b/contrib/packaging/docker/Dockerfile.runtime
index 05019794dc..a765b79f34 100644
--- a/contrib/packaging/docker/Dockerfile.runtime
+++ b/contrib/packaging/docker/Dockerfile.runtime
@@ -97,7 +97,7 @@ rm -rf /var/lib/apt/lists/*
 #
 # Go-based tools we need at runtime
 #
-FROM docker.io/library/golang:1.14.1 as runtime-gobuild
+FROM docker.io/library/golang:1.14.2 as runtime-gobuild
 WORKDIR /tmp
 RUN go get -d github.com/google/gops && \
 cd /go/src/github.com/google/gops && \
diff --git a/contrib/packaging/rpm/Makefile b/contrib/packaging/rpm/Makefile
index 7cae4143d8..cd9877de68 100644
--- a/contrib/packaging/rpm/Makefile
+++ b/contrib/packaging/rpm/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../../Makefile.defs
 
 VERSION := $(shell git tag -l --sort=-v:refname | sed 's/v\([^-].*\)/\1/g' | head -1)
diff --git a/contrib/scripts/rand-check.sh b/contrib/scripts/rand-check.sh
new file mode 100755
index 0000000000..177d950601
--- /dev/null
+++ b/contrib/scripts/rand-check.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2020 Authors of Cilium
+
+# Make sure pkg/rand is used instead of math/rand, see
+# https://github.com/cilium/cilium/issues/10988. It's fine to use math/rand in
+# tests though.
+for l in rand\.NewSource; do
+  m=$(find . -name "*.go" \
+	  -not -name "*_test.go" \
+	  -not -path "./contrib/*" \
+	  -not -path "./pkg/rand/*" \
+	  -not -path "./vendor/*" \
+	  -not -path "./test/*" \
+	  -print0 \
+	  | xargs -0 grep --exclude NewSafeSource "$l")
+  if [[ ! -z "$m" ]] ; then
+    echo "Found $l usage. Please use pkg/rand instead for a concurrency-safe implementation:"
+    echo $m
+    exit 1
+  fi
+done
diff --git a/daemon/Makefile b/daemon/Makefile
index 0fc23db04e..f8e25cee18 100644
--- a/daemon/Makefile
+++ b/daemon/Makefile
@@ -1,22 +1,26 @@
-# GOBUILD relies on the order of makefile list to get VERSION file
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-TARGET=cilium-agent
-LINKS=cilium-node-monitor
-SOURCES := $(shell find ../api ../common ../daemon ../pkg . \( -name '*.go'  ! -name '*_test.go' \))
-$(TARGET): $(SOURCES) ../Dockerfile ../Makefile ../Makefile.defs Makefile
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := cilium-agent
+LINKS := cilium-node-monitor
 
 all: $(TARGET) links
 
+.PHONY: all $(TARGET) links
+
+$(TARGET): ../Dockerfile ../Makefile ../Makefile.defs Makefile
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $(TARGET)
+
 links:
 	$(foreach link,$(LINKS), ln -f -s $(TARGET) $(link) || cp $(TARGET) $(link);)
 
 clean:
 	@$(ECHO_CLEAN)
 	$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 	$(foreach link,$(LINKS), $(QUIET)rm -f $(link);)
 
 ifeq ("$(PKG_BUILD)","")
diff --git a/daemon/cmd/daemon.go b/daemon/cmd/daemon.go
index 9aa8532f5f..d96257fe07 100644
--- a/daemon/cmd/daemon.go
+++ b/daemon/cmd/daemon.go
@@ -397,7 +397,8 @@ func NewDaemon(ctx context.Context, dp datapath.Datapath) (*Daemon, *endpointRes
 	k8s.Configure(option.Config.K8sAPIServer, option.Config.K8sKubeConfigPath, defaults.K8sClientQPSLimit, defaults.K8sClientBurst)
 	bootstrapStats.k8sInit.End(true)
 	d.k8sWatcher.RunK8sServiceHandler()
-	policyApi.InitEntities(option.Config.ClusterName)
+	treatRemoteNodeAsHost := option.Config.AlwaysAllowLocalhost() && !option.Config.EnableRemoteNodeIdentity
+	policyApi.InitEntities(option.Config.ClusterName, treatRemoteNodeAsHost)
 
 	bootstrapStats.cleanup.Start()
 	err = clearCiliumVeths()
diff --git a/daemon/cmd/status.go b/daemon/cmd/status.go
index 0797bd29f0..f7d95614c7 100644
--- a/daemon/cmd/status.go
+++ b/daemon/cmd/status.go
@@ -17,7 +17,6 @@ package cmd
 import (
 	"context"
 	"fmt"
-	"math/rand"
 	"strings"
 	"time"
 
@@ -32,6 +31,7 @@ import (
 	"github.com/cilium/cilium/pkg/lock"
 	"github.com/cilium/cilium/pkg/node"
 	"github.com/cilium/cilium/pkg/option"
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/status"
 
 	"github.com/go-openapi/runtime/middleware"
@@ -50,7 +50,7 @@ const (
 	k8sMinimumEventHearbeat = time.Minute
 )
 
-var randGen = rand.New(rand.NewSource(time.Now().UnixNano()))
+var randGen = rand.NewSafeRand(time.Now().UnixNano())
 
 type k8sVersion struct {
 	version          string
diff --git a/examples/getting-started/Vagrantfile b/examples/getting-started/Vagrantfile
index 37a87ed7ad..b2b572f247 100644
--- a/examples/getting-started/Vagrantfile
+++ b/examples/getting-started/Vagrantfile
@@ -3,14 +3,14 @@
 
 Vagrant.require_version ">= 2.0.0"
 
-cilium_version = (ENV['CILIUM_VERSION'] || "v1.7.0")
+cilium_version = (ENV['CILIUM_VERSION'] || "v1.7.2")
 cilium_opts = (ENV['CILIUM_OPTS'] || "--enable-ipv6=false --kvstore consul --kvstore-opt consul.address=127.0.0.1:8500 -t vxlan")
-cilium_tag = (ENV['CILIUM_TAG'] || "v1.7.0")
+cilium_tag = (ENV['CILIUM_TAG'] || "v1.7.2")
 
 # This runs only once when vagrant box is provisioned for the first time
 $bootstrap = <<SCRIPT
 # install docker-compose
-curl -sS -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+curl -sS -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
 chmod +x /usr/local/bin/docker-compose
 
 # install cilium client
diff --git a/examples/getting-started/docker-compose.yml b/examples/getting-started/docker-compose.yml
index 11fd4639bf..fbfbdd11c9 100644
--- a/examples/getting-started/docker-compose.yml
+++ b/examples/getting-started/docker-compose.yml
@@ -7,6 +7,7 @@ services:
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock
       - /var/run/cilium:/var/run/cilium
+      - /var/lib/cilium:/var/lib/cilium
       - /sys/fs/bpf:/sys/fs/bpf
       # To access Docker container netns:
       - /var/run/docker/netns:/var/run/docker/netns:rshared
@@ -24,6 +25,7 @@ services:
     image: docker.io/cilium/docker-plugin:${CILIUM_TAG}
     command: cilium-docker
     volumes:
+      - /var/run/docker.sock:/var/run/docker.sock
       - /var/run/cilium:/var/run/cilium
       - /run/docker/plugins:/run/docker/plugins
     network_mode: "host"
diff --git a/examples/kubernetes/addons/prometheus/templates/Makefile b/examples/kubernetes/addons/prometheus/templates/Makefile
index c3f243e219..e3e38343b5 100644
--- a/examples/kubernetes/addons/prometheus/templates/Makefile
+++ b/examples/kubernetes/addons/prometheus/templates/Makefile
@@ -1,5 +1,7 @@
-include ../../../../../Makefile.defs
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
 
+include ../../../../../Makefile.defs
 
 DESTINATION= "../monitoring-example.yaml"
 
diff --git a/examples/kubernetes/connectivity-check/Makefile b/examples/kubernetes/connectivity-check/Makefile
index a1f8ed28d2..48340af61f 100644
--- a/examples/kubernetes/connectivity-check/Makefile
+++ b/examples/kubernetes/connectivity-check/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 OUT := connectivity-check.yaml
 SRC := $(wildcard *.yaml)
 SRC := $(filter-out $(OUT), $(SRC))
diff --git a/examples/policies/l3/entities/nodes.json b/examples/policies/l3/entities/nodes.json
new file mode 100644
index 0000000000..0b670d264f
--- /dev/null
+++ b/examples/policies/l3/entities/nodes.json
@@ -0,0 +1,10 @@
+[{
+    "labels": [{"key": "name", "value": "to-dev-from-nodes-in-cluster"}],
+    "endpointSelector": {"matchLabels": {"env":"dev"}},
+    "ingress": [{
+        "fromEntities": [
+            "host",
+            "remote-node"
+        ]
+    }]
+}]
diff --git a/examples/policies/l3/entities/nodes.yaml b/examples/policies/l3/entities/nodes.yaml
new file mode 100644
index 0000000000..f4144d8730
--- /dev/null
+++ b/examples/policies/l3/entities/nodes.yaml
@@ -0,0 +1,12 @@
+apiVersion: "cilium.io/v2"
+kind: CiliumNetworkPolicy
+metadata:
+  name: "to-dev-from-nodes-in-cluster"
+spec:
+  endpointSelector:
+    matchLabels:
+      env: dev
+  ingress:
+    - fromEntities:
+      - host
+      - remote-node
diff --git a/hubble-proxy/.gitignore b/hubble-proxy/.gitignore
index d32972278f..f69665e491 100644
--- a/hubble-proxy/.gitignore
+++ b/hubble-proxy/.gitignore
@@ -1,2 +1,1 @@
 hubble-proxy
-bash_autocomplete
diff --git a/hubble-proxy/Makefile b/hubble-proxy/Makefile
index 4bfe6be7fd..ae4d207d65 100644
--- a/hubble-proxy/Makefile
+++ b/hubble-proxy/Makefile
@@ -1,27 +1,27 @@
-include ../Makefile.quiet
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-TARGET=hubble-proxy
-SOURCES := $(shell find . ../pkg/hubble \( -name '*.go' ! -name '*_test.go' \))
+TARGET := hubble-proxy
 
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
-$(TARGET): $(SOURCES)
+$(TARGET):
 	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+	$(QUIET)$(GO_BUILD) -o $@
 	strip $(TARGET)
 
 clean:
-	@$(ECHO_CLEAN) $(notdir $(shell pwd))
+	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(CONFDIR)/bash_completion.d
-	./$(TARGET) completion bash > bash_autocomplete
-	$(QUIET)$(INSTALL) -m 0644 -T bash_autocomplete $(DESTDIR)$(CONFDIR)/bash_completion.d/hubble-proxy
-
-.PHONY: clean install
+	./$(TARGET) completion bash > $(TARGET)_bash_completion
+	$(QUIET)$(INSTALL) -m 0644 -T $(TARGET)_bash_completion $(DESTDIR)$(CONFDIR)/bash_completion.d/$(TARGET)
diff --git a/install/kubernetes/Makefile b/install/kubernetes/Makefile
index 05ff959dc9..79145e7f88 100644
--- a/install/kubernetes/Makefile
+++ b/install/kubernetes/Makefile
@@ -1,5 +1,7 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
-include $(ROOT_DIR)/Makefile.quiet
 
 MANAGED_ETCD_VERSION := "v2.0.7"
 
diff --git a/install/kubernetes/cilium/charts/agent/templates/daemonset.yaml b/install/kubernetes/cilium/charts/agent/templates/daemonset.yaml
index 84bb84bbfb..2108541d92 100644
--- a/install/kubernetes/cilium/charts/agent/templates/daemonset.yaml
+++ b/install/kubernetes/cilium/charts/agent/templates/daemonset.yaml
@@ -86,6 +86,10 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: spec.nodeName
+{{- if .Values.global.ci.kubeCacheMutationDetector }}
+        - name: KUBE_CACHE_MUTATION_DETECTOR
+          value: "true"
+{{- end }}
         - name: CILIUM_K8S_NAMESPACE
           valueFrom:
             fieldRef:
diff --git a/install/kubernetes/cilium/charts/operator/templates/deployment.yaml b/install/kubernetes/cilium/charts/operator/templates/deployment.yaml
index 1283aaac68..9905be9fd0 100644
--- a/install/kubernetes/cilium/charts/operator/templates/deployment.yaml
+++ b/install/kubernetes/cilium/charts/operator/templates/deployment.yaml
@@ -40,6 +40,10 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: spec.nodeName
+{{- if .Values.global.ci.kubeCacheMutationDetector }}
+        - name: KUBE_CACHE_MUTATION_DETECTOR
+          value: "true"
+{{- end }}
         - name: CILIUM_K8S_NAMESPACE
           valueFrom:
             fieldRef:
diff --git a/install/kubernetes/cilium/values.yaml b/install/kubernetes/cilium/values.yaml
index aa86c63f61..3aac00632c 100644
--- a/install/kubernetes/cilium/values.yaml
+++ b/install/kubernetes/cilium/values.yaml
@@ -435,3 +435,8 @@ global:
     # See https://github.com/cilium/hubble/blob/master/Documentation/metrics.md for more comprehensive
     # documentation about Hubble's metric collection.
     metrics: []
+
+  # CI specific options: DO NOT USE IN PRODUCTION.
+  ci:
+    # Make Cilium panic if objects received by k8s are modified.
+    kubeCacheMutationDetector: false
diff --git a/operator/Makefile b/operator/Makefile
index 61337e3818..9e90093eaa 100644
--- a/operator/Makefile
+++ b/operator/Makefile
@@ -1,20 +1,23 @@
-# GOBUILD relies on the order of makefile list to get VERSION file
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-TARGET=cilium-operator
-SOURCES := $(shell find ../pkg . \( -name '*.go'  ! -name '*_test.go' \))
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := cilium-operator
+
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
 	@$(ECHO_CLEAN)
-	$(QUIET)rm -f $(TARGET)
-	$(GO) clean $(GOCLEAN)
+	-$(QUIET)rm -f $(TARGET)
+	$(QUIET)$(GO_CLEAN)
 
 install:
-	groupadd -f cilium
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(BINDIR)
diff --git a/operator/ccnp_event.go b/operator/ccnp_event.go
index f1dc6e57e2..770712ac62 100644
--- a/operator/ccnp_event.go
+++ b/operator/ccnp_event.go
@@ -81,9 +81,15 @@ func enableCCNPWatcher() error {
 			AddFunc: func(obj interface{}) {
 				metrics.EventTSK8s.SetToCurrentTime()
 				if cnp := k8s.ObjToSlimCNP(obj); cnp != nil {
-					groups.AddDerivativeCNPIfNeeded(cnp.CiliumNetworkPolicy)
+
+					// We need to deepcopy this structure because we are writing
+					// fields.
+					// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+					cnpCpy := cnp.DeepCopy()
+
+					groups.AddDerivativeCNPIfNeeded(cnpCpy.CiliumNetworkPolicy)
 					if kvstoreEnabled() {
-						ccnpStatusMgr.StartStatusHandler(cnp)
+						ccnpStatusMgr.StartStatusHandler(cnpCpy)
 					}
 				}
 			},
@@ -94,7 +100,14 @@ func enableCCNPWatcher() error {
 						if k8s.EqualV2CNP(oldCNP, newCNP) {
 							return
 						}
-						groups.UpdateDerivativeCNPIfNeeded(newCNP.CiliumNetworkPolicy, oldCNP.CiliumNetworkPolicy)
+
+						// We need to deepcopy this structure because we are writing
+						// fields.
+						// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+						newCNPCpy := newCNP.DeepCopy()
+						oldCNPCpy := oldCNP.DeepCopy()
+
+						groups.UpdateDerivativeCNPIfNeeded(newCNPCpy.CiliumNetworkPolicy, oldCNPCpy.CiliumNetworkPolicy)
 					}
 				}
 			},
diff --git a/operator/cnp_event.go b/operator/cnp_event.go
index 5222e3101b..9850338d02 100644
--- a/operator/cnp_event.go
+++ b/operator/cnp_event.go
@@ -89,9 +89,15 @@ func enableCNPWatcher() error {
 			AddFunc: func(obj interface{}) {
 				metrics.EventTSK8s.SetToCurrentTime()
 				if cnp := k8s.ObjToSlimCNP(obj); cnp != nil {
-					groups.AddDerivativeCNPIfNeeded(cnp.CiliumNetworkPolicy)
+
+					// We need to deepcopy this structure because we are writing
+					// fields.
+					// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+					cnpCpy := cnp.DeepCopy()
+
+					groups.AddDerivativeCNPIfNeeded(cnpCpy.CiliumNetworkPolicy)
 					if kvstoreEnabled() {
-						cnpStatusMgr.StartStatusHandler(cnp)
+						cnpStatusMgr.StartStatusHandler(cnpCpy)
 					}
 				}
 			},
@@ -102,7 +108,14 @@ func enableCNPWatcher() error {
 						if k8s.EqualV2CNP(oldCNP, newCNP) {
 							return
 						}
-						groups.UpdateDerivativeCNPIfNeeded(newCNP.CiliumNetworkPolicy, oldCNP.CiliumNetworkPolicy)
+
+						// We need to deepcopy this structure because we are writing
+						// fields.
+						// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+						newCNPCpy := newCNP.DeepCopy()
+						oldCNPCpy := oldCNP.DeepCopy()
+
+						groups.UpdateDerivativeCNPIfNeeded(newCNPCpy.CiliumNetworkPolicy, oldCNPCpy.CiliumNetworkPolicy)
 					}
 				}
 			},
diff --git a/pkg/azure/api/api.go b/pkg/azure/api/api.go
index 98c3573d5f..c446d76d24 100644
--- a/pkg/azure/api/api.go
+++ b/pkg/azure/api/api.go
@@ -290,7 +290,7 @@ func (c *Client) GetVpcsAndSubnets(ctx context.Context) (ipamTypes.VirtualNetwor
 }
 
 func generateIpConfigName() string {
-	return "Cilium-" + rand.RandomRuneWithLen(8)
+	return rand.RandomStringWithPrefix("Cilium-", 8)
 }
 
 // AssignPrivateIpAddresses assigns the IPs to the interface as specified by
diff --git a/pkg/backoff/backoff.go b/pkg/backoff/backoff.go
index a0cba1e64e..4c59988783 100644
--- a/pkg/backoff/backoff.go
+++ b/pkg/backoff/backoff.go
@@ -18,17 +18,21 @@ import (
 	"context"
 	"fmt"
 	"math"
-	"math/rand"
 	"time"
 
 	"github.com/cilium/cilium/pkg/logging"
 	"github.com/cilium/cilium/pkg/logging/logfields"
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/uuid"
 
 	"github.com/sirupsen/logrus"
 )
 
-var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "backoff")
+var (
+	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "backoff")
+
+	randGen = rand.NewSafeRand(time.Now().UnixNano())
+)
 
 // NodeManager is the interface required to implement cluster size dependent
 // intervals
@@ -78,7 +82,7 @@ func CalculateDuration(min, max time.Duration, factor float64, jitter bool, fail
 	}
 
 	if jitter {
-		t = rand.Float64()*(t-minFloat) + minFloat
+		t = randGen.Float64()*(t-minFloat) + minFloat
 	}
 
 	return time.Duration(t)
diff --git a/pkg/datapath/connector/add.go b/pkg/datapath/connector/add.go
index cfeb8df39f..9ddc9e7408 100644
--- a/pkg/datapath/connector/add.go
+++ b/pkg/datapath/connector/add.go
@@ -17,12 +17,12 @@ package connector
 import (
 	"crypto/sha256"
 	"fmt"
-	"math/rand"
 	"net"
 
 	"github.com/cilium/cilium/api/v1/models"
 	"github.com/cilium/cilium/pkg/logging"
 	"github.com/cilium/cilium/pkg/logging/logfields"
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/sysctl"
 
 	"github.com/containernetworking/plugins/pkg/ns"
@@ -53,23 +53,13 @@ func Endpoint2TempIfName(endpointID string) string {
 	return temporaryInterfacePrefix + truncateString(endpointID, 5)
 }
 
-func randIfStr(num int) string {
-	str := make([]rune, num)
-	for i := range str {
-		str[i] = ifChars[rand.Intn(len(ifChars))]
-	}
-	return string(str)
-}
-
 // Endpoint2TempRandIfName returns a random, temporary interface name for the
 // given endpointID. This is similar to Endpoint2TempIfName() but uses a
 // random string instead of endpoint ID.
 func Endpoint2TempRandIfName() string {
-	return temporaryInterfacePrefix + "_" + randIfStr(5)
+	return temporaryInterfacePrefix + "_" + rand.RandomLowercaseStringWithLen(5)
 }
 
-var ifChars = []rune("abcdefghijklmnopqrstuvwxyz")
-
 func truncateString(epID string, maxLen uint) string {
 	if maxLen <= uint(len(epID)) {
 		return epID[:maxLen]
diff --git a/pkg/hubble/observer/local_observer.go b/pkg/hubble/observer/local_observer.go
index e903d2b064..d25a1edf6e 100644
--- a/pkg/hubble/observer/local_observer.go
+++ b/pkg/hubble/observer/local_observer.go
@@ -238,6 +238,13 @@ func (s *LocalObserverServer) GetFlows(
 	ctx, cancel := context.WithCancel(server.Context())
 	defer cancel()
 
+	for _, f := range s.opts.OnGetFlows {
+		ctx, err = f.OnGetFlows(ctx, req)
+		if err != nil {
+			return err
+		}
+	}
+
 	filterList := append(filters.DefaultFilters, s.opts.OnBuildFilter...)
 	whitelist, err := filters.BuildFilterList(ctx, req.Whitelist, filterList)
 	if err != nil {
@@ -248,19 +255,9 @@ func (s *LocalObserverServer) GetFlows(
 		return err
 	}
 
-	return getFlows(ctx, req, server, s, whitelist, blacklist)
-}
-
-func getFlows(
-	ctx context.Context,
-	req *observerpb.GetFlowsRequest,
-	server observerpb.Observer_GetFlowsServer,
-	obs GRPCServer,
-	whitelist, blacklist filters.FilterFuncs,
-) (err error) {
 	start := time.Now()
-	log := obs.GetLogger()
-	ring := obs.GetRingBuffer()
+	log := s.GetLogger()
+	ring := s.GetRingBuffer()
 
 	i := uint64(0)
 	defer func() {
@@ -285,6 +282,7 @@ func getFlows(
 		return err
 	}
 
+nextFlow:
 	for ; ; i++ {
 		flow, err := flowsReader.Next(ctx)
 		if err != nil {
@@ -293,6 +291,17 @@ func getFlows(
 			}
 			return err
 		}
+
+		for _, f := range s.opts.OnFlowDelivery {
+			stop, err := f.OnFlowDelivery(ctx, flow)
+			if err != nil {
+				return err
+			}
+			if stop {
+				continue nextFlow
+			}
+		}
+
 		err = server.Send(&observerpb.GetFlowsResponse{
 			ResponseTypes: &observerpb.GetFlowsResponse_Flow{
 				Flow: flow,
diff --git a/pkg/hubble/observer/observeroption/option.go b/pkg/hubble/observer/observeroption/option.go
index b1090fc112..be264dcf90 100644
--- a/pkg/hubble/observer/observeroption/option.go
+++ b/pkg/hubble/observer/observeroption/option.go
@@ -18,6 +18,7 @@ import (
 	"context"
 
 	pb "github.com/cilium/cilium/api/v1/flow"
+	"github.com/cilium/cilium/api/v1/observer"
 	"github.com/cilium/cilium/pkg/hubble/filters"
 
 	"github.com/sirupsen/logrus"
@@ -47,6 +48,8 @@ type Options struct {
 	OnMonitorEvent []OnMonitorEvent        // invoked before an event is decoded
 	OnDecodedFlow  []OnDecodedFlow         // invoked after a flow has been decoded
 	OnBuildFilter  []filters.OnBuildFilter // invoked while building a flow filter
+	OnFlowDelivery []OnFlowDelivery        // invoked before a flow is delivered via API
+	OnGetFlows     []OnGetFlows            // invoked on new GetFlows API call
 }
 
 // returning `stop: true` from a callback stops the execution chain, regardless
@@ -96,6 +99,32 @@ func (f OnDecodedFlowFunc) OnDecodedFlow(ctx context.Context, flow *pb.Flow) (st
 	return f(ctx, flow)
 }
 
+// OnFlowDelivery is invoked before a flow is delivered via the API
+type OnFlowDelivery interface {
+	OnFlowDelivery(context.Context, *pb.Flow) (stop, error)
+}
+
+// OnFlowDeliveryFunc implements OnFlowDelivery for a single function
+type OnFlowDeliveryFunc func(context.Context, *pb.Flow) (stop, error)
+
+// OnFlowDelivery is invoked before a flow is delivered via the API
+func (f OnFlowDeliveryFunc) OnFlowDelivery(ctx context.Context, flow *pb.Flow) (stop, error) {
+	return f(ctx, flow)
+}
+
+// OnGetFlows is invoked for each GetFlows call
+type OnGetFlows interface {
+	OnGetFlows(context.Context, *observer.GetFlowsRequest) (context.Context, error)
+}
+
+// OnGetFlowsFunc implements OnGetFlows for a single function
+type OnGetFlowsFunc func(context.Context, *observer.GetFlowsRequest) (context.Context, error)
+
+// OnGetFlows is invoked for each GetFlows call
+func (f OnGetFlowsFunc) OnGetFlows(ctx context.Context, req *observer.GetFlowsRequest) (context.Context, error) {
+	return f(ctx, req)
+}
+
 // WithMonitorBuffer controls the size of the buffered channel between the
 // monitor socket and the hubble ring buffer.
 func WithMonitorBuffer(size int) Option {
@@ -172,3 +201,29 @@ func WithOnBuildFilter(f filters.OnBuildFilter) Option {
 func WithOnBuildFilterFunc(f filters.OnBuildFilterFunc) Option {
 	return WithOnBuildFilter(filters.OnBuildFilterFunc(f))
 }
+
+// WithOnFlowDelivery adds a new callback to be invoked before a flow is delivered via the API
+func WithOnFlowDelivery(f OnFlowDelivery) Option {
+	return func(o *Options) error {
+		o.OnFlowDelivery = append(o.OnFlowDelivery, f)
+		return nil
+	}
+}
+
+// WithOnFlowDeliveryFunc adds a new callback to be invoked before a flow is delivered via the API
+func WithOnFlowDeliveryFunc(f OnFlowDeliveryFunc) Option {
+	return WithOnFlowDelivery(f)
+}
+
+// WithOnGetFlows adds a new callback to be invoked for each GetFlows call
+func WithOnGetFlows(f OnGetFlows) Option {
+	return func(o *Options) error {
+		o.OnGetFlows = append(o.OnGetFlows, f)
+		return nil
+	}
+}
+
+// WithOnGetFlowsFunc adds a new callback to be invoked for each GetFlows call
+func WithOnGetFlowsFunc(f OnGetFlowsFunc) Option {
+	return WithOnGetFlows(f)
+}
diff --git a/pkg/k8s/config.go b/pkg/k8s/config.go
index c69a44961c..c44b572619 100644
--- a/pkg/k8s/config.go
+++ b/pkg/k8s/config.go
@@ -80,5 +80,6 @@ func IsEnabled() bool {
 	return config.APIServerURL != "" ||
 		config.KubeconfigPath != "" ||
 		(os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
-			os.Getenv("KUBERNETES_SERVICE_PORT") != "")
+			os.Getenv("KUBERNETES_SERVICE_PORT") != "") ||
+		os.Getenv("K8S_NODE_NAME") != ""
 }
diff --git a/pkg/k8s/informer/informer.go b/pkg/k8s/informer/informer.go
index 3a17dc1fc4..cab4b4370f 100644
--- a/pkg/k8s/informer/informer.go
+++ b/pkg/k8s/informer/informer.go
@@ -15,6 +15,7 @@
 package informer
 
 import (
+	"fmt"
 	"time"
 
 	"k8s.io/apimachinery/pkg/runtime"
@@ -55,6 +56,8 @@ func NewInformerWithStore(
 	// of update/delete deltas.
 	fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, clientState)
 
+	cacheMutationDetector := cache.NewCacheMutationDetector(fmt.Sprintf("%T", objType))
+
 	cfg := &cache.Config{
 		Queue:            fifo,
 		ListerWatcher:    lw,
@@ -68,6 +71,10 @@ func NewInformerWithStore(
 
 				obj := convertFunc(d.Object)
 
+				// In CI we detect if the objects were modified and panic
+				// this is a no-op in production environments.
+				cacheMutationDetector.AddObject(obj)
+
 				switch d.Type {
 				case cache.Sync, cache.Added, cache.Updated:
 					if old, exists, err := clientState.Get(obj); err == nil && exists {
diff --git a/pkg/k8s/watchers/cilium_clusterwide_network_policy.go b/pkg/k8s/watchers/cilium_clusterwide_network_policy.go
index 1cb2c3fb52..5e8699187b 100644
--- a/pkg/k8s/watchers/cilium_clusterwide_network_policy.go
+++ b/pkg/k8s/watchers/cilium_clusterwide_network_policy.go
@@ -56,7 +56,13 @@ func (k *K8sWatcher) ciliumClusterwideNetworkPoliciesInit(ciliumNPClient *k8s.K8
 					if cnp.RequiresDerivative() {
 						return
 					}
-					err := k.addCiliumNetworkPolicyV2(ciliumNPClient, ccnpEventStore, cnp)
+
+					// We need to deepcopy this structure because we are writing
+					// fields.
+					// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+					cnpCpy := cnp.DeepCopy()
+
+					err := k.addCiliumNetworkPolicyV2(ciliumNPClient, ccnpEventStore, cnpCpy)
 					k.K8sEventProcessed(metricCCNP, metricCreate, err == nil)
 				}
 			},
@@ -75,7 +81,13 @@ func (k *K8sWatcher) ciliumClusterwideNetworkPoliciesInit(ciliumNPClient *k8s.K8
 							return
 						}
 
-						err := k.updateCiliumNetworkPolicyV2(ciliumNPClient, ccnpEventStore, oldCNP, newCNP)
+						// We need to deepcopy this structure because we are writing
+						// fields.
+						// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+						oldCNPCpy := oldCNP.DeepCopy()
+						newCNPCpy := newCNP.DeepCopy()
+
+						err := k.updateCiliumNetworkPolicyV2(ciliumNPClient, ccnpEventStore, oldCNPCpy, newCNPCpy)
 						k.K8sEventProcessed(metricCCNP, metricUpdate, err == nil)
 					}
 				}
diff --git a/pkg/k8s/watchers/cilium_network_policy.go b/pkg/k8s/watchers/cilium_network_policy.go
index e2303aacbf..880f8ae079 100644
--- a/pkg/k8s/watchers/cilium_network_policy.go
+++ b/pkg/k8s/watchers/cilium_network_policy.go
@@ -122,7 +122,13 @@ func (k *K8sWatcher) ciliumNetworkPoliciesInit(ciliumNPClient *k8s.K8sCiliumClie
 					if cnp.RequiresDerivative() {
 						return
 					}
-					err := k.addCiliumNetworkPolicyV2(ciliumNPClient, cnpEventStore, cnp)
+
+					// We need to deepcopy this structure because we are writing
+					// fields.
+					// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+					cnpCpy := cnp.DeepCopy()
+
+					err := k.addCiliumNetworkPolicyV2(ciliumNPClient, cnpEventStore, cnpCpy)
 					k.K8sEventProcessed(metricCNP, metricCreate, err == nil)
 				}
 			},
@@ -141,7 +147,13 @@ func (k *K8sWatcher) ciliumNetworkPoliciesInit(ciliumNPClient *k8s.K8sCiliumClie
 							return
 						}
 
-						err := k.updateCiliumNetworkPolicyV2(ciliumNPClient, cnpEventStore, oldCNP, newCNP)
+						// We need to deepcopy this structure because we are writing
+						// fields.
+						// See https://github.com/cilium/cilium/blob/27fee207f5422c95479422162e9ea0d2f2b6c770/pkg/policy/api/ingress.go#L112-L134
+						oldCNPCpy := oldCNP.DeepCopy()
+						newCNPCpy := newCNP.DeepCopy()
+
+						err := k.updateCiliumNetworkPolicyV2(ciliumNPClient, cnpEventStore, oldCNPCpy, newCNPCpy)
 						k.K8sEventProcessed(metricCNP, metricUpdate, err == nil)
 					}
 				}
diff --git a/pkg/kvstore/etcd.go b/pkg/kvstore/etcd.go
index f40372f2a1..88153c3cd4 100644
--- a/pkg/kvstore/etcd.go
+++ b/pkg/kvstore/etcd.go
@@ -21,7 +21,6 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"math/rand"
 	"net/url"
 	"os"
 	"strconv"
@@ -32,6 +31,7 @@ import (
 	"github.com/cilium/cilium/pkg/defaults"
 	"github.com/cilium/cilium/pkg/lock"
 	"github.com/cilium/cilium/pkg/option"
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/spanstat"
 	"github.com/cilium/cilium/pkg/versioncheck"
 
@@ -65,7 +65,7 @@ var (
 	// exist or it was expired.
 	ErrLockLeaseExpired = errors.New("transaction did not succeed: lock lease expired")
 
-	randGen = rand.New(rand.NewSource(time.Now().UnixNano()))
+	randGen = rand.NewSafeRand(time.Now().UnixNano())
 )
 
 type etcdModule struct {
diff --git a/pkg/policy/api/entity.go b/pkg/policy/api/entity.go
index 70ed68df86..cd35a85d37 100644
--- a/pkg/policy/api/entity.go
+++ b/pkg/policy/api/entity.go
@@ -101,7 +101,7 @@ func (s EntitySlice) GetAsEndpointSelectors() EndpointSelectorSlice {
 }
 
 // InitEntities is called to initialize the policy API layer
-func InitEntities(clusterName string) {
+func InitEntities(clusterName string, treatRemoteNodeAsHost bool) {
 	EntitySelectorMapping[EntityCluster] = EndpointSelectorSlice{
 		endpointSelectorHost,
 		endpointSelectorRemoteNode,
@@ -109,4 +109,11 @@ func InitEntities(clusterName string) {
 		endpointSelectorUnmanaged,
 		NewESFromLabels(labels.NewLabel(k8sapi.PolicyLabelCluster, clusterName, labels.LabelSourceK8s)),
 	}
+
+	hostSelectors := make(EndpointSelectorSlice, 0, 2)
+	hostSelectors = append(hostSelectors, endpointSelectorHost)
+	if treatRemoteNodeAsHost {
+		hostSelectors = append(hostSelectors, endpointSelectorRemoteNode)
+	}
+	EntitySelectorMapping[EntityHost] = hostSelectors
 }
diff --git a/pkg/policy/l4.go b/pkg/policy/l4.go
index 64a172a05d..b9a919389d 100644
--- a/pkg/policy/l4.go
+++ b/pkg/policy/l4.go
@@ -566,7 +566,7 @@ func (l4 *L4Filter) attach(ctx PolicyContext, l4Policy *L4Policy) {
 //
 // hostWildcardL7 determines if L7 traffic from Host should be
 // wildcarded (in the relevant daemon mode).
-func createL4IngressFilter(policyCtx PolicyContext, fromEndpoints api.EndpointSelectorSlice, hostWildcardL7 bool, rule api.PortRule, port api.PortProtocol,
+func createL4IngressFilter(policyCtx PolicyContext, fromEndpoints api.EndpointSelectorSlice, hostWildcardL7 []string, rule api.PortRule, port api.PortProtocol,
 	protocol api.L4Proto, ruleLabels labels.LabelArray) (*L4Filter, error) {
 
 	filter, err := createL4Filter(policyCtx, fromEndpoints, rule, port, protocol, ruleLabels, true, nil)
@@ -576,11 +576,13 @@ func createL4IngressFilter(policyCtx PolicyContext, fromEndpoints api.EndpointSe
 
 	// If the filter would apply L7 rules for the Host, when we should accept everything from host,
 	// then wildcard Host at L7.
-	if !rule.Rules.IsEmpty() && hostWildcardL7 {
+	if !rule.Rules.IsEmpty() && len(hostWildcardL7) > 0 {
 		for cs := range filter.L7RulesPerSelector {
 			if cs.Selects(identity.ReservedIdentityHost) {
-				hostSelector := api.ReservedEndpointSelectors[labels.IDNameHost]
-				filter.cacheIdentitySelector(hostSelector, policyCtx.GetSelectorCache())
+				for _, name := range hostWildcardL7 {
+					selector := api.ReservedEndpointSelectors[name]
+					filter.cacheIdentitySelector(selector, policyCtx.GetSelectorCache())
+				}
 			}
 		}
 	}
diff --git a/pkg/policy/mapstate.go b/pkg/policy/mapstate.go
index fc416616e9..9305a11169 100644
--- a/pkg/policy/mapstate.go
+++ b/pkg/policy/mapstate.go
@@ -31,6 +31,11 @@ var (
 		Identity:         identity.ReservedIdentityHost.Uint32(),
 		TrafficDirection: trafficdirection.Ingress.Uint8(),
 	}
+	// localRemoteNodeKey represents an ingress L3 allow from remote nodes.
+	localRemoteNodeKey = Key{
+		Identity:         identity.ReservedIdentityRemoteNode.Uint32(),
+		TrafficDirection: trafficdirection.Ingress.Uint8(),
+	}
 )
 
 const (
@@ -140,6 +145,9 @@ func (keys MapState) DetermineAllowLocalhostIngress(l4Policy *L4Policy) {
 			},
 		}
 		keys[localHostKey] = NewMapStateEntry(derivedFrom, false)
+		if !option.Config.EnableRemoteNodeIdentity {
+			keys[localRemoteNodeKey] = NewMapStateEntry(derivedFrom, false)
+		}
 	}
 }
 
diff --git a/pkg/policy/rule.go b/pkg/policy/rule.go
index 3ab3122179..b8a5796fe7 100644
--- a/pkg/policy/rule.go
+++ b/pkg/policy/rule.go
@@ -232,12 +232,12 @@ func mergePortProto(ctx *SearchContext, existingFilter, filterToMerge *L4Filter,
 // being merged has conflicting L7 rules with those already in the provided
 // L4PolicyMap for the specified port-protocol tuple, it returns an error.
 //
-// If any rules contain L7 rules that select Host and we should accept
-// all traffic from host (hostWildcardL7 == true) the L7 rules will be
-// translated into L7 wildcards (ie, traffic will be forwarded to the
-// proxy for endpoints matching those labels, but the proxy will allow
-// all such traffic).
-func mergeIngressPortProto(policyCtx PolicyContext, ctx *SearchContext, endpoints api.EndpointSelectorSlice, hostWildcardL7 bool,
+// If any rules contain L7 rules that select Host or Remote Node and we should
+// accept all traffic from host, the L7 rules will be translated into L7
+// wildcards via 'hostWildcardL7'. That is to say, traffic will be
+// forwarded to the proxy for endpoints matching those labels, but the proxy
+// will allow all such traffic.
+func mergeIngressPortProto(policyCtx PolicyContext, ctx *SearchContext, endpoints api.EndpointSelectorSlice, hostWildcardL7 []string,
 	r api.PortRule, p api.PortProtocol, proto api.L4Proto, ruleLabels labels.LabelArray, resMap L4PolicyMap) (int, error) {
 	// Create a new L4Filter
 	filterToMerge, err := createL4IngressFilter(policyCtx, endpoints, hostWildcardL7, r, p, proto, ruleLabels)
@@ -327,7 +327,13 @@ func mergeIngress(policyCtx PolicyContext, ctx *SearchContext, fromEndpoints api
 	// restrictions on these endpoints into L7 allow-all so that the
 	// traffic is always allowed, but is also always redirected through the
 	// proxy
-	hostWildcardL7 := option.Config.AlwaysAllowLocalhost()
+	hostWildcardL7 := make([]string, 0, 2)
+	if option.Config.AlwaysAllowLocalhost() {
+		hostWildcardL7 = append(hostWildcardL7, labels.IDNameHost)
+		if !option.Config.EnableRemoteNodeIdentity {
+			hostWildcardL7 = append(hostWildcardL7, labels.IDNameRemoteNode)
+		}
+	}
 
 	var (
 		cnt int
diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go
index 3f8d873119..47a240a3a0 100644
--- a/pkg/proxy/proxy.go
+++ b/pkg/proxy/proxy.go
@@ -17,7 +17,6 @@ package proxy
 import (
 	"context"
 	"fmt"
-	"math/rand"
 	"time"
 
 	"github.com/cilium/cilium/api/v1/models"
@@ -30,6 +29,7 @@ import (
 	"github.com/cilium/cilium/pkg/node"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/proxy/logger"
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/revert"
 
 	"github.com/sirupsen/logrus"
@@ -144,7 +144,7 @@ var (
 	// allocatedPorts is the map of all allocated proxy ports
 	allocatedPorts = make(map[uint16]struct{})
 
-	portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))
+	portRandomizer = rand.NewSafeRand(time.Now().UnixNano())
 
 	// proxyPorts is a slice of all supported proxy ports
 	// The number and order of entries are fixed, and the fields
diff --git a/pkg/rand/rand_name.go b/pkg/rand/rand_name.go
index d24b4e6def..93f677c9e2 100644
--- a/pkg/rand/rand_name.go
+++ b/pkg/rand/rand_name.go
@@ -15,7 +15,6 @@
 package rand
 
 import (
-	"math/rand"
 	"time"
 )
 
@@ -23,26 +22,38 @@ import (
 // https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
 
 var (
-	randGen = rand.New(rand.NewSource(time.Now().UnixNano()))
+	randGen = NewSafeRand(time.Now().UnixNano())
 
 	letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
 )
 
-// RandomRuneWithPrefix returns a random name string with fixed prefix
-func RandomRuneWithPrefix(prefix string, n int) string {
-	return prefix + RandomRuneWithLen(n)
+// RandomStringWithPrefix returns a random string of length n + len(prefix) with
+// the given prefix, containing upper- and lowercase runes.
+func RandomStringWithPrefix(prefix string, n int) string {
+	return prefix + RandomStringWithLen(n)
 }
 
-// RandomRuneWithLen returns a random name of specified length
-func RandomRuneWithLen(n int) string {
+func randomStringFromSliceWithLen(runes []rune, n int) string {
 	b := make([]rune, n)
 	for i := range b {
-		b[i] = letterRunes[randGen.Intn(len(letterRunes))]
+		b[i] = letterRunes[randGen.Intn(len(runes))]
 	}
 	return string(b)
 }
 
-// RandomRune returns a random name with a predefined length of 12
-func RandomRune() string {
-	return RandomRuneWithLen(12)
+// RandomStringWithLen returns a random string of specified length containing
+// upper- and lowercase runes.
+func RandomStringWithLen(n int) string {
+	return randomStringFromSliceWithLen(letterRunes, n)
+}
+
+// RandomLowercaseStringWithLen returns a random string of specified length
+// containing lowercase runes.
+func RandomLowercaseStringWithLen(n int) string {
+	return randomStringFromSliceWithLen(letterRunes[:26], n)
+}
+
+// RandomString returns a random string with a predefined length of 12.
+func RandomString() string {
+	return RandomStringWithLen(12)
 }
diff --git a/pkg/rand/safe_rand.go b/pkg/rand/safe_rand.go
new file mode 100644
index 0000000000..e1656f45d1
--- /dev/null
+++ b/pkg/rand/safe_rand.go
@@ -0,0 +1,85 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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 rand
+
+import (
+	"math/rand"
+
+	"github.com/cilium/cilium/pkg/lock"
+)
+
+// safeRand is a concurrency-safe source of pseudo-random numbers. The Go
+// stdlib's math/rand.Source is not concurrency-safe. The global source in
+// math/rand would be concurrency safe (due to its internal use of
+// lockedSource), but it is prone to inter-package interference with the PRNG
+// state.
+// Also see https://github.com/cilium/cilium/issues/10988
+type safeRand struct {
+	mu lock.Mutex
+	r  *rand.Rand
+}
+
+func NewSafeRand(seed int64) safeRand {
+	return safeRand{r: rand.New(rand.NewSource(seed))}
+}
+
+func (sr *safeRand) Seed(seed int64) {
+	sr.mu.Lock()
+	sr.r.Seed(seed)
+	sr.mu.Unlock()
+}
+
+func (sr *safeRand) Int63() int64 {
+	sr.mu.Lock()
+	v := sr.r.Int63()
+	sr.mu.Unlock()
+	return v
+}
+
+func (sr *safeRand) Uint32() uint32 {
+	sr.mu.Lock()
+	v := sr.r.Uint32()
+	sr.mu.Unlock()
+	return v
+}
+
+func (sr *safeRand) Uint64() uint64 {
+	sr.mu.Lock()
+	v := sr.r.Uint64()
+	sr.mu.Unlock()
+	return v
+}
+
+func (sr *safeRand) Intn(n int) int {
+	sr.mu.Lock()
+	v := sr.r.Intn(n)
+	sr.mu.Unlock()
+	return v
+}
+
+func (sr *safeRand) Float64() float64 {
+	sr.mu.Lock()
+	v := sr.r.Float64()
+	sr.mu.Unlock()
+	return v
+}
+
+func (sr *safeRand) Perm(n int) []int {
+	sr.mu.Lock()
+	v := sr.r.Perm(n)
+	sr.mu.Unlock()
+	return v
+
+}
diff --git a/plugins/Makefile b/plugins/Makefile
index cddec89e15..8ba50778e8 100644
--- a/plugins/Makefile
+++ b/plugins/Makefile
@@ -1,11 +1,16 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-SUBDIRS = cilium-docker cilium-cni
+SUBDIRS := cilium-docker cilium-cni
+
+.PHONY: all $(SUBDIRS) clean install
 
 all: $(SUBDIRS)
 
 $(SUBDIRS): force
-	@ $(MAKE) $(SUBMAKEOPTS) -C $@ all
+	@$(MAKE) $(SUBMAKEOPTS) -C $@ all
 
 clean:
 	$(QUIET)for i in $(SUBDIRS); do $(MAKE) $(SUBMAKEOPTS) -C $$i clean; done
diff --git a/plugins/cilium-cni/Makefile b/plugins/cilium-cni/Makefile
index 740c463185..c87217e635 100644
--- a/plugins/cilium-cni/Makefile
+++ b/plugins/cilium-cni/Makefile
@@ -1,20 +1,22 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
-all: cilium-cni
+TARGET := cilium-cni
+
+.PHONY: all $(TARGET) clean install
 
-TARGET=cilium-cni
+all: $(TARGET)
+
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
 
 clean:
 	@$(ECHO_CLEAN)
-	$(QUIET)$(GO) clean $(GOCLEAN)
 	-$(QUIET)rm -f $(TARGET)
-
-SOURCES := $(shell find ../../api/v1/models ../../common ../../pkg/client ../../pkg/endpoint . -name '*.go')
-
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	# Compile without cgo to allow use of cilium-cni on non-glibc platforms - see GH-5055
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(CNICONFDIR)
diff --git a/plugins/cilium-docker/Makefile b/plugins/cilium-docker/Makefile
index 1da6f95264..5d57847eb5 100644
--- a/plugins/cilium-docker/Makefile
+++ b/plugins/cilium-docker/Makefile
@@ -1,22 +1,27 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
-TARGET=cilium-docker
+TARGET := cilium-docker
 
-all: $(TARGET)
+.PHONY: all $(TARGET) clean install
 
-SOURCES := $(shell find ../../api/v1 ../../common ../../pkg/client ../../pkg/endpoint driver . -name '*.go')
+all: $(TARGET)
 
-$(TARGET): $(SOURCES)
+$(TARGET):
 	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
-
-run:
-	./cilium-docker -d
+	$(QUIET)$(GO_BUILD) -o $@
 
 clean:
 	@$(ECHO_CLEAN)
-	$(QUIET)$(GO) clean $(GOCLEAN)
-	-$(QUIET) rm -f $(TARGET)
+	-$(QUIET)rm -f $(TARGET)
+	$(QUIET)$(GO_CLEAN)
 
 install:
+	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
 	$(QUIET)$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(BINDIR)
+
+.PHONY: run
+run:
+	./cilium-docker -d
diff --git a/proxylib/Makefile b/proxylib/Makefile
index eee8678f19..154060ee52 100644
--- a/proxylib/Makefile
+++ b/proxylib/Makefile
@@ -1,22 +1,25 @@
-include ../Makefile.quiet
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-PROXYLIB_GOLDFLAGS=$(GOLDFLAGS) -extldflags -Wl,-soname,libcilium.so.$(VERSION_MAJOR)
-PROXYLIB_GOBUILD=-ldflags '$(PROXYLIB_GOLDFLAGS)'
+EXTRA_GO_BUILD_LDFLAGS = -extldflags -Wl,-soname,libcilium.so.$(VERSION_MAJOR)
 
-TARGET=libcilium.so
-DEPS := $(shell find ../pkg accesslog npds test . \( -name '*.go' ! -name '*_test.go' \))
-$(TARGET): $(DEPS)
-	@$(ECHO_GO)
-	$(QUIET)$(_GO) build $(GOBUILD) $(PROXYLIB_GOBUILD) -o $@.$(VERSION_MAJOR) -buildmode=c-shared
-	$(QUIET)ln -sf $@.$(VERSION_MAJOR) $@ || cp $@.$(VERSION_MAJOR) $@
+TARGET := libcilium.so
+
+.PHONY: all $(TARGET) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD_WITH_CGO) -o $@.$(VERSION_MAJOR) -buildmode=c-shared
+	$(QUIET)ln -sf $@.$(VERSION_MAJOR) $@ || cp $@.$(VERSION_MAJOR) $@
+
 clean:
 	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(_GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(LIBDIR)
diff --git a/tools/Makefile b/tools/Makefile
index f7e31d3836..4e0e16cc6e 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -1,6 +1,11 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
-SUBDIRS = alignchecker maptool
+SUBDIRS := alignchecker maptool
+
+.PHONY: all $(SUBDIRS) clean install
 
 all: $(SUBDIRS)
 
@@ -15,4 +20,3 @@ install:
 
 .PHONY: force
 force :;
-
diff --git a/tools/alignchecker/Makefile b/tools/alignchecker/Makefile
index f5c3013383..6e3a4c7a9c 100644
--- a/tools/alignchecker/Makefile
+++ b/tools/alignchecker/Makefile
@@ -1,17 +1,22 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
-TARGET=cilium-align-checker
-SOURCES := $(shell find ../../pkg/bpf . \( -name '*.go' ! -name '*_test.go' \))
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := cilium-align-checker
+
+.PHONY: all $(TARGET) $(SUBDIRS) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
 	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)
diff --git a/tools/maptool/Makefile b/tools/maptool/Makefile
index 481c37fc73..ba177f5bdf 100644
--- a/tools/maptool/Makefile
+++ b/tools/maptool/Makefile
@@ -1,17 +1,22 @@
+# Copyright 2017-2020 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
-TARGET=maptool
-SOURCES := $(shell find ../../pkg/bpf . \( -name '*.go' ! -name '*_test.go' \))
-$(TARGET): $(SOURCES)
-	@$(ECHO_GO)
-	$(QUIET)$(GO) build $(GOBUILD) -o $(TARGET)
+TARGET := maptool
+
+.PHONY: all $(TARGET) $(SUBDIRS) clean install
 
 all: $(TARGET)
 
+$(TARGET):
+	@$(ECHO_GO)
+	$(QUIET)$(GO_BUILD) -o $@
+
 clean:
 	@$(ECHO_CLEAN)
 	-$(QUIET)rm -f $(TARGET)
-	$(QUIET)$(GO) clean $(GOCLEAN)
+	$(QUIET)$(GO_CLEAN)
 
 install:
 	$(QUIET)$(INSTALL) -m 0755 -d $(DESTDIR)$(BINDIR)


Test patch:
diff --git a/pkg/allocator/allocator_test.go b/pkg/allocator/allocator_test.go
index 7dbd8869d1..165ad34062 100644
--- a/pkg/allocator/allocator_test.go
+++ b/pkg/allocator/allocator_test.go
@@ -205,7 +205,7 @@ func (t TestAllocatorKey) PutKeyFromMap(m map[string]string) AllocatorKey {
 }
 
 func randomTestName() string {
-	return rand.RandomRuneWithPrefix(testPrefix, 12)
+	return rand.RandomStringWithPrefix(testPrefix, 12)
 }
 
 func (s *AllocatorSuite) TestSelectID(c *C) {
diff --git a/pkg/clustermesh/services_test.go b/pkg/clustermesh/services_test.go
index 96a6be3f45..f7bc3f2b93 100644
--- a/pkg/clustermesh/services_test.go
+++ b/pkg/clustermesh/services_test.go
@@ -65,7 +65,7 @@ var (
 func (s *ClusterMeshServicesTestSuite) SetUpTest(c *C) {
 	kvstore.SetupDummy("etcd")
 
-	s.randomName = rand.RandomRune()
+	s.randomName = rand.RandomString()
 
 	kvstore.Client().DeletePrefix(context.TODO(), "cilium/state/services/v1/"+s.randomName)
 	s.svcCache = k8s.NewServiceCache(fakeDatapath.NewNodeAddressing())
diff --git a/pkg/hubble/observer/local_observer_test.go b/pkg/hubble/observer/local_observer_test.go
index 54ae112794..aa90bf8aed 100644
--- a/pkg/hubble/observer/local_observer_test.go
+++ b/pkg/hubble/observer/local_observer_test.go
@@ -182,3 +182,119 @@ func TestHooks(t *testing.T) {
 	<-s.GetStopped()
 	assert.Equal(t, int64(numFlows), seenFlows)
 }
+
+func TestLocalObserverServer_OnFlowDelivery(t *testing.T) {
+	numFlows := 100
+	queueSize := 0
+	req := &observerpb.GetFlowsRequest{Number: uint64(100)}
+	flowsReceived := 0
+	fakeServer := &FakeGetFlowsServer{
+		OnSend: func(response *observerpb.GetFlowsResponse) error {
+			flowsReceived++
+			return nil
+		},
+		FakeGRPCServerStream: &FakeGRPCServerStream{
+			OnContext: func() context.Context {
+				return context.Background()
+			},
+		},
+	}
+
+	count := 0
+	onFlowDelivery := func(ctx context.Context, f *flowpb.Flow) (bool, error) {
+		count++
+		if count%2 == 0 {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	pp := noopParser(t)
+	s, err := NewLocalServer(pp, logger.GetLogger(),
+		observeroption.WithMaxFlows(numFlows),
+		observeroption.WithMonitorBuffer(queueSize),
+		observeroption.WithOnFlowDeliveryFunc(onFlowDelivery),
+	)
+	require.NoError(t, err)
+	go s.Start()
+
+	m := s.GetEventsChannel()
+	for i := 0; i < numFlows; i++ {
+		tn := monitor.TraceNotifyV0{Type: byte(monitorAPI.MessageTypeTrace)}
+		data := testutils.MustCreateL3L4Payload(tn)
+		pl := &flowpb.Payload{
+			Time: &timestamp.Timestamp{Seconds: int64(i)},
+			Type: flowpb.EventType_EventSample,
+			Data: data,
+		}
+		m <- pl
+	}
+	close(s.GetEventsChannel())
+	<-s.GetStopped()
+	err = s.GetFlows(req, fakeServer)
+	assert.NoError(t, err)
+	// Only every second flow should have been received
+	assert.Equal(t, flowsReceived, numFlows/2)
+}
+
+func TestLocalObserverServer_OnGetFlows(t *testing.T) {
+	numFlows := 100
+	queueSize := 0
+	req := &observerpb.GetFlowsRequest{Number: uint64(100)}
+	flowsReceived := 0
+	fakeServer := &FakeGetFlowsServer{
+		OnSend: func(response *observerpb.GetFlowsResponse) error {
+			flowsReceived++
+			return nil
+		},
+		FakeGRPCServerStream: &FakeGRPCServerStream{
+			OnContext: func() context.Context {
+				return context.Background()
+			},
+		},
+	}
+
+	type contextKey string
+	key := contextKey("foo")
+	onGetFlows := func(ctx context.Context, req *observerpb.GetFlowsRequest) (context.Context, error) {
+		return context.WithValue(ctx, key, 10), nil
+	}
+
+	onFlowDelivery := func(ctx context.Context, f *flowpb.Flow) (bool, error) {
+		// Pass if context is available
+		if ctx.Value(key) != nil {
+			return false, nil
+		}
+		return true, nil
+	}
+
+	pp := noopParser(t)
+	s, err := NewLocalServer(pp, logger.GetLogger(),
+		observeroption.WithMaxFlows(numFlows),
+		observeroption.WithMonitorBuffer(queueSize),
+		observeroption.WithOnFlowDeliveryFunc(onFlowDelivery),
+		observeroption.WithOnGetFlowsFunc(onGetFlows),
+	)
+	require.NoError(t, err)
+	go s.Start()
+
+	m := s.GetEventsChannel()
+	for i := 0; i < numFlows; i++ {
+		tn := monitor.TraceNotifyV0{Type: byte(monitorAPI.MessageTypeTrace)}
+		data := testutils.MustCreateL3L4Payload(tn)
+		pl := &flowpb.Payload{
+			Time: &timestamp.Timestamp{Seconds: int64(i)},
+			Type: flowpb.EventType_EventSample,
+			Data: data,
+		}
+		m <- pl
+	}
+	close(s.GetEventsChannel())
+	<-s.GetStopped()
+	err = s.GetFlows(req, fakeServer)
+	assert.NoError(t, err)
+	// FIXME:
+	// This should be assert.Equals(t, flowsReceived, numFlows)
+	// A bug in the ring buffer prevents this from succeeding
+	assert.Greater(t, flowsReceived, 0)
+}
diff --git a/pkg/kvstore/allocator/allocator_test.go b/pkg/kvstore/allocator/allocator_test.go
index 13829e45aa..c162ff935b 100644
--- a/pkg/kvstore/allocator/allocator_test.go
+++ b/pkg/kvstore/allocator/allocator_test.go
@@ -96,7 +96,7 @@ func (t TestAllocatorKey) PutKeyFromMap(m map[string]string) allocator.Allocator
 }
 
 func randomTestName() string {
-	return rand.RandomRuneWithPrefix(testPrefix, 12)
+	return rand.RandomStringWithPrefix(testPrefix, 12)
 }
 
 func (s *AllocatorSuite) BenchmarkAllocate(c *C) {
diff --git a/pkg/kvstore/store/store_test.go b/pkg/kvstore/store/store_test.go
index 7eca1ac8ed..cea57febe8 100644
--- a/pkg/kvstore/store/store_test.go
+++ b/pkg/kvstore/store/store_test.go
@@ -142,19 +142,19 @@ func (s *StoreSuite) TestStoreCreation(c *C) {
 	c.Assert(store, IsNil)
 
 	// Missing KeyCreator must result in error
-	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomRune()})
+	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomString()})
 	c.Assert(err, ErrorMatches, "KeyCreator must be specified")
 	c.Assert(store, IsNil)
 
 	// Basic creation should result in default values
-	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomRune(), KeyCreator: newTestType})
+	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomString(), KeyCreator: newTestType})
 	c.Assert(err, IsNil)
 	c.Assert(store, Not(IsNil))
 	c.Assert(store.conf.SynchronizationInterval, Equals, option.Config.KVstorePeriodicSync)
 	store.Close(context.TODO())
 
 	// Test with kvstore client specified
-	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomRune(), KeyCreator: newTestType, Backend: kvstore.Client()})
+	store, err = JoinSharedStore(Configuration{Prefix: rand.RandomString(), KeyCreator: newTestType, Backend: kvstore.Client()})
 	c.Assert(err, IsNil)
 	c.Assert(store, Not(IsNil))
 	c.Assert(store.conf.SynchronizationInterval, Equals, option.Config.KVstorePeriodicSync)
@@ -178,7 +178,7 @@ func expect(check func() bool) error {
 
 func (s *StoreSuite) TestStoreOperations(c *C) {
 	// Basic creation should result in default values
-	store, err := JoinSharedStore(Configuration{Prefix: rand.RandomRune(), KeyCreator: newTestType, Observer: &observer{}})
+	store, err := JoinSharedStore(Configuration{Prefix: rand.RandomString(), KeyCreator: newTestType, Observer: &observer{}})
 	c.Assert(err, IsNil)
 	c.Assert(store, Not(IsNil))
 	defer store.Close(context.TODO())
@@ -221,7 +221,7 @@ func (s *StoreSuite) TestStoreOperations(c *C) {
 func (s *StoreSuite) TestStorePeriodicSync(c *C) {
 	// Create a store with a very short periodic sync interval
 	store, err := JoinSharedStore(Configuration{
-		Prefix:                  rand.RandomRune(),
+		Prefix:                  rand.RandomString(),
 		KeyCreator:              newTestType,
 		SynchronizationInterval: 10 * time.Millisecond,
 		Observer:                &observer{},
@@ -250,7 +250,7 @@ func (s *StoreSuite) TestStorePeriodicSync(c *C) {
 
 func (s *StoreSuite) TestStoreLocalKeyProtection(c *C) {
 	store, err := JoinSharedStore(Configuration{
-		Prefix:                  rand.RandomRune(),
+		Prefix:                  rand.RandomString(),
 		KeyCreator:              newTestType,
 		SynchronizationInterval: time.Hour, // ensure that periodic sync does not interfer
 		Observer:                &observer{},
@@ -302,12 +302,12 @@ func setupStoreCollaboration(c *C, storePrefix, keyPrefix string) *SharedStore {
 }
 
 func (s *StoreSuite) TestStoreCollaboration(c *C) {
-	storePrefix := rand.RandomRune()
+	storePrefix := rand.RandomString()
 
-	collab1 := setupStoreCollaboration(c, storePrefix, rand.RandomRune())
+	collab1 := setupStoreCollaboration(c, storePrefix, rand.RandomString())
 	defer collab1.Close(context.TODO())
 
-	collab2 := setupStoreCollaboration(c, storePrefix, rand.RandomRune())
+	collab2 := setupStoreCollaboration(c, storePrefix, rand.RandomString())
 	defer collab2.Close(context.TODO())
 
 	c.Assert(expect(func() bool {
diff --git a/pkg/lock/stoppable_waitgroup_test.go b/pkg/lock/stoppable_waitgroup_test.go
index 1124e0884b..e2382edb95 100644
--- a/pkg/lock/stoppable_waitgroup_test.go
+++ b/pkg/lock/stoppable_waitgroup_test.go
@@ -167,6 +167,8 @@ func (s *SemaphoredMutexSuite) TestWaitChannel(c *C) {
 func (s *SemaphoredMutexSuite) TestParallelism(c *C) {
 	l := NewStoppableWaitGroup()
 
+	// use math/rand instead of pkg/rand to avoid a test import cycle which
+	// go vet would complain about.
 	randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
 	in := make(chan int)
 	stop := make(chan struct{})
diff --git a/pkg/policy/api/entity_test.go b/pkg/policy/api/entity_test.go
index 76f16c6e75..2cbfc33f76 100644
--- a/pkg/policy/api/entity_test.go
+++ b/pkg/policy/api/entity_test.go
@@ -36,7 +36,7 @@ func (s EntitySlice) matches(ctx labels.LabelArray) bool {
 }
 
 func (s *PolicyAPITestSuite) TestEntityMatches(c *C) {
-	InitEntities("cluster1")
+	InitEntities("cluster1", false)
 
 	c.Assert(EntityHost.matches(labels.ParseLabelArray("reserved:host")), Equals, true)
 	c.Assert(EntityHost.matches(labels.ParseLabelArray("reserved:host", "id:foo")), Equals, true)
@@ -79,7 +79,7 @@ func (s *PolicyAPITestSuite) TestEntityMatches(c *C) {
 }
 
 func (s *PolicyAPITestSuite) TestEntitySliceMatches(c *C) {
-	InitEntities("cluster1")
+	InitEntities("cluster1", false)
 
 	slice := EntitySlice{EntityHost, EntityWorld}
 	c.Assert(slice.matches(labels.ParseLabelArray("reserved:host")), Equals, true)
@@ -88,3 +88,32 @@ func (s *PolicyAPITestSuite) TestEntitySliceMatches(c *C) {
 	c.Assert(slice.matches(labels.ParseLabelArray("reserved:none")), Equals, false)
 	c.Assert(slice.matches(labels.ParseLabelArray("id=foo")), Equals, false)
 }
+
+func (s *PolicyAPITestSuite) TestEntityHostAllowsRemoteNode(c *C) {
+	tests := []struct {
+		name                  string
+		treatRemoteNodeAsHost bool
+		expectedMatches       labels.LabelArray
+		expectedNonMatches    labels.LabelArray
+	}{
+		{
+			"host entity selects remote-node identity",
+			true,
+			labels.ParseLabelArray("reserved:remote-node"),
+			labels.ParseLabelArray("reserved:all"),
+		},
+		{
+			"host entity does not select remote-node identity",
+			false,
+			labels.ParseLabelArray("reserved:host"),
+			labels.ParseLabelArray("reserved:remote-node"),
+		},
+	}
+
+	for _, tt := range tests {
+		InitEntities("cluster1", tt.treatRemoteNodeAsHost)
+		hostSelector := EntitySelectorMapping[EntityHost]
+		c.Assert(hostSelector.Matches(tt.expectedMatches), Equals, true, Commentf("Test Name: %s", tt.name))
+		c.Assert(hostSelector.Matches(tt.expectedNonMatches), Equals, false, Commentf("Test Name: %s", tt.name))
+	}
+}
diff --git a/pkg/policy/l4_filter_test.go b/pkg/policy/l4_filter_test.go
index 909d31b6c4..532c120f66 100644
--- a/pkg/policy/l4_filter_test.go
+++ b/pkg/policy/l4_filter_test.go
@@ -81,7 +81,19 @@ func (p *testPolicyContextType) GetEnvoyHTTPRules(*api.L7Rules) (*cilium.HttpNet
 	return nil, true
 }
 
-var testPolicyContext *testPolicyContextType
+var (
+	testPolicyContext               *testPolicyContextType
+	cachedRemoteNodeIdentitySetting bool
+)
+
+func (ds *PolicyTestSuite) SetUpSuite(c *C) {
+	cachedRemoteNodeIdentitySetting = option.Config.EnableRemoteNodeIdentity
+	option.Config.EnableRemoteNodeIdentity = true
+}
+
+func (ds *PolicyTestSuite) TearDownSuite(c *C) {
+	option.Config.EnableRemoteNodeIdentity = cachedRemoteNodeIdentitySetting
+}
 
 // Tests in this file:
 //
diff --git a/pkg/policy/l4_test.go b/pkg/policy/l4_test.go
index 2a8ebd5362..9a86a6eed6 100644
--- a/pkg/policy/l4_test.go
+++ b/pkg/policy/l4_test.go
@@ -48,7 +48,7 @@ func (s *PolicyTestSuite) TestCreateL4Filter(c *C) {
 		// Regardless of ingress/egress, we should end up with
 		// a single L7 rule whether the selector is wildcarded
 		// or if it is based on specific labels.
-		filter, err := createL4IngressFilter(testPolicyContext, eps, false, portrule, tuple, tuple.Protocol, nil)
+		filter, err := createL4IngressFilter(testPolicyContext, eps, nil, portrule, tuple, tuple.Protocol, nil)
 		c.Assert(err, IsNil)
 		c.Assert(len(filter.L7RulesPerSelector), Equals, 1)
 		c.Assert(filter.IsEnvoyRedirect(), Equals, true)
@@ -87,7 +87,7 @@ func (s *PolicyTestSuite) TestCreateL4FilterMissingSecret(c *C) {
 		// Regardless of ingress/egress, we should end up with
 		// a single L7 rule whether the selector is wildcarded
 		// or if it is based on specific labels.
-		_, err := createL4IngressFilter(testPolicyContext, eps, false, portrule, tuple, tuple.Protocol, nil)
+		_, err := createL4IngressFilter(testPolicyContext, eps, nil, portrule, tuple, tuple.Protocol, nil)
 		c.Assert(err, Not(IsNil))
 
 		_, err = createL4EgressFilter(testPolicyContext, eps, portrule, tuple, tuple.Protocol, nil, nil)
diff --git a/pkg/rand/rand_name_test.go b/pkg/rand/rand_name_test.go
index 017b989c62..9f331a4ded 100644
--- a/pkg/rand/rand_name_test.go
+++ b/pkg/rand/rand_name_test.go
@@ -31,13 +31,17 @@ type RandSuite struct{}
 
 var _ = Suite(&RandSuite{})
 
-func (s *RandSuite) TestRandomRune(c *C) {
-	c.Assert(len(RandomRune()), Equals, 12)
+func (s *RandSuite) TestRandomString(c *C) {
+	c.Assert(len(RandomString()), Equals, 12)
 
-	c.Assert(len(RandomRuneWithLen(12)), Equals, 12)
-	c.Assert(len(RandomRuneWithLen(0)), Equals, 0)
+	c.Assert(len(RandomStringWithLen(12)), Equals, 12)
+	c.Assert(len(RandomStringWithLen(0)), Equals, 0)
 
-	str := RandomRuneWithPrefix("foo", 12)
-	c.Assert(len(str), Equals, 15)
-	c.Assert(strings.HasPrefix(str, "foo"), Equals, true)
+	s0 := RandomStringWithPrefix("foo", 12)
+	c.Assert(len(s0), Equals, len("foo")+12)
+	c.Assert(strings.HasPrefix(s0, "foo"), Equals, true)
+
+	s1 := RandomLowercaseStringWithLen(15)
+	c.Assert(len(s1), Equals, 15)
+	c.Assert(strings.ToLower(s1), Equals, s1)
 }
diff --git a/pkg/rand/safe_rand_test.go b/pkg/rand/safe_rand_test.go
new file mode 100644
index 0000000000..b03ab133a5
--- /dev/null
+++ b/pkg/rand/safe_rand_test.go
@@ -0,0 +1,36 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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.
+
+// +build !privileged_tests
+
+package rand
+
+import (
+	"testing"
+)
+
+func TestIntn(t *testing.T) {
+	r0 := NewSafeRand(1)
+	r1 := NewSafeRand(1)
+
+	n := 10
+	for i := 0; i < 100; i++ {
+		v0 := r0.Intn(n)
+		v1 := r1.Intn(n)
+
+		if v0 != v1 {
+			t.Errorf("Intn() returned different values at iteration %d: %d != %d", i, v0, v1)
+		}
+	}
+}
diff --git a/test/Makefile b/test/Makefile
index d3d9423d9f..aae5ae6e38 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../Makefile.defs
 
 provision = true
diff --git a/test/bpf/Makefile b/test/bpf/Makefile
index b92ff59d1a..e3f9bf00dd 100644
--- a/test/bpf/Makefile
+++ b/test/bpf/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
 FLAGS := -I../../bpf/ -I../../bpf/include -I. -D__NR_CPUS__=$(shell nproc) -O2
diff --git a/test/docker-compose.yml b/test/docker-compose.yml
index 520ed2f801..5c97264740 100644
--- a/test/docker-compose.yml
+++ b/test/docker-compose.yml
@@ -15,7 +15,7 @@ services:
     command: "etcd -name etcd0 -advertise-client-urls http://0.0.0.0:4002 -listen-client-urls http://0.0.0.0:4002 -initial-cluster-token etcd-cluster-1 -initial-cluster-state new"
     privileged: true
   base_image:
-    image: "quay.io/cilium/cilium-builder:2020-04-02"
+    image: "quay.io/cilium/cilium-builder:2020-04-16"
     volumes:
       - "./../:/go/src/github.com/cilium/cilium/"
     privileged: true
diff --git a/test/helpers/kubectl.go b/test/helpers/kubectl.go
index a47ded4826..4352940c50 100644
--- a/test/helpers/kubectl.go
+++ b/test/helpers/kubectl.go
@@ -35,9 +35,9 @@ import (
 	"github.com/cilium/cilium/test/config"
 	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
 	"github.com/cilium/cilium/test/helpers/logutils"
-	"github.com/onsi/gomega"
 
 	"github.com/asaskevich/govalidator"
+	"github.com/onsi/gomega"
 	"github.com/sirupsen/logrus"
 	v1 "k8s.io/api/core/v1"
 )
@@ -85,21 +85,22 @@ var (
 	// below. These overrides represent a desire to set the default for all
 	// tests, instead of test-specific variations.
 	defaultHelmOptions = map[string]string{
-		"global.registry":               "k8s1:5000/cilium",
-		"agent.image":                   "cilium-dev",
-		"preflight.image":               "cilium-dev", // Set again in init to match agent.image!
-		"global.tag":                    "latest",
-		"operator.image":                "operator",
-		"managed-etcd.registry":         "docker.io/cilium",
-		"global.debug.enabled":          "true",
-		"global.k8s.requireIPv4PodCIDR": "true",
-		"global.pprof.enabled":          "true",
-		"global.logSystemLoad":          "true",
-		"global.bpf.preallocateMaps":    "true",
-		"global.etcd.leaseTTL":          "30s",
-		"global.ipv4.enabled":           "true",
-		"global.ipv6.enabled":           "true",
-		"global.psp.enabled":            "true",
+		"global.registry":                     "k8s1:5000/cilium",
+		"agent.image":                         "cilium-dev",
+		"preflight.image":                     "cilium-dev", // Set again in init to match agent.image!
+		"global.tag":                          "latest",
+		"operator.image":                      "operator",
+		"managed-etcd.registry":               "docker.io/cilium",
+		"global.debug.enabled":                "true",
+		"global.k8s.requireIPv4PodCIDR":       "true",
+		"global.pprof.enabled":                "true",
+		"global.logSystemLoad":                "true",
+		"global.bpf.preallocateMaps":          "true",
+		"global.etcd.leaseTTL":                "30s",
+		"global.ipv4.enabled":                 "true",
+		"global.ipv6.enabled":                 "true",
+		"global.psp.enabled":                  "true",
+		"global.ci.kubeCacheMutationDetector": "true",
 	}
 
 	flannelHelmOverrides = map[string]string{
@@ -480,6 +481,28 @@ func (kub *Kubectl) GetPodsNodes(namespace string, filter string) (map[string]st
 	return res.KVOutput(), nil
 }
 
+func (kub *Kubectl) GetPodOnNodeWithOffset(nodeName string, podFilter string, callOffset int) (string, string) {
+	var podName string
+
+	callOffset++
+
+	podsNodes, err := kub.GetPodsNodes(DefaultNamespace, fmt.Sprintf("-l %s", podFilter))
+	gomega.ExpectWithOffset(callOffset, err).Should(gomega.BeNil(), "Cannot retrieve pods nodes with filter %q", podFilter)
+	gomega.Expect(podsNodes).ShouldNot(gomega.BeEmpty(), "No pod found in namespace %s with filter %q", DefaultNamespace, podFilter)
+	for pod, node := range podsNodes {
+		if node == nodeName {
+			podName = pod
+			break
+		}
+	}
+	gomega.ExpectWithOffset(callOffset, podName).ShouldNot(gomega.BeEmpty(), "Cannot retrieve pod on node %s with filter %q", nodeName, podFilter)
+	podsIPs, err := kub.GetPodsIPs(DefaultNamespace, podFilter)
+	gomega.ExpectWithOffset(callOffset, err).Should(gomega.BeNil(), "Cannot retrieve pods IPs with filter %q", podFilter)
+	gomega.Expect(podsIPs).ShouldNot(gomega.BeEmpty(), "No pod IP found in namespace %s with filter %q", DefaultNamespace, podFilter)
+	podIP := podsIPs[podName]
+	return podName, podIP
+}
+
 // GetSvcIP returns the cluster IP for the given service. If the service
 // does not contain a cluster IP, the function keeps retrying until it has or
 // the context timesout.
@@ -719,35 +742,14 @@ func (kub *Kubectl) Logs(namespace string, pod string) *CmdRes {
 		fmt.Sprintf("%s -n %s logs %s", KubectlCmd, namespace, pod))
 }
 
-// MonitorStart runs cilium monitor in the background and dumps the contents
-// into a log file for later debugging
-func (kub *Kubectl) MonitorStart(namespace, pod, filename string) func() error {
+// MonitorStart runs cilium monitor in the background and returns the command
+// result, CmdRes, along with a cancel function. The cancel function is used to
+// stop the monitor.
+func (kub *Kubectl) MonitorStart(namespace, pod string) (res *CmdRes, cancel func()) {
 	cmd := fmt.Sprintf("%s exec -n %s %s -- cilium monitor -vv", KubectlCmd, namespace, pod)
 	ctx, cancel := context.WithCancel(context.Background())
-	res := kub.ExecInBackground(ctx, cmd, ExecOptions{SkipLog: true})
 
-	cb := func() error {
-		cancel()
-		<-ctx.Done()
-		testPath, err := CreateReportDirectory()
-		if err != nil {
-			kub.Logger().WithError(err).Errorf(
-				"cannot create test results path '%s'", testPath)
-			return err
-		}
-
-		err = WriteOrAppendToFile(
-			filepath.Join(testPath, filename),
-			res.CombineOutput().Bytes(),
-			LogPerm)
-		if err != nil {
-			log.WithError(err).Errorf("cannot create monitor log file %s", filename)
-			return err
-		}
-		return nil
-	}
-
-	return cb
+	return kub.ExecInBackground(ctx, cmd, ExecOptions{SkipLog: true}), cancel
 }
 
 // BackgroundReport dumps the result of the given commands on cilium pods each
@@ -2687,6 +2689,16 @@ func (kub *Kubectl) GetCiliumPodOnNode(namespace string, node string) (string, e
 	return res.Output().String(), nil
 }
 
+// GetNodeInfo provides the node name and IP address based on the label
+// (eg helpers.K8s1 or helpers.K8s2)
+func (kub *Kubectl) GetNodeInfo(label string) (nodeName, nodeIP string) {
+	nodeName, err := kub.GetNodeNameByLabel(label)
+	gomega.ExpectWithOffset(1, err).To(gomega.BeNil(), "Cannot get node by label "+label)
+	nodeIP, err = kub.GetNodeIPByLabel(label, false)
+	gomega.ExpectWithOffset(1, err).Should(gomega.BeNil(), "Can not retrieve Node IP for "+label)
+	return nodeName, nodeIP
+}
+
 // GetCiliumPodOnNodeWithLabel returns the name of the Cilium pod that is running on node with cilium.io/ci-node label
 func (kub *Kubectl) GetCiliumPodOnNodeWithLabel(namespace string, label string) (string, error) {
 	node, err := kub.GetNodeNameByLabel(label)
diff --git a/test/helpers/utils.go b/test/helpers/utils.go
index e429394cc8..2c96382854 100644
--- a/test/helpers/utils.go
+++ b/test/helpers/utils.go
@@ -21,7 +21,6 @@ import (
 	"html/template"
 	"io"
 	"io/ioutil"
-	"math/rand"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -29,6 +28,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/cilium/cilium/pkg/rand"
 	"github.com/cilium/cilium/pkg/versioncheck"
 	"github.com/cilium/cilium/test/config"
 	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
@@ -40,7 +40,7 @@ import (
 )
 
 // ensure that our random numbers are seeded differently on each run
-var randGen = rand.New(rand.NewSource(time.Now().UnixNano()))
+var randGen = rand.NewSafeRand(time.Now().UnixNano())
 
 // IsRunningOnJenkins detects if the currently running Ginkgo application is
 // most likely running in a Jenkins environment. Returns true if certain
@@ -283,6 +283,25 @@ func CreateLogFile(filename string, data []byte) error {
 	return err
 }
 
+// WriteToReportFile writes data to filename. It appends to existing files.
+func WriteToReportFile(data []byte, filename string) error {
+	testPath, err := CreateReportDirectory()
+	if err != nil {
+		log.WithError(err).Errorf("cannot create test results path '%s'", testPath)
+		return err
+	}
+
+	err = WriteOrAppendToFile(
+		filepath.Join(testPath, filename),
+		data,
+		LogPerm)
+	if err != nil {
+		log.WithError(err).Errorf("cannot create monitor log file %s", filename)
+		return err
+	}
+	return nil
+}
+
 // reportMap saves the output of the given commands to the specified filename.
 // Function needs a directory path where the files are going to be written and
 // a *SSHMeta instance to execute the commands
diff --git a/test/k8sT/DatapathConfiguration.go b/test/k8sT/DatapathConfiguration.go
index 8aad1a9168..a51cb3aaad 100644
--- a/test/k8sT/DatapathConfiguration.go
+++ b/test/k8sT/DatapathConfiguration.go
@@ -17,7 +17,6 @@ package k8sTest
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"regexp"
 	"strconv"
 	"strings"
@@ -149,33 +148,45 @@ var _ = Describe("K8sDatapathConfig", func() {
 				"global.bpf.monitorFlags":       "syn",
 				"global.debug.enabled":          "false",
 			})
-			monitorOutput, targetIP := monitorConnectivityAcrossNodes(kubectl, monitorLog)
-
-			By("Checking that exactly one ICMP notification in each direction was observed")
-			expEgress := fmt.Sprintf("ICMPv4.*DstIP=%s", targetIP)
-			expEgressRegex := regexp.MustCompile(expEgress)
-			egressMatches := expEgressRegex.FindAllIndex(monitorOutput, -1)
-			Expect(len(egressMatches)).To(Equal(1), "Monitor log contained unexpected number of egress notifications matching %q", expEgress)
-
-			expIngress := fmt.Sprintf("ICMPv4.*SrcIP=%s", targetIP)
-			expIngressRegex := regexp.MustCompile(expIngress)
-			ingressMatches := expIngressRegex.FindAllIndex(monitorOutput, -1)
-			Expect(len(ingressMatches)).To(Equal(1), "Monitor log contained unexpected number of ingress notifications matching %q", expIngress)
-
-			By("Checking the set of TCP notifications received matches expectations")
-			// | TCP Flags | Direction | Report? | Why?
-			// +===========+===========+=========+=====
-			// | SYN       |    ->     |    Y    | monitorFlags=SYN
-			// | SYN / ACK |    <-     |    Y    | monitorFlags=SYN
-			// | ACK       |    ->     |    N    | monitorFlags=(!ACK)
-			// | ACK       |    ...    |    N    | monitorFlags=(!ACK)
-			// | ACK       |    <-     |    N    | monitorFlags=(!ACK)
-			// | FIN       |    ->     |    Y    | monitorAggregation=medium
-			// | FIN / ACK |    <-     |    Y    | monitorAggregation=medium
-			// | ACK       |    ->     |    Y    | monitorAggregation=medium
-			egressPktCount := 3
-			ingressPktCount := 2
-			checkMonitorOutput(monitorOutput, egressPktCount, ingressPktCount)
+			monitorRes, monitorCancel, targetIP := monitorConnectivityAcrossNodes(kubectl)
+			defer monitorCancel()
+
+			var monitorOutput []byte
+			body := func() bool {
+				monitorOutput = monitorRes.CombineOutput().Bytes()
+
+				By("Checking that exactly one ICMP notification in each direction was observed")
+				expEgress := fmt.Sprintf("ICMPv4.*DstIP=%s", targetIP)
+				expEgressRegex := regexp.MustCompile(expEgress)
+				egressMatches := expEgressRegex.FindAllIndex(monitorOutput, -1)
+				Expect(len(egressMatches)).To(Equal(1), "Monitor log contained unexpected number of egress notifications matching %q", expEgress)
+
+				expIngress := fmt.Sprintf("ICMPv4.*SrcIP=%s", targetIP)
+				expIngressRegex := regexp.MustCompile(expIngress)
+				ingressMatches := expIngressRegex.FindAllIndex(monitorOutput, -1)
+				Expect(len(ingressMatches)).To(Equal(1), "Monitor log contained unexpected number of ingress notifications matching %q", expIngress)
+
+				By("Checking the set of TCP notifications received matches expectations")
+				// | TCP Flags | Direction | Report? | Why?
+				// +===========+===========+=========+=====
+				// | SYN       |    ->     |    Y    | monitorFlags=SYN
+				// | SYN / ACK |    <-     |    Y    | monitorFlags=SYN
+				// | ACK       |    ->     |    N    | monitorFlags=(!ACK)
+				// | ACK       |    ...    |    N    | monitorFlags=(!ACK)
+				// | ACK       |    <-     |    N    | monitorFlags=(!ACK)
+				// | FIN       |    ->     |    Y    | monitorAggregation=medium
+				// | FIN / ACK |    <-     |    Y    | monitorAggregation=medium
+				// | ACK       |    ->     |    Y    | monitorAggregation=medium
+				egressPktCount := 3
+				ingressPktCount := 2
+				checkMonitorOutput(monitorOutput, egressPktCount, ingressPktCount)
+
+				return true
+			}
+
+			err := helpers.WithTimeout(body, "monitor does not restrict notifications when configured", &helpers.TimeoutConfig{Timeout: helpers.HelperTimeout})
+			Expect(err).To(BeNil(), "Could not read monitor log")
+			helpers.WriteToReportFile(monitorOutput, monitorLog)
 		})
 
 		It("Checks that monitor aggregation flags send notifications", func() {
@@ -185,23 +196,35 @@ var _ = Describe("K8sDatapathConfig", func() {
 				"global.bpf.monitorFlags":       "psh",
 				"global.debug.enabled":          "false",
 			})
-			monitorOutput, _ := monitorConnectivityAcrossNodes(kubectl, monitorLog)
-
-			By("Checking the set of TCP notifications received matches expectations")
-			// | TCP Flags | Direction | Report? | Why?
-			// +===========+===========+=========+=====
-			// | SYN       |    ->     |    Y    | monitorAggregation=medium
-			// | SYN / ACK |    <-     |    Y    | monitorAggregation=medium
-			// | ACK       |    ->     |    N    | monitorFlags=(!ACK)
-			// | ACK       |    ...    |    N    | monitorFlags=(!ACK)
-			// | PSH       |    ->     |    Y    | monitorFlags=(PSH)
-			// | PSH       |    <-     |    Y    | monitorFlags=(PSH)
-			// | FIN       |    ->     |    Y    | monitorAggregation=medium
-			// | FIN / ACK |    <-     |    Y    | monitorAggregation=medium
-			// | ACK       |    ->     |    Y    | monitorAggregation=medium
-			egressPktCount := 4
-			ingressPktCount := 3
-			checkMonitorOutput(monitorOutput, egressPktCount, ingressPktCount)
+			monitorRes, monitorCancel, _ := monitorConnectivityAcrossNodes(kubectl)
+			defer monitorCancel()
+
+			var monitorOutput []byte
+			body := func() bool {
+				monitorOutput = monitorRes.CombineOutput().Bytes()
+
+				By("Checking the set of TCP notifications received matches expectations")
+				// | TCP Flags | Direction | Report? | Why?
+				// +===========+===========+=========+=====
+				// | SYN       |    ->     |    Y    | monitorAggregation=medium
+				// | SYN / ACK |    <-     |    Y    | monitorAggregation=medium
+				// | ACK       |    ->     |    N    | monitorFlags=(!ACK)
+				// | ACK       |    ...    |    N    | monitorFlags=(!ACK)
+				// | PSH       |    ->     |    Y    | monitorFlags=(PSH)
+				// | PSH       |    <-     |    Y    | monitorFlags=(PSH)
+				// | FIN       |    ->     |    Y    | monitorAggregation=medium
+				// | FIN / ACK |    <-     |    Y    | monitorAggregation=medium
+				// | ACK       |    ->     |    Y    | monitorAggregation=medium
+				egressPktCount := 4
+				ingressPktCount := 3
+				checkMonitorOutput(monitorOutput, egressPktCount, ingressPktCount)
+
+				return true
+			}
+
+			err := helpers.WithTimeout(body, "monitor aggregation did not send notifications", &helpers.TimeoutConfig{Timeout: helpers.HelperTimeout})
+			Expect(err).To(BeNil(), "Could not read monitor log")
+			helpers.WriteToReportFile(monitorOutput, monitorLog)
 		})
 	})
 
@@ -588,7 +611,7 @@ func testPodNetperf(kubectl *helpers.Kubectl, requireMultiNode bool, callOffset
 	return res.WasSuccessful(), targetIP
 }
 
-func monitorConnectivityAcrossNodes(kubectl *helpers.Kubectl, monitorLog string) (monitorOutput []byte, targetIP string) {
+func monitorConnectivityAcrossNodes(kubectl *helpers.Kubectl) (monitorRes *helpers.CmdRes, monitorCancel func(), targetIP string) {
 	// For local single-node testing, configure requireMultiNode to "false"
 	// and add the labels "cilium.io/ci-node: k8s1" to the node.
 	requireMultiNode := true
@@ -597,19 +620,14 @@ func monitorConnectivityAcrossNodes(kubectl *helpers.Kubectl, monitorLog string)
 	ExpectWithOffset(1, err).Should(BeNil(), "Cannot get cilium pod on k8s1")
 
 	By(fmt.Sprintf("Launching cilium monitor on %q", ciliumPodK8s1))
-	monitorStop := kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPodK8s1, monitorLog)
+	monitorRes, monitorCancel = kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPodK8s1)
 	result, targetIP := testPodConnectivityAndReturnIP(kubectl, requireMultiNode, 2)
-	monitorStop()
 	ExpectWithOffset(1, result).Should(BeTrue(), "Connectivity test between nodes failed")
 
-	monitorPath := fmt.Sprintf("%s/%s", helpers.ReportDirectoryPath(), monitorLog)
-	By("Reading the monitor log at %s", monitorPath)
-	monitorOutput, err = ioutil.ReadFile(monitorPath)
-	ExpectWithOffset(1, err).To(BeNil(), "Could not read monitor log")
-	return monitorOutput, targetIP
+	return monitorRes, monitorCancel, targetIP
 }
 
-func checkMonitorOutput(monitorOutput []byte, egressPktCount, ingressPktCount int) {
+func checkMonitorOutput(monitorOutput []byte, egressPktCount, ingressPktCount int) bool {
 	// Multiple connection attempts may be made, we need to
 	// narrow down to the last connection close, then match
 	// the ephemeral port + flags to ensure that the
@@ -617,7 +635,10 @@ func checkMonitorOutput(monitorOutput []byte, egressPktCount, ingressPktCount in
 	egressTCPExpr := `TCP.*DstPort=80.*FIN=true`
 	egressTCPRegex := regexp.MustCompile(egressTCPExpr)
 	egressTCPMatches := egressTCPRegex.FindAll(monitorOutput, -1)
-	ExpectWithOffset(1, len(egressTCPMatches)).To(BeNumerically(">", 0), "Could not locate final FIN notification in monitor log")
+	if len(egressTCPMatches) <= 0 {
+		GinkgoPrint("Could not locate final FIN notification in monitor log: egressTCPMatches %+v", egressTCPMatches)
+		return false
+	}
 	finalMatch := egressTCPMatches[len(egressTCPMatches)-1]
 	portRegex := regexp.MustCompile(`SrcPort=([0-9]*)`)
 	// FindSubmatch should return ["SrcPort=12345" "12345"]
@@ -625,13 +646,26 @@ func checkMonitorOutput(monitorOutput []byte, egressPktCount, ingressPktCount in
 
 	By("Looking for TCP notifications using the ephemeral port %q", portBytes)
 	port, err := strconv.Atoi(string(portBytes))
-	ExpectWithOffset(1, err).To(BeNil(), fmt.Sprintf("ephemeral port %q could not be converted to integer", string(portBytes)))
+	if err != nil {
+		GinkgoPrint("ephemeral port %q could not be converted to integer: %s", string(portBytes), err)
+		return false
+	}
+
 	expEgress := fmt.Sprintf("SrcPort=%d", port)
 	expEgressRegex := regexp.MustCompile(expEgress)
 	egressMatches := expEgressRegex.FindAllIndex(monitorOutput, -1)
-	ExpectWithOffset(1, len(egressMatches)).To(Equal(egressPktCount), "Monitor log contained unexpected number of egress notifications matching %q", expEgress)
+	if len(egressMatches) != egressPktCount {
+		GinkgoPrint("Could not locate final FIN notification in monitor log: egressTCPMatches %+v", egressTCPMatches)
+		return false
+	}
+
 	expIngress := fmt.Sprintf("DstPort=%d", port)
 	expIngressRegex := regexp.MustCompile(expIngress)
 	ingressMatches := expIngressRegex.FindAllIndex(monitorOutput, -1)
-	ExpectWithOffset(1, len(ingressMatches)).To(Equal(ingressPktCount), "Monitor log contained unexpected number of ingress notifications matching %q", expIngress)
+	if len(ingressMatches) != ingressPktCount {
+		GinkgoPrint("Monitor log contained unexpected number of ingress notifications matching %q", expIngress)
+		return false
+	}
+
+	return true
 }
diff --git a/test/k8sT/Policies.go b/test/k8sT/Policies.go
index 271306b979..cc196e7dd0 100644
--- a/test/k8sT/Policies.go
+++ b/test/k8sT/Policies.go
@@ -17,8 +17,6 @@ package k8sTest
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
-	"regexp"
 	"time"
 
 	"github.com/cilium/cilium/api/v1/models"
@@ -65,6 +63,7 @@ var _ = Describe("K8sPolicyTest", func() {
 		backgroundCancel     context.CancelFunc = func() { return }
 		backgroundError      error
 		apps                 = []string{helpers.App1, helpers.App2, helpers.App3}
+		daemonCfg            map[string]string
 	)
 
 	BeforeAll(func() {
@@ -92,10 +91,11 @@ var _ = Describe("K8sPolicyTest", func() {
 		knpAllowEgress = helpers.ManifestGet(kubectl.BasePath(), "knp-default-allow-egress.yaml")
 		cnpMatchExpression = helpers.ManifestGet(kubectl.BasePath(), "cnp-matchexpressions.yaml")
 
-		DeployCiliumOptionsAndDNS(kubectl, ciliumFilename, map[string]string{
+		daemonCfg = map[string]string{
 			"global.tls.secretsBackend": "k8s",
 			"global.debug.verbose":      "flow",
-		})
+		}
+		DeployCiliumOptionsAndDNS(kubectl, ciliumFilename, daemonCfg)
 	})
 
 	AfterEach(func() {
@@ -124,6 +124,15 @@ var _ = Describe("K8sPolicyTest", func() {
 		backgroundCancel()
 	})
 
+	// getMatcher returns a helper.CMDSucess() matcher for success or
+	// failure situations.
+	getMatcher := func(val bool) types.GomegaMatcher {
+		if val {
+			return helpers.CMDSuccess()
+		}
+		return Not(helpers.CMDSuccess())
+	}
+
 	Context("Basic Test", func() {
 		var (
 			ciliumPod        string
@@ -139,15 +148,6 @@ var _ = Describe("K8sPolicyTest", func() {
 				"policy %s cannot be applied in %q namespace", file, namespaceForTest)
 		}
 
-		// getMatcher returns a helper.CMDSucess() matcher for success or
-		// failure situations.
-		getMatcher := func(val bool) types.GomegaMatcher {
-			if val {
-				return helpers.CMDSuccess()
-			}
-			return Not(helpers.CMDSuccess())
-		}
-
 		validateConnectivity := func(expectWorldSuccess, expectClusterSuccess bool) {
 			for _, pod := range []string{appPods[helpers.App2], appPods[helpers.App3]} {
 				By("HTTP connectivity to 1.1.1.1")
@@ -855,7 +855,7 @@ var _ = Describe("K8sPolicyTest", func() {
 			checkProxyRedirection := func(resource string, redirected bool, parser policy.L7ParserType) {
 				var (
 					not     = " "
-					re      *regexp.Regexp
+					reStr   string
 					curlCmd string
 				)
 
@@ -865,12 +865,10 @@ var _ = Describe("K8sPolicyTest", func() {
 
 				switch parser {
 				case policy.ParserTypeDNS:
-					reStr := "Request dns from.*Forwarded DNS Query:.*"
-					re = regexp.MustCompile(reStr)
+					reStr = "Request dns from.*Forwarded DNS Query:.*"
 					curlCmd = helpers.CurlFail(resource)
 				case policy.ParserTypeHTTP:
-					reStr := fmt.Sprintf("verdict Forwarded GET http://%s/public", resource)
-					re = regexp.MustCompile(reStr)
+					reStr = fmt.Sprintf("verdict Forwarded GET http://%s/public", resource)
 					curlCmd = helpers.CurlFail(fmt.Sprintf("http://%s/public", resource))
 				default:
 					Fail(fmt.Sprintf("invalid parser type for proxy visibility: %s", parser))
@@ -879,7 +877,12 @@ var _ = Describe("K8sPolicyTest", func() {
 				monitorFile := fmt.Sprintf(monitorFileName, uuid.NewUUID().String())
 
 				By("Starting monitor and generating traffic which should%s redirect to proxy", not)
-				monitorStop := kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPod, monitorFile)
+				monitorRes, monitorCancel := kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPod)
+				defer func() {
+					monitorCancel()
+					monitorRes.WaitUntilFinish()
+					helpers.WriteToReportFile(monitorRes.CombineOutput().Bytes(), monitorFile)
+				}()
 
 				// Let the monitor get started since it is started in the background.
 				time.Sleep(2 * time.Second)
@@ -888,19 +891,14 @@ var _ = Describe("K8sPolicyTest", func() {
 					curlCmd)
 				// Give time for the monitor to be notified of the proxy flow.
 				time.Sleep(2 * time.Second)
-				monitorStop()
 				res.ExpectSuccess("%q cannot curl %q", appPods[helpers.App2], resource)
-				monitorPath := fmt.Sprintf("%s/%s", helpers.ReportDirectoryPath(), monitorFile)
-				By("Reading the monitor log at %s", monitorPath)
-				monitorOutput, err := ioutil.ReadFile(monitorPath)
-				ExpectWithOffset(1, err).To(BeNil(), "Could not read monitor log")
 
 				By("Checking that aforementioned traffic was%sredirected to the proxy", not)
-				out := re.Find(monitorOutput)
+				err := monitorRes.WaitUntilMatchRegexp(reStr)
 				if redirected {
-					ExpectWithOffset(1, out).ToNot(BeNil(), "traffic was not redirected to the proxy when it should have been")
+					ExpectWithOffset(1, err).To(BeNil(), "traffic was not redirected to the proxy when it should have been")
 				} else {
-					ExpectWithOffset(1, out).To(BeNil(), "traffic was redirected to the proxy when it should have not been redirected")
+					ExpectWithOffset(1, err).ToNot(BeNil(), "traffic was redirected to the proxy when it should have not been redirected")
 				}
 			}
 
@@ -961,6 +959,144 @@ var _ = Describe("K8sPolicyTest", func() {
 
 	})
 
+	Context("Multi-node policy test", func() {
+		const (
+			testDS = "zgroup=testDS"
+
+			// This currently matches GetPodOnNodeWithOffset().
+			testNamespace = helpers.DefaultNamespace
+		)
+		var demoYAML string
+
+		BeforeAll(func() {
+			By("Deploying demo daemonset")
+			demoYAML = helpers.ManifestGet(kubectl.BasePath(), "demo_ds.yaml")
+			res := kubectl.ApplyDefault(demoYAML)
+			res.ExpectSuccess("Unable to apply %s", demoYAML)
+
+			err := kubectl.WaitforPods(testNamespace, fmt.Sprintf("-l %s", testDS), helpers.HelperTimeout)
+			Expect(err).Should(BeNil())
+		})
+
+		AfterAll(func() {
+			// Explicitly ignore result of deletion of resources to
+			// avoid incomplete teardown if any step fails.
+			_ = kubectl.Delete(demoYAML)
+			ExpectAllPodsTerminated(kubectl)
+		})
+
+		AfterEach(func() {
+			By("Cleaning up after the test")
+			cmd := fmt.Sprintf("%s delete --all cnp,netpol -n %s", helpers.KubectlCmd, testNamespace)
+			_ = kubectl.Exec(cmd)
+		})
+
+		Context("validates fromEntities policies", func() {
+			const (
+				HostConnectivityAllow       = true
+				RemoteNodeConnectivityDeny  = false
+				RemoteNodeConnectivityAllow = true
+			)
+
+			var (
+				cnpFromEntitiesHost       string
+				cnpFromEntitiesRemoteNode string
+				// TODO: Add fromEntities tests (GH-10979)
+				//cnpFromEntitiesCluster    string
+				//cnpFromEntitiesWorld      string
+				//cnpFromEntitiesAll        string
+
+				k8s1Name, k8s2Name   string
+				k8s1PodIP, k8s2PodIP string
+			)
+
+			BeforeAll(func() {
+				cnpFromEntitiesHost = helpers.ManifestGet(kubectl.BasePath(), "cnp-from-entities-host.yaml")
+				cnpFromEntitiesRemoteNode = helpers.ManifestGet(kubectl.BasePath(), "cnp-from-entities-remote-node.yaml")
+				k8s1Name, _ = kubectl.GetNodeInfo(helpers.K8s1)
+				k8s2Name, _ = kubectl.GetNodeInfo(helpers.K8s2)
+				_, k8s1PodIP = kubectl.GetPodOnNodeWithOffset(k8s1Name, testDS, 0)
+				_, k8s2PodIP = kubectl.GetPodOnNodeWithOffset(k8s2Name, testDS, 0)
+			})
+
+			AfterAll(func() {
+				By("Redeploying Cilium with default configuration")
+				RedeployCilium(kubectl, ciliumFilename, daemonCfg)
+			})
+
+			validateNodeConnectivity := func(expectHostSuccess, expectRemoteNodeSuccess bool) {
+				By("Checking ingress connectivity from k8s1 node to k8s1 pod (host)")
+				res, err := kubectl.ExecInHostNetNS(context.TODO(), k8s1Name,
+					helpers.CurlFail(k8s1PodIP))
+				Expect(err).To(BeNil(), "Cannot run curl in host netns")
+				ExpectWithOffset(1, res).To(getMatcher(expectHostSuccess),
+					"HTTP ingress connectivity to pod %q from local host", k8s1PodIP)
+
+				By("Checking ingress connectivity from k8s1 node to k8s2 pod (remote-node)")
+				res, err = kubectl.ExecInHostNetNS(context.TODO(), k8s1Name,
+					helpers.CurlFail(k8s2PodIP))
+				Expect(err).To(BeNil(), "Cannot run curl in host netns")
+				ExpectWithOffset(1, res).To(getMatcher(expectRemoteNodeSuccess),
+					"HTTP ingress connectivity to pod %q from remote node", k8s2PodIP)
+			}
+
+			importPolicy := func(file, name string) {
+				_, err := kubectl.CiliumPolicyAction(
+					testNamespace, file, helpers.KubectlApply, helpers.HelperTimeout)
+				ExpectWithOffset(1, err).Should(BeNil(),
+					"policy %s cannot be applied in %q namespace", file, testNamespace)
+			}
+
+			Context("with remote-node identity disabled", func() {
+				BeforeAll(func() {
+					By("Reconfiguring Cilium to disable remote-node identity")
+					newCfg := map[string]string{
+						"global.remoteNodeIdentity": "false",
+					}
+					for k, v := range daemonCfg {
+						newCfg[k] = v
+					}
+					RedeployCilium(kubectl, ciliumFilename, newCfg)
+				})
+
+				It("Allows from all hosts with cnp fromEntities host policy", func() {
+					By("Installing fromEntities host policy")
+					importPolicy(cnpFromEntitiesHost, "from-entities-host")
+
+					By("Checking policy correctness")
+					validateNodeConnectivity(HostConnectivityAllow, RemoteNodeConnectivityAllow)
+				})
+			})
+
+			Context("with remote-node identity enabled", func() {
+				BeforeAll(func() {
+					By("Reconfiguring Cilium to enable remote-node identity")
+					newCfg := map[string]string{
+						"global.remoteNodeIdentity": "true",
+					}
+					for k, v := range daemonCfg {
+						newCfg[k] = v
+					}
+					RedeployCilium(kubectl, ciliumFilename, newCfg)
+				})
+
+				It("Validates fromEntities remote-node policy", func() {
+					By("Installing default-deny ingress policy")
+					importPolicy(cnpDenyIngress, "default-deny-ingress")
+
+					By("Checking that remote-node is disallowed by default")
+					validateNodeConnectivity(HostConnectivityAllow, RemoteNodeConnectivityDeny)
+
+					By("Installing fromEntities remote-node policy")
+					importPolicy(cnpFromEntitiesRemoteNode, "from-entities-remote-node")
+
+					By("Checking policy correctness")
+					validateNodeConnectivity(HostConnectivityAllow, RemoteNodeConnectivityAllow)
+				})
+			})
+		})
+	})
+
 	Context("GuestBook Examples", func() {
 		var (
 			deployment                = "guestbook_deployment.yaml"
diff --git a/test/k8sT/Services.go b/test/k8sT/Services.go
index fba465f672..58a39af558 100644
--- a/test/k8sT/Services.go
+++ b/test/k8sT/Services.go
@@ -53,18 +53,6 @@ var _ = Describe("K8sServicesTest", func() {
 		ExpectWithOffset(1, err).Should(BeNil(), fmt.Sprintf("Error creating resource %s: %s", path, err))
 	}
 
-	// This is wrapped this way since BeforeAll sets kubectl and we must only
-	// run this after BeforeAll has completed. This happens during the actual
-	// Context/It/By calls.
-	getNodeInfo := func(label string) (nodeName, nodeIP string) {
-		// Nodes are used in testNodePort and testExternalTrafficPolicyLocal below
-		nodeName, err := kubectl.GetNodeNameByLabel(label)
-		Expect(err).To(BeNil(), "Cannot get node by label "+label)
-		nodeIP, err = kubectl.GetNodeIPByLabel(label, false)
-		Expect(err).Should(BeNil(), "Can not retrieve Node IP for "+label)
-		return nodeName, nodeIP
-	}
-
 	BeforeAll(func() {
 		var err error
 
@@ -212,14 +200,16 @@ var _ = Describe("K8sServicesTest", func() {
 			Expect(govalidator.IsIP(clusterIP)).Should(BeTrue(), "ClusterIP is not an IP")
 
 			By("testing connectivity via cluster IP %s", clusterIP)
-			monitorStop := kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPodK8s1,
-				"cluster-ip-same-node.log")
-			defer monitorStop()
+			monitorRes, monitorCancel := kubectl.MonitorStart(helpers.CiliumNamespace, ciliumPodK8s1)
+			defer func() {
+				monitorCancel()
+				helpers.WriteToReportFile(monitorRes.CombineOutput().Bytes(), "cluster-ip-same-node.log")
+			}()
 
 			httpSVCURL := fmt.Sprintf("http://%s/", clusterIP)
 			tftpSVCURL := fmt.Sprintf("tftp://%s/hello", clusterIP)
 
-			k8s1Name, _ := getNodeInfo(helpers.K8s1)
+			k8s1Name, _ := kubectl.GetNodeInfo(helpers.K8s1)
 			status, err := kubectl.ExecInHostNetNS(context.TODO(), k8s1Name,
 				helpers.CurlFail(httpSVCURL))
 			Expect(err).To(BeNil(), "Cannot run curl in host netns")
@@ -288,7 +278,7 @@ var _ = Describe("K8sServicesTest", func() {
 			})
 
 			It("Checks service on same node", func() {
-				k8s1Name, _ := getNodeInfo(helpers.K8s1)
+				k8s1Name, _ := kubectl.GetNodeInfo(helpers.K8s1)
 				status, err := kubectl.ExecInHostNetNS(context.TODO(), k8s1Name,
 					helpers.CurlFail(`"http://[%s]/"`, demoClusterIPv6))
 				Expect(err).To(BeNil(), "Cannot run curl in host netns")
@@ -436,7 +426,7 @@ var _ = Describe("K8sServicesTest", func() {
 					if checkSourceIP {
 						cmd += " | grep client_address="
 					}
-					clientNodeName, clientIP := getNodeInfo(helpers.GetNodeWithoutCilium())
+					clientNodeName, clientIP := kubectl.GetNodeInfo(helpers.GetNodeWithoutCilium())
 					res, err := kubectl.ExecInHostNetNS(context.TODO(), clientNodeName, cmd)
 					Expect(err).Should(BeNil(), "Cannot exec in k8s3 host netns")
 					ExpectWithOffset(1, res).Should(helpers.CMDSuccess(),
@@ -453,25 +443,6 @@ var _ = Describe("K8sServicesTest", func() {
 			doRequestsFromThirdHostWithLocalPort(url, count, checkSourceIP, 0)
 		}
 
-		getPodOnNode := func(nodeName string, podFilter string) (string, string) {
-			var podName string
-			podsNodes, err := kubectl.GetPodsNodes(helpers.DefaultNamespace, fmt.Sprintf("-l %s", podFilter))
-			ExpectWithOffset(2, err).Should(BeNil(), "Cannot retrieve pods nodes with filter %q", podFilter)
-			Expect(podsNodes).ShouldNot(BeEmpty(), "No pod found in namespace %s with filter %q", helpers.DefaultNamespace, podFilter)
-			for pod, node := range podsNodes {
-				if node == nodeName {
-					podName = pod
-					break
-				}
-			}
-			ExpectWithOffset(2, podName).ShouldNot(BeEmpty(), "Cannot retrieve pod on node %s with filter %q", nodeName, podFilter)
-			podsIPs, err := kubectl.GetPodsIPs(helpers.DefaultNamespace, podFilter)
-			ExpectWithOffset(2, err).Should(BeNil(), "Cannot retrieve pods IPs with filter %q", podFilter)
-			Expect(podsIPs).ShouldNot(BeEmpty(), "No pod IP found in namespace %s with filter %q", helpers.DefaultNamespace, podFilter)
-			podIP := podsIPs[podName]
-			return podName, podIP
-		}
-
 		// srcPod:      Name of pod sending the datagram
 		// srcIP:       IPv4 of pod sending the datagram
 		// fromNode:    Node hosting the pod sending the datagram
@@ -481,8 +452,8 @@ var _ = Describe("K8sServicesTest", func() {
 		// dstPort:     Target endpoint port for sending the datagram
 		doFragmentedRequest := func(srcPod string, srcIP string, fromNode string, dstPodPort int, dstIP string, dstPort int32) {
 			var (
-				blockSize  = 512
-				blockCount = 10
+				blockSize  = 5120
+				blockCount = 1
 				srcPort    = 12345
 			)
 			ciliumPodK8s1, err := kubectl.GetCiliumPodOnNode(helpers.CiliumNamespace, helpers.K8s1)
@@ -495,8 +466,8 @@ var _ = Describe("K8sServicesTest", func() {
 				ciliumPod = ciliumPodK8s2
 			}
 
-			_, dstPodIPK8s1 := getPodOnNode(helpers.K8s1, testDS)
-			_, dstPodIPK8s2 := getPodOnNode(helpers.K8s2, testDS)
+			_, dstPodIPK8s1 := kubectl.GetPodOnNodeWithOffset(helpers.K8s1, testDS, 1)
+			_, dstPodIPK8s2 := kubectl.GetPodOnNodeWithOffset(helpers.K8s2, testDS, 1)
 
 			// Get initial number of packets for the flow we test
 			// from conntrack table. The flow is probably not in
@@ -576,8 +547,8 @@ var _ = Describe("K8sServicesTest", func() {
 
 		testNodePort := func(bpfNodePort bool) {
 			var data v1.Service
-			k8s1Name, k8s1IP := getNodeInfo(helpers.K8s1)
-			k8s2Name, k8s2IP := getNodeInfo(helpers.K8s2)
+			k8s1Name, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
+			k8s2Name, k8s2IP := kubectl.GetNodeInfo(helpers.K8s2)
 
 			waitPodsDs()
 
@@ -723,7 +694,7 @@ var _ = Describe("K8sServicesTest", func() {
 
 			err := kubectl.Get(helpers.DefaultNamespace, "service test-nodeport").Unmarshal(&data)
 			Expect(err).Should(BeNil(), "Cannot retrieve service")
-			_, k8s1IP := getNodeInfo(helpers.K8s1)
+			_, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
 
 			httpURL := getHTTPLink(k8s1IP, data.Spec.Ports[0].NodePort)
 			tftpURL := getTFTPLink(k8s1IP, data.Spec.Ports[1].NodePort)
@@ -757,8 +728,8 @@ var _ = Describe("K8sServicesTest", func() {
 				tftpURL string
 			)
 
-			k8s1Name, k8s1IP := getNodeInfo(helpers.K8s1)
-			k8s2Name, k8s2IP := getNodeInfo(helpers.K8s2)
+			k8s1Name, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
+			k8s2Name, k8s2IP := kubectl.GetNodeInfo(helpers.K8s2)
 
 			// Checks requests are not SNATed when externalTrafficPolicy=Local
 			err := kubectl.Get(helpers.DefaultNamespace, "service test-nodeport-local").Unmarshal(&data)
@@ -800,8 +771,8 @@ var _ = Describe("K8sServicesTest", func() {
 				tftpURL string
 			)
 
-			k8s1Name, _ := getNodeInfo(helpers.K8s1)
-			k8s2Name, k8s2IP := getNodeInfo(helpers.K8s2)
+			k8s1Name, _ := kubectl.GetNodeInfo(helpers.K8s1)
+			k8s2Name, k8s2IP := kubectl.GetNodeInfo(helpers.K8s2)
 
 			httpHostPort := int32(8080)
 			tftpHostPort := int32(6969)
@@ -835,8 +806,8 @@ var _ = Describe("K8sServicesTest", func() {
 
 		testHealthCheckNodePort := func() {
 			var data v1.Service
-			k8s1Name, k8s1IP := getNodeInfo(helpers.K8s1)
-			k8s2Name, k8s2IP := getNodeInfo(helpers.K8s2)
+			k8s1Name, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
+			k8s2Name, k8s2IP := kubectl.GetNodeInfo(helpers.K8s2)
 
 			// Service with HealthCheckNodePort that only has backends on k8s2
 			err := kubectl.Get(helpers.DefaultNamespace, "service test-lb-local-k8s2").Unmarshal(&data)
@@ -857,15 +828,15 @@ var _ = Describe("K8sServicesTest", func() {
 
 		testIPv4FragmentSupport := func() {
 			var data v1.Service
-			k8s1Name, k8s1IP := getNodeInfo(helpers.K8s1)
-			k8s2Name, k8s2IP := getNodeInfo(helpers.K8s2)
+			k8s1Name, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
+			k8s2Name, k8s2IP := kubectl.GetNodeInfo(helpers.K8s2)
 
 			waitPodsDs()
 
 			// Get testDSClient and testDS pods running on k8s1.
 			// This is because we search for new packets in the
 			// conntrack table for node k8s1.
-			clientPod, clientIP := getPodOnNode(helpers.K8s1, testDSClient)
+			clientPod, clientIP := kubectl.GetPodOnNodeWithOffset(helpers.K8s1, testDSClient, 1)
 
 			err := kubectl.Get(helpers.DefaultNamespace, "service test-nodeport").Unmarshal(&data)
 			Expect(err).Should(BeNil(), "Cannot retrieve service")
@@ -1030,7 +1001,7 @@ var _ = Describe("K8sServicesTest", func() {
 					var data v1.Service
 					err := kubectl.Get(helpers.DefaultNamespace, "service test-nodeport").Unmarshal(&data)
 					Expect(err).Should(BeNil(), "Cannot retrieve service")
-					_, k8s1IP := getNodeInfo(helpers.K8s1)
+					_, k8s1IP := kubectl.GetNodeInfo(helpers.K8s1)
 					url := getHTTPLink(k8s1IP, data.Spec.Ports[0].NodePort)
 					doRequestsFromThirdHost(url, 10, true)
 
diff --git a/test/k8sT/assertionHelpers.go b/test/k8sT/assertionHelpers.go
index 72b381885c..cca32d319a 100644
--- a/test/k8sT/assertionHelpers.go
+++ b/test/k8sT/assertionHelpers.go
@@ -92,13 +92,27 @@ func DeployCiliumAndDNS(vm *helpers.Kubectl, ciliumFilename string) {
 	DeployCiliumOptionsAndDNS(vm, ciliumFilename, map[string]string{"global.debug.verbose": "flow"})
 }
 
-// DeployCiliumOptionsAndDNS deploys DNS and cilium with options into the kubernetes cluster
-func DeployCiliumOptionsAndDNS(vm *helpers.Kubectl, ciliumFilename string, options map[string]string) {
+func redeployCilium(vm *helpers.Kubectl, ciliumFilename string, options map[string]string) {
 	By("Installing Cilium")
 	err := vm.CiliumInstall(ciliumFilename, options)
 	Expect(err).To(BeNil(), "Cilium cannot be installed")
 
 	ExpectCiliumRunning(vm)
+}
+
+// RedeployCilium reinstantiates the Cilium DS and ensures it is running.
+//
+// This helper is only appropriate for reconfiguring Cilium in the middle of
+// an existing testsuite that calls DeployCiliumAndDNS(...).
+func RedeployCilium(vm *helpers.Kubectl, ciliumFilename string, options map[string]string) {
+	redeployCilium(vm, ciliumFilename, options)
+	ExpectCiliumReady(vm)
+	ExpectCiliumOperatorReady(vm)
+}
+
+// DeployCiliumOptionsAndDNS deploys DNS and cilium with options into the kubernetes cluster
+func DeployCiliumOptionsAndDNS(vm *helpers.Kubectl, ciliumFilename string, options map[string]string) {
+	redeployCilium(vm, ciliumFilename, options)
 
 	By("Installing DNS Deployment")
 	switch helpers.GetCurrentIntegration() {
diff --git a/test/k8sT/manifests/cnp-from-entities-host.yaml b/test/k8sT/manifests/cnp-from-entities-host.yaml
new file mode 100644
index 0000000000..af35587148
--- /dev/null
+++ b/test/k8sT/manifests/cnp-from-entities-host.yaml
@@ -0,0 +1,11 @@
+apiVersion: "cilium.io/v2"
+kind: CiliumNetworkPolicy
+metadata:
+  name: "from-entities-host"
+spec:
+  endpointSelector:
+    matchLabels:
+      {}
+  ingress:
+  - fromEntities:
+    - host
diff --git a/test/k8sT/manifests/cnp-from-entities-remote-node.yaml b/test/k8sT/manifests/cnp-from-entities-remote-node.yaml
new file mode 100644
index 0000000000..8b4f1c7d7f
--- /dev/null
+++ b/test/k8sT/manifests/cnp-from-entities-remote-node.yaml
@@ -0,0 +1,11 @@
+apiVersion: "cilium.io/v2"
+kind: CiliumNetworkPolicy
+metadata:
+  name: "from-entities-remote-node"
+spec:
+  endpointSelector:
+    matchLabels:
+      {}
+  ingress:
+  - fromEntities:
+    - remote-node
diff --git a/test/kubernetes-test.sh b/test/kubernetes-test.sh
index 05f1cdce50..8f93870ac9 100755
--- a/test/kubernetes-test.sh
+++ b/test/kubernetes-test.sh
@@ -60,7 +60,7 @@ git clone https://github.com/kubernetes/kubernetes.git -b ${KUBERNETES_VERSION}
 cd kubernetes
 
 # Kubernetes is only compiling with golang 1.13.4 for versions >=1.17
-GO_VERSION="1.14.1"
+GO_VERSION="1.14.2"
 sudo rm -fr /usr/local/go
 curl -LO https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz
 sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
diff --git a/test/packet/scripts/install.sh b/test/packet/scripts/install.sh
index 99219ad10e..f5fb2d07ee 100644
--- a/test/packet/scripts/install.sh
+++ b/test/packet/scripts/install.sh
@@ -5,7 +5,7 @@ set -e
 # Ensure no prompts from apt & co.
 export DEBIAN_FRONTEND=noninteractive
 
-GOLANG_VERSION="1.14.1"
+GOLANG_VERSION="1.14.2"
 VAGRANT_VERSION="2.2.4"
 PACKER_VERSION="1.3.5"
 VIRTUALBOX_VERSION="6.0"
diff --git a/test/runtime/monitor.go b/test/runtime/monitor.go
index 38d2ef5384..41671ea07f 100644
--- a/test/runtime/monitor.go
+++ b/test/runtime/monitor.go
@@ -17,9 +17,7 @@ package RuntimeTest
 import (
 	"context"
 	"fmt"
-	"math/rand"
 	"strings"
-	"time"
 
 	. "github.com/cilium/cilium/test/ginkgo-ext"
 	"github.com/cilium/cilium/test/helpers"
@@ -27,9 +25,6 @@ import (
 	. "github.com/onsi/gomega"
 )
 
-// ensure that our random numbers are seeded differently on each run
-var randGen = rand.New(rand.NewSource(time.Now().UnixNano()))
-
 const (
 	// MonitorDropNotification represents the DropNotification configuration
 	// value for the Cilium monitor
diff --git a/test/startup-script/Makefile b/test/startup-script/Makefile
index ac81e6eb1d..54061df1df 100644
--- a/test/startup-script/Makefile
+++ b/test/startup-script/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 TAG=v1
 IMAGE=docker.io/cilium/startup-script
 
diff --git a/tests/Makefile b/tests/Makefile
index f50e9f49f2..1978f408c8 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,5 +1,7 @@
-include ../Makefile.defs
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
 
+include ../Makefile.defs
 
 runtime-tests:
 	./run-tests
diff --git a/tests/k8s/Makefile b/tests/k8s/Makefile
index 64aff3b5a4..a298501461 100644
--- a/tests/k8s/Makefile
+++ b/tests/k8s/Makefile
@@ -1,3 +1,6 @@
+# Copyright 2017-2019 Authors of Cilium
+# SPDX-License-Identifier: Apache-2.0
+
 include ../../Makefile.defs
 
 k8s-multi-node-tests: