Support using WinRM over an IAP tunnel
This avoids the need to expose WinRM ports on the internet and allows using instances with only an internal private IP address. When using a WinRM tunnel there is a race condition between the tunnel connection attempt timing out and packer assuming the connection was successful. To allow for this, when using WinRM the default success timeout is increased to 40 seconds.
This commit is contained in:
parent
8964367eb5
commit
37544f4d5f
@ -426,18 +426,28 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||
}
|
||||
}
|
||||
if c.IAPConfig.IAPTunnelLaunchWait == 0 {
|
||||
c.IAPConfig.IAPTunnelLaunchWait = 30
|
||||
if c.Comm.Type == "winrm" {
|
||||
// when starting up, WinRM can cause the tunnel to take 30 seconds
|
||||
// before timing out
|
||||
c.IAPConfig.IAPTunnelLaunchWait = 40
|
||||
} else {
|
||||
c.IAPConfig.IAPTunnelLaunchWait = 30
|
||||
}
|
||||
}
|
||||
|
||||
// Configure IAP: Update SSH config to use localhost proxy instead
|
||||
if c.IAPConfig.IAP {
|
||||
if c.Comm.Type == "ssh" {
|
||||
c.Comm.SSHHost = "localhost"
|
||||
} else {
|
||||
err := fmt.Errorf("Error: IAP tunnel currently only implemnted for" +
|
||||
" SSH communicator")
|
||||
if !SupportsIAPTunnel(&c.Comm) {
|
||||
err := fmt.Errorf("Error: IAP tunnel is not implemented for %s communicator", c.Comm.Type)
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
// These configuration values are copied early to the generic host parameter when configuring
|
||||
// StepConnect. As such they must be set now. Ideally we would handle this as part of
|
||||
// ApplyIAPTunnel and set them during StepStartTunnel but that means defering when the
|
||||
// CommHost function reads the value from the configuration, perhaps pass in b.config.Comm
|
||||
// instead of b.config.Comm.Host()?
|
||||
c.Comm.SSHHost = "localhost"
|
||||
c.Comm.WinRMHost = "localhost"
|
||||
}
|
||||
|
||||
// Process required parameters.
|
||||
@ -541,3 +551,25 @@ func (k *CustomerEncryptionKey) ComputeType() *compute.CustomerEncryptionKey {
|
||||
RawKey: k.RawKey,
|
||||
}
|
||||
}
|
||||
|
||||
func SupportsIAPTunnel(c *communicator.Config) bool {
|
||||
switch c.Type {
|
||||
case "ssh", "winrm":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyIAPTunnel(c *communicator.Config, port int) error {
|
||||
switch c.Type {
|
||||
case "ssh":
|
||||
c.SSHPort = port
|
||||
return nil
|
||||
case "winrm":
|
||||
c.WinRMPort = port
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("IAP tunnel is not implemented for %s communicator", c.Type)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func TestConfigPrepare(t *testing.T) {
|
||||
@ -414,9 +416,6 @@ func TestConfigPrepareIAP(t *testing.T) {
|
||||
t.Fatalf("IAP hashbang didn't default correctly to /bin/sh.")
|
||||
}
|
||||
}
|
||||
if c.Comm.SSHHost != "localhost" {
|
||||
t.Fatalf("Didn't correctly override the ssh host.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepareIAP_failures(t *testing.T) {
|
||||
@ -425,7 +424,7 @@ func TestConfigPrepareIAP_failures(t *testing.T) {
|
||||
"source_image": "foo",
|
||||
"winrm_username": "packer",
|
||||
"zone": "us-central1-a",
|
||||
"communicator": "winrm",
|
||||
"communicator": "none",
|
||||
"iap_hashbang": "/bin/bash",
|
||||
"iap_ext": ".ps1",
|
||||
"use_iap": true,
|
||||
@ -434,7 +433,7 @@ func TestConfigPrepareIAP_failures(t *testing.T) {
|
||||
var c Config
|
||||
_, errs := c.Prepare(config)
|
||||
if errs == nil {
|
||||
t.Fatalf("Should have errored because we're using winrm.")
|
||||
t.Fatalf("Should have errored because we're using none.")
|
||||
}
|
||||
if c.IAPHashBang != "/bin/bash" {
|
||||
t.Fatalf("IAP hashbang defaulted even though set.")
|
||||
@ -500,6 +499,59 @@ func TestRegion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_SSH(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "ssh",
|
||||
SSH: communicator.SSH{
|
||||
SSHHost: "example",
|
||||
SSHPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.SSHHost != "localhost" {
|
||||
t.Fatalf("Should have set SSHHost")
|
||||
}
|
||||
if c.SSHPort != 8447 {
|
||||
t.Fatalf("Should have set SSHPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_WinRM(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "winrm",
|
||||
WinRM: communicator.WinRM{
|
||||
WinRMHost: "example",
|
||||
WinRMPort: 1234,
|
||||
},
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have errors")
|
||||
}
|
||||
if c.WinRMHost != "localhost" {
|
||||
t.Fatalf("Should have set WinRMHost")
|
||||
}
|
||||
if c.WinRMPort != 8447 {
|
||||
t.Fatalf("Should have set WinRMPort")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIAPTunnel_none(t *testing.T) {
|
||||
c := &communicator.Config{
|
||||
Type: "none",
|
||||
}
|
||||
|
||||
err := ApplyIAPTunnel(c, 8447)
|
||||
if err == nil {
|
||||
t.Fatalf("Should have errors, none is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stuff below
|
||||
|
||||
func testConfig(t *testing.T) (config map[string]interface{}, tempAccountFile string) {
|
||||
|
@ -34,8 +34,6 @@ type IAPConfig struct {
|
||||
// - You must have the gcloud sdk installed on the computer running Packer.
|
||||
// - You must be using a Service Account with a credentials file (using the
|
||||
// account_file option in the Packer template)
|
||||
// - This is currently only implemented for the SSH communicator, not the
|
||||
// WinRM Communicator.
|
||||
// - You must add the given service account to project level IAP permissions
|
||||
// in https://console.cloud.google.com/security/iap. To do so, click
|
||||
// "project" > "SSH and TCP resoures" > "All Tunnel Resources" >
|
||||
@ -52,7 +50,7 @@ type IAPConfig struct {
|
||||
// Default: ".sh"
|
||||
IAPExt string `mapstructure:"iap_ext" required:"false"`
|
||||
// How long to wait, in seconds, before assuming a tunnel launch was
|
||||
// successful. Defaults to 30 seconds.
|
||||
// successful. Defaults to 30 seconds for SSH or 40 seconds for WinRM.
|
||||
IAPTunnelLaunchWait int `mapstructure:"iap_tunnel_launch_wait" required:"false"`
|
||||
}
|
||||
|
||||
@ -283,7 +281,14 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
|
||||
// This is the port the IAP tunnel listens on, on localhost.
|
||||
// TODO make setting LocalHostPort optional
|
||||
s.CommConf.SSHPort = s.IAPConf.IAPLocalhostPort
|
||||
err = ApplyIAPTunnel(s.CommConf, s.IAPConf.IAPLocalhostPort)
|
||||
if err != nil {
|
||||
// this should not occur as the config should validate that the communicator
|
||||
// supports using an IAP tunnel
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Creating tunnel launch script with args %#v", args)
|
||||
// Create temp file that contains both gcloud authentication, and gcloud
|
||||
|
Loading…
x
Reference in New Issue
Block a user