Integrating Salesforce CRM with protected Cloud Functions in enterprises

This tutorial shows you an approach to helping secure Cloud Functions that are invoked from services hosted outside of Google Cloud. Common use cases include invoking Cloud Functions from software-as-a-service (SaaS) apps such as customer relationship management (CRM) systems, partner systems, or consumer-facing web apps.

This tutorial is intended for architects, product owners, and IT professionals. It describes how to invoke a Cloud Function from an external source, and it also discusses common use cases for employing Cloud Functions.

The tutorial assumes you are familiar with Cloud Functions, Salesforce, Node.js, and REST APIs.

Objectives

  • Deploy a Cloud Function that can be invoked by an external service.
  • Control access to the function using Cloud Identity and Access Management permissions.
  • Construct a JSON Web Token (JWT) request and then invoke the Cloud Function using ID tokens from external services.
  • Verify that only authorized services can access the protected function.

Costs

This tutorial uses the following billable components of Google Cloud:

  • Cloud Functions

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Cloud Console, on the project selector page, select or create a Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Google Cloud project. Learn how to confirm billing is enabled for your project.

  4. Enable the Cloud Functions API.

    Enable the API

Introduction

Cloud Functions provides a way for you to run your code on Google Cloud and invoke the functions on demand or as a response to events.

Enterprises often have a wide array of platforms and apps in their technology ecosystem. Integrating these platforms and the business data that these platforms hold is usually key to business agility. Securing and safeguarding enterprise data and the movement of data across apps is critical to organizations. When you integrate your enterprise apps (such as the Salesforce CRM) with Cloud Functions, you should take steps to secure the invocation of your functions in order to control how and by whom these functions are invoked.

There are many benefits to a well-designed access control mechanism. It helps reduce the attack surface and employs the principles of least privilege. It also establishes security and control mechanisms and helps make costs more predictable because you are able to limit the list of permissible invokers.

To learn about adding access control to Cloud Functions, in this tutorial you see how to invoke a protected Cloud Function from the Salesforce CRM. The function is invoked when a new customer record is created in the Salesforce CRM platform and is posted to the system of record (a customer master) in Google Cloud for processing. Although this tutorial focuses on using the Salesforce CRM, the approach used in this solution can be applied to calling Cloud Functions from other SaaS apps.

For simplicity, the Cloud Function that you use is provided in a GitHub repository.

Architecture

The following figure outlines the architecture and the key steps for invoking a protected Cloud Function from an external service. You can deploy instances of the Cloud Function privately so that only authorized users and services can invoke these functions.

Architecture of this approach, showing the steps involved in getting authentication and calling the Cloud Function.

The steps in the diagram are as follows:

  1. The external Salesforce service requests an ID token using a signed JWT.
  2. If the JWT is valid, a signed ID token is returned from the Google Authorization server.
  3. The Salesforce service uses the ID token to make an API request to the protected Cloud Function.
  4. The Cloud Function container internally validates the ID token that's sent in the Authorization Bearer header by the Salesforce service.
  5. The Cloud Function is executed based on whether the token is valid:
    • If the token is valid, the Cloud Function code is executed and a success response is returned to the calling service. Other Google Cloud services can be invoked from the Cloud Function as needed.
    • If the token is not valid, an HTTP 401 or 403 status code response is returned and the Cloud Function code is not executed.

For the purposes of this tutorial, the external service calling the Cloud Function is hosted on the Salesforce. platform. Creating a customer record in the Salesforce CRM triggers the pipeline and invokes the steps outlined in the diagram. The Salesforce CRM has the Google Cloud service account's private key and performs the steps outlined in the diagram to call the protected cloud function. You use a Salesforce Apex class to do this.

When a customer record is created in the Salesforce CRM, an Apex trigger causes a queueable job in the Apex class (GCPManageCustomersService) to be run asynchronously.

Common use cases for invoking protected Cloud Functions

You can integrate Cloud Functions into your ecosystem, where they can serve as a building block in your end-to-end enterprise business process. You can use Cloud Functions for tasks such as the following:

  • Inserting business data into Google Cloud for storage and analytical processing, such as customer records from front-end CRM systems.

  • Creating or updating customer data held in backing stores such as Firestore and Cloud SQL when those stores serve as customer masters (that is, as the system of record).

  • Retrieving transactional data such as orders, service requests, service appointments, and product details from data stores on Google Cloud. You might do this in order to create customer 360-degree views in an SaaS platform such as Salesforce.

  • Transforming data files received from partner organizations that need to be parsed, processed, and then loaded into a data lake or data warehouse on Google Cloud.

  • Parsing consumer interaction data such as form submissions or image or document uploads to a website for generating insights using BigQuery and BigQuery ML.

In this tutorial, you implement the first use case—inserting business data into Google Cloud. When customer data is created in the Salesforce CRM, it's sent to Google Cloud by invoking a protected Cloud Function.

The new customer record can be processed as needed in Google Cloud. Some common patterns include inserting the customer record into a customer master for analytical processing, such as Cloud SQL, Firestore, or BigQuery. This tutorial doesn't include a section on how to process these records.

Initializing your environment

  1. From the Cloud Console, click Activate Cloud Shell.

    Open Cloud Shell

    You use Cloud Shell for all of the terminal commands in this tutorial.

  2. In Cloud Shell, set the project that you created or selected as the default project. Replace project-id with your Cloud project ID.

    gcloud config set project project-id
    
  3. Assign default settings for region and zone:

    gcloud config set run/region us-central1
    gcloud config set compute/zone us-central1-a
    

    In this tutorial, you use us-central1 as the default region and us-central1-a as the default zone.

Creating the protected Cloud Function

The first step is to create the Cloud Function that you invoke later.

Clone the source repository

  1. In Cloud Shell, clone the GitHub repository that contains the sample Cloud Function and Salesforce artifacts:

    git clone https://github.com/GoogleCloudPlatform/salesforce-cloud-functions-crm-tutorial
    
  2. Change to the directory that was created by cloning the repository:

    cd salesforce-cloudfunc-integration
    

Review code in the directory

The directory contains two directories, one called salesforce and another called manage-customer-func. The salesforce directory has the following Salesforce Apex code files:

  • CustomerAccounts.trigger file. This file contains code that triggers a queueable job in the GCPManageCustomersService class when a new customer account is created.
  • GCPManageCustomersService class. This file contains code to request a Google ID token in exchange for a generated JWT and invokes the Cloud Function.
  • Salesforce XML metadata files.

The manage-customer-func directory has the Node.js code files for the Cloud Function called secureFunction, along with the packaging files that outline the dependencies for the Cloud Function.

Create a service account for the external Salesforce service to invoke the function

It's a good practice to tightly manage access to services and resources deployed on Google Cloud by following the principle of least privilege. In this section, you create a service account for the external SaaS service that accesses the Cloud Function.

Console

  1. In the Cloud Console, go to the Service Accounts page.

    Go to the service accounts page

  2. Click Select a project and select the project you created for this tutorial.

  3. Click Create Service Account.

  4. Enter the service account name function-invoker-sa.

  5. Click Create.

  6. Select the Cloud Functions Invoker role for this service account and then click Save.

  7. Click Continue.

  8. Click Create Key, select P12 for Key Type, and then click Create.

    The P12 key file is downloaded to your local disk.

    Make a note of the default password generated by Google Cloud (normally notasecret).

  9. Click Done to complete the service account creation.

  10. In Cloud Shell, click More and select Upload File.

  11. To upload the file to Cloud Shell, select the project-id-NNNNNN.p12 key file that you downloaded earlier and click Open. (The string NNNNNN is a set of numbers that are generated as part of the key file name.)

  12. In Cloud Shell, go to your home directory:

    cd $HOME
    
  13. Rename the key file you just uploaded to use a friendlier name that you can use later in the tutorial:

    mv ${DEVSHELL_PROJECT_ID}-NNNNNN.p12 external-service-invoker-sa.p12
    

