Repository: kumahq/kuma
Pull request #1519 (2021-02-04 17:04:35)

doc(*) compliant DNS names
### Summary

A proposal for generating DNS compliant names.


Patch:
diff --git a/api/go.mod b/api/go.mod
index 7a260c329..4bb000d39 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -7,8 +7,8 @@ require (
 	github.com/envoyproxy/protoc-gen-validate v0.4.1
 	github.com/ghodss/yaml v1.0.0
 	github.com/golang/protobuf v1.4.3
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/pkg/errors v0.9.1
 	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
 	google.golang.org/grpc v1.34.0
diff --git a/api/go.sum b/api/go.sum
index e083d924b..0314f935e 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -48,12 +48,12 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -67,6 +67,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -78,6 +79,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -87,6 +89,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -94,6 +97,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -102,11 +106,10 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -117,10 +120,12 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
diff --git a/app/kuma-dp/cmd/context.go b/app/kuma-dp/cmd/context.go
index 47ba7efac..dc547cecd 100644
--- a/app/kuma-dp/cmd/context.go
+++ b/app/kuma-dp/cmd/context.go
@@ -13,9 +13,10 @@ import (
 
 // RootContext contains variables, functions and components that can be overridden when extending kuma-dp or running the test.
 type RootContext struct {
-	ComponentManager   component.Manager
-	BootstrapGenerator envoy.BootstrapConfigFactoryFunc
-	Config             *kumadp.Config
+	ComponentManager         component.Manager
+	BootstrapGenerator       envoy.BootstrapConfigFactoryFunc
+	BootstrapDynamicMetadata map[string]string
+	Config                   *kumadp.Config
 }
 
 func DefaultRootContext() *RootContext {
@@ -26,6 +27,7 @@ func DefaultRootContext() *RootContext {
 			Timeout:   10 * time.Second,
 			Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
 		}),
-		Config: &config,
+		Config:                   &config,
+		BootstrapDynamicMetadata: map[string]string{},
 	}
 }
