github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  var changeServicePasswordAttempts = utils.AttemptStrategy{
    71  	Total: 5 * time.Second,
    72  	Delay: 6 * time.Second,
    73  }
    74  
    75  // passwordChangerHelpers exists only for making the testing of the ensureJujudPasswordHelper function easier
    76  type PasswordChangerHelpers interface {
    77  	// ChangeUserPasswordLocalhost changes the password for the jujud user on the local computer using syscalls
    78  	ChangeUserPasswordLocalhost(newPassword string) error
    79  
    80  	// changeJujudServicesPassword changes the password for all the services created by the jujud user
    81  	ChangeJujudServicesPassword(newPassword string, mgr ServiceManager, listServices func() ([]string, error)) error
    82  }
    83  
    84  // passwordChanger implements passwordChangerHelpers
    85  type PasswordChanger struct{}
    86  
    87  // changeUserPasswordLocalhost changes the password for username on localhost
    88  func (c *PasswordChanger) ChangeUserPasswordLocalhost(newPassword string) error {
    89  	serverp, err := syscall.UTF16PtrFromString("localhost")
    90  	if err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  
    94  	userp, err := syscall.UTF16PtrFromString("jujud")
    95  	if err != nil {
    96  		return errors.Trace(err)
    97  	}
    98  
    99  	passp, err := syscall.UTF16PtrFromString(newPassword)
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  
   104  	info := netUserSetPassword{passp}
   105  
   106  	err = netUserSetInfo(serverp, userp, changePasswordLevel, &info, nil)
   107  	if err != nil {
   108  		return errors.Trace(err)
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func (c *PasswordChanger) ChangeJujudServicesPassword(newPassword string, mgr ServiceManager, listServices func() ([]string, error)) error {
   115  	// Iterate through all services and change the password for those belonging
   116  	// to jujud
   117  	svcs, err := listServices()
   118  	if err != nil {
   119  		return errors.Trace(err)
   120  	}
   121  	for _, svc := range svcs {
   122  		modifiedService := false
   123  		var err error
   124  		for attempt := changeServicePasswordAttempts.Start(); attempt.Next(); {
   125  			err = mgr.ChangeServicePassword(svc, newPassword)
   126  			if err != nil {
   127  				logger.Errorf("retrying to change password on service %v; error: %v", svc, err)
   128  			}
   129  			if err == nil {
   130  				modifiedService = true
   131  				break
   132  			}
   133  		}
   134  		if !modifiedService {
   135  			return errors.Trace(err)
   136  		}
   137  	}
   138  
   139  	return nil
   140  }