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  }