Dialogflow CX Cloud Function Webhook with VPC-SC

Lorenzo Caggioni
Google Cloud - Community
5 min readAug 26, 2022

Dialogflow CX is a great service that let you create text and voice agents. If you run your agent on a production environment you may want to run it with private communication between components with no communications over internet.

Dialogflow CX supports private communication to Webhooks. It integrates with Service Directory private network access, so it can connect to webhook targets inside your VPC network. This keeps the traffic within the Google Cloud network and enforces IAM and VPC Service Controls.

As of today, using Cloud Functions for Dialogflow webhooks is currently not supported out of the box when VPC Service Controls perimeter or in Service Directory is in use. You need a more articulated architecture to make it work.

In the below architecture you can find the target architecture needed to have your Dialogflow CX agent communicate to a Cloud Function Webhook without going over internet.

Dialogflow CX implemented architecture.

To design the solution we have to take into account that at the moment Service Directory private network access supports only GCE instances, TCP Internal Load Balancer (ILB) and GKE endpoint.

Different solutions can be implemented, we decided to go with the architecture above to rely on the automatic authentication between Dialogflow CX and the Cloud Function and to avoid the creation of self-signed certificates.

In the architecture that we will implement, the request would be the following:

  • Dialogflow retrieves the IP address of the Internal Load Balance and the VPC Network to use from Service Directory
  • Dialogflow will connect to the VPC network and reach the TCP ILB
  • The ILB will forward the request to the backend: the GCE instance in the architecture we will implement
  • In the GCE instance, we will configure an NGINX TCP reverse proxy that will forward all requests to the Cloud Function
  • The Cloud Function will authenticate the Dialogflow Service account and fulfill the request

Let’s start and configure all the needed resources.

Prerequisites

GCP project with billing associated where you have owner role on it.
Open the Cloud shell and be ready to create your infrastructure!

Set Environment variables

Let’s start to create some variables to make the use of the command easier. Please update the variable to match your configuration.

export BACKEND_PORT=443
export GCE_INSTANCE=webhook
export ILB_IP=10.10.20.99
export INSTANCE_GROUP=webhook
export NETWORK=net
export PROJECT_ID=$(gcloud info --format='value(config.project)')
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format=json | jq -r '.projectNumber')
export REGION=europe-west1
export SUBNET_LB=lb-subnet
export SUBNET=europe-subnet
export SD_URL=projects/$PROJECT_ID/locations/$REGION/namespaces/df-namespace/services/df-service
export ZONE=$REGION-b
export FUNCTIONS_URL=$REGION-$PROJECT_ID.cloudfunctions.net

Enable services and service identity service accounts and roles

gcloud services enable \
compute.googleapis.com \
cloudbuild.googleapis.com \
cloudfunctions.googleapis.com \
dialogflow.googleapis.com \
logging.googleapis.com \
pubsub.googleapis.com \
servicedirectory.googleapis.com
gcloud beta services identity create --service=dialogflow.googleapis.comgcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-dialogflow.iam.gserviceaccount.com \
--role=roles/servicedirectory.viewer
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-dialogflow.iam.gserviceaccount.com \
--role=roles/servicedirectory.pscAuthorizedService

Configure Network

gcloud compute networks create $NETWORK \
--project=${PROJECT_ID} \
--subnet-mode=custom
gcloud compute networks subnets create $SUBNET_LB \
--project=${PROJECT_ID} \
--range=10.10.10.0/24 \
--network=$NETWORK \
--region=$REGION \
--enable-private-ip-google-access
gcloud compute networks subnets create $SUBNET \
--project=${PROJECT_ID} \
--network=$NETWORK \
--region=$REGION \
--enable-private-ip-google-access \
--range=10.10.20.0/24
gcloud compute firewall-rules create allow \
--network $NETWORK \
--allow tcp:22,tcp:3389,icmp
gcloud compute firewall-rules create allow-ssh-ingress-from-iap \
--network=$NETWORK \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20
gcloud compute firewall-rules create allow-lb-access \
--project=${PROJECT_ID} \
--direction=INGRESS \
--priority=1000 \
--network=$NETWORK \
--action=ALLOW \
--rules=tcp,udp,icmp \
--source-ranges=10.10.10.0/24,10.10.20.0/24
gcloud compute firewall-rules create allow-health-check \
--direction=INGRESS \
--priority=1000 \
--network=$NETWORK \
--action=ALLOW \
--rules=tcp,udp,icmp \
--source-ranges=130.211.0.0/22,35.191.0.0/16 \
--target-tags=lb-backend
gcloud compute firewall-rules create allow-hope \
--direction=INGRESS \
--priority=1000 \
--network=$NETWORK \
--action=ALLOW \
--rules=tcp:80,tcp:443 \
--source-ranges=35.199.192.0/19
gcloud compute routers create nat-router \
--network=$NETWORK \
--region=$REGION
gcloud compute routers nats create nat-config \
--router=nat-router \
--auto-allocate-nat-external-ips \
--nat-all-subnet-ip-ranges \
--enable-logging \
--router-region=$REGION

