Allow user to real oauth token from properly configured Vault instance

This commit is contained in:
Megan Marsh 2019-09-20 16:52:35 -07:00
parent 2a662b451c
commit 3c14c50aba
6 changed files with 99 additions and 15 deletions

View File

@ -36,7 +36,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// representing a GCE machine image. // representing a GCE machine image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
driver, err := NewDriverGCE( driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.Account) ui, b.config.ProjectId, b.config.Account, b.config.VaultGCPOauthEngine)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -165,6 +165,18 @@ type Config struct {
// If true, use the instance's internal IP instead of its external IP // If true, use the instance's internal IP instead of its external IP
// during building. // during building.
UseInternalIP bool `mapstructure:"use_internal_ip" required:"false"` UseInternalIP bool `mapstructure:"use_internal_ip" required:"false"`
// Can be set instead of account_file. If set, this builder will use
// HashiCorp Vault to generate an Oauth token for authenticating against
// Google's cloud. The value should be the path of the token generator
// within vault.
// For information on how to configure your Vault + GCP engine to produce
// Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp.html
// You must have the environment variables VAULT_ADDR and VAULT_TOKEN set,
// along with any other relevant variables for accessing your vault
// instance. For more information, see the Vault docs:
// https://www.vaultproject.io/docs/commands/#environment-variables
// Example:`"vault_gcp_oauth_engine": "gcp/token/my-project-editor",`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
// The zone in which to launch the instance used to create the image. // The zone in which to launch the instance used to create the image.
// Example: "us-central1-a" // Example: "us-central1-a"
Zone string `mapstructure:"zone" required:"true"` Zone string `mapstructure:"zone" required:"true"`
@ -322,7 +334,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, err) errs = packer.MultiErrorAppend(errs, err)
} }
// Authenticating via an account file
if c.AccountFile != "" { if c.AccountFile != "" {
if c.VaultGCPOauthEngine != "" {
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)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend(errs, err) errs = packer.MultiErrorAppend(errs, err)

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/useragent" "github.com/hashicorp/packer/helper/useragent"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
vaultapi "github.com/hashicorp/vault/api"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
@ -35,13 +36,52 @@ type driverGCE struct {
var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"} var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
func NewClientGCE(conf *jwt.Config) (*http.Client, error) { // Define a TokenSource that gets tokens from Vault
type OauthTokenSource struct {
Path string
}
func (ots OauthTokenSource) Token() (*oauth2.Token, error) {
log.Printf("Retrieving Oauth token from Vault...")
vaultConfig := vaultapi.DefaultConfig()
cli, err := vaultapi.NewClient(vaultConfig)
if err != nil {
return nil, fmt.Errorf("%s\n", err)
}
resp, err := cli.Logical().Read(ots.Path)
if err != nil {
return nil, fmt.Errorf("Error reading vault resp: %s", err)
}
if resp == nil {
return nil, fmt.Errorf("Vault Oauth Engine does not exist at the given path.")
}
token, ok := resp.Data["token"]
if !ok {
return nil, fmt.Errorf("ERROR, token was not present in response body")
}
at := token.(string)
log.Printf("Retrieved Oauth token from Vault")
return &oauth2.Token{
AccessToken: at,
Expiry: time.Now().Add(time.Minute * time.Duration(60)),
}, nil
}
func NewClientGCE(conf *jwt.Config, vaultOauth string) (*http.Client, error) {
var err error var err error
var client *http.Client var client *http.Client
// Auth with AccountFile first if provided if vaultOauth != "" {
if conf != nil && len(conf.PrivateKey) > 0 { // Auth with Vault Oauth
log.Printf("Using Vault to generate Oauth token.")
ts := OauthTokenSource{vaultOauth}
return oauth2.NewClient(oauth2.NoContext, ts), nil
} else if conf != nil && len(conf.PrivateKey) > 0 {
// Auth with AccountFile if provided
log.Printf("[INFO] Requesting Google token via account_file...") log.Printf("[INFO] Requesting Google token via account_file...")
log.Printf("[INFO] -- Email: %s", conf.Email) log.Printf("[INFO] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes) log.Printf("[INFO] -- Scopes: %s", DriverScopes)
@ -75,8 +115,8 @@ func NewClientGCE(conf *jwt.Config) (*http.Client, error) {
return client, nil return client, nil
} }
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config) (Driver, error) { func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf) client, err := NewClientGCE(conf, vaultOauth)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -25,6 +25,7 @@ type Config struct {
Network string `mapstructure:"network"` Network string `mapstructure:"network"`
Paths []string `mapstructure:"paths"` Paths []string `mapstructure:"paths"`
Subnetwork string `mapstructure:"subnetwork"` Subnetwork string `mapstructure:"subnetwork"`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
Zone string `mapstructure:"zone"` Zone string `mapstructure:"zone"`
Account *jwt.Config Account *jwt.Config
@ -69,6 +70,12 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
p.config.Network = "default" p.config.Network = "default"
} }
if p.config.AccountFile != "" && p.config.VaultGCPOauthEngine != "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("May set either account_file or "+
"vault_gcp_oauth_engine, but not both."))
}
if len(errs.Errors) > 0 { if len(errs.Errors) > 0 {
return errs return errs
} }
@ -142,7 +149,8 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
} }
exporterConfig.CalcTimeout() exporterConfig.CalcTimeout()
driver, err := googlecompute.NewDriverGCE(ui, builderProjectId, p.config.Account) driver, err := googlecompute.NewDriverGCE(ui, builderProjectId,
p.config.Account, p.config.VaultGCPOauthEngine)
if err != nil { if err != nil {
return nil, false, false, err return nil, false, false, err
} }

