cluster.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. package gorm
  2. import (
  3. "context"
  4. "github.com/porter-dev/porter/internal/models"
  5. "github.com/porter-dev/porter/internal/repository"
  6. "gorm.io/gorm"
  7. ints "github.com/porter-dev/porter/internal/models/integrations"
  8. )
  9. // ClusterRepository uses gorm.DB for querying the database
  10. type ClusterRepository struct {
  11. db *gorm.DB
  12. key *[32]byte
  13. }
  14. // NewClusterRepository returns a ClusterRepository which uses
  15. // gorm.DB for querying the database. It accepts an encryption key to encrypt
  16. // sensitive data
  17. func NewClusterRepository(db *gorm.DB, key *[32]byte) repository.ClusterRepository {
  18. return &ClusterRepository{db, key}
  19. }
  20. // CreateClusterCandidate creates a new cluster candidate
  21. func (repo *ClusterRepository) CreateClusterCandidate(
  22. cc *models.ClusterCandidate,
  23. ) (*models.ClusterCandidate, error) {
  24. err := repo.EncryptClusterCandidateData(cc, repo.key)
  25. if err != nil {
  26. return nil, err
  27. }
  28. project := &models.Project{}
  29. if err := repo.db.Where("id = ?", cc.ProjectID).First(&project).Error; err != nil {
  30. return nil, err
  31. }
  32. assoc := repo.db.Model(&project).Association("ClusterCandidates")
  33. if assoc.Error != nil {
  34. return nil, assoc.Error
  35. }
  36. if err := assoc.Append(cc); err != nil {
  37. return nil, err
  38. }
  39. // decrypt at the end to return
  40. err = repo.DecryptClusterCandidateData(cc, repo.key)
  41. if err != nil {
  42. return nil, err
  43. }
  44. return cc, nil
  45. }
  46. // ReadClusterCandidate finds a cluster candidate by id
  47. func (repo *ClusterRepository) ReadClusterCandidate(
  48. id uint,
  49. ) (*models.ClusterCandidate, error) {
  50. cc := &models.ClusterCandidate{}
  51. if err := repo.db.Preload("Resolvers").Where("id = ?", id).First(&cc).Error; err != nil {
  52. return nil, err
  53. }
  54. repo.DecryptClusterCandidateData(cc, repo.key)
  55. return cc, nil
  56. }
  57. // ListClusterCandidatesByProjectID finds all cluster candidates
  58. // for a given project id
  59. func (repo *ClusterRepository) ListClusterCandidatesByProjectID(
  60. projectID uint,
  61. ) ([]*models.ClusterCandidate, error) {
  62. ccs := []*models.ClusterCandidate{}
  63. if err := repo.db.Preload("Resolvers").Where("project_id = ?", projectID).Find(&ccs).Error; err != nil {
  64. return nil, err
  65. }
  66. for _, cc := range ccs {
  67. repo.DecryptClusterCandidateData(cc, repo.key)
  68. }
  69. return ccs, nil
  70. }
  71. // UpdateClusterCandidateCreatedClusterID updates the CreatedClusterID for
  72. // a candidate, after the candidate has been resolved.
  73. func (repo *ClusterRepository) UpdateClusterCandidateCreatedClusterID(
  74. id uint,
  75. createdClusterID uint,
  76. ) (*models.ClusterCandidate, error) {
  77. cc := &models.ClusterCandidate{}
  78. if err := repo.db.Where("id = ?", id).First(&cc).Error; err != nil {
  79. return nil, err
  80. }
  81. cc.CreatedClusterID = createdClusterID
  82. if err := repo.db.Save(cc).Error; err != nil {
  83. return nil, err
  84. }
  85. repo.DecryptClusterCandidateData(cc, repo.key)
  86. return cc, nil
  87. }
  88. // CreateCluster creates a new cluster
  89. func (repo *ClusterRepository) CreateCluster(
  90. cluster *models.Cluster,
  91. ) (*models.Cluster, error) {
  92. ctxDB := repo.db.WithContext(context.Background())
  93. err := repo.EncryptClusterData(cluster, repo.key)
  94. if err != nil {
  95. return nil, err
  96. }
  97. project := &models.Project{}
  98. if err := ctxDB.Where("id = ?", cluster.ProjectID).First(&project).Error; err != nil {
  99. return nil, err
  100. }
  101. assoc := ctxDB.Model(&project).Association("Clusters")
  102. if assoc.Error != nil {
  103. return nil, assoc.Error
  104. }
  105. if err := assoc.Append(cluster); err != nil {
  106. return nil, err
  107. }
  108. // create a token cache by default
  109. assoc = ctxDB.Model(cluster).Association("TokenCache")
  110. if assoc.Error != nil {
  111. return nil, assoc.Error
  112. }
  113. if err := assoc.Append(&cluster.TokenCache); err != nil {
  114. return nil, err
  115. }
  116. err = repo.DecryptClusterData(cluster, repo.key)
  117. if err != nil {
  118. return nil, err
  119. }
  120. return cluster, nil
  121. }
  122. // ReadCluster finds a cluster by id
  123. func (repo *ClusterRepository) ReadCluster(
  124. id uint,
  125. ) (*models.Cluster, error) {
  126. ctxDB := repo.db.WithContext(context.Background())
  127. cluster := &models.Cluster{}
  128. // preload Clusters association
  129. if err := ctxDB.Preload("TokenCache").Where("id = ?", id).First(&cluster).Error; err != nil {
  130. return nil, err
  131. }
  132. err := repo.DecryptClusterData(cluster, repo.key)
  133. if err != nil {
  134. return nil, err
  135. }
  136. return cluster, nil
  137. }
  138. // ListClustersByProjectID finds all clusters
  139. // for a given project id
  140. func (repo *ClusterRepository) ListClustersByProjectID(
  141. projectID uint,
  142. ) ([]*models.Cluster, error) {
  143. ctxDB := repo.db.WithContext(context.Background())
  144. clusters := []*models.Cluster{}
  145. if err := ctxDB.Where("project_id = ?", projectID).Find(&clusters).Error; err != nil {
  146. return nil, err
  147. }
  148. for _, cluster := range clusters {
  149. repo.DecryptClusterData(cluster, repo.key)
  150. }
  151. return clusters, nil
  152. }
  153. // UpdateCluster modifies an existing Cluster in the database
  154. func (repo *ClusterRepository) UpdateCluster(
  155. cluster *models.Cluster,
  156. ) (*models.Cluster, error) {
  157. ctxDB := repo.db.WithContext(context.Background())
  158. err := repo.EncryptClusterData(cluster, repo.key)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if err := ctxDB.Save(cluster).Error; err != nil {
  163. return nil, err
  164. }
  165. err = repo.DecryptClusterData(cluster, repo.key)
  166. if err != nil {
  167. return nil, err
  168. }
  169. return cluster, nil
  170. }
  171. // UpdateClusterTokenCache updates the token cache for a cluster
  172. func (repo *ClusterRepository) UpdateClusterTokenCache(
  173. tokenCache *ints.ClusterTokenCache,
  174. ) (*models.Cluster, error) {
  175. ctxDB := repo.db.WithContext(context.Background())
  176. if tok := tokenCache.Token; len(tok) > 0 {
  177. cipherData, err := repository.Encrypt(tok, repo.key)
  178. if err != nil {
  179. return nil, err
  180. }
  181. tokenCache.Token = cipherData
  182. }
  183. cluster := &models.Cluster{}
  184. if err := ctxDB.Where("id = ?", tokenCache.ClusterID).First(&cluster).Error; err != nil {
  185. return nil, err
  186. }
  187. // delete the existing token cache first
  188. if err := ctxDB.Where("id = ?", tokenCache.ID).Unscoped().Delete(&cluster.TokenCache).Error; err != nil {
  189. return nil, err
  190. }
  191. // set the new token cache
  192. cluster.TokenCache.Token = tokenCache.Token
  193. cluster.TokenCache.Expiry = tokenCache.Expiry
  194. if err := ctxDB.Save(cluster).Error; err != nil {
  195. return nil, err
  196. }
  197. return cluster, nil
  198. }
  199. // DeleteCluster removes a cluster from the db
  200. func (repo *ClusterRepository) DeleteCluster(
  201. cluster *models.Cluster,
  202. ) error {
  203. // clear TokenCache association
  204. assoc := repo.db.Model(cluster).Association("TokenCache")
  205. if assoc.Error != nil {
  206. return assoc.Error
  207. }
  208. if err := assoc.Clear(); err != nil {
  209. return err
  210. }
  211. if err := repo.db.Where("id = ?", cluster.ID).Delete(&models.Cluster{}).Error; err != nil {
  212. return err
  213. }
  214. return nil
  215. }
  216. // EncryptClusterData will encrypt the user's service account data before writing
  217. // to the DB
  218. func (repo *ClusterRepository) EncryptClusterData(
  219. cluster *models.Cluster,
  220. key *[32]byte,
  221. ) error {
  222. if len(cluster.CertificateAuthorityData) > 0 {
  223. cipherData, err := repository.Encrypt(cluster.CertificateAuthorityData, key)
  224. if err != nil {
  225. return err
  226. }
  227. cluster.CertificateAuthorityData = cipherData
  228. }
  229. if tok := cluster.TokenCache.Token; len(tok) > 0 {
  230. cipherData, err := repository.Encrypt(tok, key)
  231. if err != nil {
  232. return err
  233. }
  234. cluster.TokenCache.Token = cipherData
  235. }
  236. return nil
  237. }
  238. // EncryptClusterCandidateData will encrypt the service account candidate data before
  239. // writing to the DB
  240. func (repo *ClusterRepository) EncryptClusterCandidateData(
  241. cc *models.ClusterCandidate,
  242. key *[32]byte,
  243. ) error {
  244. if len(cc.AWSClusterIDGuess) > 0 {
  245. cipherData, err := repository.Encrypt(cc.AWSClusterIDGuess, key)
  246. if err != nil {
  247. return err
  248. }
  249. cc.AWSClusterIDGuess = cipherData
  250. }
  251. if len(cc.Kubeconfig) > 0 {
  252. cipherData, err := repository.Encrypt(cc.Kubeconfig, key)
  253. if err != nil {
  254. return err
  255. }
  256. cc.Kubeconfig = cipherData
  257. }
  258. return nil
  259. }
  260. // DecryptClusterData will decrypt the user's service account data before
  261. // returning it from the DB
  262. func (repo *ClusterRepository) DecryptClusterData(
  263. cluster *models.Cluster,
  264. key *[32]byte,
  265. ) error {
  266. if len(cluster.CertificateAuthorityData) > 0 {
  267. plaintext, err := repository.Decrypt(cluster.CertificateAuthorityData, key)
  268. if err != nil {
  269. return err
  270. }
  271. cluster.CertificateAuthorityData = plaintext
  272. }
  273. if tok := cluster.TokenCache.Token; len(tok) > 0 {
  274. plaintext, err := repository.Decrypt(tok, key)
  275. // in the case that the token cache is down, set empty token
  276. if err != nil {
  277. cluster.TokenCache.Token = []byte{}
  278. } else {
  279. cluster.TokenCache.Token = plaintext
  280. }
  281. }
  282. return nil
  283. }
  284. // DecryptClusterCandidateData will decrypt the service account candidate data before
  285. // returning it from the DB
  286. func (repo *ClusterRepository) DecryptClusterCandidateData(
  287. cc *models.ClusterCandidate,
  288. key *[32]byte,
  289. ) error {
  290. if len(cc.AWSClusterIDGuess) > 0 {
  291. plaintext, err := repository.Decrypt(cc.AWSClusterIDGuess, key)
  292. if err != nil {
  293. return err
  294. }
  295. cc.AWSClusterIDGuess = plaintext
  296. }
  297. if len(cc.Kubeconfig) > 0 {
  298. plaintext, err := repository.Decrypt(cc.Kubeconfig, key)
  299. if err != nil {
  300. return err
  301. }
  302. cc.Kubeconfig = plaintext
  303. }
  304. return nil
  305. }