19.3 · 12-month platform modernization
ขอบฟ้า เดือน 6–12 — งานระดับสถาปัตยกรรมที่ ROI ของ deep scorecards จัดไว้ท้ายลำดับ เพราะ investment สูง แต่ถ้าไม่ทำ pattern เดิมจะเกิดซ้ำใน service ใหม่
/ Months 6–12 — platform-level work that deep scorecards put late in ROI order: high investment, but without it the same failure modes recur in every new service.
แกนของบทนี้: (1) central secret manager, (2) NestJS consolidation & service boundaries, (3) ลบวัฒนธรรม backdoor / @Public() debug, (4) token & frontend standard, (5) dependency LTS baseline
ไม่ใช่ "rewrite ทั้งแพลตฟอร์มใน Go" — deep docs ไม่สนับสนุน big-bang rewrite; vtrc-api/10 ระบุชัดว่า Sequelize 5→6 / Node 12→20 / Apollo v2→v4 ไม่คุ้มถ้าไม่มี feature บังคับ
หลักการก่อนเริ่มขอบฟ้า 12 เดือน
- 19.1 ต้องผ่าน Definition of Done — อย่าเริ่ม secret manager บน credential ที่ยังไม่ rotate
- 19.2 ต้องมี transaction / cron ที่เสถียรพอ — อย่าย้าย DB boundary ตอนข้อมูลยัง partial-write
- อ่าน 19.4 — modernization ≠ cosmetic rename
Pillar 1 — Central secrets manager
ทำไมต้องมี (หลักฐานซ้ำ)
| หลักฐาน | Citation |
|---|---|
| 13+ repo มี secret ใน git (deep redo) | Vol 12.5 secrets cluster |
SECRET_KEY แชร์ benefit / recruitment / ai-recruitment / pms | pms-api SEC-PMS-02 |
Fallback secret ในซอร์ส 2way-api | SEC-2WAYAPI-02 |
| ROI ท้ายของหลาย service ชี้ "ย้ายไป secret manager" | benefit ROI #5, vtrc-api ROI #10, pms ROI #3 ต่อเนื่อง |
หลัง rotate มือใน 90 วัน — ถ้ายัง copy .env ข้าม repo ด้วยมือ จะรั่วรอบใหม่ภายในปี
Target architecture
┌─────────────────────┐
│ Secret Manager │ Vault / AWS SM / GCP SM
│ (rotatable, audited)│ (องค์กรมี GCP SA pattern อยู่แล้ว — อย่า commit key)
└─────────┬───────────┘
│ runtime fetch / CSI / init container
▼
┌─────────────────────┐
│ Each service boot │ fail-fast ถ้าขาด key — ไม่มี fallback string
│ JWT secret ต่อ svc │ เลิก share ข้าม domain
└─────────────────────┘งานย่อย (เรียง)
| # | งาน | ผล |
|---|---|---|
| 1.1 | เลือก tooling ที่ ops รองรับจริง (GCP SM น่าจะสมเหตุสมผลกว่าเพิ่ม Vault ถ้ายังไม่มี) | ลด tooling sprawl |
| 1.2 | มาตรฐานชื่อ secret path ต่อ service / env | vtrc/{env}/{service}/{key} |
| 1.3 | Migrate ชุด A (domain Nest) ก่อน — benefit, recruitment, ai-recruitment, pms | ปิด shared JWT |
| 1.4 | Migrate legacy vtrc-api / cu-central-api | แยก JWT_SECRET จาก default culture |
| 1.5 | Auto-rotation policy + runbook ใน Vol 16 | rotate ไม่พึ่ง chat |
| 1.6 | แยก JWT secret ต่อ service — เลิก forge ข้ามแพลตฟอร์ม | pms-api remediation #5 |
Anti-goal
- ห้ามใส่ secret ใหม่ลง Docker image bake (
SEC-COMMON-03pattern) - ห้าม commit
.env.exampleที่มีค่าจริง - ห้ามใช้
NEXT_PUBLIC_*สำหรับ secret ที่ต้องเก็บฝั่ง server (2way-api encrypt.util fallback)
Pillar 2 — NestJS consolidation & service boundaries
2.1 สถานะปัจจุบัน (จาก deep docs)
แพลตฟอร์มมี NestJS อยู่แล้วในกลุ่ม 2way / Domain / Meeting แต่ยังมี:
| ปัญหา | ตัวอย่าง | Citation |
|---|---|---|
| Distributed monolith (cross-DB) | workflow-api TypeORM ตรงไป schema อื่น; pms-api workforce coupling ×10 | CORR-WORKFLOW-01; ARCH-PMS-01 |
| Dual schema ไม่ sync | benefit.wdHospitals vs vtrc.WDHospitals | benefit-api ROI #8 |
| Legacy GraphQL monolith ผสม REST PDF | vtrc-api | vtrc-api ROI #5 |
| Fork ซ้ำ backoffice | vtrc-backoffice ≈ vtrc-backoffice-new | dependency + bug เดียวกัน |
| Placeholder 8 ตัวว่าง | attendance/auth/career/… | workspace structure |
2.2 NestJS consolidation — ความหมายในเล่มนี้
Consolidation ในที่นี้ = ทำให้ Nest services มี มาตรฐานเดียวกัน ไม่ใช่รวมทุกอย่างเป็น monorepo เดียวในวันเดียว
มาตรฐานที่ต้องล็อกภายใน 12 เดือน:
| มาตรฐาน | รายละเอียด |
|---|---|
| Auth | global JwtAuthGuard เป็น default; @Public() ต้องมี ADR + owner |
| Config | @nestjs/config + secret manager; ห้าม fallback string |
| SQL | parameterized เท่านั้น; raw string concat = CI fail |
| Cron | ScheduleModule + distributed lock; document timezone |
| Logging | pino / nest-winston กลาง; no-console ใน CI |
| Health | /health + readiness ทุก service |
| vtrc-common | treat เป็น internal library บน branch prod — ไม่ bake secret |
2.3 แยก workload จาก vtrc-api
vtrc-api/10 ROI #5: แยก REST PDF/Excel generation ออกจาก GraphQL server
vtrc-api (GraphQL + auth + domain)
│
│ queue / internal HTTP
▼
pdf-worker (Nest or existing worker)
qpdf via execFile — ไม่มี shell
scale แยกจาก API podsคู่กับ ROI #8 — แทนที่ node-schedule ด้วย BullMQ + Redis lock สำหรับ leader election ที่เชื่อถือได้
2.4 ลด cross-DB access
ลำดับปลอดภัย:
- Inventory ทุก connection ที่ชี้ schema คนละเจ้าของ (เริ่มจาก workflow / pms workforce / benefit dual)
- Freeze — ห้าม PR เพิ่ม connection ใหม่
- อ่านผ่าน API ของเจ้าของข้อมูล (ช้ากว่าแต่มี auth/validation จุดเดียว)
- เขียนเฉพาะผ่านเจ้าของ — เลิก dual-write
Effort: สัปดาห์–เดือน ต่อ domain (pms-api ROI #9)
2.5 รวม / deprecate frontend forks
| คู่ | การตัดสินใจที่ต้องมีภายในปี |
|---|---|
vtrc-backoffice vs vtrc-backoffice-new | merge หรือ deprecate ตัวหนึ่ง — bug isRemember / AntD / react-scripts ซ้ำ |
meeting-backoffice vs meeting-backoffice-new | ยืนยันสถานะย้าย; เลิกฝัง static build ด้วยมือใน vtrc-backoffice |
| 8 placeholder APIs | implement ตาม timeline หรือ archive อย่างเป็นทางการ |
2.6 Placeholder repos — decision once
attendance-api, auth-api, career-api, hrservice-api, learning-api, notification-api, payroll-api, performing-api
แต่ละตัวเลือก (ก) timeline implement หรือ (ข) archive — อย่าปล่อยสถานะคลุมเครือให้ทีมใหม่เริ่มซ้ำ
Pillar 3 — Remove backdoors as a culture (ไม่ใช่แค่ลบ 3 จุด)
การลบ i3admin / testnaja / signinbypass ใน 90 วันเป็น incident response
งาน 12 เดือนคือกันไม่ให้เกิดใหม่:
| Control | รายละเอียด |
|---|---|
| ADR: ห้าม hardcode credentials ในซอร์ส | รวม test user — ใช้ seed + env เฉพาะ UAT |
CI rule: fail ถ้าพบ @Public() บน method ที่ชื่อ add/update/delete/import/salary | allowlist ต้องมี comment + ticket |
| Code review checklist | "มี NODE_ENV gate หรือไม่" ไม่พอ — prod path ต้องไม่มี backdoor |
| Pentest regression | curl จาก Vol 12.5 exploit examples ทุก release train |
| Audit log | alert เมื่อ empCode === 'adm9999' หรือ path /signinbypass |
อ้าง 2way-api remediation: ถ้าต้องการบัญชีทดสอบ — seed + feature flag ปิดใน prod; อย่า comment โค้ด backdoor ไว้
Pillar 4 — Token storage & frontend platform standard
ปัญหาซ้ำ
ทุก frontend/mobile เก็บ token ในที่ JS อ่านได้ — Vol 12.5, vtrc-web SEC-VTRC-WEB-01, vtrc-rc-backoffice SEC-VTRC-RC-05
ADR ที่ต้องมี (ฉบับเดียวทั้งองค์กร)
| หัวข้อ | ค่ามาตรฐานที่แนะนำ |
|---|---|
| Access token | HttpOnly Secure SameSite cookie |
| CSRF | double-submit หรือ synchronizer token คู่ cookie |
| Refresh | rotate + bind device/apiKey |
| SSO handoff | POST body — ห้าม path segment |
| XSS defense | CSP + dompurify (ทำไปแล้วใน 90 วันต้องคงไว้) |
| Reference impl | หนึ่ง repo เป็นแม่แบบให้ copy |
ROI: HttpOnly เป็นข้อใหญ่ใน frontend scorecards (#6 web, #7 rc-bo) — ทำหลัง CSP/dompurify เพราะต้องแตะ backend set-cookie
Bundle / build modernization (รอง)
จาก vtrc-web/07 ROI และ vtrc-rc-backoffice/07:
| งาน | เมื่อไหร่ |
|---|---|
| Code splitting / AntD tree-shake / lazy Stimulsoft | หลัง security cookie หรือคู่ขนานทีม frontend |
| Replace moment.js (ระวัง Buddhist year) | ปานกลาง — sync conversion |
| Webpack 5 + Node 18/20 | ใหญ่ — อย่าผสมกับ AntD major ใน PR เดียว |
| AntD v3→v4 บน vtrc-web | rewrite ทุก form (getFieldDecorator) — โปรเจกต์แยก |
Pillar 5 — Runtime & dependency LTS baseline
| Baseline ที่ควรประกาศ | เหตุผล |
|---|---|
| Node.js LTS เดียวสำหรับ repo ใหม่ | ตอนนี้กระจาย 12–20 |
| React / AntD policy สำหรับ frontend ใหม่ | เลิกเพิ่ม CRA 3.4.3 |
| รอบ upgrade ทุก 6 เดือนตาม Node LTS | ไม่ใช่ ad-hoc ตอนโดน CVE |
งาน EOL ที่เหลือจาก Wave 3 เดิม (LEG-*) จัดเข้าขอบฟ้านี้หลัง security/correctness — ยกเว้น typosquat (LEG-CU-01) ที่ควรจบใน 90 วันแล้ว
Pillar 6 — Service-to-service communication
| ปัจจุบัน | Target |
|---|---|
Hardcoded IP ไม่มี TLS (SEC-2WAYLINE-02) | internal DNS + HTTPS/mTLS |
Public endpoint + apiKey อ่อน / @Public() | gateway + service identity |
| Dual centralize callers ใน 2way | ชัด ownership ทีมต่อ endpoint |
อย่าเปิด endpoint ใหม่แบบ "ชั่วคราว public" — นั่นคือที่มาของ SEC-BENEFIT-03 / SEC-PMS-01
แผนรายไตรมาสภายในปี (หลังเดือน 6)
Q3 (เดือน 6–9)
├── Secret manager path + migrate ชุด A
├── JWT secret แยกต่อ service
├── ADR: @Public() / backdoor / token cookie
├── pdf-worker spike จาก vtrc-api
└── Freeze cross-DB connections ใหม่
Q4 (เดือน 9–12)
├── Migrate legacy secrets เข้า SM
├── HttpOnly cookie pilot (rc-backoffice หรือ web)
├── เริ่มแทน cross-DB ด้วย API (1 domain)
├── Backoffice fork decision executed
├── Placeholder archive/implement decision
└── LTS baseline ประกาศ + CI Node version gateMapping ROI ท้ายลำดับ → pillar
| Deep ROI (ท้าย) | Pillar |
|---|---|
| benefit #5 ย้าย secret manager | 1 |
| vtrc-api #5 แยก REST PDF / #8 BullMQ / #10 Vault | 1, 2 |
| pms #9 ลด workforce coupling / #10 tests | 2 |
| 2way #7–10 LINE HTTPS, encrypt, dead modules, Node unify | 2, 5, 6 |
| vtrc-web #6 HttpOnly / #8 Webpack 5 | 4, 5 |
| vtrc-rc-backoffice #7–10 cookie, CRA, MUI consolidate | 4, 5 |
| Vol 12.5 ROI #6 frontend hardening | 4 |
Success metrics (12 เดือน)
| Metric | เป้า |
|---|---|
| Secret ใน git (secret scan) | 0 บน default/canonical branches |
| Shared JWT ข้าม domain | 0 |
@Public() บน write/PII โดยไม่มี ADR | 0 |
| Hardcoded backdoor password | 0 |
| Cross-DB connections ใหม่หลัง freeze | 0 |
| Services ที่ boot จาก secret manager | ≥ ชุด A + vtrc-api |
| Frontend ที่ใช้ HttpOnly สำหรับ session | ≥ 1 pilot ใน prod |