diff --git a/app/kuma-dp/cmd/run.go b/app/kuma-dp/cmd/run.go
index 30e595f60..e155ac406 100644
--- a/app/kuma-dp/cmd/run.go
+++ b/app/kuma-dp/cmd/run.go
@@ -116,12 +116,13 @@ func newRunCmd(rootCtx *RootContext) *cobra.Command {
 			shouldQuit := setupQuitChannel()
 
 			dataplane, err := envoy.New(envoy.Opts{
-				Config:    *cfg,
-				Generator: rootCtx.BootstrapGenerator,
-				Dataplane: dp,
-				Stdout:    cmd.OutOrStdout(),
-				Stderr:    cmd.OutOrStderr(),
-				Quit:      shouldQuit,
+				Config:          *cfg,
+				Generator:       rootCtx.BootstrapGenerator,
+				Dataplane:       dp,
+				DynamicMetadata: rootCtx.BootstrapDynamicMetadata,
+				Stdout:          cmd.OutOrStdout(),
+				Stderr:          cmd.OutOrStderr(),
+				Quit:            shouldQuit,
 			})
 			if err != nil {
 				return err
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy.go b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
index fe9426771..173e4dd85 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
@@ -25,15 +25,23 @@ var (
 	runLog = core.Log.WithName("kuma-dp").WithName("run").WithName("envoy")
 )
 
-type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, dp *rest.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error)
+type BootstrapParams struct {
+	Dataplane        *rest.Resource
+	BootstrapVersion types.BootstrapVersion
+	EnvoyVersion     EnvoyVersion
+	DynamicMetadata  map[string]string
+}
+
+type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error)
 
 type Opts struct {
-	Config    kuma_dp.Config
-	Generator BootstrapConfigFactoryFunc
-	Dataplane *rest.Resource
-	Stdout    io.Writer
-	Stderr    io.Writer
-	Quit      chan struct{}
+	Config          kuma_dp.Config
+	Generator       BootstrapConfigFactoryFunc
+	Dataplane       *rest.Resource
+	DynamicMetadata map[string]string
+	Stdout          io.Writer
+	Stderr          io.Writer
+	Quit            chan struct{}
 }
 
 func New(opts Opts) (*Envoy, error) {
@@ -109,7 +117,12 @@ func (e *Envoy) Start(stop <-chan struct{}) error {
 	}
 	runLog.Info("fetched Envoy version", "version", envoyVersion)
 	runLog.Info("generating bootstrap configuration")
-	bootstrapConfig, version, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, e.opts.Dataplane, types.BootstrapVersion(e.opts.Config.Dataplane.BootstrapVersion), *envoyVersion)
+	bootstrapConfig, version, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, BootstrapParams{
+		Dataplane:        e.opts.Dataplane,
+		BootstrapVersion: types.BootstrapVersion(e.opts.Config.Dataplane.BootstrapVersion),
+		EnvoyVersion:     *envoyVersion,
+		DynamicMetadata:  e.opts.DynamicMetadata,
+	})
 	if err != nil {
 		return errors.Errorf("Failed to generate Envoy bootstrap config. %v", err)
 	}
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
index 460ee5c30..544e02ccd 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
@@ -16,7 +16,6 @@ import (
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 	"github.com/kumahq/kuma/pkg/core"
-	rest_types "github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	kuma_version "github.com/kumahq/kuma/pkg/version"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
@@ -43,7 +42,7 @@ func IsInvalidRequestErr(err error) bool {
 	return strings.HasPrefix(err.Error(), "Invalid request: ")
 }
 
-func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 	bootstrapUrl, err := net_url.Parse(url)
 	if err != nil {
 		return nil, "", err
@@ -74,7 +73,7 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 	var version types.BootstrapVersion
 	err = retry.Do(context.Background(), backoff, func(ctx context.Context) error {
 		log.Info("trying to fetch bootstrap configuration from the Control Plane")
-		respBytes, version, err = b.requestForBootstrap(bootstrapUrl, cfg, dp, bootstrapVersion, ev)
+		respBytes, version, err = b.requestForBootstrap(bootstrapUrl, cfg, params)
 		if err == nil {
 			return nil
 		}
@@ -96,11 +95,11 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 	return respBytes, version, nil
 }
 
-func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, params BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 	url.Path = "/bootstrap"
 	var dataplaneResource string
-	if dp != nil {
-		dpJSON, err := json.Marshal(dp)
+	if params.Dataplane != nil {
+		dpJSON, err := json.Marshal(params.Dataplane)
 		if err != nil {
 			return nil, "", err
 		}
@@ -114,7 +113,7 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 		AdminPort:          cfg.Dataplane.AdminPort.Lowest(),
 		DataplaneTokenPath: cfg.DataplaneRuntime.TokenPath,
 		DataplaneResource:  dataplaneResource,
-		BootstrapVersion:   bootstrapVersion,
+		BootstrapVersion:   params.BootstrapVersion,
 		Version: types.Version{
 			KumaDp: types.KumaDpVersion{
 				Version:   kuma_version.Build.Version,
@@ -123,10 +122,11 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 				BuildDate: kuma_version.Build.BuildDate,
 			},
 			Envoy: types.EnvoyVersion{
-				Version: ev.Version,
-				Build:   ev.Build,
+				Version: params.EnvoyVersion.Version,
+				Build:   params.EnvoyVersion.Build,
 			},
 		},
+		DynamicMetadata: params.DynamicMetadata,
 	}
 	jsonBytes, err := json.Marshal(request)
 	if err != nil {
diff --git a/docs/proposals/dns-naming-compliance.md b/docs/proposals/dns-naming-compliance.md
new file mode 100644
index 000000000..896f44345
--- /dev/null
+++ b/docs/proposals/dns-naming-compliance.md
@@ -0,0 +1,90 @@
+# DNS compliant names in Kuma
+
+## Current state
+
+### Service Naming
+
+In a broader sense, Kuma uses the `kuma.io/service` tag to mark a certain combination of ip/address and port as a particular service, which then
+can be used in the Policies selectors.
+
+On Kubernetes, this tag is generated automatically and is an underscore delimited combination of the K8s Service Name, Namespace, and Port: `<name>_<namespace>_svc_<port>`.
+
+On Universal, one still has to generate the name in an arbitrary form.
+
+### Cross-zone communication
+
+To account for multizone and hybrid deployments, we have extended the naming scheme through a DNS resolving service by adding a `.mesh` suffix.
+This unifies the naming of services in a way that it does not matter where the service was instantiated. Of course the naming scheme in Kubernetes
+is still applied, but now we can also also call `<name>_<namespace>_svc_<port>.mesh`.
+
+By adding the transparent proxy capabilities to Universal, we can now refer to services in the `.mesh` domain too.
+
+The DNS server in the kuma-cp will generate and assign virtual IPs (VIPs) so that any service name resolves to it, and that
+the side-cars get an additional listener for the service cluster. The default VIPs CIDR is `240.0.0.1`.
+
+### Exposing services outside the mesh
+
+The typical way to expose services from within the mesh to the external consumers is through a gateway dataplane proxy (one the Kuma side) and
+a reverse proxy on the customer-facing side. In this architecture, the traffic routes on the proxy should refer to Kuma services so that the
+listeners on the side-car Envoy, are hit with the traffic and then load-balanced through the available local and remote endpoints.
+
+On Kubernetes this happens using an Ingress resource of the form:
+
+```yaml
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: marketplace
+  namespace: default
+  annotations:
+    kubernetes.io/ingress.class: kong
+    kubernetes.io/ingress.global-static-ip-name: "kong-demo-ip"
+spec:
+  rules:
+  - host: localhost
+    http:
+      paths:
+      - path: /
+        backend:
+          serviceName: frontend
+          servicePort: 80
+```
+
+Where `serviceName: frontend` is the name of a Kubernetes Service object, which will be used by the proxy to redirect the incoming requests.
+This same service in Kuma namespace will be called `frontend_default_svc_80` and can also be resolved as a DNS name `frontend_default_svc_80.mesh`.
+
+However, the problem is that the Kubernetes Ingress Resource enforces 2 rules here:
+* `serviceName` can refer only to a Kubernetes Service
+* this service can not contain `_` or `.`
+
+### Illustrating the problem
+To illustrate the problem further, let's look at the option to manually create a Service of type `ExternalName`
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: frontend
+  namespace: default
+  annotations:
+    ingress.kubernetes.io/service-upstream: "true"
+spec:
+  type: ExternalName
+  externalName: frontend_default_svc_80.mesh
+```
+
+```shell
+The Service "frontend" is invalid: spec.externalName: Invalid value: "frontend_default_svc_80.mesh": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
+```
+
+The DNS name we try to supply is invalid according to the relevant RFCs and the Kubernetes embedded checks. So we are not even able to apply that resource.
+
+## Proposal for improvements
+
+In addition to the already existing Kubernetes generated `frontend_default_svc_80.mesh`, we can add a couple of compliant aliases:
+* use dashes as separators `frontend-default-svc-80.mesh`. This can get compllicated, cosider a service name `my-complex-service`
+  which naturally translates to `my-complex-service_default_svc_80.mesh`, and then the all dashes version `my-complex-service-default-svc-80.mesh`
+* use dots as separators `frontend.default.svc.80.mesh`. The complex service version then will be slighly more readable `my-complex-service.default.svc.80.mesh`
+
+All three of these should refer to the same VIP. We can have a `kumactl` command that enumerates the services and ServiceInsights and expose
+the DNS names associated with it.
diff --git a/docs/proposals/dns-using-envoy.md b/docs/proposals/dns-using-envoy.md
new file mode 100644
index 000000000..44c2a3f34
--- /dev/null
+++ b/docs/proposals/dns-using-envoy.md
@@ -0,0 +1,74 @@
+# Use Envoy DNSFilter for DNS resolution
+
+## Context
+
+Currently, Kuma relies on an external DNS to provide VIPs which are then added as services which proxy back to the actual Cluster in Envoy.
+For VIPs to be resolved Kuma CP runs a DNS server which maintains this mapping.
+
+This current setup has some limitations:
+
+- Apps need to set their DNS to point to the CP's DNS (or CNIs need to configure DNS to be correct)
+- The CP needs to run a DNS and handle the load
+- When the CP is down DNS requests will fail. This is usually not the case in a Service Mesh
+- DNS configuration is global to all meshes
+- DNS is a separate component which makes it less customisable compared to things that end up in XDS
+- This DNS is not aware of the origin of requests, therefore isolation of meshes breaks as anyone can resolve any domain
+
+In this proposal we suggest using [Envoy's DNSFilter](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/udp_filters/dns_filter) to replace the CP DNS.
+We add a new section in the Mesh Resource to be able to control the configuration of the DNS.
+
+## Out of scope
+
+* UDP transparent proxying
+* Handling SRV records. While DNSFilter supports it, it's probably not necessary in v1
+
+## Requirements
+
+* The top-level domain should be configurable (defaults to `mesh`)
+* Drop in replacement to Kuma DNS (Default configuration should match Kuma's current configuration)
+* Select the tag(s) to use for lower level domain names
+* Provide complete isolation between meshes. .i.e. it shouldn't be possible from a mesh to figure out services running in other meshes by querying the DNS
+* DNS query should still succeed even if the CP is down
+
+## Configuration model
+
+Here is a full example of the configuration on the mesh.
+
+```yaml
+type: Mesh
+name: default 
+dns:
+  port: "53" # [Optional defaults to 53] the port on which the UDP listener should bind in envoy, if set to "transparent" use the same port as for transparent proxying.
+  template: "{{ kuma.io/service | replace '_' '.' }}.mesh" # [Optional defaults to `{{ kuma.io/service }}` ] a gotemplate which defines the domain this resolves to.
+  tags: ["kuma.io/protocol", "kuma.io/service"] # [Optional defaults to ["kuma.io/service"]] A list of tags to use to build domnains they are joined with "." for example: "http.my-service.mesh" would be a final name.
+```
+
+## K8s compatibility
+
+In K8s we will update the pod's [DNSConfig](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) to add the sidecar as a nameserver.
+We will add an annotation `kuma.io/local-dns-disabled` which will enable users to avoid this injection (This can be useful for troubleshooting).
+
+## Universal compatibility
+
+In universal the user will need to have their /etc/resolv.conf or iptables set to redirect DNS to the envoy sidecar.
+
+## What needs to be done
+
+- Add the configuration to the Mesh resource 
+- Add possibility to add UDP listeners to XDS
+- Add a new generator which will add the UDP listener with the DNSFilter to the XDS configuration
+- Add modification of K8s pod DNSConfig to add the local nameserver when using this feature.
+
+## Backward compatibility
+
+- By default, this feature won't be used until adding DNS 
+- The default configuration will make DNS names similar to what is present by default in Kuma
+- Users pick the address and port at which the DNS server runs, thus being able to run both setups concurrently
+
+In a future version it will be possible to have a default DNS configuration to replace Kuma DNS altogether.
+
+## Notes
+
+- POC: https://github.com/lahabana/kuma/commit/f27cd908d937d3acfa9f11b3c7e9d566001e89b4
+- Original issue: https://github.com/kumahq/kuma/issues/1450
+
diff --git a/go.mod b/go.mod
index 49ed0caed..56fb62ff0 100644
--- a/go.mod
+++ b/go.mod
@@ -29,8 +29,8 @@ require (
 	github.com/kumahq/kuma/pkg/transparentproxy/istio v0.0.0-00010101000000-000000000000
 	github.com/lib/pq v1.9.0
 	github.com/miekg/dns v1.1.35
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.9.0
diff --git a/go.sum b/go.sum
index 8ccb8a504..e8d425078 100644
--- a/go.sum
+++ b/go.sum
@@ -888,8 +888,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -899,8 +899,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/open-policy-agent/opa v0.8.2/go.mod h1:rlfeSeHuZmMEpmrcGla42AjkOUjP4rGIpS96H12un3o=
 github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -1416,8 +1416,9 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
 golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1500,6 +1501,7 @@ golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459 h1:/pJfBBZw1zeMQA6vRpalDcuw4VJ/B6+N5obtmG0Xiv8=
 golang.org/x/tools v0.0.0-20210111221946-d33bae441459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/core/xds/metadata.go b/pkg/core/xds/metadata.go
index 8cd2d3941..79a976315 100644
--- a/pkg/core/xds/metadata.go
+++ b/pkg/core/xds/metadata.go
@@ -19,6 +19,7 @@ const (
 	fieldDataplaneTokenPath         = "dataplaneTokenPath"
 	fieldDataplaneAdminPort         = "dataplane.admin.port"
 	fieldDataplaneDataplaneResource = "dataplane.resource"
+	fieldDynamicMetadata            = "dynamicMetadata"
 )
 
 // DataplaneMetadata represents environment-specific part of a dataplane configuration.
@@ -39,6 +40,7 @@ type DataplaneMetadata struct {
 	DataplaneTokenPath string
 	DataplaneResource  *core_mesh.DataplaneResource
 	AdminPort          uint32
+	DynamicMetadata    map[string]string
 }
 
 func (m *DataplaneMetadata) GetDataplaneTokenPath() string {
@@ -62,6 +64,13 @@ func (m *DataplaneMetadata) GetAdminPort() uint32 {
 	return m.AdminPort
 }
 
+func (m *DataplaneMetadata) GetDynamicMetadata(key string) string {
+	if m == nil || m.DynamicMetadata == nil {
+		return ""
+	}
+	return m.DynamicMetadata[key]
+}
+
 func DataplaneMetadataFromXdsMetadata(xdsMetadata *_struct.Struct) *DataplaneMetadata {
 	metadata := DataplaneMetadata{}
 	if xdsMetadata == nil {
@@ -88,5 +97,12 @@ func DataplaneMetadataFromXdsMetadata(xdsMetadata *_struct.Struct) *DataplaneMet
 		}
 		metadata.DataplaneResource = dp
 	}
+	if value := xdsMetadata.Fields[fieldDynamicMetadata]; value != nil {
+		dynamicMetadata := map[string]string{}
+		for field, val := range value.GetStructValue().GetFields() {
+			dynamicMetadata[field] = val.GetStringValue()
+		}
+		metadata.DynamicMetadata = dynamicMetadata
+	}
 	return &metadata
 }
diff --git a/pkg/plugins/resources/k8s/native/go.mod b/pkg/plugins/resources/k8s/native/go.mod
index 357af2197..6f3227327 100644
--- a/pkg/plugins/resources/k8s/native/go.mod
+++ b/pkg/plugins/resources/k8s/native/go.mod
@@ -6,8 +6,8 @@ require (
 	github.com/go-logr/logr v0.1.0
 	github.com/golang/protobuf v1.4.3
 	github.com/kumahq/kuma/api v0.0.0-00010101000000-000000000000
-	github.com/onsi/ginkgo v1.14.2
-	github.com/onsi/gomega v1.10.4
+	github.com/onsi/ginkgo v1.15.0
+	github.com/onsi/gomega v1.10.5
 	github.com/pkg/errors v0.9.1
 	golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
 	k8s.io/apimachinery v0.18.14
diff --git a/pkg/plugins/resources/k8s/native/go.sum b/pkg/plugins/resources/k8s/native/go.sum
index c62fd831e..7c2ce3762 100644
--- a/pkg/plugins/resources/k8s/native/go.sum
+++ b/pkg/plugins/resources/k8s/native/go.sum
@@ -247,14 +247,14 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
-github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -317,6 +317,7 @@ github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@@ -350,6 +351,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -369,6 +371,7 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -381,6 +384,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -401,9 +405,9 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -430,10 +434,12 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
 gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
diff --git a/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go b/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go
new file mode 100644
index 000000000..2023e5375
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/controllers/pod_status_controller.go
@@ -0,0 +1,135 @@
+package controllers
+
+import (
+	"context"
+
+	"github.com/pkg/errors"
+
+	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
+	"github.com/kumahq/kuma/pkg/envoy/admin"
+	k8s_common "github.com/kumahq/kuma/pkg/plugins/common/k8s"
+	util_k8s "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/util"
+
+	"github.com/go-logr/logr"
+	kube_core "k8s.io/api/core/v1"
+	kube_apierrs "k8s.io/apimachinery/pkg/api/errors"
+	kube_runtime "k8s.io/apimachinery/pkg/runtime"
+	kube_record "k8s.io/client-go/tools/record"
+	kube_ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	kube_client "sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+
+	"github.com/kumahq/kuma/pkg/core/resources/manager"
+	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
+)
+
+// PodStatusReconciler tracks pods status changes and signals kuma-dp when it has to complete
+type PodStatusReconciler struct {
+	kube_client.Client
+	kube_record.EventRecorder
+	Scheme            *kube_runtime.Scheme
+	ResourceManager   manager.ResourceManager
+	Log               logr.Logger
+	ResourceConverter k8s_common.Converter
+	EnvoyAdminClient  admin.EnvoyAdminClient
+}
+
+func (r *PodStatusReconciler) Reconcile(req kube_ctrl.Request) (kube_ctrl.Result, error) {
+	ctx := context.Background()
+	log := r.Log.WithValues("pod-status", req.NamespacedName)
+
+	// Fetch the Pod instance
+	pod := &kube_core.Pod{}
+	if err := r.Get(ctx, req.NamespacedName, pod); err != nil {
+		if kube_apierrs.IsNotFound(err) {
+			return kube_ctrl.Result{}, nil
+		}
+		log.Error(err, "unable to fetch Pod")
+		return kube_ctrl.Result{}, err
+	}
+
+	// we process only Pods owned by a Job
+	isJob := false
+	for _, o := range pod.GetObjectMeta().GetOwnerReferences() {
+		if o.Kind == "Job" {
+			isJob = true
+			break
+		}
+	}
+	if !isJob {
+		return kube_ctrl.Result{}, nil
+	}
+
+	hasSidecar := false
+	for _, cs := range pod.Status.ContainerStatuses {
+		if cs.Name == util_k8s.KumaSidecarContainerName {
+			hasSidecar = true
+			if cs.State.Terminated != nil {
+				// the sidecar already terminated
+				return kube_ctrl.Result{}, nil
+			}
+		} else if cs.State.Terminated == nil || cs.State.Terminated.ExitCode != 0 {
+			// at least one non-sidecar container not terminated
+			// or did not completed successfully
+			// no need to tell envoy to quit yet
+			return kube_ctrl.Result{}, nil
+		}
+	}
+
+	if hasSidecar {
+		log.V(1).Info("unterminated pod with sidecar found, sending quit to envoy")
+
+		dataplane := &mesh_k8s.Dataplane{}
+		if err := r.Get(ctx, req.NamespacedName, dataplane); err != nil {
+			if kube_apierrs.IsNotFound(err) {
+				log.V(1).Info("dataplane is not found", "name", req.NamespacedName)
+				return kube_ctrl.Result{}, nil
+			}
+			log.Error(err, "unable to fetch Dataplane")
+			return kube_ctrl.Result{}, err
+		}
+
+		dp := core_mesh.NewDataplaneResource()
+		if err := r.ResourceConverter.ToCoreResource(dataplane, dp); err != nil {
+			converterLog.Error(err, "failed to parse Dataplane", "dataplane", dataplane.Spec)
+			return kube_ctrl.Result{}, err
+		}
+
+		err := r.EnvoyAdminClient.PostQuit(dp)
+		if err != nil {
+			return kube_ctrl.Result{}, errors.Wrapf(err, "envoy admin client failed. Most probably the pod is already going down.")
+		}
+	}
+
+	return kube_ctrl.Result{}, nil
+}
+
+func (r *PodStatusReconciler) SetupWithManager(mgr kube_ctrl.Manager) error {
+	for _, addToScheme := range []func(*kube_runtime.Scheme) error{kube_core.AddToScheme, mesh_k8s.AddToScheme} {
+		if err := addToScheme(mgr.GetScheme()); err != nil {
+			return err
+		}
+	}
+
+	return kube_ctrl.NewControllerManagedBy(mgr).
+		For(&kube_core.Pod{}, builder.WithPredicates(podStatusEvents)).
+		Complete(r)
+}
+
+// we only want status event updates
+var podStatusEvents = predicate.Funcs{
+	CreateFunc: func(event event.CreateEvent) bool {
+		return false
+	},
+	DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
+		return false
+	},
+	UpdateFunc: func(updateEvent event.UpdateEvent) bool {
+		return true
+	},
+	GenericFunc: func(genericEvent event.GenericEvent) bool {
+		return false
+	},
+}
diff --git a/pkg/plugins/runtime/k8s/plugin.go b/pkg/plugins/runtime/k8s/plugin.go
index f7b802601..61a9446e8 100644
--- a/pkg/plugins/runtime/k8s/plugin.go
+++ b/pkg/plugins/runtime/k8s/plugin.go
@@ -90,6 +90,9 @@ func addControllers(mgr kube_ctrl.Manager, rt core_runtime.Runtime, converter k8
 	if err := addPodReconciler(mgr, rt, converter); err != nil {
 		return err
 	}
+	if err := addPodStatusReconciler(mgr, rt, converter); err != nil {
+		return err
+	}
 	if err := addDNS(mgr, rt, converter); err != nil {
 		return err
 	}
@@ -150,6 +153,18 @@ func addPodReconciler(mgr kube_ctrl.Manager, rt core_runtime.Runtime, converter
 	return reconciler.SetupWithManager(mgr)
 }
 
+func addPodStatusReconciler(mgr kube_ctrl.Manager, rt core_runtime.Runtime, converter k8s_common.Converter) error {
+	reconciler := &controllers.PodStatusReconciler{
+		Client:            mgr.GetClient(),
+		EventRecorder:     mgr.GetEventRecorderFor("k8s.kuma.io/dataplane-jobs-syncer"),
+		Scheme:            mgr.GetScheme(),
+		Log:               core.Log.WithName("controllers").WithName("Pod"),
+		ResourceConverter: converter,
+		EnvoyAdminClient:  rt.EnvoyAdminClient(),
+	}
+	return reconciler.SetupWithManager(mgr)
+}
+
 func addDNS(mgr kube_ctrl.Manager, rt core_runtime.Runtime, converter k8s_common.Converter) error {
 	if rt.Config().Mode == config_core.Global {
 		return nil
diff --git a/pkg/xds/bootstrap/generator.go b/pkg/xds/bootstrap/generator.go
index 1819641ca..8b0410e62 100644
--- a/pkg/xds/bootstrap/generator.go
+++ b/pkg/xds/bootstrap/generator.go
@@ -207,6 +207,7 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 		EnvoyVersion:       request.Version.Envoy.Version,
 		EnvoyBuild:         request.Version.Envoy.Build,
 		HdsEnabled:         b.hdsEnabled,
+		DynamicMetadata:    request.DynamicMetadata,
 	}
 	log.WithValues("params", params).Info("Generating bootstrap config")
 	return b.configForParameters(params, request.BootstrapVersion)
diff --git a/pkg/xds/bootstrap/parameters.go b/pkg/xds/bootstrap/parameters.go
index bdc5adc9e..3a7b4ad3c 100644
--- a/pkg/xds/bootstrap/parameters.go
+++ b/pkg/xds/bootstrap/parameters.go
@@ -22,4 +22,5 @@ type configParameters struct {
 	EnvoyVersion       string
 	EnvoyBuild         string
 	HdsEnabled         bool
+	DynamicMetadata    map[string]string
 }
diff --git a/pkg/xds/bootstrap/template_v2.go b/pkg/xds/bootstrap/template_v2.go
index fa42790d0..2e2407199 100644
--- a/pkg/xds/bootstrap/template_v2.go
+++ b/pkg/xds/bootstrap/template_v2.go
@@ -23,6 +23,12 @@ node:
       envoy:
         version: "{{ .EnvoyVersion }}"
         build: "{{ .EnvoyBuild }}"
+{{if .DynamicMetadata }}
+    dynamicMetadata:
+{{ range $key, $value := .DynamicMetadata }}
+      {{ $key }}: "{{ $value }}"
+{{ end }}
+{{ end }}
 
 {{if .AdminPort }}
 admin:
diff --git a/pkg/xds/bootstrap/template_v3.go b/pkg/xds/bootstrap/template_v3.go
index adcd4fcf1..ff648aeea 100644
--- a/pkg/xds/bootstrap/template_v3.go
+++ b/pkg/xds/bootstrap/template_v3.go
@@ -23,6 +23,12 @@ node:
       envoy:
         version: "{{ .EnvoyVersion }}"
         build: "{{ .EnvoyBuild }}"
+{{if .DynamicMetadata }}
+    dynamicMetadata:
+{{ range $key, $value := .DynamicMetadata }}
+      {{ $key }}: "{{ $value }}"
+{{ end }}
+{{ end }}
 
 {{if .AdminPort }}
 admin:
diff --git a/pkg/xds/bootstrap/types/bootstrap_request.go b/pkg/xds/bootstrap/types/bootstrap_request.go
index 66fa2239d..4d4a6d31f 100644
--- a/pkg/xds/bootstrap/types/bootstrap_request.go
+++ b/pkg/xds/bootstrap/types/bootstrap_request.go
@@ -13,13 +13,14 @@ const (
 const BootstrapVersionHeader = "kuma-bootstrap-version"
 
 type BootstrapRequest struct {
-	Mesh               string  `json:"mesh"`
-	Name               string  `json:"name"`
-	AdminPort          uint32  `json:"adminPort,omitempty"`
-	DataplaneTokenPath string  `json:"dataplaneTokenPath,omitempty"`
-	DataplaneResource  string  `json:"dataplaneResource,omitempty"`
-	Host               string  `json:"-"`
-	Version            Version `json:"version"`
+	Mesh               string            `json:"mesh"`
+	Name               string            `json:"name"`
+	AdminPort          uint32            `json:"adminPort,omitempty"`
+	DataplaneTokenPath string            `json:"dataplaneTokenPath,omitempty"`
+	DataplaneResource  string            `json:"dataplaneResource,omitempty"`
+	Host               string            `json:"-"`
+	Version            Version           `json:"version"`
+	DynamicMetadata    map[string]string `json:"dynamicMetadata"`
 	// BootstrapVersion is an optional version to override the control plane's default setting
 	BootstrapVersion BootstrapVersion `json:"bootstrapVersion"`
 }


Test patch:
diff --git a/app/kuma-dp/cmd/run_test.go b/app/kuma-dp/cmd/run_test.go
index 90afe22f2..140dcdcb1 100644
--- a/app/kuma-dp/cmd/run_test.go
+++ b/app/kuma-dp/cmd/run_test.go
@@ -13,7 +13,6 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 
 	. "github.com/onsi/ginkgo"
@@ -106,7 +105,7 @@ var _ = Describe("run", func() {
 
 			// given
 			rootCtx := DefaultRootContext()
-			rootCtx.BootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, _ types.BootstrapVersion, version envoy.EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			rootCtx.BootstrapGenerator = func(_ string, cfg kumadp.Config, _ envoy.BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "bootstrap-config.golden.yaml"))
 				Expect(err).ToNot(HaveOccurred())
 				return respBytes, "", nil
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
index 93d792e57..502aab88c 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
@@ -16,7 +16,6 @@ import (
 	. "github.com/onsi/gomega"
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
-	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
 
@@ -69,7 +68,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return []byte(`node:
   id: example`), types.BootstrapV2, nil
 			}
@@ -137,7 +136,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return nil, "", nil
 			}
 
@@ -179,7 +178,7 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+			sampleConfig := func(string, kuma_dp.Config, BootstrapParams) ([]byte, types.BootstrapVersion, error) {
 				return nil, "", nil
 			}
 
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
index cf8f3cd35..d9bcb2107 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
@@ -29,6 +29,7 @@ var _ = Describe("Remote Bootstrap", func() {
 		config                   kuma_dp.Config
 		bootstrapVersion         types.BootstrapVersion
 		dataplane                *rest.Resource
+		dynamicMetadata          map[string]string
 		expectedBootstrapRequest string
 	}
 
@@ -65,10 +66,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		generator := NewRemoteBootstrapGenerator(http.DefaultClient)
 
 		// when
-		config, version, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, given.dataplane, given.bootstrapVersion, EnvoyVersion{
-			Build:   "hash/1.15.0/RELEASE",
-			Version: "1.15.0",
-		})
+		params := BootstrapParams{
+			Dataplane:        given.dataplane,
+			BootstrapVersion: given.bootstrapVersion,
+			EnvoyVersion: EnvoyVersion{
+				Build:   "hash/1.15.0/RELEASE",
+				Version: "1.15.0",
+			},
+			DynamicMetadata: given.dynamicMetadata,
+		}
+		config, version, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, params)
 
 		// then
 		Expect(version).To(Equal(given.bootstrapVersion))
@@ -93,6 +100,9 @@ var _ = Describe("Remote Bootstrap", func() {
 							Name: "sample",
 						},
 					},
+					dynamicMetadata: map[string]string{
+						"test": "value",
+					},
 					expectedBootstrapRequest: `
 					{
 					  "mesh": "demo",
@@ -112,7 +122,10 @@ var _ = Describe("Remote Bootstrap", func() {
 						  "build": "hash/1.15.0/RELEASE"
 						}
 					  },
-					  "bootstrapVersion": "2"
+					  "bootstrapVersion": "2",
+					  "dynamicMetadata": {
+					    "test": "value"
+					  }
 					}`,
 				}
 			}()),
@@ -154,7 +167,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "build": "hash/1.15.0/RELEASE"
                         }
                       },
-                      "bootstrapVersion": "3"
+                      "bootstrapVersion": "3",
+					  "dynamicMetadata": null
                     }`,
 				}
 			}()),
@@ -193,7 +207,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "build": "hash/1.15.0/RELEASE"
                         }
                       },
