implement iap proxy for googlecompute. ssh-only so far

pull/9105/head
Megan Marsh 6 years ago
parent 26d05abd4f
commit e6073bcec7

@ -9,9 +9,10 @@ import (
"golang.org/x/oauth2/jwt"
)
func ProcessAccountFile(text string) (*jwt.Config, error) {
func ProcessAccountFile(text string, iap bool) (*jwt.Config, error) {
driverScopes := getDriverScopes(iap)
// Assume text is a JSON string
conf, err := google.JWTConfigFromJSON([]byte(text), DriverScopes...)
conf, err := google.JWTConfigFromJSON([]byte(text), driverScopes...)
if err != nil {
// If text was not JSON, assume it is a file path instead
if _, err := os.Stat(text); os.IsNotExist(err) {
@ -25,7 +26,7 @@ func ProcessAccountFile(text string) (*jwt.Config, error) {
"Error reading account_file from path '%s': %s",
text, err)
}
conf, err = google.JWTConfigFromJSON(data, DriverScopes...)
conf, err = google.JWTConfigFromJSON(data, driverScopes...)
if err != nil {
return nil, fmt.Errorf("Error parsing account_file: %s", err)
}

@ -37,7 +37,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
// representing a GCE machine image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine)
ui, b.config.ProjectId, b.config.account, b.config.VaultGCPOauthEngine,
b.config.IAP)
if err != nil {
return nil, err
}
@ -66,6 +67,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepInstanceInfo{
Debug: b.config.PackerDebug,
},
&StepStartTunnel{
IAPConf: &b.config.IAPConfig,
CommConf: &b.config.Comm,
AccountFile: b.config.AccountFile,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: communicator.CommHost(b.config.Comm.Host(), "instance_ip"),

@ -72,6 +72,8 @@ type Config struct {
// state of your VM instances. Note: integrity monitoring relies on having
// vTPM enabled. [Details](https://cloud.google.com/security/shielded-cloud/shielded-vm)
EnableIntegrityMonitoring bool `mapstructure:"enable_integrity_monitoring" required:"false"`
// Whether to use an IAP proxy.
IAPConfig `mapstructure:",squash"`
// The unique name of the resulting image. Defaults to
// `packer-{{timestamp}}`.
ImageName string `mapstructure:"image_name" required:"false"`
@ -320,10 +322,28 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.StateTimeout = 5 * time.Minute
}
// Set up communicator
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
// set defaults for IAP
if c.IAPConfig.IAPHashBang == "" {
c.IAPConfig.IAPHashBang = "/bin/sh"
}
if c.IAPConfig.IAPExt == "" {
c.IAPConfig.IAPExt = ".sh"
}
// Configure IAP: Update SSH config to use localhost proxy instead
if c.Comm.Type == "ssh" {
c.Comm.SSHHost = "localhost"
} else {
err := fmt.Errorf("Error: IAP tunnel currently only implemnted for" +
" SSH communicator")
errs = packer.MultiErrorAppend(errs, err)
}
// Process required parameters.
if c.ProjectId == "" {
errs = packer.MultiErrorAppend(
@ -359,7 +379,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You cannot "+
"specify both account_file and vault_gcp_oauth_engine."))
}
cfg, err := ProcessAccountFile(c.AccountFile)
cfg, err := ProcessAccountFile(c.AccountFile, c.IAP)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}

@ -70,6 +70,10 @@ type FlatConfig struct {
EnableSecureBoot *bool `mapstructure:"enable_secure_boot" required:"false" cty:"enable_secure_boot"`
EnableVtpm *bool `mapstructure:"enable_vtpm" required:"false" cty:"enable_vtpm"`
EnableIntegrityMonitoring *bool `mapstructure:"enable_integrity_monitoring" required:"false" cty:"enable_integrity_monitoring"`
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"`
ImageName *string `mapstructure:"image_name" required:"false" cty:"image_name"`
ImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description"`
ImageEncryptionKey *FlatCustomerEncryptionKey `mapstructure:"image_encryption_key" required:"false" cty:"image_encryption_key"`
@ -175,6 +179,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"enable_secure_boot": &hcldec.AttrSpec{Name: "enable_secure_boot", Type: cty.Bool, Required: false},
"enable_vtpm": &hcldec.AttrSpec{Name: "enable_vtpm", Type: cty.Bool, Required: false},
"enable_integrity_monitoring": &hcldec.AttrSpec{Name: "enable_integrity_monitoring", Type: cty.Bool, Required: false},
"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},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
"image_encryption_key": &hcldec.BlockSpec{TypeName: "image_encryption_key", Nested: hcldec.ObjectSpec((*FlatCustomerEncryptionKey)(nil).HCL2Spec())},

@ -34,7 +34,13 @@ type driverGCE struct {
ui packer.Ui
}
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
func getDriverScopes(iap bool) []string {
ds := []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
// if iap {
// ds = append(ds, "https://www.googleapis.com/auth/iap.tunnelResourceAccessor")
// }
return ds
}
// Define a TokenSource that gets tokens from Vault
type OauthTokenSource struct {
@ -69,7 +75,7 @@ func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
}
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
func NewClientGCE(conf *jwt.Config, vaultOauth string, iap bool) (*http.Client, error) {
var err error
var client *http.Client
@ -84,7 +90,7 @@ func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
// Auth with AccountFile if provided
log.Printf("[INFO] Requesting Google token via account_file...")
log.Printf("[INFO] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
log.Printf("[INFO] -- Scopes: %s", getDriverScopes(iap))
log.Printf("[INFO] -- Private Key Length: %d", len(conf.PrivateKey))
// Initiate an http.Client. The following GET request will be
@ -93,7 +99,7 @@ func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
client = conf.Client(context.TODO())
} else {
log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
client, err = google.DefaultClient(context.TODO(), DriverScopes...)
client, err = google.DefaultClient(context.TODO(), getDriverScopes(iap)...)
// The DefaultClient uses the DefaultTokenSource of the google lib.
// The DefaultTokenSource uses the "Application Default Credentials"
// It looks for credentials in the following places, preferring the first location found:
@ -115,8 +121,8 @@ func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
return client, nil
}
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth)
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string, iap bool) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth, iap)
if err != nil {
return nil, err
}

