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