From b0c4784b964ac02599b2a1d468c40dbb869ac075 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Mon, 4 Feb 2019 11:58:15 -0600 Subject: [PATCH] feature: bsusurrogate, add clean get password step --- builder/osc/bsusurrogate/builder.go | 6 + builder/osc/common/step_get_password.go | 176 ++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 builder/osc/common/step_get_password.go diff --git a/builder/osc/bsusurrogate/builder.go b/builder/osc/bsusurrogate/builder.go index 7ecc2fe87..810b73dfa 100644 --- a/builder/osc/bsusurrogate/builder.go +++ b/builder/osc/bsusurrogate/builder.go @@ -186,6 +186,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe UserDataFile: b.config.UserDataFile, VolumeTags: b.config.VolumeRunTags, }, + &osccommon.StepGetPassword{ + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + BuildName: b.config.PackerBuildName, + }, } b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) diff --git a/builder/osc/common/step_get_password.go b/builder/osc/common/step_get_password.go new file mode 100644 index 000000000..d26b9bc2f --- /dev/null +++ b/builder/osc/common/step_get_password.go @@ -0,0 +1,176 @@ +package common + +import ( + "context" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "log" + "time" + + commonhelper "github.com/hashicorp/packer/helper/common" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/outscale/osc-go/oapi" +) + +// StepGetPassword reads the password from a Windows server and sets it +// on the WinRM config. +type StepGetPassword struct { + Debug bool + Comm *communicator.Config + Timeout time.Duration + BuildName string +} + +func (s *StepGetPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + // Skip if we're not using winrm + if s.Comm.Type != "winrm" { + log.Printf("[INFO] Not using winrm communicator, skipping get password...") + return multistep.ActionContinue + } + + // If we already have a password, skip it + if s.Comm.WinRMPassword != "" { + ui.Say("Skipping waiting for password since WinRM password set...") + return multistep.ActionContinue + } + + // Get the password + var password string + var err error + cancel := make(chan struct{}) + waitDone := make(chan bool, 1) + go func() { + ui.Say("Waiting for auto-generated password for vm...") + ui.Message( + "It is normal for this process to take up to 15 minutes,\n" + + "but it usually takes around 5. Please wait.") + password, err = s.waitForPassword(state, cancel) + waitDone <- true + }() + + timeout := time.After(s.Timeout) +WaitLoop: + for { + // Wait for either SSH to become available, a timeout to occur, + // or an interrupt to come through. + select { + case <-waitDone: + if err != nil { + ui.Error(fmt.Sprintf("Error waiting for password: %s", err)) + state.Put("error", err) + return multistep.ActionHalt + } + + ui.Message(fmt.Sprintf(" \nPassword retrieved!")) + s.Comm.WinRMPassword = password + break WaitLoop + case <-timeout: + err := fmt.Errorf("Timeout waiting for password.") + state.Put("error", err) + ui.Error(err.Error()) + close(cancel) + return multistep.ActionHalt + case <-time.After(1 * time.Second): + if _, ok := state.GetOk(multistep.StateCancelled); ok { + // The step sequence was cancelled, so cancel waiting for password + // and just start the halting process. + close(cancel) + log.Println("[WARN] Interrupt detected, quitting waiting for password.") + return multistep.ActionHalt + } + } + } + + // In debug-mode, we output the password + if s.Debug { + ui.Message(fmt.Sprintf( + "Password (since debug is enabled): %s", s.Comm.WinRMPassword)) + } + // store so that we can access this later during provisioning + + commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) + packer.LogSecretFilter.Set(s.Comm.WinRMPassword) + + return multistep.ActionContinue +} + +func (s *StepGetPassword) Cleanup(multistep.StateBag) { + commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) +} + +func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { + oapiconn := state.Get("oapi").(*oapi.Client) + vm := state.Get("vm").(oapi.Vm) + privateKey := s.Comm.SSHPrivateKey + + for { + select { + case <-cancel: + log.Println("[INFO] Retrieve password wait cancelled. Exiting loop.") + return "", errors.New("Retrieve password wait cancelled") + case <-time.After(5 * time.Second): + } + + resp, err := oapiconn.POST_ReadAdminPassword(oapi.ReadAdminPasswordRequest{ + VmId: vm.VmId, + }) + if err != nil { + err := fmt.Errorf("Error retrieving auto-generated vm password: %s", err) + return "", err + } + + if resp.OK.AdminPassword != "" { + decryptedPassword, err := decryptPasswordDataWithPrivateKey( + resp.OK.AdminPassword, []byte(privateKey)) + if err != nil { + err := fmt.Errorf("Error decrypting auto-generated vm password: %s", err) + return "", err + } + + return decryptedPassword, nil + } + + log.Printf("[DEBUG] Password is blank, will retry...") + } +} + +func decryptPasswordDataWithPrivateKey(passwordData string, pemBytes []byte) (string, error) { + encryptedPasswd, err := base64.StdEncoding.DecodeString(passwordData) + if err != nil { + return "", err + } + + block, _ := pem.Decode(pemBytes) + var asn1Bytes []byte + if _, ok := block.Headers["DEK-Info"]; ok { + return "", errors.New("encrypted private key isn't yet supported") + /* + asn1Bytes, err = x509.DecryptPEMBlock(block, password) + if err != nil { + return "", err + } + */ + } else { + asn1Bytes = block.Bytes + } + + key, err := x509.ParsePKCS1PrivateKey(asn1Bytes) + if err != nil { + return "", err + } + + out, err := rsa.DecryptPKCS1v15(nil, key, encryptedPasswd) + if err != nil { + return "", err + } + + return string(out), nil +}