Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OAuth2 access_token and client config as credential options. #2838

Merged
merged 1 commit into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 31 additions & 64 deletions google/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ package google

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"

"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/httpclient"
"github.com/terraform-providers/terraform-provider-google/version"

"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
googleoauth "golang.org/x/oauth2/google"
appengine "google.golang.org/api/appengine/v1"
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/cloudbilling/v1"
Expand Down Expand Up @@ -53,6 +50,7 @@ import (
// provider.
type Config struct {
Credentials string
AccessToken string
Project string
Region string
Zone string
Expand Down Expand Up @@ -98,63 +96,20 @@ type Config struct {
}

func (c *Config) loadAndValidate() error {
var account accountFile
clientScopes := []string{
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/ndev.clouddns.readwrite",
"https://www.googleapis.com/auth/devstorage.full_control",
}

var client *http.Client
var tokenSource oauth2.TokenSource

if c.Credentials != "" {
contents, _, err := pathorcontents.Read(c.Credentials)
if err != nil {
return fmt.Errorf("Error loading credentials: %s", err)
}

// Assume account_file is a JSON string
if err := parseJSON(&account, contents); err != nil {
return fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
}

// Get the token for use in our requests
log.Printf("[INFO] Requesting Google token...")
log.Printf("[INFO] -- Email: %s", account.ClientEmail)
log.Printf("[INFO] -- Scopes: %s", clientScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey))

conf := jwt.Config{
Email: account.ClientEmail,
PrivateKey: []byte(account.PrivateKey),
Scopes: clientScopes,
TokenURL: "https://accounts.google.com/o/oauth2/token",
}

// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client = conf.Client(context.Background())

tokenSource = conf.TokenSource(context.Background())
} else {
log.Printf("[INFO] Authenticating using DefaultClient")
err := error(nil)
client, err = google.DefaultClient(context.Background(), clientScopes...)
if err != nil {
return err
}

tokenSource, err = google.DefaultTokenSource(context.Background(), clientScopes...)
if err != nil {
return err
}
tokenSource, err := c.getTokenSource(clientScopes)
if err != nil {
return err
}

c.tokenSource = tokenSource

client := oauth2.NewClient(context.Background(), tokenSource)
client.Transport = logging.NewTransport("Google", client.Transport)

terraformVersion := httpclient.UserAgentString()
Expand All @@ -165,8 +120,6 @@ func (c *Config) loadAndValidate() error {
c.client = client
c.userAgent = userAgent

var err error

log.Printf("[INFO] Instantiating GCE client...")
c.clientCompute, err = compute.New(client)
if err != nil {
Expand Down Expand Up @@ -391,17 +344,31 @@ func (c *Config) loadAndValidate() error {
return nil
}

// accountFile represents the structure of the account file JSON file.
type accountFile struct {
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
}
func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, error) {
if c.AccessToken != "" {
log.Printf("[INFO] Using configured Google access token (length %d)", len(c.AccessToken))
log.Printf("[INFO] -- Scopes: %s", clientScopes)
token := &oauth2.Token{AccessToken: c.AccessToken}
return oauth2.StaticTokenSource(token), nil
}

func parseJSON(result interface{}, contents string) error {
r := strings.NewReader(contents)
dec := json.NewDecoder(r)
if c.Credentials != "" {
contents, _, err := pathorcontents.Read(c.Credentials)
if err != nil {
return nil, fmt.Errorf("Error loading credentials: %s", err)
}

creds, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(contents), clientScopes...)
if err != nil {
return nil, fmt.Errorf("Unable to parse credentials from '%s': %s", contents, err)
}

log.Printf("[INFO] Requesting Google token using Credential File %q...", c.Credentials)
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return creds.TokenSource, nil
}

return dec.Decode(result)
log.Printf("[INFO] Authenticating using DefaultClient")
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return googleoauth.DefaultTokenSource(context.Background(), clientScopes...)
}
15 changes: 15 additions & 0 deletions google/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,18 @@ func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) {
t.Fatalf("expected error, but got nil")
}
}

func TestConfigLoadValidate_accessToken(t *testing.T) {
accessToken := getTestAccessTokenFromEnv(t)

config := Config{
AccessToken: accessToken,
Project: "my-gce-project",
Region: "us-central1",
}

err := config.loadAndValidate()
if err != nil {
t.Fatalf("error: %v", err)
}
}
30 changes: 21 additions & 9 deletions google/provider.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package google

import (
"encoding/json"
"context"
"fmt"
"os"

"github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"

googleoauth "golang.org/x/oauth2/google"
)

// Global MutexKV
Expand All @@ -28,6 +30,12 @@ func Provider() terraform.ResourceProvider {
ValidateFunc: validateCredentials,
},

"access_token": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"credentials"},
},

"project": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -244,12 +252,17 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
credentials := d.Get("credentials").(string)
config := Config{
Credentials: credentials,
Project: d.Get("project").(string),
Region: d.Get("region").(string),
Zone: d.Get("zone").(string),
Project: d.Get("project").(string),
Region: d.Get("region").(string),
Zone: d.Get("zone").(string),
}

// Add credential source
if v, ok := d.GetOk("access_token"); ok {
config.AccessToken = v.(string)
} else if v, ok := d.GetOk("credentials"); ok {
config.Credentials = v.(string)
}

if err := config.loadAndValidate(); err != nil {
Expand All @@ -268,10 +281,9 @@ func validateCredentials(v interface{}, k string) (warnings []string, errors []e
if _, err := os.Stat(creds); err == nil {
return
}
var account accountFile
if err := json.Unmarshal([]byte(creds), &account); err != nil {
if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
errors = append(errors,
fmt.Errorf("credentials are not valid JSON '%s': %s", creds, err))
fmt.Errorf("JSON credentials in %q are not valid: %s", creds, err))
}

return
Expand Down
9 changes: 9 additions & 0 deletions google/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ var billingAccountEnvVars = []string{
"GOOGLE_BILLING_ACCOUNT",
}

var accessTokenEnvVars = []string{
"GOOGLE_OAUTH2_ACCESS_TOKEN",
}

func init() {
testAccProvider = Provider().(*schema.Provider)
testAccRandomProvider = random.Provider().(*schema.Provider)
Expand Down Expand Up @@ -202,6 +206,11 @@ func getTestServiceAccountFromEnv(t *testing.T) string {
return multiEnvSearch(serviceAccountEnvVars)
}

func getTestAccessTokenFromEnv(t *testing.T) string {
skipIfEnvNotSet(t, accessTokenEnvVars...)
return multiEnvSearch(accessTokenEnvVars)
}

func multiEnvSearch(ks []string) string {
for _, k := range ks {
if v := os.Getenv(k); v != "" {
Expand Down