diff --git a/packer/provisioner_mock.go b/packer/provisioner_mock.go
index 62b304ccb..0c7a25791 100644
--- a/packer/provisioner_mock.go
+++ b/packer/provisioner_mock.go
@@ -34,3 +34,15 @@ func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
func (t *MockProvisioner) Cancel() {
t.CancelCalled = true
}
+
+func (t *MockProvisioner) Communicator() Communicator {
+ return t.ProvCommunicator
+}
+
+func (t *MockProvisioner) ElevatedUser() string {
+ return "user"
+}
+
+func (t *MockProvisioner) ElevatedPassword() string {
+ return "password"
+}
diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go
index 8a3bff170..6d451ace4 100644
--- a/provisioner/chef-client/provisioner.go
+++ b/provisioner/chef-client/provisioner.go
@@ -14,6 +14,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/provisioner"
@@ -50,6 +51,8 @@ type Config struct {
ChefEnvironment string `mapstructure:"chef_environment"`
ClientKey string `mapstructure:"client_key"`
ConfigTemplate string `mapstructure:"config_template"`
+ ElevatedUser string `mapstructure:"elevated_user"`
+ ElevatedPassword string `mapstructure:"elevated_password"`
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
ExecuteCommand string `mapstructure:"execute_command"`
GuestOSType string `mapstructure:"guest_os_type"`
@@ -76,6 +79,7 @@ type Config struct {
type Provisioner struct {
config Config
+ communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
@@ -94,6 +98,10 @@ type ConfigTemplate struct {
ValidationKeyPath string
}
+type EnvVarsTemplate struct {
+ WinRMPassword string
+}
+
type ExecuteTemplate struct {
ConfigPath string
JsonPath string
@@ -111,6 +119,12 @@ type KnifeTemplate struct {
}
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,
@@ -221,6 +235,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
+ p.communicator = comm
+
nodeName := p.config.NodeName
if nodeName == "" {
nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
@@ -551,6 +567,13 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
return err
}
+ if p.config.ElevatedUser != "" {
+ command, err = provisioner.GenerateElevatedRunner(command, p)
+ if err != nil {
+ return err
+ }
+ }
+
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
cmd := &packer.RemoteCmd{
@@ -676,6 +699,31 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
return result, nil
}
+func getWinRMPassword(buildName string) string {
+ winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
+ packer.LogSecretFilter.Set(winRMPass)
+ return winRMPass
+}
+
+func (p *Provisioner) Communicator() packer.Communicator {
+ return p.communicator
+}
+
+func (p *Provisioner) ElevatedUser() string {
+ return p.config.ElevatedUser
+}
+
+func (p *Provisioner) ElevatedPassword() string {
+ // Replace ElevatedPassword for winrm users who used this feature
+ p.config.ctx.Data = &EnvVarsTemplate{
+ WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
+ }
+
+ elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
+
+ return elevatedPassword
+}
+
var DefaultConfigTemplate = `
log_level :info
log_location STDOUT
diff --git a/provisioner/elevated.go b/provisioner/elevated.go
new file mode 100644
index 000000000..d34e97e0a
--- /dev/null
+++ b/provisioner/elevated.go
@@ -0,0 +1,192 @@
+package provisioner
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strings"
+ "text/template"
+
+ "github.com/hashicorp/packer/common/uuid"
+ "github.com/hashicorp/packer/packer"
+)
+
+type ElevatedProvisioner interface {
+ Communicator() packer.Communicator
+ ElevatedUser() string
+ ElevatedPassword() string
+}
+
+type elevatedOptions struct {
+ User string
+ Password string
+ TaskName string
+ TaskDescription string
+ LogFile string
+ XMLEscapedCommand string
+}
+
+var psEscape = strings.NewReplacer(
+ "$", "`$",
+ "\"", "`\"",
+ "`", "``",
+ "'", "`'",
+)
+
+var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
+$name = "{{.TaskName}}"
+$log = [System.Environment]::ExpandEnvironmentVariables("{{.LogFile}}")
+$s = New-Object -ComObject "Schedule.Service"
+$s.Connect()
+$t = $s.NewTask($null)
+$xml = [xml]@'
+
+
+
+ {{.TaskDescription}}
+
+
+
+ {{.User}}
+ Password
+ HighestAvailable
+
+
+
+ IgnoreNew
+ false
+ false
+ true
+ false
+ false
+
+ false
+ false
+
+ true
+ true
+ false
+ false
+ false
+ PT24H
+ 4
+
+
+
+ cmd
+ /c {{.XMLEscapedCommand}}
+
+
+
+'@
+$logon_type = 1
+$password = "{{.Password}}"
+if ($password.Length -eq 0) {
+ $logon_type = 5
+ $password = $null
+ $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
+ $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
+ $node = $xml.SelectSingleNode("/ns:Task/ns:Principals/ns:Principal/ns:LogonType", $ns)
+ $node.ParentNode.RemoveChild($node) | Out-Null
+}
+$t.XmlText = $xml.OuterXml
+if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}
+$f = $s.GetFolder("\")
+$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", $password, $logon_type, $null) | Out-Null
+$t = $f.GetTask("\$name")
+$t.Run($null) | Out-Null
+$timeout = 10
+$sec = 0
+while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
+ Start-Sleep -s 1
+ $sec++
+}
+
+$line = 0
+do {
+ Start-Sleep -m 100
+ if (Test-Path $log) {
+ Get-Content $log | select -skip $line | ForEach {
+ $line += 1
+ Write-Output "$_"
+ }
+ }
+} while (!($t.state -eq 3))
+$result = $t.LastTaskResult
+if (Test-Path $log) {
+ Remove-Item $log -Force -ErrorAction SilentlyContinue | Out-Null
+}
+[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
+exit $result`))
+
+func GenerateElevatedRunner(command string, p ElevatedProvisioner) (uploadedPath string, err error) {
+ log.Printf("Building elevated command wrapper for: %s", command)
+
+ var buffer bytes.Buffer
+
+ // Output from the elevated command cannot be returned directly to the
+ // Packer console. In order to be able to view output from elevated
+ // commands and scripts an indirect approach is used by which the commands
+ // output is first redirected to file. The output file is then 'watched'
+ // by Packer while the elevated command is running and any content
+ // appearing in the file is written out to the console. Below the portion
+ // of command required to redirect output from the command to file is
+ // built and appended to the existing command string
+ taskName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
+ // Only use %ENVVAR% format for environment variables when setting the log
+ // file path; Do NOT use $env:ENVVAR format as it won't be expanded
+ // correctly in the elevatedTemplate
+ logFile := `%SYSTEMROOT%/Temp/` + taskName + ".out"
+ command += fmt.Sprintf(" > %s 2>&1", logFile)
+
+ // elevatedTemplate wraps the command in a single quoted XML text string
+ // so we need to escape characters considered 'special' in XML.
+ err = xml.EscapeText(&buffer, []byte(command))
+ if err != nil {
+ return "", fmt.Errorf("Error escaping characters special to XML in command %s: %s", command, err)
+ }
+ escapedCommand := buffer.String()
+ log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand)
+ buffer.Reset()
+
+ // Escape chars special to PowerShell in the ElevatedUser string
+ elevatedUser := p.ElevatedUser()
+ escapedElevatedUser := psEscape.Replace(elevatedUser)
+ if escapedElevatedUser != elevatedUser {
+ log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell",
+ elevatedUser, escapedElevatedUser)
+ }
+
+ // Escape chars special to PowerShell in the ElevatedPassword string
+ elevatedPassword := p.ElevatedPassword()
+ escapedElevatedPassword := psEscape.Replace(elevatedPassword)
+ if escapedElevatedPassword != elevatedPassword {
+ log.Printf("Elevated password %s converted to %s after escaping chars special to PowerShell",
+ elevatedPassword, escapedElevatedPassword)
+ }
+
+ // Generate command
+ err = elevatedTemplate.Execute(&buffer, elevatedOptions{
+ User: escapedElevatedUser,
+ Password: escapedElevatedPassword,
+ TaskName: taskName,
+ TaskDescription: "Packer elevated task",
+ LogFile: logFile,
+ XMLEscapedCommand: escapedCommand,
+ })
+
+ if err != nil {
+ fmt.Printf("Error creating elevated template: %s", err)
+ return "", err
+ }
+ uuid := uuid.TimeOrderedUUID()
+ path := fmt.Sprintf(`C:/Windows/Temp/packer-elevated-shell-%s.ps1`, uuid)
+ log.Printf("Uploading elevated shell wrapper for command [%s] to [%s]", command, path)
+ err = p.Communicator().Upload(path, &buffer, nil)
+ if err != nil {
+ return "", fmt.Errorf("Error preparing elevated powershell script: %s", err)
+ }
+
+ return fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path), err
+}
diff --git a/provisioner/elevated_test.go b/provisioner/elevated_test.go
new file mode 100644
index 000000000..ac8759ef2
--- /dev/null
+++ b/provisioner/elevated_test.go
@@ -0,0 +1,38 @@
+package provisioner
+
+import (
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/packer/packer"
+)
+
+func testConfig() map[string]interface{} {
+ return map[string]interface{}{
+ "inline": []interface{}{"foo", "bar"},
+ }
+}
+
+func TestProvisioner_GenerateElevatedRunner(t *testing.T) {
+
+ // Non-elevated
+ config := testConfig()
+ p := new(packer.MockProvisioner)
+ p.Prepare(config)
+ comm := new(packer.MockCommunicator)
+ p.ProvCommunicator = comm
+ path, err := GenerateElevatedRunner("whoami", p)
+
+ if err != nil {
+ t.Fatalf("Did not expect error: %s", err.Error())
+ }
+
+ if comm.UploadCalled != true {
+ t.Fatalf("Should have uploaded file")
+ }
+
+ matched, _ := regexp.MatchString("C:/Windows/Temp/packer-elevated-shell.*", path)
+ if !matched {
+ t.Fatalf("Got unexpected file: %s", path)
+ }
+}
diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go
deleted file mode 100644
index be8dcdf62..000000000
--- a/provisioner/powershell/elevated.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package powershell
-
-import (
- "text/template"
-)
-
-type elevatedOptions struct {
- User string
- Password string
- TaskName string
- TaskDescription string
- LogFile string
- XMLEscapedCommand string
-}
-
-var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
-$name = "{{.TaskName}}"
-$log = [System.Environment]::ExpandEnvironmentVariables("{{.LogFile}}")
-$s = New-Object -ComObject "Schedule.Service"
-$s.Connect()
-$t = $s.NewTask($null)
-$xml = [xml]@'
-
-
-
- {{.TaskDescription}}
-
-
-
- {{.User}}
- Password
- HighestAvailable
-
-
-
- IgnoreNew
- false
- false
- true
- false
- false
-
- false
- false
-
- true
- true
- false
- false
- false
- PT24H
- 4
-
-
-
- cmd
- /c {{.XMLEscapedCommand}}
-
-
-
-'@
-$logon_type = 1
-$password = "{{.Password}}"
-if ($password.Length -eq 0) {
- $logon_type = 5
- $password = $null
- $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
- $ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI)
- $node = $xml.SelectSingleNode("/ns:Task/ns:Principals/ns:Principal/ns:LogonType", $ns)
- $node.ParentNode.RemoveChild($node) | Out-Null
-}
-$t.XmlText = $xml.OuterXml
-if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}
-$f = $s.GetFolder("\")
-$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", $password, $logon_type, $null) | Out-Null
-$t = $f.GetTask("\$name")
-$t.Run($null) | Out-Null
-$timeout = 10
-$sec = 0
-while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
- Start-Sleep -s 1
- $sec++
-}
-
-$line = 0
-do {
- Start-Sleep -m 100
- if (Test-Path $log) {
- Get-Content $log | select -skip $line | ForEach {
- $line += 1
- Write-Output "$_"
- }
- }
-} while (!($t.state -eq 3))
-$result = $t.LastTaskResult
-if (Test-Path $log) {
- Remove-Item $log -Force -ErrorAction SilentlyContinue | Out-Null
-}
-[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
-exit $result`))
diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go
index 3888b3d26..1629c9aa0 100644
--- a/provisioner/powershell/provisioner.go
+++ b/provisioner/powershell/provisioner.go
@@ -4,8 +4,6 @@ package powershell
import (
"bufio"
- "bytes"
- "encoding/xml"
"errors"
"fmt"
"io/ioutil"
@@ -20,6 +18,7 @@ import (
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
+ "github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate"
)
@@ -507,90 +506,29 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error)
return "", fmt.Errorf("Error processing command: %s", err)
}
- // OK so we need an elevated shell runner to wrap our command, this is
- // going to have its own path generate the script and update the command
- // runner in the process
- path, err := p.generateElevatedRunner(command)
+ command, err = provisioner.GenerateElevatedRunner(command, p)
if err != nil {
return "", fmt.Errorf("Error generating elevated runner: %s", err)
}
- // Return the path to the elevated shell wrapper
- command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path)
-
return command, err
}
-func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) {
- log.Printf("Building elevated command wrapper for: %s", command)
+func (p *Provisioner) Communicator() packer.Communicator {
+ return p.communicator
+}
- var buffer bytes.Buffer
+func (p *Provisioner) ElevatedUser() string {
+ return p.config.ElevatedUser
+}
- // Output from the elevated command cannot be returned directly to the
- // Packer console. In order to be able to view output from elevated
- // commands and scripts an indirect approach is used by which the commands
- // output is first redirected to file. The output file is then 'watched'
- // by Packer while the elevated command is running and any content
- // appearing in the file is written out to the console. Below the portion
- // of command required to redirect output from the command to file is
- // built and appended to the existing command string
- taskName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
- // Only use %ENVVAR% format for environment variables when setting the log
- // file path; Do NOT use $env:ENVVAR format as it won't be expanded
- // correctly in the elevatedTemplate
- logFile := `%SYSTEMROOT%/Temp/` + taskName + ".out"
- command += fmt.Sprintf(" > %s 2>&1", logFile)
-
- // elevatedTemplate wraps the command in a single quoted XML text string
- // so we need to escape characters considered 'special' in XML.
- err = xml.EscapeText(&buffer, []byte(command))
- if err != nil {
- return "", fmt.Errorf("Error escaping characters special to XML in command %s: %s", command, err)
- }
- escapedCommand := buffer.String()
- log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand)
- buffer.Reset()
-
- // Escape chars special to PowerShell in the ElevatedUser string
- escapedElevatedUser := psEscape.Replace(p.config.ElevatedUser)
- if escapedElevatedUser != p.config.ElevatedUser {
- log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell",
- p.config.ElevatedUser, escapedElevatedUser)
- }
+func (p *Provisioner) ElevatedPassword() string {
// Replace ElevatedPassword for winrm users who used this feature
p.config.ctx.Data = &EnvVarsTemplate{
WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
}
- p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
+ 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 {
- log.Printf("Elevated password %s converted to %s after escaping chars special to PowerShell",
- p.config.ElevatedPassword, escapedElevatedPassword)
- }
-
- // Generate command
- err = elevatedTemplate.Execute(&buffer, elevatedOptions{
- User: escapedElevatedUser,
- Password: escapedElevatedPassword,
- TaskName: taskName,
- TaskDescription: "Packer elevated task",
- LogFile: logFile,
- XMLEscapedCommand: escapedCommand,
- })
-
- if err != nil {
- fmt.Printf("Error creating elevated template: %s", err)
- return "", err
- }
- uuid := uuid.TimeOrderedUUID()
- path := fmt.Sprintf(`C:/Windows/Temp/packer-elevated-shell-%s.ps1`, uuid)
- log.Printf("Uploading elevated shell wrapper for command [%s] to [%s]", command, path)
- err = p.communicator.Upload(path, &buffer, nil)
- if err != nil {
- return "", fmt.Errorf("Error preparing elevated powershell script: %s", err)
- }
- return path, err
+ return elevatedPassword
}
diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go
index b0a0a27ab..eef61bf8d 100644
--- a/provisioner/powershell/provisioner_test.go
+++ b/provisioner/powershell/provisioner_test.go
@@ -654,30 +654,6 @@ func TestProvision_uploadEnvVars(t *testing.T) {
}
}
-func TestProvision_generateElevatedShellRunner(t *testing.T) {
-
- // Non-elevated
- config := testConfig()
- p := new(Provisioner)
- p.Prepare(config)
- comm := new(packer.MockCommunicator)
- p.communicator = comm
- path, err := p.generateElevatedRunner("whoami")
-
- if err != nil {
- t.Fatalf("Did not expect error: %s", err.Error())
- }
-
- if comm.UploadCalled != true {
- t.Fatalf("Should have uploaded file")
- }
-
- matched, _ := regexp.MatchString("C:/Windows/Temp/packer-elevated-shell.*", path)
- if !matched {
- t.Fatalf("Got unexpected file: %s", path)
- }
-}
-
func TestRetryable(t *testing.T) {
config := testConfig()
diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go
index 3254a6134..8368e37d3 100644
--- a/provisioner/puppet-masterless/provisioner.go
+++ b/provisioner/puppet-masterless/provisioner.go
@@ -10,6 +10,7 @@ import (
"strings"
"github.com/hashicorp/packer/common"
+ commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
@@ -65,6 +66,12 @@ type Config struct {
// The directory from which the command will be executed.
// Packer requires the directory to exist when running puppet.
WorkingDir string `mapstructure:"working_directory"`
+
+ // Instructs the communicator to run the remote script as a Windows
+ // scheduled task, effectively elevating the remote user by impersonating
+ // a logged-in user
+ ElevatedUser string `mapstructure:"elevated_user"`
+ ElevatedPassword string `mapstructure:"elevated_password"`
}
type guestOSTypeConfig struct {
@@ -117,6 +124,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
type Provisioner struct {
config Config
+ communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
@@ -135,7 +143,17 @@ type ExecuteTemplate struct {
WorkingDir 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,
@@ -240,6 +258,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Puppet...")
+ p.communicator = comm
ui.Message("Creating Puppet staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err)
@@ -316,6 +335,13 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return err
}
+ if p.config.ElevatedUser != "" {
+ command, err = provisioner.GenerateElevatedRunner(command, p)
+ if err != nil {
+ return err
+ }
+ }
+
cmd := &packer.RemoteCmd{
Command: command,
}
@@ -432,10 +458,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
- cmd := &packer.RemoteCmd{
- Command: fmt.Sprintf("rm -fr '%s'", dir),
- }
-
+ cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
@@ -460,3 +483,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
+
+func getWinRMPassword(buildName string) string {
+ winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
+ packer.LogSecretFilter.Set(winRMPass)
+ return winRMPass
+}
+
+func (p *Provisioner) Communicator() packer.Communicator {
+ return p.communicator
+}
+
+func (p *Provisioner) ElevatedUser() string {
+ return p.config.ElevatedUser
+}
+
+func (p *Provisioner) ElevatedPassword() string {
+ // Replace ElevatedPassword for winrm users who used this feature
+ p.config.ctx.Data = &EnvVarsTemplate{
+ WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
+ }
+
+ elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
+
+ return elevatedPassword
+}
diff --git a/provisioner/puppet-server/provisioner.go b/provisioner/puppet-server/provisioner.go
index b7ec34ea7..686bdf847 100644
--- a/provisioner/puppet-server/provisioner.go
+++ b/provisioner/puppet-server/provisioner.go
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/hashicorp/packer/common"
+ commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
@@ -63,6 +64,12 @@ type Config struct {
// The directory from which the command will be executed.
// Packer requires the directory to exist when running puppet.
WorkingDir string `mapstructure:"working_directory"`
+
+ // Instructs the communicator to run the remote script as a Windows
+ // scheduled task, effectively elevating the remote user by impersonating
+ // a logged-in user
+ ElevatedUser string `mapstructure:"elevated_user"`
+ ElevatedPassword string `mapstructure:"elevated_password"`
}
type guestOSTypeConfig struct {
@@ -112,6 +119,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
type Provisioner struct {
config Config
+ communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
@@ -129,7 +137,17 @@ type ExecuteTemplate struct {
WorkingDir 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,
@@ -210,6 +228,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Puppet...")
+ p.communicator = comm
ui.Message("Creating Puppet staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err)
@@ -269,6 +288,13 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return err
}
+ if p.config.ElevatedUser != "" {
+ command, err = provisioner.GenerateElevatedRunner(command, p)
+ if err != nil {
+ return err
+ }
+ }
+
cmd := &packer.RemoteCmd{
Command: command,
}
@@ -321,10 +347,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
- cmd := &packer.RemoteCmd{
- Command: fmt.Sprintf("rm -fr '%s'", dir),
- }
-
+ cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
@@ -349,3 +372,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
+
+func getWinRMPassword(buildName string) string {
+ winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName)
+ packer.LogSecretFilter.Set(winRMPass)
+ return winRMPass
+}
+
+func (p *Provisioner) Communicator() packer.Communicator {
+ return p.communicator
+}
+
+func (p *Provisioner) ElevatedUser() string {
+ return p.config.ElevatedUser
+}
+
+func (p *Provisioner) ElevatedPassword() string {
+ // Replace ElevatedPassword for winrm users who used this feature
+ p.config.ctx.Data = &EnvVarsTemplate{
+ WinRMPassword: getWinRMPassword(p.config.PackerBuildName),
+ }
+
+ elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx)
+
+ return elevatedPassword
+}
diff --git a/website/source/docs/provisioners/chef-client.html.md b/website/source/docs/provisioners/chef-client.html.md
index 5c8eb086d..c0ae990b9 100644
--- a/website/source/docs/provisioners/chef-client.html.md
+++ b/website/source/docs/provisioners/chef-client.html.md
@@ -51,6 +51,11 @@ configuration is actually required.
should use a custom configuration template. See the dedicated "Chef
Configuration" section below for more details.
+- `elevated_user` and `elevated_password` (string) - If specified, Chef will
+ be run with elevated privileges using the given Windows user. See the
+ [powershell](/docs/provisionders/powershell.html) provisioner for the full
+ details.
+
- `encrypted_data_bag_secret_path` (string) - The path to the file containing
the secret for encrypted data bags. By default, this is empty, so no secret
will be available.
diff --git a/website/source/docs/provisioners/puppet-masterless.html.md b/website/source/docs/provisioners/puppet-masterless.html.md
index 1c9ef5307..e28a10e7d 100644
--- a/website/source/docs/provisioners/puppet-masterless.html.md
+++ b/website/source/docs/provisioners/puppet-masterless.html.md
@@ -115,6 +115,11 @@ manifests you should use `manifest_file` instead.
be run. If using Hiera files with relative paths, this option can be
helpful. (default: `staging_directory`)
+- `elevated_user` and `elevated_password` (string) - If specified, Puppet
+ will be run with elevated privileges using the given Windows user. See the
+ [powershell](/docs/provisioners/powershell.html) provisioner for the full
+ details.
+
## Execute Command
By default, Packer uses the following command (broken across multiple lines for
diff --git a/website/source/docs/provisioners/puppet-server.html.md b/website/source/docs/provisioners/puppet-server.html.md
index 1f40ec098..71bb54a9c 100644
--- a/website/source/docs/provisioners/puppet-server.html.md
+++ b/website/source/docs/provisioners/puppet-server.html.md
@@ -101,6 +101,11 @@ listed below:
be run. If using Hiera files with relative paths, this option can be
helpful. (default: `staging_directory`)
+- `elevated_user` and `elevated_password` (string) - If specified, Puppet
+ will be run with elevated privileges using the given Windows user. See the
+ [powershell](/docs/provisioners/powershell.html) provisioner for the full
+ details.
+
## Execute Command
By default, Packer uses the following command (broken across multiple lines for