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