@ -0,0 +1,260 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type IAPConfig
package googlecompute
import (
"bufio"
"bytes"
"context"
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"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 StepStartTunnel struct {
IAPConf *IAPConfig
CommConf *communicator.Config
AccountFile string
ctxCancel context.CancelFunc
cmd *exec.Cmd
}
func (s *StepStartTunnel) ConfigureLocalHostPort(ctx context.Context) error {
if s.IAPConf.IAPLocalhostPort == 0 {
log.Printf("Finding an available TCP port for IAP proxy")
l, err := net.ListenRangeConfig{
Min: 8000,
Max: 9000,
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)
s.IAPConf.IAPHashBang = fmt.Sprintf("#!%s\n", s.IAPConf.IAPHashBang)
log.Printf("[INFO] (google): Prepending inline gcloud setup script with %s",
s.IAPConf.IAPHashBang)
writer.WriteString(s.IAPConf.IAPHashBang)
// authenticate to gcloud
_, err = writer.WriteString(
fmt.Sprintf("gcloud auth activate-service-account --key-file='%s'\n",
s.AccountFile))
if err != nil {
return "", fmt.Errorf("Error preparing gcloud shell script: %s", err)
}
// call command
args = append([]string{"gcloud"}, args...)
argString := strings.Join(args, " ")
if _, err := writer.WriteString(argString + "\n"); 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)
}
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 != "" {
os.Rename(tempScriptFileName, fmt.Sprintf("%s%s", tempScriptFileName, s.IAPConf.IAPExt))
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("Calling tunnel launch 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)
// Shell out to gcloud.
cancelCtx, cancel := context.WithCancel(ctx)
s.ctxCancel = cancel
err = retry.Config{
Tries: 11,
ShouldRetry: func(err error) bool {
// Example of error you get as the tunnel is still getting users
// configured in the cloud:
//"ERROR: (gcloud.compute.start-iap-tunnel) Error while connecting
// [4033: u'not authorized']."
return true
// if strings.Contains(err.Error(), "[4033: u'not authorized']") {
// log.Printf("Waiting for tunnel permissions to update. Retrying...")
// return true
// }
// return false
},
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
// set stdout and stderr so we can read what's going on.
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(cancelCtx, tempScriptFileName)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err := cmd.Start()
log.Printf("Waiting 30s for tunnel to create...")
if err != nil {
err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s",
err)
cmd.Process.Kill()
return err
}
// Wait for tunnel to launch and gather response. TODO: do this without
// a sleep.
time.Sleep(30 * time.Second)
// Track stdout.
sout := stdout.String()
if sout != "" {
log.Printf("[start-iap-tunnel] stdout is:")
}
log.Printf("[start-iap-tunnel] stderr is:")
serr := stderr.String()
log.Println(serr)
if strings.Contains(serr, "ERROR") {
cmd.Process.Kill()
errIdx := strings.Index(serr, "ERROR:")
return fmt.Errorf("ERROR: %s", serr[errIdx+7:len(serr)])
}
// Store successful command on step so we can access it to cancel it
// later.
s.cmd = cmd
return nil
})
return multistep.ActionContinue
}
// Cleanup destroys the GCE instance created during the image creation process.
func (s *StepStartTunnel) Cleanup(state multistep.StateBag) {
if s.cmd != nil && s.cmd.Process != nil {
log.Printf("Cleaning up the IAP tunnel...")
// Why not just s.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 kill 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 destroy it here.
syscall.Kill(-s.cmd.Process.Pid, syscall.SIGKILL)
} else {
log.Printf("Couldn't find IAP tunnel process to kill. Continuing.")
}
return
}

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

