API Surface — เส้นไหนควรถูกลงทะเบียนบน APIM
ลดจำนวน endpoint ที่ client ภายนอกเรียกได้ โดยคุมที่การ generate OpenAPI spec — คนละมิติกับ API Restriction 00-04 ที่คุมว่าใครเรียกได้
อัปเดต: 2026-09-09
API Surface — เส้นไหนควรถูกลงทะเบียนบน APIM
เอกสารนี้เขียนขึ้นก่อนเริ่มลงมือ เพื่อให้เห็นภาพรวมว่าจะทำอะไรบ้าง ลำดับไหน และมีอะไรที่ยังต้องตัดสินก่อนเริ่ม
รอบแก้ 09/09/2026 ตาม review — ตัด EmployeeAdapter ออกจากขอบเขต · Lane BE ตัดสินแล้วว่าไปทาง ก · ปิดคำถามเรื่อง APIM import · เพิ่มข้อเท็จจริงว่า UAT ใช้ Kong ไม่ใช่ nginx · ปรับความเสี่ยงตามที่ยอมรับแล้ว
รอบแก้ที่ห้า 09/09 — POST /sentinel/internal/resolve-session แก้ถังจาก NONE เป็น BE ·
APIM policy เรียกมันจริงทุก request แต่เรียกตรงไป internal host ไม่ได้ผ่าน operation ที่ register บน APIM
⇒ มันคือ endpoint ที่มี backend caller ไม่ใช่เส้นที่ไม่มีใครเรียก · BE 31→32 · NONE 245→244
รอบแก้ที่สี่ 09/09 — Lane NONE ตัดโหมดทิ้งทั้งหมด แปะ [NotExposed] แล้วปิดเลย ·
งานที่เหลือยุบเป็น 2 ก้อน: package 10.32.0 + Lane NONE 245 เส้น · และก้อน PreProd
(ย้าย S2S ของ Sentinel/Filter ไป cluster-local · export operation list · re-import spec)
รอบแก้ที่สาม 09/09 — Lane NONE เปลี่ยนจากการซ่อนจาก spec เป็นการปิดที่ routing table จริง ผ่านกลไกใหม่ใน SupApp_util_lib · รายละเอียดและโค้ดอยู่ที่ Package — ปิด endpoint ที่ routing table
รอบตรวจซ้ำ 09/09 ก่อนหน้านั้น — เจอเส้นที่ห้ามซ่อนเด็ดขาด 1 เส้น (ข้อ 8 ข้อ 3) · แยก APIM analytics ออกจาก OTEL ซึ่งเป็นคนละตัวกัน (ข้อ 8 ข้อ 2) · เพิ่มขั้นตรวจ generator ก่อนเริ่ม (ข้อ 6 ขั้นที่ 1) · SIT ไม่มี workload รันแล้ว
สถานะ ณ 09/09/2026
| ก้อนงาน | สถานะ |
|---|---|
| Lane BE — 32 เส้น | 31 เส้นลงโค้ดแล้ว 8 repo · build ผ่านทุกตัว · verify ที่ swagger จริงแล้ว · PR รอ merge · เหลือ resolve-session ของ SentinelGateway อีก 1 เส้น |
| Lane NONE — 244 เส้น | ยังไม่เริ่ม · ต้องรอ package 10.32.0 ออกก่อน |
| ก้อน PreProd | ยังไม่เริ่ม · เลื่อนไปทำทีเดียวตอนขึ้น PreProd |
Lane BE — ที่ทำไปแล้ว
แปะ [ApiExplorerSettings(IgnoreApi = true)] 31 เส้น ใน 8 repo — ThirdParty 1 · Workflow 1 ·
Centralized 2 · Codex 3 · FileManagement 3 · Notification 8 · Task 6 · UserService 7
ทำใน worktree ของ security uplift เดิม ({Repo}-sec → feature/{service}-security-apply) ยกเว้น
UserService ที่ PR ชุดนั้น merge ไปแล้ว จึงเปิด branch ใหม่ feature/us-api-surface-be-lane
จาก origin/development
หลักฐาน — ทุก repo dotnet build ได้ 0 Error · git diff เป็น insert อย่างเดียว
ไม่มีบรรทัดถูกลบสักบรรทัด ⇒ ไม่มี Thai literal เสียหาย · และรัน service จริงแล้วดึง
/swagger/v1/swagger.json มาตรวจครบทั้ง 8 repo — endpoint ที่ตั้งใจซ่อนหายครบ 31/31
และ sibling ที่ต้องยังอยู่ยังอยู่ทุกตัว ⇒ ไม่ over-hide
รันในเครื่องได้โดยส่ง KeyVault__VaultUri=https://sua-azure-nonprd-kv.vault.azure.net/
เป็น env var ตอนรัน (ใช้ credential ของ az login ที่ล็อกอินอยู่) ไม่ต้องแก้ไฟล์ใด ๆ
ตัวอย่างที่ตรวจแล้วตรงเป๊ะที่สุดคือ CodexService — spec เหลือ 60 operations = audit 63 − BE 3 · repo อื่น ops ไม่ตรงกับ audit เป๊ะบ้าง (workflow 21 · centralized 26 · user 162) เพราะจำนวนใน spec จริง กับจำนวนที่ audit นับจาก controller ไม่ตรงกันอยู่ก่อนแล้ว ไม่ได้มาจากงานนี้
PR ที่รอ merge
| repo | branch / PR |
|---|---|
| Backend_ThirdPartyService | feature/third-party-security-apply — PR #3036 |
| Backend_WorkflowService | feature/workflow-security-apply — PR #3037 |
| Backend_Centralized | feature/centralized-security-apply — PR #3042 |
| Backend_CodexService | feature/codex-security-apply — PR #3031 |
| Backend_FileManagementService | feature/file-management-security-apply — PR #3035 |
| Backend_NotificationService | feature/notification-security-apply — PR #3033 |
| Backend_TaskService | feature/task-security-apply — PR #3034 |
| Backend_UserService | feature/us-api-surface-be-lane — PR #3078 (เปิดใหม่) |
เจ็ดใบแรกเป็น PR ของ security uplift ที่มีอยู่แล้ว งานนี้ push เพิ่มเข้าไปใน branch เดิม
เอกสารนี้ต่างจากชุด API Restriction 00-04 ยังไง
สองงานนี้อยู่คนละมิติและทำพร้อมกันได้ ไม่ขัดกัน
| ชุด 00-04 | เอกสารนี้ | |
|---|---|---|
| ตอบคำถาม | ใครเรียก endpoint นี้ได้ | endpoint นี้ควรเปิดให้เรียกจากภายนอกหรือเปล่า |
| กลไก | app context + permission/role ที่ตัดสินใน application | ไม่ generate ลง OpenAPI spec ⇒ ไม่ถูก import ขึ้น APIM |
| ตัดสินตอน | request วิ่งเข้ามาแล้ว | ก่อนหน้านั้น — ตอนลงทะเบียน API |
ไฟล์ 00 หัวข้อ “ทางที่ไม่เลือก” ปฏิเสธ “ตัดสินที่ APIM ด้วย allow-list ว่า app ไหนเรียก path ไหนได้” ซึ่งยังเป็นมติที่ใช้อยู่ — เอกสารนี้ไม่ได้รื้อมตินั้น เพราะไม่ได้เสนอให้ APIM ตัดสินสิทธิ์ราย app หรือราย role สิ่งที่เอกสารนี้ทำคือ ลดจำนวนเส้นที่ถูกเปิดออกไปตั้งแต่แรก ส่วนเส้นที่เหลือยังตัดสินสิทธิ์ที่ application ตามชุด 00-04 เหมือนเดิม
จุดที่ทั้งสองงานมาบรรจบกันคือไฟล์ 04 ที่ระบุว่า
warm/{oid} “การเข้าถึงจาก Internet ยังต้องตรวจ network/APIM จริง ไม่อนุมานจาก attribute อย่างเดียว” —
เอกสารนี้คือส่วนที่ไปทำตรงนั้น
1. เป้าหมาย
ทำให้ endpoint ที่ไม่ได้ตั้งใจให้ client ภายนอกเรียก ไม่ถูกลงทะเบียนบน APIM โดยควบคุมที่ต้นทาง คือการ generate OpenAPI spec แทนที่จะไปไล่จัดการราย operation บน APIM ด้วยมือ
หลักการ: APIM ลงทะเบียน API ด้วยการ import OpenAPI spec ⇒ สิ่งที่ไม่อยู่ใน spec จะไม่ถูก import ⇒ ไม่มี operation บน APIM ⇒ client ภายนอกเรียกไม่ได้
2. ขอบเขต
อยู่ในงาน — 11 service · Centralized · Codex · Consent · FileManagement · Notification · Orchestrator · SentinelGateway · Task · ThirdParty · User · Workflow
ตัดออกจากงานนี้แล้ว
- EmployeeAdapterService — ตัดออกตาม review 09/09 · service นี้มี 1 endpoint และอยู่ในถัง NONE จึงหักออกจากตัวเลขตามข้อ 3
- FX ทั้งหมด —
fx-service·fxrate·fxcodex·fxcontract·fxfile·fxorchestrator·fx-report·thirdpartyfx-service— ไม่นับเป็น caller และไม่ต้องแก้ - ReportService — ไม่อยู่ในงานนี้
- QA_E2eTests / QA_E2eTests_Admin_Portal — ไม่นับเป็น caller · endpoint ที่มีแต่ QA เรียกยังถือเป็น “ไม่มีคนใช้” · ยอมรับแล้วว่า QA suite จะยิงไม่ผ่านหลังงานนี้เสร็จ และไม่ถือเป็นเรื่องที่ต้องจัดการ
3. ตัวเลขที่ใช้ทำงาน
ตั้งต้นจาก audit 2026-09-07 ที่ไล่ controller ครบ 418 endpoint แล้วแยกเป็น 4 ถังตามว่าใครเรียก: FE มีหน้าจอเรียก · BE มี service อื่นเรียก · BOTH มีทั้งสอง · NONE ไม่พบผู้เรียกเลย
audit ได้ FE 122 · BE 29 · BOTH 15 · NONE 252 · หลัง scan repo ที่ audit ตัดออก และหลังตัดขอบเขตตามข้อ 2 ตัวเลขที่ใช้จริงคือ
| ถัง | audit | แก้ | ปัจจุบัน | ทำอะไร |
|---|---|---|---|---|
| FE | 122 | +4 | 126 | เก็บไว้ ลงทะเบียน APIM ตามปกติ |
| BE | 29 | +3 | 32 | Lane BE — ซ่อนจาก spec |
| BOTH | 15 | — | 15 | เก็บไว้ ลงทะเบียน APIM ตามปกติ |
| NONE | 252 | −8 | 244 | Lane NONE — ลบออกจาก routing table |
| รวม | 418 | −1 | 417 |
การแก้แบ่งเป็นสองเรื่อง — 7 เส้นที่จัดผิดถัง ตามที่อธิบายด้านล่าง และ 1 เส้นของ EmployeeAdapterService ที่หายไปเพราะตัดออกจากขอบเขต
ที่มาของ 7 เส้นที่จัดผิดถัง
ทั้งหมดเป็นเส้นที่ audit จัดผิด เพราะ repo ต้นทางของ caller ถูกตัดออกจาก scan ตั้งแต่แรก
NONE → FE (4 เส้น) — Frontend_LINE_Connectivity เป็น browser client ที่ live อยู่นอก scope
4 frontend ของ audit และเรียกผ่าน https://gateway-dev.exim.go.th/userservice-api/... คือผ่าน APIM จริง
(Frontend_LINE_Connectivity/src/environments/environment.ts:16,34-37)
POST /api/user-service/v1/line-connectivity/register/startPOST /api/user-service/v1/line-connectivity/unsubscribe/startGET /api/user-service/v1/line-connectivity/instances/{id}POST /api/user-service/v1/line-connectivity/instances/{id}/steps/{stepType}
ทั้งสี่เส้นเป็น AllowAnonymous
NONE → BE (2 เส้น) — Backend_FilterService เป็น backend ที่ deploy อยู่จริงแต่ถูกตัดออกจาก scan
POST /api/notification-service/v1/notifications/batch—Filter02.Infrastructure/Publishing/HttpMessagePublisher.cs:26เป็นconst stringGET /api/user-service/v1/subscriptions/users—Filter02.Infrastructure/Gateway/UserServiceClient.cs:39
NONE → BE (1 เส้น) — POST /sentinel/internal/resolve-session · audit จัดเป็น NONE เพราะไม่มีโค้ด
ใน repo ไหนเรียกมัน แต่คนเรียกคือ APIM policy เอง — sentinel-facade-inbound-v6.xml:158 ยิง
send-request ไปที่ http://api.sua-az-{env}.internal/sentinel/internal/resolve-session ทุก request
ที่ผ่าน facade เพื่อแลก cookie เป็น JWT · เป็น controller จริงที่ InternalController.cs:22
[Route("sentinel/internal")]
จุดที่ทำให้มันเป็น BE ไม่ใช่ NONE: policy เรียกตรงไป internal host ไม่ได้ผ่าน operation ที่ register ไว้บน APIM — เส้นนี้จึงไม่เคยถูกลงทะเบียนเป็น operation ตั้งแต่แรก และไม่ต้องลงทะเบียนด้วย · มันคือ endpoint ที่มี backend caller เต็มตัว ไม่ใช่เส้นที่ทำเผื่อไว้
ผล scan repo ที่เหลือ — ไม่มีการแก้ตัวเลขเพิ่ม
LogService เจอ 2 จุดแต่ตายทั้งคู่ (จุดหนึ่งยิงแล้วได้ 404 อยู่แล้ว · อีกจุดเป็น config ที่ไม่มีโค้ดอ่าน) · ThirdPartyFXService 0 · Console_SyncEmployee 0 · QA_E2eTests เจอ 8 เส้นแต่ตัดสินแล้วว่าไม่นับ
ข้อจำกัดที่ยังปิดไม่ได้
Backend_ThirdPartyFXService—git fetch originล้มเหลวTF401019(repo not found หรือถูก rename ฝั่ง server) จึง scan จาก ref ค้างลงวันที่ 2026-06-04 · ไม่บล็อกงานเพราะ FX ถูกตัดออกแล้ว- audit เป็น static analysis ล้วน — path ที่ประกอบแบบ dynamic เต็มรูปจับไม่ได้ · Postman collection และ cronjob ยังไม่ถูกนับ ⇒ ถัง NONE แปลว่า “ไม่พบ caller” ไม่ใช่ “ไม่มี caller” · ข้อนี้สำคัญขึ้นมากหลังตัดสินว่าไม่ใช้ telemetry เป็นด่าน (ดูข้อ 6 และ 8)
4. สองเลนของงาน
สองเลนใช้กลไกคนละตัว เพราะต้องการผลคนละอย่าง
Lane NONE — 244 เส้น · ปิดที่ routing table ไม่ใช่แค่ซ่อนจาก spec
endpoint กลุ่มนี้คือของที่ทำเผื่อไว้และยังไม่มีใครใช้ ⇒ ไม่ต้องให้เรียกได้จากทางไหนเลย
ไม่ใช่แค่ไม่โฆษณา · [ApiExplorerSettings(IgnoreApi = true)] ทำให้หายจาก spec ก็จริง
แต่ route ยัง map อยู่ใน ASP.NET routing table และยังเรียกได้จากใน cluster ⇒ ไม่พอสำหรับเลนนี้
⇒ ใช้กลไกใหม่ใน SupApp_util_lib ที่ลบ action ออกจาก application model ตอน startup
(IApplicationModelConvention) ⇒ ไม่มี route ในตาราง ได้ 404 จริง และหายจาก spec ไปพร้อมกัน ·
ไม่มีโหมด ไม่มี config key — แปะ attribute แล้วปิดเลย (มติ 09/09) ⇒ เส้นที่ audit เดาผิดจะ 404
ทันทีที่ deploy กู้ได้ทางเดียวคือถอด attribute แล้ว deploy ใหม่ · ตัวลดความเสี่ยงคือไล่ทีละ service และทำ dev ก่อน uat
โค้ดเต็ม เหตุผลที่เลือกกลไกนี้ และข้อควรระวัง อยู่ที่ Package — ปิด endpoint ที่ routing table
🔴 ลำดับของเลนนี้สลับไม่ได้ — ทำ package ให้เสร็จ → bump เป็น 10.32.0 (10.31.0 เป็นของ PR #3050) → publish →
apply เข้า service → แล้วค่อยไล่แปะ attribute ทีละเส้น · แปะก่อนที่ package จะมี type นั้น
จะ compile ไม่ผ่าน
Lane BE — 32 เส้น · ตัดสินแล้ว 09/09: [ApiExplorerSettings(IgnoreApi = true)] ไม่ย้าย path
endpoint กลุ่มนี้มี caller จริงเป็น backend ด้วยกันเอง จึงต้องยังเรียกได้จากใน cluster ⇒
ต้องการแค่ให้หายจาก spec ไม่ต้องการให้หายจาก routing table ⇒ [ApiExplorerSettings(IgnoreApi = true)]
ตอบโจทย์พอดีและ ทำได้เลยโดยไม่ต้องรอ package · ไม่แตะ path
เหตุผลที่เลือกทางนี้ — ปลอดภัยกว่าและความเสี่ยงต่ำกว่า · path ไม่เปลี่ยน แปลว่าไม่มี caller ตัวไหนพัง และย้อนกลับได้ด้วยการถอด attribute ตัวเดียว
ทางที่ไม่เลือก คือย้าย path ไปใต้ prefix /internal/ แล้วกัน prefix นั้นออกจาก ingress ·
ทางนั้นให้ด่านเพิ่มอีกชั้นจริง แต่ต้องแก้ path ทั้งฝั่ง provider และ caller ทุกตัว
และ ทำคนละวิธีกันในแต่ละ env เพราะ dev เป็น nginx ส่วน uat เป็น Kong (ดูข้อ 7)
สิ่งที่ต้องเข้าใจให้ตรงกันเรื่อง [ApiExplorerSettings]
[ApiExplorerSettings(IgnoreApi = true)] ไม่ใช่ด่านความปลอดภัย — มันซ่อนแค่จาก swagger doc เท่านั้น ·
route ยัง map อยู่ใน ASP.NET routing table และยังเรียกได้ทุกทางที่เข้าถึง pod ได้ ทั้งจากใน cluster,
ผ่าน ingress และผ่าน APIM ถ้า operation นั้นเคยถูกลงทะเบียนไว้แล้ว
สำหรับ Lane BE นั่นคือสิ่งที่ต้องการพอดี — caller ที่เป็น backend ต้องยังเรียกได้ · สำหรับ Lane NONE ไม่พอ จึงต้องใช้กลไกของ package แทน
⇒ ทั้งสองเลน การแปะ attribute อย่างเดียวไม่ปิดของเก่าที่ลงทะเบียนไว้บน APIM แล้ว · ต้อง re-import spec ให้ operation เก่าหายไปจาก APIM ด้วย และต้องรู้ก่อนว่าตอนนี้บน APIM มีอะไรอยู่บ้าง
flowchart LR
C["client ภายนอก"] --> A["APIM<br/>มีเฉพาะ operation ที่อยู่ใน spec"]
A --> I["ingress<br/>wildcard prefix ต่อ service"]
I --> P["pod ของ service"]
S["service อื่นใน cluster"] -->|"http://{svc}.{namespace}.svc.cluster.local<br/>ไม่ผ่าน APIM และไม่ผ่าน ingress"| P
style A fill:#ffb74d
style I fill:#ef5350,color:#fff
5. งานที่ต้องทำก่อน — ย้าย S2S ที่ยังวิ่งผ่าน public gateway ไป cluster-local
ทำไมต้องทำก่อน ปลายทางของงานนี้คือ operation ของ Lane BE และ Lane NONE หายไปจาก APIM ·
service ที่เรียก peer ผ่าน https://gateway-dev.exim.go.th/... จะพังทันทีที่ operation หาย
ค่าที่ใช้ตรวจมาจาก Backend_Iac/config/{service}/{env}/appsettings.json คือไฟล์ที่ overlay ทับตอน
build image ไม่ใช่ appsettings ใน service repo · URL เหล่านี้ไม่ได้มาจาก KeyVault จึงถือค่าใน IaC
เป็นค่าจริง
5.1 ต้องแก้โค้ดด้วย ไม่ใช่แค่ IaC
Backend_FilterService/src/Filter02.Infrastructure/Gateway/UserServiceClient.cs:39 ประกอบ path เป็น
$"userservice-api/api/user-service/v1/subscriptions/users{query}" — prefix userservice-api/
ฝังอยู่ใน path ⇒ แก้ BaseUrl ใน IaC อย่างเดียวจะได้ 404 ต้องลบ prefix ออกจากโค้ดพร้อมกัน
5.2 แก้ IaC อย่างเดียวพอ
| เส้นทาง | ไฟล์ IaC | key | ค่าใหม่ |
|---|---|---|---|
| SentinelGateway → UserService | config/sentinel-gateway-service/dev/appsettings.json:94 · uat:89 | UserServiceSettings.BaseUrl | http://user-service.superappdev.svc.cluster.local (uat ใช้ namespace superappuat) |
| FilterService → Notification | config/filter-service/dev/appsettings.json:87 · uat:94 | NotificationService.BaseUrl | http://notification-service.superappdev.svc.cluster.local |
| Centralized → Codex | config/centralized-service/sit/appsettings.json:85 | CodexBaseUrl | IaC cleanup เท่านั้น ไม่ใช่งานที่บล็อก — dev/uat เป็น cluster-local อยู่แล้ว และ sit ไม่มี workload รัน |
| ThirdParty → Codex | config/thirdparty-service/sit/appsettings.json:69 | BaseUrl | IaC cleanup เท่านั้น ไม่ใช่งานที่บล็อก — เหตุผลเดียวกับแถวบน |
SIT ถูกถอดออกจาก AKS ไปแล้วเมื่อ 23/08 — Backend_Iac/src/yamls/ เหลือแค่ dev uat security ไม่มี sit แต่ config/*/sit ยังค้างอยู่ 22 โฟลเดอร์ ⇒ สองแถว sit ข้างบนและ LogService ในข้อ 5.3 เป็นการเก็บกวาด config ของ env ที่ไม่มีอะไรรัน ไม่ใช่ prerequisite ที่บล็อกงาน งานที่บล็อกจริงคือ dev กับ uat เท่านั้น
หลักฐานว่าเป็น IaC-only — call site ของ Sentinel คือ
SentinelGateway01.API/Extensions/AuthenticationExtensions.cs:405
PostAsync($"api/user-service/v1/cache/users/warm/{oid}") ไม่มี prefix ใน path ·
Filter → Notification คือ HttpMessagePublisher.cs:26 ที่เป็น
const string BatchEndpoint = "/api/notification-service/v1/notifications/batch" ไม่มี prefix เช่นกัน ·
Centralized CodexServiceClient.cs และ ThirdParty CodexConfigClient.cs ประกอบ URL เป็น baseUrl + path
ไม่มี prefix ฝังใน path
5.3 config ที่ตายแล้ว — ลบทิ้งได้ ไม่ต้องย้าย
| service | ไฟล์ IaC | เหตุผล |
|---|---|---|
| CodexService | config/codex-service/dev/appsettings.json:84,88 · uat:86,90 | UserService.BaseUrl และ NotificationService.BaseUrl ผูกกับ IApiGatewayClient ที่ ไม่มี consumer สักตัว — ทั้ง repo มี 3 hit คือ interface, implementation และ DI registration เท่านั้น |
| LogService | config/log-service/sit/appsettings.json:116 | Apim:BaseUrl ผูกกับ IApiGatewayClient ตัวเดียวกันที่ไม่มี consumer · section นี้มีเฉพาะ sit ไม่มีใน dev และ uat เลย |
การแก้ IaC ทุกจุดต้องทำครบทุก env ที่กระทบตามกฎ parity — แก้ใน service repo อย่างเดียวไม่มีผลบน cluster
และ guard check-config-parity.py จะทำให้ build fail ถ้า IaC ขาด key ที่ service มี
6. ลำดับงาน — สลับลำดับไม่ได้
เรื่อง branch ตัดสินแล้ว 09/09 — งานนี้ทำใน branch เดียวกับ security uplift ของแต่ละ repo ไม่ต้องเปิด branch แยกและไม่ต้องรอ PR ชุดนั้น merge ก่อน · ยกเว้น UserService กับ ConsentService ที่ PR security uplift merge ไปแล้วจึงไม่มี branch ค้างให้เกาะ ⇒ สองตัวนี้เปิด branch ใหม่จาก development — UserService เป็นเลนที่ใหญ่ที่สุด (164 endpoint · NONE 97)
- 🔴 ตรวจว่า attribute มีผลจริงกับ generator ของแต่ละ service — ยังไม่มีใครเช็ค และ Lane BE ยืนอยู่บนข้อนี้
(Lane NONE ไม่ขึ้นกับข้อนี้แล้ว เพราะกลไกของ package ลบ action ออกจาก application model
ก่อนที่ generator ตัวไหนจะได้เห็น)
ต่อ service ต้องได้คำตอบสามข้อ (grep อย่างละครั้ง ไม่ต้อง build)
- ใช้ generator ตัวไหน ·
AddSwaggerGenของ Swashbuckle หรือMapOpenApiของMicrosoft.AspNetCore.OpenApi—api-restriction-plan-04-implementation.md:155แสดงว่า UserService มีMapOpenApiอยู่ - URL ที่ import ขึ้น APIM จริงคืออันไหน ·
/swagger/v1/swagger.jsonหรือ/openapi/v1.json - มี
IDocumentFilterตัวไหนที่ไล่ controller เองโดยไม่ผ่าน ApiExplorer หรือเปล่า — UserService และ ThirdParty มีPathSummaryOperationFilterอยู่ตามSWAGGER_OPENAPI_FIX_PLAN[ApiExplorerSettings(IgnoreApi = true)]มีผลกับทั้ง Swashbuckle และMapOpenApiเพราะทั้งคู่อ่าน ApiExplorer แต่IDocumentFilterที่ reflect controller ตรง ๆ จะข้าม attribute นี้ไปเลย
- ใช้ generator ตัวไหน ·
- ตรวจ ingress class ของทุก env (ดูข้อ 7) — dev กับ uat ใช้ ingress controller คนละตัว · ทางที่เลือก (annotation อย่างเดียว) ไม่ได้พึ่ง ingress จึงไม่บล็อก แต่ต้องรู้ภาพจริงก่อนไปแตะอะไรที่ระดับ routing
- Export operation ทั้งหมดที่อยู่บน APIM ตอนนี้ ทุก service API ทั้ง dev/sit/uat ลงเป็น JSON
ที่ commit ไว้ใน
Backend_Iac· ตอนนี้ ไม่มี inventory ของ APIM อยู่ใน version control เลย มีแต่ไฟล์ policy ⇒ ถ้า operation หายผิดตัวจะไม่มี body ให้ PUT กลับ · export นี้เป็นทั้ง inventory, baseline สำหรับ diff และ artifact สำหรับ rollback ในตัวเดียว ขั้นนี้สำคัญขึ้นมากถ้าไม่มี telemetry มาช่วยยืนยัน — export คือหลักฐานชิ้นเดียวที่จะมี - ทำงานเตรียมข้อ 5 — ย้าย S2S ที่ยังผ่าน gateway ไป cluster-local ให้ครบก่อน
- แปะ attribute — Lane BE ทำได้เลย ไม่ต้องรออะไร · Lane NONE ต้องรอ package
10.32.0ออกและ apply เข้า service ก่อน ตามลำดับในเอกสาร package · ทำใน branch ของ security uplift ตามที่ตัดสินไว้ ยกเว้นBackend_Centralizedที่origin/developmentยัง build ไม่ผ่าน NU1903 อยู่ ⇒ repo นั้น verify อะไรไม่ได้จนกว่าจะแยกตัวแก้ออกมาหรือ merge PR ที่ถือตัวแก้อยู่ - Re-import spec ขึ้น APIM ทีละ env dev ก่อน uat · ตรวจ diff กับ export จากขั้นที่ 3 ว่าหายเฉพาะที่ตั้งใจ · ต้องเช็คเรื่อง Sentinel ก่อน import ตามข้อ 8 ข้อ 3
ขั้นที่หายไปจากแผนเดิม
- “ยืนยันว่า import ทับได้จริงหรือไม่” — ปิดแล้ว · import ทับได้ และเจ้าของงานจะเป็นคนทำเอง
- “ตรวจ APIM diagnostics แล้วดึงจำนวน call ราย operation” — ยังไม่ตัดออกเสียทีเดียว ดูข้อ 8 ข้อ 2
7. ingress ต่าง env กัน — ตรวจแล้ว 09/09
| env | ingress class | path pattern | หมายเหตุ |
|---|---|---|---|
| dev | nginx-internal (27 ตัว) | path: /api/{svc}(/|$)(.*) + nginx.ingress.kubernetes.io/rewrite-target | regex + rewrite |
| uat | kong-uat (22 ตัว) | path: /api/{svc} · pathType: Prefix · konghq.com/strip-path: "false" | ไม่มี regex ไม่มี rewrite |
| prod | ยังตรวจไม่ได้ | — | Backend_Iac ไม่มีโฟลเดอร์ prod เลย มีแค่ dev/sit/uat |
บน uat ยังเหลือ nginx อยู่ 3 ไฟล์คือ fx-service-ingress.yaml (FX ตัดออกแล้ว) ·
redisinsight-service-ingress.yaml · ingress.yaml.bak
ผลต่องานนี้ — ทั้งสอง controller ปล่อยทุก path ใต้ prefix ของ service ผ่านเหมือนกัน
(nginx ด้วย regex (.*) · Kong ด้วย pathType: Prefix) ⇒ ข้อความที่ว่า “ingress ไม่กรองราย endpoint”
ยังจริงทั้งสอง env แค่คนละกลไก
และนี่คือเหตุผลเพิ่มเติมที่ทาง ก ถูก — ถ้าจะกัน /internal/ ออกจาก ingress ต้องเขียนคนละแบบสองที่
(nginx ใช้ regex negative lookahead หรือ snippet ที่ยังไม่ได้ตรวจว่าเปิดไหม · Kong ต้องใช้
konghq.com/plugins หรือ KongIngress) แล้วยังมี prod ที่ยังไม่รู้ว่าใช้อะไรอีก
8. ความเสี่ยงที่ยังเหลือ
-
QA suite จะพัง — ยอมรับแล้ว ไม่ต้องจัดการ · 6 เส้นที่มีแต่ QA เรียกจะถูกซ่อน คือ
document-numbers/policiesทั้ง POST/PUT/DELETE ·admin/flow-definitions·admin/invitations·users/me/details -
ไม่มี telemetry มายืนยัน — แต่ยังไม่ได้เช็คให้ครบ · ที่ยืนยันแล้วคือ OTEL ใช้งานไม่ได้ · APIM analytics เป็นคนละตัวกับ OTEL — เป็น diagnostic setting ฝั่ง Azure ของ APIM resource เอง ไม่เกี่ยวกับ Tempo หรือ collector บน cluster ·
Backend_Iac/docs/knowledge/EMISSARY_VS_APIM_MIGRATION_ANALYSIS.md:641ระบุ “Lost APIM observability” เป็นต้นทุนของการย้าย ⇒ น่าจะเปิดอยู่ แต่ยังไม่ verify · เช็คด้วยคำสั่งเดียว read-only:az monitor diagnostic-settings list --resource <apim-resource-id>· ถ้าเปิดอยู่จะได้จำนวน call ราย operation ย้อนเท่าที่ retention มี ฟรี ๆ และมันจับ caller ประเภทที่ static analysis พลาดได้พอดี (แบบ FilterService) · ถ้าปิดอยู่จริง ตัดสินทั้งหมดจาก static analysis ล้วน ๆ แปลว่า path ที่ประกอบ dynamic, Postman, cronjob และ repo ที่ยังไม่ถูก scan จะไม่ถูกจับได้เลยจนกว่าจะพังจริง ⇒ ตัวลดความเสี่ยงที่เหลือคือ export จากขั้นที่ 3 และการทำ dev ก่อน uat -
🔴 Sentinel —
resolve-sessionเป็น BE ไม่ใช่ NONE และห้ามลบ route เด็ดขาดPOST /sentinel/internal/resolve-sessionถูก audit จัดเป็น NONE ที่usage-api.md:444เพราะไม่มีโค้ด ในทุก repo เรียกมัน · แต่คนเรียกคือ APIM policy เอง —sentinel-facade-inbound-v6.xml:158ยิงsend-requestไปที่http://api.sua-az-{env}.internal/sentinel/internal/resolve-sessionทุก request ที่ผ่าน facade เพื่อแลก cookie เป็น JWT ⇒ ถังที่ถูกต้องคือ BEสิ่งที่ต้องเข้าใจให้ตรงกัน: เส้นนี้ไม่ได้ถูก register เป็น operation บน APIM และไม่ต้อง register · policy เรียกตรงไป internal host ข้าม operation layer ไปเลย ⇒ การไม่มีมันอยู่ในสเปกจึงไม่กระทบอะไร และการซ่อนจากสเปกก็ไม่ทำให้ login พัง
⇒ จัดการแบบ Lane BE คือแปะ
[ApiExplorerSettings(IgnoreApi = true)]เหมือน endpoint BE ตัวอื่น · 🔴 ห้ามแปะ[NotExposed]ของ Lane NONE เด็ดขาด เพราะตัวนั้นลบ route ออกจาก routing table ซึ่งจะทำให้send-requestของ APIM ได้ 404 = ระบบ login ตายทั้งระบบตอนไล่ Lane NONE ของ SentinelGateway ต้องคัดเส้นนี้ออกจากรายการก่อนเสมอ
-
branch ชนกับ security uplift — ตัดสินแล้วว่าไม่กังวล · ทำใน branch เดียวกันไปเลย
-
APIM API แยกราย env และไม่ตรงกันอยู่แล้ว (UAT ขาด 4 operation ที่ dev มี) ⇒ ต้อง export และตัดสินแยกราย env ห้ามเอา list ของ env หนึ่งไป apply กับอีก env
9. ของที่เจอระหว่างทาง ไม่อยู่ในงานนี้ แต่ควรรู้
GET /api/user-service/v1/subscriptions/usersเป็นAllowAnonymous· คืน list ของ user · และลงทะเบียนอยู่บน public gatewayBackend_ThirdPartyFXService—git fetch originล้มเหลวTF401019repo not found หรือถูก rename ฝั่ง server · local ref ค้างอยู่ที่ 2026-06-04QA_E2eTestsใช้ branch ชื่อdevelopไม่ใช่developmentและmasterเป็น stub