How to securely access Google APIs as a target from Apigee X

Introduction

Over the past few months, I've worked with a number of customers who want to use the Apigee X platform in front of Google APIs. 

Their main objectives are:

  • Allow the creation of client applications that must access Google APIs via a developer portal (and avoid the creation of these applications directly in Google Cloud)
  • Use Apigee X as an entry point for all API calls from client applications
  • Benefit from analytics directly from the Apigee X platform
  • Monetize certain API products based on the importance of data retrieved through Google APIs

In this article, I discuss how to securely access Google APIs as a target from an Apigee X runtime. I present possible solutions based on the Google Cloud Platform (GCP) capabilities proposed to access Google APIs.

Specifically, I deal with the following three possibilities:

  • Accessing Google APIs using public endpoints
  • Accessing Google APIs using Private Google Access (PGA)
  • VPC Service Control (VPC-SC) and restricted access to Google APIs

This is the reason why In the rest of this article I mention the following three modes to consume Google APIs:

  • Public Endpoint
  • Private Google Access (PGA)
  • Restricted (VPC-SC)

The Big Picture

joel_gauci_0-1655201453200.png

Google APIs are consumed using OAuth2.0 access tokens delivered by Google Identity provider (Idp).

This token is based either on end-user authentication or on a valid service account. In both cases the access token must have the required scopes to access Google APIs.

As a summary, the token handling from Apigee X consists in one of the following solutions: 

  • Generating a user token (OAuth2.0 access token) from Google IdP and hiding that token behind an Apigee token
  • Creating a service account for Apigee X to use while accessing the Google APIs

I present both solutions in the next chapter of this article. 

From a network perspective, Google APIs can be reached in three different ways:

  1. Via public internet (default)
  2. Private Google Access
  3. VPC-SC

In case of Private Google Access and VPC-SC, the API traffic is routed (from the Apigee X VPC) to take a private internal path to the Google APIs

I present these three different solutions later in this article.

Secured Access to Google APIs

In this chapter, I present two solutions that can be used to securely access the Google APIs from Apigee X:

  • Generating a user token from Google IdP
  • Creating a GCP service account 

Google Cloud APIs support two types of principals: user accounts and service accounts:

  • User accounts are managed as Google Accounts, and they represent a developer, administrator, or any other person who interacts with Google Cloud. They are intended for scenarios where your application needs to access resources on behalf of a human user. A good example is about interaction with files through Google Drive APIs.
  • Service accounts are managed by Identity & Access Management (IAM), and they represent non-human users. They are intended for scenarios where your application needs to access resources or perform actions on its own, such as executing the translation of a sentence from French to English or interacting with Compute Engine instances.

Generating a user access token from Google IdP

To access Google APIs from an Apigee X runtime, we need a valid access token that can be generated using the identity facade

The facade's identity provider (IdP) is in this case Google.

Therefore the value of the IDP_DISCOVERY_DOCUMENT, as defined in the Apigee Devrel repo documentation, is: https://accounts.google.com/.well-known/openid-configuration 

We also need a valid client Id and secret, defined as OAuth client ID credentials in the Google Cloud Platform. Apigee (identity facade API Proxy) uses these credentials to securely connect to the Google IdP. 

Here is a link you can follow to create such credentials. The client ID and client Secret you get as a result can be used to set the two following environment variables, required by the identity facade deployment script (pipeline.sh)

export TEST_IDP_APIGEE_CLIENT_ID=<OAuth credentials/client ID>
export TEST_IDP_APIGEE_CLIENT_SECRET=<OAuth credentials/client Secret>

At this step, you can deploy the identity facade on your Apigee X, hybrid or Edge runtime.

