mirror of
https://github.com/kubernetes/client-go.git
synced 2025-09-06 17:40:57 +00:00
Implement fmt.Stringer on rest.Config to sanitize sensitive fields
It's very easy to add glog.Info(config) calls for debugging (or actual logging). In some scenarios those configs will carry sensitive tokens and those tokens will end up in logs or response bodies. Leaking of those stringified configs compromises the cluster. Also implement fmt.GoStringer. Kubernetes-commit: c9ad1d7339b164dfba0846ec49fa4a52474d3e23
This commit is contained in:
committed by
Kubernetes Publisher
parent
b831b8de71
commit
420f3f77fb
@@ -19,6 +19,7 @@ package rest
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -381,3 +383,140 @@ func TestCopyConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigStringer(t *testing.T) {
|
||||
formatBytes := func(b []byte) string {
|
||||
// %#v for []byte always pre-pends "[]byte{".
|
||||
// %#v for struct with []byte field always pre-pends "[]uint8{".
|
||||
return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1)
|
||||
}
|
||||
tests := []struct {
|
||||
desc string
|
||||
c *Config
|
||||
expectContent []string
|
||||
prohibitContent []string
|
||||
}{
|
||||
{
|
||||
desc: "nil config",
|
||||
c: nil,
|
||||
expectContent: []string{"<nil>"},
|
||||
},
|
||||
{
|
||||
desc: "non-sensitive config",
|
||||
c: &Config{
|
||||
Host: "localhost:8080",
|
||||
APIPath: "v1",
|
||||
UserAgent: "gobot",
|
||||
},
|
||||
expectContent: []string{"localhost:8080", "v1", "gobot"},
|
||||
},
|
||||
{
|
||||
desc: "sensitive config",
|
||||
c: &Config{
|
||||
Host: "localhost:8080",
|
||||
Username: "gopher",
|
||||
Password: "g0ph3r",
|
||||
BearerToken: "1234567890",
|
||||
TLSClientConfig: TLSClientConfig{
|
||||
CertFile: "a.crt",
|
||||
KeyFile: "a.key",
|
||||
CertData: []byte("fake cert"),
|
||||
KeyData: []byte("fake key"),
|
||||
},
|
||||
AuthProvider: &clientcmdapi.AuthProviderConfig{
|
||||
Config: map[string]string{"secret": "s3cr3t"},
|
||||
},
|
||||
ExecProvider: &clientcmdapi.ExecConfig{
|
||||
Args: []string{"secret"},
|
||||
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
||||
},
|
||||
},
|
||||
expectContent: []string{
|
||||
"localhost:8080",
|
||||
"gopher",
|
||||
"a.crt",
|
||||
"a.key",
|
||||
"--- REDACTED ---",
|
||||
formatBytes([]byte("--- REDACTED ---")),
|
||||
formatBytes([]byte("--- TRUNCATED ---")),
|
||||
},
|
||||
prohibitContent: []string{
|
||||
"g0ph3r",
|
||||
"1234567890",
|
||||
formatBytes([]byte("fake cert")),
|
||||
formatBytes([]byte("fake key")),
|
||||
"secret",
|
||||
"s3cr3t",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
got := tt.c.String()
|
||||
t.Logf("formatted config: %q", got)
|
||||
|
||||
for _, expect := range tt.expectContent {
|
||||
if !strings.Contains(got, expect) {
|
||||
t.Errorf("missing expected string %q", expect)
|
||||
}
|
||||
}
|
||||
for _, prohibit := range tt.prohibitContent {
|
||||
if strings.Contains(got, prohibit) {
|
||||
t.Errorf("found prohibited string %q", prohibit)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSprint(t *testing.T) {
|
||||
c := &Config{
|
||||
Host: "localhost:8080",
|
||||
APIPath: "v1",
|
||||
ContentConfig: ContentConfig{
|
||||
AcceptContentTypes: "application/json",
|
||||
ContentType: "application/json",
|
||||
},
|
||||
Username: "gopher",
|
||||
Password: "g0ph3r",
|
||||
BearerToken: "1234567890",
|
||||
Impersonate: ImpersonationConfig{
|
||||
UserName: "gopher2",
|
||||
},
|
||||
AuthProvider: &clientcmdapi.AuthProviderConfig{
|
||||
Name: "gopher",
|
||||
Config: map[string]string{"secret": "s3cr3t"},
|
||||
},
|
||||
AuthConfigPersister: fakeAuthProviderConfigPersister{},
|
||||
ExecProvider: &clientcmdapi.ExecConfig{
|
||||
Command: "sudo",
|
||||
Args: []string{"secret"},
|
||||
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
|
||||
},
|
||||
TLSClientConfig: TLSClientConfig{
|
||||
CertFile: "a.crt",
|
||||
KeyFile: "a.key",
|
||||
CertData: []byte("fake cert"),
|
||||
KeyData: []byte("fake key"),
|
||||
},
|
||||
UserAgent: "gobot",
|
||||
Transport: &fakeRoundTripper{},
|
||||
WrapTransport: fakeWrapperFunc,
|
||||
QPS: 1,
|
||||
Burst: 2,
|
||||
RateLimiter: &fakeLimiter{},
|
||||
Timeout: 3 * time.Second,
|
||||
Dial: fakeDialFunc,
|
||||
}
|
||||
want := fmt.Sprintf(
|
||||
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil)}, UserAgent:"gobot", Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p)}`,
|
||||
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc,
|
||||
)
|
||||
|
||||
for _, f := range []string{"%s", "%v", "%+v", "%#v"} {
|
||||
if got := fmt.Sprintf(f, c); want != got {
|
||||
t.Errorf("fmt.Sprintf(%q, c)\ngot: %q\nwant: %q", f, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user