From 0ae911f5615972a2bf0442e5e82a77120636c685 Mon Sep 17 00:00:00 2001 From: Arnob Kumar Saha Date: Mon, 14 Aug 2023 08:17:30 +0600 Subject: [PATCH] Fix 5.0 early patch version restore issue (#1880) Signed-off-by: Arnob Kumar Saha --- pkg/backup.go | 29 ++++++--- pkg/restore.go | 156 ++++++++++++++++++++++++++++++++++++++++++++++++- pkg/utils.go | 16 +++++ 3 files changed, 191 insertions(+), 10 deletions(-) diff --git a/pkg/backup.go b/pkg/backup.go index 39bf3dcb..c3377723 100644 --- a/pkg/backup.go +++ b/pkg/backup.go @@ -539,6 +539,13 @@ func (opt *mongoOptions) backupMongoDB(targetRef api_v1beta1.TargetRef) (*restic klog.Errorf("error while creating user for %v. error: %v", host, err) return nil, err } + } else { + // We need to create a dummy role while backup to resolve `BSON field '_mergeAuthzCollections.tempRolesCollection' is missing but a required field` issue. + // https://jira.mongodb.org/browse/TOOLS-2946?focusedCommentId=4022387&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-4022387 + err = createStashBackupRole(host) + if err != nil { + return nil, err + } } // do the task primary, secondary, err := getPrimaryNSecondaryMember(host) @@ -570,6 +577,12 @@ func (opt *mongoOptions) backupMongoDB(targetRef api_v1beta1.TargetRef) (*restic // if parameters.ReplicaSets is nil, then the mongodb database doesn't have replicasets or sharded replicasets. // In this case, perform normal backup with clientconfig.Service.Name mongo dsn if parameters.ReplicaSets == nil { + // We need to create a dummy role while backup to resolve `BSON field '_mergeAuthzCollections.tempRolesCollection' is missing but a required field` issue. + // https://jira.mongodb.org/browse/TOOLS-2946?focusedCommentId=4022387&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-4022387 + err = createStashBackupRole(hostname) + if err != nil { + return nil, err + } opt.backupOptions = append(opt.backupOptions, getBackupOpt(hostname, restic.DefaultHost, true)) } @@ -838,38 +851,38 @@ func unlockSecondaryMember(mongohost string) error { return nil } -func checkRoleExists(mongoDSN string) (bool, error) { +func checkRoleExists(mongoDSN string, roleName string) (bool, error) { v := make(map[string]interface{}) args := append([]interface{}{ "admin", "--host", mongoDSN, "--quiet", - "--eval", `JSON.stringify(db.getRole("` + StashRoleName + `"))`, + "--eval", `JSON.stringify(db.getRole("` + roleName + `"))`, }, mongoCreds...) if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil { return false, err } - if val, ok := v["role"].(string); ok && string(val) == StashRoleName { + if val, ok := v["role"].(string); ok && val == roleName { return true, nil } return false, nil } -func checkUserExists(mongoDSN string) (bool, error) { +func checkUserExists(mongoDSN string, userName string) (bool, error) { v := make(map[string]interface{}) args := append([]interface{}{ "admin", "--host", mongoDSN, "--quiet", - "--eval", `JSON.stringify(db.getUser("` + StashUserName + `"))`, + "--eval", `JSON.stringify(db.getUser("` + userName + `"))`, }, mongoCreds...) if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil { return false, err } - if val, ok := v["user"].(string); ok && string(val) == StashUserName { + if val, ok := v["user"].(string); ok && val == userName { return true, nil } @@ -886,7 +899,7 @@ func createStashRoleAndUser(mongoDSN string, pass string) error { } func createStashBackupRole(mongoDSN string) error { - exists, err := checkRoleExists(mongoDSN) + exists, err := checkRoleExists(mongoDSN, StashRoleName) if err != nil { return err } @@ -914,7 +927,7 @@ func createStashBackupRole(mongoDSN string) error { } func createStashBackupUser(mongoDSN string, pass string) error { - exists, err := checkUserExists(mongoDSN) + exists, err := checkUserExists(mongoDSN, StashUserName) if err != nil { return err } diff --git a/pkg/restore.go b/pkg/restore.go index d9f0c7f3..89a78abd 100644 --- a/pkg/restore.go +++ b/pkg/restore.go @@ -34,6 +34,7 @@ import ( "github.com/spf13/cobra" license "go.bytebuilders.dev/license-verifier/kubernetes" "gomodules.xyz/flags" + "gomodules.xyz/go-sh" "gomodules.xyz/pointer" core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,6 +49,8 @@ import ( "kubedb.dev/apimachinery/apis/config/v1alpha1" ) +var cleanupRestoreFuncs []func() error + func NewCmdRestore() *cobra.Command { var ( masterURL string @@ -73,6 +76,7 @@ func NewCmdRestore() *cobra.Command { return checkCommandExists() }, RunE: func(cmd *cobra.Command, args []string) error { + defer cleanupRestore() flags.EnsureRequiredFlags(cmd, "appbinding", "provider", "storage-secret-name", "storage-secret-namespace") // prepare client @@ -153,6 +157,14 @@ func NewCmdRestore() *cobra.Command { return cmd } +func cleanupRestore() { + for _, f := range cleanupRestoreFuncs { + if err := f(); err != nil { + klog.Errorln(err) + } + } +} + func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*restic.RestoreOutput, error) { var err error err = license.CheckLicenseEndpoint(opt.config, licenseApiService, SupportedProducts) @@ -293,7 +305,6 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti "--authenticationDatabase", "$external", } mongoCreds = append(mongoCreds, userAuth...) - dumpCreds = append(dumpCreds, userAuth...) } else { userAuth := []interface{}{ @@ -302,7 +313,11 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti "--authenticationDatabase", opt.authenticationDatabase, } mongoCreds = append(mongoCreds, userAuth...) - dumpCreds = append(dumpCreds, userAuth...) + } + + err = opt.workOnSuperUser(parameters, hostname) + if err != nil { + return nil, err } getDumpOpts := func(mongoDSN, hostKey string, isStandalone bool) restic.DumpOptions { @@ -392,6 +407,8 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti // hide password, don't print cmd resticWrapper.HideCMD() + klog.Infof("==> %+v \n %+v \n", opt.dumpOptions, targetRef) + // Run dump out, err := resticWrapper.ParallelDump(opt.dumpOptions, targetRef, opt.maxConcurrency) if err != nil { @@ -401,6 +418,141 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti return out, nil } +const ( + SuperRoleName = "interalUseOnlyOplogRestore" + SuperUserName = "superoplogger" +) + +func (opt *mongoOptions) workOnSuperUser(parameters v1alpha1.MongoDBConfiguration, hostNameForStandalone string) error { + pass, err := genPassword(12) + if err != nil { + return err + } + + if parameters.ConfigServer != "" { + dsn := parameters.ConfigServer + err := createSuperRoleAndUser(dsn, pass) + if err != nil { + return err + } + } + for key := range parameters.ReplicaSets { + dsn := parameters.ReplicaSets[key] + err := createSuperRoleAndUser(dsn, pass) + if err != nil { + return err + } + } + + if parameters.ReplicaSets == nil { + err := createSuperRoleAndUser(hostNameForStandalone, pass) + if err != nil { + return err + } + } + + superUserAuth := []interface{}{ + fmt.Sprintf("--username=%s", SuperUserName), + fmt.Sprintf("--password=%s", pass), + "--authenticationDatabase", opt.authenticationDatabase, + } + dumpCreds = append(dumpCreds, superUserAuth...) + return nil +} + +func createSuperRoleAndUser(mongoDSN, pass string) error { + klog.Infof("creating SuperRole & User for %s \n", mongoDSN) + err := createSuperRole(mongoDSN) + if err != nil { + return err + } + + err = createSuperUser(mongoDSN, pass) + cleanupRestoreFuncs = append(cleanupRestoreFuncs, func() error { + return deleteSuperUser(mongoDSN) + }) + return err +} + +func createSuperRole(mongoDSN string) error { + exists, err := checkRoleExists(mongoDSN, SuperRoleName) + if err != nil { + return err + } + if !exists { + klog.Infoln("creating role " + SuperRoleName) + v := make(map[string]interface{}) + + args := append([]interface{}{ + "admin", + "--host", mongoDSN, + "--quiet", + "--eval", `JSON.stringify(db.runCommand({createRole: "` + SuperRoleName + `",privileges:[{resource:{anyResource:true},actions:["anyAction"]}],roles: []}))`, + }, mongoCreds...) + + if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil { + return err + } + + if val, ok := v["ok"].(float64); !ok || int(val) != 1 { + return fmt.Errorf("unable to create role %v. got response: %v", SuperRoleName, v) + } + } + return nil +} + +func createSuperUser(mongoDSN, pass string) error { + exists, err := checkUserExists(mongoDSN, SuperUserName) + if err != nil { + return err + } + if !exists { + klog.Infoln("creating user " + SuperUserName) + v := make(map[string]interface{}) + + args := append([]interface{}{ + "admin", + "--host", mongoDSN, + "--quiet", + "--eval", `JSON.stringify(db.runCommand({createUser: "` + SuperUserName + `" ,pwd: "` + pass + `", roles:[{role:"root", db:"admin"},"__system","interalUseOnlyOplogRestore","backup"]}))`, + }, mongoCreds...) + if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil { + return err + } + + if val, ok := v["ok"].(float64); !ok || int(val) != 1 { + return fmt.Errorf("unable to create user %v. got response: %v", SuperUserName, v) + } + } + return nil +} + +func deleteSuperUser(mongoDSN string) error { + exists, err := checkUserExists(mongoDSN, SuperUserName) + if err != nil { + return err + } + if exists { + klog.Infoln("deleting user " + SuperUserName) + v := make(map[string]interface{}) + + args := append([]interface{}{ + "admin", + "--host", mongoDSN, + "--quiet", + "--eval", `JSON.stringify(db.runCommand({dropUser: "` + SuperUserName + `"}))`, + }, mongoCreds...) + if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil { + return err + } + + if val, ok := v["ok"].(float64); !ok || int(val) != 1 { + return fmt.Errorf("unable to delete user %v. got response: %v", SuperUserName, v) + } + } + return nil +} + func (opt *mongoOptions) getHostRestoreStats(err error) []api_v1beta1.HostRestoreStats { var restoreStats []api_v1beta1.HostRestoreStats diff --git a/pkg/utils.go b/pkg/utils.go index c28de5cf..4aa03b20 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -17,6 +17,7 @@ limitations under the License. package pkg import ( + "crypto/rand" "fmt" "os/exec" "strings" @@ -94,3 +95,18 @@ func containsArg(args []string, checklist sets.String) bool { } return false } + +var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-" + +func genPassword(length int) (string, error) { + ll := len(chars) + b := make([]byte, length) + _, err := rand.Read(b) // generates len(b) random bytes + if err != nil { + return "", err + } + for i := 0; i < length; i++ { + b[i] = chars[int(b[i])%ll] + } + return string(b), nil +}