From 688f0f9b88ac13a1513e94b98bf0cb75d7e8afcc Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Thu, 30 Jul 2026 15:43:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20TLS=20=EC=A2=85=EB=A3=8C=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=97=94=ED=8A=B8=EB=A6=AC=20(server.js)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adapter-node 의 기본 엔트리(`node build`)는 평문 HTTP 만 서빙한다. 리버스 프록시가 앞에 있으면 충분하지만, 그 계층이 없는 환경에서는 앱이 직접 TLS 를 끝내야 한다. IdP 는 브라우저가 직접 붙고 OIDC issuer 가 https 여야 하므로(ORIGIN/IDP_ISSUER_URL) 평문으로 서빙하면 로그인 흐름이 깨진다. - TLS_DIR 이 있으면 https, 없으면 평문 = **기존 동작 그대로**라 리버스 프록시 뒤의 배포에는 아무 영향이 없다. - TLS 는 fail-hard — 인증서를 못 읽으면 평문 폴백 없이 죽는다. TLS 로 뜨라고 지시했는데 조용히 평문으로 뜨는 것은 사고이고, IdP 는 그 경우 자격증명이 그대로 노출된다. - BUILD_TARGET=node 로 빌드했을 때만 의미가 있다. `node server.js` 로 실행한다. 검증 (BUILD_TARGET=node DB_DIALECT=postgres): - 빌드 성공, build/handler.js 생성 - fail-hard: 잘못된 TLS_DIR → exit=1, ENOENT .../tls.crt 로 기동 거부 - 평문: listening (plaintext) + /api/health 응답 - TLS: listening (TLS) + https 응답 + 같은 포트에 평문 접속 시 exit=52 --- server.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 server.js diff --git a/server.js b/server.js new file mode 100644 index 0000000..ca431a4 --- /dev/null +++ b/server.js @@ -0,0 +1,40 @@ +// 커스텀 서버 엔트리 — adapter-node 의 handler 를 직접 감싼다. +// +// 왜 필요한가: adapter-node 의 기본 엔트리(`node build`)는 평문 HTTP 만 서빙한다. +// ingress controller 나 리버스 프록시가 앞에 있으면 그걸로 충분하지만, 그런 계층이 +// 없는 환경에서는 앱이 직접 TLS 를 끝내야 한다. IdP 는 **브라우저가 직접 붙는** +// 서비스이고 OIDC 는 issuer 가 https 여야 하므로(ORIGIN/IDP_ISSUER_URL 이 https), +// 평문으로 서빙하면 로그인 흐름이 깨진다. +// +// TLS 는 **fail-hard** 다. TLS_DIR 을 줬는데 인증서를 못 읽으면 평문으로 폴백하지 않고 +// 죽는다 — TLS 로 뜨라고 지시했는데 조용히 평문으로 뜨는 것은 사고다. 특히 IdP 는 +// 평문으로 뜨면 자격증명이 그대로 노출된다. +// +// TLS_DIR 을 주지 않으면 평문 = 기존 동작 그대로다. 따라서 리버스 프록시 뒤에서 쓰던 +// 배포에는 아무 영향이 없다. +// +// BUILD_TARGET=node 로 빌드했을 때만 의미가 있다(cloudflare 어댑터는 build/handler.js 를 +// 만들지 않는다). `node server.js` 로 실행한다. +import { createServer as createHttpServer } from "node:http"; +import { createServer as createHttpsServer } from "node:https"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { handler } from "./build/handler.js"; + +const PORT = Number.parseInt(process.env.PORT ?? "3000", 10); +const HOST = process.env.HOST ?? "0.0.0.0"; +const TLS_DIR = process.env.TLS_DIR; + +function loadTls(dir) { + // readFileSync 가 던지면 그대로 죽인다 — 폴백하지 않는다. + return { + cert: readFileSync(join(dir, "tls.crt")), + key: readFileSync(join(dir, "tls.key")), + }; +} + +const server = TLS_DIR ? createHttpsServer(loadTls(TLS_DIR), handler) : createHttpServer(handler); + +server.listen(PORT, HOST, () => { + console.log(`[keystone] listening on ${HOST}:${PORT} (${TLS_DIR ? "TLS" : "plaintext"})`); +});