kubeconfig.go 9.0 KB

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