CVE-2022-24797: Other (NVD-CWE-Other)
value: Pomerium is an identity-aware access proxy. In distributed service mode, Pomerium's Authenticate service exposes pprof debug and prometheus metrics handlers to untrusted traffic. This can leak potentially sensitive environmental information or lead to limited denial of service conditions. This issue is patched in version v0.17.1 Workarounds: Block access to `/debug` and `/metrics` paths on the authenticate service. This can be done with any L7 proxy, including Pomerium's own proxy service.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 9.1
Published: 2022-03-31T23:15Z
Repository: https://github.com/pomerium/pomerium
Language: Go
Files changed: config/config.go, config/config_source.go, config/envoyconfig/bootstrap_test.go, config/envoyconfig/builder.go, config/envoyconfig/clusters.go, config/envoyconfig/clusters_test.go, config/envoyconfig/listeners.go, config/envoyconfig/listeners_test.go, config/envoyconfig/routes.go, config/envoyconfig/routes_test.go, config/metrics.go, config/options.go, internal/cmd/pomerium/pomerium.go, internal/controlplane/http.go, internal/controlplane/server.go, internal/registry/reporter.go
Commit date: 2022-03-30 09:37:37 -0600
Commit message: authenticate: fix debug and metrics endpoints (#3212)


Diff: diff --git a/config/config.go b/config/config.go
index ec76f64e..14cdacb4 100644
--- a/config/config.go
+++ b/config/config.go
@@ -16,10 +16,14 @@ type Config struct {
 	GRPCPort string
 	// HTTPPort is the port the HTTP server is running on.
 	HTTPPort string
 	// OutboundPort is the port the outbound gRPC listener is running on.
 	OutboundPort string
+	// MetricsPort is the port the metrics listener is running on.
+	MetricsPort string
+	// DebugPort is the port the debug listener is running on.
+	DebugPort string
 }
 
 // Clone creates a clone of the config.
 func (cfg *Config) Clone() *Config {
 	newOptions := new(Options)
@@ -30,10 +34,12 @@ func (cfg *Config) Clone() *Config {
 		EnvoyVersion:     cfg.EnvoyVersion,
 
 		GRPCPort:     cfg.GRPCPort,
 		HTTPPort:     cfg.HTTPPort,
 		OutboundPort: cfg.OutboundPort,
+		MetricsPort:  cfg.MetricsPort,
+		DebugPort:    cfg.DebugPort,
 	}
 }
 
 // AllCertificates returns all the certificates in the config.
 func (cfg *Config) AllCertificates() ([]tls.Certificate, error) {
diff --git a/config/config_source.go b/config/config_source.go
index ac39b375..2d9a1e0c 100644
--- a/config/config_source.go
+++ b/config/config_source.go
@@ -108,25 +108,29 @@ func NewFileOrEnvironmentSource(
 	options, err := newOptionsFromConfig(configFile)
 	if err != nil {
 		return nil, err
 	}
 
-	ports, err := netutil.AllocatePorts(3)
+	ports, err := netutil.AllocatePorts(5)
 	if err != nil {
 		return nil, err
 	}
 	grpcPort := ports[0]
 	httpPort := ports[1]
 	outboundPort := ports[2]
+	metricsPort := ports[3]
+	debugPort := ports[4]
 
 	cfg := &Config{
 		Options:      options,
 		EnvoyVersion: envoyVersion,
 
 		GRPCPort:     grpcPort,
 		HTTPPort:     httpPort,
 		OutboundPort: outboundPort,
+		MetricsPort:  metricsPort,
+		DebugPort:    debugPort,
 	}
 	metrics.SetConfigInfo(ctx, cfg.Options.Services, "local", cfg.Checksum(), true)
 
 	src := &FileOrEnvironmentSource{
 		configFile: configFile,
diff --git a/config/envoyconfig/bootstrap_test.go b/config/envoyconfig/bootstrap_test.go
index 82b29b0c..885980b9 100644
--- a/config/envoyconfig/bootstrap_test.go
+++ b/config/envoyconfig/bootstrap_test.go
@@ -9,11 +9,11 @@ import (
 	"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
 	"github.com/pomerium/pomerium/internal/testutil"
 )
 
 func TestBuilder_BuildBootstrapAdmin(t *testing.T) {
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	t.Run("valid", func(t *testing.T) {
 		adminCfg, err := b.BuildBootstrapAdmin(&config.Config{
 			Options: &config.Options{
 				EnvoyAdminAddress: "localhost:9901",
 			},
@@ -39,11 +39,11 @@ func TestBuilder_BuildBootstrapAdmin(t *testing.T) {
 		assert.Error(t, err)
 	})
 }
 
 func TestBuilder_BuildBootstrapLayeredRuntime(t *testing.T) {
-	b := New("localhost:1111", "localhost:2222", filemgr.NewManager(), nil)
+	b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
 	staticCfg, err := b.BuildBootstrapLayeredRuntime()
 	assert.NoError(t, err)
 	testutil.AssertProtoJSONEqual(t, `
 		{ "layers": [{
 			"name": "static_layer_0",
@@ -56,11 +56,11 @@ func TestBuilder_BuildBootstrapLayeredRuntime(t *testing.T) {
 	`, staticCfg)
 }
 
 func TestBuilder_BuildBootstrapStaticResources(t *testing.T) {
 	t.Run("valid", func(t *testing.T) {
-		b := New("localhost:1111", "localhost:2222", filemgr.NewManager(), nil)
+		b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
 		staticCfg, err := b.BuildBootstrapStaticResources()
 		assert.NoError(t, err)
 		testutil.AssertProtoJSONEqual(t, `
 			{
 				"clusters": [
@@ -88,18 +88,18 @@ func TestBuilder_BuildBootstrapStaticResources(t *testing.T) {
 				]
 			}
 		`, staticCfg)
 	})
 	t.Run("bad gRPC address", func(t *testing.T) {
-		b := New("xyz:zyx", "localhost:2222", filemgr.NewManager(), nil)
+		b := New("xyz:zyx", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
 		_, err := b.BuildBootstrapStaticResources()
 		assert.Error(t, err)
 	})
 }
 
 func TestBuilder_BuildBootstrapStatsConfig(t *testing.T) {
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	t.Run("valid", func(t *testing.T) {
 		statsCfg, err := b.BuildBootstrapStatsConfig(&config.Config{
 			Options: &config.Options{
 				Services: "all",
 			},
diff --git a/config/envoyconfig/builder.go b/config/envoyconfig/builder.go
index c0ec627e..2609e181 100644
--- a/config/envoyconfig/builder.go
+++ b/config/envoyconfig/builder.go
@@ -5,25 +5,28 @@ import (
 	"github.com/pomerium/pomerium/internal/httputil/reproxy"
 )
 
 // A Builder builds envoy config from pomerium config.
 type Builder struct {
-	localGRPCAddress string
-	localHTTPAddress string
-	filemgr          *filemgr.Manager
-	reproxy          *reproxy.Handler
+	localGRPCAddress    string
+	localHTTPAddress    string
+	localMetricsAddress string
+	filemgr             *filemgr.Manager
+	reproxy             *reproxy.Handler
 }
 
 // New creates a new Builder.
 func New(
 	localGRPCAddress string,
 	localHTTPAddress string,
+	localMetricsAddress string,
 	fileManager *filemgr.Manager,
 	reproxyHandler *reproxy.Handler,
 ) *Builder {
 	return &Builder{
-		localGRPCAddress: localGRPCAddress,
-		localHTTPAddress: localHTTPAddress,
-		filemgr:          fileManager,
-		reproxy:          reproxyHandler,
+		localGRPCAddress:    localGRPCAddress,
+		localHTTPAddress:    localHTTPAddress,
+		localMetricsAddress: localMetricsAddress,
+		filemgr:             fileManager,
+		reproxy:             reproxyHandler,
 	}
 }
diff --git a/config/envoyconfig/clusters.go b/config/envoyconfig/clusters.go
index 90d1dd86..0e5dc525 100644
--- a/config/envoyconfig/clusters.go
+++ b/config/envoyconfig/clusters.go
@@ -32,10 +32,14 @@ func (b *Builder) BuildClusters(ctx context.Context, cfg *config.Config) ([]*env
 	}
 	httpURL := &url.URL{
 		Scheme: "http",
 		Host:   b.localHTTPAddress,
 	}
+	metricsURL := &url.URL{
+		Scheme: "http",
+		Host:   b.localMetricsAddress,
+	}
 	authorizeURLs, err := cfg.Options.GetInternalAuthorizeURLs()
 	if err != nil {
 		return nil, err
 	}
 	databrokerURLs, err := cfg.Options.GetDataBrokerURLs()
@@ -51,10 +55,15 @@ func (b *Builder) BuildClusters(ctx context.Context, cfg *config.Config) ([]*env
 	controlHTTP, err := b.buildInternalCluster(ctx, cfg.Options, "pomerium-control-plane-http", []*url.URL{httpURL}, upstreamProtocolAuto)
 	if err != nil {
 		return nil, err
 	}
 
+	controlMetrics, err := b.buildInternalCluster(ctx, cfg.Options, "pomerium-control-plane-metrics", []*url.URL{metricsURL}, upstreamProtocolAuto)
+	if err != nil {
+		return nil, err
+	}
+
 	authorizeCluster, err := b.buildInternalCluster(ctx, cfg.Options, "pomerium-authorize", authorizeURLs, upstreamProtocolHTTP2)
 	if err != nil {
 		return nil, err
 	}
 	if len(authorizeURLs) > 1 {
@@ -72,10 +81,11 @@ func (b *Builder) BuildClusters(ctx context.Context, cfg *config.Config) ([]*env
 	}
 
 	clusters := []*envoy_config_cluster_v3.Cluster{
 		controlGRPC,
 		controlHTTP,
+		controlMetrics,
 		authorizeCluster,
 		databrokerCluster,
 	}
 
 	tracingCluster, err := buildTracingCluster(cfg.Options)
diff --git a/config/envoyconfig/clusters_test.go b/config/envoyconfig/clusters_test.go
index 4e173324..f6f71d4b 100644
--- a/config/envoyconfig/clusters_test.go
+++ b/config/envoyconfig/clusters_test.go
@@ -23,11 +23,11 @@ import (
 func Test_buildPolicyTransportSocket(t *testing.T) {
 	ctx := context.Background()
 	cacheDir, _ := os.UserCacheDir()
 	customCA := filepath.Join(cacheDir, "pomerium", "envoy", "files", "custom-ca-32484c314b584447463735303142374c31414145374650305a525539554938594d524855353757313942494d473847535231.pem")
 
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	rootCABytes, _ := getCombinedCertificateAuthority("", "")
 	rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
 
 	o1 := config.NewDefaultOptions()
 	o2 := config.NewDefaultOptions()
@@ -377,11 +377,11 @@ func Test_buildPolicyTransportSocket(t *testing.T) {
 	})
 }
 
 func Test_buildCluster(t *testing.T) {
 	ctx := context.Background()
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	rootCABytes, _ := getCombinedCertificateAuthority("", "")
 	rootCA := b.filemgr.BytesDataSource("ca.pem", rootCABytes).GetFilename()
 	o1 := config.NewDefaultOptions()
 	t.Run("insecure", func(t *testing.T) {
 		endpoints, err := b.buildPolicyEndpoints(ctx, o1, &config.Policy{
@@ -856,11 +856,11 @@ func Test_validateClusters(t *testing.T) {
 
 func Test_bindConfig(t *testing.T) {
 	ctx, clearTimeout := context.WithTimeout(context.Background(), time.Second*10)
 	defer clearTimeout()
 
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	t.Run("no bind config", func(t *testing.T) {
 		cluster, err := b.buildPolicyCluster(ctx, &config.Options{}, &config.Policy{
 			From: "https://from.example.com",
 			To:   mustParseWeightedURLs(t, "https://to.example.com"),
 		})
diff --git a/config/envoyconfig/listeners.go b/config/envoyconfig/listeners.go
index 8d0f3134..8c88415a 100644
--- a/config/envoyconfig/listeners.go
+++ b/config/envoyconfig/listeners.go
@@ -484,11 +484,11 @@ func (b *Builder) buildMetricsHTTPConnectionManagerFilter() (*envoy_config_liste
 				PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"},
 			},
 			Action: &envoy_config_route_v3.Route_Route{
 				Route: &envoy_config_route_v3.RouteAction{
 					ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
-						Cluster: "pomerium-control-plane-http",
+						Cluster: "pomerium-control-plane-metrics",
 					},
 				},
 			},
 		}},
 	}})
diff --git a/config/envoyconfig/listeners_test.go b/config/envoyconfig/listeners_test.go
index 3f7cc328..5114ad9d 100644
--- a/config/envoyconfig/listeners_test.go
+++ b/config/envoyconfig/listeners_test.go
@@ -23,11 +23,11 @@ const (
 func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
 	cacheDir, _ := os.UserCacheDir()
 	certFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-crt-354e49305a5a39414a545530374e58454e48334148524c4e324258463837364355564c4e4532464b54355139495547514a38.pem")
 	keyFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-key-3350415a38414e4e4a4655424e55393430474147324651433949384e485341334b5157364f424b4c5856365a545937383735.pem")
 
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 	li, err := b.buildMetricsListener(&config.Config{
 		Options: &config.Options{
 			MetricsAddr:           "127.0.0.1:9902",
 			MetricsCertificate:    aExampleComCert,
 			MetricsCertificateKey: aExampleComKey,
@@ -63,11 +63,11 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
 							"name": "metrics",
 							"match": {
 								"prefix": "/"
 							},
 							"route": {
-								"cluster": "pomerium-control-plane-http"
+								"cluster": "pomerium-control-plane-metrics"
 							}
 						}]
 					}]
 				},
 				"statPrefix": "metrics"
@@ -106,11 +106,11 @@ func Test_buildMetricsHTTPConnectionManagerFilter(t *testing.T) {
 	}]
 }`, li)
 }
 
 func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
-	b := New("local-grpc", "local-http", nil, nil)
+	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
 
 	options := config.NewDefaultOptions()
 	options.SkipXffAppend = true
 	options.XffNumTrustedHops = 1
 	filter, err := b.buildMainHTTPConnectionManagerFilter(options, []string{"example.com"}, "*")
@@ -521,11 +521,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
 		}
 	}`, filter)
 }
 
 func Test_buildDownstreamTLSContext(t *testing.T) {
-	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
+	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
 
 	cacheDir, _ := os.UserCacheDir()
 	certFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-crt-354e49305a5a39414a545530374e58454e48334148524c4e324258463837364355564c4e4532464b54355139495547514a38.pem")
 	keyFileName := filepath.Join(cacheDir, "pomerium", "envoy", "files", "tls-key-3350415a38414e4e4a4655424e55393430474147324651433949384e485341334b5157364f424b4c5856365a545937383735.pem")
 
@@ -803,21 +803,21 @@ func Test_hostMatchesDomain(t *testing.T) {
 	assert.False(t, hostMatchesDomain(mustParseURL(t, "http://example.com:81"), "example.com"))
 	assert.False(t, hostMatchesDomain(mustParseURL(t, "http://example.com:81"), "example.com:80"))
 }
 
 func Test_buildRouteConfiguration(t *testing.T) {
-	b := New("local-grpc", "local-http", nil, nil)
+	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
 	virtualHosts := make([]*envoy_config_route_v3.VirtualHost, 10)
 	routeConfig, err := b.buildRouteConfiguration("test-route-configuration", virtualHosts)
 	require.NoError(t, err)
 	assert.Equal(t, "test-route-configuration", routeConfig.GetName())
 	assert.Equal(t, virtualHosts, routeConfig.GetVirtualHosts())
 	assert.False(t, routeConfig.GetValidateClusters().GetValue())
 }
 
 func Test_requireProxyProtocol(t *testing.T) {
-	b := New("local-grpc", "local-http", nil, nil)
+	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
 	t.Run("required", func(t *testing.T) {
 		li, err := b.buildMainListener(context.Background(), &config.Config{Options: &config.Options{
 			UseProxyProtocol: true,
 			InsecureServer:   true,
 		}})
diff --git a/config/envoyconfig/routes.go b/config/envoyconfig/routes.go
index 34793372..be702eb7 100644
--- a/config/envoyconfig/routes.go
+++ b/config/envoyconfig/routes.go
@@ -55,68 +55,36 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, domain string
 	isFrontingAuthenticate, err := isProxyFrontingAuthenticate(options, domain)
 	if err != nil {
 		return nil, err
 	}
 	if !isFrontingAuthenticate {
-		// enable ext_authz
-		r, err := b.buildControlPlanePathRoute("/.pomerium/jwt", true)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-
-		// disable ext_authz and passthrough to proxy handlers
-		r, err = b.buildControlPlanePathRoute("/ping", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-		r, err = b.buildControlPlanePathRoute("/healthz", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-		r, err = b.buildControlPlanePathRoute("/.pomerium", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-		r, err = b.buildControlPlanePrefixRoute("/.pomerium/", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-		r, err = b.buildControlPlanePathRoute("/.well-known/pomerium", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
-		r, err = b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
+		routes = append(routes,
+			// enable ext_authz
+			b.buildControlPlanePathRoute("/.pomerium/jwt", true),
+			// disable ext_authz and passthrough to proxy handlers
+			b.buildControlPlanePathRoute("/ping", false),
+			b.buildControlPlanePathRoute("/healthz", false),
+			b.buildControlPlanePathRoute("/.pomerium", false),
+			b.buildControlPlanePrefixRoute("/.pomerium/", false),
+			b.buildControlPlanePathRoute("/.well-known/pomerium", false),
+			b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false),
+		)
 		// per #837, only add robots.txt if there are no unauthenticated routes
 		if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) {
-			r, err := b.buildControlPlanePathRoute("/robots.txt", false)
-			if err != nil {
-				return nil, err
-			}
-			routes = append(routes, r)
+			routes = append(routes, b.buildControlPlanePathRoute("/robots.txt", false))
 		}
 	}
 	// if we're handling authentication, add the oauth2 callback url
 	authenticateURL, err := options.GetInternalAuthenticateURL()
 	if err != nil {
 		return nil, err
 	}
 	if config.IsAuthenticate(options.Services) && hostMatchesDomain(authenticateURL, domain) {
-		r, err := b.buildControlPlanePrefixRoute("/", false)
-		if err != nil {
-			return nil, err
-		}
-		routes = append(routes, r)
+		routes = append(routes,
+			b.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false),
+			b.buildControlPlanePathRoute("/", false),
+		)
 	}
 	// if we're the proxy and this is the forward-auth url
 	forwardAuthURL, err := options.GetForwardAuthURL()
 	if err != nil {
 		return nil, err
@@ -194,11 +162,11 @@ func (b *Builder) buildControlPlanePathAndQueryRoute(path string, queryparams []
 			"envoy.filters.http.ext_authz": disableExtAuthz,
 		},
 	}, nil
 }
 
-func (b *Builder) buildControlPlanePathRoute(path string, protected bool) (*envoy_config_route_v3.Route, error) {
+func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy_config_route_v3.Route {
 	r := &envoy_config_route_v3.Route{
 		Name: "pomerium-path-" + path,
 		Match: &envoy_config_route_v3.RouteMatch{
 			PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path},
 		},
@@ -213,14 +181,14 @@ func (b *Builder) buildControlPlanePathRoute(path string, protected bool) (*envo
 	if !protected {
 		r.TypedPerFilterConfig = map[string]*any.Any{
 			"envoy.filters.http.ext_authz": disableExtAuthz,
 		}
 	}
-	return r, nil
+	return r
 }
 
-func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) (*envoy_config_route_v3.Route, error) {
+func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *envoy_config_route_v3.Route {
 	r := &envoy_config_route_v3.Route{
 		Name: "pomerium-prefix-" + prefix,
 		Match: &envoy_config_route_v3.RouteMatch{
 			PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix},
 		},
@@ -235,11 +203,11 @@ func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) (*
 	if !protected {
 		r.TypedPerFilterConfig = map[string]*any.Any{
 			"envoy.filters.http.ext_authz": disableExtAuthz,
 		}
 	}
-	return r, nil
+	return r
 }
 
 // getClusterID returns a cluster ID
 var getClusterID = func(policy *config.Policy) string {
 	prefix := getClusterStatsName(policy)
diff --git a/config/envoyconfig/routes_test.go b/config/envoyconfig/routes_test.go
index 44985e14..3830ac66 100644
--- a/config/envoyconfig/routes_test.go
+++ b/config/envoyconfig/routes_test.go
@@ -94,11 +94,12 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 			`+routeString("path", "/.pomerium", false)+`,
 			`+routeString("prefix", "/.pomerium/", false)+`,
 			`+routeString("path", "/.well-known/pomerium", false)+`,
 			`+routeString("prefix", "/.well-known/pomerium/", false)+`,
 			`+routeString("path", "/robots.txt", false)+`,
-			`+routeString("prefix", "/", false)+`
+			`+routeString("path", "/oauth2/callback", false)+`,
+			`+routeString("path", "/", false)+`
 		]`, routes)
 	})
 	t.Run("proxy fronting authenticate", func(t *testing.T) {
 		options := &config.Options{
 			Services:                 "proxy",
@@ -165,12 +166,11 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 	})
 }
 
 func Test_buildControlPlanePathRoute(t *testing.T) {
 	b := &Builder{filemgr: filemgr.NewManager()}
-	route, err := b.buildControlPlanePathRoute("/hello/world", false)
-	require.NoError(t, err)
+	route := b.buildControlPlanePathRoute("/hello/world", false)
 	testutil.AssertProtoJSONEqual(t, `
 		{
 			"name": "pomerium-path-/hello/world",
 			"match": {
 				"path": "/hello/world"
@@ -188,12 +188,11 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
 	`, route)
 }
 
 func Test_buildControlPlanePrefixRoute(t *testing.T) {
 	b := &Builder{filemgr: filemgr.NewManager()}
-	route, err := b.buildControlPlanePrefixRoute("/hello/world/", false)
-	require.NoError(t, err)
+	route := b.buildControlPlanePrefixRoute("/hello/world/", false)
 	testutil.AssertProtoJSONEqual(t, `
 		{
 			"name": "pomerium-prefix-/hello/world/",
 			"match": {
 				"prefix": "/hello/world/"
diff --git a/config/metrics.go b/config/metrics.go
index fdf291a6..f671c2af 100644
--- a/config/metrics.go
+++ b/config/metrics.go
@@ -1,10 +1,11 @@
 package config
 
 import (
 	"context"
 	"net/http"
+	"net/url"
 	"os"
 	"sync"
 
 	"github.com/pomerium/pomerium/internal/log"
 	"github.com/pomerium/pomerium/internal/middleware"
@@ -14,16 +15,17 @@ import (
 	"github.com/rs/zerolog"
 )
 
 // A MetricsManager manages metrics for a given configuration.
 type MetricsManager struct {
-	mu             sync.RWMutex
-	installationID string
-	serviceName    string
-	addr           string
-	basicAuth      string
-	handler        http.Handler
+	mu                sync.RWMutex
+	installationID    string
+	serviceName       string
+	addr              string
+	basicAuth         string
+	envoyAdminAddress string
+	handler           http.Handler
 }
 
 // NewMetricsManager creates a new MetricsManager.
 func NewMetricsManager(ctx context.Context, src Source) *MetricsManager {
 	ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
@@ -44,12 +46,12 @@ func (mgr *MetricsManager) Close() error {
 // OnConfigChange updates the metrics manager when configuration is changed.
 func (mgr *MetricsManager) OnConfigChange(ctx context.Context, cfg *Config) {
 	mgr.mu.Lock()
 	defer mgr.mu.Unlock()
 
-	mgr.updateInfo(cfg)
-	mgr.updateServer(cfg)
+	mgr.updateInfo(ctx, cfg)
+	mgr.updateServer(ctx, cfg)
 }
 
 func (mgr *MetricsManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	mgr.mu.RLock()
 	defer mgr.mu.RUnlock()
@@ -59,46 +61,54 @@ func (mgr *MetricsManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	mgr.handler.ServeHTTP(w, r)
 }
 
-func (mgr *MetricsManager) updateInfo(cfg *Config) {
+func (mgr *MetricsManager) updateInfo(ctx context.Context, cfg *Config) {
 	serviceName := telemetry.ServiceName(cfg.Options.Services)
 	if serviceName == mgr.serviceName {
 		return
 	}
 
 	hostname, err := os.Hostname()
 	if err != nil {
-		log.Error(context.TODO()).Err(err).Msg("telemetry/metrics: failed to get OS hostname")
+		log.Error(ctx).Err(err).Msg("telemetry/metrics: failed to get OS hostname")
 		hostname = "__unknown__"
 	}
 
 	metrics.SetBuildInfo(serviceName, hostname, cfg.EnvoyVersion)
 	mgr.serviceName = serviceName
 }
 
-func (mgr *MetricsManager) updateServer(cfg *Config) {
+func (mgr *MetricsManager) updateServer(ctx context.Context, cfg *Config) {
 	if cfg.Options.MetricsAddr == mgr.addr &&
 		cfg.Options.MetricsBasicAuth == mgr.basicAuth &&
-		cfg.Options.InstallationID == mgr.installationID {
+		cfg.Options.InstallationID == mgr.installationID &&
+		cfg.Options.EnvoyAdminAddress == mgr.envoyAdminAddress {
 		return
 	}
 
 	mgr.addr = cfg.Options.MetricsAddr
 	mgr.basicAuth = cfg.Options.MetricsBasicAuth
 	mgr.installationID = cfg.Options.InstallationID
+	mgr.envoyAdminAddress = cfg.Options.EnvoyAdminAddress
 	mgr.handler = nil
 
 	if mgr.addr == "" {
-		log.Info(context.TODO()).Msg("metrics: http server disabled")
+		log.Info(ctx).Msg("metrics: http server disabled")
 		return
 	}
 
-	handler, err := metrics.PrometheusHandler(EnvoyAdminURL, mgr.installationID)
+	envoyURL, err := url.Parse("http://" + cfg.Options.EnvoyAdminAddress)
 	if err != nil {
-		log.Error(context.TODO()).Err(err).Msg("metrics: failed to create prometheus handler")
+		log.Error(ctx).Err(err).Msg("metrics: invalid envoy admin address, disabling")
+		return
+	}
+
+	handler, err := metrics.PrometheusHandler(envoyURL, mgr.installationID)
+	if err != nil {
+		log.Error(ctx).Err(err).Msg("metrics: failed to create prometheus handler")
 		return
 	}
 
 	if username, password, ok := cfg.Options.GetMetricsBasicAuth(); ok {
 		handler = middleware.RequireBasicAuth(username, password)(handler)
diff --git a/config/options.go b/config/options.go
index 9d855844..8fb7ea92 100644
--- a/config/options.go
+++ b/config/options.go
@@ -47,13 +47,10 @@ const (
 // DefaultAlternativeAddr is the address used is two services are competing over
 // the same listener. Typically this is invisible to the end user (e.g. localhost)
 // gRPC server, or is used for healthchecks (authorize only service)
 const DefaultAlternativeAddr = ":5443"
 
-// EnvoyAdminURL indicates where the envoy control plane is listening
-var EnvoyAdminURL = &url.URL{Host: "127.0.0.1:9901", Scheme: "http"}
-
 // The randomSharedKey is used if no shared key is supplied in all-in-one mode.
 var randomSharedKey = cryptutil.NewBase64Key()
 
 // Options are the global environmental flags used to set up pomerium's services.
 // Use NewXXXOptions() methods for a safely initialized data structure.
diff --git a/internal/cmd/pomerium/pomerium.go b/internal/cmd/pomerium/pomerium.go
index 851921b2..3662d50f 100644
--- a/internal/cmd/pomerium/pomerium.go
+++ b/internal/cmd/pomerium/pomerium.go
@@ -84,10 +84,12 @@ func Run(ctx context.Context, configFile string) error {
 
 	log.Info(ctx).
 		Str("grpc-port", src.GetConfig().GRPCPort).
 		Str("http-port", src.GetConfig().HTTPPort).
 		Str("outbound-port", src.GetConfig().OutboundPort).
+		Str("metrics-port", src.GetConfig().MetricsPort).
+		Str("debug-port", src.GetConfig().DebugPort).
 		Msg("server started")
 
 	// create envoy server
 	envoyServer, err := envoy.NewServer(ctx, src, controlPlane.Builder)
 	if err != nil {
diff --git a/internal/controlplane/http.go b/internal/controlplane/http.go
index cbb8c36b..e76cbba2 100644
--- a/internal/controlplane/http.go
+++ b/internal/controlplane/http.go
@@ -47,14 +47,14 @@ func (srv *Server) addHTTPMiddleware() {
 	}, srv.name))
 	root.HandleFunc("/healthz", httputil.HealthCheck)
 	root.HandleFunc("/ping", httputil.HealthCheck)
 
 	// pprof
-	root.Path("/debug/pprof/cmdline").HandlerFunc(pprof.Cmdline)
-	root.Path("/debug/pprof/profile").HandlerFunc(pprof.Profile)
-	root.Path("/debug/pprof/symbol").HandlerFunc(pprof.Symbol)
-	root.Path("/debug/pprof/trace").HandlerFunc(pprof.Trace)
-	root.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
+	srv.DebugRouter.Path("/debug/pprof/cmdline").HandlerFunc(pprof.Cmdline)
+	srv.DebugRouter.Path("/debug/pprof/profile").HandlerFunc(pprof.Profile)
+	srv.DebugRouter.Path("/debug/pprof/symbol").HandlerFunc(pprof.Symbol)
+	srv.DebugRouter.Path("/debug/pprof/trace").HandlerFunc(pprof.Trace)
+	srv.DebugRouter.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
 
 	// metrics
-	root.Handle("/metrics", srv.metricsMgr)
+	srv.MetricsRouter.Handle("/metrics", srv.metricsMgr)
 }
diff --git a/internal/controlplane/server.go b/internal/controlplane/server.go
index 9db363ad..ed056ebc 100644
--- a/internal/controlplane/server.go
+++ b/internal/controlplane/server.go
@@ -48,15 +48,19 @@ func (avo *atomicVersionedConfig) Store(cfg versionedConfig) {
 	avo.value.Store(cfg)
 }
 
 // A Server is the control-plane gRPC and HTTP servers.
 type Server struct {
-	GRPCListener net.Listener
-	GRPCServer   *grpc.Server
-	HTTPListener net.Listener
-	HTTPRouter   *mux.Router
-	Builder      *envoyconfig.Builder
+	GRPCListener    net.Listener
+	GRPCServer      *grpc.Server
+	HTTPListener    net.Listener
+	HTTPRouter      *mux.Router
+	MetricsListener net.Listener
+	MetricsRouter   *mux.Router
+	DebugListener   net.Listener
+	DebugRouter     *mux.Router
+	Builder         *envoyconfig.Builder
 
 	currentConfig atomicVersionedConfig
 	name          string
 	xdsmgr        *xdsmgr.Manager
 	filemgr       *filemgr.Manager
@@ -104,19 +108,38 @@ func NewServer(cfg *config.Config, metricsMgr *config.MetricsManager) (*Server,
 	srv.HTTPListener, err = net.Listen("tcp4", net.JoinHostPort("127.0.0.1", cfg.HTTPPort))
 	if err != nil {
 		_ = srv.GRPCListener.Close()
 		return nil, err
 	}
+
+	srv.MetricsListener, err = net.Listen("tcp4", net.JoinHostPort("127.0.0.1", cfg.MetricsPort))
+	if err != nil {
+		_ = srv.GRPCListener.Close()
+		_ = srv.HTTPListener.Close()
+		return nil, err
+	}
+
+	srv.DebugListener, err = net.Listen("tcp4", net.JoinHostPort("127.0.0.1", cfg.DebugPort))
+	if err != nil {
+		_ = srv.GRPCListener.Close()
+		_ = srv.HTTPListener.Close()
+		_ = srv.DebugListener.Close()
+		return nil, err
+	}
+
 	srv.HTTPRouter = mux.NewRouter()
+	srv.DebugRouter = mux.NewRouter()
+	srv.MetricsRouter = mux.NewRouter()
 	srv.addHTTPMiddleware()
 
 	srv.filemgr = filemgr.NewManager()
 	srv.filemgr.ClearCache()
 
 	srv.Builder = envoyconfig.New(
 		srv.GRPCListener.Addr().String(),
 		srv.HTTPListener.Addr().String(),
+		srv.MetricsListener.Addr().String(),
 		srv.filemgr,
 		srv.reproxy,
 	)
 
 	ctx := log.WithContext(context.Background(), func(c zerolog.Context) zerolog.Context {
@@ -173,32 +196,45 @@ func (srv *Server) Run(ctx context.Context) error {
 		<-ctx.Done()
 
 		return nil
 	})
 
-	hsrv := (&http.Server{
-		BaseContext: func(li net.Listener) context.Context {
-			return ctx
-		},
-		Handler: srv.HTTPRouter,
-	})
+	for _, entry := range []struct {
+		Name     string
+		Listener net.Listener
+		Handler  *mux.Router
+	}{
+		{"http", srv.HTTPListener, srv.HTTPRouter},
+		{"debug", srv.DebugListener, srv.DebugRouter},
+		{"metrics", srv.MetricsListener, srv.MetricsRouter},
+	} {
+		entry := entry
+		hsrv := (&http.Server{
+			BaseContext: func(li net.Listener) context.Context {
+				return ctx
+			},
+			Handler: entry.Handler,
+		})
 
-	// start the HTTP server
-	eg.Go(func() error {
-		log.Info(ctx).Str("addr", srv.HTTPListener.Addr().String()).Msg("starting control-plane HTTP server")
-		return hsrv.Serve(srv.HTTPListener)
-	})
+		// start the HTTP server
+		eg.Go(func() error {
+			log.Info(ctx).
+				Str("addr", entry.Listener.Addr().String()).
+				Msgf("starting control-plane %s server", entry.Name)
+			return hsrv.Serve(entry.Listener)
+		})
 
-	// gracefully stop the HTTP server on context cancellation
-	eg.Go(func() error {
-		<-ctx.Done()
+		// gracefully stop the HTTP server on context cancellation
+		eg.Go(func() error {
+			<-ctx.Done()
 
-		ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
-		defer cleanup()
+			ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
+			defer cleanup()
 
-		return hsrv.Shutdown(ctx)
-	})
+			return hsrv.Shutdown(ctx)
+		})
+	}
 
 	return eg.Wait()
 }
 
 // OnConfigChange updates the pomerium config options.
diff --git a/internal/registry/reporter.go b/internal/registry/reporter.go
index 3121f61b..7168f8b8 100644
--- a/internal/registry/reporter.go
+++ b/internal/registry/reporter.go
@@ -64,10 +64,14 @@ func (r *Reporter) OnConfigChange(ctx context.Context, cfg *config.Config) {
 		r.cancel = cancel
 	}
 }
 
 func getReportedServices(cfg *config.Config) ([]*pb.Service, error) {
+	if cfg.Options.MetricsAddr == "" {
+		return nil, nil
+	}
+
 	mu, err := metricsURL(*cfg.Options)
 	if err != nil {
 		return nil, err
 	}
 

Vulnerable code: 	ports, err := netutil.AllocatePorts(3)
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
	b := New("localhost:1111", "localhost:2222", filemgr.NewManager(), nil)
		b := New("localhost:1111", "localhost:2222", filemgr.NewManager(), nil)
		b := New("xyz:zyx", "localhost:2222", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
	localGRPCAddress string
	localHTTPAddress string
	filemgr          *filemgr.Manager
	reproxy          *reproxy.Handler
		localGRPCAddress: localGRPCAddress,
		localHTTPAddress: localHTTPAddress,
		filemgr:          fileManager,
		reproxy:          reproxyHandler,
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
						Cluster: "pomerium-control-plane-http",
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
								"cluster": "pomerium-control-plane-http"
	b := New("local-grpc", "local-http", nil, nil)
	b := New("local-grpc", "local-http", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", nil, nil)
	b := New("local-grpc", "local-http", nil, nil)
		// enable ext_authz
		r, err := b.buildControlPlanePathRoute("/.pomerium/jwt", true)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)

		// disable ext_authz and passthrough to proxy handlers
		r, err = b.buildControlPlanePathRoute("/ping", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
		r, err = b.buildControlPlanePathRoute("/healthz", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
		r, err = b.buildControlPlanePathRoute("/.pomerium", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
		r, err = b.buildControlPlanePrefixRoute("/.pomerium/", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
		r, err = b.buildControlPlanePathRoute("/.well-known/pomerium", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
		r, err = b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
			r, err := b.buildControlPlanePathRoute("/robots.txt", false)
			if err != nil {
				return nil, err
			}
			routes = append(routes, r)
		r, err := b.buildControlPlanePrefixRoute("/", false)
		if err != nil {
			return nil, err
		}
		routes = append(routes, r)
func (b *Builder) buildControlPlanePathRoute(path string, protected bool) (*envoy_config_route_v3.Route, error) {
	return r, nil
func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) (*envoy_config_route_v3.Route, error) {
	return r, nil
			`+routeString("prefix", "/", false)+`
	route, err := b.buildControlPlanePathRoute("/hello/world", false)
	require.NoError(t, err)
	route, err := b.buildControlPlanePrefixRoute("/hello/world/", false)
	require.NoError(t, err)
	mu             sync.RWMutex
	installationID string
	serviceName    string
	addr           string
	basicAuth      string
	handler        http.Handler
	mgr.updateInfo(cfg)
	mgr.updateServer(cfg)
func (mgr *MetricsManager) updateInfo(cfg *Config) {
		log.Error(context.TODO()).Err(err).Msg("telemetry/metrics: failed to get OS hostname")
func (mgr *MetricsManager) updateServer(cfg *Config) {
		cfg.Options.InstallationID == mgr.installationID {
		log.Info(context.TODO()).Msg("metrics: http server disabled")
	handler, err := metrics.PrometheusHandler(EnvoyAdminURL, mgr.installationID)
		log.Error(context.TODO()).Err(err).Msg("metrics: failed to create prometheus handler")
// EnvoyAdminURL indicates where the envoy control plane is listening
var EnvoyAdminURL = &url.URL{Host: "127.0.0.1:9901", Scheme: "http"}

	root.Path("/debug/pprof/cmdline").HandlerFunc(pprof.Cmdline)
	root.Path("/debug/pprof/profile").HandlerFunc(pprof.Profile)
	root.Path("/debug/pprof/symbol").HandlerFunc(pprof.Symbol)
	root.Path("/debug/pprof/trace").HandlerFunc(pprof.Trace)
	root.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
	root.Handle("/metrics", srv.metricsMgr)
	GRPCListener net.Listener
	GRPCServer   *grpc.Server
	HTTPListener net.Listener
	HTTPRouter   *mux.Router
	Builder      *envoyconfig.Builder
	hsrv := (&http.Server{
		BaseContext: func(li net.Listener) context.Context {
			return ctx
		},
		Handler: srv.HTTPRouter,
	})
	// start the HTTP server
	eg.Go(func() error {
		log.Info(ctx).Str("addr", srv.HTTPListener.Addr().String()).Msg("starting control-plane HTTP server")
		return hsrv.Serve(srv.HTTPListener)
	})
	// gracefully stop the HTTP server on context cancellation
	eg.Go(func() error {
		<-ctx.Done()
		ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
		defer cleanup()
		return hsrv.Shutdown(ctx)
	})
Fixed code: 	// MetricsPort is the port the metrics listener is running on.
	MetricsPort string
	// DebugPort is the port the debug listener is running on.
	DebugPort string
		MetricsPort:  cfg.MetricsPort,
		DebugPort:    cfg.DebugPort,
	ports, err := netutil.AllocatePorts(5)
	metricsPort := ports[3]
	debugPort := ports[4]
		MetricsPort:  metricsPort,
		DebugPort:    debugPort,
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
	b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
		b := New("localhost:1111", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
		b := New("xyz:zyx", "localhost:2222", "localhost:3333", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
	localGRPCAddress    string
	localHTTPAddress    string
	localMetricsAddress string
	filemgr             *filemgr.Manager
	reproxy             *reproxy.Handler
	localMetricsAddress string,
		localGRPCAddress:    localGRPCAddress,
		localHTTPAddress:    localHTTPAddress,
		localMetricsAddress: localMetricsAddress,
		filemgr:             fileManager,
		reproxy:             reproxyHandler,
	metricsURL := &url.URL{
		Scheme: "http",
		Host:   b.localMetricsAddress,
	}
	controlMetrics, err := b.buildInternalCluster(ctx, cfg.Options, "pomerium-control-plane-metrics", []*url.URL{metricsURL}, upstreamProtocolAuto)
	if err != nil {
		return nil, err
	}

		controlMetrics,
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
						Cluster: "pomerium-control-plane-metrics",
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
								"cluster": "pomerium-control-plane-metrics"
	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
	b := New("local-grpc", "local-http", "local-metrics", filemgr.NewManager(), nil)
	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
	b := New("local-grpc", "local-http", "local-metrics", nil, nil)
		routes = append(routes,
			// enable ext_authz
			b.buildControlPlanePathRoute("/.pomerium/jwt", true),
			// disable ext_authz and passthrough to proxy handlers
			b.buildControlPlanePathRoute("/ping", false),
			b.buildControlPlanePathRoute("/healthz", false),
			b.buildControlPlanePathRoute("/.pomerium", false),
			b.buildControlPlanePrefixRoute("/.pomerium/", false),
			b.buildControlPlanePathRoute("/.well-known/pomerium", false),
			b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false),
		)
			routes = append(routes, b.buildControlPlanePathRoute("/robots.txt", false))
		routes = append(routes,
			b.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false),
			b.buildControlPlanePathRoute("/", false),
		)
func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy_config_route_v3.Route {
	return r
func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *envoy_config_route_v3.Route {
	return r
			`+routeString("path", "/oauth2/callback", false)+`,
			`+routeString("path", "/", false)+`
	route := b.buildControlPlanePathRoute("/hello/world", false)
	route := b.buildControlPlanePrefixRoute("/hello/world/", false)
	"net/url"
	mu                sync.RWMutex
	installationID    string
	serviceName       string
	addr              string
	basicAuth         string
	envoyAdminAddress string
	handler           http.Handler
	mgr.updateInfo(ctx, cfg)
	mgr.updateServer(ctx, cfg)
func (mgr *MetricsManager) updateInfo(ctx context.Context, cfg *Config) {
		log.Error(ctx).Err(err).Msg("telemetry/metrics: failed to get OS hostname")
func (mgr *MetricsManager) updateServer(ctx context.Context, cfg *Config) {
		cfg.Options.InstallationID == mgr.installationID &&
		cfg.Options.EnvoyAdminAddress == mgr.envoyAdminAddress {
	mgr.envoyAdminAddress = cfg.Options.EnvoyAdminAddress
		log.Info(ctx).Msg("metrics: http server disabled")
	envoyURL, err := url.Parse("http://" + cfg.Options.EnvoyAdminAddress)
		log.Error(ctx).Err(err).Msg("metrics: invalid envoy admin address, disabling")
		return
	}

	handler, err := metrics.PrometheusHandler(envoyURL, mgr.installationID)
	if err != nil {
		log.Error(ctx).Err(err).Msg("metrics: failed to create prometheus handler")
		Str("metrics-port", src.GetConfig().MetricsPort).
		Str("debug-port", src.GetConfig().DebugPort).
	srv.DebugRouter.Path("/debug/pprof/cmdline").HandlerFunc(pprof.Cmdline)
	srv.DebugRouter.Path("/debug/pprof/profile").HandlerFunc(pprof.Profile)
	srv.DebugRouter.Path("/debug/pprof/symbol").HandlerFunc(pprof.Symbol)
	srv.DebugRouter.Path("/debug/pprof/trace").HandlerFunc(pprof.Trace)
	srv.DebugRouter.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
	srv.MetricsRouter.Handle("/metrics", srv.metricsMgr)
	GRPCListener    net.Listener
	GRPCServer      *grpc.Server
	HTTPListener    net.Listener
	HTTPRouter      *mux.Router
	MetricsListener net.Listener
	MetricsRouter   *mux.Router
	DebugListener   net.Listener
	DebugRouter     *mux.Router
	Builder         *envoyconfig.Builder

	srv.MetricsListener, err = net.Listen("tcp4", net.JoinHostPort("127.0.0.1", cfg.MetricsPort))
	if err != nil {
		_ = srv.GRPCListener.Close()
		_ = srv.HTTPListener.Close()
		return nil, err
	}

	srv.DebugListener, err = net.Listen("tcp4", net.JoinHostPort("127.0.0.1", cfg.DebugPort))
	if err != nil {
		_ = srv.GRPCListener.Close()
		_ = srv.HTTPListener.Close()
		_ = srv.DebugListener.Close()
		return nil, err
	}

	srv.DebugRouter = mux.NewRouter()
	srv.MetricsRouter = mux.NewRouter()
		srv.MetricsListener.Addr().String(),
	for _, entry := range []struct {
		Name     string
		Listener net.Listener
		Handler  *mux.Router
	}{
		{"http", srv.HTTPListener, srv.HTTPRouter},
		{"debug", srv.DebugListener, srv.DebugRouter},
		{"metrics", srv.MetricsListener, srv.MetricsRouter},
	} {
		entry := entry
		hsrv := (&http.Server{
			BaseContext: func(li net.Listener) context.Context {
				return ctx
			},
			Handler: entry.Handler,
		})
		// start the HTTP server
		eg.Go(func() error {
			log.Info(ctx).
				Str("addr", entry.Listener.Addr().String()).
				Msgf("starting control-plane %s server", entry.Name)
			return hsrv.Serve(entry.Listener)
		})
		// gracefully stop the HTTP server on context cancellation
		eg.Go(func() error {
			<-ctx.Done()
			ctx, cleanup := context.WithTimeout(ctx, time.Second*5)
			defer cleanup()
			return hsrv.Shutdown(ctx)
		})
	}
	if cfg.Options.MetricsAddr == "" {
		return nil, nil
	}