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.
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)
ui, b.config.ProjectId, b.config.Account, b.config.VaultGCPOauthEngine)
if err != nil {
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
// during building.
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.
// Example: "us-central1-a"
Zone string `mapstructure:"zone" required:"true"`
@ -322,7 +334,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, err)
}
// Authenticating via an account file
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)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/useragent"
"github.com/hashicorp/packer/packer"
vaultapi "github.com/hashicorp/vault/api"
"golang.org/x/oauth2"
"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"}
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 client *http.Client
// Auth with AccountFile first if provided
if conf != nil && len(conf.PrivateKey) > 0 {
if vaultOauth != "" {
// 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] -- Email: %s", conf.Email)
log.Printf("[INFO] -- Scopes: %s", DriverScopes)
@ -75,8 +115,8 @@ func NewClientGCE(conf *jwt.Config) (*http.Client, error) {
return client, nil
}
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config) (Driver, error) {
client, err := NewClientGCE(conf)
func NewDriverGCE(ui packer.Ui, p string, conf *jwt.Config, vaultOauth string) (Driver, error) {
client, err := NewClientGCE(conf, vaultOauth)
if err != nil {
return nil, err
}

View File

@ -25,6 +25,7 @@ type Config struct {
Network string `mapstructure:"network"`
Paths []string `mapstructure:"paths"`
Subnetwork string `mapstructure:"subnetwork"`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
Zone string `mapstructure:"zone"`
Account *jwt.Config
@ -69,6 +70,12 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
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 {
return errs
}
@ -142,7 +149,8 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
}
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 {
return nil, false, false, err
}

View File

@ -34,6 +34,7 @@ type Config struct {
ImageLabels map[string]string `mapstructure:"image_labels"`
ImageName string `mapstructure:"image_name"`
SkipClean bool `mapstructure:"skip_clean"`
VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"`
Account *jwt.Config
ctx interpolate.Context
@ -78,6 +79,12 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
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{
"bucket": &p.config.Bucket,
"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) {
client, err := googlecompute.NewClientGCE(p.config.Account)
client, err := googlecompute.NewClientGCE(p.config.Account, p.config.VaultGCPOauthEngine)
if err != nil {
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
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",`