k8s.io/client-go@v0.31.1/tools/clientcmd/api/helpers.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package api
    18  
    19  import (
    20  	"encoding/base64"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"path/filepath"
    25  	"reflect"
    26  	"strings"
    27  )
    28  
    29  func init() {
    30  	sDec, _ := base64.StdEncoding.DecodeString("REDACTED+")
    31  	redactedBytes = []byte(string(sDec))
    32  	sDec, _ = base64.StdEncoding.DecodeString("DATA+OMITTED")
    33  	dataOmittedBytes = []byte(string(sDec))
    34  }
    35  
    36  // IsConfigEmpty returns true if the config is empty.
    37  func IsConfigEmpty(config *Config) bool {
    38  	return len(config.AuthInfos) == 0 && len(config.Clusters) == 0 && len(config.Contexts) == 0 &&
    39  		len(config.CurrentContext) == 0 &&
    40  		len(config.Preferences.Extensions) == 0 && !config.Preferences.Colors &&
    41  		len(config.Extensions) == 0
    42  }
    43  
    44  // MinifyConfig read the current context and uses that to keep only the relevant pieces of config
    45  // This is useful for making secrets based on kubeconfig files
    46  func MinifyConfig(config *Config) error {
    47  	if len(config.CurrentContext) == 0 {
    48  		return errors.New("current-context must exist in order to minify")
    49  	}
    50  
    51  	currContext, exists := config.Contexts[config.CurrentContext]
    52  	if !exists {
    53  		return fmt.Errorf("cannot locate context %v", config.CurrentContext)
    54  	}
    55  
    56  	newContexts := map[string]*Context{}
    57  	newContexts[config.CurrentContext] = currContext
    58  
    59  	newClusters := map[string]*Cluster{}
    60  	if len(currContext.Cluster) > 0 {
    61  		if _, exists := config.Clusters[currContext.Cluster]; !exists {
    62  			return fmt.Errorf("cannot locate cluster %v", currContext.Cluster)
    63  		}
    64  
    65  		newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster]
    66  	}
    67  
    68  	newAuthInfos := map[string]*AuthInfo{}
    69  	if len(currContext.AuthInfo) > 0 {
    70  		if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists {
    71  			return fmt.Errorf("cannot locate user %v", currContext.AuthInfo)
    72  		}
    73  
    74  		newAuthInfos[currContext.AuthInfo] = config.AuthInfos[currContext.AuthInfo]
    75  	}
    76  
    77  	config.AuthInfos = newAuthInfos
    78  	config.Clusters = newClusters
    79  	config.Contexts = newContexts
    80  
    81  	return nil
    82  }
    83  
    84  var (
    85  	dataOmittedBytes []byte
    86  	redactedBytes    []byte
    87  )
    88  
    89  // ShortenConfig redacts raw data entries from the config object for a human-readable view.
    90  func ShortenConfig(config *Config) {
    91  	// trick json encoder into printing a human-readable string in the raw data
    92  	// by base64 decoding what we want to print. Relies on implementation of
    93  	// http://golang.org/pkg/encoding/json/#Marshal using base64 to encode []byte
    94  	for key, authInfo := range config.AuthInfos {
    95  		if len(authInfo.ClientKeyData) > 0 {
    96  			authInfo.ClientKeyData = dataOmittedBytes
    97  		}
    98  		if len(authInfo.ClientCertificateData) > 0 {
    99  			authInfo.ClientCertificateData = dataOmittedBytes
   100  		}
   101  		if len(authInfo.Token) > 0 {
   102  			authInfo.Token = "REDACTED"
   103  		}
   104  		config.AuthInfos[key] = authInfo
   105  	}
   106  	for key, cluster := range config.Clusters {
   107  		if len(cluster.CertificateAuthorityData) > 0 {
   108  			cluster.CertificateAuthorityData = dataOmittedBytes
   109  		}
   110  		config.Clusters[key] = cluster
   111  	}
   112  }
   113  
   114  // FlattenConfig changes the config object into a self-contained config (useful for making secrets)
   115  func FlattenConfig(config *Config) error {
   116  	for key, authInfo := range config.AuthInfos {
   117  		baseDir, err := MakeAbs(filepath.Dir(authInfo.LocationOfOrigin), "")
   118  		if err != nil {
   119  			return err
   120  		}
   121  
   122  		if err := FlattenContent(&authInfo.ClientCertificate, &authInfo.ClientCertificateData, baseDir); err != nil {
   123  			return err
   124  		}
   125  		if err := FlattenContent(&authInfo.ClientKey, &authInfo.ClientKeyData, baseDir); err != nil {
   126  			return err
   127  		}
   128  
   129  		config.AuthInfos[key] = authInfo
   130  	}
   131  	for key, cluster := range config.Clusters {
   132  		baseDir, err := MakeAbs(filepath.Dir(cluster.LocationOfOrigin), "")
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		if err := FlattenContent(&cluster.CertificateAuthority, &cluster.CertificateAuthorityData, baseDir); err != nil {
   138  			return err
   139  		}
   140  
   141  		config.Clusters[key] = cluster
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  func FlattenContent(path *string, contents *[]byte, baseDir string) error {
   148  	if len(*path) != 0 {
   149  		if len(*contents) > 0 {
   150  			return errors.New("cannot have values for both path and contents")
   151  		}
   152  
   153  		var err error
   154  		absPath := ResolvePath(*path, baseDir)
   155  		*contents, err = os.ReadFile(absPath)
   156  		if err != nil {
   157  			return err
   158  		}
   159  
   160  		*path = ""
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // ResolvePath returns the path as an absolute paths, relative to the given base directory
   167  func ResolvePath(path string, base string) string {
   168  	// Don't resolve empty paths
   169  	if len(path) > 0 {
   170  		// Don't resolve absolute paths
   171  		if !filepath.IsAbs(path) {
   172  			return filepath.Join(base, path)
   173  		}
   174  	}
   175  
   176  	return path
   177  }
   178  
   179  func MakeAbs(path, base string) (string, error) {
   180  	if filepath.IsAbs(path) {
   181  		return path, nil
   182  	}
   183  	if len(base) == 0 {
   184  		cwd, err := os.Getwd()
   185  		if err != nil {
   186  			return "", err
   187  		}
   188  		base = cwd
   189  	}
   190  	return filepath.Join(base, path), nil
   191  }
   192  
   193  // RedactSecrets replaces any sensitive values with REDACTED
   194  func RedactSecrets(config *Config) error {
   195  	return redactSecrets(reflect.ValueOf(config), false)
   196  }
   197  
   198  func redactSecrets(curr reflect.Value, redact bool) error {
   199  	redactedBytes = []byte("REDACTED")
   200  	if !curr.IsValid() {
   201  		return nil
   202  	}
   203  
   204  	actualCurrValue := curr
   205  	if curr.Kind() == reflect.Ptr {
   206  		actualCurrValue = curr.Elem()
   207  	}
   208  
   209  	switch actualCurrValue.Kind() {
   210  	case reflect.Map:
   211  		for _, v := range actualCurrValue.MapKeys() {
   212  			err := redactSecrets(actualCurrValue.MapIndex(v), false)
   213  			if err != nil {
   214  				return err
   215  			}
   216  		}
   217  		return nil
   218  
   219  	case reflect.String:
   220  		if redact {
   221  			if !actualCurrValue.IsZero() {
   222  				actualCurrValue.SetString("REDACTED")
   223  			}
   224  		}
   225  		return nil
   226  
   227  	case reflect.Slice:
   228  		if actualCurrValue.Type() == reflect.TypeOf([]byte{}) && redact {
   229  			if !actualCurrValue.IsNil() {
   230  				actualCurrValue.SetBytes(redactedBytes)
   231  			}
   232  			return nil
   233  		}
   234  		for i := 0; i < actualCurrValue.Len(); i++ {
   235  			err := redactSecrets(actualCurrValue.Index(i), false)
   236  			if err != nil {
   237  				return err
   238  			}
   239  		}
   240  		return nil
   241  
   242  	case reflect.Struct:
   243  		for fieldIndex := 0; fieldIndex < actualCurrValue.NumField(); fieldIndex++ {
   244  			currFieldValue := actualCurrValue.Field(fieldIndex)
   245  			currFieldType := actualCurrValue.Type().Field(fieldIndex)
   246  			currYamlTag := currFieldType.Tag.Get("datapolicy")
   247  			currFieldTypeYamlName := strings.Split(currYamlTag, ",")[0]
   248  			if currFieldTypeYamlName != "" {
   249  				err := redactSecrets(currFieldValue, true)
   250  				if err != nil {
   251  					return err
   252  				}
   253  			} else {
   254  				err := redactSecrets(currFieldValue, false)
   255  				if err != nil {
   256  					return err
   257  				}
   258  			}
   259  		}
   260  		return nil
   261  
   262  	default:
   263  		return nil
   264  	}
   265  }