2
0

cluster.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. package forms
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "net/url"
  6. "strings"
  7. "github.com/porter-dev/porter/internal/kubernetes"
  8. "github.com/porter-dev/porter/internal/models"
  9. "github.com/porter-dev/porter/internal/repository"
  10. "k8s.io/client-go/tools/clientcmd/api"
  11. ints "github.com/porter-dev/porter/internal/models/integrations"
  12. )
  13. // ResolveClusterForm will resolve a cluster candidate and create a new cluster
  14. type ResolveClusterForm struct {
  15. Resolver *models.ClusterResolverAll `form:"required"`
  16. ClusterCandidateID uint `json:"cluster_candidate_id" form:"required"`
  17. ProjectID uint `json:"project_id" form:"required"`
  18. UserID uint `json:"user_id" form:"required"`
  19. // populated during the ResolveIntegration step
  20. IntegrationID uint
  21. ClusterCandidate *models.ClusterCandidate
  22. RawConf *api.Config
  23. }
  24. // ResolveIntegration creates an integration in the DB
  25. func (rcf *ResolveClusterForm) ResolveIntegration(
  26. repo repository.Repository,
  27. ) error {
  28. cc, err := repo.Cluster.ReadClusterCandidate(rcf.ClusterCandidateID)
  29. if err != nil {
  30. return err
  31. }
  32. rcf.ClusterCandidate = cc
  33. rawConf, err := kubernetes.GetRawConfigFromBytes(cc.Kubeconfig)
  34. if err != nil {
  35. return err
  36. }
  37. rcf.RawConf = rawConf
  38. context := rawConf.Contexts[rawConf.CurrentContext]
  39. authInfoName := context.AuthInfo
  40. authInfo := rawConf.AuthInfos[authInfoName]
  41. // iterate through the resolvers, and use the ClusterResolverAll to populate
  42. // the required fields
  43. var id uint
  44. switch cc.AuthMechanism {
  45. case models.X509:
  46. id, err = rcf.resolveX509(repo, authInfo)
  47. case models.Bearer:
  48. id, err = rcf.resolveToken(repo, authInfo)
  49. case models.Basic:
  50. id, err = rcf.resolveBasic(repo, authInfo)
  51. case models.Local:
  52. id, err = rcf.resolveLocal(repo, authInfo)
  53. case models.OIDC:
  54. id, err = rcf.resolveOIDC(repo, authInfo)
  55. case models.GCP:
  56. id, err = rcf.resolveGCP(repo, authInfo)
  57. case models.AWS:
  58. id, err = rcf.resolveAWS(repo, authInfo)
  59. }
  60. if err != nil {
  61. return err
  62. }
  63. rcf.IntegrationID = id
  64. return nil
  65. }
  66. func (rcf *ResolveClusterForm) resolveX509(
  67. repo repository.Repository,
  68. authInfo *api.AuthInfo,
  69. ) (uint, error) {
  70. ki := &ints.KubeIntegration{
  71. Mechanism: ints.KubeX509,
  72. UserID: rcf.UserID,
  73. ProjectID: rcf.ProjectID,
  74. }
  75. // attempt to construct cert and key from raw config
  76. if len(authInfo.ClientCertificateData) > 0 {
  77. ki.ClientCertificateData = authInfo.ClientCertificateData
  78. }
  79. if len(authInfo.ClientKeyData) > 0 {
  80. ki.ClientKeyData = authInfo.ClientKeyData
  81. }
  82. // override with resolver
  83. if rcf.Resolver.ClientCertData != "" {
  84. decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClientCertData)
  85. if err != nil {
  86. return 0, err
  87. }
  88. ki.ClientCertificateData = decoded
  89. }
  90. if rcf.Resolver.ClientKeyData != "" {
  91. decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClientKeyData)
  92. if err != nil {
  93. return 0, err
  94. }
  95. ki.ClientKeyData = decoded
  96. }
  97. // if resolvable, write kube integration to repo
  98. if len(ki.ClientCertificateData) == 0 || len(ki.ClientKeyData) == 0 {
  99. return 0, errors.New("could not resolve kube integration (x509)")
  100. }
  101. // return integration id if exists
  102. ki, err := repo.KubeIntegration.CreateKubeIntegration(ki)
  103. if err != nil {
  104. return 0, err
  105. }
  106. return ki.Model.ID, nil
  107. }
  108. func (rcf *ResolveClusterForm) resolveToken(
  109. repo repository.Repository,
  110. authInfo *api.AuthInfo,
  111. ) (uint, error) {
  112. ki := &ints.KubeIntegration{
  113. Mechanism: ints.KubeBearer,
  114. UserID: rcf.UserID,
  115. ProjectID: rcf.ProjectID,
  116. }
  117. // attempt to construct token from raw config
  118. if len(authInfo.Token) > 0 {
  119. ki.Token = []byte(authInfo.Token)
  120. }
  121. // supplement with resolver
  122. if rcf.Resolver.TokenData != "" {
  123. ki.Token = []byte(rcf.Resolver.TokenData)
  124. }
  125. // if resolvable, write kube integration to repo
  126. if len(ki.Token) == 0 {
  127. return 0, errors.New("could not resolve kube integration (token)")
  128. }
  129. // return integration id if exists
  130. ki, err := repo.KubeIntegration.CreateKubeIntegration(ki)
  131. if err != nil {
  132. return 0, err
  133. }
  134. return ki.Model.ID, nil
  135. }
  136. func (rcf *ResolveClusterForm) resolveBasic(
  137. repo repository.Repository,
  138. authInfo *api.AuthInfo,
  139. ) (uint, error) {
  140. ki := &ints.KubeIntegration{
  141. Mechanism: ints.KubeBasic,
  142. UserID: rcf.UserID,
  143. ProjectID: rcf.ProjectID,
  144. }
  145. if len(authInfo.Username) > 0 {
  146. ki.Username = []byte(authInfo.Username)
  147. }
  148. if len(authInfo.Password) > 0 {
  149. ki.Password = []byte(authInfo.Password)
  150. }
  151. if len(ki.Username) == 0 || len(ki.Password) == 0 {
  152. return 0, errors.New("could not resolve kube integration (basic)")
  153. }
  154. // return integration id if exists
  155. ki, err := repo.KubeIntegration.CreateKubeIntegration(ki)
  156. if err != nil {
  157. return 0, err
  158. }
  159. return ki.Model.ID, nil
  160. }
  161. func (rcf *ResolveClusterForm) resolveLocal(
  162. repo repository.Repository,
  163. authInfo *api.AuthInfo,
  164. ) (uint, error) {
  165. ki := &ints.KubeIntegration{
  166. Mechanism: ints.KubeLocal,
  167. UserID: rcf.UserID,
  168. ProjectID: rcf.ProjectID,
  169. Kubeconfig: rcf.ClusterCandidate.Kubeconfig,
  170. }
  171. // return integration id if exists
  172. ki, err := repo.KubeIntegration.CreateKubeIntegration(ki)
  173. if err != nil {
  174. return 0, err
  175. }
  176. return ki.Model.ID, nil
  177. }
  178. func (rcf *ResolveClusterForm) resolveOIDC(
  179. repo repository.Repository,
  180. authInfo *api.AuthInfo,
  181. ) (uint, error) {
  182. oidc := &ints.OIDCIntegration{
  183. Client: ints.OIDCKube,
  184. UserID: rcf.UserID,
  185. ProjectID: rcf.ProjectID,
  186. }
  187. if url, ok := authInfo.AuthProvider.Config["idp-issuer-url"]; ok {
  188. oidc.IssuerURL = []byte(url)
  189. }
  190. if clientID, ok := authInfo.AuthProvider.Config["client-id"]; ok {
  191. oidc.ClientID = []byte(clientID)
  192. }
  193. if clientSecret, ok := authInfo.AuthProvider.Config["client-secret"]; ok {
  194. oidc.ClientSecret = []byte(clientSecret)
  195. }
  196. if caData, ok := authInfo.AuthProvider.Config["idp-certificate-authority-data"]; ok {
  197. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  198. // which means we will not decode it here
  199. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  200. oidc.CertificateAuthorityData = []byte(caData)
  201. }
  202. if idToken, ok := authInfo.AuthProvider.Config["id-token"]; ok {
  203. oidc.IDToken = []byte(idToken)
  204. }
  205. if refreshToken, ok := authInfo.AuthProvider.Config["refresh-token"]; ok {
  206. oidc.RefreshToken = []byte(refreshToken)
  207. }
  208. // override with resolver
  209. if rcf.Resolver.OIDCIssuerCAData != "" {
  210. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  211. // which means we will not decode it here
  212. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  213. oidc.CertificateAuthorityData = []byte(rcf.Resolver.OIDCIssuerCAData)
  214. }
  215. // return integration id if exists
  216. oidc, err := repo.OIDCIntegration.CreateOIDCIntegration(oidc)
  217. if err != nil {
  218. return 0, err
  219. }
  220. return oidc.Model.ID, nil
  221. }
  222. func (rcf *ResolveClusterForm) resolveGCP(
  223. repo repository.Repository,
  224. authInfo *api.AuthInfo,
  225. ) (uint, error) {
  226. // TODO -- add GCP project ID and GCP email so that source is trackable
  227. gcp := &ints.GCPIntegration{
  228. UserID: rcf.UserID,
  229. ProjectID: rcf.ProjectID,
  230. }
  231. // supplement with resolver
  232. if rcf.Resolver.GCPKeyData != "" {
  233. gcp.GCPKeyData = []byte(rcf.Resolver.GCPKeyData)
  234. }
  235. // throw error if no data
  236. if len(gcp.GCPKeyData) == 0 {
  237. return 0, errors.New("could not resolve gcp integration")
  238. }
  239. // return integration id if exists
  240. gcp, err := repo.GCPIntegration.CreateGCPIntegration(gcp)
  241. if err != nil {
  242. return 0, err
  243. }
  244. return gcp.Model.ID, nil
  245. }
  246. func (rcf *ResolveClusterForm) resolveAWS(
  247. repo repository.Repository,
  248. authInfo *api.AuthInfo,
  249. ) (uint, error) {
  250. // TODO -- add AWS session token as an optional param
  251. // TODO -- add AWS entity and user ARN
  252. aws := &ints.AWSIntegration{
  253. UserID: rcf.UserID,
  254. ProjectID: rcf.ProjectID,
  255. }
  256. // override with resolver
  257. if rcf.Resolver.AWSClusterID != "" {
  258. aws.AWSClusterID = []byte(rcf.Resolver.AWSClusterID)
  259. }
  260. if rcf.Resolver.AWSAccessKeyID != "" {
  261. aws.AWSAccessKeyID = []byte(rcf.Resolver.AWSAccessKeyID)
  262. }
  263. if rcf.Resolver.AWSSecretAccessKey != "" {
  264. aws.AWSSecretAccessKey = []byte(rcf.Resolver.AWSSecretAccessKey)
  265. }
  266. // throw error if no data
  267. if len(aws.AWSClusterID) == 0 || len(aws.AWSAccessKeyID) == 0 || len(aws.AWSSecretAccessKey) == 0 {
  268. return 0, errors.New("could not resolve aws integration")
  269. }
  270. // return integration id if exists
  271. aws, err := repo.AWSIntegration.CreateAWSIntegration(aws)
  272. if err != nil {
  273. return 0, err
  274. }
  275. return aws.Model.ID, nil
  276. }
  277. // ResolveCluster writes a new cluster to the DB -- this must be called after
  278. // rcf.ResolveIntegration, since it relies on the previously created integration.
  279. func (rcf *ResolveClusterForm) ResolveCluster(
  280. repo repository.Repository,
  281. ) (*models.Cluster, error) {
  282. // build a cluster from the candidate
  283. cluster, err := rcf.buildCluster()
  284. if err != nil {
  285. return nil, err
  286. }
  287. // save cluster to db
  288. return repo.Cluster.CreateCluster(cluster)
  289. }
  290. func (rcf *ResolveClusterForm) buildCluster() (*models.Cluster, error) {
  291. rawConf := rcf.RawConf
  292. kcContext := rawConf.Contexts[rawConf.CurrentContext]
  293. kcAuthInfoName := kcContext.AuthInfo
  294. kcAuthInfo := rawConf.AuthInfos[kcAuthInfoName]
  295. kcClusterName := kcContext.Cluster
  296. kcCluster := rawConf.Clusters[kcClusterName]
  297. cc := rcf.ClusterCandidate
  298. cluster := &models.Cluster{
  299. AuthMechanism: cc.AuthMechanism,
  300. ProjectID: cc.ProjectID,
  301. Name: cc.Name,
  302. Server: cc.Server,
  303. ClusterLocationOfOrigin: kcCluster.LocationOfOrigin,
  304. TLSServerName: kcCluster.TLSServerName,
  305. InsecureSkipTLSVerify: kcCluster.InsecureSkipTLSVerify,
  306. UserLocationOfOrigin: kcAuthInfo.LocationOfOrigin,
  307. UserImpersonate: kcAuthInfo.Impersonate,
  308. }
  309. if len(kcAuthInfo.ImpersonateGroups) > 0 {
  310. cluster.UserImpersonateGroups = strings.Join(kcAuthInfo.ImpersonateGroups, ",")
  311. }
  312. if len(kcCluster.CertificateAuthorityData) > 0 {
  313. cluster.CertificateAuthorityData = kcCluster.CertificateAuthorityData
  314. }
  315. if rcf.Resolver.ClusterCAData != "" {
  316. decoded, err := base64.StdEncoding.DecodeString(rcf.Resolver.ClusterCAData)
  317. // skip if decoding error
  318. if err != nil {
  319. return nil, err
  320. }
  321. cluster.CertificateAuthorityData = decoded
  322. }
  323. if rcf.Resolver.ClusterHostname != "" {
  324. serverURL, err := url.Parse(cluster.Server)
  325. if err != nil {
  326. return nil, err
  327. }
  328. if serverURL.Port() == "" {
  329. serverURL.Host = rcf.Resolver.ClusterHostname
  330. } else {
  331. serverURL.Host = rcf.Resolver.ClusterHostname + ":" + serverURL.Port()
  332. }
  333. cluster.Server = serverURL.String()
  334. }
  335. switch cc.AuthMechanism {
  336. case models.X509, models.Bearer, models.Basic, models.Local:
  337. cluster.KubeIntegrationID = rcf.IntegrationID
  338. case models.OIDC:
  339. cluster.OIDCIntegrationID = rcf.IntegrationID
  340. case models.GCP:
  341. cluster.GCPIntegrationID = rcf.IntegrationID
  342. case models.AWS:
  343. cluster.AWSIntegrationID = rcf.IntegrationID
  344. }
  345. return cluster, nil
  346. }