-                      "bootstrapVersion": ""
+                      "bootstrapVersion": "",
+					  "dynamicMetadata": null
                     }`,
 				}
 			}()),
@@ -226,13 +241,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		// when
 		cfg := kuma_dp.DefaultConfig()
 		cfg.ControlPlane.Retry.Backoff = 10 * time.Millisecond
-		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, &rest.Resource{
-			Meta: rest.ResourceMeta{
-				Type: "Dataplane",
-				Mesh: "default",
-				Name: "dp-1",
+		params := BootstrapParams{
+			Dataplane: &rest.Resource{
+				Meta: rest.ResourceMeta{
+					Type: "Dataplane",
+					Mesh: "default",
+					Name: "dp-1",
+				},
 			},
-		}, "", EnvoyVersion{})
+		}
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, params)
 
 		// then
 		Expect(err).ToNot(HaveOccurred())
@@ -258,9 +276,16 @@ var _ = Describe("Remote Bootstrap", func() {
 		config := kuma_dp.DefaultConfig()
 		config.ControlPlane.Retry.Backoff = 10 * time.Millisecond
 		config.ControlPlane.Retry.MaxDuration = 100 * time.Millisecond
-		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), config, &rest.Resource{
-			Meta: rest.ResourceMeta{Mesh: "default", Name: "dp-1"},
-		}, "", EnvoyVersion{})
+		params := BootstrapParams{
+			Dataplane: &rest.Resource{
+				Meta: rest.ResourceMeta{
+					Type: "Dataplane",
+					Mesh: "default",
+					Name: "dp-1",
+				},
+			},
+		}
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), config, params)
 
 		// then
 		Expect(err).To(MatchError("retryable: Dataplane entity not found. If you are running on Universal please create a Dataplane entity on kuma-cp before starting kuma-dp. If you are running on Kubernetes, please check the kuma-cp logs to determine why the Dataplane entity could not be created by the automatic sidecar injection."))
diff --git a/pkg/plugins/runtime/k8s/controllers/pod_status_controller_test.go b/pkg/plugins/runtime/k8s/controllers/pod_status_controller_test.go
new file mode 100644
index 000000000..3f7057ab2
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/controllers/pod_status_controller_test.go
@@ -0,0 +1,301 @@
+package controllers_test
+
+import (
+	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
+	util_k8s "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/util"
+	"github.com/kumahq/kuma/pkg/test/runtime"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	"github.com/kumahq/kuma/pkg/plugins/resources/k8s"
+
+	. "github.com/kumahq/kuma/pkg/plugins/runtime/k8s/controllers"
+
+	"github.com/kumahq/kuma/pkg/core"
+
+	kube_core "k8s.io/api/core/v1"
+	kube_meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kube_types "k8s.io/apimachinery/pkg/types"
+	kube_record "k8s.io/client-go/tools/record"
+	kube_ctrl "sigs.k8s.io/controller-runtime"
+	kube_client "sigs.k8s.io/controller-runtime/pkg/client"
+	kube_client_fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+var _ = Describe("PodStatusReconciler", func() {
+
+	var kubeClient kube_client.Client
+	var reconciler *PodStatusReconciler
+	var postQuitCalled int
+
+	BeforeEach(func() {
+		kubeClient = kube_client_fake.NewFakeClientWithScheme(
+			k8sClientScheme,
+			&kube_core.Pod{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-without-kuma-sidecar",
+				},
+				Status: kube_core.PodStatus{
+					ContainerStatuses: []kube_core.ContainerStatus{
+						{
+							Name: "another-side-car",
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+						{
+							Name: "workload",
+							State: kube_core.ContainerState{
+								Terminated: &kube_core.ContainerStateTerminated{},
+							},
+						},
+					},
+				},
+			},
+			&kube_core.Pod{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-with-kuma-sidecar-workload-not-owned-by-a-job",
+					OwnerReferences: []kube_meta.OwnerReference{
+						{
+							Kind: "ReplicaSet",
+						},
+					},
+				},
+				Status: kube_core.PodStatus{
+					ContainerStatuses: []kube_core.ContainerStatus{
+						{
+							Name: util_k8s.KumaSidecarContainerName,
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+						{
+							Name: "workload",
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+					},
+				},
+			},
+			&kube_core.Pod{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-with-kuma-sidecar-workload-not-terminated",
+					OwnerReferences: []kube_meta.OwnerReference{
+						{
+							Kind: "Job",
+						},
+					},
+				},
+				Status: kube_core.PodStatus{
+					ContainerStatuses: []kube_core.ContainerStatus{
+						{
+							Name: util_k8s.KumaSidecarContainerName,
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+						{
+							Name: "workload",
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+					},
+				},
+			},
+			&kube_core.Pod{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-with-kuma-sidecar-workload-not-terminated-successfully",
+					OwnerReferences: []kube_meta.OwnerReference{
+						{
+							Kind: "Job",
+						},
+					},
+				},
+				Status: kube_core.PodStatus{
+					ContainerStatuses: []kube_core.ContainerStatus{
+						{
+							Name: util_k8s.KumaSidecarContainerName,
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+						{
+							Name: "workload",
+							State: kube_core.ContainerState{
+								Terminated: &kube_core.ContainerStateTerminated{
+									ExitCode: -1,
+								},
+							},
+						},
+					},
+				},
+			},
+			&kube_core.Pod{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-with-kuma-sidecar-workload-terminated",
+					OwnerReferences: []kube_meta.OwnerReference{
+						{
+							Kind: "Job",
+						},
+					},
+				},
+				Status: kube_core.PodStatus{
+					ContainerStatuses: []kube_core.ContainerStatus{
+						{
+							Name: util_k8s.KumaSidecarContainerName,
+							State: kube_core.ContainerState{
+								Terminated: nil,
+							},
+						},
+						{
+							Name: "workload",
+							State: kube_core.ContainerState{
+								Terminated: &kube_core.ContainerStateTerminated{
+									ExitCode: 0,
+								},
+							},
+						},
+					},
+				},
+			},
+			&mesh_k8s.Dataplane{
+				ObjectMeta: kube_meta.ObjectMeta{
+					Namespace: "demo",
+					Name:      "pod-with-kuma-sidecar-workload-terminated",
+				},
+			},
+		)
+
+		postQuitCalled = 0
+		reconciler = &PodStatusReconciler{
+			Client:            kubeClient,
+			EventRecorder:     kube_record.NewFakeRecorder(10),
+			Scheme:            k8sClientScheme,
+			Log:               core.Log.WithName("test"),
+			ResourceConverter: k8s.NewSimpleConverter(),
+			EnvoyAdminClient: &runtime.DummyEnvoyAdminClient{
+				PostQuitCalled: &postQuitCalled,
+			},
+		}
+	})
+
+	It("should ignore non-existing Pods", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "non-existing-pod"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should ignore Pods without kuma sidecar", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-without-kuma-sidecar"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should ignore Pods with kuma sidecar, not owned by a job", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-with-kuma-sidecar-workload-not-owned-by-a-job"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should ignore Pods with kuma sidecar terminated", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-with-kuma-sidecar-terminated"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should ignore Pods with workload not terminated", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-with-kuma-sidecar-workload-not-terminated"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should ignore Pods with workload not terminated successfully", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-with-kuma-sidecar-workload-not-terminated-successfully"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(0))
+	})
+
+	It("should call envoy quit for Pods with workload terminated", func() {
+		// given
+		req := kube_ctrl.Request{
+			NamespacedName: kube_types.NamespacedName{Namespace: "demo", Name: "pod-with-kuma-sidecar-workload-terminated"},
+		}
+
+		// when
+		result, err := reconciler.Reconcile(req)
+		// then
+		Expect(err).ToNot(HaveOccurred())
+		// and
+		Expect(result).To(BeZero())
+		// and
+		Expect(postQuitCalled).To(Equal(1))
+	})
+})
diff --git a/pkg/xds/bootstrap/generator_test.go b/pkg/xds/bootstrap/generator_test.go
index 1e8d80a28..b92506958 100644
--- a/pkg/xds/bootstrap/generator_test.go
+++ b/pkg/xds/bootstrap/generator_test.go
@@ -192,6 +192,9 @@ var _ = Describe("bootstrapGenerator", func() {
 				Name:               "name.namespace",
 				AdminPort:          1234,
 				DataplaneTokenPath: "/tmp/token",
+				DynamicMetadata: map[string]string{
+					"test": "value",
+				},
 				DataplaneResource: `
 {
   "type": "Dataplane",
diff --git a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
index 29bfe62e7..a00bf6e79 100644
--- a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
@@ -59,6 +59,8 @@ node:
       8443, "tags": { "kuma.io/protocol": "http2", "kuma.io/service": "backend" }
       }, ] } }'
     dataplaneTokenPath: /tmp/token
