feat(tls): serve HTTPS on 443 and trust Floci's cert in Lambda containers#1595
Conversation
…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.
|
| 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
%%{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
Reviews (3): Last reviewed commit: "Merge branch 'feat/tls-lambda-https-call..." | Re-trigger Greptile
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
left a comment
There was a problem hiding this comment.
Thanks @abanna,
This is a great addition to Floci.
Summary
CDK / CloudFormation custom resources (e.g.
Custom::LogRetention, the CDK provider framework,AwsCustomResource) send their result to thecfn-responseResponseURL usingaws-cdk-lib's bundled response code. That bundled code parses only the URL's hostname and path, then callsrequire("https").request(...)— it ignores the scheme and port. So even though Floci hands the resource anhttp://<host>:4566ResponseURL, the callback is always sent tohttps://<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(defaultfalse), so non-TLS setups are unchanged.TlsProxyServeralso binds 443. The same first-byte protocol-detecting handler now listens on a configurablefloci.tls.aws-https-port(default443,0to 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.CertificateGenerator.generateSelfSignedCertificate()— a genuinely self-signed cert (issuer == subject,CA:true,keyCertSign) usable as a trust anchor. The existinggenerateCertificate()is unchanged (it still stamps the cosmetic Amazon issuer DN for ACM realism, which cannot be verified as an anchor).TlsConfigSourceuses the self-signed variant for Floci's HTTPS cert and regenerates a legacy non-self-signed cert on upgrade.ContainerLaunchercopies Floci's CA cert into each Lambda container at/etc/floci-ca.crtand setsNODE_EXTRA_CA_CERTS,AWS_CA_BUNDLE,SSL_CERT_FILE,REQUESTS_CA_BUNDLEso the container trusts Floci's HTTPS endpoint.NODE_EXTRA_CA_CERTSadds to Node's built-in CAs, so public TLS still works.Type of change
fix:)feat:)feat!:orfix!:)AWS Compatibility
Incorrect behavior before this change: with
floci.tls.enabled=true, a CloudFormation/CDK custom resource'scfn-responsecallback (hardcodedhttps://, 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-cdk2.155 (cdklocal) and AWS CLI 2.x:4566and443; a direct HTTPS request to:443returns200.https://<host>/...with no explicit port (exactly thecfn-responsepattern) now connects and the cert verifies — previouslyECONNREFUSED/UNABLE_TO_VERIFY_LEAF_SIGNATURE.cdklocalreachesCREATE_COMPLETEwith all resources, including theCustom::LogRetentionthat previously rolled back.Checklist
TlsProxyServerTest,CertificateGeneratorSelfSignedTest,ContainerLauncherCaTrustTest, plus existingTlsConfigSource*andCertificateGenerator*): 93 passed, 0 failures. Full suite runs in CI.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).