From 17597b48e1002717faac9b61fdc653612fe23e00 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 4 Jul 2016 23:44:33 +0100 Subject: [PATCH] Base64 encode powershell to avoid any necessary escaping --- powershell/powershell.go | 2 +- provisioner/powershell/powershell.go | 6 ++ provisioner/powershell/provisioner.go | 38 +++++++---- provisioner/powershell/provisioner_test.go | 77 ++++++++++++++-------- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/powershell/powershell.go b/powershell/powershell.go index 051f03a67..044ec6384 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -101,7 +101,7 @@ func IsPowershellAvailable() (bool, string, error) { if err != nil { return false, "", err } else { - return false, path, err + return true, path, err } } diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index 1f5a7ffad..5f339d116 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -15,3 +15,9 @@ func powershellEncode(buffer []byte) string { input := []uint8(wideCmd) return base64.StdEncoding.EncodeToString(input) } + +func powershellDecode(message string) (retour string) { + base64Text := make([]byte, base64.StdEncoding.DecodedLen(len(message))) + base64.StdEncoding.Decode(base64Text, []byte(message)) + return string(base64Text) +} diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 4d8a98e5a..ad7f71b0f 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -107,24 +107,25 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) + if err != nil { return err } if p.config.EnvVarFormat == "" { - p.config.EnvVarFormat = `$env:%s=\"%s\"; ` + p.config.EnvVarFormat = `$env:%s="%s"; ` } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` + p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `{{.Vars}}{{.Path}}` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -374,28 +375,41 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e } func (p *Provisioner) createCommandText() (command string, err error) { + // Return the interpolated command + if p.config.ElevatedUser == "" { + return p.createCommandTextNonPrivileged() + } else { + return p.createCommandTextPrivileged() + } +} + +func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) { // Create environment variables to set before executing the command flattenedEnvVars, err := p.createFlattenedEnvVars(false) if err != nil { return "", err } - p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) + if err != nil { return "", fmt.Errorf("Error processing command: %s", err) } - // Return the interpolated command - if p.config.ElevatedUser == "" { - return command, nil - } + encodedCommand := "powershell -executionpolicy bypass -encodedCommand " + powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; "+command+"; exit $LastExitCode")) + + return encodedCommand, err +} +func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { // Can't double escape the env vars, lets create shiny new ones - flattenedEnvVars, err = p.createFlattenedEnvVars(true) + flattenedEnvVars, err := p.createFlattenedEnvVars(true) + if err != nil { + return "", err + } p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, @@ -412,7 +426,7 @@ func (p *Provisioner) createCommandText() (command string, err error) { // Return the path to the elevated shell wrapper command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path) - return + return command, err } func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { @@ -425,7 +439,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 2719a9621..cb4c26eb3 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `{{.Vars}}{{.Path}}` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `{{.Vars}}{{.Path}}'` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -95,8 +95,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } } - if p.config.ElevatedEnvVarFormat != `$env:%s=\"%s\"; ` { - t.Fatalf(`Default command should be powershell '$env:%%s=\"%%s\"; ', but got %s`, p.config.ElevatedEnvVarFormat) + if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { + t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } @@ -389,11 +389,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } envVars := make([]string, 2) @@ -408,11 +412,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded = expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -434,12 +442,15 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error") } - //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -468,11 +479,15 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -500,7 +515,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -511,7 +526,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -522,7 +537,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -545,7 +560,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +571,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +582,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,8 +597,16 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { - t.Fatalf("Got unexpected non-elevated command: %s", cmd) + + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + + // Should run the command without alteration + if cmd != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } // Elevated