Repository: cilium/cilium
Pull request #11038 (2020-04-17 16:16:20)

v1.7 backports 2020-04-17
 * #10936 -- endpoint: Avoid transient drops during policy map update (@jrajahalme)
 * #10949 -- endpoint: Do not skip datapath rewrites on duplicate regenerations (@jrajahalme)
 * #11006 -- Fix issue where --enable-remote-node-identity=false causes policy drops (@joestringer)

Once this PR is merged, you can update the PR labels via:
```upstream-prs
$ for pr in 10936 10949 11006; do contrib/backporting/set-labels.py $pr done 1.7; done
```


jrajahalme: test-me-please
jrajahalme: test-me-please
jrajahalme: never-tell-me-the-odds
jrajahalme: test-gke
jrajahalme: `Cilium-Ginkgo-Tests` passed fired more tests.
joestringer: I don't think GKE / Kernel tests are enabled on v1.7 branch /cc @nebril 
joestringer: never-tell-me-the-odds
joestringer: Lots of service issues in CI, some of them affecting both 1.11/net-next and 1.17 builds. We'll need to triage these to understand whether they are the known flakes in the [v1.7 project](https://github.com/cilium/cilium/projects/85) or new.
brb: `"Timeout reached: timed out waiting for pods with filter -l k8s-app=cilium to be ready"` - this is worrisome. I've opened https://github.com/cilium/cilium/pull/11075 to investigate whether `v1.7` is broken.
brb: Just ran the ginkgo locally for this PR. Hit the same `"Timeout reached: timed out waiting for pods with filter -l k8s-app=cilium to be ready"` which is due to:
```
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  13m                   default-scheduler  Successfully assigned kube-system/cilium-p46l6 to k8s2
  Normal   Pulling    12m (x4 over 13m)     kubelet, k8s2      Pulling image "k8s1:5000/cilium/cilium-dev:latest"
  Warning  Failed     12m (x4 over 13m)     kubelet, k8s2      Failed to pull image "k8s1:5000/cilium/cilium-dev:latest": rpc error: code = Unknown desc = Error response from daemon: manifest for k8s1:5000/cilium/cilium-dev:latest not found: manifest unknown: manifest unknown
  Warning  Failed     12m (x4 over 13m)     kubelet, k8s2      Error: ErrImagePull
  Warning  Failed     11m (x6 over 13m)     kubelet, k8s2      Error: ImagePullBackOff
  Normal   BackOff    3m26s (x42 over 13m)  kubelet, k8s2      Back-off pulling image "k8s1:5000/cilium/cilium-dev:latest"
```

```
vagrant@k8s1:~$ docker pull k8s1:5000/cilium/cilium-dev:latest
Error response from daemon: manifest for k8s1:5000/cilium/cilium-dev:latest not found: manifest unknown: manifest unknown
```
brb: This might be a suspect: https://github.com/cilium/cilium/commit/5346170b6f7ed121a58cbad78587c1e5f3c62587.

@nebril Why did we stop pushing the images? `$CILIUM_IMAGE` seems to be not set in any Jenkinsfile except for EKS.
brb: never-tell-me-the-odds
brb: test-missed-k8s

Patch:
diff --git a/.travis.yml b/.travis.yml
index 0f53af4068..def364f8cb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ dist: trusty
 sudo: required
 
 go:
- - 1.13.9
+ - 1.13.10
 
 if: branch = master OR type = pull_request
 
@@ -17,6 +17,6 @@ before_install: ./.travis/prepare.sh
 
 before_script:
   - export PATH=/usr/local/clang/bin:$PATH
-  - export GO=/home/travis/.gimme/versions/go1.13.9.linux.amd64/bin/go
+  - export GO=/home/travis/.gimme/versions/go1.13.10.linux.amd64/bin/go
 
 script: ./.travis/build.sh
diff --git a/Dockerfile b/Dockerfile
index f41b6d6a12..8b0c4bbfa6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,7 +13,7 @@ FROM quay.io/cilium/cilium-envoy:63de0bd958d05d82e2396125dcf6286d92464c56 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-03-20-v1.7 as builder
+FROM quay.io/cilium/cilium-builder:2020-04-09-v1.7 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-03-20-v1.7
+FROM quay.io/cilium/cilium-runtime:2020-04-09-v1.7
 LABEL maintainer="maintainer@cilium.io"
 COPY --from=builder /tmp/install /
 COPY --from=cilium-envoy / /
diff --git a/Dockerfile.builder b/Dockerfile.builder
index e09b2ae60e..89185e7887 100644
--- a/Dockerfile.builder
+++ b/Dockerfile.builder
@@ -14,7 +14,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.13.9
+ENV GO_VERSION 1.13.10
 
 #
 # Build dependencies
diff --git a/Documentation/contributing/development/dev_setup.rst b/Documentation/contributing/development/dev_setup.rst
index 9ccd29b1ab..b3d8c0d4e6 100644
--- a/Documentation/contributing/development/dev_setup.rst
+++ b/Documentation/contributing/development/dev_setup.rst
@@ -28,7 +28,7 @@ contribute to Cilium:
 +----------------------------------------------------------------------------------+--------------------------+-------------------------------------------------------------------------------+
 |  libelf-devel                                                                    | latest                   | N/A (OS-specific)                                                             |
 +----------------------------------------------------------------------------------+--------------------------+-------------------------------------------------------------------------------+
-| `go <https://golang.org/dl/>`_                                                   | 1.13.9                   | N/A (OS-specific)                                                             |
+| `go <https://golang.org/dl/>`_                                                   | 1.13.10                  | N/A (OS-specific)                                                             |
 +----------------------------------------------------------------------------------+--------------------------+-------------------------------------------------------------------------------+
 | `go-bindata <https://github.com/cilium/go-bindata>`_                             | ``a0ff2567cfb``          | ``go get -u github.com/cilium/go-bindata/...``                                |
 +----------------------------------------------------------------------------------+--------------------------+-------------------------------------------------------------------------------+
diff --git a/Documentation/install/upgrade.rst b/Documentation/install/upgrade.rst
index 1055b67d8a..0a853621e9 100644
--- a/Documentation/install/upgrade.rst
+++ b/Documentation/install/upgrade.rst
@@ -437,13 +437,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
@@ -461,16 +461,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
@@ -479,19 +479,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/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 084f22677b..f3352b3fef 100644
--- a/GO_VERSION
+++ b/GO_VERSION
@@ -1,1 +1,1 @@
-1.13.9
+1.13.10
diff --git a/Makefile b/Makefile
index 47e9425771..46f7bbf303 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ JOB_BASE_NAME ?= cilium_test
 
 UTC_DATE=$(shell date -u "+%Y-%m-%d")
 
-GO_VERSION := 1.13.9
+GO_VERSION := 1.13.10
 
 # Since there's a bug with NFS or the kernel, the flock syscall hangs the documentation
 # build in the developer VM. For this reason the documentation build is skipped if NFS
diff --git a/cilium-docker-plugin.Dockerfile b/cilium-docker-plugin.Dockerfile
index d949753018..1009cc75b7 100644
--- a/cilium-docker-plugin.Dockerfile
+++ b/cilium-docker-plugin.Dockerfile
@@ -1,4 +1,4 @@
-FROM docker.io/library/golang:1.13.9 as builder
+FROM docker.io/library/golang:1.13.10 as builder
 LABEL maintainer="maintainer@cilium.io"
 ADD . /go/src/github.com/cilium/cilium
 WORKDIR /go/src/github.com/cilium/cilium/plugins/cilium-docker
diff --git a/cilium-operator.Dockerfile b/cilium-operator.Dockerfile
index 164e2a18b7..2e59601f4a 100644
--- a/cilium-operator.Dockerfile
+++ b/cilium-operator.Dockerfile
@@ -1,4 +1,4 @@
-FROM docker.io/library/golang:1.13.9 as builder
+FROM docker.io/library/golang:1.13.10 as builder
 LABEL maintainer="maintainer@cilium.io"
 ADD . /go/src/github.com/cilium/cilium
 WORKDIR /go/src/github.com/cilium/cilium/operator
diff --git a/contrib/packaging/docker/Dockerfile.runtime b/contrib/packaging/docker/Dockerfile.runtime
index 3ab76640de..a9bfc83904 100644
--- a/contrib/packaging/docker/Dockerfile.runtime
+++ b/contrib/packaging/docker/Dockerfile.runtime
@@ -81,7 +81,7 @@ apt-get clean
 #
 # Go-based tools we need at runtime
 #
-FROM docker.io/library/golang:1.13.9 as runtime-gobuild
+FROM docker.io/library/golang:1.13.10 as runtime-gobuild
 WORKDIR /tmp
 RUN go get -d github.com/google/gops && \
 cd /go/src/github.com/google/gops && \
diff --git a/daemon/daemon.go b/daemon/daemon.go
index 0b55ff9b1e..b42c11b373 100644
--- a/daemon/daemon.go
+++ b/daemon/daemon.go
@@ -391,7 +391,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/endpoint.go b/daemon/endpoint.go
index c6bb0f5d32..5da9b17deb 100644
--- a/daemon/endpoint.go
+++ b/daemon/endpoint.go
@@ -27,6 +27,7 @@ import (
 	"github.com/cilium/cilium/pkg/api"
 	"github.com/cilium/cilium/pkg/endpoint"
 	endpointid "github.com/cilium/cilium/pkg/endpoint/id"
+	"github.com/cilium/cilium/pkg/endpoint/regeneration"
 	"github.com/cilium/cilium/pkg/k8s"
 	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
 	"github.com/cilium/cilium/pkg/labels"
@@ -293,11 +294,18 @@ func (d *Daemon) createEndpoint(ctx context.Context, epTemplate *models.Endpoint
 		}
 	}
 
+	// e.ID assigned here
 	err = d.endpointManager.AddEndpoint(d, ep, "Create endpoint from API PUT")
 	if err != nil {
 		return d.errorDuringCreation(ep, fmt.Errorf("unable to insert endpoint into manager: %s", err))
 	}
 
