diff --git a/README.md b/README.md index b79520a05..189197ee8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ curl -L https://raw.githubusercontent.com/apigee/apigeecli/main/downloadLatest.s NOTE: The signature is not verified and the original zip is not preserved. +
+ Signature Verification +### Signature Verification To test the signature of the binary, import the gpg public key: ```sh @@ -39,6 +42,8 @@ gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 72D1 1E3A 3B1E 9FE2 2110 EC45 A714 872F 32F3 4390 ``` +
+ ## Getting Started ### User Tokens @@ -50,23 +55,15 @@ token=$(gcloud auth print-access-token) apigeecli orgs list -t $token ``` -### Set Preferences -If you are using the same GCP project for Apigee, then consider setting up preferences so they don't have to be included in every command. Preferences are written to the `$HOME/.apigeecli` folder - -``` -project=$(gcloud config get-value project | head -n 1) - -apigeecli prefs set -o $project -``` +### Metadata OAuth2 Access Tokens -Subsequent commands can be like this: +If you are using `apigeecli` on Cloud Shell, GCE instances, Cloud Build, then you can use the metadata to get the access token -``` -token=$(gcloud auth print-access-token) -apigeecli orgs get -t $token #fetches the org details of the org set in preferences +```sh +apigeecli orgs list --metadata-token ``` -### Access Token Generation +### Access Token Generation from Service Accounts `apigeecli` can use the service account directly and obtain an access token. @@ -95,6 +92,27 @@ token=$(gcloud auth print-access-token) apigeecli token cache -t $token ``` +or +```bash +apigeecli token cache --metadata-token +``` + +## Set Preferences +If you are using the same GCP project for Apigee, then consider setting up preferences so they don't have to be included in every command. Preferences are written to the `$HOME/.apigeecli` folder + +``` +project=$(gcloud config get-value project | head -n 1) + +apigeecli prefs set -o $project +``` + +Subsequent commands can be like this: + +``` +token=$(gcloud auth print-access-token) +apigeecli orgs get -t $token #fetches the org details of the org set in preferences +``` + ## Container download The lastest container version for apigeecli can be downloaded via diff --git a/cmd/root.go b/cmd/root.go index 384f8cabd..f53a52bc9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -63,8 +63,18 @@ var RootCmd = &cobra.Command{ Short: "Utility to work with Apigee APIs.", Long: "This command lets you interact with Apigee APIs.", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - apiclient.SetServiceAccount(serviceAccount) - apiclient.SetApigeeToken(accessToken) + if metadataToken && (serviceAccount != "" || accessToken != "") { + return fmt.Errorf("metadata-token cannot be used with token or account flags") + } + + if serviceAccount != "" && accessToken != "" { + return fmt.Errorf("token and account flags cannot be used together") + } + + if !metadataToken { + apiclient.SetServiceAccount(serviceAccount) + apiclient.SetApigeeToken(accessToken) + } if !disableCheck { if ok, _ := apiclient.TestAndUpdateLastCheck(); !ok { @@ -78,6 +88,10 @@ var RootCmd = &cobra.Command{ } } + if metadataToken { + return apiclient.GetDefaultAccessToken() + } + _ = apiclient.SetAccessToken() return nil @@ -93,8 +107,8 @@ func Execute() { } var ( - accessToken, serviceAccount string - disableCheck, printOutput, noOutput bool + accessToken, serviceAccount string + disableCheck, printOutput, noOutput, metadataToken bool ) const ENABLED = "true" @@ -117,6 +131,9 @@ func init() { RootCmd.PersistentFlags().BoolVarP(&noOutput, "no-output", "", false, "Disable printing all statements to stdout") + RootCmd.PersistentFlags().BoolVarP(&metadataToken, "metadata-token", "", + false, "Metadata OAuth2 access token") + RootCmd.AddCommand(apis.Cmd) RootCmd.AddCommand(org.Cmd) RootCmd.AddCommand(sync.Cmd) diff --git a/internal/apiclient/options.go b/internal/apiclient/options.go index c2aa21ca3..7f32584d7 100644 --- a/internal/apiclient/options.go +++ b/internal/apiclient/options.go @@ -41,6 +41,7 @@ type ApigeeClientOptions struct { PrintOutput bool // prints output from http calls NoOutput bool // Disable all statements to stdout ProxyUrl string // use a proxy url + MetadataToken bool // use metadata outh2 token APIRate Rate // throttle api calls to Apigee } diff --git a/internal/apiclient/token.go b/internal/apiclient/token.go index 2a7b80e6a..7f9deecf4 100644 --- a/internal/apiclient/token.go +++ b/internal/apiclient/token.go @@ -259,3 +259,82 @@ func SetAccessToken() error { } return fmt.Errorf("token expired: request a new access token or pass the service account") } + +func getMetadata(metadata string) (respBpdy []byte, err error) { + var req *http.Request + + metadataURL := fmt.Sprintf( + "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/%s", metadata) + + err = GetHttpClient() + if err != nil { + return nil, err + } + + if DryRun() { + return nil, nil + } + + clilog.Debug.Println("Connecting to: ", metadataURL) + + req, err = http.NewRequest(http.MethodGet, metadataURL, nil) + if err != nil { + clilog.Error.Println("error in client: ", err) + return nil, err + } + + req.Header.Set("Metadata-Flavor", "Google") + resp, err := ApigeeAPIClient.Do(req) + if err != nil { + clilog.Error.Println("error connecting: ", err) + return nil, err + } + + if resp != nil { + defer resp.Body.Close() + } + + if resp == nil { + clilog.Error.Println("error in response: Response was null") + return nil, fmt.Errorf("error in response: Response was null") + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + clilog.Error.Println("error in response: ", err) + return nil, err + } else if resp.StatusCode > 399 { + clilog.Debug.Printf("status code %d, error in response: %s\n", resp.StatusCode, string(respBody)) + clilog.HttpError.Println(string(respBody)) + return nil, errors.New(getErrorMessage(resp.StatusCode)) + } + return respBody, err +} + +// GetDefaultAccessToken +func GetDefaultAccessToken() (err error) { + var tokenResponse map[string]interface{} + + respBody, err := getMetadata("token") + if err != nil { + return err + } + + err = json.Unmarshal(respBody, &tokenResponse) + if err != nil { + return err + } + + SetApigeeToken(tokenResponse["access_token"].(string)) + + ClientPrintHttpResponse.Set(false) + defer ClientPrintHttpResponse.Set(GetCmdPrintHttpResponseSetting()) + + respBody, _ = getMetadata("email") + clilog.Debug.Println("service token email: ", string(respBody)) + + respBody, _ = getMetadata("scopes") + clilog.Debug.Println("scopes: ", string(respBody)) + + return nil +}