gcloud

  1. Go to your home directory:

    cd $HOME
    
  2. Create an environment variable to hold the name of the service account that you will create:

    export SERVICE_ACCOUNT_NAME=function-invoker-sa
    
  3. Create a service account:

    gcloud beta iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
        --display-name ${SERVICE_ACCOUNT_NAME}
    
  4. Generate a P12 key for the service account and download it:

    gcloud iam service-accounts keys create ~/external-service-invoker-sa.p12 \
        --key-file-type p12 \
        --iam-account ${SERVICE_ACCOUNT_NAME}@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com
    

    The P12 key is downloaded to your home ($HOME) directory in Cloud Shell. The output you see in Cloud Shell also shows you the location to which the key is downloaded.\

Deploy the Cloud Function

You deploy the Cloud Function as a protected function. Only users and service accounts that have been given permission to access it will be able to invoke the function. You set up Cloud IAM access in the next step to establish the permissions.

  1. In Cloud Shell, change to the manage-customer-func directory:

    cd manage-customer-func
    
  2. Deploy the function:

    gcloud functions deploy secureFunction \
        --runtime nodejs8 \
        --trigger-http
    
  3. Validate that the function has successfully deployed:

    gcloud functions describe secureFunction
    

    A successful deployment is indicated by a ready status similar to the following:

    status:  ACTIVE
    timeout:  60s
    httpsTrigger:
      url: https://us-central1-[PROJECT_ID].cloudfunctions.net/secureFunction
    

    Make a note of the httpsTrigger URL; you need it later.

Grant access to the deployed function

Next, you assign an appropriate role to the service account you created in order to access the secureFunction Cloud Function. You also remove the allUsers account that's automatically added. The result is that only the service account can invoke the Cloud Function.

console

  1. In the Cloud Console, open the Cloud Functions page.

    Go to the Cloud Functions page

  2. Select secureFunction in the list of functions.

  3. If the Info Panel is not open, click Show Info Panel in the toolbar.

  4. In the Permissions tab, click Add Members.

  5. In the New members box, type function-invoker-sa to search for the service account that needs access to your function, and then select the service account.

  6. In the Select a role list, select Cloud Functions, and then select the Cloud Functions Invoker role.

  7. Click Save.

  8. In the Permissions tab, expand the Cloud Functions Invoker role.

  9. In the row for the allUsers member, click the Delete button and then click Remove to confirm. This removes public access from this function.

gcloud

  1. Assign the Cloud Function Invoker role to the service account that you created in order to allow it to access the deployed function:

    gcloud beta functions add-iam-policy-binding secureFunction \
        --member=serviceAccount:${SERVICE_ACCOUNT_NAME}@${DEVSHELL_PROJECT_ID}.iam.gserviceaccount.com \
        --role=roles/cloudfunctions.invoker
    
  2. Remove the allUsers member from the Cloud Function Invoker role:

    gcloud beta functions remove-iam-policy-binding secureFunction \
        --member=allUsers \
        --role=roles/cloudfunctions.invoker
    

Creating a Salesforce service to invoke the protected function

In your Salesforce org, you need to setup the Apex code that's required in order to invoke the business process flow. You use the Salesforce CLI (SFDX) to deploy the Salesforce code and the required configuration into a scratch org. If you want, you can later promote these deployed changes to production environments.

The following list summarizes the tasks you need to perform in your Salesforce org. The detailed instructions in this section walk you through each task.

  1. Set up the Salesforce CLI.
  2. Create a scratch org and push the code from the cloned repository to that org.
  3. Create a Java Keystore (JKS) key from the exported Google Cloud service account P12 key and import the JKS key into the Salesforce service.
  4. Populate the custom metadata type object with data relevant to your Google Cloud setup. This includes the endpoint of the function to invoke, the service account email, and token endpoint that the Salesforce service can use to request an ID token.
  5. Review the created Apex class and make sure that the custom metadata type field names and JKS key name are correctly referenced in the Apex code.
  6. Create a customer record in the Salesforce CRM. This invokes the CustomerAccounts.trigger code, which starts the process to send the new customer record data to Google Cloud.