+	// Now that we have ep.ID we can pin the map from this point. This
+	// also has to happen before the first build takes place.
+	if err = ep.PinDatapathMap(); err != nil {
+		return d.errorDuringCreation(ep, fmt.Errorf("unable to pin datapath maps: %s", err))
+	}
+
 	// We need to update the the visibility policy after adding the endpoint in
 	// the endpoint manager because the endpoint manager create the endpoint
 	// queue of the endpoint. If we execute this function before the endpoint
@@ -313,7 +321,7 @@ func (d *Daemon) createEndpoint(ctx context.Context, epTemplate *models.Endpoint
 		})
 	}
 
-	ep.UpdateLabels(ctx, addLabels, infoLabels, true)
+	regenTriggered := ep.UpdateLabels(ctx, addLabels, infoLabels, true)
 
 	select {
 	case <-ctx.Done():
@@ -321,14 +329,25 @@ func (d *Daemon) createEndpoint(ctx context.Context, epTemplate *models.Endpoint
 	default:
 	}
 
-	// Now that we have ep.ID we can pin the map from this point. This
-	// also has to happen before the first build took place.
-	if err = ep.PinDatapathMap(); err != nil {
-		return d.errorDuringCreation(ep, fmt.Errorf("unable to pin datapath maps: %s", err))
+	if !regenTriggered {
+		regenMetadata := &regeneration.ExternalRegenerationMetadata{
+			Reason:            "Initial build on endpoint creation",
+			ParentContext:     ctx,
+			RegenerationLevel: regeneration.RegenerateWithDatapathRewrite,
+		}
+		build, err := ep.SetRegenerateStateIfAlive(regenMetadata)
+		if err != nil {
+			return d.errorDuringCreation(ep, err)
+		}
+		if build {
+			ep.Regenerate(regenMetadata)
+		}
 	}
 
-	if err := ep.RegenerateAfterCreation(ctx, epTemplate.SyncBuildEndpoint); err != nil {
-		return d.errorDuringCreation(ep, err)
+	if epTemplate.SyncBuildEndpoint {
+		if err := ep.WaitForFirstRegeneration(ctx); err != nil {
+			return d.errorDuringCreation(ep, err)
+		}
 	}
 
 	// The endpoint has been successfully created, stop the expiration
@@ -439,8 +458,14 @@ func (h *patchEndpointID) Handle(params PatchEndpointIDParams) middleware.Respon
 	}
 
 	if reason != "" {
-		if err := ep.RegenerateWait(reason); err != nil {
-			return api.Error(PatchEndpointIDFailedCode, err)
+		regenMetadata := &regeneration.ExternalRegenerationMetadata{
+			Reason:            reason,
+			RegenerationLevel: regeneration.RegenerateWithDatapathRewrite,
+		}
+		if !<-ep.Regenerate(regenMetadata) {
+			return api.Error(PatchEndpointIDFailedCode,
+				fmt.Errorf("error while regenerating endpoint."+
+					" For more info run: 'cilium endpoint get %d'", ep.ID))
 		}
 		// FIXME: Special return code to indicate regeneration happened?
 	}
diff --git a/daemon/policy.go b/daemon/policy.go
index 6488954517..2ba6ec2694 100644
--- a/daemon/policy.go
+++ b/daemon/policy.go
@@ -68,7 +68,10 @@ func (d *Daemon) policyUpdateTrigger(reasons []string) {
 	log.Debugf("Regenerating all endpoints")
 	reason := strings.Join(reasons, ", ")
 
-	regenerationMetadata := &regeneration.ExternalRegenerationMetadata{Reason: reason}
+	regenerationMetadata := &regeneration.ExternalRegenerationMetadata{
+		Reason:            reason,
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
+	}
 	d.endpointManager.RegenerateAllEndpoints(regenerationMetadata)
 }
 
@@ -452,7 +455,10 @@ func reactToRuleUpdates(epsToBumpRevision, epsToRegen *policy.EndpointSet, rev u
 	})
 
 	// Regenerate all other endpoints.
-	regenMetadata := &regeneration.ExternalRegenerationMetadata{Reason: "policy rules added"}
+	regenMetadata := &regeneration.ExternalRegenerationMetadata{
+		Reason:            "policy rules added",
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
+	}
 	epsToRegen.ForEachGo(&enqueueWaitGroup, func(ep policy.Endpoint) {
 		if ep != nil {
 			switch e := ep.(type) {
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/go.mod b/go.mod
index 5ba1f4a1bd..ae1c824a3b 100644
--- a/go.mod
+++ b/go.mod
@@ -83,13 +83,13 @@ require (
 	gopkg.in/fsnotify.v1 v1.4.7
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.2.8
-	k8s.io/api v0.17.4
+	k8s.io/api v0.17.5
 	k8s.io/apiextensions-apiserver v0.0.0
-	k8s.io/apimachinery v0.17.4
+	k8s.io/apimachinery v0.17.5
 	k8s.io/client-go v8.0.0+incompatible
 	k8s.io/code-generator v0.0.0
 	k8s.io/klog v1.0.0
-	k8s.io/kubernetes v1.17.4
+	k8s.io/kubernetes v1.17.5
 	sigs.k8s.io/yaml v1.1.0
 )
 
@@ -604,34 +604,34 @@ replace (
 	honnef.co/go/tools => honnef.co/go/tools v0.0.1-2019.2.2
 	istio.io/gogo-genproto => istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb
 
-	// v0.0.0-20200312205431-8d8aa3959853 -> k8s v1.17.4
-	k8s.io/api => k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/apiextensions-apiserver => k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/apimachinery => k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/apiserver => k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/cli-runtime => k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/client-go => github.com/cilium/client-go v0.0.0-20200323094943-b43e7e2f9a75
-	k8s.io/cloud-provider => k8s.io/kubernetes/staging/src/k8s.io/cloud-provider v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/cluster-bootstrap => k8s.io/kubernetes/staging/src/k8s.io/cluster-bootstrap v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/code-generator => k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/component-base => k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/cri-api => k8s.io/kubernetes/staging/src/k8s.io/cri-api v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/csi-translation-lib => k8s.io/kubernetes/staging/src/k8s.io/csi-translation-lib v0.0.0-20200312205431-8d8aa3959853
+	// v0.0.0-20200416113457-e0fccafd6954 -> k8s v1.17.5
+	k8s.io/api => k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/apiextensions-apiserver => k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/apimachinery => k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/apiserver => k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/cli-runtime => k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/client-go => github.com/cilium/client-go v0.0.0-20200417200125-fb1ce7014cf6
+	k8s.io/cloud-provider => k8s.io/kubernetes/staging/src/k8s.io/cloud-provider v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/cluster-bootstrap => k8s.io/kubernetes/staging/src/k8s.io/cluster-bootstrap v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/code-generator => k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/component-base => k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/cri-api => k8s.io/kubernetes/staging/src/k8s.io/cri-api v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/csi-translation-lib => k8s.io/kubernetes/staging/src/k8s.io/csi-translation-lib v0.0.0-20200416113457-e0fccafd6954
 	k8s.io/gengo => k8s.io/gengo v0.0.0-20190822140433-26a664648505
 	k8s.io/heapster => k8s.io/heapster v1.2.0-beta.1
 	k8s.io/klog => k8s.io/klog v1.0.0
-	k8s.io/kube-aggregator => k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/kube-controller-manager => k8s.io/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20200312205431-8d8aa3959853
+	k8s.io/kube-aggregator => k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/kube-controller-manager => k8s.io/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20200416113457-e0fccafd6954
 	k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
-	k8s.io/kube-proxy => k8s.io/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/kube-scheduler => k8s.io/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/kubectl => k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/kubelet => k8s.io/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/kubernetes => k8s.io/kubernetes v1.17.4
-	k8s.io/legacy-cloud-providers => k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers v0.0.0-20200312205431-8d8aa3959853
-	k8s.io/metrics => k8s.io/kubernetes/staging/src/k8s.io/metrics v0.0.0-20200312205431-8d8aa3959853
+	k8s.io/kube-proxy => k8s.io/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/kube-scheduler => k8s.io/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/kubectl => k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/kubelet => k8s.io/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/kubernetes => k8s.io/kubernetes v1.17.5
+	k8s.io/legacy-cloud-providers => k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers v0.0.0-20200416113457-e0fccafd6954
+	k8s.io/metrics => k8s.io/kubernetes/staging/src/k8s.io/metrics v0.0.0-20200416113457-e0fccafd6954
 	k8s.io/repo-infra => k8s.io/repo-infra v0.0.1-alpha.1
-	k8s.io/sample-apiserver => k8s.io/kubernetes/staging/src/k8s.io/sample-apiserver v0.0.0-20200312205431-8d8aa3959853
+	k8s.io/sample-apiserver => k8s.io/kubernetes/staging/src/k8s.io/sample-apiserver v0.0.0-20200416113457-e0fccafd6954
 	k8s.io/system-validators => k8s.io/system-validators v1.0.4
 	k8s.io/utils => k8s.io/utils v0.0.0-20191114184206-e782cd3c129f
 	modernc.org/cc => modernc.org/cc v1.0.0
@@ -645,6 +645,7 @@ replace (
 	rsc.io/goversion => rsc.io/goversion v1.0.0
 	sigs.k8s.io/kustomize => sigs.k8s.io/kustomize v2.0.3+incompatible
 	sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06
+	sigs.k8s.io/structured-merge-diff/v2 => sigs.k8s.io/structured-merge-diff/v2 v2.0.1
 	sigs.k8s.io/yaml => sigs.k8s.io/yaml v1.1.0
 	sourcegraph.com/sqs/pbtypes => sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4
 	vbom.ml/util => vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc
diff --git a/go.sum b/go.sum
index b603ba1c9a..98ed12c078 100644
--- a/go.sum
+++ b/go.sum
@@ -87,8 +87,8 @@ github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod
 github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
 github.com/cilium/arping v1.0.1-0.20190728065459-c5eaf8d7a710 h1:htVjkajqUYy6JmLMGlZYxfZ4urQq7rDvgUfmSJX7fSg=
 github.com/cilium/arping v1.0.1-0.20190728065459-c5eaf8d7a710/go.mod h1:ohfPr9hSb4vRsw5UEjCs3OoWD38U0z8EfSgZPbIe/+M=
-github.com/cilium/client-go v0.0.0-20200323094943-b43e7e2f9a75 h1:ZlSMc4DobhUVnIf1LoqRX6392nkH5JrCtoiY03utsGQ=
-github.com/cilium/client-go v0.0.0-20200323094943-b43e7e2f9a75/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
+github.com/cilium/client-go v0.0.0-20200417200125-fb1ce7014cf6 h1:D0xPKvmOdw3/AFspR215WuCTvjFnaXmcB2jVkCqmOHg=
+github.com/cilium/client-go v0.0.0-20200417200125-fb1ce7014cf6/go.mod h1:S8uZpBpjJJdEH/fEyxcqg7Rn0P5jH+ilkgBHjriSmNo=
 github.com/cilium/dns v1.1.4-0.20190417235132-8e25ec9a0ff3 h1:wenYMyWJ08dgEUUj0Ija8qdK/V9vL3ThAD5sjOYlFlg=
 github.com/cilium/dns v1.1.4-0.20190417235132-8e25ec9a0ff3/go.mod h1:cXN7jgo+gsGlNvQ7Vqu2ELdc3f7i7PPgupHqSkLzzBo=
 github.com/cilium/ebpf v0.0.0-20191113100448-d9fb101ca1fb h1:bQ0NJ9dAB8vsw7ffajBDX/7Wr64BdLWeJkYY36UkeRY=
@@ -639,32 +639,32 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
-k8s.io/kubernetes v1.17.4 h1:hnz5goC7sf4LyG9kjJv6eBBJ/8DeD6TL3UAdCoHpaJE=
-k8s.io/kubernetes v1.17.4/go.mod h1:T2iWC2zSz7Nq5mQvvFPQB8mc2sEBIAdjMJPxavtZkcg=
-k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200312205431-8d8aa3959853 h1:hi2yNrx0L0HO7PxCqENlGKN+gYt5S7YG6U+1yHVsjaA=
-k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200312205431-8d8aa3959853/go.mod h1:D7sf1F7GAHEK5DOn11dKP5J/QJvUShU2AopXe/AXghk=
-k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200312205431-8d8aa3959853 h1:+Q7gT+EWDljlWTgS328l5d0mHKoqDB5fWI0xtmpAebU=
-k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200312205431-8d8aa3959853/go.mod h1:h8Z8vCtlVWoVWwGDsmirREYT5Ot3kRyY62zQ/L18DR4=
-k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200312205431-8d8aa3959853 h1:s5bqnOLu3kJgoGUiy6toA3Ll3qG0nzgsdKxEhMTp9Jo=
-k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200312205431-8d8aa3959853/go.mod h1:aSKJ8oirsor4diiLHY6P9ygjPwLQXdR3awaqBbnJX/c=
-k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20200312205431-8d8aa3959853/go.mod h1:cUQzw7Gvx7kALJkqJpFHwcEAzRmcjBQ7MKypzA8EZXQ=
-k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20200312205431-8d8aa3959853/go.mod h1:2j76SpnTiazjWbdidJY0tDtSLe6k0tY2v3vV9TLV6Cg=
-k8s.io/kubernetes/staging/src/k8s.io/cloud-provider v0.0.0-20200312205431-8d8aa3959853/go.mod h1:ycFddVBapK98UCwexOuDsvQLD3tfiVdRlXm2jjNP14c=
-k8s.io/kubernetes/staging/src/k8s.io/cluster-bootstrap v0.0.0-20200312205431-8d8aa3959853/go.mod h1:45LXh7SMJ3p4QnHER5TrOGhYz0CWcjb3xyq6aQqnebk=
-k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200312205431-8d8aa3959853 h1:qVonaTMnOYFOOfTFuaqE0Tt61CaVbv4TboGQ4SG0xpU=
-k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200312205431-8d8aa3959853/go.mod h1:k2c6AL2BX9sZq1YlrdnWj8UOSzlYNGXyompiqK2qt6k=
-k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20200312205431-8d8aa3959853/go.mod h1:cTBtMr/eUcihQ4sFhgiDPlmlFpn1xBY37yQ+zNcT1IE=
-k8s.io/kubernetes/staging/src/k8s.io/cri-api v0.0.0-20200312205431-8d8aa3959853/go.mod h1:MFtntPG6S7PSTqf06WHsjhdZ/gOTxfzFgNEzfhO5Uus=
-k8s.io/kubernetes/staging/src/k8s.io/csi-translation-lib v0.0.0-20200312205431-8d8aa3959853/go.mod h1:LO9jViQKnx/FqMjlXE9SmKjZ3I4PE/SLsaDWKTlmiRw=
-k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20200312205431-8d8aa3959853/go.mod h1:vh3GqAHrJU/N5vuEaKFmWYb78D7L6fiVBS4Id3OyDSc=
-k8s.io/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20200312205431-8d8aa3959853/go.mod h1:mKHow8zS1GpDcLHlAgghj2BKPOZrpEIHA30lWyRF3gg=
-k8s.io/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20200312205431-8d8aa3959853/go.mod h1:6SB+e2ragxZ8CPWrgNZ7F9KkCFykx5zQjnLVP2SWQ00=
-k8s.io/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20200312205431-8d8aa3959853/go.mod h1:8rHmgUeQCsvaVrSy79lQc3DKD5PQp9cJH0DLw2GSAdk=
-k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20200312205431-8d8aa3959853/go.mod h1:1f5qLii1ERhwADRUUxPM961i2GpaOJAMXHBcLAEhrdE=
-k8s.io/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20200312205431-8d8aa3959853/go.mod h1:oHq6KV/WjiamPxKs2Np7JxcOEwHXfMybRBnLrMsTOHs=
-k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers v0.0.0-20200312205431-8d8aa3959853/go.mod h1:/Ay7JUxnDftnjC20qd2wU13RQSAi6jsvdbx5P3g+Rig=
-k8s.io/kubernetes/staging/src/k8s.io/metrics v0.0.0-20200312205431-8d8aa3959853/go.mod h1:e70DTGI+y72YcU8iGDifilyP5uW6sPmBflDEjtiH/p0=
-k8s.io/kubernetes/staging/src/k8s.io/sample-apiserver v0.0.0-20200312205431-8d8aa3959853/go.mod h1:Ey49UYt1h2yQl0P61nfZEo/LZUu78Bj55oBL/VOejW4=
+k8s.io/kubernetes v1.17.5 h1:ZPZDhS4wRIPSKahglFHK5Q8wcUH/Q2QE5AbopkuLYyU=
+k8s.io/kubernetes v1.17.5/go.mod h1:4A7Bm7SHrj2SCb9vxMBil1wu2uJy3k1zSjoD43SS8Zg=
+k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200416113457-e0fccafd6954 h1:enXwuFIVLJ4k8smTeET8cWeJkHzwC5oHt738sFTKU90=
+k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200416113457-e0fccafd6954/go.mod h1:D7sf1F7GAHEK5DOn11dKP5J/QJvUShU2AopXe/AXghk=
+k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200416113457-e0fccafd6954 h1:Pas0hQzC+X5oB1mMz5Qp/HsMZtt6J+OwEscCMxbOtfk=
+k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200416113457-e0fccafd6954/go.mod h1:H/6nVXsff/HrqcWilsJCtaQ6dKWuiAnOp10JvixI3C8=
+k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200416113457-e0fccafd6954 h1:rsHf+LusioF57X34yPHe4LPM7RpCORCFFx1Nc1AjTJA=
+k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200416113457-e0fccafd6954/go.mod h1:SoRH+pFIxNh4L//CgOgEv2BPs4VSY5WAcL5We/B6rFo=
+k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20200416113457-e0fccafd6954/go.mod h1:AB1oL6w6fMcJgr7EuXagl03Esnj3kXrYXILCUqdKfNA=
+k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20200416113457-e0fccafd6954/go.mod h1:2j76SpnTiazjWbdidJY0tDtSLe6k0tY2v3vV9TLV6Cg=
+k8s.io/kubernetes/staging/src/k8s.io/cloud-provider v0.0.0-20200416113457-e0fccafd6954/go.mod h1:ycFddVBapK98UCwexOuDsvQLD3tfiVdRlXm2jjNP14c=
+k8s.io/kubernetes/staging/src/k8s.io/cluster-bootstrap v0.0.0-20200416113457-e0fccafd6954/go.mod h1:45LXh7SMJ3p4QnHER5TrOGhYz0CWcjb3xyq6aQqnebk=
+k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200416113457-e0fccafd6954 h1:fXjHJ/hzBRBSjzQV4lst0rI+YGxYzxXSxWdWBttvjHY=
+k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200416113457-e0fccafd6954/go.mod h1:objHARrhjZIrVVDB4/Qbj68QLFiVYFfdQZSrVZQeAsA=
+k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20200416113457-e0fccafd6954/go.mod h1:cTBtMr/eUcihQ4sFhgiDPlmlFpn1xBY37yQ+zNcT1IE=
+k8s.io/kubernetes/staging/src/k8s.io/cri-api v0.0.0-20200416113457-e0fccafd6954/go.mod h1:MFtntPG6S7PSTqf06WHsjhdZ/gOTxfzFgNEzfhO5Uus=
+k8s.io/kubernetes/staging/src/k8s.io/csi-translation-lib v0.0.0-20200416113457-e0fccafd6954/go.mod h1:LO9jViQKnx/FqMjlXE9SmKjZ3I4PE/SLsaDWKTlmiRw=
+k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator v0.0.0-20200416113457-e0fccafd6954/go.mod h1:ylF0j/snuq5bAczgFWOG2CyRDvjp9wBn4c28tixYO0E=
+k8s.io/kubernetes/staging/src/k8s.io/kube-controller-manager v0.0.0-20200416113457-e0fccafd6954/go.mod h1:mKHow8zS1GpDcLHlAgghj2BKPOZrpEIHA30lWyRF3gg=
+k8s.io/kubernetes/staging/src/k8s.io/kube-proxy v0.0.0-20200416113457-e0fccafd6954/go.mod h1:6SB+e2ragxZ8CPWrgNZ7F9KkCFykx5zQjnLVP2SWQ00=
+k8s.io/kubernetes/staging/src/k8s.io/kube-scheduler v0.0.0-20200416113457-e0fccafd6954/go.mod h1:8rHmgUeQCsvaVrSy79lQc3DKD5PQp9cJH0DLw2GSAdk=
+k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20200416113457-e0fccafd6954/go.mod h1:eROQGvP85y3JBs9crYvkseuCIzARI+XY/nRjdcPhrdQ=
+k8s.io/kubernetes/staging/src/k8s.io/kubelet v0.0.0-20200416113457-e0fccafd6954/go.mod h1:oHq6KV/WjiamPxKs2Np7JxcOEwHXfMybRBnLrMsTOHs=
+k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers v0.0.0-20200416113457-e0fccafd6954/go.mod h1:/Ay7JUxnDftnjC20qd2wU13RQSAi6jsvdbx5P3g+Rig=
+k8s.io/kubernetes/staging/src/k8s.io/metrics v0.0.0-20200416113457-e0fccafd6954/go.mod h1:e70DTGI+y72YcU8iGDifilyP5uW6sPmBflDEjtiH/p0=
+k8s.io/kubernetes/staging/src/k8s.io/sample-apiserver v0.0.0-20200416113457-e0fccafd6954/go.mod h1:GRqxyhYThPRj3I4Yju4R+yDjeOH01cuZX+jslbUiJp0=
 k8s.io/repo-infra v0.0.1-alpha.1/go.mod h1:wO1t9WaB99V80ljbeENTnayuEEwNZt7gECYh/CEyOJ8=
 k8s.io/system-validators v1.0.4/go.mod h1:HgSgTg4NAGNoYYjKsUyk52gdNi2PVDswQ9Iyn66R7NI=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
@@ -681,6 +681,7 @@ rsc.io/goversion v1.0.0 h1:/IhXBiai89TyuerPquiZZ39IQkTfAUbZB2awsyYZ/2c=
 rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
 sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
+sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/pkg/endpoint/api.go b/pkg/endpoint/api.go
index d3dd348edb..9acf7d9d75 100644
--- a/pkg/endpoint/api.go
+++ b/pkg/endpoint/api.go
@@ -556,6 +556,12 @@ func (e *Endpoint) ProcessChangeRequest(newEp *Endpoint, validPatchTransitionSta
 			reason = "Waiting on endpoint regeneration because identity is known while handling API PATCH"
 		case StateWaitingForIdentity:
 			reason = "Waiting on endpoint initial program regeneration while handling API PATCH"
+		default:
+			// Caller skips regeneration if reason == "". Bump the skipped regeneration level so that next
+			// regeneration will realise endpoint changes.
+			if e.skippedRegenerationLevel < regeneration.RegenerateWithDatapathRewrite {
+				e.skippedRegenerationLevel = regeneration.RegenerateWithDatapathRewrite
+			}
 		}
 	}
 
diff --git a/pkg/endpoint/bpf.go b/pkg/endpoint/bpf.go
index 9030775589..2bd3fb0c95 100644
--- a/pkg/endpoint/bpf.go
+++ b/pkg/endpoint/bpf.go
@@ -1069,6 +1069,7 @@ func (e *Endpoint) applyPolicyMapChanges() (proxyChanges bool, err error) {
 	//  from the desired policy here.
 	adds, deletes := e.desiredPolicy.ConsumeMapChanges()
 
+	// Add policy map entries before deleting to avoid transient drops
 	for keyToAdd, entry := range adds {
 		// Keep the existing proxy port, if any
 		if entry != policy.NoRedirectEntry {
@@ -1108,6 +1109,12 @@ func (e *Endpoint) syncPolicyMap() error {
 	if e.realizedPolicy != e.desiredPolicy {
 		errors := 0
 
+		// Add policy map entries before deleting to avoid transient drops
+		err := e.addPolicyMapDelta()
+		if err != nil {
+			errors++
+		}
+
 		// Delete policy keys present in the realized state, but not present in the desired state
 		for keyToDelete := range e.realizedPolicy.PolicyMapState {
 			// If key that is in realized state is not in desired state, just remove it.
@@ -1118,11 +1125,6 @@ func (e *Endpoint) syncPolicyMap() error {
 			}
 		}
 
-		err := e.addPolicyMapDelta()
-		if err != nil {
-			errors++
-		}
-
 		if errors > 0 {
 			return fmt.Errorf("syncPolicyMapDelta failed")
 		}
diff --git a/pkg/endpoint/endpoint.go b/pkg/endpoint/endpoint.go
index 45120a4b2a..e331e95ec4 100644
--- a/pkg/endpoint/endpoint.go
+++ b/pkg/endpoint/endpoint.go
@@ -282,6 +282,12 @@ type Endpoint struct {
 
 	eventQueue *eventqueue.EventQueue
 
+	// skippedRegenerationLevel is the DatapathRegenerationLevel of the regeneration event that
+	// was skipped due to another regeneration event already being queued, as indicated by
+	// state. A lower-level current regeneration is bumped to this level to cover for the
+	// skipped regeneration levels.
+	skippedRegenerationLevel regeneration.DatapathRegenerationLevel
+
 	// DatapathConfiguration is the endpoint's datapath configuration as
 	// passed in via the plugin that created the endpoint, e.g. the CNI
 	// plugin which performed the plumbing will enable certain datapath
@@ -836,7 +842,8 @@ func (e *Endpoint) Update(cfg *models.EndpointConfigurationSpec) error {
 	// Note: This "retry" behaviour is better suited to a controller, and can be
 	// moved there once we have an endpoint regeneration controller.
 	regenCtx := &regeneration.ExternalRegenerationMetadata{
-		Reason: "endpoint was updated via API",
+		Reason:            "endpoint was updated via API",
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
 	}
 
 	// If configuration options are provided, we only regenerate if necessary.
@@ -868,19 +875,14 @@ func (e *Endpoint) Update(cfg *models.EndpointConfigurationSpec) error {
 		for {
 			select {
 			case <-ticker.C:
-				if err := e.lockAlive(); err != nil {
+				regen, err := e.SetRegenerateStateIfAlive(regenCtx)
+				if err != nil {
 					return err
 				}
-				// Check endpoint state before attempting configuration update because
-				// configuration updates can only be applied when the endpoint is in
-				// specific states. See GH-3058.
-				stateTransitionSucceeded := e.setState(StateWaitingToRegenerate, regenCtx.Reason)
-				if stateTransitionSucceeded {
-					e.unlock()
+				if regen {
 					e.Regenerate(regenCtx)
 					return nil
 				}
-				e.unlock()
 			case <-timeout:
 				e.getLogger().Warning("timed out waiting for endpoint state to change")
 				return UpdateStateChangeError{fmt.Sprintf("unable to regenerate endpoint program because state transition to %s was unsuccessful; check `cilium endpoint log %d` for more information", StateWaitingToRegenerate, e.ID)}
@@ -1025,16 +1027,6 @@ func (e *Endpoint) leaveLocked(proxyWaitGroup *completion.WaitGroup, conf Delete
 	return errors
 }
 
-// RegenerateWait should only be called when endpoint's state has successfully
-// been changed to "waiting-to-regenerate"
-func (e *Endpoint) RegenerateWait(reason string) error {
-	if !<-e.Regenerate(&regeneration.ExternalRegenerationMetadata{Reason: reason}) {
-		return fmt.Errorf("error while regenerating endpoint."+
-			" For more info run: 'cilium endpoint get %d'", e.ID)
-	}
-	return nil
-}
-
 // GetContainerName returns the name of the container for the endpoint.
 func (e *Endpoint) GetContainerName() string {
 	e.unconditionalRLock()
@@ -1587,9 +1579,11 @@ func (e *Endpoint) IsInit() bool {
 // container runtime layer will periodically synchronize labels.
 //
 // If a net label changed was performed, the endpoint will receive a new
-// identity and will be regenerated. Both of these operations will happen in
-// the background.
-func (e *Endpoint) UpdateLabels(ctx context.Context, identityLabels, infoLabels pkgLabels.Labels, blocking bool) {
+// security identity and will be regenerated. Both of these operations will
+// run first synchronously if 'blocking' is true, and then in the background.
+//
+// Returns 'true' if endpoint regeneration was triggered.
+func (e *Endpoint) UpdateLabels(ctx context.Context, identityLabels, infoLabels pkgLabels.Labels, blocking bool) (regenTriggered bool) {
 	log.WithFields(logrus.Fields{
 		logfields.ContainerID:    e.GetShortContainerID(),
 		logfields.EndpointID:     e.StringID(),
@@ -1599,7 +1593,7 @@ func (e *Endpoint) UpdateLabels(ctx context.Context, identityLabels, infoLabels
 
 	if err := e.lockAlive(); err != nil {
 		e.logDisconnectedMutexAction(err, "when trying to refresh endpoint labels")
-		return
+		return false
 	}
 
 	e.replaceInformationLabels(infoLabels)
@@ -1607,8 +1601,10 @@ func (e *Endpoint) UpdateLabels(ctx context.Context, identityLabels, infoLabels
 	rev := e.replaceIdentityLabels(identityLabels)
 	e.unlock()
 	if rev != 0 {
-		e.runIdentityResolver(ctx, rev, blocking)
+		return e.runIdentityResolver(ctx, rev, blocking)
 	}
+
+	return false
 }
 
 func (e *Endpoint) identityResolutionIsObsolete(myChangeRev int) bool {
@@ -1625,13 +1621,14 @@ func (e *Endpoint) identityResolutionIsObsolete(myChangeRev int) bool {
 // are currently configured on the endpoint.
 //
 // Must be called with e.Mutex NOT held.
-func (e *Endpoint) runIdentityResolver(ctx context.Context, myChangeRev int, blocking bool) {
-	if err := e.rlockAlive(); err != nil {
+func (e *Endpoint) runIdentityResolver(ctx context.Context, myChangeRev int, blocking bool) (regenTriggered bool) {
+	err := e.rlockAlive()
+	if err != nil {
 		// If a labels update and an endpoint delete API request arrive
 		// in quick succession, this could occur; in that case, there's
 		// no point updating the controller.
 		e.getLogger().WithError(err).Info("Cannot run labels resolver")
-		return
+		return false
 	}
 	newLabels := e.OpLabels.IdentityLabels()
 	e.runlock()
@@ -1640,14 +1637,14 @@ func (e *Endpoint) runIdentityResolver(ctx context.Context, myChangeRev int, blo
 	// If we are certain we can resolve the identity without accessing the KV
 	// store, do it first synchronously right now. This can reduce the number
 	// of regenerations for the endpoint during its initialization.
+	regenTriggered = false
 	if blocking || identity.IdentityAllocationIsLocal(newLabels) {
 		scopedLog.Info("Resolving identity labels (blocking)")
-
-		err := e.identityLabelsChanged(ctx, myChangeRev)
+		regenTriggered, err = e.identityLabelsChanged(ctx, myChangeRev)
 		switch err {
 		case ErrNotAlive:
 			scopedLog.Debug("not changing endpoint identity because endpoint is in process of being removed")
-			return
+			return false
 		default:
 			if err != nil {
 				scopedLog.WithError(err).Warn("Error changing endpoint identity")
@@ -1661,7 +1658,7 @@ func (e *Endpoint) runIdentityResolver(ctx context.Context, myChangeRev int, blo
 	e.controllers.UpdateController(ctrlName,
 		controller.ControllerParams{
 			DoFunc: func(ctx context.Context) error {
-				err := e.identityLabelsChanged(ctx, myChangeRev)
+				_, err := e.identityLabelsChanged(ctx, myChangeRev)
 				switch err {
 				case ErrNotAlive:
 					e.getLogger().Debug("not changing endpoint identity because endpoint is in process of being removed")
@@ -1674,11 +1671,14 @@ func (e *Endpoint) runIdentityResolver(ctx context.Context, myChangeRev int, blo
 			Context:     e.aliveCtx,
 		},
 	)
+
+	return regenTriggered
 }
 
-func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) error {
-	if err := e.rlockAlive(); err != nil {
-		return ErrNotAlive
+func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) (regenTriggered bool, err error) {
+	// e.setState() called below, can't take a read lock.
+	if err := e.lockAlive(); err != nil {
+		return false, ErrNotAlive
 	}
 	newLabels := e.OpLabels.IdentityLabels()
 	elog := e.getLogger().WithFields(logrus.Fields{
@@ -1688,9 +1688,9 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 
 	// Since we unlocked the endpoint and re-locked, the label update may already be obsolete
 	if e.identityResolutionIsObsolete(myChangeRev) {
-		e.runlock()
+		e.unlock()
 		elog.Debug("Endpoint identity has changed, aborting resolution routine in favour of new one")
-		return nil
+		return false, nil
 	}
 
 	if e.SecurityIdentity != nil && e.SecurityIdentity.Labels.Equals(newLabels) {
@@ -1698,13 +1698,13 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 		if e.getState() == StateWaitingForIdentity {
 			e.setState(StateReady, "Set identity for this endpoint")
 		}
-		e.runlock()
+		e.unlock()
 		elog.Debug("Endpoint labels unchanged, skipping resolution of identity")
-		return nil
+		return false, nil
 	}
 
 	// Unlock the endpoint mutex for the possibly long lasting kvstore operation
-	e.runlock()
+	e.unlock()
 	elog.Debug("Resolving identity for labels")
 
 	allocateCtx, cancel := context.WithTimeout(context.Background(), option.Config.KVstoreConnectivityTimeout)
@@ -1714,7 +1714,7 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 	if err != nil {
 		err = fmt.Errorf("unable to resolve identity: %s", err)
 		e.LogStatus(Other, Warning, fmt.Sprintf("%s (will retry)", err.Error()))
-		return err
+		return false, err
 	}
 
 	// When releasing identities after allocation due to either failure of
@@ -1736,7 +1736,7 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 
 	if err := e.lockAlive(); err != nil {
 		releaseNewlyAllocatedIdentity()
-		return err
+		return false, err
 	}
 
 	// Since we unlocked the endpoint and re-locked, the label update may already be obsolete
@@ -1745,7 +1745,7 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 
 		releaseNewlyAllocatedIdentity()
 
-		return nil
+		return false, nil
 	}
 
 	// If endpoint has an old identity, defer release of it to the end of
@@ -1768,14 +1768,14 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 
 			if err := e.lockAlive(); err != nil {
 				releaseNewlyAllocatedIdentity()
-				return err
+				return false, err
 			}
 
 			// Since we unlocked the endpoint and re-locked, the label update may already be obsolete
 			if e.identityResolutionIsObsolete(myChangeRev) {
 				e.unlock()
 				releaseNewlyAllocatedIdentity()
-				return nil
+				return false, nil
 			}
 		}
 	}
@@ -1794,6 +1794,10 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 	}
 
 	readyToRegenerate := false
+	regenMetadata := &regeneration.ExternalRegenerationMetadata{
+		Reason:            "updated security labels",
+		RegenerationLevel: regeneration.RegenerateWithDatapathRewrite,
+	}
 
 	// Regeneration is only triggered once the endpoint ID has been
 	// assigned. This ensures that on the initial creation, the endpoint is
@@ -1804,7 +1808,7 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 	// called, the controller calling identityLabelsChanged() will trigger
 	// the regeneration as soon as the identity is known.
 	if e.ID != 0 {
-		readyToRegenerate = e.setState(StateWaitingToRegenerate, "Triggering regeneration due to new identity")
+		readyToRegenerate = e.setRegenerateStateLocked(regenMetadata)
 	}
 
 	// Unconditionally force policy recomputation after a new identity has been
@@ -1814,10 +1818,10 @@ func (e *Endpoint) identityLabelsChanged(ctx context.Context, myChangeRev int) e
 	e.unlock()
 
 	if readyToRegenerate {
-		e.Regenerate(&regeneration.ExternalRegenerationMetadata{Reason: "updated security labels"})
+		e.Regenerate(regenMetadata)
 	}
 
-	return nil
+	return readyToRegenerate, nil
 }
 
 // SetPolicyRevision sets the endpoint's policy revision with the given
@@ -2127,56 +2131,11 @@ func (e *Endpoint) GetProxyInfoByFields() (uint64, string, string, []string, str
 	return e.GetID(), e.GetIPv4Address(), e.GetIPv6Address(), e.GetLabels(), e.GetLabelsSHA(), uint64(e.GetIdentity()), err
 }
 
-// RegenerateAfterCreation handles the first regeneration of an endpoint after
-// it is created.
-// After a call to `Regenerate` on the endpoint is made, `endpointStartFunc`
-// is invoked - this can be used as a callback to expose the endpoint to other
-// subsystems if needed.
-// If syncBuild is true, this function waits for specific conditions until
-// returning:
+// WaitForFirstRegeneration waits for specific conditions before returning:
 // * if the endpoint has a sidecar proxy, it waits for the endpoint's BPF
 // program to be generated for the first time.
 // * otherwise, waits for the endpoint to complete its first full regeneration.
-func (e *Endpoint) RegenerateAfterCreation(ctx context.Context, syncBuild bool) error {
-	if err := e.lockAlive(); err != nil {
-		return fmt.Errorf("endpoint was deleted while processing the request")
-	}
-
-	build := e.getState() == StateReady
-	if build {
-		e.setState(StateWaitingToRegenerate, "Identity is known at endpoint creation time")
-	}
-	e.unlock()
-
-	if build {
-		// Do not synchronously regenerate the endpoint when first creating it.
-		// We have custom logic later for waiting for specific checkpoints to be
-		// reached upon regeneration later (checking for when BPF programs have
-		// been compiled), as opposed to waiting for the entire regeneration to
-		// be complete (including proxies being configured). This is done to
-		// avoid a chicken-and-egg problem with L7 policies are imported which
-		// select the endpoint being generated, as when such policies are
-		// imported, regeneration blocks on waiting for proxies to be
-		// configured. When Cilium is used with Istio, though, the proxy is
-		// started as a sidecar, and is not launched yet when this specific code
-		// is executed; if we waited for regeneration to be complete, including
-		// proxy configuration, this code would effectively deadlock addition
-		// of endpoints.
-		e.Regenerate(&regeneration.ExternalRegenerationMetadata{
-			Reason:        "Initial build on endpoint creation",
-			ParentContext: ctx,
-		})
-	}
-
-	// Wait for endpoint to be in "ready" state if specified in API call.
-	if !syncBuild {
-		return nil
-	}
-
-	return e.waitForFirstRegeneration(ctx)
-}
-
-func (e *Endpoint) waitForFirstRegeneration(ctx context.Context) error {
+func (e *Endpoint) WaitForFirstRegeneration(ctx context.Context) error {
 	e.getLogger().Info("Waiting for endpoint to be generated")
 
 	// Default timeout for PUT /endpoint/{id} is 60 seconds, so put timeout
diff --git a/pkg/endpoint/manager.go b/pkg/endpoint/manager.go
index eadeb1517c..1b2ec60ce6 100644
--- a/pkg/endpoint/manager.go
+++ b/pkg/endpoint/manager.go
@@ -36,8 +36,14 @@ type endpointManager interface {
 // Expose exposes the endpoint to the endpointmanager. After this function
 // is called, the endpoint may be accessed by any lookup in the endpointmanager.
 func (e *Endpoint) Expose(mgr endpointManager) error {
+	// No need to check liveness as an endpoint can only be deleted via the
+	// API after it has been inserted into the manager.
+	// 'e.ID' written below, read lock is not enough.
+	e.unconditionalLock()
+
 	newID, err := mgr.AllocateID(e.ID)
 	if err != nil {
+		e.unlock()
 		return err
 	}
 	defer close(e.exposed)
@@ -57,12 +63,10 @@ func (e *Endpoint) Expose(mgr endpointManager) error {
 
 	// No need to check liveness as an endpoint can only be deleted via the
 	// API after it has been inserted into the manager.
-	e.unconditionalRLock()
 	mgr.UpdateIDReference(e)
 	e.updateReferences(mgr)
-	e.runlock()
-
 	e.getLogger().Info("New endpoint")
+	e.unlock()
 
 	mgr.RunK8sCiliumEndpointSync(e, option.Config)
 	return nil
diff --git a/pkg/endpoint/policy.go b/pkg/endpoint/policy.go
index d7c08e5855..94570c242e 100644
--- a/pkg/endpoint/policy.go
+++ b/pkg/endpoint/policy.go
@@ -287,6 +287,14 @@ func (e *Endpoint) regenerate(context *regenerationContext) (retErr error) {
 		return fmt.Errorf("Skipping build due to invalid state: %s", e.state)
 	}
 
+	// Bump priority if higher priority event was skipped.
+	// This must be done in the same critical section as the state transition above.
+	if e.skippedRegenerationLevel > context.datapathRegenerationContext.regenerationLevel {
+		context.datapathRegenerationContext.regenerationLevel = e.skippedRegenerationLevel
+	}
+	// reset to the default lowest level
+	e.skippedRegenerationLevel = regeneration.Invalid
+
 	e.unlock()
 
 	stats.prepareBuild.Start()
@@ -427,6 +435,43 @@ func (e *Endpoint) updateRegenerationStatistics(context *regenerationContext, er
 	e.LogStatusOK(BPF, "Successfully regenerated endpoint program (Reason: "+context.Reason+")")
 }
 
+// SetRegenerateStateIfAlive tries to change the state of the endpoint for pending regeneration.
+// Returns 'true' if 'e.Regenerate()' should be called after releasing the endpoint lock.
+// Return 'false' if returned error is non-nil.
+func (e *Endpoint) SetRegenerateStateIfAlive(regenMetadata *regeneration.ExternalRegenerationMetadata) (bool, error) {
+	regen := false
+	err := e.lockAlive()
+	if err != nil {
+		e.LogStatus(Policy, Failure, "Error while handling policy updates for endpoint: "+err.Error())
+	} else {
+		regen = e.setRegenerateStateLocked(regenMetadata)
+		e.unlock()
+	}
+	return regen, err
+}
+
+// setRegenerateStateLocked tries to change the state of the endpoint for pending regeneration.
+// returns 'true' if 'e.Regenerate()' should be called after releasing the endpoint lock.
+func (e *Endpoint) setRegenerateStateLocked(regenMetadata *regeneration.ExternalRegenerationMetadata) bool {
+	var regen bool
+	state := e.getState()
+	switch state {
+	case StateRestoring, StateWaitingToRegenerate:
+		// Bump the skipped regeneration level if needed so that the existing/queued
+		// regeneration can regenerate on the required level.
+		if regenMetadata.RegenerationLevel > e.skippedRegenerationLevel {
+			e.skippedRegenerationLevel = regenMetadata.RegenerationLevel
+			e.logStatusLocked(Other, OK, fmt.Sprintf("Skipped duplicate endpoint regeneration level %s trigger due to %s", regenMetadata.RegenerationLevel.String(), regenMetadata.Reason))
+		} else {
+			e.logStatusLocked(Other, OK, fmt.Sprintf("Skipped duplicate endpoint regeneration trigger due to %s", regenMetadata.Reason))
+		}
+		regen = false
+	default:
+		regen = e.setState(StateWaitingToRegenerate, fmt.Sprintf("Triggering endpoint regeneration due to %s", regenMetadata.Reason))
+	}
+	return regen
+}
+
 // RegenerateIfAlive queue a regeneration of this endpoint into the build queue
 // of the endpoint and returns a channel that is closed when the regeneration of
 // the endpoint is complete. The channel returns:
@@ -434,24 +479,13 @@ func (e *Endpoint) updateRegenerationStatistics(context *regenerationContext, er
 //  - true if the regeneration succeed
 //  - nothing and the channel is closed if the regeneration did not happen
 func (e *Endpoint) RegenerateIfAlive(regenMetadata *regeneration.ExternalRegenerationMetadata) <-chan bool {
-	if err := e.lockAlive(); err != nil {
+	regen, err := e.SetRegenerateStateIfAlive(regenMetadata)
+	if err != nil {
 		log.WithError(err).Warnf("Endpoint disappeared while queued to be regenerated: %s", regenMetadata.Reason)
-		e.LogStatus(Policy, Failure, "Error while handling policy updates for endpoint: "+err.Error())
-	} else {
-		var regen bool
-		state := e.getState()
-		switch state {
-		case StateRestoring, StateWaitingToRegenerate:
-			e.setState(state, fmt.Sprintf("Skipped duplicate endpoint regeneration trigger due to %s", regenMetadata.Reason))
-			regen = false
-		default:
-			regen = e.setState(StateWaitingToRegenerate, fmt.Sprintf("Triggering endpoint regeneration due to %s", regenMetadata.Reason))
-		}
-		e.unlock()
-		if regen {
-			// Regenerate logs status according to the build success/failure
-			return e.Regenerate(regenMetadata)
-		}
+	}
+	if regen {
+		// Regenerate logs status according to the build success/failure
+		return e.Regenerate(regenMetadata)
 	}
 
 	ch := make(chan bool)
@@ -560,20 +594,7 @@ func (e *Endpoint) startRegenerationFailureHandler() {
 				return nil
 			}
 
-			if err := e.lockAlive(); err != nil {
-				// We don't need to regenerate because the endpoint is d
-				// disconnecting / is disconnected, exit gracefully.
-				return nil
-			}
-
-			stateTransitionSucceeded := e.setState(StateWaitingToRegenerate, reasonRegenRetry)
-			e.unlock()
-			if !stateTransitionSucceeded {
-				// Another regeneration has already been enqueued.
-				return nil
-			}
-
-			r := &regeneration.ExternalRegenerationMetadata{
+			regenMetadata := &regeneration.ExternalRegenerationMetadata{
 				// TODO (ianvernon) - is there a way we can plumb a parent
 				// context to a controller (e.g., endpoint.aliveCtx)?
 				ParentContext: ctx,
@@ -582,7 +603,15 @@ func (e *Endpoint) startRegenerationFailureHandler() {
 				// of the failure, simply that something failed.
 				RegenerationLevel: regeneration.RegenerateWithDatapathRewrite,
 			}
-			if success := <-e.Regenerate(r); success {
+			regen, _ := e.SetRegenerateStateIfAlive(regenMetadata)
+			if !regen {
+				// We don't need to regenerate because the endpoint is d
+				// disconnecting / is disconnected, or another regeneration has
+				// already been enqueued. Exit gracefully.
+				return nil
+			}
+
+			if success := <-e.Regenerate(regenMetadata); success {
 				return nil
 			}
 			return fmt.Errorf("regeneration recovery failed")
diff --git a/pkg/endpoint/regeneration/regeneration_context.go b/pkg/endpoint/regeneration/regeneration_context.go
index 9ab2f7ea7c..9a1c04f66f 100644
--- a/pkg/endpoint/regeneration/regeneration_context.go
+++ b/pkg/endpoint/regeneration/regeneration_context.go
@@ -23,9 +23,12 @@ import (
 type DatapathRegenerationLevel int
 
 const (
+	// Invalid is the default level to enforce explicit setting of
+	// the regeneration level.
+	Invalid DatapathRegenerationLevel = iota
 	// RegenerateWithoutDatapath indicates that datapath rebuild or reload
 	// is not required to implement this regeneration.
-	RegenerateWithoutDatapath DatapathRegenerationLevel = iota
+	RegenerateWithoutDatapath
 	// RegenerateWithDatapathLoad indicates that the datapath must be
 	// reloaded but not recompiled to implement this regeneration.
 	RegenerateWithDatapathLoad
@@ -40,6 +43,8 @@ const (
 // String converts a DatapathRegenerationLevel into a human-readable string.
 func (r DatapathRegenerationLevel) String() string {
 	switch r {
+	case Invalid:
+		return "invalid"
 	case RegenerateWithoutDatapath:
 		return "no-rebuild"
 	case RegenerateWithDatapathLoad:
diff --git a/pkg/endpoint/regenerationcontext.go b/pkg/endpoint/regenerationcontext.go
index c0d08c9aad..27928127d7 100644
--- a/pkg/endpoint/regenerationcontext.go
+++ b/pkg/endpoint/regenerationcontext.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/cilium/cilium/pkg/completion"
 	"github.com/cilium/cilium/pkg/endpoint/regeneration"
+	"github.com/cilium/cilium/pkg/logging/logfields"
 	"github.com/cilium/cilium/pkg/revert"
 )
 
@@ -46,6 +47,10 @@ type regenerationContext struct {
 }
 
 func ParseExternalRegenerationMetadata(ctx context.Context, c context.CancelFunc, e *regeneration.ExternalRegenerationMetadata) *regenerationContext {
+	if e.RegenerationLevel == regeneration.Invalid {
+		log.WithField(logfields.Reason, e.Reason).Errorf("Uninitialized regeneration level")
+	}
+
 	return &regenerationContext{
 		Reason: e.Reason,
 		datapathRegenerationContext: &datapathRegenerationContext{
diff --git a/pkg/endpoint/restore.go b/pkg/endpoint/restore.go
index 6342ad0fa4..559977d401 100644
--- a/pkg/endpoint/restore.go
+++ b/pkg/endpoint/restore.go
@@ -165,7 +165,8 @@ func (e *Endpoint) RegenerateAfterRestore() error {
 	scopedLog := log.WithField(logfields.EndpointID, e.ID)
 
 	regenerationMetadata := &regeneration.ExternalRegenerationMetadata{
-		Reason: "syncing state to host",
+		Reason:            "syncing state to host",
+		RegenerationLevel: regeneration.RegenerateWithDatapathRewrite,
 	}
 	if buildSuccess := <-e.Regenerate(regenerationMetadata); !buildSuccess {
 		scopedLog.Warn("Failed while regenerating endpoint")
diff --git a/pkg/k8s/watchers/pod.go b/pkg/k8s/watchers/pod.go
index 8cb489a4b9..830331ca87 100644
--- a/pkg/k8s/watchers/pod.go
+++ b/pkg/k8s/watchers/pod.go
@@ -240,15 +240,16 @@ func (k *K8sWatcher) updateK8sPodV1(oldK8sPod, newK8sPod *types.Pod) error {
 }
 
 func realizePodAnnotationUpdate(podEP *endpoint.Endpoint) {
+	regenMetadata := &regeneration.ExternalRegenerationMetadata{
+		Reason:            "annotations updated",
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
+	}
 	// No need to log an error if the state transition didn't succeed,
 	// if it didn't succeed that means the endpoint is being deleted, or
 	// another regeneration has already been queued up for this endpoint.
-	stateTransitionSucceeded := podEP.SetState(endpoint.StateWaitingToRegenerate, "annotations updated")
-	if stateTransitionSucceeded {
-		podEP.Regenerate(&regeneration.ExternalRegenerationMetadata{
-			Reason:            "annotations updated",
-			RegenerationLevel: regeneration.RegenerateWithoutDatapath,
-		})
+	regen, _ := podEP.SetRegenerateStateIfAlive(regenMetadata)
+	if regen {
+		podEP.Regenerate(regenMetadata)
 	}
 }
 
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 e3f223a42b..f71e0570e8 100644
--- a/pkg/policy/l4.go
+++ b/pkg/policy/l4.go
@@ -565,7 +565,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)
@@ -575,11 +575,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 4c6478668e..e26eb96be7 100644
--- a/pkg/policy/mapstate.go
+++ b/pkg/policy/mapstate.go
@@ -30,6 +30,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(),
+	}
 )
 
 // MapState is a state of a policy map.
@@ -101,6 +106,9 @@ func (keys MapState) RedirectPreferredInsert(key Key, entry MapStateEntry) {
 func (keys MapState) DetermineAllowLocalhostIngress(l4Policy *L4Policy) {
 	if option.Config.AlwaysAllowLocalhost() {
 		keys[localHostKey] = MapStateEntry{}
+		if !option.Config.EnableRemoteNodeIdentity {
+			keys[localRemoteNodeKey] = MapStateEntry{}
+		}
 	}
 }
 
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/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD b/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD
new file mode 100644
index 0000000000..eaf6912f64
--- /dev/null
+++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/BUILD
@@ -0,0 +1,61 @@
+package(default_visibility = ["//visibility:public"])
+
+load(
+    "@io_bazel_rules_go//go:def.bzl",
+    "go_library",
+    "go_test",
+)
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "formats.go",
+        "validation.go",
+    ],
+    importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
+    importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/validation",
+    deps = [
+        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
+        "//vendor/github.com/go-openapi/errors:go_default_library",
+        "//vendor/github.com/go-openapi/spec:go_default_library",
+        "//vendor/github.com/go-openapi/strfmt:go_default_library",
+        "//vendor/github.com/go-openapi/validate:go_default_library",
+    ],
+)
+
+filegroup(
+    name = "package-srcs",
+    srcs = glob(["**"]),
+    tags = ["automanaged"],
+    visibility = ["//visibility:private"],
+)
+
+filegroup(
+    name = "all-srcs",
+    srcs = [":package-srcs"],
+    tags = ["automanaged"],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+        "formats_test.go",
+        "validation_test.go",
+    ],
+    embed = [":go_default_library"],
+    deps = [
+        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
+        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library",
+        "//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
+        "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
+        "//vendor/github.com/go-openapi/spec:go_default_library",
+        "//vendor/github.com/go-openapi/strfmt:go_default_library",
+    ],
+)
diff --git a/vendor/k8s.io/apimachinery/pkg/util/net/http.go b/vendor/k8s.io/apimachinery/pkg/util/net/http.go
index f9540c63bb..bd79d6c4a0 100644
--- a/vendor/k8s.io/apimachinery/pkg/util/net/http.go
+++ b/vendor/k8s.io/apimachinery/pkg/util/net/http.go
@@ -55,6 +55,12 @@ func JoinPreservingTrailingSlash(elem ...string) string {
 	return result
 }
 
+// IsTimeout returns true if the given error is a network timeout error
+func IsTimeout(err error) bool {
+	neterr, ok := err.(net.Error)
+	return ok && neterr != nil && neterr.Timeout()
+}
+
 // IsProbableEOF returns true if the given error resembles a connection termination
 // scenario that would justify assuming that the watch is empty.
 // These errors are what the Go http stack returns back to us which are general
diff --git a/vendor/k8s.io/apimachinery/pkg/watch/streamwatcher.go b/vendor/k8s.io/apimachinery/pkg/watch/streamwatcher.go
index 8af256eb12..4269a836a8 100644
--- a/vendor/k8s.io/apimachinery/pkg/watch/streamwatcher.go
+++ b/vendor/k8s.io/apimachinery/pkg/watch/streamwatcher.go
@@ -113,7 +113,7 @@ func (sw *StreamWatcher) receive() {
 			case io.ErrUnexpectedEOF:
 				klog.V(1).Infof("Unexpected EOF during watch stream event decoding: %v", err)
 			default:
-				if net.IsProbableEOF(err) {
+				if net.IsProbableEOF(err) || net.IsTimeout(err) {
 					klog.V(5).Infof("Unable to decode an event from the watch stream: %v", err)
 				} else {
 					sw.result <- Event{
diff --git a/vendor/k8s.io/client-go/rest/request.go b/vendor/k8s.io/client-go/rest/request.go
index 863b356fe8..411a2dcd03 100644
--- a/vendor/k8s.io/client-go/rest/request.go
+++ b/vendor/k8s.io/client-go/rest/request.go
@@ -618,7 +618,7 @@ func (r *Request) Watch() (watch.Interface, error) {
 	if err != nil {
 		// The watch stream mechanism handles many common partial data errors, so closed
 		// connections can be retried in many cases.
-		if net.IsProbableEOF(err) {
+		if net.IsProbableEOF(err) || net.IsTimeout(err) {
 			return watch.NewEmptyWatch(), nil
 		}
 		return nil, err
diff --git a/vendor/k8s.io/client-go/tools/cache/reflector.go b/vendor/k8s.io/client-go/tools/cache/reflector.go
index 62749ed7d3..aa04b21a0d 100644
--- a/vendor/k8s.io/client-go/tools/cache/reflector.go
+++ b/vendor/k8s.io/client-go/tools/cache/reflector.go
@@ -296,6 +296,8 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
 			AllowWatchBookmarks: true,
 		}
 
+		// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent
+		start := r.clock.Now()
 		w, err := r.listerWatcher.Watch(options)
 		if err != nil {
 			switch err {
@@ -317,7 +319,7 @@ func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
 			return nil
 		}
 
-		if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
+		if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
 			if err != errorStopRequested {
 				switch {
 				case apierrs.IsResourceExpired(err):
@@ -341,8 +343,7 @@ func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) err
 }
 
 // watchHandler watches w and keeps *resourceVersion up to date.
-func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
-	start := r.clock.Now()
+func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
 	eventCount := 0
 
 	// Stopping the watcher should be idempotent and if we return from this function there's no way
diff --git a/vendor/k8s.io/code-generator/go.mod b/vendor/k8s.io/code-generator/go.mod
index 2436804e8f..c0c7e54d2b 100644
--- a/vendor/k8s.io/code-generator/go.mod
+++ b/vendor/k8s.io/code-generator/go.mod
@@ -11,7 +11,6 @@ require (
 	github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d
 	github.com/json-iterator/go v1.1.8 // indirect
 	github.com/mailru/easyjson v0.7.0 // indirect
-	github.com/modern-go/reflect2 v1.0.1 // indirect
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.4.0 // indirect
 	golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
@@ -21,7 +20,7 @@ require (
 	gopkg.in/yaml.v2 v2.2.8 // indirect
 	k8s.io/gengo v0.0.0-20190822140433-26a664648505
 	k8s.io/klog v1.0.0
-	k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
+	k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d
 )
 
 replace (
diff --git a/vendor/k8s.io/code-generator/go.sum b/vendor/k8s.io/code-generator/go.sum
index afc21aae56..10083f5703 100644
--- a/vendor/k8s.io/code-generator/go.sum
+++ b/vendor/k8s.io/code-generator/go.sum
@@ -6,7 +6,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -36,10 +35,9 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
-github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
@@ -59,14 +57,12 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
@@ -75,7 +71,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
@@ -123,12 +118,12 @@ k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
-k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
-k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d h1:jocF7XFucw2pEiv2wS7wk2FRFCjDFGV1oa4TMs0SAT0=
+k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
 modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
-sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6dc1277432..1550676156 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -597,7 +597,7 @@ gopkg.in/natefinch/lumberjack.v2
 gopkg.in/tomb.v1
 # gopkg.in/yaml.v2 v2.2.8 => gopkg.in/yaml.v2 v2.2.4
 gopkg.in/yaml.v2
-# k8s.io/api v0.17.4 => k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200312205431-8d8aa3959853
+# k8s.io/api v0.17.5 => k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200416113457-e0fccafd6954
 k8s.io/api/admissionregistration/v1
 k8s.io/api/admissionregistration/v1beta1
 k8s.io/api/apps/v1
@@ -638,7 +638,7 @@ k8s.io/api/settings/v1alpha1
 k8s.io/api/storage/v1
 k8s.io/api/storage/v1alpha1
 k8s.io/api/storage/v1beta1
-# k8s.io/apiextensions-apiserver v0.0.0 => k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200312205431-8d8aa3959853
+# k8s.io/apiextensions-apiserver v0.0.0 => k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200416113457-e0fccafd6954
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
@@ -647,7 +647,7 @@ k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
 k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme
 k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1
-# k8s.io/apimachinery v0.17.4 => k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200312205431-8d8aa3959853
+# k8s.io/apimachinery v0.17.5 => k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20200416113457-e0fccafd6954
 k8s.io/apimachinery/pkg/api/equality
 k8s.io/apimachinery/pkg/api/errors
 k8s.io/apimachinery/pkg/api/meta
@@ -692,7 +692,7 @@ k8s.io/apimachinery/pkg/version
 k8s.io/apimachinery/pkg/watch
 k8s.io/apimachinery/third_party/forked/golang/json
 k8s.io/apimachinery/third_party/forked/golang/reflect
-# k8s.io/client-go v8.0.0+incompatible => github.com/cilium/client-go v0.0.0-20200323094943-b43e7e2f9a75
+# k8s.io/client-go v8.0.0+incompatible => github.com/cilium/client-go v0.0.0-20200417200125-fb1ce7014cf6
 k8s.io/client-go/discovery
 k8s.io/client-go/discovery/fake
 k8s.io/client-go/kubernetes
@@ -804,7 +804,7 @@ k8s.io/client-go/util/homedir
 k8s.io/client-go/util/jsonpath
 k8s.io/client-go/util/keyutil
 k8s.io/client-go/util/retry
-# k8s.io/code-generator v0.0.0 => k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200312205431-8d8aa3959853
+# k8s.io/code-generator v0.0.0 => k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20200416113457-e0fccafd6954
 k8s.io/code-generator
 k8s.io/code-generator/cmd/client-gen
 k8s.io/code-generator/cmd/client-gen/args
@@ -851,14 +851,14 @@ k8s.io/gengo/parser
 k8s.io/gengo/types
 # k8s.io/klog v1.0.0 => k8s.io/klog v1.0.0
 k8s.io/klog
-# k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a => k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
+# k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d => k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
 k8s.io/kube-openapi/cmd/openapi-gen/args
 k8s.io/kube-openapi/pkg/common
 k8s.io/kube-openapi/pkg/generators
 k8s.io/kube-openapi/pkg/generators/rules
 k8s.io/kube-openapi/pkg/util/proto
 k8s.io/kube-openapi/pkg/util/sets
-# k8s.io/kubernetes v1.17.4 => k8s.io/kubernetes v1.17.4
+# k8s.io/kubernetes v1.17.5 => k8s.io/kubernetes v1.17.5
 k8s.io/kubernetes/pkg/apis/core
 k8s.io/kubernetes/pkg/apis/scheduling
 k8s.io/kubernetes/pkg/kubelet/types


Test patch:
diff --git a/daemon/policy_test.go b/daemon/policy_test.go
index c9270fec7a..0a9a4a9e23 100644
--- a/daemon/policy_test.go
+++ b/daemon/policy_test.go
@@ -71,7 +71,8 @@ var (
 	testEndpointID = uint16(1)
 
 	regenerationMetadata = &regeneration.ExternalRegenerationMetadata{
-		Reason: "test",
+		Reason:            "test",
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
 	}
 
 	CNPAllowTCP80 = api.PortRule{
diff --git a/pkg/endpoint/restore_test.go b/pkg/endpoint/restore_test.go
index 17870b91d9..c1552fba90 100644
--- a/pkg/endpoint/restore_test.go
+++ b/pkg/endpoint/restore_test.go
@@ -90,7 +90,8 @@ func (ds *EndpointSuite) endpointCreator(id uint16, secID identity.NumericIdenti
 
 var (
 	regenerationMetadata = &regeneration.ExternalRegenerationMetadata{
-		Reason: "test",
+		Reason:            "test",
+		RegenerationLevel: regeneration.RegenerateWithoutDatapath,
 	}
 )
 
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 2ea5679f38..1f7885be1a 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/test/helpers/cons.go b/test/helpers/cons.go
index 5f7eca56b7..9ffc4d3678 100644
--- a/test/helpers/cons.go
+++ b/test/helpers/cons.go
@@ -204,13 +204,14 @@ const (
 	PrivateIface = "enp0s8"
 
 	// Logs messages that should not be in the cilium logs.
-	panicMessage      = "panic:"
-	deadLockHeader    = "POTENTIAL DEADLOCK:"                  // from github.com/sasha-s/go-deadlock/deadlock.go:header
-	segmentationFault = "segmentation fault"                   // from https://github.com/cilium/cilium/issues/3233
-	NACKreceived      = "NACK received for version"            // from https://github.com/cilium/cilium/issues/4003
-	RunInitFailed     = "JoinEP: "                             // from https://github.com/cilium/cilium/pull/5052
-	sizeMismatch      = "size mismatch for BPF map"            // from https://github.com/cilium/cilium/issues/7851
-	emptyBPFInitArg   = "empty argument passed to bpf/init.sh" // from https://github.com/cilium/cilium/issues/10228
+	panicMessage       = "panic:"
+	deadLockHeader     = "POTENTIAL DEADLOCK:"                  // from github.com/sasha-s/go-deadlock/deadlock.go:header
+	segmentationFault  = "segmentation fault"                   // from https://github.com/cilium/cilium/issues/3233
+	NACKreceived       = "NACK received for version"            // from https://github.com/cilium/cilium/issues/4003
+	RunInitFailed      = "JoinEP: "                             // from https://github.com/cilium/cilium/pull/5052
+	sizeMismatch       = "size mismatch for BPF map"            // from https://github.com/cilium/cilium/issues/7851
+	emptyBPFInitArg    = "empty argument passed to bpf/init.sh" // from https://github.com/cilium/cilium/issues/10228
+	uninitializedRegen = "Uninitialized regeneration level"     // from https://github.com/cilium/cilium/pull/10949
 
 	// HelmTemplate is the location of the Helm templates to install Cilium
 	HelmTemplate = "../install/kubernetes/cilium"
@@ -257,13 +258,14 @@ const CiliumConfigMapPatchKvstoreAllocator = "cilium-cm-kvstore-allocator-patch.
 // badLogMessages is a map which key is a part of a log message which indicates
 // a failure if the message does not contain any part from value list.
 var badLogMessages = map[string][]string{
-	panicMessage:      nil,
-	deadLockHeader:    nil,
-	segmentationFault: nil,
-	NACKreceived:      nil,
-	RunInitFailed:     {"signal: terminated", "signal: killed"},
-	sizeMismatch:      nil,
-	emptyBPFInitArg:   nil,
+	panicMessage:       nil,
+	deadLockHeader:     nil,
+	segmentationFault:  nil,
+	NACKreceived:       nil,
+	RunInitFailed:      {"signal: terminated", "signal: killed"},
+	sizeMismatch:       nil,
+	emptyBPFInitArg:    nil,
+	uninitializedRegen: nil,
 }
 
 var ciliumCLICommands = map[string]string{
diff --git a/test/helpers/kubectl.go b/test/helpers/kubectl.go
index 90b1688cfe..55d8db7be4 100644
--- a/test/helpers/kubectl.go
+++ b/test/helpers/kubectl.go
@@ -39,6 +39,7 @@ import (
 	"github.com/cilium/cilium/test/helpers/logutils"
 
 	"github.com/asaskevich/govalidator"
+	"github.com/onsi/gomega"
 	"github.com/sirupsen/logrus"
 	v1 "k8s.io/api/core/v1"
 )
@@ -449,6 +450,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.
@@ -2527,6 +2550,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)
+	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/k8sT/Policies.go b/test/k8sT/Policies.go
index 0cb8dbc43a..c6b4a8b41f 100644
--- a/test/k8sT/Policies.go
+++ b/test/k8sT/Policies.go
@@ -65,6 +65,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 +93,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 +126,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 +150,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")
@@ -966,6 +968,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 3164baaa32..506aa243f1 100644
--- a/test/k8sT/Services.go
+++ b/test/k8sT/Services.go
@@ -52,18 +52,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)
-		Expect(err).Should(BeNil(), "Can not retrieve Node IP for "+label)
-		return nodeName, nodeIP
-	}
-
 	BeforeAll(func() {
 		var err error
 
@@ -201,7 +189,8 @@ var _ = Describe("K8sServicesTest", func() {
 				"cluster-ip-same-node.log")
 			defer monitorStop()
 
-			k8s1Name, _ := getNodeInfo(helpers.K8s1)
+			k8s1Name, _ := kubectl.GetNodeInfo(helpers.K8s1)
+
 			status, err := kubectl.ExecInHostNetNS(context.TODO(), k8s1Name,
 				helpers.CurlFail("http://%s/", clusterIP))
 			Expect(err).To(BeNil(), "Cannot run curl in host netns")
@@ -265,7 +254,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")
@@ -412,7 +401,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(),
@@ -431,8 +420,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()
 
@@ -580,8 +569,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)
@@ -619,8 +608,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)
@@ -773,7 +762,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 26d32758eb..a022343f69 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{})
 }
 
-// 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/provision/k8s_install.sh b/test/provision/k8s_install.sh
index d412e82d13..49331ad0a4 100755
--- a/test/provision/k8s_install.sh
+++ b/test/provision/k8s_install.sh
@@ -237,7 +237,7 @@ case $K8S_VERSION in
         ;;
     "1.16")
         KUBERNETES_CNI_VERSION="0.7.5"
-        K8S_FULL_VERSION="1.16.8"
+        K8S_FULL_VERSION="1.16.9"
         KUBEADM_OPTIONS="--ignore-preflight-errors=cri"
         KUBEADM_SLAVE_OPTIONS="--discovery-token-unsafe-skip-ca-verification --ignore-preflight-errors=cri,SystemVerification"
         sudo ln -sf $COREDNS_DEPLOYMENT $DNS_DEPLOYMENT
@@ -245,7 +245,7 @@ case $K8S_VERSION in
         ;;
     "1.17")
         KUBERNETES_CNI_VERSION="0.7.5"
-        K8S_FULL_VERSION="1.17.4"
+        K8S_FULL_VERSION="1.17.5"
         KUBEADM_OPTIONS="--ignore-preflight-errors=cri"
         KUBEADM_SLAVE_OPTIONS="--discovery-token-unsafe-skip-ca-verification --ignore-preflight-errors=cri,SystemVerification"
         sudo ln -sf $COREDNS_DEPLOYMENT $DNS_DEPLOYMENT