Skip to content

Commit d70f9be

Browse files
nyobeclaude
andcommitted
Updated blog post with a deployable adapter example
Co-Authored-By: Claude <[email protected]>
1 parent 3766db0 commit d70f9be

File tree

1 file changed

+85
-19
lines changed

1 file changed

+85
-19
lines changed

content/blog/esc-connect/index.md

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Pulumi ESC has [native integrations](/docs/esc/integrations/) with popular secre
2020

2121
ESC Connect changes this by letting you build simple HTTPS adapter services using the [`external` provider](/docs/esc/integrations/dynamic-secrets/external/). Your adapter handles requests from ESC, fetches secrets from your custom source, and returns them. ESC handles authentication with signed JWT tokens, so you get fine-grained control over access without building a complete security infrastructure.
2222

23-
## Building an Adapter
23+
## Building an adapter
2424

2525
Here's an [ESC environment](/docs/esc/environments/) configuration that uses ESC Connect:
2626

@@ -33,24 +33,71 @@ values:
3333
secretName: DATABASE_PASSWORD
3434
```
3535
36-
When you open this environment, ESC makes an authenticated POST request to your adapter. Your adapter validates the JWT token, fetches the secret from your source, and returns it:
36+
When you open this environment, ESC makes an authenticated POST request to your adapter. Your adapter validates the JWT token, fetches the secret from your source, and returns it.
37+
38+
Here's a reusable validation helper you can copy into any adapter:
39+
40+
```typescript
41+
class ESCRequestValidator {
42+
private client: jwksClient.JwksClient;
43+
44+
constructor(jwksUrl: string = "https://api.pulumi.com/.well-known/jwks.json") {
45+
this.client = jwksClient({
46+
jwksUri: jwksUrl,
47+
cache: true,
48+
cacheMaxAge: 600000,
49+
});
50+
}
51+
52+
async validateRequest(event: APIGatewayProxyEvent): Promise<{
53+
claims: jwt.JwtPayload;
54+
requestBody: any;
55+
}> {
56+
// Extract and verify JWT from Authorization header
57+
const authHeader = event.headers.Authorization || event.headers.authorization;
58+
if (!authHeader?.startsWith("Bearer ")) {
59+
throw new Error("Missing or invalid Authorization header");
60+
}
61+
62+
const token = authHeader.substring(7);
63+
const requestBody = event.body || "{}";
64+
65+
// Verify JWT signature using Pulumi's JWKS
66+
const claims = await this.verifyJWT(token);
67+
68+
// Verify audience matches adapter URL
69+
const requestUrl = `https://${event.headers.Host}${event.requestContext.path}`;
70+
if (claims.aud !== requestUrl) {
71+
throw new Error("Audience mismatch");
72+
}
73+
74+
// Verify body hash to prevent replay attacks
75+
if (!this.verifyBodyHash(requestBody, claims.body_hash)) {
76+
throw new Error("Body hash verification failed");
77+
}
78+
79+
return { claims, requestBody: JSON.parse(requestBody) };
80+
}
81+
// ... (additional helper methods)
82+
}
83+
```
84+
85+
Use it in your adapter:
86+
87+
```typescript
88+
const validator = new ESCRequestValidator();
3789

38-
```python
39-
# Simplified example - see docs for complete implementation
40-
class AdapterHandler(BaseHTTPRequestHandler):
41-
def do_POST(self):
42-
# Verify JWT token from Authorization header
43-
token = self.headers.get("Authorization", "").replace("Bearer ", "")
44-
claims = verify_jwt(token) # Validates signature, expiration, audience
90+
const handler = async (event: APIGatewayProxyEvent) => {
91+
const { claims, requestBody } = await validator.validateRequest(event);
4592

46-
# Parse request and fetch secret
47-
request = json.loads(self.rfile.read())
48-
secret_value = fetch_from_your_source(request["secretName"])
93+
// Fetch secret from your source
94+
const secret = await fetchFromYourSource(requestBody.secretName);
4995

50-
# Return response
51-
response = {"value": secret_value}
52-
self.send_response(200)
53-
self.wfile.write(json.dumps(response).encode())
96+
return {
97+
statusCode: 200,
98+
body: JSON.stringify({ value: secret }),
99+
};
100+
};
54101
```
55102

56103
Once deployed, the secrets become available in your ESC environment:
@@ -60,7 +107,7 @@ environmentVariables:
60107
DB_PASSWORD: ${customSecrets.response.value}
61108
```
62109
63-
The [documentation](/docs/esc/integrations/dynamic-secrets/external/) includes complete adapter examples with JWT verification, body hash validation, and security best practices.
110+
The [documentation](/docs/esc/integrations/dynamic-secrets/external/) includes a complete implementation with all helper methods and security best practices.
64111
65112
## Automated Rotation
66113
@@ -79,8 +126,27 @@ values:
79126

80127
The [rotation documentation](/docs/esc/integrations/rotated-secrets/external/) covers state management, dual-secret strategies, and implementation patterns.
81128

82-
## Try It Out
129+
## Try it out
130+
131+
ESC Connect is available now in Pulumi ESC. We've created a deployable reference implementation on AWS Lambda that you can use as a starting point:
132+
133+
[![Deploy this example with Pulumi](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new?template=https://github.com/pulumi/examples/blob/master/aws-ts-esc-external-adapter-lambda/README.md)
134+
135+
The example includes the `ESCRequestValidator` class shown above and demonstrates:
136+
- JWT validation with Pulumi Cloud's JWKS
137+
- Request integrity checking with body hash verification
138+
- Inline Lambda deployment using Pulumi's function serialization
139+
- CloudWatch logging for debugging
140+
141+
Deploy it with:
142+
143+
```bash
144+
git clone https://github.com/pulumi/examples.git
145+
cd examples/aws-ts-esc-external-adapter-lambda
146+
npm install
147+
pulumi up
148+
```
83149

84-
ESC Connect is available now in Pulumi ESC. Check out the documentation for the [external provider](/docs/esc/integrations/dynamic-secrets/external/) and [external rotation](/docs/esc/integrations/rotated-secrets/external/) to get started. The docs include complete adapter examples with JWT verification, security best practices, and example implementations in multiple languages.
150+
Check out the documentation for the [external provider](/docs/esc/integrations/dynamic-secrets/external/) and [external rotation](/docs/esc/integrations/rotated-secrets/external/) to learn more about building production adapters.
85151

86152
To learn more about Pulumi ESC, explore the [ESC documentation](/docs/esc/) or [get started for free](/docs/esc/get-started/). If you build an adapter for a system that others might find useful, share it in the [Pulumi Community Slack](https://slack.pulumi.com) — we'd love to see what you build.

0 commit comments

Comments
 (0)