第8章封面圖:Section 08 API Gateway與安全呼叫

這章要學會什麼

  • 寫一個統一的「呼叫服務」,讓所有對上游的呼叫都走同一條路:呼叫端只給「版本編號+參數」,網址、方法、標頭、憑證全部由已核准的版本內容決定。
  • 在送出請求之前,依序檢查版本已發布、參數符合核准的規格、網址在允許清單內,任何一項不通過都不送出請求。
  • 認識「伺服器端請求偽造」這種攻擊手法,並知道怎麼在連線的那一刻擋住它。
  • 把上游各種失敗(逾時、連不上、錯誤狀態碼、轉址)轉換成固定的錯誤代碼,每次呼叫都留下一筆可追查的紀錄。
  • 知道為什麼「新增資料」類的請求絕不自動重試,以及「防重複鍵」怎麼保證同一把鍵重送也不會建立第二筆資料。

先備知識

要先完成第 7 章。要知道基本的網路請求跟「取消」的概念,以及 HTTP 的 3xx 狀態碼代表「轉址」。

觀念白話講

為什麼需要一道統一的「安全閘道」

平台要代替會員(跟他們的 AI)去呼叫別人的服務。如果每個地方各自發送網路請求,會發生:逾時設定不一致、錯誤各自處理、密鑰散落在程式各個角落、呼叫紀錄漏寫。更危險的是:只要有任何一處讓呼叫端能夠影響到真正打出去的網址或標頭,平台就變成攻擊者的跳板。所以這門課只設計一個地方會真正對上游發送 HTTP 請求,後面章節不管是測試工具、AI 工具呼叫,都經過同一道閘道。

呼叫端能決定什麼、不能決定什麼

項目由誰決定理由
要呼叫哪個版本呼叫端(給版本編號)使用者選擇要用的工具
參數值呼叫端,但必須符合這個版本核准過的規格參數是業務內容;規格是審核過的東西
網址、呼叫方法已核准的版本內容呼叫端能改網址,等於能叫平台打任何地方
標頭(含密鑰)安全閘道呼叫端能加標頭,就能偽造身分或覆蓋密鑰
逾時、回應大小上限閘道的固定設定避免一支慢服務或巨大回應拖垮整個平台

呼叫端要送的「請求」這個資料格式裡,根本沒有網址跟標頭這兩個欄位可以填——就算測試工具的表單刻意多塞這兩項資料,也不會有任何程式去讀取它們。

什麼是「伺服器端請求偽造」(SSRF)

這是指攻擊者想辦法讓伺服器「代替他」發出請求,去存取攻擊者原本自己連不到的地方——例如雲端主機的內部憑證服務、只綁在本機的管理介面、或公司內網。攻擊的入口就在「登記一支服務」這個動作:會員填的網址會被平台呼叫。這門課設計了四層防線:

Gateway 送出請求前的四道檢查:①版本已發布?→②參數符合規格?→③網址在允許清單內?→④連線當下的 IP 合法嗎?→⑤才真的送出一次分類結果。前三項任何一項不通過,請求根本不會送出,耗時是 0 毫秒。
圖 8-1 Gateway 送出請求前的四道檢查:①版本已發布?→②參數符合規格?→③網址在允許清單內?→④連線當下的 IP 合法嗎?→⑤才真的送出一次分類結果。前三項任何一項不通過,請求根本不會送出,耗時是 0 毫秒。
  1. 審核:管理員看得到端點網址,可以退回,但人總會有看漏的時候。
  2. 精確允許清單:只有伺服器設定檔裡登記過、逐字相同的網址才准呼叫,會員無法透過任何頁面擴充這份清單。
  3. 連線當下再檢查一次 IP:就算網址在允許清單內,如果它實際解析出來的位址是私有網段、本機位址,一樣拒絕連線——因為網域名稱可以「先騙過檢查,連線那一刻才偷偷改指向內網」,所以檢查必須發生在「真正要連線」的那一刻,而不是提早查一次就算數。
  4. 不跟隨轉址:上游服務如果回傳「轉址到某個網址」,平台一律不會跟過去。

逾時、取消,跟「絕不自動重試」

每次呼叫都有固定的逾時秒數,而且逾時判斷跟「使用者自己放棄、關掉頁面」是分開處理的兩種情況。「新增資料」類的請求(例如建立工單)絕對不會自動重試——因為平台無法確定「第一次真的失敗」還是「上游其實已經處理完成,只是回應在半路遺失」,自動重送有可能造成同一件事被建立兩次。所以每一個新增資料的請求都會附上一把「防重複鍵」,上游服務可以據此判斷「這是不是同一個請求的重新傳送」,重複送出多次也只會產生一筆結果。

把各種失敗,都轉成固定的錯誤代碼

不管是「參數格式不對」「網址不在允許清單」「連線目標是私有位址」「上游逾時」「上游回錯誤」「上游要轉址」,都被轉換成一組固定不變的英文錯誤代碼,方便程式判斷;同時附上一句中文說明給人看。每一次呼叫,不管成功還是被擋下,都會產生一筆呼叫紀錄,附上一組可追查的代碼——但紀錄裡不會保存參數與回應內容本身,只記結果分類、狀態碼、耗時這類中繼資訊。

老師示範做了什麼

示範情境:在後臺「API 測試台」依序測試十幾種情況,證明「成功、參數錯誤、上游逾時」都能得到可以辨識的結果,而且呼叫者無法指定任意網址或標頭;最後到資料庫檢視每次呼叫留下的紀錄。

