action.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. package forms
  2. import (
  3. "encoding/base64"
  4. "net/url"
  5. "strings"
  6. "github.com/porter-dev/porter/internal/kubernetes"
  7. "github.com/porter-dev/porter/internal/models"
  8. "github.com/porter-dev/porter/internal/repository"
  9. )
  10. // ActionResolver exposes an interface for resolving an action as a ServiceAccount.
  11. // So that actions can be chained together, a pointer to a serviceAccount can be
  12. // used -- if this points to nil, a new service account is created
  13. type ActionResolver interface {
  14. PopulateServiceAccount(repo repository.ServiceAccountRepository) error
  15. }
  16. // ServiceAccountActionResolver is the base type for resolving a ServiceAccountAction
  17. // that belongs to a given ServiceAccountCandidate
  18. type ServiceAccountActionResolver struct {
  19. ServiceAccountCandidateID uint `json:"sa_candidate_id" form:"required"`
  20. SA *models.ServiceAccount
  21. SACandidate *models.ServiceAccountCandidate
  22. }
  23. // PopulateServiceAccount will create a service account if it does not exist,
  24. // or will append a new cluster given by a ServiceAccountCandidate to the
  25. // ServiceAccount
  26. func (sar *ServiceAccountActionResolver) PopulateServiceAccount(
  27. repo repository.ServiceAccountRepository,
  28. ) error {
  29. var err error
  30. id := sar.ServiceAccountCandidateID
  31. if sar.SACandidate == nil {
  32. sar.SACandidate, err = repo.ReadServiceAccountCandidate(id)
  33. if err != nil {
  34. return err
  35. }
  36. }
  37. rawConf, err := kubernetes.GetRawConfigFromBytes(sar.SACandidate.Kubeconfig)
  38. if err != nil {
  39. return err
  40. }
  41. context := rawConf.Contexts[rawConf.CurrentContext]
  42. authInfoName := context.AuthInfo
  43. authInfo := rawConf.AuthInfos[authInfoName]
  44. clusterName := context.Cluster
  45. cluster := rawConf.Clusters[clusterName]
  46. modelCluster := models.Cluster{
  47. Name: clusterName,
  48. LocationOfOrigin: cluster.LocationOfOrigin,
  49. Server: cluster.Server,
  50. TLSServerName: cluster.TLSServerName,
  51. InsecureSkipTLSVerify: cluster.InsecureSkipTLSVerify,
  52. }
  53. if len(cluster.CertificateAuthorityData) > 0 {
  54. modelCluster.CertificateAuthorityData = cluster.CertificateAuthorityData
  55. }
  56. if sar.SA == nil {
  57. sar.SA = &models.ServiceAccount{
  58. ProjectID: sar.SACandidate.ProjectID,
  59. Kind: sar.SACandidate.Kind,
  60. Clusters: []models.Cluster{modelCluster},
  61. AuthMechanism: sar.SACandidate.AuthMechanism,
  62. LocationOfOrigin: authInfo.LocationOfOrigin,
  63. Impersonate: authInfo.Impersonate,
  64. ImpersonateGroups: strings.Join(authInfo.ImpersonateGroups, ","),
  65. }
  66. } else {
  67. doesClusterExist := false
  68. for _, cluster := range sar.SA.Clusters {
  69. if cluster.Name == sar.SACandidate.ClusterName && cluster.Server == sar.SACandidate.ClusterEndpoint {
  70. doesClusterExist = true
  71. }
  72. }
  73. if !doesClusterExist {
  74. sar.SA.Clusters = append(sar.SA.Clusters, modelCluster)
  75. }
  76. }
  77. // if auth mechanism is local, just write the kubeconfig and return: rest of config is
  78. // unnecessary
  79. if sar.SACandidate.AuthMechanism == models.Local && len(sar.SACandidate.Kubeconfig) > 0 {
  80. sar.SA.Kubeconfig = sar.SACandidate.Kubeconfig
  81. return nil
  82. }
  83. if len(authInfo.ClientCertificateData) > 0 {
  84. sar.SA.ClientCertificateData = authInfo.ClientCertificateData
  85. }
  86. if len(authInfo.ClientKeyData) > 0 {
  87. sar.SA.ClientKeyData = authInfo.ClientKeyData
  88. }
  89. if len(authInfo.Token) > 0 {
  90. sar.SA.Token = []byte(authInfo.Token)
  91. }
  92. if len(authInfo.Username) > 0 {
  93. sar.SA.Username = []byte(authInfo.Username)
  94. }
  95. if len(authInfo.Password) > 0 {
  96. sar.SA.Password = []byte(authInfo.Password)
  97. }
  98. if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" {
  99. if url, ok := authInfo.AuthProvider.Config["idp-issuer-url"]; ok {
  100. sar.SA.OIDCIssuerURL = []byte(url)
  101. }
  102. if clientID, ok := authInfo.AuthProvider.Config["client-id"]; ok {
  103. sar.SA.OIDCClientID = []byte(clientID)
  104. }
  105. if clientSecret, ok := authInfo.AuthProvider.Config["client-secret"]; ok {
  106. sar.SA.OIDCClientSecret = []byte(clientSecret)
  107. }
  108. if caData, ok := authInfo.AuthProvider.Config["idp-certificate-authority-data"]; ok {
  109. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  110. // which means we will not decode it here
  111. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  112. sar.SA.OIDCCertificateAuthorityData = []byte(caData)
  113. }
  114. if idToken, ok := authInfo.AuthProvider.Config["id-token"]; ok {
  115. sar.SA.OIDCIDToken = []byte(idToken)
  116. }
  117. if refreshToken, ok := authInfo.AuthProvider.Config["refresh-token"]; ok {
  118. sar.SA.OIDCRefreshToken = []byte(refreshToken)
  119. }
  120. }
  121. return nil
  122. }
  123. // ClusterCADataAction contains the base64 encoded cluster CA data
  124. type ClusterCADataAction struct {
  125. *ServiceAccountActionResolver
  126. ClusterCAData string `json:"cluster_ca_data" form:"required"`
  127. }
  128. // PopulateServiceAccount will add cluster ca data to a cluster in the ServiceAccount's
  129. // list of clusters
  130. func (cda *ClusterCADataAction) PopulateServiceAccount(
  131. repo repository.ServiceAccountRepository,
  132. ) error {
  133. err := cda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  134. if err != nil {
  135. return err
  136. }
  137. saCandidate := cda.ServiceAccountActionResolver.SACandidate
  138. for i, cluster := range cda.ServiceAccountActionResolver.SA.Clusters {
  139. if cluster.Name == saCandidate.ClusterName && cluster.Server == saCandidate.ClusterEndpoint {
  140. decoded, err := base64.StdEncoding.DecodeString(cda.ClusterCAData)
  141. // skip if decoding error
  142. if err != nil {
  143. return err
  144. }
  145. (&cluster).CertificateAuthorityData = decoded
  146. cda.ServiceAccountActionResolver.SA.Clusters[i] = cluster
  147. }
  148. }
  149. return nil
  150. }
  151. // ClusterLocalhostAction contains the non-localhost server
  152. type ClusterLocalhostAction struct {
  153. *ServiceAccountActionResolver
  154. ClusterHostname string `json:"cluster_hostname" form:"required"`
  155. }
  156. // PopulateServiceAccount will add cluster ca data to a cluster in the ServiceAccount's
  157. // list of clusters
  158. func (cla *ClusterLocalhostAction) PopulateServiceAccount(
  159. repo repository.ServiceAccountRepository,
  160. ) error {
  161. err := cla.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  162. if err != nil {
  163. return err
  164. }
  165. saCandidate := cla.ServiceAccountActionResolver.SACandidate
  166. for i, cluster := range cla.ServiceAccountActionResolver.SA.Clusters {
  167. if cluster.Name == saCandidate.ClusterName && cluster.Server == saCandidate.ClusterEndpoint {
  168. serverURL, err := url.Parse(cluster.Server)
  169. if err != nil {
  170. continue
  171. }
  172. if serverURL.Port() == "" {
  173. serverURL.Host = cla.ClusterHostname
  174. } else {
  175. serverURL.Host = cla.ClusterHostname + ":" + serverURL.Port()
  176. }
  177. (&cluster).Server = serverURL.String()
  178. cla.ServiceAccountActionResolver.SA.Clusters[i] = cluster
  179. }
  180. }
  181. return nil
  182. }
  183. // ClientCertDataAction contains the base64 encoded cluster cert data
  184. type ClientCertDataAction struct {
  185. *ServiceAccountActionResolver
  186. ClientCertData string `json:"client_cert_data" form:"required"`
  187. }
  188. // PopulateServiceAccount will add client CA data to a ServiceAccount
  189. func (ccda *ClientCertDataAction) PopulateServiceAccount(
  190. repo repository.ServiceAccountRepository,
  191. ) error {
  192. err := ccda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  193. if err != nil {
  194. return err
  195. }
  196. decoded, err := base64.StdEncoding.DecodeString(ccda.ClientCertData)
  197. // skip if decoding error
  198. if err != nil {
  199. return err
  200. }
  201. ccda.ServiceAccountActionResolver.SA.ClientCertificateData = decoded
  202. return nil
  203. }
  204. // ClientKeyDataAction contains the base64 encoded cluster key data
  205. type ClientKeyDataAction struct {
  206. *ServiceAccountActionResolver
  207. ClientKeyData string `json:"client_key_data" form:"required"`
  208. }
  209. // PopulateServiceAccount will add client CA data to a ServiceAccount
  210. func (ckda *ClientKeyDataAction) PopulateServiceAccount(
  211. repo repository.ServiceAccountRepository,
  212. ) error {
  213. err := ckda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  214. if err != nil {
  215. return err
  216. }
  217. decoded, err := base64.StdEncoding.DecodeString(ckda.ClientKeyData)
  218. // skip if decoding error
  219. if err != nil {
  220. return err
  221. }
  222. ckda.ServiceAccountActionResolver.SA.ClientKeyData = decoded
  223. return nil
  224. }
  225. // OIDCIssuerDataAction contains the base64 encoded IDP issuer CA data
  226. type OIDCIssuerDataAction struct {
  227. *ServiceAccountActionResolver
  228. OIDCIssuerCAData string `json:"oidc_idp_issuer_ca_data" form:"required"`
  229. }
  230. // PopulateServiceAccount will add OIDC issuer CA data to a ServiceAccount
  231. func (oida *OIDCIssuerDataAction) PopulateServiceAccount(
  232. repo repository.ServiceAccountRepository,
  233. ) error {
  234. err := oida.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  235. if err != nil {
  236. return err
  237. }
  238. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  239. // which means we will not decode it here
  240. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  241. oida.ServiceAccountActionResolver.SA.OIDCCertificateAuthorityData = []byte(oida.OIDCIssuerCAData)
  242. return nil
  243. }
  244. // TokenDataAction contains the token data to use
  245. type TokenDataAction struct {
  246. *ServiceAccountActionResolver
  247. TokenData string `json:"token_data" form:"required"`
  248. }
  249. // PopulateServiceAccount will add bearer token data to a ServiceAccount
  250. func (tda *TokenDataAction) PopulateServiceAccount(
  251. repo repository.ServiceAccountRepository,
  252. ) error {
  253. err := tda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  254. if err != nil {
  255. return err
  256. }
  257. tda.ServiceAccountActionResolver.SA.Token = []byte(tda.TokenData)
  258. return nil
  259. }
  260. // GCPKeyDataAction contains the GCP key data
  261. type GCPKeyDataAction struct {
  262. *ServiceAccountActionResolver
  263. GCPKeyData string `json:"gcp_key_data" form:"required"`
  264. }
  265. // PopulateServiceAccount will add GCP key data to a ServiceAccount
  266. func (gkda *GCPKeyDataAction) PopulateServiceAccount(
  267. repo repository.ServiceAccountRepository,
  268. ) error {
  269. err := gkda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  270. if err != nil {
  271. return err
  272. }
  273. gkda.ServiceAccountActionResolver.SA.GCPKeyData = []byte(gkda.GCPKeyData)
  274. return nil
  275. }
  276. // AWSDataAction contains the AWS data (access id, key)
  277. type AWSDataAction struct {
  278. *ServiceAccountActionResolver
  279. AWSAccessKeyID string `json:"aws_access_key_id" form:"required"`
  280. AWSSecretAccessKey string `json:"aws_secret_access_key" form:"required"`
  281. AWSClusterID string `json:"aws_cluster_id" form:"required"`
  282. }
  283. // PopulateServiceAccount will add GCP key data to a ServiceAccount
  284. func (akda *AWSDataAction) PopulateServiceAccount(
  285. repo repository.ServiceAccountRepository,
  286. ) error {
  287. err := akda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  288. if err != nil {
  289. return err
  290. }
  291. akda.ServiceAccountActionResolver.SA.AWSAccessKeyID = []byte(akda.AWSAccessKeyID)
  292. akda.ServiceAccountActionResolver.SA.AWSSecretAccessKey = []byte(akda.AWSSecretAccessKey)
  293. akda.ServiceAccountActionResolver.SA.AWSClusterID = []byte(akda.AWSClusterID)
  294. return nil
  295. }