Set up the Salesforce CLI

In this section, you install the Salesforce CLI and set up authorization for it.

Install the Salesforce CLI

  1. In Cloud Shell, go to the home directory:

    cd $HOME
    
  2. Install the Salesforce CLI in your Cloud Shell session:

    wget https://developer.salesforce.com/media/salesforce-cli/sfdx-linux-amd64.tar.xz
    
  3. Uncompress the downloaded tar file:

    tar -xvf sfdx-linux-amd64.tar.xz
    
  4. Add the Salesforce binary to the path:

    export PATH="$PATH:${HOME}/sfdx-cli-v7.8.1-8f830784cc-linux-x64/bin"
    

    You are now able to execute commands using the Salesforce CLI.

  5. Verify that the CLI has been set up correctly:

    sfdx
    

    The output is as follows:

    Terminal listing showing the output of the sfdx command, including version, usage, commands, and topics.

    You see the version information and a list of commands.

  6. From Setup in your Salesforce org, in the Quick Find box, enter Dev Hub, and then select Dev Hub:

    Searching for 'Dev' in the Salesforce dashboard.

  7. Enable Dev Hub:

    Enabling Dev Hub using the 'Dev Hub' toggle switch.

    This step enables you to set up a scratch org to deploy the code in the tutorial.

  8. In Cloud Shell, generate a certificate and key pair to authenticate to your Salesforce org from the SFDX CLI:

    openssl req -x509 -sha256 -nodes -days 36500 -newkey \
       rsa:2048 -keyout salesforce.key -out \
       salesforce.crt
    

    You are prompted to enter details to identify the certificate. For this tutorial, these values aren't important, so press Enter to accept the defaults.

  9. In Cloud Shell, click More and select Download File.

  10. In the Fully qualified file path box, enter the following filename and then click Download:

    salesforce.crt
    

    This downloads the certificate that you generated to your local machine. You upload the certificate to your Salesforce org in the next procedure.

Create a connection app in the Salesforce (Dev Hub) org to authorize the Salesforce CLI

  1. Depending on your Salesforce org edition, use one of the following steps to create a connected app:

    • In Lightning Experience, use the App Manager to create connected apps. From Setup in your Salesforce org, in the Quick Find box, enter App, and then select App Manager. Click New Connected App.
    • In Salesforce Classic, from Setup in your Salesforce org, in the Quick Find box, enter Apps, and then select Apps (under Build, then Create). In the Connected Apps pane, click New.
  2. Enter the contact email information and any other information appropriate for your app.

  3. Select Enable OAuth Settings.

  4. For the Callback URL value, enter http://localhost:1717/OauthRedirect.

  5. To enable the option to use digital signatures, click Choose File, and then select the salesforce.crt certificate file that you downloaded earlier.

    This certificate serves as the public key that's used to authenticate and connect the SFDX client running in a Cloud Shell session to the Salesforce Dev Hub org.

  6. Add the following OAuth scopes to Selected OAuth Scopes by clicking them and then clicking the Add arrow to select them:

    • Access and manage your data (api)
    • Perform requests on your behalf at any time (refresh_token, offline_access)
    • Provide access to your data via the web (web)

      OAuth scopes selected in the Salesforce dashoard.

  7. Click Save.

  8. Make a note of the Consumer Key that's displayed.

  9. Click Manage and then click Edit Policies to change OAuth policies.

  10. Set Permitted users to Admin Approved Users are Pre-Authorized and confirm the choice.

  11. Set IP relaxation to Relax IP Restrictions.

  12. Click Save.

  13. Click Manage Profiles and add the System Administrator profile.

    This lets users who assume this profile log in to the SFDX CLI.

  14. Click Save.

