kubeconfig.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. package connect
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "os"
  9. "strings"
  10. "github.com/fatih/color"
  11. awsLocal "github.com/porter-dev/porter/cli/cmd/providers/aws/local"
  12. gcpLocal "github.com/porter-dev/porter/cli/cmd/providers/gcp/local"
  13. "github.com/porter-dev/porter/cli/cmd/utils"
  14. "github.com/porter-dev/porter/internal/kubernetes/local"
  15. "github.com/porter-dev/porter/cli/cmd/api"
  16. "github.com/porter-dev/porter/internal/models"
  17. )
  18. // Kubeconfig creates a service account for a project by parsing the local
  19. // kubeconfig and resolving actions that must be performed.
  20. func Kubeconfig(
  21. client *api.Client,
  22. kubeconfigPath string,
  23. contexts []string,
  24. projectID uint,
  25. isLocal bool,
  26. ) error {
  27. // if project ID is 0, ask the user to set the project ID or create a project
  28. if projectID == 0 {
  29. return fmt.Errorf("no project set, please run porter project set [id]")
  30. }
  31. // get the kubeconfig
  32. rawBytes, err := local.GetKubeconfigFromHost(kubeconfigPath, contexts)
  33. if err != nil {
  34. return err
  35. }
  36. // send kubeconfig to client
  37. ccs, err := client.CreateProjectCandidates(
  38. context.Background(),
  39. projectID,
  40. &api.CreateProjectCandidatesRequest{
  41. Kubeconfig: string(rawBytes),
  42. IsLocal: isLocal,
  43. },
  44. )
  45. if err != nil {
  46. return err
  47. }
  48. for _, cc := range ccs {
  49. var cluster *models.ClusterExternal
  50. if len(cc.Resolvers) > 0 {
  51. allResolver := &models.ClusterResolverAll{}
  52. for _, resolver := range cc.Resolvers {
  53. switch resolver.Name {
  54. case models.ClusterCAData:
  55. absKubeconfigPath, err := local.ResolveKubeconfigPath(kubeconfigPath)
  56. if err != nil {
  57. return err
  58. }
  59. filename, err := utils.GetFileReferenceFromKubeconfig(
  60. resolver.Data["filename"],
  61. absKubeconfigPath,
  62. )
  63. if err != nil {
  64. return err
  65. }
  66. err = resolveClusterCAAction(filename, allResolver)
  67. if err != nil {
  68. return err
  69. }
  70. case models.ClusterLocalhost:
  71. err := resolveLocalhostAction(allResolver)
  72. if err != nil {
  73. return err
  74. }
  75. case models.ClientCertData:
  76. absKubeconfigPath, err := local.ResolveKubeconfigPath(kubeconfigPath)
  77. if err != nil {
  78. return err
  79. }
  80. filename, err := utils.GetFileReferenceFromKubeconfig(
  81. resolver.Data["filename"],
  82. absKubeconfigPath,
  83. )
  84. if err != nil {
  85. return err
  86. }
  87. err = resolveClientCertAction(filename, allResolver)
  88. if err != nil {
  89. return err
  90. }
  91. case models.ClientKeyData:
  92. absKubeconfigPath, err := local.ResolveKubeconfigPath(kubeconfigPath)
  93. if err != nil {
  94. return err
  95. }
  96. filename, err := utils.GetFileReferenceFromKubeconfig(
  97. resolver.Data["filename"],
  98. absKubeconfigPath,
  99. )
  100. if err != nil {
  101. return err
  102. }
  103. err = resolveClientKeyAction(filename, allResolver)
  104. if err != nil {
  105. return err
  106. }
  107. case models.OIDCIssuerData:
  108. absKubeconfigPath, err := local.ResolveKubeconfigPath(kubeconfigPath)
  109. if err != nil {
  110. return err
  111. }
  112. filename, err := utils.GetFileReferenceFromKubeconfig(
  113. resolver.Data["filename"],
  114. absKubeconfigPath,
  115. )
  116. if err != nil {
  117. return err
  118. }
  119. err = resolveOIDCIssuerAction(filename, allResolver)
  120. if err != nil {
  121. return err
  122. }
  123. case models.TokenData:
  124. absKubeconfigPath, err := local.ResolveKubeconfigPath(kubeconfigPath)
  125. if err != nil {
  126. return err
  127. }
  128. filename, err := utils.GetFileReferenceFromKubeconfig(
  129. resolver.Data["filename"],
  130. absKubeconfigPath,
  131. )
  132. if err != nil {
  133. return err
  134. }
  135. err = resolveTokenDataAction(filename, allResolver)
  136. if err != nil {
  137. return err
  138. }
  139. case models.GCPKeyData:
  140. err := resolveGCPKeyAction(
  141. cc.Server,
  142. cc.Name,
  143. allResolver,
  144. )
  145. if err != nil {
  146. return err
  147. }
  148. case models.AWSData:
  149. err := resolveAWSAction(
  150. cc.Server,
  151. cc.Name,
  152. cc.AWSClusterIDGuess,
  153. kubeconfigPath,
  154. cc.ContextName,
  155. allResolver,
  156. )
  157. if err != nil {
  158. return err
  159. }
  160. }
  161. }
  162. resp, err := client.CreateProjectCluster(
  163. context.Background(),
  164. projectID,
  165. cc.ID,
  166. allResolver,
  167. )
  168. if err != nil {
  169. return err
  170. }
  171. clExt := models.ClusterExternal(*resp)
  172. cluster = &clExt
  173. } else {
  174. resp, err := client.GetProjectCluster(
  175. context.Background(),
  176. projectID,
  177. cc.CreatedClusterID,
  178. )
  179. if err != nil {
  180. return err
  181. }
  182. clExt := models.ClusterExternal(*resp)
  183. cluster = &clExt
  184. }
  185. color.New(color.FgGreen).Printf("created cluster %s with id %d\n", cluster.Name, cluster.ID)
  186. }
  187. return nil
  188. }
  189. // resolves a cluster ca data action
  190. func resolveClusterCAAction(
  191. filename string,
  192. resolver *models.ClusterResolverAll,
  193. ) error {
  194. fileBytes, err := ioutil.ReadFile(filename)
  195. if err != nil {
  196. return err
  197. }
  198. resolver.ClusterCAData = base64.StdEncoding.EncodeToString(fileBytes)
  199. return nil
  200. }
  201. func resolveLocalhostAction(
  202. resolver *models.ClusterResolverAll,
  203. ) error {
  204. resolver.ClusterHostname = "host.docker.internal"
  205. return nil
  206. }
  207. // resolves a client cert data action
  208. func resolveClientCertAction(
  209. filename string,
  210. resolver *models.ClusterResolverAll,
  211. ) error {
  212. fileBytes, err := ioutil.ReadFile(filename)
  213. if err != nil {
  214. return err
  215. }
  216. resolver.ClientCertData = base64.StdEncoding.EncodeToString(fileBytes)
  217. return nil
  218. }
  219. // resolves a client key data action
  220. func resolveClientKeyAction(
  221. filename string,
  222. resolver *models.ClusterResolverAll,
  223. ) error {
  224. fileBytes, err := ioutil.ReadFile(filename)
  225. if err != nil {
  226. return err
  227. }
  228. resolver.ClientKeyData = base64.StdEncoding.EncodeToString(fileBytes)
  229. return nil
  230. }
  231. // resolves an oidc issuer data action
  232. func resolveOIDCIssuerAction(
  233. filename string,
  234. resolver *models.ClusterResolverAll,
  235. ) error {
  236. fileBytes, err := ioutil.ReadFile(filename)
  237. if err != nil {
  238. return err
  239. }
  240. resolver.OIDCIssuerCAData = base64.StdEncoding.EncodeToString(fileBytes)
  241. return nil
  242. }
  243. // resolves a token data action
  244. func resolveTokenDataAction(
  245. filename string,
  246. resolver *models.ClusterResolverAll,
  247. ) error {
  248. fileBytes, err := ioutil.ReadFile(filename)
  249. if err != nil {
  250. return err
  251. }
  252. resolver.TokenData = string(fileBytes)
  253. return nil
  254. }
  255. // resolves a gcp key data action
  256. func resolveGCPKeyAction(
  257. endpoint string,
  258. clusterName string,
  259. resolver *models.ClusterResolverAll,
  260. ) error {
  261. userResp, err := utils.PromptPlaintext(
  262. fmt.Sprintf(
  263. `Detected GKE cluster in kubeconfig for the endpoint %s (%s).
  264. Porter can set up a service account in your GCP project to connect to this cluster automatically.
  265. Would you like to proceed? %s `,
  266. color.New(color.FgCyan).Sprintf("%s", endpoint),
  267. clusterName,
  268. color.New(color.FgCyan).Sprintf("[y/n]"),
  269. ),
  270. )
  271. if err != nil {
  272. return err
  273. }
  274. if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
  275. agent, err := gcpLocal.NewDefaultAgent()
  276. if err != nil {
  277. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  278. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  279. }
  280. projID, err := agent.GetProjectIDForGKECluster(endpoint)
  281. if err != nil {
  282. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  283. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  284. }
  285. agent.ProjectID = projID
  286. name := "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
  287. // create the service account and give it the correct iam permissions
  288. resp, err := agent.CreateServiceAccount(name)
  289. if err != nil {
  290. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  291. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  292. }
  293. err = agent.SetServiceAccountIAMPolicy(resp)
  294. if err != nil {
  295. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  296. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  297. }
  298. // get the service account key data to send to the server
  299. bytes, err := agent.CreateServiceAccountKey(resp)
  300. if err != nil {
  301. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  302. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  303. }
  304. resolver.GCPKeyData = string(bytes)
  305. return nil
  306. }
  307. return resolveGCPKeyActionManual(endpoint, clusterName, resolver)
  308. }
  309. func resolveGCPKeyActionManual(
  310. endpoint string,
  311. clusterName string,
  312. resolver *models.ClusterResolverAll,
  313. ) error {
  314. keyFileLocation, err := utils.PromptPlaintext(fmt.Sprintf(`Please provide the full path to a service account key file.
  315. Key file location: `))
  316. if err != nil {
  317. return err
  318. }
  319. // attempt to read the key file location
  320. if info, err := os.Stat(keyFileLocation); !os.IsNotExist(err) && !info.IsDir() {
  321. // read the file
  322. bytes, err := ioutil.ReadFile(keyFileLocation)
  323. if err != nil {
  324. return err
  325. }
  326. resolver.GCPKeyData = string(bytes)
  327. return nil
  328. }
  329. return errors.New("Key file not found")
  330. }
  331. // resolves an aws key data action
  332. func resolveAWSAction(
  333. endpoint string,
  334. clusterName string,
  335. awsClusterIDGuess string,
  336. kubeconfigPath string,
  337. contextName string,
  338. resolver *models.ClusterResolverAll,
  339. ) error {
  340. userResp, err := utils.PromptPlaintext(
  341. fmt.Sprintf(
  342. `Detected AWS cluster in kubeconfig for the endpoint %s (%s).
  343. Porter can set up an IAM user in your AWS account to connect to this cluster automatically.
  344. Would you like to proceed? %s `,
  345. color.New(color.FgCyan).Sprintf("%s", endpoint),
  346. clusterName,
  347. color.New(color.FgCyan).Sprintf("[y/n]"),
  348. ),
  349. )
  350. if err != nil {
  351. return err
  352. }
  353. if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
  354. agent, err := awsLocal.NewDefaultAgent(kubeconfigPath, contextName)
  355. if err != nil {
  356. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  357. return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess, resolver)
  358. }
  359. creds, err := agent.CreateIAMKubernetesMapping(awsClusterIDGuess)
  360. if err != nil {
  361. color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
  362. return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess, resolver)
  363. }
  364. resolver.AWSAccessKeyID = creds.AWSAccessKeyID
  365. resolver.AWSSecretAccessKey = creds.AWSSecretAccessKey
  366. resolver.AWSClusterID = creds.AWSClusterID
  367. return nil
  368. }
  369. // fallback to manual
  370. return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess, resolver)
  371. }
  372. func resolveAWSActionManual(
  373. endpoint string,
  374. clusterName string,
  375. awsClusterIDGuess string,
  376. resolver *models.ClusterResolverAll,
  377. ) error {
  378. // query to see if the AWS cluster ID guess is correct
  379. var clusterID string
  380. userResp, err := utils.PromptPlaintext(
  381. fmt.Sprintf(
  382. `Detected AWS cluster ID as %s. Is this correct? %s `,
  383. color.New(color.FgCyan).Sprintf(awsClusterIDGuess),
  384. color.New(color.FgCyan).Sprintf("[y/n]"),
  385. ),
  386. )
  387. if err != nil {
  388. return err
  389. }
  390. if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
  391. clusterID = awsClusterIDGuess
  392. } else {
  393. clusterID, err = utils.PromptPlaintext(fmt.Sprintf(`Cluster ID: `))
  394. if err != nil {
  395. return err
  396. }
  397. }
  398. // query for the access key id
  399. accessKeyID, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Access Key ID: `))
  400. if err != nil {
  401. return err
  402. }
  403. // query for the secret access key
  404. secretKey, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Secret Access Key: `))
  405. if err != nil {
  406. return err
  407. }
  408. resolver.AWSAccessKeyID = accessKeyID
  409. resolver.AWSSecretAccessKey = secretKey
  410. resolver.AWSClusterID = clusterID
  411. return nil
  412. }