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  }