Complete the authorization steps for the Salesforce CLI

  1. In Cloud Shell, create an environment variable to hold the Consumer Key created by the Connected App in the previous step:

    export CONSUMER_KEY=your-consumer-key
    

    Replace your-consumer-key with your consumer key.

  2. Create an environment variable to hold the username of the Salesforce org you created earlier in this tutorial:

    export SALESFORCE_USERNAME=your-salesforce-username
    

    Replace your-salesforce-username with your Salesforce username.

  3. In Cloud Shell, authenticate to your Salesforce org using a JWT grant:

    sfdx force:auth:jwt:grant \
        -u ${SALESFORCE_USERNAME} \
        -f $(pwd)/salesforce.key -i ${CONSUMER_KEY}
    

    You see a message saying that you have been authorized.

    For more information on authorizing your org using the JWT-based flow, see Authorize an Org Using the JWT-Based Flow in the Salesforce documentation.

Push the Salesforce metadata to a scratch org

  1. In Cloud Shell, go to the directory of the cloned repository:

    cd salesforce-cloudfunc-integration
    
  2. Create a scratch org to test the repository that you cloned for this tutorial.

    sfdx force:org:create \
        --setdefaultusername \
        --definitionfile salesforce/config/project-scratch-def.json \
        --targetdevhubusername ${SALESFORCE_USERNAME} \
        --setalias gcp-func-test-scratch-org
    

    It's a good practice to use a scratch org for this purpose, which provides a safe space to test new packages and cloned repositories.

  3. Go to the salesforce subdirectory:

    cd salesforce
    
  4. Push the metadata and code to the scratch org:

    sfdx force:source:push
    
  5. Generate a URL for the scratch org:

    sfdx force:org:open
    
  6. Click the generated URL to go to the scratch org in your browser.

Create a JKS keystore file containing the exported P12 key

Salesforce certificates and key pairs are used for authenticated SSL communication between the Salesforce service and external websites and servers. The Salesforce service has to securely store the Google Cloud service account P12 key generated that you generated earlier in this tutorial. Keys in the Salesforce service must be stored in JKS (Java KeyStore) format.

  1. In Cloud Shell, add the P12 key to a JKS keystore file called salesforce.jks in your home directory:

    keytool \
        -importkeystore \
        -srckeystore $HOME/external-service-invoker-sa.p12 \
        -destkeystore $HOME/salesforce.jks \
        -srcstoretype pkcs12 \
        -srcstorepass notasecret \
        -deststorepass notasecret \
        -deststoretype jks \
        -destalias google_cloud \
        -srcalias privatekey
    
  2. In Cloud Shell, click More and then click Download File.

  3. In the Fully qualified file path box, enter the following filename and then click Download:

    salesforce.jks
    

    This downloads the keystore you generated to your local computer.

Import the JKS file into your Salesforce scratch org

You can now complete the steps to enable the Salesforce.com identity provider and upload the JKS key.

  1. From Setup in your Salesforce org, in the Quick Find box, enter Identity, and then click Identity Provider to navigate to the Identity Provider setup page.

    Navigating to the identity provider setup page in the Salesforce dashboard.

  2. Click Enable Identity Provider and then click Save to accept the certificate.

  3. From Setup, in the Quick Find box, enter Certificate, and then select Certificate and Key Management.

    Navigating to the certification and key management page in the Salesforce dashboard.

  4. Click Import from Keystore.

  5. Click Choose File and select the JKS file that you downloaded earlier.

  6. In the Keystore Password box, enter notasecret.

  7. Click Save.

  8. Make sure that the certificate label is called google_cloud. The label is important, because the Apex class references the certificate using this name.

Create custom metadata type object records