API 測試台成功呼叫商品查詢:左欄只有版本、參數、防重複鍵三個輸入欄位——完全沒有網址、方法、標頭的輸入框,這就是「呼叫者不能指定任意網址或標頭」在畫面上的具體樣子;右欄顯示結果、狀態碼、耗時、追查代碼與回應內容。
圖 8-2 API 測試台成功呼叫商品查詢:左欄只有版本、參數、防重複鍵三個輸入欄位——完全沒有網址、方法、標頭的輸入框,這就是「呼叫者不能指定任意網址或標頭」在畫面上的具體樣子;右欄顯示結果、狀態碼、耗時、追查代碼與回應內容。
允許清單內的網域,因為連線當下解析成本機位址而被擋下:結果顯示「拒絕」跟明確的錯誤代碼;說明文字寫著這個網域解析成私有或本機位址、已拒絕連線;耗時是 0 毫秒,代表根本沒有任何連線被真正建立。
圖 8-3 允許清單內的網域,因為連線當下解析成本機位址而被擋下:結果顯示「拒絕」跟明確的錯誤代碼;說明文字寫著這個網域解析成私有或本機位址、已拒絕連線;耗時是 0 毫秒,代表根本沒有任何連線被真正建立。

老師依序測試了成功、參數型別錯誤、參數不在允許值內、寫入請求重複送出同一把防重複鍵(得到相同結果)、寫入請求少帶必要旗標、上游逾時、上游轉址、雲端內部憑證位址、網域解析到本機位址、不在允許清單內的網址、偷塞網址跟標頭欄位(完全不影響結果)、已下架版本、上游服務沒開機,總共十幾種情況,每一種都得到可以清楚辨識、彼此不會搞混的結果。最後到資料庫查詢最近幾筆呼叫紀錄,證明被擋下的呼叫也一樣有紀錄,而且紀錄裡沒有任何參數或回應內容的欄位。

自己動手的步驟

步驟一:組出真正要送出的請求

依版本的網址範本跟呼叫方法,把呼叫端給的參數填進正確的位置——網址裡的路徑參數、查詢字串、或請求本文,视方法而定。特別注意:路徑參數的值一定要做網址編碼,確保像 ../ 這種內容不能被用來跳脫出原本的路徑結構。標頭字典也只在這一步組出來,包含固定的接受格式、追查代碼、給新增資料請求用的防重複鍵,以及需要的話才在這一刻才解密密鑰、填進標頭。

步驟二:允許清單與連線當下的 IP 檢查

允許清單放在伺服器的設定檔裡,是一份寫死的精確網址清單——不管會員登記什麼服務、審核通不通過,都不可能讓這份清單自己多出一項。連線當下的 IP 檢查則是在「真正建立網路連線」的那一刻,自己先查一次網域名稱對應到的位址,如果不是明確寫成「本機」名稱,卻解析到私有或本機網段的位址,就直接拒絕連線;轉址也設定成完全不跟隨。

步驟三:送出、逾時與結果分類

建立一個跟呼叫端的取消動作、以及固定秒數逾時綁在一起的取消機制,兩者任一個觸發都會中止請求。送出之後只送一次,不自動重試;依照收到的狀態碼或發生的例外,把結果分類成固定的幾種:成功、上游要轉址(視為被拒絕)、上游逾時、連不上上游、上游回傳錯誤狀態碼。不管走到哪一種結果,最後都要經過同一個地方寫紀錄,確保沒有任何一條路徑會漏寫。

步驟四:測試台與防重複鍵驗證

後臺的測試台頁面只給管理員使用,表單只讀「版本、參數、防重複鍵」三個欄位交給呼叫服務。驗證方式:選擇「建立工單」這支寫入型服務,帶同一把防重複鍵連續送出兩次,第一次會得到「已建立」的狀態碼跟一組編號,第二次應該得到相同的狀態碼與完全相同的編號——證明上游服務真的把這把鍵當成「同一件事」處理。

常見錯誤

症狀原因怎麼處理
測試台一直顯示連不上上游示範用的上游服務沒有啟動,或還在跑舊版本、缺少測試用的端點另開一個終端機視窗啟動最新版的示範服務
允許清單裡明明有這個網址,卻被判定為私有位址這個網址雖然名字寫著別的名稱,實際解析出來卻是本機位址,而它並沒有被明確寫成「localhost」這個保留名稱這正是連線當下 IP 檢查在正常運作;本機開發直接用保留名稱即可
自動化測試腳本送出表單後,卻意外把自己登出了腳本的元素選取條件太寬鬆,先選到了導覽列上的登出按鈕把選取條件寫得更精確,明確指定要送出表單所在的區塊

怎麼驗收+反向驗證

  1. 成功有可辨識的結果:正常呼叫得到成功、狀態碼 200、有追查代碼跟回應內容。
  2. 參數錯誤有可辨識的結果:型別不對的參數得到明確的「參數不符合規格」錯誤,而且耗時是 0 毫秒——代表上游根本沒有收到這個請求。
  3. 上游逾時有可辨識的結果:刻意呼叫一個會拖很久的測試端點,得到「逾時」分類,耗時約等於設定的逾時秒數。
  4. 呼叫者不能指定任意網址或標頭:刻意在請求裡夾帶額外的網址、標頭欄位,結果必須跟正常呼叫完全相同;對不在允許清單內的網址,必須得到「不允許」且沒有送出任何請求。
  5. 反向驗證:允許清單內、但解析到私有位址的網域仍然要被擋下;上游回傳轉址時不能被跟隨。
← 上一章 回課程地圖 下一章 →