Skip to content

Commit

Permalink
Update redacting functionality to redact all sensitive info in config…
Browse files Browse the repository at this point in the history
… when printing with view (#109189)

* Add RedactSecrets function

* Move RedactSecrets method to existing RawBytesData case

* Update TestRedactSecrets to use new pattern of os.CreateTemp()

Kubernetes-commit: e721272d10dd6c4d85ff613182ba0eaddcec9272
  • Loading branch information
mpuckett159 authored and k8s-publishing-bot committed Nov 8, 2022
1 parent 4b1a9fd commit 7694435
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 17 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
google.golang.org/protobuf v1.28.1
k8s.io/api v0.0.0-20221108053748-98c1aa6b3d0a
k8s.io/apimachinery v0.0.0-20221108052757-4fe4321a9d5e
k8s.io/apimachinery v0.0.0-20221108055230-fd8a60496be5
k8s.io/klog/v2 v2.80.1
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d
Expand Down Expand Up @@ -60,5 +60,5 @@ require (

replace (
k8s.io/api => k8s.io/api v0.0.0-20221108053748-98c1aa6b3d0a
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20221108052757-4fe4321a9d5e
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20221108055230-fd8a60496be5
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.0.0-20221108053748-98c1aa6b3d0a h1:GaCla9HtNyi63kysI/cyeA4bv6wRkIyuiUeXpaTF+dw=
k8s.io/api v0.0.0-20221108053748-98c1aa6b3d0a/go.mod h1:PSXY9/fSNyKgKHUU+O9scnZiW8m+V1znqk49oI6hAEY=
k8s.io/apimachinery v0.0.0-20221108052757-4fe4321a9d5e h1:zX/AC2CNrYwngyVHVHcsCL36uUtC/3tiZOE6/gfojvc=
k8s.io/apimachinery v0.0.0-20221108052757-4fe4321a9d5e/go.mod h1:VXMmlsE7YRJ5vyAyWpkKIfFkEbDNpVs0ObpkuQf1WfM=
k8s.io/apimachinery v0.0.0-20221108055230-fd8a60496be5 h1:iFAMJ1evvrO6X7dS7EKujS6An+bp3u/dD6opu8rn0QA=
k8s.io/apimachinery v0.0.0-20221108055230-fd8a60496be5/go.mod h1:VXMmlsE7YRJ5vyAyWpkKIfFkEbDNpVs0ObpkuQf1WfM=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
Expand Down
88 changes: 82 additions & 6 deletions tools/clientcmd/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"strings"
)

func init() {
Expand Down Expand Up @@ -81,21 +83,21 @@ func MinifyConfig(config *Config) error {
}

var (
redactedBytes []byte
dataOmittedBytes []byte
redactedBytes []byte
)

// Flatten redacts raw data entries from the config object for a human-readable view.
// ShortenConfig redacts raw data entries from the config object for a human-readable view.
func ShortenConfig(config *Config) {
// trick json encoder into printing a human readable string in the raw data
// trick json encoder into printing a human-readable string in the raw data
// by base64 decoding what we want to print. Relies on implementation of
// http://golang.org/pkg/encoding/json/#Marshal using base64 to encode []byte
for key, authInfo := range config.AuthInfos {
if len(authInfo.ClientKeyData) > 0 {
authInfo.ClientKeyData = redactedBytes
authInfo.ClientKeyData = dataOmittedBytes
}
if len(authInfo.ClientCertificateData) > 0 {
authInfo.ClientCertificateData = redactedBytes
authInfo.ClientCertificateData = dataOmittedBytes
}
if len(authInfo.Token) > 0 {
authInfo.Token = "REDACTED"
Expand All @@ -110,7 +112,7 @@ func ShortenConfig(config *Config) {
}
}

// Flatten changes the config object into a self contained config (useful for making secrets)
// FlattenConfig changes the config object into a self-contained config (useful for making secrets)
func FlattenConfig(config *Config) error {
for key, authInfo := range config.AuthInfos {
baseDir, err := MakeAbs(path.Dir(authInfo.LocationOfOrigin), "")
Expand Down Expand Up @@ -188,3 +190,77 @@ func MakeAbs(path, base string) (string, error) {
}
return filepath.Join(base, path), nil
}

// RedactSecrets replaces any sensitive values with REDACTED
func RedactSecrets(config *Config) error {
return redactSecrets(reflect.ValueOf(config), false)
}

func redactSecrets(curr reflect.Value, redact bool) error {
redactedBytes = []byte("REDACTED")
if !curr.IsValid() {
return nil
}

actualCurrValue := curr
if curr.Kind() == reflect.Ptr {
actualCurrValue = curr.Elem()
}

switch actualCurrValue.Kind() {
case reflect.Map:
for _, v := range actualCurrValue.MapKeys() {
err := redactSecrets(actualCurrValue.MapIndex(v), false)
if err != nil {
return err
}
}
return nil

case reflect.String:
if redact {
if !actualCurrValue.IsZero() {
actualCurrValue.SetString("REDACTED")
}
}
return nil

case reflect.Slice:
if actualCurrValue.Type() == reflect.TypeOf([]byte{}) && redact {
if !actualCurrValue.IsNil() {
actualCurrValue.SetBytes(redactedBytes)
}
return nil
}
for i := 0; i < actualCurrValue.Len(); i++ {
err := redactSecrets(actualCurrValue.Index(i), false)
if err != nil {
return err
}
}
return nil

case reflect.Struct:
for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
currFieldValue := actualCurrValue.Field(fieldIndex)
currFieldType := actualCurrValue.Type().Field(fieldIndex)
currYamlTag := currFieldType.Tag.Get("datapolicy")
currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
if currFieldTypeYamlName != "" {
err := redactSecrets(currFieldValue, true)
if err != nil {
return err
}
} else {
err := redactSecrets(currFieldValue, false)
if err != nil {
return err
}
}
}
return nil

default:
return nil
}
}
66 changes: 59 additions & 7 deletions tools/clientcmd/api/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package api

import (
"bytes"
"fmt"
"os"
"reflect"
Expand Down Expand Up @@ -240,8 +241,8 @@ func Example_minifyAndShorten() {
// users:
// red-user:
// LocationOfOrigin: ""
// client-certificate-data: REDACTED
// client-key-data: REDACTED
// client-certificate-data: DATA+OMITTED
// client-key-data: DATA+OMITTED
// token: REDACTED
}

Expand Down Expand Up @@ -274,7 +275,6 @@ func TestShortenSuccess(t *testing.T) {
t.Errorf("expected %v, got %v", startingConfig.Contexts, mutatingConfig.Contexts)
}

redacted := string(redactedBytes)
dataOmitted := string(dataOmittedBytes)
if len(mutatingConfig.Clusters) != 2 {
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
Expand All @@ -292,13 +292,65 @@ func TestShortenSuccess(t *testing.T) {
if !reflect.DeepEqual(startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo]) {
t.Errorf("expected %v, got %v", startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo])
}
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData) != redacted {
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData))
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData) != dataOmitted {
t.Errorf("expected %v, got %v", dataOmitted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientCertificateData))
}
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData) != redacted {
t.Errorf("expected %v, got %v", redacted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData))
if string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData) != dataOmitted {
t.Errorf("expected %v, got %v", dataOmitted, string(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData))
}
if mutatingConfig.AuthInfos[changingAuthInfo].Token != "REDACTED" {
t.Errorf("expected REDACTED, got %q", mutatingConfig.AuthInfos[changingAuthInfo].Token)
}
}

func TestRedactSecrets(t *testing.T) {
certFile, _ := os.CreateTemp("", "")
defer os.Remove(certFile.Name())
keyFile, _ := os.CreateTemp("", "")
defer os.Remove(keyFile.Name())
caFile, _ := os.CreateTemp("", "")
defer os.Remove(caFile.Name())

certData := "cert"
keyData := "key"
caData := "ca"

unchangingCluster := "chicken-cluster"
unchangingAuthInfo := "blue-user"
changingAuthInfo := "red-user"

startingConfig := newMergedConfig(certFile.Name(), certData, keyFile.Name(), keyData, caFile.Name(), caData, t)
mutatingConfig := startingConfig

err := RedactSecrets(&mutatingConfig)
if err != nil {
t.Errorf("unexpected error redacting secrets:\n%v", err)
}

if len(mutatingConfig.Contexts) != 2 {
t.Errorf("unexpected contexts: %v", mutatingConfig.Contexts)
}
if !reflect.DeepEqual(startingConfig.Contexts, mutatingConfig.Contexts) {
t.Errorf("expected %v, got %v", startingConfig.Contexts, mutatingConfig.Contexts)
}

if len(mutatingConfig.Clusters) != 2 {
t.Errorf("unexpected clusters: %v", mutatingConfig.Clusters)
}
if !reflect.DeepEqual(startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster]) {
t.Errorf("expected %v, got %v", startingConfig.Clusters[unchangingCluster], mutatingConfig.Clusters[unchangingCluster])
}

if len(mutatingConfig.AuthInfos) != 2 {
t.Errorf("unexpected users: %v", mutatingConfig.AuthInfos)
}
if !reflect.DeepEqual(startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo]) {
t.Errorf("expected %v, got %v", startingConfig.AuthInfos[unchangingAuthInfo], mutatingConfig.AuthInfos[unchangingAuthInfo])
}
if mutatingConfig.AuthInfos[changingAuthInfo].Token != "REDACTED" {
t.Errorf("expected REDACTED, got %v", mutatingConfig.AuthInfos[changingAuthInfo].Token)
}
if !bytes.Equal(mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData, []byte("REDACTED")) {
t.Errorf("expected REDACTED, got %s", mutatingConfig.AuthInfos[changingAuthInfo].ClientKeyData)
}
}

0 comments on commit 7694435

Please sign in to comment.