|
1 | 1 | # Tailscale Lambda Extension Proxy
|
2 | 2 |
|
3 |
| -[](https://badge.fury.io/js/tailscale-lambda-proxy) |
| 3 | +[](https://badge.fury.io/js/tailscale-lambda-proxy) |
4 | 4 | [](https://badge.fury.io/py/tailscale-lambda-proxy)
|
5 | 5 |
|
6 |
| -A CDK construct that creates an AWS Lambda Function that acts as a proxy to your Tailscale network. |
| 6 | +A CDK construct that creates an AWS Lambda Function acting as a transparent proxy to your Tailscale network. |
7 | 7 |
|
8 |
| -Available in CDK as a TypeScript NPM Package and Python PyPi Package: |
| 8 | +Available as both a TypeScript NPM Package and a Python PyPi Package: |
9 | 9 | - [TypeScript NPM Package](https://www.npmjs.com/package/tailscale-lambda-proxy)
|
10 | 10 | - [Python PyPi Package](https://pypi.org/project/tailscale-lambda-proxy/)
|
11 | 11 |
|
12 |
| -## Why use a proxy? |
| 12 | +## Why use a proxy? |
13 | 13 |
|
14 |
| -The Proxy Lambda uses the [Tailscale Lambda Extension](https://github.com/rehanvdm/tailscale-lambda-extension) CDK |
15 |
| -construct. |
| 14 | +The Proxy Lambda leverages the [Tailscale Lambda Extension](https://github.com/rehanvdm/tailscale-lambda-extension) CDK |
| 15 | +construct. |
16 | 16 |
|
17 | 17 | It is recommended to use the Proxy Lambda to simplify connecting to your Tailscale network and reduces cold starts
|
18 | 18 | by reusing the same Lambda function for all your Tailscale connected traffic.
|
19 | 19 |
|
20 | 20 | Use the extension directly if:
|
21 |
| -- You only have **a single Lambda**/service that needs to connect to your Tailscale network. |
22 |
| -- You are okay with accepting that this Lambda will have mixed responsibilities (i.e. connecting to Tailscale and your |
23 |
| - business logic). |
24 |
| - |
25 |
| -Use the Proxy Lambda (recommended) construct if: |
26 |
| -- You have **multiple Lambdas**/services that need to connect to your Tailscale network. The proxy Lambda will |
27 |
| - effectively create a "pool" of warm connections to your Tailscale network, ready to be used by any other Lambda |
28 |
| - that calls it. |
29 |
| -- You want to separate concerns and have a dedicated Lambda function that only connects to your Tailscale network. |
30 |
| -- Authentication to your Tailscale network (through the proxy) is then moved to an IAM level. Access is granted to |
31 |
| - the Proxy Lambda Function URL (FURL), and not to the Tailscale API Secret Manager directly. |
| 21 | +- You have **a single Lambda** or service that needs to connect to your Tailscale network. |
| 22 | +- You are comfortable with this Lambda having mixed responsibilities, such as connecting to Tailscale and running |
| 23 | + business logic. |
| 24 | + |
| 25 | +Use the Proxy Lambda (recommended) if: |
| 26 | +- You have **multiple Lambdas** or services requiring connection to your Tailscale network. The Proxy Lambda *eventually* |
| 27 | + creates a "pool of warm connections" to the Tailscale network, ready for use by other Lambdas. |
| 28 | +- You want to separate responsibilities by having a dedicated Lambda for Tailscale connectivity. |
| 29 | +- Authentication to the Tailscale network is handled at the IAM level, where access is granted to the Proxy Lambda's |
| 30 | + Function URL (FURL), instead of directly to the Tailscale API Secret Manager. |
32 | 31 |
|
33 | 32 | ## Usage
|
34 | 33 |
|
35 | 34 | > [!TIP]
|
36 |
| -> See the complete example in the [tailscale-lambda-proxy-example](https://github.com/rehanvdm/tailscale-lambda-proxy-example) |
37 |
| -> repository. |
| 35 | +> Refer to the [tailscale-lambda-proxy-example](https://github.com/rehanvdm/tailscale-lambda-proxy-example) repository |
| 36 | +> for a complete example. |
| 37 | +
|
| 38 | +### Installation |
38 | 39 |
|
39 |
| -Install the package: |
| 40 | +Install the package: |
40 | 41 | ```bash
|
41 | 42 | npm install tailscale-lambda-proxy
|
42 | 43 | ```
|
43 | 44 |
|
44 |
| -The Lambda Proxy requires the following: |
45 |
| -- `tsSecretApiKey` - The AWS Secrets Manager secret that contains the pure text Tailscale API Key. |
46 |
| -- `tsHostname` - The "Machine" name as shown in the Tailscale admin console that identifies this Lambda(s) function. |
| 45 | +The Proxy Lambda requires the following: |
| 46 | +- `tsSecretApiKey`: The AWS Secrets Manager secret containing the Tailscale API Key as plain text. |
| 47 | +- `tsHostname`: The "Machine" name as shown in the Tailscale admin console, which identifies the Lambda function(s). |
47 | 48 |
|
48 | 49 | ```typescript
|
49 | 50 | import * as cdk from 'aws-cdk-lib';
|
@@ -90,70 +91,101 @@ export class MyStack extends cdk.Stack {
|
90 | 91 |
|
91 | 92 | ## Accessing your Tailscale Network through the Proxy
|
92 | 93 |
|
93 |
| -The code below can be found in the |
94 |
| -[tailscale-lambda-proxy-example](https://github.com/rehanvdm/tailscale-lambda-proxy-example) repository. |
| 94 | +The [tailscale-lambda-proxy-example](https://github.com/rehanvdm/tailscale-lambda-proxy-example) repository contains |
| 95 | +the following example. |
95 | 96 |
|
96 |
| -**The Tailscale Lambda Proxy is transparent**. It does not modify the request or response that is sent to the machine in |
97 |
| -any way. It simply forwards the request (path, method, headers, and body) to the machine and returns the response. |
| 97 | +The Tailscale Lambda Proxy is fully transparent. It forwards requests (path, method, headers, and body) to the machine |
| 98 | +and returns the response without modification. |
98 | 99 |
|
99 |
| -There are 2 important components when interacting with the Tailscale Lambda Proxy: |
| 100 | +Key considerations when using the Proxy: |
100 | 101 | 1. All requests must be signed with the IAM Signature V4 algorithm.
|
101 |
| -2. The IP address and port of your Tailscale connected machine/device should be placed in the |
102 |
| - headers when making the call to the proxy. |
103 |
| - |
104 |
| -### Signing Requests |
| 102 | +2. The target machine's IP address and port must be included in the headers when making requests to the Proxy. |
105 | 103 |
|
106 |
| -The Lambda Proxy exposes a Function URL secured with IAM Authentication. The caller Lambda only needs this URL and IAM |
107 |
| -permissions to call it. Then it needs to sign all requests with the IAM Signature V4 algorithm. For Typescript users, |
108 |
| -you can use the [aws4](https://www.npmjs.com/package/aws4) package to sign requests. |
| 104 | +#### Signing Requests |
109 | 105 |
|
110 |
| -### Including the correct headers |
| 106 | +The Proxy Lambda exposes a Function URL secured with IAM Authentication. The caller Lambda requires this URL and |
| 107 | +IAM permissions to make requests. These requests must be signed with the IAM Signature V4 algorithm. For TypeScript, |
| 108 | +use the [aws4](https://www.npmjs.com/package/aws4) package to sign requests. |
111 | 109 |
|
112 |
| -When making a request to the Proxy, you need to include the IP address and port of the Tailscale connected machine/device |
113 |
| -that you are targeting in the headers. |
| 110 | +#### Including Target Headers |
114 | 111 |
|
115 |
| -The `ts-` headers below will be removed before forwarding the request to the Tailscale machine/device, they are only |
116 |
| -used for routing and internal logic to the proxy. |
| 112 | +When calling the Proxy, include the following headers to specify the target machine: |
| 113 | +- `ts-target-ip`: The IP address of the Tailscale-connected machine/device. |
| 114 | +- `ts-target-port`: The port of the Tailscale-connected machine/device. |
117 | 115 |
|
118 |
| -- `ts-target-ip` - The IP address of the Tailscale connected machine/device. |
119 |
| -- `ts-target-port` - The port of the Tailscale connected machine/device. |
| 116 | +These `ts-` headers are removed before the request is forwarded to the target machine. |
120 | 117 |
|
121 |
| -### Create CloudWatch Tracking Metrics |
| 118 | +### Creating CloudWatch Tracking Metrics |
122 | 119 |
|
123 |
| -These metrics are optional and are used to track the success and failure of requests made through the proxy. |
124 |
| - |
125 |
| -To enable the optional tracking metrics, you can include the following headers as well: |
126 |
| -- `ts-metric-service` - The name of the service or API making the request. |
127 |
| -- `ts-metric-dimension-name` - The name of the dimension to track (e.g., client name). |
128 |
| -- `ts-metric-dimension-value` - The value associated with the dimension. |
| 120 | +To enable optional tracking metrics, add the following headers to your request: |
| 121 | +- `ts-metric-service`: The name of the service or API making the request. |
| 122 | +- `ts-metric-dimension-name`: The dimension name for tracking (such as client name). |
| 123 | +- `ts-metric-dimension-value`: The value associated with the dimension. |
129 | 124 |
|
130 |
| -For each request, one of the following CloudWatch metrics is created: |
131 |
| -- `success`: Recorded when the request is successfully delivered to the target server. Note that this does not depend on |
132 |
| - the HTTP status code of the API response. |
133 |
| -- `failure`: Recorded when the request fails to reach the target server. Possible reasons include network issues or the |
134 |
| - target server being unavailable. |
| 125 | +Metrics generated in CloudWatch: |
| 126 | +- `success`: Logged when a request reaches the target server, regardless of the API response status. |
| 127 | +- `failure`: Logged when a request fails to reach the target server, typically due to network issues or server |
| 128 | + unavailability. |
135 | 129 |
|
136 |
| -Here is an example of headers for a request: |
137 |
| -- `ts-metric-service`: `gallagher` the name of the calling service or API being targeted. |
138 |
| -- `ts-metric-dimension-name`: `client` tracks the client name to be used for monitoring or alerts. |
139 |
| -- `ts-metric-dimension-value`: `rehan-test-client` the specific client name. |
| 130 | +Example headers for a request: |
| 131 | +- `ts-metric-service`: `gallagher`, indicating the service or API making the request. |
| 132 | +- `ts-metric-dimension-name`: `client`, used for monitoring or alerts. |
| 133 | +- `ts-metric-dimension-value`: `rehan-test-client`, identifying the specific client. |
140 | 134 |
|
141 |
| -This setup generates a CloudWatch metric similar to the one below: |
| 135 | +This configuration generates CloudWatch metrics similar to the screenshot below: |
142 | 136 | 
|
143 | 137 |
|
144 | 138 | ### Error Handling
|
145 | 139 |
|
146 |
| -In efforts to keep the Proxy Lambda transparent, all traffic, including errors are passed back to the caller. This makes |
147 |
| -it difficult to determine if the error was due to the Proxy Lambda or if it originated from the Tailscale connected |
148 |
| -machine/device. |
| 140 | +To maintain transparency, the Proxy Lambda passes all traffic, including errors, back to the caller. This approach |
| 141 | +makes it difficult to determine whether an error originated from the Proxy Lambda or the Tailscale-connected machine. |
| 142 | + |
| 143 | +A Proxy Lambda error can be identified by the following headers in the response: |
| 144 | +- `ts-error-name`: The error name. |
| 145 | +- `ts-error-message`: The error message. |
| 146 | + |
| 147 | +### Code Examples |
| 148 | + |
| 149 | +Refer to the [tailscale-lambda-proxy-example](https://github.com/rehanvdm/tailscale-lambda-proxy-example) repository |
| 150 | +for the complete example. The partial snippet below shows a Lambda function accessing an express server running |
| 151 | +on a Tailscale-connected machine. The [TailscaleProxyApi](https://github.com/rehanvdm/tailscale-lambda-proxy-example/blob/f5e95c9b2294bd185bbe5b372a24dafcafc17297/lib/lambda/tailscale-caller/tailscale-proxy-api.ts) |
| 152 | +class implements the above-mentioned usage specifications. |
| 153 | + |
| 154 | +```typescript |
| 155 | +import {TailscaleProxyApi} from "./tailscale-proxy-api"; |
| 156 | + |
| 157 | +export const handler = async (event: any) => { |
| 158 | + console.log(JSON.stringify(event, null, 2)); |
149 | 159 |
|
150 |
| -A Lambda Proxy error can be identified by the presence of the following response headers: |
151 |
| -- `ts-error-name` - The name of the error. |
152 |
| -- `ts-error-message` - The error message. |
| 160 | + // Connect to the tailscale network with your laptop, get your tailscale IP, then start the express server in |
| 161 | + // `express-local-api` with `npm run start` and then run the lambda function to test the connection. |
| 162 | + const targetIp = "100.91.164.93"; |
| 163 | + const targetPort = 3000; |
| 164 | + |
| 165 | + const api = new TailscaleProxyApi(process.env.TS_PROXY_URL!, process.env.AWS_REGION!, |
| 166 | + targetIp, targetPort, |
| 167 | + "express-local-api", "client", "client-a" |
| 168 | + ); |
| 169 | + |
| 170 | + const resp = await api.request("/ping", "GET"); |
| 171 | + |
| 172 | + if(resp.proxyError) { |
| 173 | + throw new Error(`PROXY ERROR: ${resp.proxyError}`); |
| 174 | + } |
| 175 | + else if(resp.response.statusCode !== 200) { |
| 176 | + throw new Error(`API ERROR: ${resp.response.statusCode} with body: ${resp.response.body}`); |
| 177 | + } |
| 178 | + |
| 179 | + console.log(''); |
| 180 | + console.log('API SUCCESS: ', resp.response.body); |
| 181 | + |
| 182 | + return true; |
| 183 | +}; |
| 184 | +``` |
153 | 185 |
|
154 | 186 | ## Additional Information
|
155 | 187 |
|
156 |
| -See the [Tailscale Lambda Extension](https://github.com/rehanvdm/tailscale-lambda-extension) for more information on: |
157 |
| -- How to configure Tailscale |
158 |
| -- Limitations like cold start times, package sizes and the lack of DNS resolution. |
159 |
| -- Implementation Details |
| 188 | +Refer to the [Tailscale Lambda Extension](https://github.com/rehanvdm/tailscale-lambda-extension) documentation for: |
| 189 | +- Configuring Tailscale. |
| 190 | +- Understanding limitations such as cold start times, package sizes, and the lack of DNS resolution. |
| 191 | +- Additional implementation details. |
0 commit comments