kubeconfig.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package local
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "github.com/porter-dev/porter/internal/kubernetes"
  9. k8s "k8s.io/client-go/kubernetes"
  10. "k8s.io/client-go/tools/clientcmd"
  11. "k8s.io/client-go/tools/clientcmd/api"
  12. clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
  13. "k8s.io/client-go/util/homedir"
  14. )
  15. // GetKubeconfigFromHost returns the kubeconfig for a list of contexts using default
  16. // options set on the host, or an explicit kubeconfig path. It then strips the kubeconfig
  17. // of contexts not specified in the contexts array, and returns generate kubeconfig.
  18. func GetKubeconfigFromHost(kubeconfigPath string, contexts []string) ([]byte, error) {
  19. kubeconfigPath, err := ResolveKubeconfigPath(kubeconfigPath)
  20. if err != nil {
  21. return nil, err
  22. }
  23. loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
  24. loadingRules.ExplicitPath = kubeconfigPath
  25. clientConf := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
  26. rawConf, err := clientConf.RawConfig()
  27. if err != nil {
  28. return nil, err
  29. }
  30. if len(contexts) == 0 {
  31. contexts = []string{rawConf.CurrentContext}
  32. if contexts[0] == "" {
  33. return nil, fmt.Errorf("at least one context must be specified")
  34. }
  35. }
  36. conf, err := stripAndValidateClientContexts(&rawConf, contexts[0], contexts)
  37. if err != nil {
  38. return nil, err
  39. }
  40. strippedRawConf, err := conf.RawConfig()
  41. if err != nil {
  42. return nil, err
  43. }
  44. return clientcmd.Write(strippedRawConf)
  45. }
  46. // GetSelfAgentFromFileConfig reads a kubeconfig from a local file and generates an
  47. // Agent from that kubeconfig
  48. func GetSelfAgentFromFileConfig(kubeconfigPath string) (*kubernetes.Agent, error) {
  49. configBytes, err := GetKubeconfigFromHost(kubeconfigPath, []string{})
  50. if err != nil {
  51. return nil, err
  52. }
  53. cmdConf, err := clientcmd.NewClientConfigFromBytes(configBytes)
  54. if err != nil {
  55. return nil, err
  56. }
  57. restConf, err := cmdConf.ClientConfig()
  58. if err != nil {
  59. return nil, err
  60. }
  61. var namespace string
  62. cmdConfNamespace, _, err := cmdConf.Namespace()
  63. if err == nil && cmdConfNamespace != "" {
  64. namespace = cmdConfNamespace
  65. }
  66. restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf, namespace)
  67. clientset, err := k8s.NewForConfig(restConf)
  68. return &kubernetes.Agent{
  69. RESTClientGetter: restClientGetter,
  70. Clientset: clientset,
  71. }, nil
  72. }
  73. // ResolveKubeconfigPath finds the path to a kubeconfig, first searching for the
  74. // passed string, then in the home directory, then as an env variable.
  75. func ResolveKubeconfigPath(kubeconfigPath string) (string, error) {
  76. envVarName := clientcmd.RecommendedConfigPathEnvVar
  77. if kubeconfigPath != "" {
  78. if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
  79. // the specified kubeconfig does not exist, throw error
  80. return "", fmt.Errorf("kubeconfig not found: %s does not exist", kubeconfigPath)
  81. }
  82. }
  83. if kubeconfigPath == "" {
  84. if os.Getenv(envVarName) == "" {
  85. if home := homedir.HomeDir(); home != "" {
  86. kubeconfigPath = filepath.Join(home, ".kube", "config")
  87. }
  88. } else {
  89. kubeconfigPath = os.Getenv(envVarName)
  90. }
  91. }
  92. return kubeconfigPath, nil
  93. }
  94. // GetConfigFromHostWithCertData gets the kubeconfig using default options set on the host:
  95. // the kubeconfig can either be retrieved from a specified path or an environment variable.
  96. // This function only outputs a clientcmd that uses the allowedContexts.
  97. //
  98. // This function also populates all of the certificate data that's specified as a filepath.
  99. func GetConfigFromHostWithCertData(kubeconfigPath string, allowedContexts []string) (clientcmd.ClientConfig, error) {
  100. envVarName := clientcmd.RecommendedConfigPathEnvVar
  101. if kubeconfigPath != "" {
  102. if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
  103. // the specified kubeconfig does not exist so fallback to other options
  104. kubeconfigPath = ""
  105. }
  106. }
  107. if kubeconfigPath == "" && os.Getenv(envVarName) == "" {
  108. if home := homedir.HomeDir(); home != "" {
  109. kubeconfigPath = filepath.Join(home, ".kube", "config")
  110. }
  111. }
  112. loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
  113. loadingRules.ExplicitPath = kubeconfigPath
  114. clientConf := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
  115. rawConf, err := clientConf.RawConfig()
  116. if err != nil {
  117. return nil, err
  118. }
  119. populateCertificateRefs(&rawConf)
  120. populateOIDCPluginCerts(&rawConf)
  121. if len(allowedContexts) == 0 {
  122. allowedContexts = []string{rawConf.CurrentContext}
  123. if allowedContexts[0] == "" {
  124. return nil, fmt.Errorf("at least one context must be specified")
  125. }
  126. }
  127. res, err := stripAndValidateClientContexts(&rawConf, allowedContexts[0], allowedContexts)
  128. if err != nil {
  129. return nil, err
  130. }
  131. return res, nil
  132. }
  133. func stripAndValidateClientContexts(
  134. rawConf *clientcmdapi.Config,
  135. currentContext string,
  136. allowedContexts []string,
  137. ) (clientcmd.ClientConfig, error) {
  138. // grab a copy to get the pointer and set clusters, authinfos, and contexts to empty
  139. copyConf := rawConf.DeepCopy()
  140. copyConf.Clusters = make(map[string]*api.Cluster)
  141. copyConf.AuthInfos = make(map[string]*api.AuthInfo)
  142. copyConf.Contexts = make(map[string]*api.Context)
  143. copyConf.CurrentContext = currentContext
  144. // put allowed clusters in a map
  145. aContextMap := kubernetes.CreateAllowedContextMap(allowedContexts)
  146. for contextName, context := range rawConf.Contexts {
  147. userName := context.AuthInfo
  148. clusterName := context.Cluster
  149. authInfo, userFound := rawConf.AuthInfos[userName]
  150. cluster, clusterFound := rawConf.Clusters[clusterName]
  151. // make sure the cluster is "allowed"
  152. _, isAllowed := aContextMap[contextName]
  153. if userFound && clusterFound && isAllowed {
  154. copyConf.Clusters[clusterName] = cluster
  155. copyConf.AuthInfos[userName] = authInfo
  156. copyConf.Contexts[contextName] = context
  157. }
  158. }
  159. // validate the copyConf and create a ClientConfig
  160. err := clientcmd.Validate(*copyConf)
  161. if err != nil {
  162. return nil, err
  163. }
  164. clientConf := clientcmd.NewDefaultClientConfig(*copyConf, &clientcmd.ConfigOverrides{})
  165. return clientConf, nil
  166. }
  167. func populateCertificateRefs(config *clientcmdapi.Config) {
  168. for _, cluster := range config.Clusters {
  169. refs := clientcmd.GetClusterFileReferences(cluster)
  170. for _, str := range refs {
  171. // only write certificate if the file reference is CA
  172. if *str != cluster.CertificateAuthority {
  173. break
  174. }
  175. fileBytes, err := ioutil.ReadFile(*str)
  176. if err != nil {
  177. continue
  178. }
  179. cluster.CertificateAuthorityData = fileBytes
  180. cluster.CertificateAuthority = ""
  181. }
  182. }
  183. for _, authInfo := range config.AuthInfos {
  184. refs := clientcmd.GetAuthInfoFileReferences(authInfo)
  185. for _, str := range refs {
  186. if *str == "" {
  187. continue
  188. }
  189. var refType int
  190. if authInfo.ClientCertificate == *str {
  191. refType = 0
  192. } else if authInfo.ClientKey == *str {
  193. refType = 1
  194. } else if authInfo.TokenFile == *str {
  195. refType = 2
  196. }
  197. fileBytes, err := ioutil.ReadFile(*str)
  198. if err != nil {
  199. continue
  200. }
  201. if refType == 0 {
  202. authInfo.ClientCertificateData = fileBytes
  203. authInfo.ClientCertificate = ""
  204. } else if refType == 1 {
  205. authInfo.ClientKeyData = fileBytes
  206. authInfo.ClientKey = ""
  207. } else if refType == 2 {
  208. authInfo.Token = base64.StdEncoding.EncodeToString(fileBytes)
  209. authInfo.TokenFile = ""
  210. }
  211. }
  212. }
  213. }
  214. func populateOIDCPluginCerts(config *clientcmdapi.Config) {
  215. for _, authInfo := range config.AuthInfos {
  216. if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" {
  217. if ca, ok := authInfo.AuthProvider.Config["idp-certificate-authority"]; ok && ca != "" {
  218. fileBytes, err := ioutil.ReadFile(ca)
  219. if err != nil {
  220. continue
  221. }
  222. authInfo.AuthProvider.Config["idp-certificate-authority-data"] = base64.StdEncoding.EncodeToString(fileBytes)
  223. delete(authInfo.AuthProvider.Config, "idp-certificate-authority")
  224. }
  225. }
  226. }
  227. }