github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/mongo/admin.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package mongo 5 6 import ( 7 "fmt" 8 "os" 9 "os/exec" 10 "syscall" 11 12 "labix.org/v2/mgo" 13 14 "github.com/juju/juju/upstart" 15 ) 16 17 var ( 18 processSignal = (*os.Process).Signal 19 ) 20 21 type EnsureAdminUserParams struct { 22 // DialInfo specifies how to connect to the mongo server. 23 DialInfo *mgo.DialInfo 24 // Namespace is the agent namespace, used to derive the Mongo service name. 25 Namespace string 26 // DataDir is the Juju data directory, used to start a --noauth server. 27 DataDir string 28 // Port is the listening port of the Mongo server. 29 Port int 30 // User holds the user to log in to the mongo server as. 31 User string 32 // Password holds the password for the user to log in as. 33 Password string 34 } 35 36 // EnsureAdminUser ensures that the specified user and password 37 // are added to the admin database. 38 // 39 // This function will stop the Mongo service if it needs to add 40 // the admin user, as it must restart Mongo in --noauth mode. 41 func EnsureAdminUser(p EnsureAdminUserParams) (added bool, err error) { 42 if len(p.DialInfo.Addrs) > 1 { 43 logger.Infof("more than one state server; admin user must exist") 44 return false, nil 45 } 46 p.DialInfo.Addrs = []string{fmt.Sprintf("127.0.0.1:%d", p.Port)} 47 p.DialInfo.Direct = true 48 49 // Attempt to login to the admin database first. 50 session, err := mgo.DialWithInfo(p.DialInfo) 51 if err != nil { 52 return false, fmt.Errorf("can't dial mongo to ensure admin user: %v", err) 53 } 54 err = session.DB("admin").Login(p.User, p.Password) 55 session.Close() 56 if err == nil { 57 return false, nil 58 } 59 logger.Debugf("admin login failed: %v", err) 60 61 // Login failed, so we need to add the user. 62 // Stop mongo, so we can start it in --noauth mode. 63 mongoServiceName := ServiceName(p.Namespace) 64 mongoService := upstart.NewService(mongoServiceName) 65 if err := upstartServiceStop(mongoService); err != nil { 66 return false, fmt.Errorf("failed to stop %v: %v", mongoServiceName, err) 67 } 68 69 // Start mongod in --noauth mode. 70 logger.Debugf("starting mongo with --noauth") 71 cmd, err := noauthCommand(p.DataDir, p.Port) 72 if err != nil { 73 return false, fmt.Errorf("failed to prepare mongod command: %v", err) 74 } 75 if err := cmd.Start(); err != nil { 76 return false, fmt.Errorf("failed to start mongod: %v", err) 77 } 78 defer cmd.Process.Kill() 79 80 // Add the user to the admin database. 81 logger.Debugf("setting admin password") 82 if session, err = mgo.DialWithInfo(p.DialInfo); err != nil { 83 return false, fmt.Errorf("can't dial mongo to ensure admin user: %v", err) 84 } 85 err = SetAdminMongoPassword(session, p.User, p.Password) 86 session.Close() 87 if err != nil { 88 return false, fmt.Errorf("failed to add %q to admin database: %v", p.User, err) 89 } 90 logger.Infof("added %q to admin database", p.User) 91 92 // Restart mongo using upstart. 93 if err := processSignal(cmd.Process, syscall.SIGTERM); err != nil { 94 return false, fmt.Errorf("cannot kill mongod: %v", err) 95 } 96 if err := cmd.Wait(); err != nil { 97 if _, ok := err.(*exec.ExitError); !ok { 98 return false, fmt.Errorf("mongod did not cleanly terminate: %v", err) 99 } 100 } 101 if err := upstartServiceStart(mongoService); err != nil { 102 return false, err 103 } 104 return true, nil 105 } 106 107 // SetAdminMongoPassword sets the administrative password 108 // to access a mongo database. If the password is non-empty, 109 // all subsequent attempts to access the database must 110 // be authorized; otherwise no authorization is required. 111 func SetAdminMongoPassword(session *mgo.Session, user, password string) error { 112 admin := session.DB("admin") 113 if password != "" { 114 if err := admin.UpsertUser(&mgo.User{ 115 Username: user, 116 Password: password, 117 Roles: []mgo.Role{mgo.RoleDBAdminAny, mgo.RoleUserAdminAny, mgo.RoleClusterAdmin, mgo.RoleReadWriteAny}, 118 }); err != nil { 119 return fmt.Errorf("cannot set admin password: %v", err) 120 } 121 if err := admin.Login(user, password); err != nil { 122 return fmt.Errorf("cannot login after setting password: %v", err) 123 } 124 } else { 125 if err := admin.RemoveUser(user); err != nil && err != mgo.ErrNotFound { 126 return fmt.Errorf("cannot disable admin password: %v", err) 127 } 128 } 129 return nil 130 } 131 132 // SetMongoPassword sets the mongo password in the specified databases for the given user name. 133 // Previous passwords are invalidated. 134 func SetMongoPassword(name, password string, dbs ...*mgo.Database) error { 135 user := &mgo.User{ 136 Username: name, 137 Password: password, 138 Roles: []mgo.Role{mgo.RoleReadWriteAny, mgo.RoleUserAdmin}, 139 } 140 for _, db := range dbs { 141 if err := db.UpsertUser(user); err != nil { 142 return fmt.Errorf("cannot set password in juju db %q for %q: %v", db.Name, name, err) 143 } 144 } 145 return nil 146 }