VYPR
High severityNVD Advisory· Published Jun 13, 2024· Updated Aug 19, 2024

Cilium leaks sensitive information in cilium-bugtool

CVE-2024-37307

Description

Cilium is a networking, observability, and security solution with an eBPF-based dataplane. Starting in version 1.13.0 and prior to versions 1.13.7, 1.14.12, and 1.15.6, the output of cilium-bugtool can contain sensitive data when the tool is run (with the --envoy-dump flag set) against Cilium deployments with the Envoy proxy enabled. Users of the TLS inspection, Ingress with TLS termination, Gateway API with TLS termination, and Kafka network policies with API key filtering features are affected. The sensitive data includes the CA certificate, certificate chain, and private key used by Cilium HTTP Network Policies, and when using Ingress/Gateway API and the API keys used in Kafka-related network policy. cilium-bugtool is a debugging tool that is typically invoked manually and does not run during the normal operation of a Cilium cluster. This issue has been patched in Cilium v1.15.6, v1.14.12, and v1.13.17. There is no workaround to this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/cilium/ciliumGo
>= 1.13.0, < 1.13.171.13.17
github.com/cilium/ciliumGo
>= 1.14.0, < 1.14.121.14.12
github.com/cilium/ciliumGo
>= 1.15.0, < 1.15.61.15.6

Affected products

1

Patches

6
958d7b77274b

bugtool: Add json masking function

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
3 files changed · +258 0
  • bugtool/cmd/mask.go+64 0 added
    @@ -0,0 +1,64 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"encoding/json"
    +)
    +
    +const (
    +	redacted = "[redacted]"
    +	ident    = "\t"
    +)
    +
    +// jsonFieldMaskPostProcess returns a postProcessFunc that masks the specified field names.
    +// The input byte slice is expected to be a JSON object.
    +func jsonFieldMaskPostProcess(fieldNames []string) postProcessFunc {
    +	return func(b []byte) ([]byte, error) {
    +		return maskFields(b, fieldNames)
    +	}
    +}
    +
    +func maskFields(b []byte, fieldNames []string) ([]byte, error) {
    +	var data map[string]interface{}
    +
    +	if err := json.Unmarshal(b, &data); err != nil {
    +		return nil, err
    +	}
    +
    +	mask(data, fieldNames)
    +
    +	// MarshalIndent is used to make the output more readable.
    +	return json.MarshalIndent(data, "", ident)
    +}
    +
    +func contains(names []string, name string) bool {
    +	for _, n := range names {
    +		if n == name {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +func mask(data map[string]interface{}, fieldNames []string) {
    +	for k, v := range data {
    +		if contains(fieldNames, k) {
    +			data[k] = redacted
    +			continue
    +		}
    +
    +		switch t := v.(type) {
    +		case map[string]interface{}:
    +			mask(t, fieldNames)
    +		case []interface{}:
    +			for i, item := range t {
    +				if subData, ok := item.(map[string]interface{}); ok {
    +					mask(subData, fieldNames)
    +					t[i] = subData
    +				}
    +			}
    +		}
    +	}
    +}
    
  • bugtool/cmd/mask_test.go+192 0 added
    @@ -0,0 +1,192 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"testing"
    +
    +	"github.com/stretchr/testify/require"
    +)
    +
    +func Test_jsonFieldMaskPostProcess(t *testing.T) {
    +	type args struct {
    +		input      []byte
    +		fieldNames []string
    +	}
    +	tests := []struct {
    +		name    string
    +		args    args
    +		want    []byte
    +		wantErr bool
    +	}{
    +		{
    +			name: "simple struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "mypassword"
    +						},
    +						{
    +							"password": "anotherone"
    +						}
    +					],
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					],
    +					"password": "[redacted]",
    +					"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "nested struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"password": "[redacted]"
    +				}
    +			}`),
    +		},
    +		{
    +			name: "nested array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"secrets": [
    +							{
    +								"password": "mypassword"
    +							},
    +							{
    +								"password": "anotherpassword"
    +							}
    +						]
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					]
    +				}
    +			}`),
    +		},
    +		{
    +			name: "no masked field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"no-such-field"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +		},
    +		{
    +			name: "mask object field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"complex"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": "[redacted]"
    +				}`),
    +		},
    +		{
    +			name: "invalid input",
    +			args: args{
    +				input:      []byte(`{"username": "user1",}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want:    nil,
    +			wantErr: true,
    +		},
    +	}
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			got, err := jsonFieldMaskPostProcess(tt.args.fieldNames)(tt.args.input)
    +			require.Equal(t, tt.wantErr, err != nil)
    +			// only assert the output if there is no error
    +			// as JSONEq func is used to compare the output
    +			if !tt.wantErr {
    +				require.JSONEq(t, string(tt.want), string(got))
    +			}
    +		})
    +	}
    +}
    
  • bugtool/cmd/root.go+2 0 modified
    @@ -172,6 +172,8 @@ func isValidArchiveType(archiveType string) bool {
     	return false
     }
     
    +type postProcessFunc func(output []byte) ([]byte, error)
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    
224e288a5bf4

