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 }