kubeconfig.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. "github.com/porter-dev/porter/cli/cmd/utils"
  12. "github.com/porter-dev/porter/internal/kubernetes/local"
  13. gcpLocal "github.com/porter-dev/porter/internal/providers/gcp/local"
  14. "github.com/porter-dev/porter/cli/cmd/api"
  15. "github.com/porter-dev/porter/internal/models"
  16. )
  17. // Kubeconfig creates a service account for a project by parsing the local
  18. // kubeconfig and resolving actions that must be performed.
  19. func Kubeconfig(
  20. client *api.Client,
  21. kubeconfigPath string,
  22. contexts []string,
  23. projectID uint,
  24. ) error {
  25. // if project ID is 0, ask the user to set the project ID or create a project
  26. if projectID == 0 {
  27. return fmt.Errorf("no project set, please run porter project set [id]")
  28. }
  29. // get the kubeconfig
  30. rawBytes, err := local.GetKubeconfigFromHost(kubeconfigPath, contexts)
  31. if err != nil {
  32. return err
  33. }
  34. // send kubeconfig to client
  35. saCandidates, err := client.CreateProjectCandidates(
  36. context.Background(),
  37. projectID,
  38. &api.CreateProjectCandidatesRequest{
  39. Kubeconfig: string(rawBytes),
  40. },
  41. )
  42. if err != nil {
  43. return err
  44. }
  45. for _, saCandidate := range saCandidates {
  46. if len(saCandidate.Actions) > 0 {
  47. resolvers := make(api.CreateProjectServiceAccountRequest, 0)
  48. for _, action := range saCandidate.Actions {
  49. switch action.Name {
  50. case models.ClusterCADataAction:
  51. resolveAction, err := resolveClusterCAAction(action.Filename)
  52. if err != nil {
  53. return err
  54. }
  55. resolvers = append(resolvers, resolveAction)
  56. case models.ClientCertDataAction:
  57. resolveAction, err := resolveClientCertAction(action.Filename)
  58. if err != nil {
  59. return err
  60. }
  61. resolvers = append(resolvers, resolveAction)
  62. case models.ClientKeyDataAction:
  63. resolveAction, err := resolveClientKeyAction(action.Filename)
  64. if err != nil {
  65. return err
  66. }
  67. resolvers = append(resolvers, resolveAction)
  68. case models.OIDCIssuerDataAction:
  69. resolveAction, err := resolveOIDCIssuerAction(action.Filename)
  70. if err != nil {
  71. return err
  72. }
  73. resolvers = append(resolvers, resolveAction)
  74. case models.TokenDataAction:
  75. resolveAction, err := resolveTokenDataAction(action.Filename)
  76. if err != nil {
  77. return err
  78. }
  79. resolvers = append(resolvers, resolveAction)
  80. case models.GCPKeyDataAction:
  81. resolveAction, err := resolveGCPKeyAction(
  82. saCandidate.ClusterEndpoint,
  83. saCandidate.ClusterName,
  84. )
  85. if err != nil {
  86. return err
  87. }
  88. resolvers = append(resolvers, resolveAction)
  89. case models.AWSDataAction:
  90. resolveAction, err := resolveAWSAction(
  91. saCandidate.ClusterEndpoint,
  92. saCandidate.ClusterName,
  93. saCandidate.AWSClusterIDGuess,
  94. )
  95. if err != nil {
  96. return err
  97. }
  98. resolvers = append(resolvers, resolveAction)
  99. }
  100. }
  101. sa, err := client.CreateProjectServiceAccount(
  102. context.Background(),
  103. projectID,
  104. saCandidate.ID,
  105. resolvers,
  106. )
  107. if err != nil {
  108. return err
  109. }
  110. for _, cluster := range sa.Clusters {
  111. color.New(color.FgGreen).Printf("created service account for cluster %s with id %d\n", cluster.Name, sa.ID)
  112. // sanity check to ensure it's working
  113. namespaces, err := client.GetK8sNamespaces(
  114. context.Background(),
  115. projectID,
  116. sa.ID,
  117. cluster.ID,
  118. )
  119. if err != nil {
  120. return err
  121. }
  122. for _, ns := range namespaces.Items {
  123. fmt.Println(ns.ObjectMeta.GetName())
  124. }
  125. }
  126. }
  127. }
  128. return nil
  129. }
  130. // resolves a cluster ca data action
  131. func resolveClusterCAAction(
  132. filename string,
  133. ) (*models.ServiceAccountAllActions, error) {
  134. fileBytes, err := ioutil.ReadFile(filename)
  135. if err != nil {
  136. return nil, err
  137. }
  138. return &models.ServiceAccountAllActions{
  139. Name: models.ClusterCADataAction,
  140. ClusterCAData: base64.StdEncoding.EncodeToString(fileBytes),
  141. }, nil
  142. }
  143. // resolves a client cert data action
  144. func resolveClientCertAction(
  145. filename string,
  146. ) (*models.ServiceAccountAllActions, error) {
  147. fileBytes, err := ioutil.ReadFile(filename)
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &models.ServiceAccountAllActions{
  152. Name: models.ClientCertDataAction,
  153. ClientCertData: base64.StdEncoding.EncodeToString(fileBytes),
  154. }, nil
  155. }
  156. // resolves a client key data action
  157. func resolveClientKeyAction(
  158. filename string,
  159. ) (*models.ServiceAccountAllActions, error) {
  160. fileBytes, err := ioutil.ReadFile(filename)
  161. if err != nil {
  162. return nil, err
  163. }
  164. return &models.ServiceAccountAllActions{
  165. Name: models.ClientKeyDataAction,
  166. ClientKeyData: base64.StdEncoding.EncodeToString(fileBytes),
  167. }, nil
  168. }
  169. // resolves an oidc issuer data action
  170. func resolveOIDCIssuerAction(
  171. filename string,
  172. ) (*models.ServiceAccountAllActions, error) {
  173. fileBytes, err := ioutil.ReadFile(filename)
  174. if err != nil {
  175. return nil, err
  176. }
  177. return &models.ServiceAccountAllActions{
  178. Name: models.OIDCIssuerDataAction,
  179. OIDCIssuerCAData: base64.StdEncoding.EncodeToString(fileBytes),
  180. }, nil
  181. }
  182. // resolves a token data action
  183. func resolveTokenDataAction(
  184. filename string,
  185. ) (*models.ServiceAccountAllActions, error) {
  186. fileBytes, err := ioutil.ReadFile(filename)
  187. if err != nil {
  188. return nil, err
  189. }
  190. return &models.ServiceAccountAllActions{
  191. Name: models.TokenDataAction,
  192. TokenData: string(fileBytes),
  193. }, nil
  194. }
  195. // resolves a gcp key data action
  196. func resolveGCPKeyAction(endpoint string, clusterName string) (*models.ServiceAccountAllActions, error) {
  197. userResp, err := utils.PromptPlaintext(
  198. fmt.Sprintf(
  199. `Detected GKE cluster in kubeconfig for the endpoint %s (%s).
  200. Porter can set up a service account in your GCP project to connect to this cluster automatically.
  201. Would you like to proceed? %s `,
  202. color.New(color.FgCyan).Sprintf("%s", endpoint),
  203. clusterName,
  204. color.New(color.FgCyan).Sprintf("[y/n]"),
  205. ),
  206. )
  207. if err != nil {
  208. return nil, err
  209. }
  210. if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
  211. agent, _ := gcpLocal.NewDefaultAgent()
  212. projID, err := agent.GetProjectIDForGKECluster(endpoint)
  213. if err != nil {
  214. return nil, err
  215. }
  216. agent.ProjectID = projID
  217. name := "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
  218. // create the service account and give it the correct iam permissions
  219. resp, err := agent.CreateServiceAccount(name)
  220. if err != nil {
  221. color.New(color.FgRed).Println("Automatic creation failed, manual input required.")
  222. return resolveGCPKeyActionManual(endpoint, clusterName)
  223. }
  224. err = agent.SetServiceAccountIAMPolicy(resp)
  225. if err != nil {
  226. return nil, err
  227. }
  228. // get the service account key data to send to the server
  229. bytes, err := agent.CreateServiceAccountKey(resp)
  230. if err != nil {
  231. return nil, err
  232. }
  233. return &models.ServiceAccountAllActions{
  234. Name: models.GCPKeyDataAction,
  235. GCPKeyData: string(bytes),
  236. }, nil
  237. }
  238. return resolveGCPKeyActionManual(endpoint, clusterName)
  239. }
  240. func resolveGCPKeyActionManual(endpoint string, clusterName string) (*models.ServiceAccountAllActions, error) {
  241. keyFileLocation, err := utils.PromptPlaintext(fmt.Sprintf(`Please provide the full path to a service account key file.
  242. Key file location: `))
  243. if err != nil {
  244. return nil, err
  245. }
  246. // attempt to read the key file location
  247. if info, err := os.Stat(keyFileLocation); !os.IsNotExist(err) && !info.IsDir() {
  248. // read the file
  249. bytes, err := ioutil.ReadFile(keyFileLocation)
  250. if err != nil {
  251. return nil, err
  252. }
  253. return &models.ServiceAccountAllActions{
  254. Name: models.GCPKeyDataAction,
  255. GCPKeyData: string(bytes),
  256. }, nil
  257. }
  258. return nil, errors.New("Key file not found")
  259. }
  260. // resolves an aws key data action
  261. func resolveAWSAction(
  262. endpoint string,
  263. clusterName string,
  264. awsClusterIDGuess string,
  265. ) (*models.ServiceAccountAllActions, error) {
  266. // just support manual for now
  267. return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess)
  268. }
  269. func resolveAWSActionManual(
  270. endpoint string,
  271. clusterName string,
  272. awsClusterIDGuess string,
  273. ) (*models.ServiceAccountAllActions, error) {
  274. // query to see if the AWS cluster ID guess is correct
  275. var clusterID string
  276. userResp, err := utils.PromptPlaintext(
  277. fmt.Sprintf(
  278. `Detected AWS cluster ID as %s. Is this correct? %s `,
  279. color.New(color.FgCyan).Sprintf(awsClusterIDGuess),
  280. color.New(color.FgCyan).Sprintf("[y/n]"),
  281. ),
  282. )
  283. if err != nil {
  284. return nil, err
  285. }
  286. if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
  287. clusterID = awsClusterIDGuess
  288. } else {
  289. clusterID, err = utils.PromptPlaintext(fmt.Sprintf(`Cluster ID: `))
  290. if err != nil {
  291. return nil, err
  292. }
  293. }
  294. // query for the access key id
  295. accessKeyID, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Access Key ID: `))
  296. if err != nil {
  297. return nil, err
  298. }
  299. // query for the secret access key
  300. secretKey, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Secret Access Key: `))
  301. if err != nil {
  302. return nil, err
  303. }
  304. return &models.ServiceAccountAllActions{
  305. Name: models.AWSDataAction,
  306. AWSAccessKeyID: accessKeyID,
  307. AWSSecretAccessKey: secretKey,
  308. AWSClusterID: clusterID,
  309. }, nil
  310. }