The output of the script consists in an https URL on the freshly deployed /authorize endpoint with the right Apigee client ID (aka "consumer key") as well as the other required or recommended query parameters:

  • client_id
  • state
  • response_type (=code for the OIDC authorization code flow) 
  • redirect_uri (=https://httpbin.org/get : you can change this default value on the identityApp application created on your Apigee organization)
  • scope
  • prompt (=consent)

Here is the format of the request that can be used to invoke the /authorize endpoint of the identity facade proxy:

https://${APIGEE_X_HOSTNAME}/v1/oauth20/authorize?client_id=xxx&state=xxx&redirect_uri=https://httpbin.org/get&prompt=consent&response_type=code&scope=email profile openid

 In this example we consider that the Proof Key for Code Exchange (PKCE) mode has not been enabled on the Apigee identity facade. If you want to enable the PKCE mode, set the value of the flow.pkce.enabled variable to true on the AM-SetPKCEMode policy and redeploy the identity-facade proxy, as shown on the following  picture:

joel_gauci_1-1655201850672.png

The scope parameter must be defined according to the Google APIs that you want to access.

As an example, here is the exhaustive list of the Google Drive V3 API scopes:

https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/drive.photos.readonly https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.scripts

The client application requests consent for a given set of API scopes, and Apigee (identity facade) can limit which clients can request which scopes. Do not forget to include openid in the list of scopes when invoking the /authorize endpoint: this indicates to the Google IdP that it must return an ID Token to the identity facade, once the end-user has been authenticated and once he/she has given his/her consent for the client application to access APIs. This ID Token contains an OAuth2.0 access token that is used to invoke the backend API.

Interactions between an end-user, client app, Apigee (identity facade) and an Identity Provider are detailed in the Apigee Devrel documentation of the identity facade.

At the end of the OIDC flow, you get an access token, which has been created by Apigee (identity-facade proxy) and that can be verified on API proxies on Apigee.

Once the verification of this token has been processed, it is possible to get the access token generated by the Google Idp and inject it into a new authorization header to a Google API.

During the OIDC flow, the identity facade is responsible for setting the Google IdP access token as an attribute of the Apigee access token. 

The Google IdP access token can be found in the following context variable: accesstoken.idp.access_token

The sanitization of the Apigee bearer token (input authorization header) and injection of a new authorization header can be achieved using the following Assign Message policy:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage continueOnError="false" enabled="true" name="AM-SetAuthorizationBearer">
    <!-- remove the Apigee Bearer Token (AZ header) -->
    <Remove>
        <Headers>
            <Header name="Authorization"/>
        </Headers>
    </Remove>
    <!-- inject the Google IdP AZ header -->
    <Set>
        <Headers>
            <Header name="Authorization">Bearer {accesstoken.idp.access_token}</Header>
        </Headers>
    </Set>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>

Generating an access token using a Service Account

A service account is another principal (or identity) that can be granted access to a resource. 

On Apigee X and hybrid, you can define a Service Account (SA) at the API Proxy Deployment level. This SA is used to generate an access token that can be used to access Google APIs.

As an example, if you want - from an Apigee API proxy - to access Cloud Translation APIs to translate texts, you must create a Google service account in the same GCP project where your Apigee organization was created. You must provide this service account's name when you deploy an API proxy that is configured to use Google authentication, and the OAuth2.0 tokens that are generated will represent the service account.

For example, if you want to allow your service account to translate text, assign the role roles/cloudtranslate.user to it. 

Here is an example of how to do it using gcloud commands:

# Create a service account
gcloud iam service-accounts create sa4googletranslate \
--project "$PROJECT_ID"

# grant the service account the role roles/cloudtranslate.user on your project
gcloud iam service-accounts add-iam-policy-binding "$PROJECT_ID" \
--member serviceAccount:sa4googletranslate@"$PROJECT_ID".iam.gserviceaccount.com \
--role roles/cloudtranslate.user

Once this service account has been created, you can specify it when deploying the API proxy that needs to use it. 

You can do this from the Web UI or using the Apigee APIs, as detailed here (cf. serviceAccount query parameter).

Another way to deploy an API proxy is to use the Apigee sackmesser tool: the "--deployment-sa" option must be used in this case to set the GCP Service Account to associate with the deployment.

At the TargetEndpoint level of your API Proxy, you need to configure the Google authentication that is used to mediate the service account into a valid access token.

Here is an example of such configuration when working with Cloud Translation APIs:

<TargetEndpoint name="default">
...
<HTTPTargetConnection>
   <Authentication>
      <GoogleAccessToken>
        <Scopes>
          <Scope>https://www.googleapis.com/auth/cloud-translation</Scope>
        </Scopes>
      </GoogleAccessToken>
    </Authentication>
    <URL>https://translation.googleapis.com/language/translate/v2</URL>
</HTTPTargetConnection>
...
</TargetEndpoint>

To learn more about API proxies authentication configuration options, please refer to the following documentation page.

If you want to discover more about authentication strategies on Apigee, please refer to the following documentation page.

Getting the IP addresses of Google APIs

To demonstrate that the IP address of the Google APIs you reach is public, private or restricted, we recommend to use the following AssignMessage policy on the API proxies that you are using for accessing Google APIs:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage continueOnError="false" enabled="true" name="AM-IPAddr-GoogleAPI">
    <Add>
        <Headers>
            <Header name="x-target-ip">{target.ip}</Header> <!-- trace the IP @ of the target/Google API -->
        </Headers>
    </Add>
</AssignMessage>

This policy is used for checking the IP address of the backend API: it's not required or recommended to do this in production.

This policy must be added at the TargetEndpoint level of your API Proxies, on the response flow (Preflow or Postflow).

When executing your API, the response provides the IP address used to access the Google API. In the following example the Google API is translation.googleapis.com:

joel_gauci_0-1655202495843.png

You can get more information  (company, range, hostname…) about the IP address you get  in the response, using the website https://ipinfo.io/<IP_ADDRESS>, where IP_ADDRESS is the value of the target.ip context variable (or x-target-ip response header).

Public Access to Google APIs

In this chapter, we present the default behavior when using Apigee, which consists in accessing Google APIs using public endpoints.

The target endpoint that we use in this case is https://www.googleapis.com/drive/v3  as we want to access Google Drive APIs.

The documentation related to the Google Drive APIs can be found here.

First we generate a user token (access token) using the identity-facade API Proxy, as presented in the previous chapter Secured Access to Google APIs.

For testing purposes, we use a scope that allows us to see information about a user's Google Drive files. This scope is: https://www.googleapis.com/auth/drive.metadata.readonly 

If you are using the identity-facade from the Apigee Devrel, then you can access the /authorize endpoint using a web browser and an URL with the following format:

https://${APIGEE_HOSTNAME}/v1/oauth20/authorize?client_id=<client_id>&redirect_uri=https://httpbin.org/get&scope=https://www.googleapis.com/auth/drive openid&state=abcd-1234&response_type=code

The client_id is the consumer key of a client application defined on Apigee. This application is part of the identity facade reference (for testing purposes only) and is named identityApp.

The consumer key of this application has the prefix "xkey-" and its consumer secret is "xsecret".

 After authenticating as a Google user and giving your consent to the third party application to access your Google Drive with the scope provided, the authorization code is redirected to the callback endpoint of the identity facade. Copy this value and paste it into the following POST request that you have to execute on the token endpoint, to get a valid access token from Apigee:

export APIGEE_ACCESS_TOKEN=$(curl https://${APIGEE_HOSTNAME}/v1/oauth20/token \
-H "Authorization: Basic base64(client_id:client_secret)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'code=<authorization_code>&scope=https://www.googleapis.com/auth/drive openid&grant_type=authorization_code&redirect_uri=https://httpbin.org/get&state=abcd-1234' \
-v | jq -r .access_token)

 You can use the access token you received to request an API Proxy (let's call it drive-api-v1) configured with the target connection URL https://www.googleapis.com/drive/v3

On the drive-api-v1 API Proxy:

Access the API Proxy using the following request:

curl https://${APIGEE_HOSTNAME}/drive/v1/files \
-H "Authorization: Bearer ${APIGEE_ACCESS_TOKEN}" \
-v

Using the -v or --verbose option of the cURL command, you can see the x-target-ip response header. Its value is the IP address of the Google Drive APIs. Note that the IP address of the Google Drive APIs is public. This is the default behavior when accessing Google APIs from Apigee.

Accessing Google APIs using Private Google Access

In this chapter we are going to configure GCP for the drive-api-v1 API Proxy to access Google APIs using Private Google Access (PGA).

Private Cloud DNS on the consumer project

After enabling the Cloud DNS API, we configure a private Cloud DNS on the consumer project. In our example, the name of the private zone is googleapis-zone:

export NETWORK=apigeex-consumer-vpc

gcloud services enable dns.googleapis.com

gcloud dns managed-zones create googleapis-zone \
--description="googleapis zone" \
--dns-name=googleapis.com \
--networks=$NETWORK \
--visibility=private

We start a DNS zone editing transaction:

gcloud dns record-sets transaction start --zone=googleapis-zone

A transaction.yaml file will be created wherever you run this command. Further commands will edit this file, and then finally we will execute this file to push the changes up to the gcloud command.

We start adding the A record private.googleapis.com, which points to the private Google API range: 199.36.153.8/30:

gcloud dns record-sets transaction add --zone=googleapis-zone \
--name="private.googleapis.com" \
--ttl 300 \
--type A 199.36.153.8 199.36.153.9 199.36.153.10 199.36.153.11

Then, we add a CNAME (alias) for www.googleapis.com  to private.googleapis.com:

gcloud dns record-sets transaction add --zone=googleapis-zone \
--type=CNAME \
--name="www.googleapis.com" \
--ttl 300 \
"private.googleapis.com."

Finally we can execute those changes:

gcloud dns record-sets transaction execute --zone=googleapis-zone

And once they have been pushed we can list them, using the following command:

gcloud dns record-sets list --zone=googleapis-zone

The response provides details about the CNAME and A records.

DNS Peering 

On the Apigee X runtime, we want www.googleapis.com to be resolved as private.googleapis.com. For this, we need to set a DNS peering between the Apigee X VPC and the consumer VPC (referred as $NETWORK )

gcloud services peered-dns-domains create googleapis-domain \
--network=$NETWORK \
--dns-suffix=www.googleapis.com.

DNS Peering is done at the network level so you need to ensure that any other service that is used from an API Proxy in Apigee and which also points to www.googleapis.com  also supports PGA.

Private Internal Path to Google APIs

On the consumer VPC, the traffic to private.googleapis.com is automatically routed to take a private internal path to the Google APIs.

You do not need to create a custom route on your VPC but the Private Google Access feature must be enabled on the VPC subnet.

You can enable PGA on a VPC subnet ($SUBNET) on the GCP region ($REGION,) using the following gcloud command:

gcloud compute networks subnets update $SUBNET \
--region=$REGION \
--enable-private-ip-google-access

Testing the PGA Configuration

Now, we can generate a new token (as it has probably expired!) and make a test to access the Google Drive APIs exposed on Apigee through the drive-api-v1 API Proxy, as shown on the following picture:

joel_gauci_0-1655212944520.png
We can attest the fact that our Apigee X instance uses PGA to access Google Drive APIs.

VPC Service Control

The last topic we discuss in this article is VPC Service Control (VPC-SC). It is another option to invoke Google APIs using restricted access, from an Apigee X runtime.

It is important to note that all public Google APIs are not accessible once VPC SC has been enabled on an Apigee X tenant project. Public APIs are not accessible because the Apigee tenant no longer has internet egress. VPC Service Controls supports the products listed in the Google Cloud documentation.

Coming back to our previous example, Google Drive APIs cannot be accessed once VPC-SC has been enabled on Apigee X, which is not the case for Cloud Translation APIs. This is the reason why we focus our example on the Cloud Translation APIs in the rest of this chapter.

Enabling VPC-SC on the Apigee X tenant project is completed using the following gcloud command:

gcloud services vpc-peerings enable-vpc-service-controls \
--network=${NETWORK} \
--service=servicenetworking.googleapis.com \
--project=${PROJECT_ID}

The documentation related to Apigee X/hybrid and VPC-SC can be found here.

At this point you do not need to apply any other configuration as Google APIs will be automatically reached from the restricted IP range 199.36.153.4/30. This - of course - also includes the Apigee APIs (apigee.googleapis.com).

Testing the VPC-SC Configuration

Testing the VPC-SC configuration consists in creating a passthrough API proxy as described in Generating an access token using a Service Account.

This API Proxy has the following properties:

  • Name: translate-api-v1
  • Base path: /translate/v1
  • Target Endpoint URL: https://translation.googleapis.com/language/translate/v2
  • Service Account: a service account used when deploying the API Proxy. This service account is in the same Google Cloud project where your Apigee organization was created. As you want to use Google Cloud Translation API to translate text from one language to another, you need to set the role roles/cloudtranslate.user on this service account.

Here is the cURL command used to test the API:

curl https://${APIGEE_HOSTNAME}/translate/v1 \
-d '{"q":"Bonjour","source":"fr","target":"en","format":"text"}' \
-H "Content-type: application/json" \
-v

You should notice now that the target.ip context variable (or x-target-ip response header) has a value in the 199.36.153.4/30 IP range, as shown on the debug trace of the translate-api-v1 API Proxy:

joel_gauci_1-1655213365521.png

Thanks a lot to Daniel Strebel & Omid Tahouri for their precious feedback on drafts of this article!

Contributors
Version history
Last update:
‎06-14-2022 09:41 PM
Updated by: