github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/jujud/util/password/password_windows.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 // +build windows 6 7 package password 8 9 import ( 10 "syscall" 11 "time" 12 13 // https://bugs.launchpad.net/juju-core/+bug/1470820 14 "github.com/gabriel-samfira/sys/windows/registry" 15 "github.com/juju/errors" 16 "github.com/juju/utils" 17 18 "github.com/juju/juju/juju/osenv" 19 "github.com/juju/juju/service/windows" 20 "github.com/juju/juju/service/windows/securestring" 21 ) 22 23 // netUserSetInfo is used to change the password on a user. 24 //sys netUserSetInfo(servername *uint16, username *uint16, level uint32, buf *netUserSetPassword, parm_err *uint16) (err error) [failretval!=0] = netapi32.NetUserSetInfo 25 26 // The USER_INFO_1003 structure contains a user password. This information 27 // level is valid only when you call the NetUserSetInfo function. 28 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370963(v=vs.85).aspx 29 type netUserSetPassword struct { 30 Password *uint16 31 } 32 33 const ( 34 alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 35 36 // Specifies a user password. The buf parameter points to a USER_INFO_1003 structure. 37 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370659(v=vs.85).aspx 38 changePasswordLevel = 1003 39 ) 40 41 var ( 42 ERR_REGKEY_EXIST = errors.New("Registry key already exists") 43 ) 44 45 // EnsureJujudPassword sets a password on the jujud service and the jujud user. Writes that 46 // password in a registry file to be read at a later point. This should only be 47 // done as an initialization when starting the agent. It only does something on 48 // windows. 49 var EnsureJujudPassword = func() error { 50 newPassword, err := utils.RandomPassword() 51 if err != nil { 52 return errors.Trace(err) 53 } 54 mgr, err := windows.NewServiceManager() 55 if err != nil { 56 return errors.Annotate(err, "could not start service manager") 57 } 58 59 err = ensureJujudPasswordHelper("jujud", newPassword, osenv.JujuRegistryKey, osenv.JujuRegistryPasswordKey, mgr, &passwordChanger{}) 60 if err == ERR_REGKEY_EXIST { 61 return nil 62 } 63 return err 64 } 65 66 // ensureJujudPasswordHelper actually does the heavy lifting of changing the password. It checks the registry for a password. If it doesn't exist 67 // then it writes a new one to the registry, changes the password for the local jujud user and sets the password for all it's services. 68 func ensureJujudPasswordHelper(username, newPassword, regKey, regEntry string, mgr windows.ServiceManager, helpers passwordChangerHelpers) error { 69 k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKey[6:], registry.ALL_ACCESS) 70 if err != nil { 71 return errors.Annotate(err, "failed to open juju registry key") 72 } 73 defer k.Close() 74 75 // Check if the password already exists in the registry 76 if _, _, err := k.GetBinaryValue(regEntry); err == nil { 77 return ERR_REGKEY_EXIST 78 } 79 80 enc, err := securestring.Encrypt(newPassword) 81 if err != nil { 82 return errors.Annotate(err, "could not encrypt password") 83 } 84 85 err = k.SetBinaryValue(regEntry, []byte(enc)) 86 if err != nil { 87 return errors.Annotate(err, "could not write password registry key") 88 } 89 90 err = helpers.changeUserPasswordLocalhost(newPassword) 91 if err != nil { 92 delErr := k.DeleteValue(regEntry) 93 if delErr != nil { 94 return errors.Annotatef(err, "could not change user password, reverting config; could not erase entry %s at %s", regEntry, regKey) 95 } 96 return errors.Annotate(err, "could not change user password, reverting config") 97 } 98 99 err = helpers.changeJujudServicesPassword(newPassword, mgr, windows.ListServices) 100 if err != nil { 101 delErr := k.DeleteValue(regEntry) 102 if delErr != nil { 103 return errors.Annotatef(err, "could not change password for all jujud services; could not erase entry %s at %s", regEntry, regKey) 104 } 105 return errors.Annotate(err, "could not change password for all jujud services") 106 } 107 108 return nil 109 } 110 111 var changeServicePasswordAttempts = utils.AttemptStrategy{ 112 Total: 5 * time.Second, 113 Delay: 6 * time.Second, 114 } 115 116 // passwordChangerHelpers exists only for making the testing of the ensureJujudPasswordHelper function easier 117 type passwordChangerHelpers interface { 118 // changeUserPasswordLocalhost changes the password for the jujud user on the local computer using syscalls 119 changeUserPasswordLocalhost(newPassword string) error 120 121 // changeJujudServicesPassword changes the password for all the services created by the jujud user 122 changeJujudServicesPassword(newPassword string, mgr windows.ServiceManager, listServices func() ([]string, error)) error 123 } 124 125 // passwordChanger implements passwordChangerHelpers 126 type passwordChanger struct{} 127 128 // changeUserPasswordLocalhost changes the password for username on localhost 129 func (c *passwordChanger) changeUserPasswordLocalhost(newPassword string) error { 130 serverp, err := syscall.UTF16PtrFromString("localhost") 131 if err != nil { 132 return errors.Trace(err) 133 } 134 135 userp, err := syscall.UTF16PtrFromString("jujud") 136 if err != nil { 137 return errors.Trace(err) 138 } 139 140 passp, err := syscall.UTF16PtrFromString(newPassword) 141 if err != nil { 142 return errors.Trace(err) 143 } 144 145 info := netUserSetPassword{passp} 146 147 err = netUserSetInfo(serverp, userp, changePasswordLevel, &info, nil) 148 if err != nil { 149 return errors.Trace(err) 150 } 151 152 return nil 153 } 154 155 func (c *passwordChanger) changeJujudServicesPassword(newPassword string, mgr windows.ServiceManager, listServices func() ([]string, error)) error { 156 // Iterate through all services and change the password for those belonging 157 // to jujud 158 svcs, err := listServices() 159 if err != nil { 160 return errors.Trace(err) 161 } 162 for _, svc := range svcs { 163 modifiedService := false 164 var err error 165 for attempt := changeServicePasswordAttempts.Start(); attempt.Next(); { 166 err = mgr.ChangeServicePassword(svc, newPassword) 167 if err != nil { 168 logger.Errorf("retrying to change password on service %v; error: %v", svc, err) 169 } 170 if err == nil { 171 modifiedService = true 172 break 173 } 174 } 175 if !modifiedService { 176 return errors.Trace(err) 177 } 178 } 179 180 return nil 181 }