Commit b5c0c63e authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

builder/googlecompute: use new auth scheme

parent 13a0c2b0
...@@ -2,11 +2,19 @@ package googlecompute ...@@ -2,11 +2,19 @@ package googlecompute
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "os"
) )
// clientSecrets represents the client secrets of a GCE service account. // accountFile represents the structure of the account file JSON file.
type clientSecrets struct { type accountFile struct {
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
}
// clientSecretsFile represents the structure of the client secrets JSON file.
type clientSecretsFile struct {
Web struct { Web struct {
AuthURI string `json:"auth_uri"` AuthURI string `json:"auth_uri"`
ClientEmail string `json:"client_email"` ClientEmail string `json:"client_email"`
...@@ -15,18 +23,13 @@ type clientSecrets struct { ...@@ -15,18 +23,13 @@ type clientSecrets struct {
} }
} }
// loadClientSecrets loads the GCE client secrets file identified by path. func loadJSON(result interface{}, path string) error {
func loadClientSecrets(path string) (*clientSecrets, error) { f, err := os.Open(path)
var cs *clientSecrets
secretBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
err = json.Unmarshal(secretBytes, &cs)
if err != nil { if err != nil {
return nil, err return err
} }
defer f.Close()
return cs, nil dec := json.NewDecoder(f)
return dec.Decode(result)
} }
...@@ -35,7 +35,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -35,7 +35,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// representing a GCE machine image. // representing a GCE machine image.
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
driver, err := NewDriverGCE( driver, err := NewDriverGCE(
ui, b.config.ProjectId, b.config.clientSecrets, b.config.privateKeyBytes) ui, b.config.ProjectId, &b.config.account, &b.config.clientSecrets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package googlecompute
import (
"io/ioutil"
"testing"
)
func testClientSecretsFile(t *testing.T) string {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
if _, err := tf.Write([]byte(testClientSecretsContent)); err != nil {
t.Fatalf("err: %s", err)
}
return tf.Name()
}
func TestLoadClientSecrets(t *testing.T) {
_, err := loadClientSecrets(testClientSecretsFile(t))
if err != nil {
t.Fatalf("err: %s", err)
}
}
// This is just some dummy data that doesn't actually work (it was revoked
// a long time ago).
const testClientSecretsContent = `{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_id":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}`
...@@ -16,8 +16,11 @@ import ( ...@@ -16,8 +16,11 @@ import (
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
AccountFile string `mapstructure:"account_file"`
ClientSecretsFile string `mapstructure:"client_secrets_file"`
ProjectId string `mapstructure:"project_id"`
BucketName string `mapstructure:"bucket_name"` BucketName string `mapstructure:"bucket_name"`
ClientSecretsFile string `mapstructure:"client_secrets_file"`
DiskSizeGb int64 `mapstructure:"disk_size"` DiskSizeGb int64 `mapstructure:"disk_size"`
ImageName string `mapstructure:"image_name"` ImageName string `mapstructure:"image_name"`
ImageDescription string `mapstructure:"image_description"` ImageDescription string `mapstructure:"image_description"`
...@@ -25,9 +28,6 @@ type Config struct { ...@@ -25,9 +28,6 @@ type Config struct {
MachineType string `mapstructure:"machine_type"` MachineType string `mapstructure:"machine_type"`
Metadata map[string]string `mapstructure:"metadata"` Metadata map[string]string `mapstructure:"metadata"`
Network string `mapstructure:"network"` Network string `mapstructure:"network"`
Passphrase string `mapstructure:"passphrase"`
PrivateKeyFile string `mapstructure:"private_key_file"`
ProjectId string `mapstructure:"project_id"`
SourceImage string `mapstructure:"source_image"` SourceImage string `mapstructure:"source_image"`
SourceImageProjectId string `mapstructure:"source_image_project_id"` SourceImageProjectId string `mapstructure:"source_image_project_id"`
SSHUsername string `mapstructure:"ssh_username"` SSHUsername string `mapstructure:"ssh_username"`
...@@ -37,7 +37,8 @@ type Config struct { ...@@ -37,7 +37,8 @@ type Config struct {
Tags []string `mapstructure:"tags"` Tags []string `mapstructure:"tags"`
Zone string `mapstructure:"zone"` Zone string `mapstructure:"zone"`
clientSecrets *clientSecrets account accountFile
clientSecrets clientSecretsFile
instanceName string instanceName string
privateKeyBytes []byte privateKeyBytes []byte
sshTimeout time.Duration sshTimeout time.Duration
...@@ -104,15 +105,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -104,15 +105,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
// Process Templates // Process Templates
templates := map[string]*string{ templates := map[string]*string{
"account_file": &c.AccountFile,
"client_secrets_file": &c.ClientSecretsFile,
"bucket_name": &c.BucketName, "bucket_name": &c.BucketName,
"client_secrets_file": &c.ClientSecretsFile,
"image_name": &c.ImageName, "image_name": &c.ImageName,
"image_description": &c.ImageDescription, "image_description": &c.ImageDescription,
"instance_name": &c.InstanceName, "instance_name": &c.InstanceName,
"machine_type": &c.MachineType, "machine_type": &c.MachineType,
"network": &c.Network, "network": &c.Network,
"passphrase": &c.Passphrase,
"private_key_file": &c.PrivateKeyFile,
"project_id": &c.ProjectId, "project_id": &c.ProjectId,
"source_image": &c.SourceImage, "source_image": &c.SourceImage,
"source_image_project_id": &c.SourceImageProjectId, "source_image_project_id": &c.SourceImageProjectId,
...@@ -137,14 +138,14 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -137,14 +138,14 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs, errors.New("a bucket_name must be specified")) errs, errors.New("a bucket_name must be specified"))
} }
if c.ClientSecretsFile == "" { if c.AccountFile == "" {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("a client_secrets_file must be specified")) errs, errors.New("an account_file must be specified"))
} }
if c.PrivateKeyFile == "" { if c.ClientSecretsFile == "" {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("a private_key_file must be specified")) errs, errors.New("a client_secrets_file must be specified"))
} }
if c.ProjectId == "" { if c.ProjectId == "" {
...@@ -177,22 +178,17 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -177,22 +178,17 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
c.stateTimeout = stateTimeout c.stateTimeout = stateTimeout
if c.ClientSecretsFile != "" { if c.AccountFile != "" {
// Load the client secrets file. if err := loadJSON(&c.account, c.AccountFile); err != nil {
cs, err := loadClientSecrets(c.ClientSecretsFile)
if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing client secrets file: %s", err)) errs, fmt.Errorf("Failed parsing account file: %s", err))
} }
c.clientSecrets = cs
} }
if c.PrivateKeyFile != "" { if c.ClientSecretsFile != "" {
// Load the private key. if err := loadJSON(&c.clientSecrets, c.ClientSecretsFile); err != nil {
c.privateKeyBytes, err = processPrivateKeyFile(c.PrivateKeyFile, c.Passphrase)
if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed loading private key file: %s", err)) errs, fmt.Errorf("Failed parsing client secrets file: %s", err))
} }
} }
......
package googlecompute package googlecompute
import ( import (
"io/ioutil"
"testing" "testing"
) )
func testConfig(t *testing.T) map[string]interface{} { func testConfig(t *testing.T) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"account_file": testAccountFile(t),
"bucket_name": "foo", "bucket_name": "foo",
"client_secrets_file": testClientSecretsFile(t), "client_secrets_file": testClientSecretsFile(t),
"private_key_file": testPrivateKeyFile(t),
"project_id": "hashicorp", "project_id": "hashicorp",
"source_image": "foo", "source_image": "foo",
"zone": "us-east-1a", "zone": "us-east-1a",
...@@ -84,16 +85,6 @@ func TestConfigPrepare(t *testing.T) { ...@@ -84,16 +85,6 @@ func TestConfigPrepare(t *testing.T) {
true, true,
}, },
{
"private_key_file",
nil,
true,
},
{
"private_key_file",
testPrivateKeyFile(t),
false,
},
{ {
"private_key_file", "private_key_file",
"/tmp/i/should/not/exist", "/tmp/i/should/not/exist",
...@@ -174,3 +165,37 @@ func TestConfigPrepare(t *testing.T) { ...@@ -174,3 +165,37 @@ func TestConfigPrepare(t *testing.T) {
} }
} }
} }
func testAccountFile(t *testing.T) string {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
if _, err := tf.Write([]byte(testAccountContent)); err != nil {
t.Fatalf("err: %s", err)
}
return tf.Name()
}
func testClientSecretsFile(t *testing.T) string {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
if _, err := tf.Write([]byte(testClientSecretsContent)); err != nil {
t.Fatalf("err: %s", err)
}
return tf.Name()
}
// This is just some dummy data that doesn't actually work (it was revoked
// a long time ago).
const testAccountContent = `{}`
const testClientSecretsContent = `{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_id":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}`
...@@ -23,22 +23,27 @@ type driverGCE struct { ...@@ -23,22 +23,27 @@ type driverGCE struct {
const DriverScopes string = "https://www.googleapis.com/auth/compute " + const DriverScopes string = "https://www.googleapis.com/auth/compute " +
"https://www.googleapis.com/auth/devstorage.full_control" "https://www.googleapis.com/auth/devstorage.full_control"
func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte) (Driver, error) { func NewDriverGCE(ui packer.Ui, p string, a *accountFile, c *clientSecretsFile) (Driver, error) {
log.Printf("[INFO] Requesting token...") // Get the token for use in our requests
log.Printf("[INFO] -- Email: %s", c.Web.ClientEmail) log.Printf("[INFO] Requesting Google token...")
log.Printf("[INFO] -- Email: %s", a.ClientEmail)
log.Printf("[INFO] -- Scopes: %s", DriverScopes) log.Printf("[INFO] -- Scopes: %s", DriverScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(key)) log.Printf("[INFO] -- Private Key Length: %d", len(a.PrivateKey))
log.Printf("[INFO] -- Token URL: %s", c.Web.TokenURI) log.Printf("[INFO] -- Token URL: %s", c.Web.TokenURI)
jwtTok := jwt.NewToken(c.Web.ClientEmail, DriverScopes, key) jwtTok := jwt.NewToken(
a.ClientEmail,
DriverScopes,
[]byte(a.PrivateKey))
jwtTok.ClaimSet.Aud = c.Web.TokenURI jwtTok.ClaimSet.Aud = c.Web.TokenURI
token, err := jwtTok.Assert(new(http.Client)) token, err := jwtTok.Assert(new(http.Client))
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Error retrieving auth token: %s", err)
} }
// Instantiate the transport to communicate to Google
transport := &oauth.Transport{ transport := &oauth.Transport{
Config: &oauth.Config{ Config: &oauth.Config{
ClientId: c.Web.ClientId, ClientId: a.ClientId,
Scope: DriverScopes, Scope: DriverScopes,
TokenURL: c.Web.TokenURI, TokenURL: c.Web.TokenURI,
AuthURL: c.Web.AuthURI, AuthURL: c.Web.AuthURI,
...@@ -46,14 +51,14 @@ func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte) ...@@ -46,14 +51,14 @@ func NewDriverGCE(ui packer.Ui, projectId string, c *clientSecrets, key []byte)
Token: token, Token: token,
} }
log.Printf("[INFO] Instantiating client...") log.Printf("[INFO] Instantiating GCE client...")
service, err := compute.New(transport.Client()) service, err := compute.New(transport.Client())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &driverGCE{ return &driverGCE{
projectId: projectId, projectId: p,
service: service, service: service,
ui: ui, ui: ui,
}, nil }, nil
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment