Private Docs

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/09POST /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}-secfeature/{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

repobranch / PR
Backend_ThirdPartyServicefeature/third-party-security-apply — PR #3036
Backend_WorkflowServicefeature/workflow-security-apply — PR #3037
Backend_Centralizedfeature/centralized-security-apply — PR #3042
Backend_CodexServicefeature/codex-security-apply — PR #3031
Backend_FileManagementServicefeature/file-management-security-apply — PR #3035
Backend_NotificationServicefeature/notification-security-apply — PR #3033
Backend_TaskServicefeature/task-security-apply — PR #3034
Backend_UserServicefeature/us-api-surface-be-lanePR #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แก้ปัจจุบันทำอะไร
FE122+4126เก็บไว้ ลงทะเบียน APIM ตามปกติ
BE29+332Lane BE — ซ่อนจาก spec
BOTH1515เก็บไว้ ลงทะเบียน APIM ตามปกติ
NONE252−8244Lane NONE — ลบออกจาก routing table
รวม418−1417

การแก้แบ่งเป็นสองเรื่อง — 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/start
  • POST /api/user-service/v1/line-connectivity/unsubscribe/start
  • GET /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/batchFilter02.Infrastructure/Publishing/HttpMessagePublisher.cs:26 เป็น const string
  • GET /api/user-service/v1/subscriptions/usersFilter02.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_ThirdPartyFXServicegit 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 อย่างเดียวพอ

เส้นทางไฟล์ IaCkeyค่าใหม่
SentinelGateway → UserServiceconfig/sentinel-gateway-service/dev/appsettings.json:94 · uat:89UserServiceSettings.BaseUrlhttp://user-service.superappdev.svc.cluster.local (uat ใช้ namespace superappuat)
FilterService → Notificationconfig/filter-service/dev/appsettings.json:87 · uat:94NotificationService.BaseUrlhttp://notification-service.superappdev.svc.cluster.local
Centralized → Codexconfig/centralized-service/sit/appsettings.json:85CodexBaseUrlIaC cleanup เท่านั้น ไม่ใช่งานที่บล็อก — dev/uat เป็น cluster-local อยู่แล้ว และ sit ไม่มี workload รัน
ThirdParty → Codexconfig/thirdparty-service/sit/appsettings.json:69BaseUrlIaC 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เหตุผล
CodexServiceconfig/codex-service/dev/appsettings.json:84,88 · uat:86,90UserService.BaseUrl และ NotificationService.BaseUrl ผูกกับ IApiGatewayClient ที่ ไม่มี consumer สักตัว — ทั้ง repo มี 3 hit คือ interface, implementation และ DI registration เท่านั้น
LogServiceconfig/log-service/sit/appsettings.json:116Apim: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)

  1. 🔴 ตรวจว่า attribute มีผลจริงกับ generator ของแต่ละ service — ยังไม่มีใครเช็ค และ Lane BE ยืนอยู่บนข้อนี้ (Lane NONE ไม่ขึ้นกับข้อนี้แล้ว เพราะกลไกของ package ลบ action ออกจาก application model ก่อนที่ generator ตัวไหนจะได้เห็น) ต่อ service ต้องได้คำตอบสามข้อ (grep อย่างละครั้ง ไม่ต้อง build)
    • ใช้ generator ตัวไหน · AddSwaggerGen ของ Swashbuckle หรือ MapOpenApi ของ Microsoft.AspNetCore.OpenApiapi-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 นี้ไปเลย
  2. ตรวจ ingress class ของทุก env (ดูข้อ 7) — dev กับ uat ใช้ ingress controller คนละตัว · ทางที่เลือก (annotation อย่างเดียว) ไม่ได้พึ่ง ingress จึงไม่บล็อก แต่ต้องรู้ภาพจริงก่อนไปแตะอะไรที่ระดับ routing
  3. 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 คือหลักฐานชิ้นเดียวที่จะมี
  4. ทำงานเตรียมข้อ 5 — ย้าย S2S ที่ยังผ่าน gateway ไป cluster-local ให้ครบก่อน
  5. แปะ attributeLane BE ทำได้เลย ไม่ต้องรออะไร · Lane NONE ต้องรอ package 10.32.0 ออกและ apply เข้า service ก่อน ตามลำดับในเอกสาร package · ทำใน branch ของ security uplift ตามที่ตัดสินไว้ ยกเว้น Backend_Centralized ที่ origin/development ยัง build ไม่ผ่าน NU1903 อยู่ ⇒ repo นั้น verify อะไรไม่ได้จนกว่าจะแยกตัวแก้ออกมาหรือ merge PR ที่ถือตัวแก้อยู่
  6. 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

envingress classpath patternหมายเหตุ
devnginx-internal (27 ตัว)path: /api/{svc}(/|$)(.*) + nginx.ingress.kubernetes.io/rewrite-targetregex + rewrite
uatkong-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. ความเสี่ยงที่ยังเหลือ

  1. QA suite จะพัง — ยอมรับแล้ว ไม่ต้องจัดการ · 6 เส้นที่มีแต่ QA เรียกจะถูกซ่อน คือ document-numbers/policies ทั้ง POST/PUT/DELETE · admin/flow-definitions · admin/invitations · users/me/details

  2. ไม่มี 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

  3. 🔴 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 ต้องคัดเส้นนี้ออกจากรายการก่อนเสมอ

  4. branch ชนกับ security uplift — ตัดสินแล้วว่าไม่กังวล · ทำใน branch เดียวกันไปเลย

  5. 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 gateway
  • Backend_ThirdPartyFXServicegit fetch origin ล้มเหลว TF401019 repo not found หรือถูก rename ฝั่ง server · local ref ค้างอยู่ที่ 2026-06-04
  • QA_E2eTests ใช้ branch ชื่อ develop ไม่ใช่ development และ master เป็น stub