package keyrotate import ( "encoding/hex" "fmt" "github.com/porter-dev/porter/internal/models" ints "github.com/porter-dev/porter/internal/models/integrations" gorm "github.com/porter-dev/porter/internal/repository/gorm" _gorm "gorm.io/gorm" ) // process 100 records at a time const stepSize = 100 func Rotate(db *_gorm.DB, oldKey, newKey *[32]byte) error { oldKeyBytes := make([]byte, 32) newKeyBytes := make([]byte, 32) copy(oldKeyBytes[:], oldKey[:]) copy(newKeyBytes[:], newKey[:]) err := rotateClusterModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on cluster rotation: %v\n", err) return err } err = rotateClusterCandidateModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on cc rotation: %v\n", err) return err } err = rotateRegistryModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on registry rotation: %v\n", err) return err } err = rotateHelmRepoModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on hr rotation: %v\n", err) return err } err = rotateInfraModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on infra rotation: %v\n", err) return err } err = rotateKubeIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on ki rotation: %v\n", err) return err } err = rotateBasicIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on basic rotation: %v\n", err) return err } err = rotateOIDCIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on oidc rotation: %v\n", err) return err } err = rotateOAuthIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on oauth rotation: %v\n", err) return err } err = rotateGCPIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on gcp rotation: %v\n", err) return err } err = rotateAWSIntegrationModel(db, oldKey, newKey) if err != nil { fmt.Printf("failed on aws rotation: %v\n", err) return err } return nil } func rotateClusterModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&models.Cluster{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewClusterRepository(db, oldKey).(*gorm.ClusterRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { clusters := []*models.Cluster{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&clusters).Error; err != nil { return err } // decrypt with the old key for _, cluster := range clusters { // attach the token cache, not fatal if it fails db.Where("id = ?", cluster.TokenCacheID).First(&cluster.TokenCache) err := repo.DecryptClusterData(cluster, oldKey) if err != nil { fmt.Printf("error decrypting cluster %d\n", cluster.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it if err := db.Unscoped().Where("id = ?", cluster.TokenCacheID).Delete(&ints.ClusterTokenCache{}).Error; err != nil { return err } cluster.TokenCacheID = 0 } } // encrypt with the new key and re-insert for _, cluster := range clusters { err := repo.EncryptClusterData(cluster, newKey) if err != nil { fmt.Printf("error encrypting cluster %d\n", cluster.ID) return err } if err := db.Save(cluster).Error; err != nil { return err } if err := db.Save(&cluster.TokenCache).Error; err != nil { return err } } } fmt.Printf("rotated %d clusters\n", count) return nil } func rotateClusterCandidateModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&models.ClusterCandidate{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewClusterRepository(db, oldKey).(*gorm.ClusterRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { ccs := []*models.ClusterCandidate{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&ccs).Error; err != nil { return err } // decrypt with the old key for _, cc := range ccs { err := repo.DecryptClusterCandidateData(cc, oldKey) if err != nil { fmt.Printf("error decrypting cluster candidate %d\n", cc.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it cc.AWSClusterIDGuess = []byte{} cc.Kubeconfig = []byte{} } } // encrypt with the new key and re-insert for _, cc := range ccs { err := repo.EncryptClusterCandidateData(cc, newKey) if err != nil { fmt.Printf("error encrypting cluster candidate %d\n", cc.ID) return err } if err := db.Save(cc).Error; err != nil { return err } } } fmt.Printf("rotated %d cluster candidates\n", count) return nil } func rotateRegistryModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&models.Registry{}).Count(&count).Error; err != nil { return err } // registry-scoped repository repo := gorm.NewRegistryRepository(db, oldKey).(*gorm.RegistryRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { regs := []*models.Registry{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Preload("TokenCache").Find(®s).Error; err != nil { return err } // decrypt with the old key for _, reg := range regs { err := repo.DecryptRegistryData(reg, oldKey) if err != nil { fmt.Printf("error decrypting registry %d\n", reg.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it reg.TokenCache.Token = []byte{} } } // encrypt with the new key and re-insert for _, reg := range regs { err := repo.EncryptRegistryData(reg, newKey) if err != nil { fmt.Printf("error encrypting registry %d\n", reg.ID) return err } if err := db.Save(reg).Error; err != nil { return err } } } fmt.Printf("rotated %d registries\n", count) return nil } func rotateHelmRepoModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&models.HelmRepo{}).Count(&count).Error; err != nil { return err } // helm repo-scoped repository repo := gorm.NewHelmRepoRepository(db, oldKey).(*gorm.HelmRepoRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { hrs := []*models.HelmRepo{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Preload("TokenCache").Find(&hrs).Error; err != nil { return err } // decrypt with the old key for _, hr := range hrs { err := repo.DecryptHelmRepoData(hr, oldKey) if err != nil { fmt.Printf("error decrypting helm repo %d\n", hr.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it hr.TokenCache.Token = []byte{} } } // encrypt with the new key and re-insert for _, hr := range hrs { err := repo.EncryptHelmRepoData(hr, newKey) if err != nil { fmt.Printf("error encrypting helm repo %d\n", hr.ID) return err } if err := db.Save(hr).Error; err != nil { return err } } } fmt.Printf("rotated %d helm repos\n", count) return nil } func rotateInfraModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&models.Infra{}).Count(&count).Error; err != nil { return err } // helm repo-scoped repository repo := gorm.NewInfraRepository(db, oldKey).(*gorm.InfraRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { infras := []*models.Infra{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&infras).Error; err != nil { return err } // decrypt with the old key for _, infra := range infras { err := repo.DecryptInfraData(infra, oldKey) if err != nil { oldKeyBytes := make([]byte, 32) newKeyBytes := make([]byte, 32) copy(oldKeyBytes[:], oldKey[:]) copy(newKeyBytes[:], newKey[:]) fmt.Printf("error decrypting infra %d, %s, %s, %s\n", infra.ID, hex.EncodeToString(infra.LastApplied), string(oldKeyBytes), string(newKeyBytes)) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it infra.LastApplied = []byte{} } } // encrypt with the new key and re-insert for _, infra := range infras { err := repo.EncryptInfraData(infra, newKey) if err != nil { fmt.Printf("error encrypting infra %d\n", infra.ID) return err } if err := db.Save(infra).Error; err != nil { return err } } } fmt.Printf("rotated %d infras\n", count) return nil } func rotateKubeIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.KubeIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewKubeIntegrationRepository(db, oldKey).(*gorm.KubeIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { kis := []*ints.KubeIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&kis).Error; err != nil { return err } // decrypt with the old key for _, ki := range kis { err := repo.DecryptKubeIntegrationData(ki, oldKey) if err != nil { fmt.Printf("error decrypting kube integration %d\n", ki.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it ki.ClientCertificateData = []byte{} ki.ClientKeyData = []byte{} ki.Token = []byte{} ki.Username = []byte{} ki.Password = []byte{} ki.Kubeconfig = []byte{} } } // encrypt with the new key and re-insert for _, ki := range kis { err := repo.EncryptKubeIntegrationData(ki, newKey) if err != nil { fmt.Printf("error encrypting kube integration %d\n", ki.ID) return err } if err := db.Save(ki).Error; err != nil { return err } } } fmt.Printf("rotated %d kube integrations\n", count) return nil } func rotateBasicIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.BasicIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewBasicIntegrationRepository(db, oldKey).(*gorm.BasicIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { basics := []*ints.BasicIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&basics).Error; err != nil { return err } // decrypt with the old key for _, basic := range basics { err := repo.DecryptBasicIntegrationData(basic, oldKey) if err != nil { fmt.Printf("error decrypting basic integration %d\n", basic.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it basic.Username = []byte{} basic.Password = []byte{} } } // encrypt with the new key and re-insert for _, basic := range basics { err := repo.EncryptBasicIntegrationData(basic, newKey) if err != nil { fmt.Printf("error encrypting basic integration %d\n", basic.ID) return err } if err := db.Save(basic).Error; err != nil { return err } } } fmt.Printf("rotated %d basic integrations\n", count) return nil } func rotateOIDCIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.OIDCIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewOIDCIntegrationRepository(db, oldKey).(*gorm.OIDCIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { oidcs := []*ints.OIDCIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&oidcs).Error; err != nil { return err } // decrypt with the old key for _, oidc := range oidcs { err := repo.DecryptOIDCIntegrationData(oidc, oldKey) if err != nil { fmt.Printf("error decrypting oidc integration %d\n", oidc.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it oidc.IssuerURL = []byte{} oidc.ClientID = []byte{} oidc.ClientSecret = []byte{} oidc.CertificateAuthorityData = []byte{} oidc.IDToken = []byte{} oidc.RefreshToken = []byte{} } } // encrypt with the new key and re-insert for _, oidc := range oidcs { err := repo.EncryptOIDCIntegrationData(oidc, newKey) if err != nil { fmt.Printf("error encrypting oidc integration %d\n", oidc.ID) return err } if err := db.Save(oidc).Error; err != nil { return err } } } fmt.Printf("rotated %d oidc integrations\n", count) return nil } func rotateOAuthIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.OAuthIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewOAuthIntegrationRepository(db, oldKey, nil).(*gorm.OAuthIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { oauths := []*ints.OAuthIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&oauths).Error; err != nil { return err } // decrypt with the old key for _, oauth := range oauths { err := repo.DecryptOAuthIntegrationData(oauth, oldKey) if err != nil { fmt.Printf("error decrypting oauth integration %d\n", oauth.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it oauth.ClientID = []byte{} oauth.AccessToken = []byte{} oauth.RefreshToken = []byte{} } } // encrypt with the new key and re-insert for _, oauth := range oauths { err := repo.EncryptOAuthIntegrationData(oauth, newKey) if err != nil { fmt.Printf("error encrypting oauth integration %d\n", oauth.ID) return err } if err := db.Save(oauth).Error; err != nil { return err } } } fmt.Printf("rotated %d oauth integrations\n", count) return nil } func rotateGCPIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.GCPIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewGCPIntegrationRepository(db, oldKey, nil).(*gorm.GCPIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { gcps := []*ints.GCPIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&gcps).Error; err != nil { return err } // decrypt with the old key for _, gcp := range gcps { err := repo.DecryptGCPIntegrationData(gcp, oldKey) if err != nil { fmt.Printf("error decrypting gcp integration %d\n", gcp.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it gcp.GCPKeyData = []byte{} } } // encrypt with the new key and re-insert for _, gcp := range gcps { err := repo.EncryptGCPIntegrationData(gcp, newKey) if err != nil { fmt.Printf("error encrypting gcp integration %d\n", gcp.ID) return err } if err := db.Save(gcp).Error; err != nil { return err } } } fmt.Printf("rotated %d gcp integrations\n", count) return nil } func rotateAWSIntegrationModel(db *_gorm.DB, oldKey, newKey *[32]byte) error { // get count of model var count int64 if err := db.Model(&ints.AWSIntegration{}).Count(&count).Error; err != nil { return err } // cluster-scoped repository repo := gorm.NewAWSIntegrationRepository(db, oldKey, nil).(*gorm.AWSIntegrationRepository) // iterate (count / stepSize) + 1 times using Limit and Offset for i := 0; i < (int(count)/stepSize)+1; i++ { awss := []*ints.AWSIntegration{} if err := db.Order("id asc").Offset(i * stepSize).Limit(stepSize).Find(&awss).Error; err != nil { return err } // decrypt with the old key for _, aws := range awss { err := repo.DecryptAWSIntegrationData(aws, oldKey) if err != nil { fmt.Printf("error encrypting aws integration %d\n", aws.ID) // in these cases we'll wipe the data -- if it can't be decrypted, we can't // recover it aws.AWSAccessKeyID = []byte{} aws.AWSClusterID = []byte{} aws.AWSSecretAccessKey = []byte{} aws.AWSSessionToken = []byte{} } } // encrypt with the new key and re-insert for _, aws := range awss { err := repo.EncryptAWSIntegrationData(aws, newKey) if err != nil { fmt.Printf("error decrypting aws integration %d\n", aws.ID) return err } if err := db.Save(aws).Error; err != nil { return err } } } fmt.Printf("rotated %d aws integrations\n", count) return nil }