Skip to content

Commit 932d7a4

Browse files
authored
feat: HTTPS support (#91)
1 parent 4588408 commit 932d7a4

19 files changed

Lines changed: 1905 additions & 6 deletions

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ endpoints declared in an OpenAPI 3.1.x specification. Handlers are pure function
2020
- [JSON mapping](#json-mapping)
2121
- [Body parsers and response writers](#body-parsers-and-response-writers)
2222
- [Server configuration](#server-configuration)
23+
- [HTTPS](#https)
2324
- [Interceptors and response decorators](#interceptors-and-response-decorators)
2425
- [After-response hooks](#after-response-hooks)
2526
- [Security](#security)
@@ -326,6 +327,83 @@ OpenApiServer.builder()
326327
.build();
327328
```
328329

330+
### HTTPS
331+
332+
Point the builder at a PEM certificate chain and a PEM PKCS#8 private key:
333+
334+
```java
335+
import java.nio.file.Path;
336+
337+
var server = OpenApiServer.builder()
338+
.spec(spec)
339+
.handlers(handlers)
340+
.https(
341+
Path.of("/etc/letsencrypt/live/example.com/fullchain.pem"),
342+
Path.of("/etc/letsencrypt/live/example.com/privkey.pem"))
343+
.build();
344+
```
345+
346+
certbot / Let's Encrypt write exactly these two files to
347+
`/etc/letsencrypt/live/<domain>/`: `fullchain.pem` (your certificate + the
348+
issuing intermediates, concatenated PEM) and `privkey.pem` (unencrypted PKCS#8).
349+
No conversion to PKCS12 / JKS is needed; the library parses the PEM directly
350+
using JDK APIs only.
351+
352+
Both RSA and EC (P-256) private keys are accepted; the algorithm is detected
353+
automatically.
354+
355+
**Deployment.** Don't bake `privkey.pem` into your container image — you
356+
lose rotation and leak the key into image layers and registries. Mount the
357+
two PEM files at runtime from a secret manager:
358+
359+
- **Kubernetes:** [cert-manager](https://cert-manager.io) writes the
360+
certificate and key into a `Secret`; mount it as a volume at the path you
361+
pass to `.https(...)`. Renewal is automatic; restart the pod (e.g. via a
362+
rolling deploy keyed off the Secret's revision) to pick up the new cert.
363+
- **GCP:** Store both files in Secret Manager and project them with the
364+
[Secret Manager CSI driver](https://cloud.google.com/secret-manager/docs/access-control)
365+
or a Workload Identity-bound init container that writes the files to an
366+
`emptyDir` shared with the app container.
367+
- **AWS:** [Secrets Manager](https://docs.aws.amazon.com/secretsmanager/) via
368+
the [AWS Secrets and Configuration Provider](https://github.com/aws/secrets-store-csi-driver-provider-aws)
369+
for the CSI driver follows the same pattern.
370+
371+
Whatever the source: mount the volume read-only, give `privkey.pem` mode
372+
`0400` (owner-read only), and ensure the JVM process owns or can read it.
373+
374+
When `.https(...)` is set, the default port changes from `8080` to `8443`.
375+
`port(int)` still overrides explicitly:
376+
377+
```java
378+
OpenApiServer.builder()
379+
.spec(spec)
380+
.handlers(handlers)
381+
.https(certChain, privateKey)
382+
.port(443) // overrides the 8443 default
383+
.build();
384+
```
385+
386+
For local development without a real certificate, generate a self-signed pair
387+
with one openssl command:
388+
389+
```bash
390+
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
391+
-keyout privkey.pem -out fullchain.pem \
392+
-subj "/CN=localhost" \
393+
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
394+
```
395+
396+
Clients (browsers, `curl`, `HttpClient`) need to trust the resulting certificate
397+
explicitly — it isn't signed by a public CA.
398+
399+
**Not in this release** (each can land later without breaking the API):
400+
401+
- Encrypted / password-protected private keys
402+
- PKCS12 / JKS keystore inputs
403+
- Certificate hot-reload on renewal (restart the process after `certbot renew`)
404+
- TLS protocol / cipher overrides (JDK defaults apply: TLS 1.2 and 1.3)
405+
- Serving HTTP and HTTPS from one `OpenApiServer` instance
406+
329407
### Graceful shutdown
330408

331409
`OpenApiServer` exposes `stop(int delaySeconds)` for explicit shutdown that waits up to the given

0 commit comments

Comments
 (0)