Create GCE instance

Let’s create a GCE instance with no public IP. In the startup script we will install Docker and other components needed to run the application.

gcloud compute instances create $GCE_INSTANCE \
--project=$PROJECT_ID \
--zone=$ZONE \
--machine-type=e2-medium \
--network-interface=subnet=$SUBNET_LB,no-address \
--maintenance-policy=MIGRATE \
--tags=lb-backend \
--create-disk=auto-delete=yes,boot=yes,device-name=instance-1,image=projects/debian-cloud/global/images/debian-10-buster-v20220519,mode=rw,size=10,type=projects/$PROJECT_ID/zones/$ZONE/diskTypes/pd-balanced \
--no-shielded-secure-boot \
--shielded-vtpm \
--shielded-integrity-monitoring \
--reservation-affinity=any \
--metadata=startup-script='#! /bin/bash
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release \
nginx
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
# For the purpose of the demo, we will create an Unmanaged Instance group. For a production ready env, we would suggest a MIG.
gcloud compute instance-groups unmanaged create webhook \
--zone=$ZONE
gcloud compute instance-groups unmanaged add-instances webhook \
--zone=$ZONE \
--instances=$GCE_INSTANCE

Configure GCE instance

Let’s configure the NGINX TCP reverse proxy

gcloud compute ssh $GCE_INSTANCE \
--zone=$ZONE
export FUNCTIONS_URL=YOU_REGION-YOUR_PROJECT_ID.cloudfunctions.netmkdir nginx_confcat > nginx_conf/nginx.conf << EOL
events {}
stream {
server {
listen 443;
proxy_pass $FUNCTIONS_URL:443;
}
}
EOL
sudo docker run -v ~/nginx_conf/:/etc/nginx/ -p 443:443 -d nginx

Create Cloud Function Webhook

Manually create a cloud function in the same project and region. The Cloud Function need to allow internal traffic only. Here you can find a detailed step by step guide with a working example.

Configure ILB TCP (Level 4)

gcloud compute health-checks create tcp hc-tcp \
--region=$REGION \
--port=443
gcloud compute backend-services create be-ilb \
--load-balancing-scheme=internal \
--protocol=tcp \
--region=$REGION \
--health-checks=hc-tcp \
--health-checks-region=$REGION
gcloud compute backend-services add-backend be-ilb \
--region=$REGION \
--instance-group=webhook \
--instance-group-zone=$ZONE
gcloud compute forwarding-rules create fr-ilb \
--region=$REGION \
--load-balancing-scheme=internal \
--network=$NETWORK \
--subnet=$SUBNET \
--address=10.10.20.99 \
--ip-protocol=TCP \
--ports=443 \
--backend-service=be-ilb \
--backend-service-region=$REGION

Configure Service Directory

gcloud service-directory namespaces create df-namespace \
--location $REGION
gcloud service-directory services create df-service \
--namespace df-namespace \
--location $REGION
gcloud service-directory endpoints create df-endpoint \
--service=df-service \
--namespace=df-namespace \
--location=$REGION \
--address=10.10.20.99 \
--port=443 \
--network=projects/$PROJECT_NUMBER/locations/global/networks/$NETWORK
gcloud service-directory services describe df-service \
--location $REGION\
--namespace df-namespace

Configure Dialogflow CX

We are almost done! Lets manually create an Agent in the same region that will use the Cloud Function created as a webhook. Here you can find a detailed step by step guide with a working example.

Configure a webhook with the following configuration:

Next Steps

Once you have your infrastructure working, you can start customising your agent and webhook to match you business needs.

--

--

Lorenzo Caggioni
Google Cloud - Community

Multiple years of experience working in Integration and Big Data projects. Working at Google since 2010, lead technical integration with global customers.