Skip to content

feat(tls): serve HTTPS on 443 and trust Floci's cert in Lambda containers#1595

Merged
hectorvent merged 5 commits into
floci-io:mainfrom
abanna:feat/tls-lambda-https-callbacks
Jul 2, 2026
Merged

feat(tls): serve HTTPS on 443 and trust Floci's cert in Lambda containers#1595
hectorvent merged 5 commits into
floci-io:mainfrom
abanna:feat/tls-lambda-https-callbacks

Conversation

@abanna

@abanna abanna commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

CDK / CloudFormation custom resources (e.g. Custom::LogRetention, the CDK provider framework, AwsCustomResource) send their result to the cfn-response ResponseURL using aws-cdk-lib's bundled response code. That bundled code parses only the URL's hostname and path, then calls require("https").request(...) — it ignores the scheme and port. So even though Floci hands the resource an http://<host>:4566 ResponseURL, the callback is always sent to https://<host>:443. Against Floci that fails (nothing on 443, or an untrusted cert once 443 is served), so every custom resource rolls back — even though the resource's own AWS SDK calls (on 4566) succeed. In real AWS this works because the ResponseURL is a pre-signed S3 HTTPS URL on 443.

This makes Floci reachable — and trusted — where the callback actually connects, without touching the CDK-owned handler. Everything is gated on floci.tls.enabled (default false), so non-TLS setups are unchanged.

  1. TlsProxyServer also binds 443. The same first-byte protocol-detecting handler now listens on a configurable floci.tls.aws-https-port (default 443, 0 to disable, de-duped against the public port) in addition to the public Floci port. The first byte still selects the HTTP (4510) or HTTPS (4511) backend, so 443 serves both like the main port.
  2. CertificateGenerator.generateSelfSignedCertificate() — a genuinely self-signed cert (issuer == subject, CA:true, keyCertSign) usable as a trust anchor. The existing generateCertificate() is unchanged (it still stamps the cosmetic Amazon issuer DN for ACM realism, which cannot be verified as an anchor). TlsConfigSource uses the self-signed variant for Floci's HTTPS cert and regenerates a legacy non-self-signed cert on upgrade.
  3. ContainerLauncher copies Floci's CA cert into each Lambda container at /etc/floci-ca.crt and sets NODE_EXTRA_CA_CERTS, AWS_CA_BUNDLE, SSL_CERT_FILE, REQUESTS_CA_BUNDLE so the container trusts Floci's HTTPS endpoint. NODE_EXTRA_CA_CERTS adds to Node's built-in CAs, so public TLS still works.

Type of change

  • Bug fix (fix:)
  • New feature (feat:)
  • Breaking change (feat!: or fix!:)
  • Docs / chore

AWS Compatibility

Incorrect behavior before this change: with floci.tls.enabled=true, a CloudFormation/CDK custom resource's cfn-response callback (hardcoded https://, no port) could not reach Floci, so custom resources failed and their stacks rolled back. In real AWS the callback is a pre-signed S3 HTTPS URL on 443.

Verified with aws-cdk 2.155 (cdklocal) and AWS CLI 2.x:

  • Proxy binds both 4566 and 443; a direct HTTPS request to :443 returns 200.
  • A Lambda issuing https://<host>/... with no explicit port (exactly the cfn-response pattern) now connects and the cert verifies — previously ECONNREFUSED / UNABLE_TO_VERIFY_LEAF_SIGNATURE.
  • A real CDK app deployed via cdklocal reaches CREATE_COMPLETE with all resources, including the Custom::LogRetention that previously rolled back.

Checklist

  • Tests pass locally — ran the affected/related classes in a JDK 25 container (TlsProxyServerTest, CertificateGeneratorSelfSignedTest, ContainerLauncherCaTrustTest, plus existing TlsConfigSource* and CertificateGenerator*): 93 passed, 0 failures. Full suite runs in CI.
  • New tests added — TlsProxyServerTest (dual-port bind + first-byte routing + aws-https-port=0/dedupe), CertificateGeneratorSelfSignedTest (self-signed cert is its own issuer/CA and self-verifies; ACM-style cert keeps the cosmetic issuer and is not a CA), ContainerLauncherCaTrustTest (CA path resolution + injected env vars). Behavior also verified end-to-end live (above).
  • Commit messages follow Conventional Commits

abanna added 2 commits June 26, 2026 16:33
…allbacks

CDK/CloudFormation custom resources (LogRetention, the provider framework,
AwsCustomResource, ...) PUT their result to the cfn-response ResponseURL using
aws-cdk-lib's bundled cfn-response code. That code parses only the URL's hostname
and path and then calls require("https").request(...) — it ignores the scheme and
port. So even though Floci hands the resource an http://...:4566 ResponseURL, the
callback always goes to https://<host>:443. Against Floci that fails — so every
custom resource rolls back, even though its own AWS SDK calls (on 4566) succeed.

This makes Floci reachable, and trusted, where the callback actually connects,
without touching the CDK-owned handler:

- TlsProxyServer binds the same protocol-detecting handler on a configurable
  floci.tls.aws-https-port (default 443, 0 to disable, deduped against the public
  port) in addition to the public Floci port. The first byte still selects the
  HTTP (4510) or HTTPS (4511) backend, so 443 serves both like the main port.

- CertificateGenerator gains generateSelfSignedCertificate(): a genuinely
  self-signed cert (issuer == subject, CA:true, keyCertSign) that can be a trust
  anchor. The existing generateCertificate() is unchanged — it still stamps the
  cosmetic Amazon issuer DN for ACM realism, which is NOT verifiable as an anchor.
  TlsConfigSource now uses the self-signed variant for Floci's HTTPS cert and
  regenerates legacy non-self-signed certs on upgrade.

- ContainerLauncher, when TLS is enabled, copies Floci's CA certificate into each
  Lambda container at /etc/floci-ca.crt and sets NODE_EXTRA_CA_CERTS, AWS_CA_BUNDLE,
  SSL_CERT_FILE and REQUESTS_CA_BUNDLE so the container trusts Floci's HTTPS
  endpoint. NODE_EXTRA_CA_CERTS adds to Node's built-in CAs, so public TLS still
  works.

All behaviors are gated on floci.tls.enabled (default false); non-TLS setups are
unchanged.
…d log

CI surfaced 18 NPEs in the existing ContainerLauncherTest and LambdaImageConfigTest:
ContainerLauncher.launch() now reads config.tls().enabled(), but those tests mock
EmulatorConfig without stubbing tls() (returns null). Fixes:
- launch() short-circuits so cert-path/storage config is only read when TLS is enabled.
- Both tests stub config.tls() with a TlsConfig whose enabled() is false.
- TlsProxyServer logs a failed bind of the extra AWS-HTTPS port (443, privileged) at
  WARN instead of ERROR — it is expected and non-fatal in unprivileged envs (CI/test);
  the public port still errors loudly.
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a real-world compatibility gap: CDK/CloudFormation custom resource cfn-response callbacks hardcode https:// on port 443 regardless of the ResponseURL port, so they were always failing against Floci's TLS setup. The fix has three well-integrated parts: binding the protocol-detecting TLS proxy on an additional configurable port (default 443), generating a genuinely self-signed CA cert that clients can trust as a root anchor, and injecting that cert into Lambda containers via NODE_EXTRA_CA_CERTS and AWS_CA_BUNDLE so cfn-response callbacks succeed.

  • TlsProxyServer now maintains a List<NetServer>, binding the same first-byte-detection handler on both the public Floci port and floci.tls.aws-https-port (default 443, 0 to disable, deduplicated), with graceful non-fatal handling when the privileged port can't be bound.
  • CertificateGenerator.generateSelfSignedCertificate() produces a cert with issuer==subject, CA:true, and keyCertSign, while the existing generateCertificate() (cosmetic Amazon issuer for ACM realism) is unchanged; TlsConfigSource now forces regeneration of legacy non-self-signed certs on upgrade via isSelfSigned().
  • ContainerLauncher resolves the cert path (user-provided or auto-generated), copies it into each Lambda container at /etc/floci-ca.crt, and injects only the append-style / AWS-scoped env vars (NODE_EXTRA_CA_CERTS, AWS_CA_BUNDLE), deliberately omitting the replacement-style SSL_CERT_FILE and REQUESTS_CA_BUNDLE.

Confidence Score: 5/5

Safe to merge; all changes are gated on floci.tls.enabled=false by default, so non-TLS deployments are completely unaffected.

The core logic — dual-port binding, self-signed CA cert generation, and container CA injection — is correct and well-tested with three new test classes covering protocol routing, cert properties, and env var injection. The two minor concerns (injected CA env vars placed before user function env vars, and the duplicated filename constant) are both edge-case scenarios that don't affect the primary use case.