+    dynamicMetadata:
+      test: value
     version:
       envoy:
         build: hash/1.15.0/RELEASE
diff --git a/test/e2e/kuma_deploy_hybrid_test.go b/test/e2e/kuma_deploy_hybrid_test.go
index c8e076e27..10d8e19f5 100644
--- a/test/e2e/kuma_deploy_hybrid_test.go
+++ b/test/e2e/kuma_deploy_hybrid_test.go
@@ -238,6 +238,17 @@ metadata:
 			"curl", "-v", "-m", "3", "--fail", "localhost:4001")
 		Expect(err).ToNot(HaveOccurred())
 		Expect(stdout).To(ContainSubstring("HTTP/1.1 200 OK"))
+
+		// Remote 1
+		// check for job support
+		// k8s access remote k8s service
+		err = DemoClientJobK8s(nonDefaultMesh, "echo-server_kuma-test_svc_80.mesh")(remote_1)
+		Expect(err).ToNot(HaveOccurred())
+
+		// Remote 2
+		// k8s access remote universal service
+		err = DemoClientJobK8s(nonDefaultMesh, "echo-server_kuma-test_svc_8080.mesh")(remote_2)
+		Expect(err).ToNot(HaveOccurred())
 	})
 
 	It("should sync traffic permissions", func() {
@@ -270,5 +281,12 @@ metadata:
 				"curl", "-v", "-m", "3", "localhost:4001")
 			return stdout, err
 		}, "10s", "1s").Should(ContainSubstring("HTTP/1.1 503 Service Unavailable"))
