tailscale-lambda-extension
Project description
Tailscale Lambda Extension
A CDK construct that creates an AWS Lambda Layer containing Tailscale binaries, enabling Lambda functions to connect to your Tailscale network.
Available in CDK as a TypeScript NPM Package and Python PyPi Package.
Can be used with ALL AWS Lambda runtimes(Node, Python, Go, etc.) running on Amazon Linux 2023.
Installation
npm install tailscale-lambda-extension
Usage
The Lambda function using this layer requires the following Environment Variables:
TS_SECRET_API_KEY- The name of the AWS Secrets Manager secret that contains the pure text Tailscale API Key (auth key or OAuth client key).TS_HOSTNAME- The "Machine" name as shown in the Tailscale admin console that identifies the Lambda function.
Optional Environment Variables:
TS_ADVERTISE_TAGS- Tags to advertise duringtailscale up(e.g.,tag:aws). Required when using OAuth client keys instead of auth keys.TS_EXIT_NODE- Exit node hostname or IP address. When set, internet-bound traffic is routed through the specified exit node. The extension waits up to 10 seconds for the exit node to become reachable.TS_EXIT_NODE_REQUIRED- Set totrueto abort the extension if the exit node is not reachable within 10 seconds (only the literal valuetrueis recognized, case-insensitive). Defaults tofalse(warn and continue).
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import { TailscaleLambdaExtension } from 'tailscale-lambda-extension';
export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define the layer
const tailscaleExtension = new TailscaleLambdaExtension(this, 'TailscaleExtension');
// Add the layer to your Lambda function
const myLambda = new NodejsFunction(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
entry: "/path/to/my/file.ts",
handler: 'index.handler',
layers: [tailscaleExtension.layer],
environment: {
TS_SECRET_API_KEY: "tailscale-api-key",
TS_HOSTNAME: "my-lambda",
// Optional: advertise tags (required for OAuth client keys)
TS_ADVERTISE_TAGS: "tag:aws",
// Optional: route internet-bound traffic through an exit node
TS_EXIT_NODE: "100.x.y.z",
}
});
// Give the Lambda and thus the Extension permission to read the Tailscale API Key Secret from Secrets Manager
const tsApiKeySecret = secretsmanager.Secret.fromSecretNameV2(this, "tailscale-api-key", "tailscale-api-key");
tsApiKeySecret.grantRead(myLambda);
}
}
Accessing your Tailscale Network within the Lambda
[!TIP] For ease of use, the Tailscale Lambda Proxy should be used. API calls can be made to the proxy, which implements the SOCKS5 functionality, so you don’t need to worry about low level implementation details. This section briefly shows how to do it manually.
The Tailscale process exposes a local SOCKS5 proxy on port 1055. You can use this proxy in your AWS runtime to route
traffic through your Tailscale network. Here is an example of how it can be done with the socks-proxy-agent package
and native http package in a TS Node.js function:
npm install socks-proxy-agent aws-lambda
import http from 'http';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { APIGatewayProxyResultV2 } from 'aws-lambda';
// Helper Function wrapping the http request and returning the response in known APIGatewayProxyResultV2
async function proxyHttpRequest(
target: Pick<http.RequestOptions, "hostname" | "port" | "agent">,
request: {
path: string,
method: string,
headers: Record<string, string>,
body: string | undefined,
}
): Promise<APIGatewayProxyResultV2> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
const apiRequest = http.request({
...target,
path: request.path,
method: request.method,
headers: request.headers,
}, (res: http.IncomingMessage) => {
res.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
res.on('end', () => {
const responseBody = Buffer.concat(chunks);
resolve({
statusCode: res.statusCode || 500,
headers: res.headers as Record<string, string>,
body: responseBody.toString('base64'),
isBase64Encoded: true,
});
});
res.on('error', (error: Error): void => {
console.error('Error receiving response:', error);
reject(error);
});
});
apiRequest.on('error', (error: Error): void => {
console.error('Error sending request:', error);
reject(error);
});
if (request.body != null) {
apiRequest.write(request.body);
}
apiRequest.end();
});
}
export async function handler() {
const socksProxyAgent = new SocksProxyAgent('socks://localhost:1055');
const response = await proxyHttpRequest({
hostname: "Target IP Address that is connected to the TailScale network",
port: "Target IP Address that is connected to the TailScale network",
agent: socksProxyAgent,
}, {
path: "/test",
headers: {
'Content-Type': 'application/json',
},
method: "POST",
body: {
"test": "data"
},
}
);
}
Tailscale Configuration
[!IMPORTANT] The Tailscale setup below shows the minimum configuration required.
Tags
Tags are created for access control and to enable certain features on Auth Key, we are specifically interested in the fact that if you tag an Auth Key, then that Auth Key will not expire. Create a tag for our Lambdas.
- Go to your Tailscale network Access Control: https://login.tailscale.com/admin/acls/file
- Add the tag to the top of the file in the correct property:
{
...
"tagOwners": {
"tag:aws": [],
},
...
}
- Save the file.
OAuth Client Keys (Recommended)
OAuth client keys do not expire, eliminating the need for manual key rotation. They require --advertise-tags to be passed during tailscale up.
- Go to your Tailscale network, Settings, OAuth clients: https://login.tailscale.com/admin/settings/trust-credentials
- Click "Generate OAuth client..."
- Grant the
devices:core,auth_keysscopes - Add the
tag:awstag - Copy the client secret to your AWS Secrets Manager secret
- Set the
TS_ADVERTISE_TAGSenvironment variable on your Lambda totag:aws
Exit Nodes
To route Lambda internet-bound traffic through a Tailscale exit node:
- Ensure you have an exit node configured in your Tailscale network
- Set the
TS_EXIT_NODEenvironment variable on your Lambda to the exit node's Tailscale IP (e.g.,100.x.y.z) - The extension will configure the exit node during initialization and wait up to 10 seconds for it to become reachable
- By default, if the exit node is not reachable in time, the extension logs a warning and continues (fail-open)
- Set
TS_EXIT_NODE_REQUIRED=trueto abort the extension if the exit node is not reachable (fail-closed) — recommended when exit node routing is critical (e.g., residential IP requirement)
[!NOTE] Troubleshooting:
- Ensure that the exit node has been Allowed. This is a manual step that needs to be done on the Tailscale Admin Console.
- Ensure that your ACL routing rules include
"dst": ["autogroup:intenet"]to allow discovery and routing through the exit node.
Auth Keys
[!WARNING] Auth Keys always expire. The maximum duration can be 90 days, so they need constant rotation. If you do not want to worry about rotating keys, use OAuth Client Keys instead.
Create a new Key on Tailscale:
- Go to your Tailscale network, Settings, Keys https://login.tailscale.com/admin/settings/keys
- Click "Generate auth key...".
- Select
Reusable - Select
Ephemeral - Select
Tagsthen Add thetag:awstag. - Click on "Generate key" and copy the Auth Key to your Tailscale AWS Secrets Manager Secret (create it manually if it
does not exist, then place the Secret name in the
TS_SECRET_API_KEYenvironment variable).
Compatibility
- Compatible with ALL AWS Lambda runtimes running on Amazon Linux 2023
- Only Supports x86_64 architecture
Limitations
- The IP address of the Tailscale target must be used, Domain Name resolution is not set up. This is not too much of a limitation/risk as the IP address of the target server can be changed from the Tailscale Admin Console if need be.
- The Layer adds about 50MB to the Lambda package size, most of which is the Tailscale binaries.
- Expect a 5-10 second addition to your cold start time due to the Tailscale process starting up.
Implementation Details
The extension is build using the following steps:
- Uses Docker to build the binaries in an Amazon Linux 2023 environment
- Installs Tailscale from the official repository
- Packages the binaries into a Lambda Layer
- Starts the Tailscale process as an External Extension, allowing the main Runtime process to communicate with the Tailscale process over a local SOCKS5 proxy on port 1055.
Tailscale Version
The current version is v1.96.4
Resources used
Credit to the following resources for providing the necessary information to build this extension:
- https://tailscale.com/kb/1113/aws-lambda - Only shows how to use with a docker image Lambda, not extensio
- https://github.com/QuinnyPig/tailscale-layer - Most of the Layer code is based from this repository
- https://github.com/aws-samples/aws-lambda-extensions/blob/main/custom-runtime-extension-demo/extensionssrc/extensions/extension1.sh - AWS official repo showing how to start an external extension with base code for the extension (the source Corey also used)
- https://aws.amazon.com/blogs/compute/building-a-secure-webhook-forwarder-using-an-aws-lambda-extension-and-tailscale/ - Blog post showing how to build and use a Tailscale Lambda Extension. But it is overly complicated with outdated CDK code.
- https://github.com/rails-lambda/tailscale-extension/tree/main - A similar project for Lambda Container runtime language, but it is not a CDK construct and it exposes the TailScale API Key as an environment variable, which is not best practice.
License
Apache-2.0
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tailscale_lambda_extension-0.0.11.tar.gz.
File metadata
- Download URL: tailscale_lambda_extension-0.0.11.tar.gz
- Upload date:
- Size: 36.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d5a68b36c86ef26524b60248d1fbea2f5a282d8f21a07b921a207d7d8b55719
|
|
| MD5 |
03866964cc1d7cc8957dd54ca81f8d54
|
|
| BLAKE2b-256 |
92a3088d30050f8f070e57724aaa07f19550783838e403d37fdf1d63d088f7ee
|
File details
Details for the file tailscale_lambda_extension-0.0.11-py3-none-any.whl.
File metadata
- Download URL: tailscale_lambda_extension-0.0.11-py3-none-any.whl
- Upload date:
- Size: 36.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8868d4c2ba52e5d092c90e89ee54f528d1124cfeb4920b0aff93883936cdb59
|
|
| MD5 |
3845284f611d51ef6f0e65d610e3a5d6
|
|
| BLAKE2b-256 |
591f428308f39c20c4d80daac5ab27e7c6b6fd078e4d4ee5ebbdc38223c96faf
|