mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 10:20:51 +00:00
Merge pull request #71149 from awly/rest-config-stringer
Implement fmt.Stringer on rest.Config to sanitize sensitive fields
This commit is contained in:
commit
914e383c9b
@ -18,6 +18,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/transport:go_default_library",
|
"//staging/src/k8s.io/client-go/transport:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/util/connrotation:go_default_library",
|
"//staging/src/k8s.io/client-go/util/connrotation:go_default_library",
|
||||||
|
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||||
"//vendor/golang.org/x/crypto/ssh/terminal:go_default_library",
|
"//vendor/golang.org/x/crypto/ssh/terminal:go_default_library",
|
||||||
"//vendor/k8s.io/klog:go_default_library",
|
"//vendor/k8s.io/klog:go_default_library",
|
||||||
],
|
],
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -73,8 +74,10 @@ func newCache() *cache {
|
|||||||
return &cache{m: make(map[string]*Authenticator)}
|
return &cache{m: make(map[string]*Authenticator)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var spewConfig = &spew.ConfigState{DisableMethods: true, Indent: " "}
|
||||||
|
|
||||||
func cacheKey(c *api.ExecConfig) string {
|
func cacheKey(c *api.ExecConfig) string {
|
||||||
return fmt.Sprintf("%#v", c)
|
return spewConfig.Sprint(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
|
@ -129,6 +129,47 @@ type Config struct {
|
|||||||
// Version string
|
// Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = new(Config)
|
||||||
|
var _ fmt.GoStringer = new(Config)
|
||||||
|
|
||||||
|
type sanitizedConfig *Config
|
||||||
|
|
||||||
|
type sanitizedAuthConfigPersister struct{ AuthProviderConfigPersister }
|
||||||
|
|
||||||
|
func (sanitizedAuthConfigPersister) GoString() string {
|
||||||
|
return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
|
||||||
|
}
|
||||||
|
func (sanitizedAuthConfigPersister) String() string {
|
||||||
|
return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
|
||||||
|
// to prevent accidental leaking via logs.
|
||||||
|
func (c *Config) GoString() string {
|
||||||
|
return c.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer and sanitizes sensitive fields of Config to
|
||||||
|
// prevent accidental leaking via logs.
|
||||||
|
func (c *Config) String() string {
|
||||||
|
if c == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
cc := sanitizedConfig(CopyConfig(c))
|
||||||
|
// Explicitly mark non-empty credential fields as redacted.
|
||||||
|
if cc.Password != "" {
|
||||||
|
cc.Password = "--- REDACTED ---"
|
||||||
|
}
|
||||||
|
if cc.BearerToken != "" {
|
||||||
|
cc.BearerToken = "--- REDACTED ---"
|
||||||
|
}
|
||||||
|
if cc.AuthConfigPersister != nil {
|
||||||
|
cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%#v", cc)
|
||||||
|
}
|
||||||
|
|
||||||
// ImpersonationConfig has all the available impersonation options
|
// ImpersonationConfig has all the available impersonation options
|
||||||
type ImpersonationConfig struct {
|
type ImpersonationConfig struct {
|
||||||
// UserName is the username to impersonate on each request.
|
// UserName is the username to impersonate on each request.
|
||||||
@ -168,6 +209,40 @@ type TLSClientConfig struct {
|
|||||||
CAData []byte
|
CAData []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = TLSClientConfig{}
|
||||||
|
var _ fmt.GoStringer = TLSClientConfig{}
|
||||||
|
|
||||||
|
type sanitizedTLSClientConfig TLSClientConfig
|
||||||
|
|
||||||
|
// GoString implements fmt.GoStringer and sanitizes sensitive fields of
|
||||||
|
// TLSClientConfig to prevent accidental leaking via logs.
|
||||||
|
func (c TLSClientConfig) GoString() string {
|
||||||
|
return c.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer and sanitizes sensitive fields of
|
||||||
|
// TLSClientConfig to prevent accidental leaking via logs.
|
||||||
|
func (c TLSClientConfig) String() string {
|
||||||
|
cc := sanitizedTLSClientConfig{
|
||||||
|
Insecure: c.Insecure,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
CertFile: c.CertFile,
|
||||||
|
KeyFile: c.KeyFile,
|
||||||
|
CAFile: c.CAFile,
|
||||||
|
CertData: c.CertData,
|
||||||
|
KeyData: c.KeyData,
|
||||||
|
CAData: c.CAData,
|
||||||
|
}
|
||||||
|
// Explicitly mark non-empty credential fields as redacted.
|
||||||
|
if len(cc.CertData) != 0 {
|
||||||
|
cc.CertData = []byte("--- TRUNCATED ---")
|
||||||
|
}
|
||||||
|
if len(cc.KeyData) != 0 {
|
||||||
|
cc.KeyData = []byte("--- REDACTED ---")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%#v", cc)
|
||||||
|
}
|
||||||
|
|
||||||
type ContentConfig struct {
|
type ContentConfig struct {
|
||||||
// AcceptContentTypes specifies the types the client will accept and is optional.
|
// AcceptContentTypes specifies the types the client will accept and is optional.
|
||||||
// If not set, ContentType will be used to define the Accept header
|
// If not set, ContentType will be used to define the Accept header
|
||||||
|
@ -19,6 +19,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -26,6 +27,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -150,6 +152,25 @@ type AuthProviderConfig struct {
|
|||||||
Config map[string]string `json:"config,omitempty"`
|
Config map[string]string `json:"config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = new(AuthProviderConfig)
|
||||||
|
var _ fmt.GoStringer = new(AuthProviderConfig)
|
||||||
|
|
||||||
|
// GoString implements fmt.GoStringer and sanitizes sensitive fields of
|
||||||
|
// AuthProviderConfig to prevent accidental leaking via logs.
|
||||||
|
func (c AuthProviderConfig) GoString() string {
|
||||||
|
return c.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer and sanitizes sensitive fields of
|
||||||
|
// AuthProviderConfig to prevent accidental leaking via logs.
|
||||||
|
func (c AuthProviderConfig) String() string {
|
||||||
|
cfg := "<nil>"
|
||||||
|
if c.Config != nil {
|
||||||
|
cfg = "--- REDACTED ---"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("api.AuthProviderConfig{Name: %q, Config: map[string]string{%s}}", c.Name, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
// ExecConfig specifies a command to provide client credentials. The command is exec'd
|
||||||
// and outputs structured stdout holding credentials.
|
// and outputs structured stdout holding credentials.
|
||||||
//
|
//
|
||||||
@ -172,6 +193,29 @@ type ExecConfig struct {
|
|||||||
APIVersion string `json:"apiVersion,omitempty"`
|
APIVersion string `json:"apiVersion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = new(ExecConfig)
|
||||||
|
var _ fmt.GoStringer = new(ExecConfig)
|
||||||
|
|
||||||
|
// GoString implements fmt.GoStringer and sanitizes sensitive fields of
|
||||||
|
// ExecConfig to prevent accidental leaking via logs.
|
||||||
|
func (c ExecConfig) GoString() string {
|
||||||
|
return c.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer and sanitizes sensitive fields of ExecConfig
|
||||||
|
// to prevent accidental leaking via logs.
|
||||||
|
func (c ExecConfig) String() string {
|
||||||
|
var args []string
|
||||||
|
if len(c.Args) > 0 {
|
||||||
|
args = []string{"--- REDACTED ---"}
|
||||||
|
}
|
||||||
|
env := "[]ExecEnvVar(nil)"
|
||||||
|
if len(c.Env) > 0 {
|
||||||
|
env = "[]ExecEnvVar{--- REDACTED ---}"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("api.AuthProviderConfig{Command: %q, Args: %#v, Env: %s, APIVersion: %q}", c.Command, args, env, c.APIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
// ExecEnvVar is used for setting environment variables when executing an exec-based
|
||||||
// credential plugin.
|
// credential plugin.
|
||||||
type ExecEnvVar struct {
|
type ExecEnvVar struct {
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
|
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/evanphx/json-patch",
|
"ImportPath": "github.com/evanphx/json-patch",
|
||||||
"Rev": "36442dbdb585210f8d5a1b45e67aa323c197d5c4"
|
"Rev": "36442dbdb585210f8d5a1b45e67aa323c197d5c4"
|
||||||
|
Loading…
Reference in New Issue
Block a user