istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/install/kubeconfig.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package install 16 17 import ( 18 "fmt" 19 "net" 20 "os" 21 "path/filepath" 22 23 "k8s.io/client-go/tools/clientcmd/api" 24 "k8s.io/client-go/tools/clientcmd/api/latest" 25 "sigs.k8s.io/yaml" 26 27 "istio.io/istio/cni/pkg/config" 28 "istio.io/istio/pilot/pkg/model" 29 "istio.io/istio/pkg/file" 30 ) 31 32 type kubeconfig struct { 33 // The full kubeconfig 34 Full string 35 // Kubeconfig with confidential data redacted. 36 Redacted string 37 } 38 39 func createKubeConfig(cfg *config.InstallConfig) (kubeconfig, error) { 40 if len(cfg.K8sServiceHost) == 0 { 41 return kubeconfig{}, fmt.Errorf("KUBERNETES_SERVICE_HOST not set. Is this not running within a pod?") 42 } 43 44 if len(cfg.K8sServicePort) == 0 { 45 return kubeconfig{}, fmt.Errorf("KUBERNETES_SERVICE_PORT not set. Is this not running within a pod?") 46 } 47 48 protocol := model.GetOrDefault(cfg.K8sServiceProtocol, "https") 49 cluster := &api.Cluster{ 50 Server: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(cfg.K8sServiceHost, cfg.K8sServicePort)), 51 } 52 53 if cfg.SkipTLSVerify { 54 // User explicitly opted into insecure. 55 cluster.InsecureSkipTLSVerify = true 56 } else { 57 caFile := model.GetOrDefault(cfg.KubeCAFile, cfg.K8sServiceAccountPath+"/ca.crt") 58 caContents, err := os.ReadFile(caFile) 59 if err != nil { 60 return kubeconfig{}, err 61 } 62 cluster.CertificateAuthorityData = caContents 63 } 64 65 token, err := os.ReadFile(cfg.K8sServiceAccountPath + "/token") 66 if err != nil { 67 return kubeconfig{}, err 68 } 69 70 const contextName = "istio-cni-context" 71 const clusterName = "local" 72 const userName = "istio-cni" 73 kcfg := &api.Config{ 74 Kind: "Config", 75 APIVersion: "v1", 76 Preferences: api.Preferences{}, 77 Clusters: map[string]*api.Cluster{ 78 clusterName: cluster, 79 }, 80 AuthInfos: map[string]*api.AuthInfo{ 81 userName: { 82 Token: string(token), 83 }, 84 }, 85 Contexts: map[string]*api.Context{ 86 contextName: { 87 AuthInfo: userName, 88 Cluster: clusterName, 89 }, 90 }, 91 CurrentContext: contextName, 92 } 93 94 lcfg, err := latest.Scheme.ConvertToVersion(kcfg, latest.ExternalVersion) 95 if err != nil { 96 return kubeconfig{}, err 97 } 98 // Convert to v1 schema which has proper encoding 99 fullYaml, err := yaml.Marshal(lcfg) 100 if err != nil { 101 return kubeconfig{}, err 102 } 103 104 // Log with redaction 105 if err := api.RedactSecrets(kcfg); err != nil { 106 return kubeconfig{}, err 107 } 108 for _, c := range kcfg.Clusters { 109 // Not actually sensitive, just annoyingly verbose. 110 c.CertificateAuthority = "REDACTED" 111 } 112 lrcfg, err := latest.Scheme.ConvertToVersion(kcfg, latest.ExternalVersion) 113 if err != nil { 114 return kubeconfig{}, err 115 } 116 redacted, err := yaml.Marshal(lrcfg) 117 if err != nil { 118 return kubeconfig{}, err 119 } 120 121 return kubeconfig{ 122 Full: string(fullYaml), 123 Redacted: string(redacted), 124 }, nil 125 } 126 127 // maybeWriteKubeConfigFile will validate the existing kubeConfig file, and rewrite/replace it if required. 128 func maybeWriteKubeConfigFile(cfg *config.InstallConfig) error { 129 kc, err := createKubeConfig(cfg) 130 if err != nil { 131 return err 132 } 133 134 if err := checkExistingKubeConfigFile(cfg, kc); err != nil { 135 installLog.Info("kubeconfig either does not exist or is out of date, writing a new one") 136 kubeconfigFilepath := filepath.Join(cfg.MountedCNINetDir, cfg.KubeconfigFilename) 137 if err := file.AtomicWrite(kubeconfigFilepath, []byte(kc.Full), os.FileMode(cfg.KubeconfigMode)); err != nil { 138 return err 139 } 140 installLog.Infof("wrote kubeconfig file %s with: \n%+v", kubeconfigFilepath, kc.Redacted) 141 } 142 return nil 143 } 144 145 // checkExistingKubeConfigFile returns an error if no kubeconfig exists at the configured path, 146 // or if a kubeconfig exists there, but differs from the current config. 147 // In any case, an error indicates the file must be (re)written, and no error means no action need be taken 148 func checkExistingKubeConfigFile(cfg *config.InstallConfig, expectedKC kubeconfig) error { 149 kubeconfigFilepath := filepath.Join(cfg.MountedCNINetDir, cfg.KubeconfigFilename) 150 151 existingKC, err := os.ReadFile(kubeconfigFilepath) 152 if err != nil { 153 installLog.Debugf("no preexisting kubeconfig at %s, assuming we need to create one", kubeconfigFilepath) 154 return err 155 } 156 157 if expectedKC.Full == string(existingKC) { 158 installLog.Debugf("preexisting kubeconfig %s is an exact match for expected, no need to update", kubeconfigFilepath) 159 return nil 160 } 161 162 return fmt.Errorf("kubeconfig on disk differs from expected, assuming we need to rewrite it") 163 }