kubeconfig.go 6.4 KB

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