bugtool: Add json masking function

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
3 files changed · +250 0
  • bugtool/cmd/mask.go+56 0 added
    @@ -0,0 +1,56 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"encoding/json"
    +	"slices"
    +)
    +
    +const (
    +	redacted = "[redacted]"
    +	ident    = "\t"
    +)
    +
    +// jsonFieldMaskPostProcess returns a postProcessFunc that masks the specified field names.
    +// The input byte slice is expected to be a JSON object.
    +func jsonFieldMaskPostProcess(fieldNames []string) postProcessFunc {
    +	return func(b []byte) ([]byte, error) {
    +		return maskFields(b, fieldNames)
    +	}
    +}
    +
    +func maskFields(b []byte, fieldNames []string) ([]byte, error) {
    +	var data map[string]interface{}
    +
    +	if err := json.Unmarshal(b, &data); err != nil {
    +		return nil, err
    +	}
    +
    +	mask(data, fieldNames)
    +
    +	// MarshalIndent is used to make the output more readable.
    +	return json.MarshalIndent(data, "", ident)
    +}
    +
    +func mask(data map[string]interface{}, fieldNames []string) {
    +	for k, v := range data {
    +		if slices.Contains(fieldNames, k) {
    +			data[k] = redacted
    +			continue
    +		}
    +
    +		switch t := v.(type) {
    +		case map[string]interface{}:
    +			mask(t, fieldNames)
    +		case []interface{}:
    +			for i, item := range t {
    +				if subData, ok := item.(map[string]interface{}); ok {
    +					mask(subData, fieldNames)
    +					t[i] = subData
    +				}
    +			}
    +		}
    +	}
    +}
    
  • bugtool/cmd/mask_test.go+192 0 added
    @@ -0,0 +1,192 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"testing"
    +
    +	"github.com/stretchr/testify/require"
    +)
    +
    +func Test_jsonFieldMaskPostProcess(t *testing.T) {
    +	type args struct {
    +		input      []byte
    +		fieldNames []string
    +	}
    +	tests := []struct {
    +		name    string
    +		args    args
    +		want    []byte
    +		wantErr bool
    +	}{
    +		{
    +			name: "simple struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "mypassword"
    +						},
    +						{
    +							"password": "anotherone"
    +						}
    +					],
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					],
    +					"password": "[redacted]",
    +					"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "nested struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"password": "[redacted]"
    +				}
    +			}`),
    +		},
    +		{
    +			name: "nested array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"secrets": [
    +							{
    +								"password": "mypassword"
    +							},
    +							{
    +								"password": "anotherpassword"
    +							}
    +						]
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					]
    +				}
    +			}`),
    +		},
    +		{
    +			name: "no masked field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"no-such-field"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +		},
    +		{
    +			name: "mask object field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"complex"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": "[redacted]"
    +				}`),
    +		},
    +		{
    +			name: "invalid input",
    +			args: args{
    +				input:      []byte(`{"username": "user1",}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want:    nil,
    +			wantErr: true,
    +		},
    +	}
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			got, err := jsonFieldMaskPostProcess(tt.args.fieldNames)(tt.args.input)
    +			require.Equal(t, tt.wantErr, err != nil)
    +			// only assert the output if there is no error
    +			// as JSONEq func is used to compare the output
    +			if !tt.wantErr {
    +				require.JSONEq(t, string(tt.want), string(got))
    +			}
    +		})
    +	}
    +}
    
  • bugtool/cmd/root.go+2 0 modified
    @@ -168,6 +168,8 @@ func isValidArchiveType(archiveType string) bool {
     	return false
     }
     
    +type postProcessFunc func(output []byte) ([]byte, error)
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    
0191b1ebcfdd

