action.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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 len(authInfo.ClientCertificateData) > 0 {
  78. sar.SA.ClientCertificateData = authInfo.ClientCertificateData
  79. }
  80. if len(authInfo.ClientKeyData) > 0 {
  81. sar.SA.ClientKeyData = authInfo.ClientKeyData
  82. }
  83. if len(authInfo.Token) > 0 {
  84. sar.SA.Token = []byte(authInfo.Token)
  85. }
  86. if len(authInfo.Username) > 0 {
  87. sar.SA.Username = []byte(authInfo.Username)
  88. }
  89. if len(authInfo.Password) > 0 {
  90. sar.SA.Password = []byte(authInfo.Password)
  91. }
  92. if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" {
  93. if url, ok := authInfo.AuthProvider.Config["idp-issuer-url"]; ok {
  94. sar.SA.OIDCIssuerURL = []byte(url)
  95. }
  96. if clientID, ok := authInfo.AuthProvider.Config["client-id"]; ok {
  97. sar.SA.OIDCClientID = []byte(clientID)
  98. }
  99. if clientSecret, ok := authInfo.AuthProvider.Config["client-secret"]; ok {
  100. sar.SA.OIDCClientSecret = []byte(clientSecret)
  101. }
  102. if caData, ok := authInfo.AuthProvider.Config["idp-certificate-authority-data"]; ok {
  103. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  104. // which means we will not decode it here
  105. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  106. sar.SA.OIDCCertificateAuthorityData = []byte(caData)
  107. }
  108. if idToken, ok := authInfo.AuthProvider.Config["id-token"]; ok {
  109. sar.SA.OIDCIDToken = []byte(idToken)
  110. }
  111. if refreshToken, ok := authInfo.AuthProvider.Config["refresh-token"]; ok {
  112. sar.SA.OIDCRefreshToken = []byte(refreshToken)
  113. }
  114. }
  115. return nil
  116. }
  117. // ClusterCADataAction contains the base64 encoded cluster CA data
  118. type ClusterCADataAction struct {
  119. *ServiceAccountActionResolver
  120. ClusterCAData string `json:"cluster_ca_data" form:"required"`
  121. }
  122. // PopulateServiceAccount will add cluster ca data to a cluster in the ServiceAccount's
  123. // list of clusters
  124. func (cda *ClusterCADataAction) PopulateServiceAccount(
  125. repo repository.ServiceAccountRepository,
  126. ) error {
  127. err := cda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  128. if err != nil {
  129. return err
  130. }
  131. saCandidate := cda.ServiceAccountActionResolver.SACandidate
  132. for i, cluster := range cda.ServiceAccountActionResolver.SA.Clusters {
  133. if cluster.Name == saCandidate.ClusterName && cluster.Server == saCandidate.ClusterEndpoint {
  134. decoded, err := base64.StdEncoding.DecodeString(cda.ClusterCAData)
  135. // skip if decoding error
  136. if err != nil {
  137. return err
  138. }
  139. (&cluster).CertificateAuthorityData = decoded
  140. cda.ServiceAccountActionResolver.SA.Clusters[i] = cluster
  141. }
  142. }
  143. return nil
  144. }
  145. // ClusterLocalhostAction contains the non-localhost server
  146. type ClusterLocalhostAction struct {
  147. *ServiceAccountActionResolver
  148. ClusterHostname string `json:"cluster_hostname" form:"required"`
  149. }
  150. // PopulateServiceAccount will add cluster ca data to a cluster in the ServiceAccount's
  151. // list of clusters
  152. func (cla *ClusterLocalhostAction) PopulateServiceAccount(
  153. repo repository.ServiceAccountRepository,
  154. ) error {
  155. err := cla.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  156. if err != nil {
  157. return err
  158. }
  159. saCandidate := cla.ServiceAccountActionResolver.SACandidate
  160. for i, cluster := range cla.ServiceAccountActionResolver.SA.Clusters {
  161. if cluster.Name == saCandidate.ClusterName && cluster.Server == saCandidate.ClusterEndpoint {
  162. serverURL, err := url.Parse(cluster.Server)
  163. if err != nil {
  164. continue
  165. }
  166. if serverURL.Port() == "" {
  167. serverURL.Host = cla.ClusterHostname
  168. } else {
  169. serverURL.Host = cla.ClusterHostname + ":" + serverURL.Port()
  170. }
  171. (&cluster).Server = serverURL.String()
  172. cla.ServiceAccountActionResolver.SA.Clusters[i] = cluster
  173. }
  174. }
  175. return nil
  176. }
  177. // ClientCertDataAction contains the base64 encoded cluster cert data
  178. type ClientCertDataAction struct {
  179. *ServiceAccountActionResolver
  180. ClientCertData string `json:"client_cert_data" form:"required"`
  181. }
  182. // PopulateServiceAccount will add client CA data to a ServiceAccount
  183. func (ccda *ClientCertDataAction) PopulateServiceAccount(
  184. repo repository.ServiceAccountRepository,
  185. ) error {
  186. err := ccda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  187. if err != nil {
  188. return err
  189. }
  190. decoded, err := base64.StdEncoding.DecodeString(ccda.ClientCertData)
  191. // skip if decoding error
  192. if err != nil {
  193. return err
  194. }
  195. ccda.ServiceAccountActionResolver.SA.ClientCertificateData = decoded
  196. return nil
  197. }
  198. // ClientKeyDataAction contains the base64 encoded cluster key data
  199. type ClientKeyDataAction struct {
  200. *ServiceAccountActionResolver
  201. ClientKeyData string `json:"client_key_data" form:"required"`
  202. }
  203. // PopulateServiceAccount will add client CA data to a ServiceAccount
  204. func (ckda *ClientKeyDataAction) PopulateServiceAccount(
  205. repo repository.ServiceAccountRepository,
  206. ) error {
  207. err := ckda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  208. if err != nil {
  209. return err
  210. }
  211. decoded, err := base64.StdEncoding.DecodeString(ckda.ClientKeyData)
  212. // skip if decoding error
  213. if err != nil {
  214. return err
  215. }
  216. ckda.ServiceAccountActionResolver.SA.ClientKeyData = decoded
  217. return nil
  218. }
  219. // OIDCIssuerDataAction contains the base64 encoded IDP issuer CA data
  220. type OIDCIssuerDataAction struct {
  221. *ServiceAccountActionResolver
  222. OIDCIssuerCAData string `json:"oidc_idp_issuer_ca_data" form:"required"`
  223. }
  224. // PopulateServiceAccount will add OIDC issuer CA data to a ServiceAccount
  225. func (oida *OIDCIssuerDataAction) PopulateServiceAccount(
  226. repo repository.ServiceAccountRepository,
  227. ) error {
  228. err := oida.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  229. if err != nil {
  230. return err
  231. }
  232. // based on the implementation, the oidc plugin expects the data to be base64 encoded,
  233. // which means we will not decode it here
  234. // reference: https://github.com/kubernetes/kubernetes/blob/9dfb4c876bfca7a5ae84259fae2bc337ed90c2d7/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go#L135
  235. oida.ServiceAccountActionResolver.SA.OIDCCertificateAuthorityData = []byte(oida.OIDCIssuerCAData)
  236. return nil
  237. }
  238. // TokenDataAction contains the token data to use
  239. type TokenDataAction struct {
  240. *ServiceAccountActionResolver
  241. TokenData string `json:"token_data" form:"required"`
  242. }
  243. // PopulateServiceAccount will add bearer token data to a ServiceAccount
  244. func (tda *TokenDataAction) PopulateServiceAccount(
  245. repo repository.ServiceAccountRepository,
  246. ) error {
  247. err := tda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  248. if err != nil {
  249. return err
  250. }
  251. tda.ServiceAccountActionResolver.SA.Token = []byte(tda.TokenData)
  252. return nil
  253. }
  254. // GCPKeyDataAction contains the GCP key data
  255. type GCPKeyDataAction struct {
  256. *ServiceAccountActionResolver
  257. GCPKeyData string `json:"gcp_key_data" form:"required"`
  258. }
  259. // PopulateServiceAccount will add GCP key data to a ServiceAccount
  260. func (gkda *GCPKeyDataAction) PopulateServiceAccount(
  261. repo repository.ServiceAccountRepository,
  262. ) error {
  263. err := gkda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  264. if err != nil {
  265. return err
  266. }
  267. gkda.ServiceAccountActionResolver.SA.GCPKeyData = []byte(gkda.GCPKeyData)
  268. return nil
  269. }
  270. // AWSDataAction contains the AWS data (access id, key)
  271. type AWSDataAction struct {
  272. *ServiceAccountActionResolver
  273. AWSAccessKeyID string `json:"aws_access_key_id" form:"required"`
  274. AWSSecretAccessKey string `json:"aws_secret_access_key" form:"required"`
  275. AWSClusterID string `json:"aws_cluster_id" form:"required"`
  276. }
  277. // PopulateServiceAccount will add GCP key data to a ServiceAccount
  278. func (akda *AWSDataAction) PopulateServiceAccount(
  279. repo repository.ServiceAccountRepository,
  280. ) error {
  281. err := akda.ServiceAccountActionResolver.PopulateServiceAccount(repo)
  282. if err != nil {
  283. return err
  284. }
  285. akda.ServiceAccountActionResolver.SA.AWSAccessKeyID = []byte(akda.AWSAccessKeyID)
  286. akda.ServiceAccountActionResolver.SA.AWSSecretAccessKey = []byte(akda.AWSSecretAccessKey)
  287. akda.ServiceAccountActionResolver.SA.AWSClusterID = []byte(akda.AWSClusterID)
  288. return nil
  289. }