kubeconfig.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. restClientGetter := kubernetes.NewRESTClientGetterFromInClusterConfig(restConf)
  62. clientset, err := k8s.NewForConfig(restConf)
  63. return &kubernetes.Agent{
  64. RESTClientGetter: restClientGetter,
  65. Clientset: clientset,
  66. }, nil
  67. }
  68. // ResolveKubeconfigPath finds the path to a kubeconfig, first searching for the
  69. // passed string, then in the home directory, then as an env variable.
  70. func ResolveKubeconfigPath(kubeconfigPath string) (string, error) {
  71. envVarName := clientcmd.RecommendedConfigPathEnvVar
  72. if kubeconfigPath != "" {
  73. if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
  74. // the specified kubeconfig does not exist, throw error
  75. return "", fmt.Errorf("kubeconfig not found: %s does not exist", kubeconfigPath)
  76. }
  77. }
  78. if kubeconfigPath == "" {
  79. if os.Getenv(envVarName) == "" {
  80. if home := homedir.HomeDir(); home != "" {
  81. kubeconfigPath = filepath.Join(home, ".kube", "config")
  82. }
  83. } else {
  84. kubeconfigPath = os.Getenv(envVarName)
  85. }
  86. }
  87. return kubeconfigPath, nil
  88. }
  89. // GetConfigFromHostWithCertData gets the kubeconfig using default options set on the host:
  90. // the kubeconfig can either be retrieved from a specified path or an environment variable.
  91. // This function only outputs a clientcmd that uses the allowedContexts.
  92. //
  93. // This function also populates all of the certificate data that's specified as a filepath.
  94. func GetConfigFromHostWithCertData(kubeconfigPath string, allowedContexts []string) (clientcmd.ClientConfig, error) {
  95. envVarName := clientcmd.RecommendedConfigPathEnvVar
  96. if kubeconfigPath != "" {
  97. if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
  98. // the specified kubeconfig does not exist so fallback to other options
  99. kubeconfigPath = ""
  100. }
  101. }
  102. if kubeconfigPath == "" && os.Getenv(envVarName) == "" {
  103. if home := homedir.HomeDir(); home != "" {
  104. kubeconfigPath = filepath.Join(home, ".kube", "config")
  105. }
  106. }
  107. loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
  108. loadingRules.ExplicitPath = kubeconfigPath
  109. clientConf := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
  110. rawConf, err := clientConf.RawConfig()
  111. if err != nil {
  112. return nil, err
  113. }
  114. populateCertificateRefs(&rawConf)
  115. populateOIDCPluginCerts(&rawConf)
  116. if len(allowedContexts) == 0 {
  117. allowedContexts = []string{rawConf.CurrentContext}
  118. if allowedContexts[0] == "" {
  119. return nil, fmt.Errorf("at least one context must be specified")
  120. }
  121. }
  122. res, err := stripAndValidateClientContexts(&rawConf, allowedContexts[0], allowedContexts)
  123. if err != nil {
  124. return nil, err
  125. }
  126. return res, nil
  127. }
  128. func stripAndValidateClientContexts(
  129. rawConf *clientcmdapi.Config,
  130. currentContext string,
  131. allowedContexts []string,
  132. ) (clientcmd.ClientConfig, error) {
  133. // grab a copy to get the pointer and set clusters, authinfos, and contexts to empty
  134. copyConf := rawConf.DeepCopy()
  135. copyConf.Clusters = make(map[string]*api.Cluster)
  136. copyConf.AuthInfos = make(map[string]*api.AuthInfo)
  137. copyConf.Contexts = make(map[string]*api.Context)
  138. copyConf.CurrentContext = currentContext
  139. // put allowed clusters in a map
  140. aContextMap := kubernetes.CreateAllowedContextMap(allowedContexts)
  141. for contextName, context := range rawConf.Contexts {
  142. userName := context.AuthInfo
  143. clusterName := context.Cluster
  144. authInfo, userFound := rawConf.AuthInfos[userName]
  145. cluster, clusterFound := rawConf.Clusters[clusterName]
  146. // make sure the cluster is "allowed"
  147. _, isAllowed := aContextMap[contextName]
  148. if userFound && clusterFound && isAllowed {
  149. copyConf.Clusters[clusterName] = cluster
  150. copyConf.AuthInfos[userName] = authInfo
  151. copyConf.Contexts[contextName] = context
  152. }
  153. }
  154. // validate the copyConf and create a ClientConfig
  155. err := clientcmd.Validate(*copyConf)
  156. if err != nil {
  157. return nil, err
  158. }
  159. clientConf := clientcmd.NewDefaultClientConfig(*copyConf, &clientcmd.ConfigOverrides{})
  160. return clientConf, nil
  161. }
  162. func populateCertificateRefs(config *clientcmdapi.Config) {
  163. for _, cluster := range config.Clusters {
  164. refs := clientcmd.GetClusterFileReferences(cluster)
  165. for _, str := range refs {
  166. // only write certificate if the file reference is CA
  167. if *str != cluster.CertificateAuthority {
  168. break
  169. }
  170. fileBytes, err := ioutil.ReadFile(*str)
  171. if err != nil {
  172. continue
  173. }
  174. cluster.CertificateAuthorityData = fileBytes
  175. cluster.CertificateAuthority = ""
  176. }
  177. }
  178. for _, authInfo := range config.AuthInfos {
  179. refs := clientcmd.GetAuthInfoFileReferences(authInfo)
  180. for _, str := range refs {
  181. if *str == "" {
  182. continue
  183. }
  184. var refType int
  185. if authInfo.ClientCertificate == *str {
  186. refType = 0
  187. } else if authInfo.ClientKey == *str {
  188. refType = 1
  189. } else if authInfo.TokenFile == *str {
  190. refType = 2
  191. }
  192. fileBytes, err := ioutil.ReadFile(*str)
  193. if err != nil {
  194. continue
  195. }
  196. if refType == 0 {
  197. authInfo.ClientCertificateData = fileBytes
  198. authInfo.ClientCertificate = ""
  199. } else if refType == 1 {
  200. authInfo.ClientKeyData = fileBytes
  201. authInfo.ClientKey = ""
  202. } else if refType == 2 {
  203. authInfo.Token = base64.StdEncoding.EncodeToString(fileBytes)
  204. authInfo.TokenFile = ""
  205. }
  206. }
  207. }
  208. }
  209. func populateOIDCPluginCerts(config *clientcmdapi.Config) {
  210. for _, authInfo := range config.AuthInfos {
  211. if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" {
  212. if ca, ok := authInfo.AuthProvider.Config["idp-certificate-authority"]; ok && ca != "" {
  213. fileBytes, err := ioutil.ReadFile(ca)
  214. if err != nil {
  215. continue
  216. }
  217. authInfo.AuthProvider.Config["idp-certificate-authority-data"] = base64.StdEncoding.EncodeToString(fileBytes)
  218. delete(authInfo.AuthProvider.Config, "idp-certificate-authority")
  219. }
  220. }
  221. }
  222. }