Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/24044.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
xds: Add the opt-in proxy config option `envoy_disable_xfcc_source_metadata`. When set to `true` for an HTTP service, the sidecar public listener no longer adds the `header_to_metadata` filter that runs regexes over the `x-forwarded-client-cert` header to fill the `consul.*` source metadata, which lowers sidecar CPU per request. The XFCC header is still built and forwarded, so peered intentions are unaffected. Do not set it on services that use extensions or access logs reading `consul.*` metadata. A value that is not a boolean is ignored. To override a `true` from proxy-defaults on one service, use the string `"false"`; a boolean `false` is treated as unset by the config merge.
```
20 changes: 20 additions & 0 deletions agent/xds/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package config

import (
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -130,6 +131,19 @@ type ProxyConfig struct {
// sent from downstream clients to upstream services. If not set, uses Envoy's default.
MaxRequestHeadersKB *uint32 `json:",omitempty" mapstructure:"max_request_headers_kb"`

// DisableXFCCSourceMetadata stops the public listener of an HTTP service from adding the
// header_to_metadata filter that copies the caller's identity out of the
// x-forwarded-client-cert header into the consul.* dynamic metadata (trust-domain,
// partition, namespace, datacenter, service). The XFCC header is still built and
// forwarded, so RBAC for peered callers is unaffected. Extensions and access logs that
// read consul.* metadata see no values while this is set.
//
// It is decoded as a string and read through XFCCSourceMetadataDisabled so that a
// mistyped value cannot fail the decode of the whole proxy config, which would drop the
// service back to the tcp protocol. Anything strconv.ParseBool does not accept counts as
// false.
DisableXFCCSourceMetadata string `mapstructure:"envoy_disable_xfcc_source_metadata"`

// SuppressEnvoyHeaders controls whether Envoy rewrites the "server" response
// header to "envoy" on all HTTP responses. When set to true, the
// HttpConnectionManager ServerHeaderTransformation is set to PASS_THROUGH,
Expand All @@ -152,6 +166,12 @@ type ProxyConfig struct {
EnvoyServerHeaderName string `mapstructure:"envoy_server_header_name"`
}

// XFCCSourceMetadataDisabled reports whether the consul.* source metadata filter is turned off.
func (c *ProxyConfig) XFCCSourceMetadataDisabled() bool {
v, err := strconv.ParseBool(strings.TrimSpace(c.DisableXFCCSourceMetadata))
return err == nil && v
}

// ParseProxyConfig returns the ProxyConfig parsed from the an opaque map. If an
// error occurs during parsing it is returned along with the default config this
// allows caller to choose whether and how to report the error.
Expand Down
51 changes: 51 additions & 0 deletions agent/xds/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ func TestParseProxyConfig(t *testing.T) {
LocalConnectTimeoutMs: 5000,
},
},
{
name: "disable xfcc source metadata, bool",
input: map[string]interface{}{
"envoy_disable_xfcc_source_metadata": true,
},
want: ProxyConfig{
Protocol: "tcp",
LocalConnectTimeoutMs: 5000,
DisableXFCCSourceMetadata: "1",
},
},
{
name: "disable xfcc source metadata, string",
input: map[string]interface{}{
"envoy_disable_xfcc_source_metadata": "true",
},
want: ProxyConfig{
Protocol: "tcp",
LocalConnectTimeoutMs: 5000,
DisableXFCCSourceMetadata: "true",
},
},
{
name: "protocol override",
input: map[string]interface{}{
Expand Down Expand Up @@ -441,3 +463,32 @@ func Test_ProxyConfig_GetXDSFetchTimeout(t *testing.T) {
})
}
}

func TestProxyConfig_XFCCSourceMetadataDisabled(t *testing.T) {
cases := []struct {
name string
input map[string]interface{}
want bool
}{
{"unset", map[string]interface{}{}, false},
{"bool true", map[string]interface{}{"envoy_disable_xfcc_source_metadata": true}, true},
{"bool false", map[string]interface{}{"envoy_disable_xfcc_source_metadata": false}, false},
{"string true", map[string]interface{}{"envoy_disable_xfcc_source_metadata": "true"}, true},
{"string TRUE", map[string]interface{}{"envoy_disable_xfcc_source_metadata": "TRUE"}, true},
{"string false", map[string]interface{}{"envoy_disable_xfcc_source_metadata": "false"}, false},
{"number 1", map[string]interface{}{"envoy_disable_xfcc_source_metadata": 1}, true},
{"junk string", map[string]interface{}{"envoy_disable_xfcc_source_metadata": "maybe"}, false},
// A bad value must never fail the decode of the whole config: the protocol stays http.
{"junk does not drop protocol", map[string]interface{}{"protocol": "http", "envoy_disable_xfcc_source_metadata": "maybe"}, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := ParseProxyConfig(tc.input)
require.NoError(t, err)
require.Equal(t, tc.want, got.XFCCSourceMetadataDisabled())
if p, ok := tc.input["protocol"]; ok {
require.Equal(t, p, got.Protocol)
}
})
}
}
13 changes: 8 additions & 5 deletions agent/xds/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -1690,12 +1690,15 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
filterOpts.stripForwardClientCertHeader = true
filterOpts.forwardClientPolicy = envoy_http_v3.HttpConnectionManager_APPEND_FORWARD

addMeta, err := parseXFCCToDynamicMetaHTTPFilter()
if err != nil {
return nil, err
// The consul.* source metadata costs five regexes over the whole XFCC header per
// request. Services that opt out keep XFCC (peered RBAC reads it directly).
if !cfg.XFCCSourceMetadataDisabled() {
addMeta, err := parseXFCCToDynamicMetaHTTPFilter()
if err != nil {
return nil, err
}
filterOpts.httpAuthzFilters = append(filterOpts.httpAuthzFilters, addMeta)
}
filterOpts.httpAuthzFilters = append(filterOpts.httpAuthzFilters, addMeta)

}
setNormalizationOptions(cfgSnap.MeshConfig().GetHTTPIncomingRequestNormalization(), &filterOpts)
}
Expand Down
64 changes: 64 additions & 0 deletions agent/xds/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,41 @@ func TestAllResourcesFromSnapshot(t *testing.T) {
})
},
},
{
// No header_to_metadata filter; XFCC is still built and forwarded.
name: "http-public-listener-disable-xfcc-source-metadata",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
ns.Proxy.Config["protocol"] = "http"
ns.Proxy.Config["envoy_disable_xfcc_source_metadata"] = true
}, nil)
},
},
{
// With peering the RBAC XFCC match must stay exactly as it is without the option.
name: "http-public-listener-disable-xfcc-source-metadata-with-peer-trust-bundles",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
ns.Proxy.Config["protocol"] = "http"
ns.Proxy.Config["envoy_disable_xfcc_source_metadata"] = true
}, []proxycfg.UpdateEvent{{CorrelationID: "peering-trust-bundles", Result: proxycfg.TestPeerTrustBundles(t)}})
Comment thread
Copilot marked this conversation as resolved.
Resolved
},
},
{
// Baseline for the case below: peered L7 intention, XFCC source metadata enabled.
name: "http-public-listener-peered-l7-intention",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return testSnapshotHTTPPeeredL7Intention(t, false)
},
},
{
// Same as above with the option set: the RBAC XFCC match must be identical and only
// the header_to_metadata filter may disappear.
name: "http-public-listener-peered-l7-intention-disable-xfcc-source-metadata",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
return testSnapshotHTTPPeeredL7Intention(t, true)
},
},
{
name: "http-listener-with-timeouts",
create: func(t testinf.T) *proxycfg.ConfigSnapshot {
Expand Down Expand Up @@ -3867,3 +3902,32 @@ func getXDSFetchTimeoutTestCases() []goldenTestCase {
},
}
}

// testSnapshotHTTPPeeredL7Intention returns an HTTP sidecar snapshot with peer trust bundles and an
// L7 intention from a peer, so the public listener RBAC filter matches on the XFCC header.
func testSnapshotHTTPPeeredL7Intention(t testinf.T, disableXFCCSourceMetadata bool) *proxycfg.ConfigSnapshot {
bundles := proxycfg.TestPeerTrustBundles(t)
return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) {
ns.Proxy.Config["protocol"] = "http"
if disableXFCCSourceMetadata {
ns.Proxy.Config["envoy_disable_xfcc_source_metadata"] = true
}
}, []proxycfg.UpdateEvent{
{CorrelationID: "peering-trust-bundles", Result: bundles},
{
CorrelationID: "intentions",
Result: structs.SimplifiedIntentions{
{
SourceName: "source",
SourcePeer: bundles.Bundles[0].PeerName,
DestinationName: "web",
Action: structs.IntentionActionAllow,
Permissions: []*structs.IntentionPermission{{
Action: structs.IntentionActionAllow,
HTTP: &structs.IntentionHTTPPermission{PathPrefix: "/api"},
}},
},
},
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"nonce": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"circuitBreakers": {},
"commonLbConfig": {
"healthyPanicThreshold": {}
},
"connectTimeout": "5s",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"outlierDetection": {},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"tlsParams": {},
"validationContext": {
"matchTypedSubjectAltNames": [
{
"matcher": {
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db"
},
"sanType": "URI"
}
],
"trustedCa": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
}
}
},
"sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
},
"type": "EDS"
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"circuitBreakers": {},
"connectTimeout": "5s",
"edsClusterConfig": {
"edsConfig": {
"ads": {},
"resourceApiVersion": "V3"
}
},
"name": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul",
"outlierDetection": {},
"transportSocket": {
"name": "tls",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"commonTlsContext": {
"tlsCertificates": [
{
"certificateChain": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
},
"privateKey": {
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
}
}
],
"tlsParams": {},
"validationContext": {
"matchTypedSubjectAltNames": [
{
"matcher": {
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/geo-cache-target"
},
"sanType": "URI"
},
{
"matcher": {
"exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc2/svc/geo-cache-target"
},
"sanType": "URI"
}
],
"trustedCa": {
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
}
}
},
"sni": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
}
},
"type": "EDS"
},
{
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "local_app",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "127.0.0.1",
"portValue": 8080
}
}
}
}
]
}
]
},
"name": "local_app",
"type": "STATIC"
}
],
"typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"versionInfo": "00000001"
}
Loading
Loading