Lambda mTLS to Private ALBs: East-West Calls Without Plaintext

Lambda mTLS to Private ALBs: East-West Calls Without Plaintext

Shared VPCs make plaintext HTTP between Lambdas and private ALBs feel “internal enough.” It is not. Any compromised ENI, over-broad security group, or curious sidecar can read tokens, PII, and tool payloads in flight. The unfair advantage is mutual TLS end-to-end: Lambda presents a client certificate from ACM Private CA, the ALB terminates and verifies it, and east-west traffic never travels as cleartext — even on RFC1918 links.

⚡ TL;DR: Issue short-lived client certs via ACM PCA (or short rotation from Secrets Manager). Mount them into Lambda with a cold-start refresh that fails closed. Configure the private ALB HTTPS listener with mTLS verify mode and a trust store pointing at your PCA. Pin SAN/URI names per service identity. Pair with AI Coding in VPC: Private Bedrock Endpoints and Secure AI Sandboxes so agent backends inherit the same east-west posture.

Why security groups alone are not enough

SGs answer “who can open a TCP socket,” not “who proved cryptographic identity.” In a flat VPC with overlapping CIDRs across accounts, SG rules drift. mTLS binds the call to a named principal.

// BAD: plaintext east-west — anyone on the path can sniff Authorization
const resBad = await fetch("http://orders.internal:80/v1/quote", {
  headers: { Authorization: `Bearer ${token}` },
});

// GOOD: HTTPS + client cert — ALB rejects callers without a trusted leaf
import https from "node:https";
import fs from "node:fs";

const agent = new https.Agent({
  cert: fs.readFileSync(process.env.MTLS_CERT_PATH!),
  key: fs.readFileSync(process.env.MTLS_KEY_PATH!),
  ca: fs.readFileSync(process.env.MTLS_CA_PATH!), // PCA root/chain
  keepAlive: true,
  maxCachedSessions: 64,
});

const res = await fetch("https://orders.internal/v1/quote", {
  // node fetch / undici agent wiring depends on runtime
  dispatcher: undefined,
  headers: { Authorization: `Bearer ${token}` },
});
void agent; void resBad;

✅ ALB listener policy requires present + valid client cert.
❌ Relying on “private subnet = encrypted.”

ALB trust store + PCA wiring

# illustrative listener shape
Listener:
  Port: 443
  Protocol: HTTPS
  MutualAuthentication:
    Mode: verify          # not passthrough
    TrustStoreArn: !Ref InternalTrustStore
  Certificates:
    - CertificateArn: !Ref ServerCertArn
TrustStore:
  CaCertificatesBundleS3Bucket: pca-bundles
  CaCertificatesBundleS3Key: internal-root.pem

Rotate the CA bundle with a dual-trust window. Snap the Lambda client CA env on the same schedule as the trust store update so you never brick invokes mid-rotation.

Cold-start cert refresh without leaking keys

import { SecretsManager } from "@aws-sdk/client-secrets-manager";
import { writeFileSync, chmodSync } from "node:fs";

const sm = new SecretsManager({});
let primed = false;

export async function ensureMtlsMaterial() {
  if (primed) return;
  const secret = await sm.getSecretValue({ SecretId: process.env.MTLS_SECRET! });
  const { cert, key, ca } = JSON.parse(secret.SecretString!);
  for (const [path, body] of [
    [process.env.MTLS_CERT_PATH!, cert],
    [process.env.MTLS_KEY_PATH!, key],
    [process.env.MTLS_CA_PATH!, ca],
  ] as const) {
    writeFileSync(path, body, { mode: 0o600 });
    chmodSync(path, 0o600);
  }
  primed = true;
}

export const handler = async (_event: unknown) => {
  await ensureMtlsMaterial();
  // call private ALB with mTLS agent
};

Keep private keys out of image layers and out of CloudWatch. Prefer Secrets Manager + IAM condition keys over baking PEMs into the deployment zip. See Secret-Aware Context Filters for how AI editors must never echo these paths into PRs.

Identity per service, not one god cert

CN=orders-writer.lambda.prod.example
URI:spiffe://prod/ns/checkout/sa/orders-writer

Map ALB rules on SPIFFE/SAN when you graduate past “any trusted client.” One shared client cert across fifty functions is lateral-movement gift wrapping.

Closing checklist

  • [ ] Private ALB HTTPS listener uses mTLS verify with PCA trust store
  • [ ] Lambda presents short-rotation client cert+key; CA chain pinned
  • [ ] No plaintext HTTP listeners remain for the same target groups
  • [ ] Cert material loaded at init from Secrets Manager, mode 0600
  • [ ] Per-service SAN/SPIFFE — not one org-wide client cert
  • [ ] Dual-trust CA rotation runbook tested in staging

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply