Skip to content

Commit

Permalink
Add support for GOOGLE_CREDENTIALS environment variable for JSON key …
Browse files Browse the repository at this point in the history
…file
  • Loading branch information
Ivan Gusev committed Nov 17, 2022
1 parent 3124356 commit aaa68e2
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 88 deletions.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ resource "ipam_ip_range" "sub1" {

### Authentication

The provider uses application default credentials to authenticate against the backend. Alternatively you can directly provide an identity token via the GCP_IDENTITY_TOKEN environment variable to access the Cloud Run instance, the audience for the identity token should be the domain of the Cloud Run service.
The provider uses application default credentials to authenticate against the backend. Alternatively you can set GOOGLE_CREDENTIALS variable to point or contain JSON account key or directly provide an identity token via the GCP_IDENTITY_TOKEN environment variable to access the Cloud Run instance, the audience for the identity token should be the domain of the Cloud Run service.

5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module github.com/openx/terraform-provider-gcp-ipam-autopilot
go 1.18

require (
github.com/go-git/go-git/v5 v5.4.2
github.com/hashicorp/terraform-plugin-docs v0.13.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0
github.com/mitchellh/go-homedir v1.1.0
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
google.golang.org/api v0.34.0
)
Expand All @@ -20,6 +22,8 @@ require (
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
Expand Down Expand Up @@ -71,4 +75,5 @@ require (
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down
115 changes: 115 additions & 0 deletions ipam/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ipam

import (
"context"
"fmt"
"log"

"golang.org/x/oauth2"
googleoauth "golang.org/x/oauth2/google"
"google.golang.org/api/idtoken"
"google.golang.org/api/option"
)

// Config is used as a general provider config throughout the provider
type Config struct {
Url string
AccessToken string
Credentials string
context context.Context
}

// staticTokenSource is used to be able to identify static token sources without reflection.
type staticTokenSource struct {
oauth2.TokenSource
}

const (
userInfoScope = "https://www.googleapis.com/auth/userinfo.email"
)

func (c *Config) getToken() (string, error) {

creds, err := c.GetCredentials([]string{userInfoScope})
if err != nil {
return "", fmt.Errorf("error calling getCredentials(): %v", err)
}

ctx := context.Background()

targetAudience := c.Url // "http://ipam-autopilot.com"

co := []option.ClientOption{}
co = append(co, idtoken.WithCredentialsJSON(creds.JSON))

idTokenSource, err := idtoken.NewTokenSource(ctx, targetAudience, co...)

if err != nil {
return "", fmt.Errorf("unable to retrieve TokenSource: %v", err)
}
idToken, err := idTokenSource.Token()
if err != nil {
return "", fmt.Errorf("unable to retrieve Token: %v", err)
}

return idToken.AccessToken, nil
}

// Get a set of credentials with a given scope (clientScopes) based on the Config object.
func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) {
if c.AccessToken != "" {
contents, _, err := pathOrContents(c.AccessToken)
if err != nil {
return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err)
}

token := &oauth2.Token{AccessToken: contents}

log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...")
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return googleoauth.Credentials{
TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)},
}, nil
}

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

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

log.Printf("[INFO] Authenticating using configured Google JSON 'credentials'...")
log.Printf("[INFO] -- Scopes: %s", clientScopes)
return *creds, nil
}

log.Printf("[INFO] Authenticating using DefaultClient...")
log.Printf("[INFO] -- Scopes: %s", clientScopes)
defaultTS, err := googleoauth.DefaultTokenSource(context.Background(), clientScopes...)
if err != nil {
return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block. No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'. Original error: %w", err)
}

return googleoauth.Credentials{
TokenSource: defaultTS,
}, err
}
20 changes: 0 additions & 20 deletions ipam/config/config.go

This file was deleted.

94 changes: 83 additions & 11 deletions ipam/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,120 @@
package ipam

import (
"context"
"fmt"
"os"

"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/config"
"github.com/openx/terraform-provider-gcp-ipam-autopilot/ipam/resources"

//"github.com/google/martian/v3/log"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

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

// Provider for Simple IPAM
func Provider() *schema.Provider {
return &schema.Provider{
provider := &schema.Provider{
Schema: map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "URL where to connect with the IPAM Autopilot backend",
},
"credentials": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateCredentials,
ConflictsWith: []string{"access_token"},
},

"access_token": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"credentials"},
},
},
ResourcesMap: map[string]*schema.Resource{
"ipam_ip_range": resources.ResourceIpRange(),
"ipam_routing_domain": resources.ResourceRoutingDomain(),
"ipam_ip_range": ResourceIpRange(),
"ipam_routing_domain": ResourceRoutingDomain(),
},
DataSourcesMap: map[string]*schema.Resource{},
ConfigureFunc: providerConfigure,
}
provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
return providerConfigure(ctx, d, provider)
}

return provider
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Provider) (interface{}, diag.Diagnostics) {
config := Config{}

url := d.Get("url").(string)
if url == "" {
url = os.Getenv("IPAM_URL")
}

if url == "" {
return nil, fmt.Errorf("URL needed to access IPAM Autopilot")
return nil, diag.Errorf("URL needed to access IPAM Autopilot")
}

config.Url = url

// Check for primary credentials in config. Note that if neither is set, ADCs
// will be used if available.
if v, ok := d.GetOk("access_token"); ok {
config.AccessToken = v.(string)
}

config := config.Config{
Url: url,
if v, ok := d.GetOk("credentials"); ok {
config.Credentials = v.(string)
}

// only check environment variables if neither value was set in config- this
// means config beats env var in all cases.
if config.AccessToken == "" && config.Credentials == "" {
config.Credentials = multiEnvSearch([]string{
"GOOGLE_CREDENTIALS",
"GOOGLE_CLOUD_KEYFILE_JSON",
"GCLOUD_KEYFILE_JSON",
})

// Retrieve token if credentials were available in one of ENV variables
if config.Credentials != "" {

token, err := config.getToken()
if err != nil {
return "", diag.Errorf("unable to retrieve identityToken: %v", err)
}
config.AccessToken = token
return config, nil
}

config.AccessToken = multiEnvSearch([]string{
"GOOGLE_OAUTH_ACCESS_TOKEN",
"GCP_IDENTITY_TOKEN",
})

}

return config, nil
}

func validateCredentials(v interface{}, k string) (warnings []string, errors []error) {
if v == nil || v.(string) == "" {
return
}
creds := v.(string)
// if this is a path and we can stat it, assume it's ok
if _, err := os.Stat(creds); err == nil {
return
}
if _, err := googleoauth.CredentialsFromJSON(context.Background(), []byte(creds)); err != nil {
errors = append(errors,
fmt.Errorf("JSON credentials are not valid: %s", err))
}

return
}
Loading

0 comments on commit aaa68e2

Please sign in to comment.