github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/service/windows/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 windows
     8  
     9  import (
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  )
    16  
    17  // netUserSetInfo is used to change the password on a user.
    18  //sys netUserSetInfo(servername *uint16, username *uint16, level uint32, buf *netUserSetPassword, parm_err *uint16) (err error) [failretval!=0] = netapi32.NetUserSetInfo
    19  
    20  // The USER_INFO_1003 structure contains a user password. This information
    21  // level is valid only when you call the NetUserSetInfo function.
    22  // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370963(v=vs.85).aspx
    23  type netUserSetPassword struct {
    24  	Password *uint16
    25  }
    26  
    27  const (
    28  	// Specifies a user password. The buf parameter points to a USER_INFO_1003 structure.
    29  	// https://msdn.microsoft.com/en-us/library/windows/desktop/aa370659(v=vs.85).aspx
    30  	changePasswordLevel = 1003
    31  )
    32  
    33  // resetJujudPassword sets a password on the jujud service and the jujud user
    34  // and returns it. This should only be done when we're deploying new units.
    35  // The reason is that there isn't a completely secure way of storing the user's password
    36  // and we do not *really* need it except when deploying new units.
    37  var resetJujudPassword = func() (string, error) {
    38  	newPassword, err := utils.RandomPassword()
    39  	if err != nil {
    40  		return "", errors.Trace(err)
    41  	}
    42  	mgr, err := NewServiceManager()
    43  	if err != nil {
    44  		return "", errors.Annotate(err, "could not start service manager")
    45  	}
    46  
    47  	err = ensureJujudPasswordHelper("jujud", newPassword, mgr, &PasswordChanger{})
    48  	if err != nil {
    49  		return "", errors.Annotate(err, "could not change password")
    50  	}
    51  	return newPassword, nil
    52  }
    53  
    54  // ensureJujudPasswordHelper actually does the heavy lifting of changing the password. It checks the registry for a password. If it doesn't exist
    55  // 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.
    56  func ensureJujudPasswordHelper(username, newPassword string, mgr ServiceManager, helpers PasswordChangerHelpers) error {
    57  	err := helpers.ChangeUserPasswordLocalhost(newPassword)
    58  	if err != nil {
    59  		return errors.Annotate(err, "could not change user password")
    60  	}
    61  
    62  	err = helpers.ChangeJujudServicesPassword(newPassword, mgr, ListServices)
    63  	if err != nil {
    64  		return errors.Annotate(err, "could not change password for all jujud services")
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  // TODO(katco): 2016-08-09: lp:1611427
    71  var changeServicePasswordAttempts = utils.AttemptStrategy{
    72  	Total: 5 * time.Second,
    73  	Delay: 6 * time.Second,
    74  }
    75  
    76  // passwordChangerHelpers exists only for making the testing of the ensureJujudPasswordHelper function easier
    77  type PasswordChangerHelpers interface {
    78  	// ChangeUserPasswordLocalhost changes the password for the jujud user on the local computer using syscalls
    79  	ChangeUserPasswordLocalhost(newPassword string) error
    80  
    81  	// changeJujudServicesPassword changes the password for all the services created by the jujud user
    82  	ChangeJujudServicesPassword(newPassword string, mgr ServiceManager, listServices func() ([]string, error)) error
    83  }
    84  
    85  // passwordChanger implements passwordChangerHelpers
    86  type PasswordChanger struct{}
    87  
    88  // changeUserPasswordLocalhost changes the password for username on localhost
    89  func (c *PasswordChanger) ChangeUserPasswordLocalhost(newPassword string) error {
    90  	serverp, err := syscall.UTF16PtrFromString("localhost")
    91  	if err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  
    95  	userp, err := syscall.UTF16PtrFromString("jujud")
    96  	if err != nil {
    97  		return errors.Trace(err)
    98  	}
    99  
   100  	passp, err := syscall.UTF16PtrFromString(newPassword)
   101  	if err != nil {
   102  		return errors.Trace(err)
   103  	}
   104  
   105  	info := netUserSetPassword{passp}
   106  
   107  	err = netUserSetInfo(serverp, userp, changePasswordLevel, &info, nil)
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func (c *PasswordChanger) ChangeJujudServicesPassword(newPassword string, mgr ServiceManager, listServices func() ([]string, error)) error {
   116  	// Iterate through all services and change the password for those belonging
   117  	// to jujud
   118  	svcs, err := listServices()
   119  	if err != nil {
   120  		return errors.Trace(err)
   121  	}
   122  	for _, svc := range svcs {
   123  		modifiedService := false
   124  		var err error
   125  		for attempt := changeServicePasswordAttempts.Start(); attempt.Next(); {
   126  			err = mgr.ChangeServicePassword(svc, newPassword)
   127  			if err != nil {
   128  				logger.Errorf("retrying to change password on service %v; error: %v", svc, err)
   129  			}
   130  			if err == nil {
   131  				modifiedService = true
   132  				break
   133  			}
   134  		}
   135  		if !modifiedService {
   136  			return errors.Trace(err)
   137  		}
   138  	}
   139  
   140  	return nil
   141  }