Serverless functions have become increasingly popular due to their scalability and ease of use. However, connecting these functions to on-premises resources, such as databases, can be challenging. In this post, I'll explore an solution to connect Google Cloud Functions to an on-premises PostgreSQL database using Tailscale for secure networking and Gost for port forwarding.
Problem
Cloud Functions are designed to be stateless and ephemeral, making it difficult to establish persistent connections to resources outside the cloud environment. This becomes particularly problematic when you need to interact with on-premises databases that aren't directly accessible from the cloud.
Solution
We'll use a combination of technologies to bridge this gap:
- Google Cloud Functions: Our serverless compute platform.
- Tailscale: A modern VPN built on WireGuard, providing secure networking.
- Gost: A versatile proxy server that will forward our database connections.
Let's dive into the implementation details.
The Cloud Function code
First, let's look at our basic Cloud Function code:
import functions_framework @functions_framework.cloud_event def cloud_event_function(cloud_event): pass
This is a minimal Cloud Function that uses the functions_framework
decorator. In a real-world scenario, you'd add your database interaction logic here.
Building the Custom Container
To incorporate Tailscale and Gost, we need to build a custom container. Here's our Dockerfile:
FROM golang:1.22.6-bullseye AS builder # Build gost. WORKDIR /proxy RUN git clone https://github.com/ginuerzh/gost.git \ && cd gost/cmd/gost \ && go build FROM python:3.10 # Copy gost binary. WORKDIR /proxy COPY --from=builder /proxy/gost/cmd/gost/gost ./ WORKDIR /app # Install dependencies. COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt # Copy source code. COPY . . RUN chmod +x start.sh # Copy Tailscale binaries from the tailscale image on Docker Hub COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /app/tailscaled COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /app/tailscale RUN mkdir -p /var/run/tailscale /var/cache/tailscale /var/lib/tailscale # Run on container startup. CMD ["/app/start.sh"]
This Dockerfile does several important things:
- It builds Gost from source in a separate stage.
- It sets up a Python environment for our Cloud Function.
- It copies the Tailscale binaries from the official Docker image.
Configuring the Startup Script
The heart of our solution lies in the startup script:
#!/bin/sh echo Tailscale starting... /app/tailscaled --tun=userspace-networking --socks5-server=localhost:1055 & /app/tailscale up --authkey=${TAILSCALE_OAUTH_CLIENT_SECRET} --hostname=${TAILSCALE_MACHINE_NAME} --advertise-tags=tag:tag_for_your_postgres_server echo Tailscale started echo Gost starting... /proxy/gost -L tcp://:5432/your_postgres_server:5432 -F socks5://localhost:1055 & echo Gost started functions-framework --target cloud_event_function --signature-type cloudevent
This script:
- Starts Tailscale in userspace networking mode and connects to your Tailscale network.
- Launches Gost, configuring it to forward connections from port 5432 to my on-prem PostgreSQL server through the Tailscale SOCKS5 proxy.
- Starts the Cloud Function using the functions-framework.
How It All Works Together
When deployed, this setup creates a secure tunnel between your Cloud Function and your on-premises network:
- Tailscale establishes a secure connection to your private network.
- Gost listens on port 5432 (the default PostgreSQL port) and forwards connections through the Tailscale network.
- Your Cloud Function can now connect to
localhost:5432
, and Gost will securely proxy the connection to your on-premises PostgreSQL server.
Deployment and Configuration
To deploy this solution:
- Build and push your container image to Google Container Registry or Artifact Registry.
- Deploy your Cloud Function, specifying your custom container.
- Set the necessary environment variables:
TAILSCALE_OAUTH_CLIENT_SECRET
: Your Tailscale auth keyTAILSCALE_MACHINE_NAME
: A unique name for this Tailscale node
Conclusion
This approach allows you to securely connect your Cloud Functions to on-premises resources without exposing your database to the public internet. It combines the scalability and ease of use of serverless functions with the security and flexibility of a modern VPN solution.
While We're focused on PostgreSQL, this method can be adapted for other databases or services. The combination of Tailscale and Gost provides a powerful and flexible way to bridge the gap between cloud and on-premises resources.
Remember to always follow security best practices and thoroughly test your setup before using it in a production environment.