ContainerLauncher.java — the ordering of flociCaEnv relative to user function env vars and the duplicated SELF_SIGNED_CERT_NAME constant are worth a second look before a future refactor touches either class.

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/config/TlsProxyServer.java Refactored from a single NetServer to a list; same protocol-detection handler shared across both the public port and the new aws-https-port (default 443), with correct deduplication and graceful non-fatal failure when the privileged port can't be bound.
src/main/java/io/github/hectorvent/floci/services/acm/CertificateGenerator.java Factored common cert-building into buildCertificate(); new generateSelfSignedCertificate() correctly sets issuer==subject, CA:true, and keyCertSign; existing generateCertificate() behaviour unchanged.
src/main/java/io/github/hectorvent/floci/config/TlsConfigSource.java Switches to generateSelfSignedCertificate() and adds isSelfSigned() guard to force regeneration of legacy cosmetic-Amazon-issuer certs on upgrade; logic is correct.
src/main/java/io/github/hectorvent/floci/services/lambda/launcher/ContainerLauncher.java Injects Floci CA cert into Lambda containers and sets NODE_EXTRA_CA_CERTS/AWS_CA_BUNDLE; the injected env vars are added before user function env vars, so user-supplied overrides of those keys silently win.
src/main/java/io/github/hectorvent/floci/config/EmulatorConfig.java Adds awsHttpsPort() to TlsConfig with @WithDefault("443") and a clear Javadoc covering the 0-to-disable and dedup semantics.
src/test/java/io/github/hectorvent/floci/config/TlsProxyServerTest.java New test class covers dual-port bind, first-byte routing, awsHttpsPort=0/dedupe, and TLS-disabled no-bind; uses ephemeral ports to avoid privilege requirements.
src/test/java/io/github/hectorvent/floci/services/acm/CertificateGeneratorSelfSignedTest.java New test verifies self-signed cert has issuer==subject, CA:true, keyCertSign, and passes cert.verify(cert.getPublicKey()); also guards that legacy ACM-style cert keeps its cosmetic Amazon issuer.
src/test/java/io/github/hectorvent/floci/services/lambda/launcher/ContainerLauncherCaTrustTest.java New unit tests cover cert path resolution, env var injection, and the isSelfSignedCaCertificate helper; no Docker/Quarkus needed.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CDK as CDK cfn-response
    participant Proxy443 as TlsProxyServer :443
    participant ProxyPub as TlsProxyServer :4566
    participant HTTPS as Quarkus HTTPS :4511
    participant HTTP as Quarkus HTTP :4510
    participant Lambda as Lambda Container
    participant CertGen as CertificateGenerator

    Note over CertGen: generateSelfSignedCertificate()<br/>(issuer==subject, CA:true, keyCertSign)
    CertGen->>Lambda: /etc/floci-ca.crt (via copyFileToContainer)
    Note over Lambda: NODE_EXTRA_CA_CERTS=/etc/floci-ca.crt<br/>AWS_CA_BUNDLE=/etc/floci-ca.crt

    CDK->>Proxy443: "PUT https://host/ (no explicit port -> :443)"
    Proxy443->>Proxy443: "peek first byte == 0x16 (TLS ClientHello)?"
    Proxy443->>HTTPS: pipe to :4511

    Lambda->>ProxyPub: "AWS SDK call -> :4566"
    ProxyPub->>ProxyPub: "first byte == 0x16?"
    alt HTTPS
        ProxyPub->>HTTPS: pipe to :4511
    else HTTP
        ProxyPub->>HTTP: pipe to :4510
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CDK as CDK cfn-response
    participant Proxy443 as TlsProxyServer :443
    participant ProxyPub as TlsProxyServer :4566
    participant HTTPS as Quarkus HTTPS :4511
    participant HTTP as Quarkus HTTP :4510
    participant Lambda as Lambda Container
    participant CertGen as CertificateGenerator

    Note over CertGen: generateSelfSignedCertificate()<br/>(issuer==subject, CA:true, keyCertSign)
    CertGen->>Lambda: /etc/floci-ca.crt (via copyFileToContainer)
    Note over Lambda: NODE_EXTRA_CA_CERTS=/etc/floci-ca.crt<br/>AWS_CA_BUNDLE=/etc/floci-ca.crt

    CDK->>Proxy443: "PUT https://host/ (no explicit port -> :443)"
    Proxy443->>Proxy443: "peek first byte == 0x16 (TLS ClientHello)?"
    Proxy443->>HTTPS: pipe to :4511

    Lambda->>ProxyPub: "AWS SDK call -> :4566"
    ProxyPub->>ProxyPub: "first byte == 0x16?"
    alt HTTPS
        ProxyPub->>HTTPS: pipe to :4511
    else HTTP
        ProxyPub->>HTTP: pipe to :4510
    end
Loading

Reviews (3): Last reviewed commit: "Merge branch 'feat/tls-lambda-https-call..." | Re-trigger Greptile

abanna added 2 commits June 30, 2026 12:09
flociCaEnv set SSL_CERT_FILE and REQUESTS_CA_BUNDLE to Floci's cert, which
REPLACE the OpenSSL / Python-requests trust store with only that cert — so a
Lambda's external HTTPS calls (curl, openssl, requests/botocore) to anything but
Floci failed certificate verification. Inject only the appending/AWS-scoped vars:
NODE_EXTRA_CA_CERTS (adds to Node's CAs) and AWS_CA_BUNDLE (AWS-SDK traffic is
redirected to Floci via AWS_ENDPOINT_URL anyway).

resolveFlociCaCertPath injects the resolved cert into containers as a trust
anchor (CA). A user-supplied floci.tls.cert-path pointing at a leaf/server cert
only pins that exact certificate; document that and log a warning when the
configured cert is not a self-signed CA (issuer != subject or no BasicConstraints
CA:true), via the new isSelfSignedCaCertificate helper.

Updates ContainerLauncherCaTrustTest for the trimmed env set and adds coverage
for self-signed-CA vs leaf-cert detection.
…m:443/abanna/floci into feat/tls-lambda-https-callbacks

@hectorvent hectorvent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @abanna,

This is a great addition to Floci.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants