github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/agent/bootstrap.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agent 5 6 import ( 7 "fmt" 8 9 "github.com/juju/names" 10 "github.com/juju/utils" 11 12 "github.com/juju/juju/constraints" 13 "github.com/juju/juju/environs/config" 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/state" 16 "github.com/juju/juju/state/api/params" 17 "github.com/juju/juju/version" 18 ) 19 20 // InitializeState should be called on the bootstrap machine's agent 21 // configuration. It uses that information to create the state server, dial the 22 // state server, and initialize it. It also generates a new password for the 23 // bootstrap machine and calls Write to save the the configuration. 24 // 25 // The envCfg values will be stored in the state's EnvironConfig; the 26 // machineCfg values will be used to configure the bootstrap Machine, 27 // and its constraints will be also be used for the environment-level 28 // constraints. The connection to the state server will respect the 29 // given timeout parameter. 30 // 31 // InitializeState returns the newly initialized state and bootstrap 32 // machine. If it fails, the state may well be irredeemably compromised. 33 type StateInitializer interface { 34 InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error) 35 } 36 37 // BootstrapMachineConfig holds configuration information 38 // to attach to the bootstrap machine. 39 type BootstrapMachineConfig struct { 40 // Addresses holds the bootstrap machine's addresses. 41 Addresses []instance.Address 42 43 // Constraints holds the bootstrap machine's constraints. 44 // This value is also used for the environment-level constraints. 45 Constraints constraints.Value 46 47 // Jobs holds the jobs that the machine agent will run. 48 Jobs []params.MachineJob 49 50 // InstanceId holds the instance id of the bootstrap machine. 51 InstanceId instance.Id 52 53 // Characteristics holds hardware information on the 54 // bootstrap machine. 55 Characteristics instance.HardwareCharacteristics 56 57 // SharedSecret is the Mongo replica set shared secret (keyfile). 58 SharedSecret string 59 } 60 61 const BootstrapMachineId = "0" 62 63 func InitializeState(c ConfigSetter, envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) { 64 if c.Tag() != names.MachineTag(BootstrapMachineId) { 65 return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration") 66 } 67 servingInfo, ok := c.StateServingInfo() 68 if !ok { 69 return nil, nil, fmt.Errorf("state serving information not available") 70 } 71 // N.B. no users are set up when we're initializing the state, 72 // so don't use any tag or password when opening it. 73 info, ok := c.StateInfo() 74 if !ok { 75 return nil, nil, fmt.Errorf("stateinfo not available") 76 } 77 info.Tag = "" 78 info.Password = "" 79 80 logger.Debugf("initializing address %v", info.Addrs) 81 st, err := state.Initialize(info, envCfg, timeout, policy) 82 if err != nil { 83 return nil, nil, fmt.Errorf("failed to initialize state: %v", err) 84 } 85 logger.Debugf("connected to initial state") 86 defer func() { 87 if resultErr != nil { 88 st.Close() 89 } 90 }() 91 servingInfo.SharedSecret = machineCfg.SharedSecret 92 c.SetStateServingInfo(servingInfo) 93 if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil { 94 return nil, nil, err 95 } 96 if err := st.SetStateServingInfo(servingInfo); err != nil { 97 return nil, nil, fmt.Errorf("cannot set state serving info: %v", err) 98 } 99 m, err := initUsersAndBootstrapMachine(c, st, machineCfg) 100 if err != nil { 101 return nil, nil, err 102 } 103 return st, m, nil 104 } 105 106 func initUsersAndBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { 107 if err := initBootstrapUser(st, c.OldPassword()); err != nil { 108 return nil, fmt.Errorf("cannot initialize bootstrap user: %v", err) 109 } 110 if err := st.SetEnvironConstraints(cfg.Constraints); err != nil { 111 return nil, fmt.Errorf("cannot set initial environ constraints: %v", err) 112 } 113 m, err := initBootstrapMachine(c, st, cfg) 114 if err != nil { 115 return nil, fmt.Errorf("cannot initialize bootstrap machine: %v", err) 116 } 117 return m, nil 118 } 119 120 // initBootstrapUser creates the initial admin user for the database, and sets 121 // the initial password. 122 func initBootstrapUser(st *state.State, passwordHash string) error { 123 logger.Debugf("adding admin user") 124 // Set up initial authentication. 125 u, err := st.AddUser("admin", "", "") 126 if err != nil { 127 return err 128 } 129 130 // Note that at bootstrap time, the password is set to 131 // the hash of its actual value. The first time a client 132 // connects to mongo, it changes the mongo password 133 // to the original password. 134 logger.Debugf("setting password hash for admin user") 135 // TODO(jam): http://pad.lv/1248839 136 // We could teach bootstrap how to generate a custom salt and apply 137 // that to the hash that was generated. At which point we'd need to set 138 // it here. For now, we pass "" so that on first login we will create a 139 // new salt, but the fixed-salt password is still available from 140 // cloud-init. 141 if err := u.SetPasswordHash(passwordHash, ""); err != nil { 142 return err 143 } 144 if err := st.SetAdminMongoPassword(passwordHash); err != nil { 145 return err 146 } 147 return nil 148 } 149 150 // initBootstrapMachine initializes the initial bootstrap machine in state. 151 func initBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) { 152 logger.Infof("initialising bootstrap machine with config: %+v", cfg) 153 154 jobs := make([]state.MachineJob, len(cfg.Jobs)) 155 for i, job := range cfg.Jobs { 156 machineJob, err := state.MachineJobFromParams(job) 157 if err != nil { 158 return nil, fmt.Errorf("invalid bootstrap machine job %q: %v", job, err) 159 } 160 jobs[i] = machineJob 161 } 162 m, err := st.AddOneMachine(state.MachineTemplate{ 163 Addresses: cfg.Addresses, 164 Series: version.Current.Series, 165 Nonce: state.BootstrapNonce, 166 Constraints: cfg.Constraints, 167 InstanceId: cfg.InstanceId, 168 HardwareCharacteristics: cfg.Characteristics, 169 Jobs: jobs, 170 }) 171 if err != nil { 172 return nil, fmt.Errorf("cannot create bootstrap machine in state: %v", err) 173 } 174 if m.Id() != BootstrapMachineId { 175 return nil, fmt.Errorf("bootstrap machine expected id 0, got %q", m.Id()) 176 } 177 // Read the machine agent's password and change it to 178 // a new password (other agents will change their password 179 // via the API connection). 180 logger.Debugf("create new random password for machine %v", m.Id()) 181 182 newPassword, err := utils.RandomPassword() 183 if err != nil { 184 return nil, err 185 } 186 if err := m.SetPassword(newPassword); err != nil { 187 return nil, err 188 } 189 if err := m.SetMongoPassword(newPassword); err != nil { 190 return nil, err 191 } 192 c.SetPassword(newPassword) 193 return m, nil 194 } 195 196 // initAPIHostPorts sets the initial API host/port addresses in state. 197 func initAPIHostPorts(c ConfigSetter, st *state.State, addrs []instance.Address, apiPort int) error { 198 hostPorts := instance.AddressesWithPort(addrs, apiPort) 199 return st.SetAPIHostPorts([][]instance.HostPort{hostPorts}) 200 }