kubeconfig.go 6.8 KB

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