mirror of https://github.com/hashicorp/packer
Merge pull request #9105 from hashicorp/google_iap
Implement iap proxy for googlecomputepull/9197/head
commit
9476aa03de
@ -0,0 +1,331 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type IAPConfig
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common/net"
|
||||
"github.com/hashicorp/packer/common/retry"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/packer/tmp"
|
||||
)
|
||||
|
||||
// StepStartTunnel represents a Packer build step that launches an IAP tunnel
|
||||
type IAPConfig struct {
|
||||
// Whether to use an IAP proxy.
|
||||
// Prerequisites and limitations for using IAP:
|
||||
// - You must manually enable the IAP API in the Google Cloud console.
|
||||
// - 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" >
|
||||
// "Add Member". Then add your service account and choose the role
|
||||
// "IAP-secured Tunnel User" and add any conditions you may care about.
|
||||
IAP bool `mapstructure:"use_iap" required:"false"`
|
||||
// Which port to connect the local end of the IAM localhost proxy to. If
|
||||
// left blank, Packer will choose a port for you from available ports.
|
||||
IAPLocalhostPort int `mapstructure:"iap_localhost_port"`
|
||||
// What "hashbang" to use to invoke script that sets up gcloud.
|
||||
// Default: "/bin/sh"
|
||||
IAPHashBang string `mapstructure:"iap_hashbang" required:"false"`
|
||||
// What file extension to use for script that sets up gcloud.
|
||||
// Default: ".sh"
|
||||
IAPExt string `mapstructure:"iap_ext" required:"false"`
|
||||
}
|
||||
|
||||
type TunnelDriver interface {
|
||||
StartTunnel(context.Context, string) error
|
||||
StopTunnel()
|
||||
}
|
||||
|
||||
func RunTunnelCommand(cmd *exec.Cmd) error {
|
||||
// set stdout and stderr so we can read what's going on.
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s",
|
||||
err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Give tunnel 30 seconds to either launch, or return an error.
|
||||
// Unfortunately, the SDK doesn't provide any official acknowledgment that
|
||||
// the tunnel is launched when it's not being run through a TTY so we
|
||||
// are just trusting here that 30s is enough to know whether the tunnel
|
||||
// launch was going to fail. Yep, feels icky to me too. But I spent an
|
||||
// afternoon trying to figure out how to get the SDK to actually send
|
||||
// the "Listening on port [n]" line I see when I run it manually, and I
|
||||
// can't justify spending more time than that on aesthetics.
|
||||
for i := 0; i < 30; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
lineStderr, err := stderr.ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
log.Printf("Err from scanning stderr is %s", err)
|
||||
return fmt.Errorf("Error reading stderr from tunnel launch: %s", err)
|
||||
}
|
||||
if lineStderr != "" {
|
||||
log.Printf("stderr: %s", lineStderr)
|
||||
}
|
||||
|
||||
lineStdout, err := stdout.ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
log.Printf("Err from scanning stdout is %s", err)
|
||||
return fmt.Errorf("Error reading stdout from tunnel launch: %s", err)
|
||||
}
|
||||
if lineStdout != "" {
|
||||
log.Printf("stdout: %s", lineStdout)
|
||||
}
|
||||
|
||||
if strings.Contains(lineStderr, "ERROR") {
|
||||
// 4033: Either you don't have permission to access the instance,
|
||||
// the instance doesn't exist, or the instance is stopped.
|
||||
// The two sub-errors we may see while the permissions settle are
|
||||
// "not authorized" and "failed to connect to backend," but after
|
||||
// about a minute of retries this goes away and we're able to
|
||||
// connect.
|
||||
// 4003: "failed to connect to backend". Network blip.
|
||||
if strings.Contains(lineStderr, "4033") || strings.Contains(lineStderr, "4003") {
|
||||
return RetryableTunnelError{lineStderr}
|
||||
} else {
|
||||
log.Printf("NOT RETRYABLE: %s", lineStderr)
|
||||
return fmt.Errorf("Non-retryable tunnel error: %s", lineStderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("No error detected after tunnel launch; continuing...")
|
||||
return nil
|
||||
}
|
||||
|
||||
type RetryableTunnelError struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e RetryableTunnelError) Error() string {
|
||||
return "Tunnel start: " + e.s
|
||||
}
|
||||
|
||||
type StepStartTunnel struct {
|
||||
IAPConf *IAPConfig
|
||||
CommConf *communicator.Config
|
||||
AccountFile string
|
||||
ProjectId string
|
||||
|
||||
tunnelDriver TunnelDriver
|
||||
}
|
||||
|
||||
func (s *StepStartTunnel) ConfigureLocalHostPort(ctx context.Context) error {
|
||||
minPortNumber, maxPortNumber := 8000, 9000
|
||||
|
||||
if s.IAPConf.IAPLocalhostPort != 0 {
|
||||
minPortNumber = s.IAPConf.IAPLocalhostPort
|
||||
maxPortNumber = minPortNumber
|
||||
log.Printf("Using TCP port for %d IAP proxy", s.IAPConf.IAPLocalhostPort)
|
||||
} else {
|
||||
log.Printf("Finding an available TCP port for IAP proxy")
|
||||
}
|
||||
|
||||
l, err := net.ListenRangeConfig{
|
||||
Min: minPortNumber,
|
||||
Max: maxPortNumber,
|
||||
Addr: "0.0.0.0",
|
||||
Network: "tcp",
|
||||
}.Listen(ctx)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error finding an available port to initiate a session tunnel: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.IAPConf.IAPLocalhostPort = l.Port
|
||||
l.Close()
|
||||
log.Printf("Setting up proxy to listen on localhost at %d",
|
||||
s.IAPConf.IAPLocalhostPort)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StepStartTunnel) createTempGcloudScript(args []string) (string, error) {
|
||||
// Generate temp script that contains both gcloud auth and gcloud compute
|
||||
// iap launch call.
|
||||
|
||||
// Create temp file.
|
||||
tf, err := tmp.File("gcloud-setup")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error preparing gcloud setup script: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
// Write our contents to it
|
||||
writer := bufio.NewWriter(tf)
|
||||
|
||||
if s.IAPConf.IAPHashBang != "" {
|
||||
s.IAPConf.IAPHashBang = fmt.Sprintf("#!%s\n", s.IAPConf.IAPHashBang)
|
||||
log.Printf("[INFO] (google): Prepending inline gcloud setup script with %s",
|
||||
s.IAPConf.IAPHashBang)
|
||||
_, err = writer.WriteString(s.IAPConf.IAPHashBang)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error preparing inline hashbang: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
launchTemplate := `
|
||||
gcloud auth activate-service-account --key-file='{{.AccountFile}}'
|
||||
gcloud config set project {{.ProjectID}}
|
||||
{{.Args}}
|
||||
`
|
||||
if runtime.GOOS == "windows" {
|
||||
launchTemplate = `
|
||||
call gcloud auth activate-service-account --key-file "{{.AccountFile}}"
|
||||
call gcloud config set project {{.ProjectID}}
|
||||
call {{.Args}}
|
||||
`
|
||||
}
|
||||
// call command
|
||||
args = append([]string{"gcloud"}, args...)
|
||||
argString := strings.Join(args, " ")
|
||||
|
||||
var tpl = template.Must(template.New("createTunnel").Parse(launchTemplate))
|
||||
var b bytes.Buffer
|
||||
|
||||
opts := map[string]string{
|
||||
"AccountFile": s.AccountFile,
|
||||
"ProjectID": s.ProjectId,
|
||||
"Args": argString,
|
||||
}
|
||||
|
||||
err = tpl.Execute(&b, opts)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if _, err := writer.WriteString(b.String()); err != nil {
|
||||
return "", fmt.Errorf("Error preparing gcloud shell script: %s", err)
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return "", fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
// Have to close temp file before renaming it or Windows will complain.
|
||||
tf.Close()
|
||||
err = os.Chmod(tf.Name(), 0700)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] (google): error modifying permissions of temp script file: %s", err.Error())
|
||||
}
|
||||
|
||||
// figure out what extension the file should have, and rename it.
|
||||
tempScriptFileName := tf.Name()
|
||||
if s.IAPConf.IAPExt != "" {
|
||||
err := os.Rename(tempScriptFileName, fmt.Sprintf("%s%s", tempScriptFileName, s.IAPConf.IAPExt))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error setting the correct temp file extension: %s", err)
|
||||
}
|
||||
tempScriptFileName = fmt.Sprintf("%s%s", tempScriptFileName, s.IAPConf.IAPExt)
|
||||
}
|
||||
|
||||
return tempScriptFileName, nil
|
||||
}
|
||||
|
||||
// Run executes the Packer build step that creates an IAP tunnel.
|
||||
func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if !s.IAPConf.IAP {
|
||||
log.Printf("Skipping step launch IAP tunnel; \"iap\" is false.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// shell out to create the tunnel.
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instanceName := state.Get("instance_name").(string)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
ui.Say("Step Launch IAP Tunnel...")
|
||||
|
||||
err := s.ConfigureLocalHostPort(ctx)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Generate list of args to use to call gcloud cli.
|
||||
args := []string{"compute", "start-iap-tunnel", instanceName,
|
||||
strconv.Itoa(s.CommConf.Port()),
|
||||
fmt.Sprintf("--local-host-port=localhost:%d", s.IAPConf.IAPLocalhostPort),
|
||||
"--zone", c.Zone,
|
||||
}
|
||||
|
||||
// This is the port the IAP tunnel listens on, on localhost.
|
||||
// TODO make setting LocalHostPort optional
|
||||
s.CommConf.SSHPort = s.IAPConf.IAPLocalhostPort
|
||||
|
||||
log.Printf("Creating tunnel launch script with args %#v", args)
|
||||
// Create temp file that contains both gcloud authentication, and gcloud
|
||||
// proxy setup call.
|
||||
tempScriptFileName, err := s.createTempGcloudScript(args)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer os.Remove(tempScriptFileName)
|
||||
|
||||
s.tunnelDriver = NewTunnelDriver()
|
||||
|
||||
err = retry.Config{
|
||||
Tries: 11,
|
||||
ShouldRetry: func(err error) bool {
|
||||
switch err.(type) {
|
||||
case RetryableTunnelError:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
// tunnel launcher/destroyer has to be different on windows vs. unix.
|
||||
err := s.tunnelDriver.StartTunnel(ctx, tempScriptFileName)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Cleanup stops the IAP tunnel and cleans up processes.
|
||||
func (s *StepStartTunnel) Cleanup(state multistep.StateBag) {
|
||||
if !s.IAPConf.IAP {
|
||||
log.Printf("Skipping cleanup of IAP tunnel; \"iap\" is false.")
|
||||
return
|
||||
}
|
||||
if s.tunnelDriver != nil {
|
||||
s.tunnelDriver.StopTunnel()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
// Code generated by "mapstructure-to-hcl2 -type IAPConfig"; DO NOT EDIT.
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatIAPConfig is an auto-generated flat version of IAPConfig.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatIAPConfig struct {
|
||||
IAP *bool `mapstructure:"use_iap" required:"false" cty:"use_iap"`
|
||||
IAPLocalhostPort *int `mapstructure:"iap_localhost_port" cty:"iap_localhost_port"`
|
||||
IAPHashBang *string `mapstructure:"iap_hashbang" required:"false" cty:"iap_hashbang"`
|
||||
IAPExt *string `mapstructure:"iap_ext" required:"false" cty:"iap_ext"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatIAPConfig.
|
||||
// FlatIAPConfig is an auto-generated flat version of IAPConfig.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*IAPConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatIAPConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a IAPConfig.
|
||||
// This spec is used by HCL to read the fields of IAPConfig.
|
||||
// The decoded values from this spec will then be applied to a FlatIAPConfig.
|
||||
func (*FlatIAPConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"use_iap": &hcldec.AttrSpec{Name: "use_iap", Type: cty.Bool, Required: false},
|
||||
"iap_localhost_port": &hcldec.AttrSpec{Name: "iap_localhost_port", Type: cty.Number, Required: false},
|
||||
"iap_hashbang": &hcldec.AttrSpec{Name: "iap_hashbang", Type: cty.String, Required: false},
|
||||
"iap_ext": &hcldec.AttrSpec{Name: "iap_ext", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
)
|
||||
|
||||
type MockTunnelDriver struct {
|
||||
StopTunnelCalled bool
|
||||
StartTunnelCalled bool
|
||||
}
|
||||
|
||||
func (m *MockTunnelDriver) StopTunnel() {
|
||||
m.StopTunnelCalled = true
|
||||
}
|
||||
|
||||
func (m *MockTunnelDriver) StartTunnel(context.Context, string) error {
|
||||
m.StartTunnelCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTestStepStartTunnel() *StepStartTunnel {
|
||||
return &StepStartTunnel{
|
||||
IAPConf: &IAPConfig{
|
||||
IAP: true,
|
||||
IAPLocalhostPort: 0,
|
||||
IAPHashBang: "/bin/bash",
|
||||
IAPExt: "",
|
||||
},
|
||||
CommConf: &communicator.Config{
|
||||
SSH: communicator.SSH{
|
||||
SSHPort: 1234,
|
||||
},
|
||||
},
|
||||
AccountFile: "/path/to/account_file.json",
|
||||
ProjectId: "fake-project-123",
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_CreateTempScript(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
|
||||
args := []string{"compute", "start-iap-tunnel", "fakeinstance-12345",
|
||||
"1234", "--local-host-port=localhost:8774", "--zone", "us-central-b"}
|
||||
|
||||
scriptPath, err := s.createTempGcloudScript(args)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error building script file.")
|
||||
}
|
||||
defer os.Remove(scriptPath)
|
||||
|
||||
f, err := ioutil.ReadFile(scriptPath)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't read created inventoryfile: %s", err)
|
||||
}
|
||||
|
||||
expected := `#!/bin/bash
|
||||
|
||||
gcloud auth activate-service-account --key-file='/path/to/account_file.json'
|
||||
gcloud config set project fake-project-123
|
||||
gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b
|
||||
`
|
||||
if runtime.GOOS == "windows" {
|
||||
// in real life you'd not be passing a HashBang here, but GIGO.
|
||||
expected = `#!/bin/bash
|
||||
|
||||
call gcloud auth activate-service-account --key-file "/path/to/account_file.json"
|
||||
call gcloud config set project fake-project-123
|
||||
call gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b
|
||||
`
|
||||
}
|
||||
if fmt.Sprintf("%s", f) != expected {
|
||||
t.Fatalf("script didn't match expected:\n\n expected: \n%s\n; recieved: \n%s\n", expected, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_Cleanup(t *testing.T) {
|
||||
// Check IAP true
|
||||
s := getTestStepStartTunnel()
|
||||
td := &MockTunnelDriver{}
|
||||
s.tunnelDriver = td
|
||||
|
||||
state := testState(t)
|
||||
s.Cleanup(state)
|
||||
|
||||
if !td.StopTunnelCalled {
|
||||
t.Fatalf("Should have called StopTunnel, since IAP is true")
|
||||
}
|
||||
|
||||
// Check IAP false
|
||||
s = getTestStepStartTunnel()
|
||||
td = &MockTunnelDriver{}
|
||||
s.tunnelDriver = td
|
||||
|
||||
s.IAPConf.IAP = false
|
||||
|
||||
s.Cleanup(state)
|
||||
|
||||
if td.StopTunnelCalled {
|
||||
t.Fatalf("Should not have called StopTunnel, since IAP is false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_ConfigurePort_port_set_by_user(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
s.IAPConf.IAPLocalhostPort = 8447
|
||||
|
||||
ctx := context.TODO()
|
||||
err := s.ConfigureLocalHostPort(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error detecting port")
|
||||
}
|
||||
if s.IAPConf.IAPLocalhostPort != 8447 {
|
||||
t.Fatalf("Shouldn't have found new port; one was configured.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStartTunnel_ConfigurePort_port_not_set_by_user(t *testing.T) {
|
||||
s := getTestStepStartTunnel()
|
||||
s.IAPConf.IAPLocalhostPort = 0
|
||||
|
||||
ctx := context.TODO()
|
||||
err := s.ConfigureLocalHostPort(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error detecting port")
|
||||
}
|
||||
if s.IAPConf.IAPLocalhostPort == 0 {
|
||||
t.Fatalf("Should have found new port; none was configured.")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
// +build !windows
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func NewTunnelDriver() TunnelDriver {
|
||||
return &TunnelDriverLinux{}
|
||||
}
|
||||
|
||||
type TunnelDriverLinux struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func (t *TunnelDriverLinux) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error {
|
||||
cmd := exec.CommandContext(cancelCtx, tempScriptFileName)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
err := RunTunnelCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store successful command on step so we can access it to cancel it
|
||||
// later.
|
||||
t.cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TunnelDriverLinux) StopTunnel() {
|
||||
if t.cmd != nil && t.cmd.Process != nil {
|
||||
log.Printf("Cleaning up the IAP tunnel...")
|
||||
// Why not just cmd.Process.Kill()? I'm glad you asked. The gcloud
|
||||
// call spawns a python subprocess that listens on the port, and you
|
||||
// need to use the process _group_ id to halt this process and its
|
||||
// daemon child. We create the group ID with the syscall.SysProcAttr
|
||||
// call inside the retry loop above, and then store that ID on the
|
||||
// command so we can halt it here.
|
||||
err := syscall.Kill(-t.cmd.Process.Pid, syscall.SIGINT)
|
||||
if err != nil {
|
||||
log.Printf("Issue stopping IAP tunnel: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Couldn't find IAP tunnel process to kill. Continuing.")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
// +build windows
|
||||
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func NewTunnelDriver() TunnelDriver {
|
||||
return &TunnelDriverWindows{}
|
||||
}
|
||||
|
||||
type TunnelDriverWindows struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func (t *TunnelDriverWindows) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error {
|
||||
args := []string{"/C", "call", tempScriptFileName}
|
||||
cmd := exec.CommandContext(cancelCtx, "cmd", args...)
|
||||
err := RunTunnelCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store successful command on step so we can access it to cancel it
|
||||
// later.
|
||||
t.cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TunnelDriverWindows) StopTunnel() {
|
||||
if t.cmd != nil && t.cmd.Process != nil {
|
||||
err := t.cmd.Process.Kill()
|
||||
if err != nil {
|
||||
log.Printf("Issue stopping IAP tunnel: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Couldn't find IAP tunnel process to kill. Continuing.")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<!-- Code generated from the comments of the IAPConfig struct in builder/googlecompute/step_start_tunnel.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `use_iap` (bool) - Whether to use an IAP proxy.
|
||||
Prerequisites and limitations for using IAP:
|
||||
- You must manually enable the IAP API in the Google Cloud console.
|
||||
- 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" >
|
||||
"Add Member". Then add your service account and choose the role
|
||||
"IAP-secured Tunnel User" and add any conditions you may care about.
|
||||
|
||||
- `iap_localhost_port` (int) - Which port to connect the local end of the IAM localhost proxy to. If
|
||||
left blank, Packer will choose a port for you from available ports.
|
||||
|
||||
- `iap_hashbang` (string) - What "hashbang" to use to invoke script that sets up gcloud.
|
||||
Default: "/bin/sh"
|
||||
|
||||
- `iap_ext` (string) - What file extension to use for script that sets up gcloud.
|
||||
Default: ".sh"
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
<!-- Code generated from the comments of the IAPConfig struct in builder/googlecompute/step_start_tunnel.go; DO NOT EDIT MANUALLY -->
|
||||
StepStartTunnel represents a Packer build step that launches an IAP tunnel
|
||||
Loading…
Reference in new issue