+
+		// Remote 1
+		// check for failing job support
+		// k8s can not access remote k8s service
+		err = DemoClientJobK8s(nonDefaultMesh, "echo-server_kuma-test_svc_8080.mesh")(remote_1)
+		Expect(err).To(HaveOccurred())
 	})
+
 })
diff --git a/test/framework/setup.go b/test/framework/setup.go
index e16163e0f..a55b4eb71 100644
--- a/test/framework/setup.go
+++ b/test/framework/setup.go
@@ -4,6 +4,9 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/gruntwork-io/terratest/modules/logger"
+	"github.com/gruntwork-io/terratest/modules/testing"
+
 	"github.com/kumahq/kuma/pkg/tls"
 
 	"github.com/go-errors/errors"
@@ -94,6 +97,51 @@ func WaitPodsAvailable(namespace, app string) InstallFunc {
 	}
 }
 
+func WaitUntilPodCompleteE(t testing.TestingT, options *k8s.KubectlOptions, podName string, retries int, sleepBetweenRetries time.Duration) error {
+	statusMsg := fmt.Sprintf("Wait for pod %s to be provisioned.", podName)
+	message, err := retry.DoWithRetryE(
+		t,
+		statusMsg,
+		retries,
+		sleepBetweenRetries,
+		func() (string, error) {
+			pod, err := k8s.GetPodE(t, options, podName)
+			if err != nil {
+				return "", err
+			}
+			for _, cs := range pod.Status.ContainerStatuses {
+				if cs.State.Terminated == nil || cs.State.Terminated.ExitCode != 0 {
+					return "", errors.Errorf("Pod is not complete yet")
+				}
+			}
+			return "Pod is now complete", nil
+		},
+	)
+	if err != nil {
+		logger.Logf(t, "Timedout waiting for Pod to be completed: %s", err)
+		return err
+	}
+	logger.Logf(t, message)
+	return nil
+}
+
+func WaitPodsComplete(namespace, app string) InstallFunc {
+	return func(c Cluster) error {
+		pods, err := k8s.ListPodsE(c.GetTesting(), c.GetKubectlOptions(namespace),
+			kube_meta.ListOptions{LabelSelector: fmt.Sprintf("app=%s", app)})
+		if err != nil {
+			return err
+		}
+		for _, p := range pods {
+			err := WaitUntilPodCompleteE(c.GetTesting(), c.GetKubectlOptions(namespace), p.GetName(), DefaultRetries, DefaultTimeout)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+}
+
 func WaitPodsNotAvailable(namespace, app string) InstallFunc {
 	return func(c Cluster) error {
 		pods, err := k8s.ListPodsE(c.GetTesting(), c.GetKubectlOptions(namespace),
@@ -274,6 +322,44 @@ spec:
 	)
 }
 
+func DemoClientJobK8s(mesh, destination string) InstallFunc {
+	const name = "demo-job-client"
+	deployment := `
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: demo-job-client
+  namespace: kuma-test
+  labels:
+    app: demo-job-client
+spec:
+  template:
+    metadata:
+      annotations:
+        kuma.io/mesh: %s
+      labels:
+        app: demo-job-client
+    spec:
+      containers:
+      - name: demo-job-client
+        image: kuma-universal
+        imagePullPolicy: IfNotPresent
+        command: [ "curl" ]
+        args:
+          - -v
+          - -m
+          - "3"
+          - --fail
+          - %s
+      restartPolicy: OnFailure
+`
+	return Combine(
+		YamlK8s(fmt.Sprintf(deployment, mesh, destination)),
+		WaitNumPods(1, name),
+		WaitPodsComplete(TestNamespace, name),
+	)
+}
+
 func DemoClientUniversal(mesh, token string, fs ...DeployOptionsFunc) InstallFunc {
 	return func(cluster Cluster) error {
 		fs = append(fs, WithMesh(mesh), WithAppname(AppModeDemoClient), WithToken(token))