IMPL PLAN 00 — Stop the Bleeding
3 งานเร่งด่วนก่อนทุกหมวด: ถอด hardcoded credential + rotate, ปิด JWT dev-bypass, ปิด endpoint anonymous อันตราย
อัปเดต: 2026-07-07
IMPL PLAN 00 — ปิดประตูที่เปิดอยู่ (Stop the Bleeding)
วันที่: 07/07/2026 · สถานะ: ⏳ รอ verify · Priority: 🔴 สูงสุด — ทำก่อนทุกหมวด อ้างอิง: SECURITY_UPLIFT_DISCUSSION_TOPICS.md หมวด 0 · evidence จาก ROADMAP §3.1
สรุป
3 งานเร่งด่วน: (1) ถอด hardcoded credential + rotate (2) ปิด JWT dev-bypass (3) ปิด endpoint anonymous อันตราย — ทั้งหมดเป็นการ “ปิดรู” ไม่ใช่สร้างของใหม่
งาน 0.1 — ถอด Hardcoded Credential + Rotate
ไฟล์ที่ต้องแก้
| # | ไฟล์ | Action |
|---|---|---|
| 1 | Backend_NotificationService/src/Notification01.API/Controllers/v1/DiagnosticsController.cs:144 | ลบ username="admin@exim.go.th", password="Exim@2026!" → อ่านจาก IConfiguration (มาจาก KV) หรือลบ endpoint ทิ้ง (แนะนำ — เป็น diagnostics ที่ไม่ควรอยู่ใน prod) |
| 2 | DiagnosticsController.cs:138,155 | endpoint ConnectivityEximToken/ConnectivityEximHeroSlides — ถอด [AllowAnonymous] → [Authorize(Policy="RequireAdmin")] |
| 3 | Backend_ThirdPartyService/src/ThirdParty01.API/appsettings.Production.json:9-11 | ลบค่า ConsumerKey/ConsumerSecret → เหลือ placeholder ว่าง "" แล้วให้ KV inject ผ่าน AddAzureKeyVaultWithServicePrefix |
Setup (Key Vault + IaC)
# 1. สร้าง secret ใน KV (ค่าใหม่หลัง rotate — ห้ามใช้ค่าเดิมที่รั่ว)
az keyvault secret set --vault-name <kv-prod> --name "thirdparty-EGovSettings--ConsumerKey" --value "<NEW>"
az keyvault secret set --vault-name <kv-prod> --name "thirdparty-EGovSettings--ConsumerSecret" --value "<NEW>"
# naming ตาม ServicePrefixKeyVaultSecretManager convention: {service-prefix}-{Section}--{Key}
⚠️ IaC parity (บังคับ): appsettings ที่แก้ต้อง update ที่ IaC config repo (Backend_Iac/config/) ทุก env ที่กระทบด้วย — CI deploy จะ override appsettings ใน image ด้วยค่าจาก IaC เสมอ
ลำดับ rotate (สำคัญ — ทำผิดลำดับ = service ล่ม)
sequenceDiagram
autonumber
participant Dev as ทีม Dev
participant Ext as เจ้าของ credential<br/>(EXIM admin / DGA)
participant KV as Key Vault
participant Repo as Git Repo
participant Pod as Service Pod
Dev->>Ext: ขอ credential ใหม่ (ยังไม่ revoke ตัวเก่า)
Ext-->>Dev: credential ใหม่
Dev->>KV: set secret ค่าใหม่
Dev->>Repo: commit — ลบ hardcoded, อ่านจาก config/KV
Dev->>Pod: deploy + restart
Pod->>KV: โหลด secret ใหม่ตอน startup
Dev->>Dev: ทดสอบ flow ที่ใช้ credential (token acquisition)
Dev->>Ext: ยืนยันใช้ค่าใหม่ได้แล้ว → revoke ค่าเก่า
Note over Repo: ขั้นสุดท้าย (ตัดสินใจทีม): purge git history<br/>ดู IMPL_PLAN_06 §Purge
Verify
grep -r "Exim@2026\|ConsumerSecret.*[A-Za-z0-9]\{8,\}" --include="*.cs" --include="*.json"= 0 hit (ยกเว้น placeholder ว่าง)- เรียก diagnostics endpoint โดยไม่มี token → ได้ 401/404
- ThirdParty ขอ DGA token สำเร็จด้วยค่าใหม่จาก KV
งาน 0.2 — ปิด JWT Dev-Bypass (2 แบบ + Template)
สถานะปัจจุบัน vs เป้าหมาย
sequenceDiagram
autonumber
participant C as Client (ใครก็ได้บน dev)
participant S as Backend Service (dev cluster)
participant E as Entra ID (JWKS)
rect rgb(255, 230, 230)
Note over C,S: ปัจจุบัน (แบบ A) — env=Development
C->>S: token ปลอมรูปร่าง JWT (เซ็นเองก็ได้)
S->>S: if(isDev) → SignatureValidator passthrough<br/>ValidateIssuer=false, ValidateLifetime=false
S-->>C: 200 OK — ผ่านหมด ❌
end
rect rgb(230, 255, 230)
Note over C,E: เป้าหมาย — ทุก env รวม dev (ตามแบบ UserService)
C->>S: token
S->>E: ดึง signing keys จาก .well-known/jwks (cache)
S->>S: ValidateIssuer + Audience + Lifetime + Signature
alt token ถูกต้อง
S-->>C: 200 OK
else token ปลอม/หมดอายุ
S-->>C: 401 Unauthorized ✅
end
end
ไฟล์ที่ต้องแก้ (เรียงตามลำดับ)
| ลำดับ | Repo/ไฟล์ | Action |
|---|---|---|
| 1 | Backend_Template/src/SuperApp01.API/.../AuthenticationExtensions.cs:27,44 | แก้ก่อน — กันของใหม่ติดเชื้อ: if (isDev) → if (false) (ตามแบบ Backend_UserService/.../AuthenticationExtensions.cs:38) |
| 2-10 | แบบ A อีก 9 service: Filter, Log, Consent, Centralized, Codex, FileManagement, Orchestrator, ThirdParty, Task — ไฟล์ AuthenticationExtensions.cs ของแต่ละตัว (บรรทัด ~27-28, 44-45) | if (isDev) → if (false) — service ละ 1 PR, ทดสอบ E2E บน dev ก่อน merge ตัวถัดไป |
| 11 | Backend_WorkflowService/src/WorkFlow01.API/Program.cs:73-81 + BypassAuthHandler.cs | แบบ B: ลบ BypassAuthHandler → port AddAppAuthentication (JWT + AzureAd:AllowedTenants) จาก UserService/Notification ตามที่ comment ในโค้ดระบุไว้เอง |
| 12 | Backend_ThirdPartyFXService (ทั้ง repo) | ไม่มี auth เลย: เพิ่ม AddAppAuthentication + UseAuthentication/UseAuthorization + [Authorize] บน POST counterRates + SignalR hub (ดู hub token ผ่าน query ?access_token= — ลอก pattern จาก NotificationService OnMessageReceived) |
💡 ระยะยาว: ย้าย logic นี้เป็น guard กลางใน Backend_Package (ดู IMPL_PLAN_05) — ผูก bypass กับ
#if DEBUGbuild flag ที่ไม่มีวัน true บน cluster image
Prerequisite ก่อนแก้: dev cluster ต้องมี token จริงใช้
dev-bypass ถูกใส่มาเพราะทีม dev ทดสอบสะดวก — ก่อนปิดต้องเตรียมทางเลือก:
- ยืนยัน flow ขอ token จริงบน dev ใช้งานได้ (Entra dev tenant / test users)
- อัปเดต Postman/Swagger collection ให้มี auth helper
- แจ้งทีมล่วงหน้า + กำหนดวัน cutover ต่อ service
Verify
- Syntactic:
grep -rn "if (isDev)" --include="AuthenticationExtensions.cs"= 0 hit ทุก repo ·grep -rn "BypassAuthHandler"= 0 hit - Functional (ต่อ service): บน dev cluster — token ปลอม → 401 · token จริงจาก Entra → 200 · E2E suite เขียว
งาน 0.3 — ปิด Endpoint Anonymous อันตราย
| ไฟล์ | ปัญหา | Action |
|---|---|---|
Backend_NotificationService/.../MaintenanceController.cs:14,17 | POST toggle เปิด/ปิด maintenance mode ได้โดยไม่ auth | [Authorize(Policy="RequireAdmin")] หรือ [RequireApiKey] (ถ้าเรียกจาก ops tooling — ต้อง verify Filters.Add<ApiKeyAuthFilter>() ลงทะเบียนแล้ว) |
| Notification whoami endpoint | คืน raw JWT header/payload/claims แบบ anonymous — recon surface | ลบ หรือ [Authorize] + จำกัดข้อมูลที่คืน |
Backend_ConsentService/.../ConsentController.cs:14-16 | ไม่มี attribute เลย — POST /receipt + GET /user?identifier= เปิด anonymous | ตัดสินใจ end-state ก่อน (D-checklist): user-facing → [Authorize] · service-to-service → [AllowAnonymous]+[RequireApiKey] (⚠️ Consent ยังไม่ register ApiKeyAuthFilter — ต้องเพิ่มใน AddControllers ก่อน มิฉะนั้น attribute เงียบ = no-op) |
Verify
ยิงทุก endpoint ข้างบนโดยไม่มี credential → ต้องได้ 401/403 ทุกตัว (สคริปต์ curl เก็บเป็น regression test)
⚠️ ข้อควรระวัง
- ห้าม rotate credential ก่อนมีค่าใหม่พร้อมใช้ — ลำดับตาม sequence diagram งาน 0.1
- ห้ามปิด bypass ทุก service พร้อมกัน — ทีละ service, มี rollback plan (revert PR + redeploy)
- Consent
POST /receipt— เลือกทางเดียว (JWT หรือ API key) ห้ามปล่อยกำกวม - งานนี้ไม่รวม FallbackPolicy — นั่นคือ IMPL_PLAN_05 ซึ่งมีลำดับปลอดภัยของตัวเอง
✅ Definition of Done
- credential ทั้ง 2 จุดถอด + rotate + KV + IaC parity ครบ
- dev-bypass = 0 hit ทั้ง platform (แบบ A + B) + ThirdPartyFX มี auth
- endpoint อันตราย 3 กลุ่มคืน 401/403
- E2E เขียวบน dev + SIT หลังทุกการเปลี่ยนแปลง
🎯 ถ้าเป็นผมจะทำอย่างไร (ความเห็น Fable)
- หมวดนี้ไม่ควรรอมติประชุม — 0.1 กับ 0.2 (เฉพาะ Template) ไม่มีประเด็นให้ถก ทำได้เลยภายใน 48 ชม. แล้วรายงานผลในที่ประชุมแทน — สิ่งที่ต้องรอคือการ coordinate rotate กับ EXIM/DGA เท่านั้น
- DiagnosticsController: ผมเลือกลบ endpoint ทิ้ง ไม่ใช่ย้าย credential ไป KV — connectivity check ควรเป็น health check/synthetic monitor ไม่ใช่ endpoint ที่ถือ credential จริงค้างใน API surface
- ลำดับของผม: Template (กันของใหม่ติดเชื้อ) → rotate credential → ปิด endpoint อันตราย (0.3) → dev-bypass ทีละ service — เหตุผล: สามอย่างแรกเสร็จในวันเดียว ส่วน bypass 9 service คืองานยาวที่ต้องมี token จริงบน dev พร้อมก่อน (prerequisite สำคัญกว่าตัวแก้)
- อย่าใช้
if (false)เป็น end-state — มันคือ dead code ที่รอคน flip กลับ · ใช้เป็น hotfix ชั่วคราวได้ แต่ end-state ต้องเป็น guard กลาง#if DEBUGใน Package (IMPL_PLAN_05) — ลบif (false)ทิ้งตอน rollout helper - สิ่งที่คนมักพลาด: rotate แล้วลืมตรวจว่าค่าเก่าถูก revoke จริง — rotate โดยไม่ revoke = ค่าที่รั่วยังใช้ได้ เท่ากับไม่ได้ rotate — ใส่ “ยืนยัน revoke แล้ว” เป็น checkbox สุดท้ายเสมอ
📚 อ้างอิงมาตรฐาน (สำหรับใช้ในที่ประชุม):
| หลักการ | แหล่งอ้างอิง |
|---|---|
| Hardcoded credential / auth bypass = Broken Authentication | OWASP API2 — Broken Authentication |
| วิธีจัดการ secret ที่รั่วแล้ว (rotate + ถือว่า compromised) | OWASP Secrets Management Cheat Sheet |
| Purge secret ออกจาก git history | GitHub Docs — Removing sensitive data from a repository |
| Endpoint anonymous/misconfig ที่เปิดค้าง | OWASP API8 — Security Misconfiguration |
| เก็บ secret ใน vault แทน config/source | Azure Key Vault — Best practices |