From d689e6b4d3cd651a90de77adafea1017c9969808 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 8 Mar 2018 15:14:57 -0800 Subject: [PATCH 1/8] allow users of AWS to use the dynamically-generated admin password which we use as the winRM password as an elevated password in the Powershell provisioner, as well as an environment variable in same provisoner. --- builder/amazon/common/step_get_password.go | 7 ++++++- common/step_http_server.go | 22 ++++++++-------------- provisioner/powershell/provisioner.go | 19 ++++++++++++++++++- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/builder/amazon/common/step_get_password.go b/builder/amazon/common/step_get_password.go index 5e2a236c2..c9011d930 100644 --- a/builder/amazon/common/step_get_password.go +++ b/builder/amazon/common/step_get_password.go @@ -12,6 +12,7 @@ import ( "time" "github.com/aws/aws-sdk-go/service/ec2" + commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -92,11 +93,15 @@ WaitLoop: 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) return multistep.ActionContinue } -func (s *StepGetPassword) Cleanup(multistep.StateBag) {} +func (s *StepGetPassword) Cleanup(multistep.StateBag) { + commonhelper.RemoveSharedStateFile("winrm_password") +} func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { ec2conn := state.Get("ec2").(*ec2.EC2) diff --git a/common/step_http_server.go b/common/step_http_server.go index e90c6ef60..953ef38c1 100644 --- a/common/step_http_server.go +++ b/common/step_http_server.go @@ -3,14 +3,12 @@ package common import ( "context" "fmt" - "io/ioutil" "log" "math/rand" "net" "net/http" - "os" - "path/filepath" + "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) @@ -77,25 +75,21 @@ func (s *StepHTTPServer) Run(_ context.Context, state multistep.StateBag) multis return multistep.ActionContinue } -func httpAddrFilename(suffix string) string { - uuid := os.Getenv("PACKER_RUN_UUID") - return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s", uuid, suffix)) -} - func SetHTTPPort(port string) error { - return ioutil.WriteFile(httpAddrFilename("port"), []byte(port), 0644) + return common.SetSharedState("port", port) } func SetHTTPIP(ip string) error { - return ioutil.WriteFile(httpAddrFilename("ip"), []byte(ip), 0644) + return common.SetSharedState("ip", ip) } func GetHTTPAddr() string { - ip, err := ioutil.ReadFile(httpAddrFilename("ip")) + ip, err := common.RetrieveSharedState("ip") if err != nil { return "" } - port, err := ioutil.ReadFile(httpAddrFilename("port")) + + port, err := common.RetrieveSharedState("port") if err != nil { return "" } @@ -107,6 +101,6 @@ func (s *StepHTTPServer) Cleanup(multistep.StateBag) { // Close the listener so that the HTTP server stops s.l.Close() } - os.Remove(httpAddrFilename("port")) - os.Remove(httpAddrFilename("ip")) + common.RemoveSharedStateFile("port") + common.RemoveSharedStateFile("ip") } diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 9655f6fed..a071a6b21 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/uuid" + commonhelper "github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -114,7 +115,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) - if err != nil { return err } @@ -358,10 +358,17 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { // Always available Packer provided env vars envVars["PACKER_BUILD_NAME"] = p.config.PackerBuildName envVars["PACKER_BUILDER_TYPE"] = p.config.PackerBuilderType + httpAddr := common.GetHTTPAddr() if httpAddr != "" { envVars["PACKER_HTTP_ADDR"] = httpAddr } + winRMPass, err := commonhelper.RetrieveSharedState("winrm_password") + // This shared state is only created by the amazon builder. + if err == nil { + envVars["AUTO_WINRM_PASSWORD"] = psEscape.Replace(winRMPass) + + } // Split vars into key/value components for _, envVar := range p.config.Vars { @@ -501,6 +508,16 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell", p.config.ElevatedUser, escapedElevatedUser) } + // Replace ElevatedPassword for winrm users who used this feature + if strings.Compare(p.config.ElevatedPassword, ".AUTO_WINRM_PASSWORD") == 0 { + winRMPass, err := commonhelper.RetrieveSharedState("winrm_password") + // This shared state is only created by the amazon builder. + if err != nil { + fmt.Printf("Error reading AUTO_WINRM_PASSWORD from state: %s", err) + return "", err + } + p.config.ElevatedPassword = winRMPass + } // Escape chars special to PowerShell in the ElevatedPassword string escapedElevatedPassword := psEscape.Replace(p.config.ElevatedPassword) From 4d19f4f8b6cb5b923170684c438a13d62a155b61 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 8 Mar 2018 15:22:28 -0800 Subject: [PATCH 2/8] add all the new files --- helper/common/shared_state.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 helper/common/shared_state.go diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go new file mode 100644 index 000000000..5029bfbab --- /dev/null +++ b/helper/common/shared_state.go @@ -0,0 +1,31 @@ +package common + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// Used to set variables which we need to access later in the build, where +// state bag and config information won't work +func sharedStateFilename(suffix string) string { + uuid := os.Getenv("PACKER_RUN_UUID") + return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s", uuid, suffix)) +} + +func SetSharedState(key string, value string) error { + return ioutil.WriteFile(sharedStateFilename(key), []byte(value), 0644) +} + +func RetrieveSharedState(key string) (string, error) { + value, err := ioutil.ReadFile(sharedStateFilename(key)) + if err != nil { + return "", err + } + return string(value), nil +} + +func RemoveSharedStateFile(key string) { + os.Remove(sharedStateFilename(key)) +} From 559719020cf0722c9044fb7aca74eacddf7f8ccf Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 14 Mar 2018 17:19:17 -0700 Subject: [PATCH 3/8] use {{.WinRMPassword}} instead of some other weirdness --- provisioner/powershell/provisioner.go | 51 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index a071a6b21..77ac61afc 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -100,11 +100,21 @@ type Provisioner struct { } type ExecuteCommandTemplate struct { - Vars string - Path string + Vars string + Path string + WinRMPassword string +} + +type EnvVarsTemplate struct { + WinRMPassword string } func (p *Provisioner) Prepare(raws ...interface{}) error { + //Create passthrough for winrm password so we can fill it in once we know it + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: `{{.WinRMPassword}}`, + } + err := config.Decode(&p.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, @@ -115,6 +125,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) + if err != nil { return err } @@ -369,9 +380,16 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { envVars["AUTO_WINRM_PASSWORD"] = psEscape.Replace(winRMPass) } - + // interpolate environment variables + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: p.getWinRMPassword(), + } // Split vars into key/value components for _, envVar := range p.config.Vars { + envVar, err = interpolate.Render(envVar, &p.config.ctx) + if err != nil { + return + } keyValue := strings.SplitN(envVar, "=", 2) // Escape chars special to PS in each env var value escapedEnvVarValue := psEscape.Replace(keyValue[1]) @@ -430,8 +448,9 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro } p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: envVarPath, + Path: p.config.RemotePath, + Vars: envVarPath, + WinRMPassword: p.getWinRMPassword(), } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) @@ -443,6 +462,11 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return command, nil } +func (p *Provisioner) getWinRMPassword() string { + winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password") + return winRMPass + +} func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { // Prepare everything needed to enable the required env vars within the remote environment envVarPath, err := p.prepareEnvVars(true) @@ -451,8 +475,9 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) } p.config.ctx.Data = &ExecuteCommandTemplate{ - Path: p.config.RemotePath, - Vars: envVarPath, + Path: p.config.RemotePath, + Vars: envVarPath, + WinRMPassword: p.getWinRMPassword(), } command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) if err != nil { @@ -509,16 +534,12 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin p.config.ElevatedUser, escapedElevatedUser) } // Replace ElevatedPassword for winrm users who used this feature - if strings.Compare(p.config.ElevatedPassword, ".AUTO_WINRM_PASSWORD") == 0 { - winRMPass, err := commonhelper.RetrieveSharedState("winrm_password") - // This shared state is only created by the amazon builder. - if err != nil { - fmt.Printf("Error reading AUTO_WINRM_PASSWORD from state: %s", err) - return "", err - } - p.config.ElevatedPassword = winRMPass + p.config.ctx.Data = &EnvVarsTemplate{ + WinRMPassword: p.getWinRMPassword(), } + p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) + // Escape chars special to PowerShell in the ElevatedPassword string escapedElevatedPassword := psEscape.Replace(p.config.ElevatedPassword) if escapedElevatedPassword != p.config.ElevatedPassword { From ce1ab1f021d5a6fe6d28fbced4fa1c319474f15b Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 15 Mar 2018 09:44:22 -0700 Subject: [PATCH 4/8] fix winrm password --- provisioner/powershell/provisioner.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 77ac61afc..e3340d771 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -374,19 +374,14 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { if httpAddr != "" { envVars["PACKER_HTTP_ADDR"] = httpAddr } - winRMPass, err := commonhelper.RetrieveSharedState("winrm_password") - // This shared state is only created by the amazon builder. - if err == nil { - envVars["AUTO_WINRM_PASSWORD"] = psEscape.Replace(winRMPass) - } // interpolate environment variables p.config.ctx.Data = &EnvVarsTemplate{ WinRMPassword: p.getWinRMPassword(), } // Split vars into key/value components for _, envVar := range p.config.Vars { - envVar, err = interpolate.Render(envVar, &p.config.ctx) + envVar, err := interpolate.Render(envVar, &p.config.ctx) if err != nil { return } From 4e32d0da24e345b3d041612fa08faee0e64027ec Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 16 Mar 2018 14:11:32 -0700 Subject: [PATCH 5/8] stricter permissions on shared state file since it now contains a password --- helper/common/shared_state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go index 5029bfbab..92ffbaeaf 100644 --- a/helper/common/shared_state.go +++ b/helper/common/shared_state.go @@ -15,7 +15,7 @@ func sharedStateFilename(suffix string) string { } func SetSharedState(key string, value string) error { - return ioutil.WriteFile(sharedStateFilename(key), []byte(value), 0644) + return ioutil.WriteFile(sharedStateFilename(key), []byte(value), 0600) } func RetrieveSharedState(key string) (string, error) { From de7d1430bdcd9430e1b9a006b260871eabbc735f Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 16 Mar 2018 14:25:09 -0700 Subject: [PATCH 6/8] update powershell docs to include reference to the new winrmpassword template variable --- .../docs/provisioners/powershell.html.md | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/website/source/docs/provisioners/powershell.html.md b/website/source/docs/provisioners/powershell.html.md index a9c218638..d2707c76a 100644 --- a/website/source/docs/provisioners/powershell.html.md +++ b/website/source/docs/provisioners/powershell.html.md @@ -72,7 +72,16 @@ Optional parameters: - `environment_vars` (array of strings) - An array of key/value pairs to inject prior to the execute\_command. The format should be `key=value`. Packer injects some environmental variables by default into the - environment, as well, which are covered in the section below. + environment, as well, which are covered in the section below. If you are + using AWS and would like to use the randomly-generated unique + If you are running on AWS and would like to access the AWS-generated + Administrator password that Packer uses to connect to the instance via + WinRM, you can use the template variable `{{.WinRMPassword}}` to set this + as an environment variable. For example: + + ```json + "environment_vars": "WINRMPASS={{.WinRMPassword}}", + ``` - `execute_command` (string) - The command to use to execute the script. By default this is as follows: @@ -89,7 +98,15 @@ Optional parameters: - `elevated_user` and `elevated_password` (string) - If specified, the PowerShell script will be run with elevated privileges using the given - Windows user. + Windows user. If you are running a build on AWS and would like to run using + the AWS-generated password that Packer uses to connect to the instance via, + WinRM, you may do so by using the template variable {{.WinRMPassword}}. + For example: + + ``` json + "elevated_user": "Administrator", + "elevated_password": "{{.WinRMPassword}}", + ``` - `remote_path` (string) - The path where the script will be uploaded to in the machine. This defaults to "c:/Windows/Temp/script.ps1". This value must From f3dc7546d4f5724dc0de4eaf491423d5b123f266 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 16 Mar 2018 14:28:20 -0700 Subject: [PATCH 7/8] even clearer example in winrmpassword docs --- website/source/docs/provisioners/powershell.html.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/source/docs/provisioners/powershell.html.md b/website/source/docs/provisioners/powershell.html.md index d2707c76a..92fc00ed2 100644 --- a/website/source/docs/provisioners/powershell.html.md +++ b/website/source/docs/provisioners/powershell.html.md @@ -80,7 +80,11 @@ Optional parameters: as an environment variable. For example: ```json - "environment_vars": "WINRMPASS={{.WinRMPassword}}", + { + "type": "powershell", + "environment_vars": "WINRMPASS={{.WinRMPassword}}", + "inline": ["Write-Host \"Automatically generated aws password is: $Env:WINRMPASS\""] + }, ``` - `execute_command` (string) - The command to use to execute the script. By From 4e321b2dfa1f825d91d823ef5c3c61ce0f824e31 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 22 Mar 2018 15:54:46 -0700 Subject: [PATCH 8/8] don't need to use a receiver with this function --- provisioner/powershell/provisioner.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index e3340d771..6b7adfac6 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -111,6 +111,7 @@ type EnvVarsTemplate struct { func (p *Provisioner) Prepare(raws ...interface{}) error { //Create passthrough for winrm password so we can fill it in once we know it + log.Printf("MEGAN context is %#v", p.config.ctx) p.config.ctx.Data = &EnvVarsTemplate{ WinRMPassword: `{{.WinRMPassword}}`, } @@ -247,6 +248,7 @@ func extractScript(p *Provisioner) (string, error) { } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + log.Printf("MEGAN context is %#v", p.config.ctx) ui.Say(fmt.Sprintf("Provisioning with Powershell...")) p.communicator = comm @@ -377,7 +379,7 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { // interpolate environment variables p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: p.getWinRMPassword(), + WinRMPassword: getWinRMPassword(), } // Split vars into key/value components for _, envVar := range p.config.Vars { @@ -445,7 +447,7 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro p.config.ctx.Data = &ExecuteCommandTemplate{ Path: p.config.RemotePath, Vars: envVarPath, - WinRMPassword: p.getWinRMPassword(), + WinRMPassword: getWinRMPassword(), } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) @@ -457,7 +459,7 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return command, nil } -func (p *Provisioner) getWinRMPassword() string { +func getWinRMPassword() string { winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password") return winRMPass @@ -472,7 +474,7 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) p.config.ctx.Data = &ExecuteCommandTemplate{ Path: p.config.RemotePath, Vars: envVarPath, - WinRMPassword: p.getWinRMPassword(), + WinRMPassword: getWinRMPassword(), } command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) if err != nil { @@ -530,7 +532,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin } // Replace ElevatedPassword for winrm users who used this feature p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: p.getWinRMPassword(), + WinRMPassword: getWinRMPassword(), } p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)