Pushing the repository using the Salesforce CLI creates a custom metadata type object for you. You need to populate details in that object by creating a record as shown in this section. The record is referenced in the Apex class and used to map relevant metadata to the Cloud Function that you call from the Salesforce service.

  1. From Setup in your Salesforce scratch org, in the Quick Find box, enter Custom metadata, and then select Custom Metadata Types.

    You see a custom metadata type called GCP Key.

  2. Next to GCP Key, click Manage Records.

  3. Click New to create a new record and then enter the following:

    1. Label. Enter GCP Function Invoker.
    2. Client Email. Enter the address of the service account you created in the following format:

      function-invoker-sa@project-id.iam.gserviceaccount.com.

      Replace project-id with the ID of your Cloud project for this tutorial.

    3. Function Audience Endpoint. Enter the URL of the httpsTrigger endpoint of the Cloud Function:

      https://us-central1-project-id.cloudfunctions.net/secureFunction
      
    4. Token Endpoint. Enter https://www.googleapis.com/oauth2/v4/token

      Setting the token endpoint in the Salesforce dashboard.

  4. Click Save.

Create a remote site setting in your Salesforce scratch org

You add a remote site setting to make Apex callouts to the Google Cloud Cloud Function and token endpoints from your Salesforce scratch org.

  1. From Setup in your Salesforce org, in the Quick Find box, enter Remote, and then select Remote Site Settings.
  2. Click New Remote Site.
  3. For Remote Site Name, enter GCPCloudFunction.
  4. For Remote Site URL, enter the httpsTrigger endpoint of the Cloud Function:

    https://us-central1-project-id.cloudfunctions.net/secureFunction
    
  5. Click Save.

  6. Create another remote site setting by clicking New Remote Site.

  7. For Remote Site Name, enter GCPToken.

  8. For Remote Site URL, enter https://www.googleapis.com.

    Setting the remote site name and URL in the Salesforce dashboard.

  9. Click Save.

Testing the Cloud Function

To test the Cloud Function, you run two tests:

  • Trigger the Salesforce service to invoke the Cloud Function using a valid token.
  • Invoke the Google Cloud Cloud Function with no token. This verifies that the Cloud Function can't be called unless you're authenticated.

Invoke the Cloud Function from the Salesforce service using a valid ID token

  1. In your Salesforce org, click the App Launcher icon.

    Starting the App Launcher in the Salesforce dashboard.

  2. In the search bar, search for Accounts.

  3. In the results, click Accounts.

  4. Click New to create a new account.

  5. For Name, enter Test Account.

  6. Click Save.

    Creating the account triggers the Apex class, which invokes the secureFunction Cloud Function.

Verify that the Cloud Function was invoked

You validate that the record was successfully received by the Cloud Function by reviewing Cloud Logging logs.

  • In Cloud Shell, view the logs generated by the secureFunction Cloud Function:

    gcloud functions logs read secureFunction
    

    If the Cloud Function was invoked, you see the customer name Test Account that you created in the Salesforce service in the logs.

Invoke the Cloud Function with no token

You can now verify that only authenticated and authorized users can access the Cloud Function.

  1. Make an unauthenticated request to the Cloud Function:

    curl https://us-central1-${DEVSHELL_PROJECT_ID}.cloudfunctions.net/secureFunction
    

    The response is an HTTP 403 Forbidden status code confirming that access is denied:

    <html><head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <title>403 Forbidden</title>
    </head>
    <body text=#000000 bgcolor=#ffffff>
    <h1>Error: Forbidden</h1>
    <h2>Your client does not have permission to get URL <code>/secureFunction</code> from this server.</h2>
    <h2></h2>
    </body></html>
    

Cleaning up

The easiest way to eliminate billing is to delete the project you created for the tutorial.

Delete the project

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  2. In the project list, select the project that you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

Delete Salesforce resources

You should also delete the Salesforce Developer Edition org and the associated scratch org that you created for this tutorial.

Deactivate your Developer Edition org

  1. From Setup in your Salesforce (Dev Hub) org, in the Quick Find box, enter Company, and then select Company Information.
  2. Click Company Information.
  3. Click the Deactivate Org button.

    The Deactivate Org button in the Salesforce setup page.

Delete the scratch org

  • In Cloud Shell, run the following command to delete your Salesforce scratch org:

    sfdx force:org:delete -u gcp-func-test-scratch-org
    

What's next