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  }