github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/authenticator/patcher/patcher.go (about) 1 package patcher 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 9 "k8s.io/client-go/tools/clientcmd" 10 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 11 12 "github.com/datawire/dlib/dlog" 13 "github.com/telepresenceio/telepresence/rpc/v2/connector" 14 "github.com/telepresenceio/telepresence/rpc/v2/daemon" 15 "github.com/telepresenceio/telepresence/v2/pkg/filelocation" 16 "github.com/telepresenceio/telepresence/v2/pkg/ioutil" 17 "github.com/telepresenceio/telepresence/v2/pkg/maps" 18 ) 19 20 const ( 21 kubeConfigStubSubCommands = "kubeauth" 22 kubeConfigs = "kube" 23 ) 24 25 // AddressProvider is a function that returns the path to the telepresence executable and an address to a service that 26 // implements the Authenticator gRPC. 27 // 28 // The function will typically start the gRPC service, and the service is therefore given 29 // a list of files that it must listen to in order to reliably resolve requests. It is 30 // also passed a pointer to the minified config that will be stored in a file so that it 31 // has a chance to modify it. 32 type ( 33 AddressProvider func(configFiles []string) (string, string, error) 34 Patcher func(*clientcmdapi.Config) error 35 ) 36 37 // CreateExternalKubeConfig will load the current kubeconfig and minimize it so that it just contains the current 38 // context. It will then check if that context contains an Exec config, and if it does, replace that config with 39 // an Exec config that instead runs a process that will use a gRPC call to the address returned by the given 40 // authAddressFunc. 41 func CreateExternalKubeConfig( 42 ctx context.Context, 43 loader clientcmd.ClientConfig, 44 kubeContext string, 45 authAddressFunc AddressProvider, 46 patcher Patcher, 47 ) (*clientcmdapi.Config, error) { 48 ns, _, err := loader.Namespace() 49 if err != nil { 50 return nil, err 51 } 52 53 configFiles := loader.ConfigAccess().GetLoadingPrecedence() 54 dlog.Debugf(ctx, "host kubeconfig = %v", configFiles) 55 origConfig, err := loader.RawConfig() 56 if err != nil { 57 return nil, err 58 } 59 var config clientcmdapi.Config 60 origConfig.DeepCopyInto(&config) 61 62 // Minify the config so that we only deal with the current context. 63 if kubeContext != "" { 64 config.CurrentContext = kubeContext 65 } 66 if err = clientcmdapi.MinifyConfig(&config); err != nil { 67 return nil, err 68 } 69 dlog.Debugf(ctx, "context = %q, namespace %q", config.CurrentContext, ns) 70 71 // Minify guarantees that the CurrentContext is set, but not that it has a cluster 72 cc := config.Contexts[config.CurrentContext] 73 if cc.Cluster == "" { 74 return nil, fmt.Errorf("current context %q has no cluster", config.CurrentContext) 75 } 76 77 if needsStubbedExec(&config) { 78 executable, addr, err := authAddressFunc(configFiles) 79 if err != nil { 80 return nil, err 81 } 82 if err = replaceAuthExecWithStub(&config, executable, addr); err != nil { 83 return nil, err 84 } 85 } 86 87 // Ensure that all certs are embedded instead of reachable using a path 88 if err = clientcmdapi.FlattenConfig(&config); err != nil { 89 return nil, err 90 } 91 92 if patcher != nil { 93 if err = patcher(&config); err != nil { 94 return nil, err 95 } 96 } 97 98 // Store the file using its context name under the <telepresence cache>/kube directory 99 kubeConfigFile := ioutil.SafeName(config.CurrentContext) 100 kubeConfigDir := filepath.Join(filelocation.AppUserCacheDir(ctx), kubeConfigs) 101 if err = os.MkdirAll(kubeConfigDir, 0o700); err != nil { 102 return nil, err 103 } 104 if err = clientcmd.WriteToFile(config, filepath.Join(kubeConfigDir, kubeConfigFile)); err != nil { 105 return nil, err 106 } 107 return &config, nil 108 } 109 110 // replaceAuthExecWithStub goes through the kubeconfig and replaces all uses of the Exec auth method by 111 // an invocation of the stub binary. 112 func replaceAuthExecWithStub(rawConfig *clientcmdapi.Config, executable, address string) error { 113 for contextName, kubeContext := range rawConfig.Contexts { 114 // Find related Auth. 115 authInfo, ok := rawConfig.AuthInfos[kubeContext.AuthInfo] 116 if !ok { 117 return fmt.Errorf("auth info %s not found for context %s", kubeContext.AuthInfo, contextName) 118 } 119 120 // If it isn't an exec mode context, just return the default host kubeconfig. 121 if authInfo.Exec == nil { 122 continue 123 } 124 125 // Patch exec. 126 authInfo.Exec = &clientcmdapi.ExecConfig{ 127 InteractiveMode: clientcmdapi.NeverExecInteractiveMode, 128 APIVersion: authInfo.Exec.APIVersion, 129 Command: executable, 130 Args: []string{kubeConfigStubSubCommands, contextName, address}, 131 } 132 } 133 return nil 134 } 135 136 // needsStubbedExec returns true if the config contains at least one user with an Exec type AuthInfo. 137 func needsStubbedExec(rawConfig *clientcmdapi.Config) bool { 138 for _, kubeContext := range rawConfig.Contexts { 139 if authInfo, ok := rawConfig.AuthInfos[kubeContext.AuthInfo]; ok && authInfo.Exec != nil { 140 return true 141 } 142 } 143 return false 144 } 145 146 // AnnotateConnectRequest is used when the CLI connects to a containerized user-daemon. It adds a ContainerKubeFlagOverrides 147 // to the given ConnectRequest containing the path to the modified kubeconfig file to be used in the container. 148 func AnnotateConnectRequest(cr *connector.ConnectRequest, cacheDir, kubeContext string) { 149 kubeConfigFile := ioutil.SafeName(kubeContext) 150 if cr.ContainerKubeFlagOverrides == nil { 151 cr.ContainerKubeFlagOverrides = make(map[string]string) 152 } 153 // Concatenate using "/". This will be used in linux 154 cr.ContainerKubeFlagOverrides["kubeconfig"] = fmt.Sprintf("%s/%s/%s", cacheDir, kubeConfigs, kubeConfigFile) 155 156 // We never instruct the remote containerized daemon to modify its KUBECONFIG environment. 157 delete(cr.Environment, "KUBECONFIG") 158 delete(cr.Environment, "-KUBECONFIG") 159 } 160 161 // AnnotateOutboundInfo is used when a non-containerized user-daemon connects to the root-daemon. The KubeFlags 162 // are modified to contain the path to the modified kubeconfig file. 163 func AnnotateOutboundInfo(ctx context.Context, oi *daemon.OutboundInfo, kubeContext string) { 164 kubeConfigFile := ioutil.SafeName(kubeContext) 165 if oi.KubeFlags == nil { 166 oi.KubeFlags = make(map[string]string) 167 } else { 168 oi.KubeFlags = maps.Copy(oi.KubeFlags) 169 } 170 // Concatenate using "/". This will be used in linux 171 oi.KubeFlags["kubeconfig"] = fmt.Sprintf("%s/%s/%s", filelocation.AppUserCacheDir(ctx), kubeConfigs, kubeConfigFile) 172 }