@ -22,6 +22,7 @@ type Config struct {
common.PackerConfig `mapstructure:",squash"`
AccountFile string `mapstructure:"account_file"`
IAP bool `mapstructure:"iap"`
DiskSizeGb int64 `mapstructure:"disk_size"`
DiskType string `mapstructure:"disk_type"`
@ -111,14 +112,14 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
// Set up credentials for GCE driver.
if builderAccountFile != "" {
cfg, err := googlecompute.ProcessAccountFile(builderAccountFile)
cfg, err := googlecompute.ProcessAccountFile(builderAccountFile, p.config.IAP)
if err != nil {
return nil, false, false, err
}
p.config.account = cfg
}
if p.config.AccountFile != "" {
cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile)
cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile, p.config.IAP)
if err != nil {
return nil, false, false, err
}
@ -159,7 +160,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
}
driver, err := googlecompute.NewDriverGCE(ui, builderProjectId,
p.config.account, p.config.VaultGCPOauthEngine)
p.config.account, p.config.VaultGCPOauthEngine, p.config.IAP)
if err != nil {
return nil, false, false, err
}

@ -17,6 +17,7 @@ type FlatConfig struct {
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
AccountFile *string `mapstructure:"account_file" cty:"account_file"`
IAP *bool `mapstructure:"iap" cty:"iap"`
DiskSizeGb *int64 `mapstructure:"disk_size" cty:"disk_size"`
DiskType *string `mapstructure:"disk_type" cty:"disk_type"`
MachineType *string `mapstructure:"machine_type" cty:"machine_type"`
@ -48,6 +49,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
"iap": &hcldec.AttrSpec{Name: "iap", Type: cty.Bool, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_type": &hcldec.AttrSpec{Name: "disk_type", Type: cty.String, Required: false},
"machine_type": &hcldec.AttrSpec{Name: "machine_type", Type: cty.String, Required: false},

@ -28,6 +28,7 @@ type Config struct {
AccountFile string `mapstructure:"account_file"`
ProjectId string `mapstructure:"project_id"`
IAP bool `mapstructure:"iap"`
Bucket string `mapstructure:"bucket"`
GCSObjectName string `mapstructure:"gcs_object_name"`
@ -77,7 +78,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
}
if p.config.AccountFile != "" {
cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile)
cfg, err := googlecompute.ProcessAccountFile(p.config.AccountFile, p.config.IAP)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
@ -117,7 +118,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
}
p.config.ctx.Data = generatedData
client, err := googlecompute.NewClientGCE(p.config.account, p.config.VaultGCPOauthEngine)
client, err := googlecompute.NewClientGCE(p.config.account, p.config.VaultGCPOauthEngine, p.config.IAP)
if err != nil {
return nil, false, false, err
}

@ -18,6 +18,7 @@ type FlatConfig struct {
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
AccountFile *string `mapstructure:"account_file" cty:"account_file"`
ProjectId *string `mapstructure:"project_id" cty:"project_id"`
IAP *bool `mapstructure:"iap" cty:"iap"`
Bucket *string `mapstructure:"bucket" cty:"bucket"`
GCSObjectName *string `mapstructure:"gcs_object_name" cty:"gcs_object_name"`
ImageDescription *string `mapstructure:"image_description" cty:"image_description"`
@ -50,6 +51,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"iap": &hcldec.AttrSpec{Name: "iap", Type: cty.Bool, Required: false},
"bucket": &hcldec.AttrSpec{Name: "bucket", Type: cty.String, Required: false},
"gcs_object_name": &hcldec.AttrSpec{Name: "gcs_object_name", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},

@ -217,6 +217,8 @@ builder.
@include 'builder/googlecompute/Config-not-required.mdx'
@include 'builder/googlecompute/IAPConfig-not-required.mdx'
## Startup Scripts
Startup scripts can be a powerful tool for configuring the instance from which

@ -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…
Cancel
Save