Merge pull request #7078 from aspectcapital/issue-5478

Elevated support for puppet-* & chef-client provisioners
This commit is contained in:
Megan Marsh 2018-12-14 19:06:46 -08:00 committed by GitHub
commit 70c6fcb824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 420 additions and 205 deletions

View File

@ -34,3 +34,15 @@ func (t *MockProvisioner) Provision(ui Ui, comm Communicator) error {
func (t *MockProvisioner) Cancel() { func (t *MockProvisioner) Cancel() {
t.CancelCalled = true 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"
}

View File

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/common/uuid"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner" "github.com/hashicorp/packer/provisioner"
@ -50,6 +51,8 @@ type Config struct {
ChefEnvironment string `mapstructure:"chef_environment"` ChefEnvironment string `mapstructure:"chef_environment"`
ClientKey string `mapstructure:"client_key"` ClientKey string `mapstructure:"client_key"`
ConfigTemplate string `mapstructure:"config_template"` ConfigTemplate string `mapstructure:"config_template"`
ElevatedUser string `mapstructure:"elevated_user"`
ElevatedPassword string `mapstructure:"elevated_password"`
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"` EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
ExecuteCommand string `mapstructure:"execute_command"` ExecuteCommand string `mapstructure:"execute_command"`
GuestOSType string `mapstructure:"guest_os_type"` GuestOSType string `mapstructure:"guest_os_type"`
@ -76,6 +79,7 @@ type Config struct {
type Provisioner struct { type Provisioner struct {
config Config config Config
communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands guestCommands *provisioner.GuestCommands
} }
@ -94,6 +98,10 @@ type ConfigTemplate struct {
ValidationKeyPath string ValidationKeyPath string
} }
type EnvVarsTemplate struct {
WinRMPassword string
}
type ExecuteTemplate struct { type ExecuteTemplate struct {
ConfigPath string ConfigPath string
JsonPath string JsonPath string
@ -111,6 +119,12 @@ type KnifeTemplate struct {
} }
func (p *Provisioner) Prepare(raws ...interface{}) error { 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{ err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true, Interpolate: true,
InterpolateContext: &p.config.ctx, 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 { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
p.communicator = comm
nodeName := p.config.NodeName nodeName := p.config.NodeName
if nodeName == "" { if nodeName == "" {
nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
@ -551,6 +567,13 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
return err 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)) ui.Message(fmt.Sprintf("Executing Chef: %s", command))
cmd := &packer.RemoteCmd{ cmd := &packer.RemoteCmd{
@ -676,6 +699,31 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
return result, nil 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 = ` var DefaultConfigTemplate = `
log_level :info log_level :info
log_location STDOUT log_location STDOUT

192
provisioner/elevated.go Normal file
View File

@ -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]@'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>{{.TaskDescription}}</Description>
</RegistrationInfo>
<Principals>
<Principal id="Author">
<UserId>{{.User}}</UserId>
<LogonType>Password</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
<Priority>4</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>cmd</Command>
<Arguments>/c {{.XMLEscapedCommand}}</Arguments>
</Exec>
</Actions>
</Task>
'@
$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
}

View File

@ -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)
}
}

View File

@ -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]@'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>{{.TaskDescription}}</Description>
</RegistrationInfo>
<Principals>
<Principal id="Author">
<UserId>{{.User}}</UserId>
<LogonType>Password</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
<Priority>4</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>cmd</Command>
<Arguments>/c {{.XMLEscapedCommand}}</Arguments>
</Exec>
</Actions>
</Task>
'@
$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`))

View File

@ -4,8 +4,6 @@ package powershell
import ( import (
"bufio" "bufio"
"bytes"
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -20,6 +18,7 @@ import (
commonhelper "github.com/hashicorp/packer/helper/common" commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate" "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) return "", fmt.Errorf("Error processing command: %s", err)
} }
// OK so we need an elevated shell runner to wrap our command, this is command, err = provisioner.GenerateElevatedRunner(command, p)
// going to have its own path generate the script and update the command
// runner in the process
path, err := p.generateElevatedRunner(command)
if err != nil { if err != nil {
return "", fmt.Errorf("Error generating elevated runner: %s", err) 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 return command, err
} }
func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { func (p *Provisioner) Communicator() packer.Communicator {
log.Printf("Building elevated command wrapper for: %s", command) 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 func (p *Provisioner) ElevatedPassword() string {
// 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)
}
// Replace ElevatedPassword for winrm users who used this feature // Replace ElevatedPassword for winrm users who used this feature
p.config.ctx.Data = &EnvVarsTemplate{ p.config.ctx.Data = &EnvVarsTemplate{
WinRMPassword: getWinRMPassword(p.config.PackerBuildName), 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 return elevatedPassword
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
} }

View File

@ -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) { func TestRetryable(t *testing.T) {
config := testConfig() config := testConfig()

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner" "github.com/hashicorp/packer/provisioner"
@ -65,6 +66,12 @@ type Config struct {
// The directory from which the command will be executed. // The directory from which the command will be executed.
// Packer requires the directory to exist when running puppet. // Packer requires the directory to exist when running puppet.
WorkingDir string `mapstructure:"working_directory"` 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 { type guestOSTypeConfig struct {
@ -117,6 +124,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
type Provisioner struct { type Provisioner struct {
config Config config Config
communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands guestCommands *provisioner.GuestCommands
} }
@ -135,7 +143,17 @@ type ExecuteTemplate struct {
WorkingDir string WorkingDir string
} }
type EnvVarsTemplate struct {
WinRMPassword string
}
func (p *Provisioner) Prepare(raws ...interface{}) error { 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{ err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true, Interpolate: true,
InterpolateContext: &p.config.ctx, 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 { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Puppet...") ui.Say("Provisioning with Puppet...")
p.communicator = comm
ui.Message("Creating Puppet staging directory...") ui.Message("Creating Puppet staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err) 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 return err
} }
if p.config.ElevatedUser != "" {
command, err = provisioner.GenerateElevatedRunner(command, p)
if err != nil {
return err
}
}
cmd := &packer.RemoteCmd{ cmd := &packer.RemoteCmd{
Command: command, 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 { func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{ cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
Command: fmt.Sprintf("rm -fr '%s'", dir),
}
if err := cmd.StartWithUi(comm, ui); err != nil { if err := cmd.StartWithUi(comm, ui); err != nil {
return err return err
} }
@ -460,3 +483,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil) 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
}

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner" "github.com/hashicorp/packer/provisioner"
@ -63,6 +64,12 @@ type Config struct {
// The directory from which the command will be executed. // The directory from which the command will be executed.
// Packer requires the directory to exist when running puppet. // Packer requires the directory to exist when running puppet.
WorkingDir string `mapstructure:"working_directory"` 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 { type guestOSTypeConfig struct {
@ -112,6 +119,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
type Provisioner struct { type Provisioner struct {
config Config config Config
communicator packer.Communicator
guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands guestCommands *provisioner.GuestCommands
} }
@ -129,7 +137,17 @@ type ExecuteTemplate struct {
WorkingDir string WorkingDir string
} }
type EnvVarsTemplate struct {
WinRMPassword string
}
func (p *Provisioner) Prepare(raws ...interface{}) error { 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{ err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true, Interpolate: true,
InterpolateContext: &p.config.ctx, 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 { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Puppet...") ui.Say("Provisioning with Puppet...")
p.communicator = comm
ui.Message("Creating Puppet staging directory...") ui.Message("Creating Puppet staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err) 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 return err
} }
if p.config.ElevatedUser != "" {
command, err = provisioner.GenerateElevatedRunner(command, p)
if err != nil {
return err
}
}
cmd := &packer.RemoteCmd{ cmd := &packer.RemoteCmd{
Command: command, 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 { func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{ cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
Command: fmt.Sprintf("rm -fr '%s'", dir),
}
if err := cmd.StartWithUi(comm, ui); err != nil { if err := cmd.StartWithUi(comm, ui); err != nil {
return err return err
} }
@ -349,3 +372,28 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil) 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
}

View File

@ -51,6 +51,11 @@ configuration is actually required.
should use a custom configuration template. See the dedicated "Chef should use a custom configuration template. See the dedicated "Chef
Configuration" section below for more details. 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 - `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 the secret for encrypted data bags. By default, this is empty, so no secret
will be available. will be available.

View File

@ -115,6 +115,11 @@ manifests you should use `manifest_file` instead.
be run. If using Hiera files with relative paths, this option can be be run. If using Hiera files with relative paths, this option can be
helpful. (default: `staging_directory`) 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 ## Execute Command
By default, Packer uses the following command (broken across multiple lines for By default, Packer uses the following command (broken across multiple lines for

View File

@ -101,6 +101,11 @@ listed below:
be run. If using Hiera files with relative paths, this option can be be run. If using Hiera files with relative paths, this option can be
helpful. (default: `staging_directory`) 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 ## Execute Command
By default, Packer uses the following command (broken across multiple lines for By default, Packer uses the following command (broken across multiple lines for