Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ _testmain.go
*.cov
bin
pkg

#IntelliJ Go Project Files
.idea
*.iml
57 changes: 40 additions & 17 deletions builder/amazon-windows/common/run_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"

"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
)

Expand All @@ -13,19 +14,24 @@ import (
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
ConfigureSecureWinRM bool `mapstructure:"winrm_autoconfigure"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
KeyPairPrivateKeyFile string `mapstructure:"key_pair_private_key_file"`
NewAdministratorPassword string `mapstructure:"new_administrator_password"`
RunTags map[string]string `mapstructure:"run_tags"`
SourceAmi string `mapstructure:"source_ami"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
WinRMPrivateIp bool `mapstructure:"winrm_private_ip"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"`
WinRMPrivateIp bool `mapstructure:"winrm_private_ip"`
WinRMCertificateFile string `mapstructure:"winrm_certificate_file"`
}

func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
Expand All @@ -38,17 +44,19 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}

templates := map[string]*string{
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"spot_price": &c.SpotPrice,
"spot_price_auto_product": &c.SpotPriceAutoProduct,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"vpc_id": &c.VpcId,
"availability_zone": &c.AvailabilityZone,
"user_data": &c.UserData,
"user_data_file": &c.UserDataFile,
"security_group_id": &c.SecurityGroupId,
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"key_pair_private_key_file": &c.KeyPairPrivateKeyFile,
"spot_price": &c.SpotPrice,
"spot_price_auto_product": &c.SpotPriceAutoProduct,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"temporary_key_pair_name": &c.TemporaryKeyPairName,
"vpc_id": &c.VpcId,
"availability_zone": &c.AvailabilityZone,
"user_data": &c.UserData,
"user_data_file": &c.UserDataFile,
"security_group_id": &c.SecurityGroupId,
}

errs := make([]error, 0)
Expand All @@ -70,18 +78,33 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
errs = append(errs, errors.New("An instance_type must be specified"))
}

if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf(
"packer %s", uuid.TimeOrderedUUID())
}

if c.SpotPrice == "auto" {
if c.SpotPriceAutoProduct == "" {
errs = append(errs, errors.New(
"spot_price_auto_product must be specified when spot_price is auto"))
}
}

if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
if c.ConfigureSecureWinRM {
if c.UserData != "" || c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("winrm_autoconfigure cannot be used in conjunction with user_data or user_data_file."))
}

if c.WinRMCertificateFile == "" {
errs = append(errs, fmt.Errorf("winrm_certificate_file must be set to the path of a PFX container holding the certificate to be used for WinRM."))
}
} else {
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ func WinRMAddress(e *ec2.EC2, port uint, private bool) func(multistep.StateBag)
}

// Creates a WinRM connect step for an EC2 instance
func NewConnectStep(ec2 *ec2.EC2, private bool, winrmConfig wincommon.WinRMConfig) multistep.Step {
func NewConnectStep(ec2 *ec2.EC2, private bool, winrmConfig *wincommon.WinRMConfig) multistep.Step {
return &wincommon.StepConnectWinRM{
WinRMAddress: WinRMAddress(ec2, winrmConfig.WinRMPort, private),
WinRMUser: winrmConfig.WinRMUser,
WinRMPassword: winrmConfig.WinRMPassword,
WinRMConfig: winrmConfig,
WinRMWaitTimeout: winrmConfig.WinRMWaitTimeout,
}
}
154 changes: 154 additions & 0 deletions builder/amazon-windows/common/step_generate_secure_vm_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package common

import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"text/template"

"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"

wincommon "github.com/packer-community/packer-windows-plugins/common"
)

type StepGenerateSecureWinRMUserData struct {
WinRMConfig *wincommon.WinRMConfig
WinRMCertificateFile string
RunConfig *RunConfig
}

func (s *StepGenerateSecureWinRMUserData) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)

if !s.RunConfig.ConfigureSecureWinRM {
return multistep.ActionContinue
}

ui.Say("Generating user data for configuring WinRM over TLS...")

certBytes, err := ioutil.ReadFile(s.WinRMCertificateFile)
if err != nil {
ui.Error(fmt.Sprintf("Error reading WinRM certificate file: %s", err))
return multistep.ActionHalt
}

encodedCert := base64.StdEncoding.EncodeToString(certBytes)

var adminPasswordBuffer bytes.Buffer
if s.RunConfig.NewAdministratorPassword != "" {
ui.Say("Configuring user data to change Administrator password...")
err = changeAdministratorPasswordTemplate.Execute(&adminPasswordBuffer, changeAdministratorPasswordOptions{
NewAdministratorPassword: s.RunConfig.NewAdministratorPassword,
})
if err != nil {
ui.Error(fmt.Sprintf("Error executing Change Administrator Password template: %s", err))
return multistep.ActionHalt
}
}