bugtool: Add json masking function

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
3 files changed · +250 0
  • bugtool/cmd/mask.go+56 0 added
    @@ -0,0 +1,56 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"encoding/json"
    +	"slices"
    +)
    +
    +const (
    +	redacted = "[redacted]"
    +	ident    = "\t"
    +)
    +
    +// jsonFieldMaskPostProcess returns a postProcessFunc that masks the specified field names.
    +// The input byte slice is expected to be a JSON object.
    +func jsonFieldMaskPostProcess(fieldNames []string) postProcessFunc {
    +	return func(b []byte) ([]byte, error) {
    +		return maskFields(b, fieldNames)
    +	}
    +}
    +
    +func maskFields(b []byte, fieldNames []string) ([]byte, error) {
    +	var data map[string]interface{}
    +
    +	if err := json.Unmarshal(b, &data); err != nil {
    +		return nil, err
    +	}
    +
    +	mask(data, fieldNames)
    +
    +	// MarshalIndent is used to make the output more readable.
    +	return json.MarshalIndent(data, "", ident)
    +}
    +
    +func mask(data map[string]interface{}, fieldNames []string) {
    +	for k, v := range data {
    +		if slices.Contains(fieldNames, k) {
    +			data[k] = redacted
    +			continue
    +		}
    +
    +		switch t := v.(type) {
    +		case map[string]interface{}:
    +			mask(t, fieldNames)
    +		case []interface{}:
    +			for i, item := range t {
    +				if subData, ok := item.(map[string]interface{}); ok {
    +					mask(subData, fieldNames)
    +					t[i] = subData
    +				}
    +			}
    +		}
    +	}
    +}
    
  • bugtool/cmd/mask_test.go+192 0 added
    @@ -0,0 +1,192 @@
    +// SPDX-License-Identifier: Apache-2.0
    +// Copyright Authors of Cilium
    +
    +package cmd
    +
    +import (
    +	"testing"
    +
    +	"github.com/stretchr/testify/require"
    +)
    +
    +func Test_jsonFieldMaskPostProcess(t *testing.T) {
    +	type args struct {
    +		input      []byte
    +		fieldNames []string
    +	}
    +	tests := []struct {
    +		name    string
    +		args    args
    +		want    []byte
    +		wantErr bool
    +	}{
    +		{
    +			name: "simple struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "mypassword"
    +						},
    +						{
    +							"password": "anotherone"
    +						}
    +					],
    +					"password": "mypassword",
    +					"email": "user1@example.com"
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					],
    +					"password": "[redacted]",
    +					"email": "user1@example.com"
    +			}`),
    +		},
    +		{
    +			name: "nested struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"password": "[redacted]"
    +				}
    +			}`),
    +		},
    +		{
    +			name: "nested array struct",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"secrets": [
    +							{
    +								"password": "mypassword"
    +							},
    +							{
    +								"password": "anotherpassword"
    +							}
    +						]
    +					}
    +				}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want: []byte(`{
    +				"username": "user1",
    +				"password": "[redacted]",
    +				"email": "user1@example.com",
    +				"complex": {
    +					"secrets": [
    +						{
    +							"password": "[redacted]"
    +						},
    +						{
    +							"password": "[redacted]"
    +						}
    +					]
    +				}
    +			}`),
    +		},
    +		{
    +			name: "no masked field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"no-such-field"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +		},
    +		{
    +			name: "mask object field",
    +			args: args{
    +				input: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": {
    +						"password": "anotherpassword"
    +					}
    +				}`),
    +				fieldNames: []string{"complex"},
    +			},
    +			want: []byte(`{
    +					"username": "user1",
    +					"password": "mypassword",
    +					"email": "user1@example.com",
    +					"complex": "[redacted]"
    +				}`),
    +		},
    +		{
    +			name: "invalid input",
    +			args: args{
    +				input:      []byte(`{"username": "user1",}`),
    +				fieldNames: []string{"password"},
    +			},
    +			want:    nil,
    +			wantErr: true,
    +		},
    +	}
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			got, err := jsonFieldMaskPostProcess(tt.args.fieldNames)(tt.args.input)
    +			require.Equal(t, tt.wantErr, err != nil)
    +			// only assert the output if there is no error
    +			// as JSONEq func is used to compare the output
    +			if !tt.wantErr {
    +				require.JSONEq(t, string(tt.want), string(got))
    +			}
    +		})
    +	}
    +}
    
  • bugtool/cmd/root.go+2 0 modified
    @@ -168,6 +168,8 @@ func isValidArchiveType(archiveType string) bool {
     	return false
     }
     
    +type postProcessFunc func(output []byte) ([]byte, error)
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    
9eb25ba40391

bugtool: Add post-processing masking function for Envoy

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
1 file changed · +46 4
  • bugtool/cmd/root.go+46 4 modified
    @@ -27,6 +27,7 @@ import (
     	"github.com/cilium/cilium/pkg/components"
     	"github.com/cilium/cilium/pkg/defaults"
     	"github.com/cilium/cilium/pkg/option"
    +	"github.com/cilium/cilium/pkg/safeio"
     )
     
     // BugtoolRootCmd is the top level command for the bugtool.
    @@ -170,6 +171,18 @@ func isValidArchiveType(archiveType string) bool {
     
     type postProcessFunc func(output []byte) ([]byte, error)
     
    +var envoySecretMask = jsonFieldMaskPostProcess([]string{
    +	// Cilium LogEntry -> KafkaLogEntry{l7} -> KafkaLogEntry{api_key}
    +	"api_key",
    +	// This could be from one of the following:
    +	// - Cilium NetworkPolicy -> PortNetworkPolicy{ingress_per_port_policies, egress_per_port_policies}
    +	//	-> PortNetworkPolicyRule{rules} -> TLSContext{downstream_tls_context, upstream_tls_context}
    +	// - Upstream Envoy tls_certificate
    +	"trusted_ca",
    +	"certificate_chain",
    +	"private_key",
    +})
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    @@ -216,13 +229,13 @@ func runTool() {
     		}
     	} else {
     		if envoyDump {
    -			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json", envoySecretMask); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to dump envoy config: %s\n", err)
     			}
     		}
     
     		if envoyMetrics {
    -			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt", nil); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to retrieve envoy prometheus metrics: %s\n", err)
     			}
     		}
    @@ -516,7 +529,7 @@ func dumpHubbleMetrics(rootDir string) error {
     	return downloadToFile(httpClient, url, filepath.Join(rootDir, "hubble-metrics.txt"))
     }
     
    -func dumpEnvoy(rootDir string, resource string, fileName string) error {
    +func dumpEnvoy(rootDir string, resource string, fileName string, postProcess postProcessFunc) error {
     	// curl --unix-socket /var/run/cilium/envoy/sockets/admin.sock http:/admin/config_dump\?include_eds > dump.json
     	c := &http.Client{
     		Transport: &http.Transport{
    @@ -525,7 +538,11 @@ func dumpEnvoy(rootDir string, resource string, fileName string) error {
     			},
     		},
     	}
    -	return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +
    +	if postProcess == nil {
    +		return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +	}
    +	return downloadToFileWithPostProcess(c, resource, filepath.Join(rootDir, fileName), postProcess)
     }
     
     func pprofTraces(rootDir string, pprofDebug int) error {
    @@ -589,3 +606,28 @@ func downloadToFile(client *http.Client, url, file string) error {
     	_, err = io.Copy(out, resp.Body)
     	return err
     }
    +
    +// downloadToFileWithPostProcess downloads the content from the given URL and writes it to the given file.
    +// The content is then post-processed using the given postProcess function before being written to the file.
    +// Note: Please use downloadToFile instead of this function if no post-processing is required.
    +func downloadToFileWithPostProcess(client *http.Client, url, file string, postProcess postProcessFunc) error {
    +	resp, err := client.Get(url)
    +	if err != nil {
    +		return err
    +	}
    +	defer resp.Body.Close()
    +	if resp.StatusCode != http.StatusOK {
    +		return fmt.Errorf("bad status: %s", resp.Status)
    +	}
    +
    +	b, err := safeio.ReadAllLimit(resp.Body, safeio.MB)
    +	if err != nil {
    +		return err
    +	}
    +
    +	b, err = postProcess(b)
    +	if err != nil {
    +		return err
    +	}
    +	return os.WriteFile(file, b, 0644)
    +}
    
9299c0fd0024

bugtool: Add post-processing masking function for Envoy

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
1 file changed · +46 4
  • bugtool/cmd/root.go+46 4 modified
    @@ -27,6 +27,7 @@ import (
     	"github.com/cilium/cilium/pkg/components"
     	"github.com/cilium/cilium/pkg/defaults"
     	"github.com/cilium/cilium/pkg/option"
    +	"github.com/cilium/cilium/pkg/safeio"
     )
     
     // BugtoolRootCmd is the top level command for the bugtool.
    @@ -170,6 +171,18 @@ func isValidArchiveType(archiveType string) bool {
     
     type postProcessFunc func(output []byte) ([]byte, error)
     
    +var envoySecretMask = jsonFieldMaskPostProcess([]string{
    +	// Cilium LogEntry -> KafkaLogEntry{l7} -> KafkaLogEntry{api_key}
    +	"api_key",
    +	// This could be from one of the following:
    +	// - Cilium NetworkPolicy -> PortNetworkPolicy{ingress_per_port_policies, egress_per_port_policies}
    +	//	-> PortNetworkPolicyRule{rules} -> TLSContext{downstream_tls_context, upstream_tls_context}
    +	// - Upstream Envoy tls_certificate
    +	"trusted_ca",
    +	"certificate_chain",
    +	"private_key",
    +})
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    @@ -216,13 +229,13 @@ func runTool() {
     		}
     	} else {
     		if envoyDump {
    -			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json", envoySecretMask); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to dump envoy config: %s\n", err)
     			}
     		}
     
     		if envoyMetrics {
    -			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt", nil); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to retrieve envoy prometheus metrics: %s\n", err)
     			}
     		}
    @@ -516,7 +529,7 @@ func dumpHubbleMetrics(rootDir string) error {
     	return downloadToFile(httpClient, url, filepath.Join(rootDir, "hubble-metrics.txt"))
     }
     
    -func dumpEnvoy(rootDir string, resource string, fileName string) error {
    +func dumpEnvoy(rootDir string, resource string, fileName string, postProcess postProcessFunc) error {
     	// curl --unix-socket /var/run/cilium/envoy/sockets/admin.sock http:/admin/config_dump\?include_eds > dump.json
     	c := &http.Client{
     		Transport: &http.Transport{
    @@ -525,7 +538,11 @@ func dumpEnvoy(rootDir string, resource string, fileName string) error {
     			},
     		},
     	}
    -	return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +
    +	if postProcess == nil {
    +		return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +	}
    +	return downloadToFileWithPostProcess(c, resource, filepath.Join(rootDir, fileName), postProcess)
     }
     
     func pprofTraces(rootDir string, pprofDebug int) error {
    @@ -589,3 +606,28 @@ func downloadToFile(client *http.Client, url, file string) error {
     	_, err = io.Copy(out, resp.Body)
     	return err
     }
    +
    +// downloadToFileWithPostProcess downloads the content from the given URL and writes it to the given file.
    +// The content is then post-processed using the given postProcess function before being written to the file.
    +// Note: Please use downloadToFile instead of this function if no post-processing is required.
    +func downloadToFileWithPostProcess(client *http.Client, url, file string, postProcess postProcessFunc) error {
    +	resp, err := client.Get(url)
    +	if err != nil {
    +		return err
    +	}
    +	defer resp.Body.Close()
    +	if resp.StatusCode != http.StatusOK {
    +		return fmt.Errorf("bad status: %s", resp.Status)
    +	}
    +
    +	b, err := safeio.ReadAllLimit(resp.Body, safeio.MB)
    +	if err != nil {
    +		return err
    +	}
    +
    +	b, err = postProcess(b)
    +	if err != nil {
    +		return err
    +	}
    +	return os.WriteFile(file, b, 0644)
    +}
    
bf9a1ae1b2d2

bugtool: Add post-processing masking function for Envoy

https://github.com/cilium/ciliumTam MachJun 5, 2024via ghsa
1 file changed · +45 4
  • bugtool/cmd/root.go+45 4 modified
    @@ -174,6 +174,18 @@ func isValidArchiveType(archiveType string) bool {
     
     type postProcessFunc func(output []byte) ([]byte, error)
     
    +var envoySecretMask = jsonFieldMaskPostProcess([]string{
    +	// Cilium LogEntry -> KafkaLogEntry{l7} -> KafkaLogEntry{api_key}
    +	"api_key",
    +	// This could be from one of the following:
    +	// - Cilium NetworkPolicy -> PortNetworkPolicy{ingress_per_port_policies, egress_per_port_policies}
    +	//	-> PortNetworkPolicyRule{rules} -> TLSContext{downstream_tls_context, upstream_tls_context}
    +	// - Upstream Envoy tls_certificate
    +	"trusted_ca",
    +	"certificate_chain",
    +	"private_key",
    +})
    +
     func runTool() {
     	// Validate archive type
     	if !isValidArchiveType(archiveType) {
    @@ -220,13 +232,13 @@ func runTool() {
     		}
     	} else {
     		if envoyDump {
    -			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/config_dump?include_eds", "envoy-config.json", envoySecretMask); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to dump envoy config: %s\n", err)
     			}
     		}
     
     		if envoyMetrics {
    -			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt"); err != nil {
    +			if err := dumpEnvoy(cmdDir, "http://admin/stats/prometheus", "envoy-metrics.txt", nil); err != nil {
     				fmt.Fprintf(os.Stderr, "Unable to retrieve envoy prometheus metrics: %s\n", err)
     			}
     		}
    @@ -520,7 +532,7 @@ func dumpHubbleMetrics(rootDir string) error {
     	return downloadToFile(httpClient, url, filepath.Join(rootDir, "hubble-metrics.txt"))
     }
     
    -func dumpEnvoy(rootDir string, resource string, fileName string) error {
    +func dumpEnvoy(rootDir string, resource string, fileName string, postProcess postProcessFunc) error {
     	// curl --unix-socket /var/run/cilium/envoy-admin.sock http:/admin/config_dump\?include_eds > dump.json
     	c := &http.Client{
     		Transport: &http.Transport{
    @@ -529,7 +541,11 @@ func dumpEnvoy(rootDir string, resource string, fileName string) error {
     			},
     		},
     	}
    -	return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +
    +	if postProcess == nil {
    +		return downloadToFile(c, resource, filepath.Join(rootDir, fileName))
    +	}
    +	return downloadToFileWithPostProcess(c, resource, filepath.Join(rootDir, fileName), postProcess)
     }
     
     func pprofTraces(rootDir string, pprofDebug int) error {
    @@ -593,3 +609,28 @@ func downloadToFile(client *http.Client, url, file string) error {
     	_, err = io.Copy(out, resp.Body)
     	return err
     }
    +
    +// downloadToFileWithPostProcess downloads the content from the given URL and writes it to the given file.
    +// The content is then post-processed using the given postProcess function before being written to the file.
    +// Note: Please use downloadToFile instead of this function if no post-processing is required.
    +func downloadToFileWithPostProcess(client *http.Client, url, file string, postProcess postProcessFunc) error {
    +	resp, err := client.Get(url)
    +	if err != nil {
    +		return err
    +	}
    +	defer resp.Body.Close()
    +	if resp.StatusCode != http.StatusOK {
    +		return fmt.Errorf("bad status: %s", resp.Status)
    +	}
    +
    +	b, err := io.ReadAll(resp.Body)
    +	if err != nil {
    +		return err
    +	}
    +
    +	b, err = postProcess(b)
    +	if err != nil {
    +		return err
    +	}
    +	return os.WriteFile(file, b, 0644)
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.