View File

@ -34,6 +34,7 @@ type Config struct {
ImageLabels map[string]string `mapstructure:"image_labels"` ImageLabels map[string]string `mapstructure:"image_labels"`
ImageName string `mapstructure:"image_name"` ImageName string `mapstructure:"image_name"`
SkipClean bool `mapstructure:"skip_clean"` SkipClean bool `mapstructure:"skip_clean"`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
Account *jwt.Config Account *jwt.Config
ctx interpolate.Context ctx interpolate.Context
@ -78,6 +79,12 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
p.config.Account = cfg p.config.Account = cfg
} }
if p.config.AccountFile != "" && p.config.VaultGCPOauthEngine != "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("May set either account_file or "+
"vault_gcp_oauth_engine, but not both."))
}
templates := map[string]*string{ templates := map[string]*string{
"bucket": &p.config.Bucket, "bucket": &p.config.Bucket,
"image_name": &p.config.ImageName, "image_name": &p.config.ImageName,
@ -98,7 +105,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
} }
func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) { func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) {
client, err := googlecompute.NewClientGCE(p.config.Account) client, err := googlecompute.NewClientGCE(p.config.Account, p.config.VaultGCPOauthEngine)
if err != nil { if err != nil {
return nil, false, false, err return nil, false, false, err
} }

View File

@ -125,3 +125,15 @@
- `use_internal_ip` (bool) - If true, use the instance's internal IP instead of its external IP - `use_internal_ip` (bool) - If true, use the instance's internal IP instead of its external IP
during building. during building.
- `vault_gcp_oauth_engine` (string) - Can be set instead of account_file. If set, this builder will use
HashiCorp Vault to generate an Oauth token for authenticating against
Google's cloud. The value should be the path of the token generator
within vault.
For information on how to configure your Vault + GCP engine to produce
Oauth tokens, see https://www.vaultproject.io/docs/auth/gcp.html
You must have the environment variables VAULT_ADDR and VAULT_TOKEN set,
along with any other relevant variables for accessing your vault
instance. For more information, see the Vault docs:
https://www.vaultproject.io/docs/commands/#environment-variables
Example:`"vault_gcp_oauth_engine": "gcp/token/my-project-editor",`