var buffer bytes.Buffer
err = configureSecureWinRMTemplate.Execute(&buffer, configureSecureWinRMOptions{
CertificatePfxBase64Encoded: encodedCert,
InstallListenerCommand: installListenerCommand,
AllowBasicCommand: allowBasicCommand,
AllowUnencryptedCommand: allowUnencryptedCommand,
AllowCredSSPCommand: allowCredSSPCommand,
MaxMemoryPerShellCommand: maxMemoryPerShellCommand,
MaxTimeoutMsCommand: maxTimeoutMsCommand,
ChangeAdministratorPasswordCommand: adminPasswordBuffer.String(),
})
if err != nil {
ui.Error(fmt.Sprintf("Error executing Secure WinRM User Data template: %s", err))
return multistep.ActionHalt
}

s.RunConfig.UserData = buffer.String()
return multistep.ActionContinue
}

func (s *StepGenerateSecureWinRMUserData) Cleanup(multistep.StateBag) {
// No cleanup...
}

type changeAdministratorPasswordOptions struct {
NewAdministratorPassword string
}

var changeAdministratorPasswordTemplate = template.Must(template.New("ChangeAdministratorPassword").Parse(`$user = [adsi]"WinNT://localhost/Administrator,user"
$user.SetPassword("{{.NewAdministratorPassword}}")
$user.SetInfo()`))

type configureSecureWinRMOptions struct {
CertificatePfxBase64Encoded string
InstallListenerCommand string
AllowBasicCommand string
AllowUnencryptedCommand string
AllowCredSSPCommand string
MaxMemoryPerShellCommand string
MaxTimeoutMsCommand string
ChangeAdministratorPasswordCommand string
}

//This is needed to because Powershell uses ` for escapes and there's no straightforward way of constructing
// the necessary escaping in the hash otherwise.
const (
installListenerCommand = "Start-Process -FilePath winrm -ArgumentList \"create winrm/config/listener?Address=*+Transport=HTTPS @{Hostname=`\"$certSubjectName`\";CertificateThumbprint=`\"$certThumbprint`\";Port=`\"5986`\"}\" -NoNewWindow -Wait"

allowBasicCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service/auth @{Basic=`\"true`\"}\" -NoNewWindow -Wait"
allowUnencryptedCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service @{AllowUnencrypted=`\"false`\"}\" -NoNewWindow -Wait"
allowCredSSPCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/service/auth @{CredSSP=`\"true`\"}\" -NoNewWindow -Wait"
maxMemoryPerShellCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config/winrs @{MaxMemoryPerShellMB=`\"1024`\"}\" -NoNewWindow -Wait"
maxTimeoutMsCommand = "Start-Process -FilePath winrm -ArgumentList \"set winrm/config @{MaxTimeoutms=`\"1800000`\"}\" -NoNewWindow -Wait"
)

var configureSecureWinRMTemplate = template.Must(template.New("ConfigureSecureWinRM").Parse(`<powershell>
Write-Host "Disabling WinRM over HTTP..."
Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP"
Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC"

Start-Process -FilePath winrm -ArgumentList "delete winrm/config/listener?Address=*+Transport=HTTP" -NoNewWindow -Wait

Write-Host "Configuring WinRM for HTTPS..."

{{.MaxTimeoutMsCommand}}

{{.MaxMemoryPerShellCommand}}

{{.AllowUnencryptedCommand}}

{{.AllowBasicCommand}}

{{.AllowCredSSPCommand}}

New-NetFirewallRule -Name "WINRM-HTTPS-In-TCP" -DisplayName "Windows Remote Management (HTTPS-In)" -Description "Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]" -Group "Windows Remote Management" -Program "System" -Protocol TCP -LocalPort "5986" -Action Allow -Profile Domain,Private

New-NetFirewallRule -Name "WINRM-HTTPS-In-TCP-PUBLIC" -DisplayName "Windows Remote Management (HTTPS-In)" -Description "Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]" -Group "Windows Remote Management" -Program "System" -Protocol TCP -LocalPort "5986" -Action Allow -Profile Public

$certContent = "{{.CertificatePfxBase64Encoded}}"

$certBytes = [System.Convert]::FromBase64String($certContent)
$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($certBytes, "", "Exportable,PersistKeySet,MachineKeySet")
$certThumbprint = $pfx.Thumbprint
$certSubjectName = $pfx.SubjectName.Name.TrimStart("CN = ").Trim()

$store = new-object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine")
try {
$store.Open("ReadWrite,MaxAllowed")
$store.Add($pfx)

} finally {
$store.Close()
}

{{.InstallListenerCommand}}

{{.ChangeAdministratorPasswordCommand}}

Write-Host "Restarting WinRM Service..."
Stop-Service winrm
Set-Service winrm -StartupType "Automatic"
Start-Service winrm
</powershell>`))
Loading