diff --git a/apiclient/options.go b/apiclient/options.go index 927c2ef8..8635fcf7 100644 --- a/apiclient/options.go +++ b/apiclient/options.go @@ -14,6 +14,10 @@ package apiclient +import ( + "os" +) + //BaseURL is the Apigee control plane endpoint const BaseURL = "https://apigee.googleapis.com/v1/organizations/" @@ -31,90 +35,118 @@ type ApigeeClientOptions struct { var options = ApigeeClientOptions{SkipCache: false, SkipCheck: true, SkipLogInfo: true} +//NewApigeeClient sets up options to invoke Apigee APIs func NewApigeeClient(o ApigeeClientOptions) { options = o } +//SetApigeeOrg sets the org variable func SetApigeeOrg(org string) { options.Org = org } +//GetApigeeOrg gets the org variable func GetApigeeOrg() string { return options.Org } +//GetApigeeOrgP gets the pointer to org variable func GetApigeeOrgP() *string { return &options.Org } +//SetApigeeEnv set the env variable func SetApigeeEnv(env string) { options.Env = env } +//GetApigeeEnv gets the env variable func GetApigeeEnv() string { return options.Env } +//GetApigeeEnvP gets the pointer to env variable func GetApigeeEnvP() *string { return &options.Env } +//SetApigeeToken sets the access token for use with Apigee API calls func SetApigeeToken(token string) { options.Token = token } +//GetApigeeToken get the access token value in client opts (does not generate it) func GetApigeeToken() string { return options.Token } +//GetApigeeToken get the access token pointer func GetApigeeTokenP() *string { return &options.Token } +//SetProjectID sets the project id func SetProjectID(projectID string) { options.ProjectID = projectID } +//GetProjectID gets the project id func GetProjectID() string { return options.ProjectID } +//GetProjectID gets the project id pointer func GetProjectIDP() *string { return &options.ProjectID } +//SetServiceAccount func SetServiceAccount(serviceAccount string) { options.ServiceAccount = serviceAccount } +//GetServiceAccount func GetServiceAccount() string { + if options.ServiceAccount == "" { + envVar := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") + if envVar != "" { + options.ServiceAccount = envVar + } + } return options.ServiceAccount } +//GetServiceAccountP func GetServiceAccountP() *string { return &options.ServiceAccount } +//IsSkipCheck func IsSkipCheck() bool { return options.SkipCheck } +//SkipCheck func SkipCheck() *bool { return &options.SkipCheck } +//IsSkipCache func IsSkipCache() bool { return options.SkipCache } +//SkipCache func SkipCache() *bool { return &options.SkipCache } +//IsSkipLogInfo func IsSkipLogInfo() bool { return options.SkipLogInfo } +//SkipLogInfo func SkipLogInfo() *bool { return &options.SkipLogInfo } diff --git a/apiclient/token.go b/apiclient/token.go index 5699a806..9be22c82 100644 --- a/apiclient/token.go +++ b/apiclient/token.go @@ -25,20 +25,35 @@ import ( "net/url" "os/user" "path" + "reflect" "strconv" "strings" "time" "github.com/lestrrat/go-jwx/jwa" "github.com/lestrrat/go-jwx/jwt" - "github.com/spf13/viper" "github.com/srinandan/apigeecli/clilog" ) const accessTokenFile = ".access_token" -func getPrivateKey() (interface{}, error) { - pemPrivateKey := fmt.Sprintf("%v", viper.Get("private_key")) +type serviceAccount struct { + Type string `json:"type,omitempty"` + ProjectID string `json:"project_id,omitempty"` + PrivateKeyID string `json:"private_key_id,omitempty"` + PrivateKey string `json:"private_key,omitempty"` + ClientEmail string `json:"client_email,omitempty"` + ClientID string `json:"client_id,omitempty"` + AuthURI string `json:"auth_uri,omitempty"` + TokenURI string `json:"token_uri,omitempty"` + AuthProviderCertURL string `json:"auth_provider_x509_cert_url,omitempty"` + ClientCertURL string `json:"client_x509_cert_url,omitempty"` +} + +var account = serviceAccount{} + +func getPrivateKey(privateKey string) (interface{}, error) { + pemPrivateKey := fmt.Sprintf("%v", privateKey) block, _ := pem.Decode([]byte(pemPrivateKey)) privKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { @@ -48,11 +63,11 @@ func getPrivateKey() (interface{}, error) { return privKey, nil } -func generateJWT() (string, error) { +func generateJWT(privateKey string) (string, error) { const aud = "https://www.googleapis.com/oauth2/v4/token" const scope = "https://www.googleapis.com/auth/cloud-platform" - privKey, err := getPrivateKey() + privKey, err := getPrivateKey(privateKey) if err != nil { return "", err @@ -62,7 +77,7 @@ func generateJWT() (string, error) { token := jwt.New() _ = token.Set(jwt.AudienceKey, aud) - _ = token.Set(jwt.IssuerKey, viper.Get("client_email")) + _ = token.Set(jwt.IssuerKey, getServiceAccountProperty("ClientEmail")) _ = token.Set("scope", scope) _ = token.Set(jwt.IssuedAtKey, now.Unix()) _ = token.Set(jwt.ExpirationKey, now.Unix()) @@ -76,8 +91,8 @@ func generateJWT() (string, error) { return string(payload), nil } -//GenerateAccessToken generates a Google OAuth access token from a service account -func GenerateAccessToken() (string, error) { +//generateAccessToken generates a Google OAuth access token from a service account +func generateAccessToken(privateKey string) (string, error) { const tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token" const grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" @@ -89,7 +104,7 @@ func GenerateAccessToken() (string, error) { TokenType string `json:"token_type,omitempty"` } - token, err := generateJWT() + token, err := generateJWT(privateKey) if err != nil { return "", nil @@ -132,6 +147,25 @@ func GenerateAccessToken() (string, error) { return accessToken.AccessToken, nil } +func readServiceAccount(serviceAccountPath string) error { + content, err := ioutil.ReadFile(serviceAccountPath) + if err != nil { + return err + } + + err = json.Unmarshal(content, &account) + if err != nil { + return err + } + return nil +} + +func getServiceAccountProperty(key string) (value string) { + r := reflect.ValueOf(&account) + field := reflect.Indirect(r).FieldByName(key) + return field.String() +} + func readAccessToken() error { usr, err := user.Current() if err != nil { @@ -215,18 +249,18 @@ func SetAccessToken() error { return fmt.Errorf("token expired: request a new access token or pass the service account") } if GetServiceAccount() != "" { - viper.SetConfigFile(GetServiceAccount()) - err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file + err := readServiceAccount(GetServiceAccount()) + if err != nil { // Handle errors reading the config file return fmt.Errorf("error reading config file: %s", err) } - if viper.Get("private_key") == "" { + privateKey := getServiceAccountProperty("PrivateKey") + if privateKey == "" { return fmt.Errorf("private key missing in the service account") } - if viper.Get("client_email") == "" { + if getServiceAccountProperty("ClientEmail") == "" { return fmt.Errorf("client email missing in the service account") } - _, err = GenerateAccessToken() + _, err = generateAccessToken(privateKey) if err != nil { return fmt.Errorf("fatal error generating access token: %s", err) } diff --git a/cmd/root.go b/cmd/root.go index 7d7fba54..f6503cd6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/srinandan/apigeecli/apiclient" "github.com/srinandan/apigeecli/clilog" "github.com/srinandan/apigeecli/cmd/apis" @@ -58,11 +57,9 @@ func init() { RootCmd.PersistentFlags().StringVarP(apiclient.GetApigeeTokenP(), "token", "t", "", "Google OAuth Token") - _ = viper.BindPFlag("token", RootCmd.PersistentFlags().Lookup("token")) RootCmd.PersistentFlags().StringVarP(apiclient.GetServiceAccountP(), "account", "a", "", "Path Service Account private key in JSON") - _ = viper.BindPFlag("account", RootCmd.PersistentFlags().Lookup("account")) RootCmd.PersistentFlags().BoolVar(apiclient.SkipCache(), "skipCache", false, "Skip caching Google OAuth Token") @@ -91,9 +88,7 @@ func init() { } func initConfig() { - viper.SetEnvPrefix("APIGEE") - viper.AutomaticEnv() // read in environment variables that match - viper.SetConfigType("json") + } // GetRootCmd returns the root of the cobra command-tree. diff --git a/cmd/token/cachetk/cachetk.go b/cmd/token/cachetk/cachetk.go index 2cb5d1bf..efd08237 100644 --- a/cmd/token/cachetk/cachetk.go +++ b/cmd/token/cachetk/cachetk.go @@ -37,8 +37,8 @@ var Cmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { clilog.Init(apiclient.IsSkipLogInfo()) - token, err := apiclient.GenerateAccessToken() - fmt.Printf("Token %s cached\n", token) + err := apiclient.SetAccessToken() + fmt.Printf("Token %s cached\n", apiclient.GetApigeeToken()) return err }, }