iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Cloud Native

與雲原生精靈共舞:APISIX使用者的兩年旅程系列 第 20

Ch14 - 告別「時好時壞」的 LDAP 驗證:用 APISIX 打造高可用 AD 代理服務

  • 分享至 

  • xImage
  •  

SSO服務,說白了一開始就是作為AD驗證的中介、LDAP協議到OpenID的轉換,讓有身份驗證需求的應用,可以透過OAuth 2.0的流程進行。

在最初設定上,使用ldap://<REALM>.tw的形式與AD服務進行連線。比如AD的基礎位置是dc=company,dc=tw,就會成爲ldap://company.tw的格式。(AD域帳戶會是company.tw\user)

ldap://company.tw這個位置,實際上有可能是由多臺DC主機提供服務。可以透過nltest.exe /DCLIST:company.tw查詢:

https://ithelp.ithome.com.tw/upload/images/20251004/20112470yHdaewg1ub.png

或是透過dig company.tw查詢DNS伺服器記錄內容:

https://ithelp.ithome.com.tw/upload/images/20251004/20112470xNCGyHatko.png

公司內的DNS記錄,就包含了12個IPv4的A類型記錄。如果換用nslookup company.tw查詢,每次取得的第一個IP也都不同:

https://ithelp.ithome.com.tw/upload/images/20251004/20112470IMlgCGteTl.png

這是因爲DNS伺服器會輪詢提供查詢結果,這也能夠達到基於DNS的負載平衡。

有些DNS伺服器除了輪詢,還可以依據請求來源IP的地理位置、CIDR提供不同查詢結果。


這樣出現一個問題就是:當某一台DC掛掉的時候,會導致部分使用者或系統的認證失敗,
-- HAproxy + Keepalive實現LDAP代理服務

因爲是輪詢使用DC伺服器,SSO服務遇過一種情況是:時而可以;時而不行。當時為了找到問題,利用了pingldapsearch一個個針對DC伺服器檢查,竟有一段時間,接近4臺DC連線是存在問題的(34%),但DNS伺服器記錄依然存在。也是在這過後,將伺服器連線設定調整成了ldap://dc01.company.tw, ldap://dc02.company.tw的形式。

故障轉移 (Failover)

這是最常見的用法。當主要 LDAP 伺服器無法連線時,系統會自動切換到備援的 LDAP 伺服器,確保服務的可用性。

設定方式:
大多數支援此功能的軟體,通常允許你在設定檔中用空格將多個 URL 隔開。

  • 範例格式: ldap://server1.example.com:389/dc=example,dc=com ldap://server2.example.com:389/dc=example,dc=com

  • 運作原理:系統會依序嘗試連線每一個 URL。如果第一個 URL 連線失敗,它就會嘗試第二個,直到成功為止。
    (由Gemini生成)

運作上:如果第一個 URL 連線失敗,它就會嘗試第二個,直到成功為止。但奇怪的是,最近又因爲AD伺服器滾動式更新,卻依然造成了SSO服務登入提供能力異常Orz。

雖然在Keycloak 16.1.1版本驗證過這種寫法,但疑似在21版後不再支援此種寫法。不巧的是,在架構調整時,順便做了跳板升級到22.0.5版本。

(以下是Gemini回答)
根據 Keycloak 的官方文件和社群討論,在 Keycloak 21.0.1 版本之後,已經不再支援在單一「連線 URL (Connection URL)」欄位中使用空格來分隔多個 LDAP URL 的寫法。

變動詳情

在 Keycloak 20.0.1 及更早的版本中,許多使用者會透過以下格式來設定多個 LDAP 伺服器進行故障轉移:

ldap://server1.example.com:389 ldap://server2.example.com:389

然而,從 Keycloak 21.0.1 開始,此寫法會導致連線失敗,並拋出 java.lang.IllegalArgumentException: URI '...' is not valid 的錯誤。

解決方案

如果你的 Keycloak 版本是 21.0.1 或更高,並需要設定多個 LDAP 伺服器,官方建議的作法是:

  1. 為每一個 LDAP 伺服器建立獨立的 User Federation 設定。
  2. 透過設定這些 User Federation 的優先順序(Priority)來實現故障轉移。 系統會依序檢查每個供應者,直到成功連線並找到使用者為止。

這種方式提供了更清晰且可控的管理機制,取代了舊版在單一欄位中放入多個 URL 的作法。

為了讓SSO服務優先使用接近的DC伺服器,最後決定由APISIX提供LDAP的代理。不但APISIX會針對同資料區域的DC伺服器進行負載平衡,當該區域DC伺服器都發生問題時,也會嘗試其他資料中心的伺服器。

為了達到高可用性,我們更改了環境拓撲,用四台linux伺服器充當代理伺服器,代理所有ldap請求。
-- HAproxy + Keepalive實現LDAP代理服務

由APISIX提供LDAP的代理

實踐LAB

設定監聽端口

在「「Quick Start」中,docker-compose.yaml已經轉發389端口,這是LDAP在沒有使用TLS情況下的預設端口。接著看看apisix_config/config.yaml的部分內容:

apisix:
  stream_proxy:
    tcp:
    - 389

nginx_config:
  stream_configuration_snippet: |
    log_format basic '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received  $session_time $upstream_addr $upstream_bytes_sent $upstream_bytes_received $upstream_connect_time';
    access_log /dev/stdout basic;

apisix.stream_proxy告訴APISIX,同樣監聽389端口作爲TCP的「流」代理。nginx_config.stream_configuration_snippet則設定「流」代理時的記錄保存格式與位置。

「流」代理記錄

設定上游

類似設定SSO服務的上游,也同樣需要設定AD伺服器上游:

curl --request PUT \
  --url http://127.0.0.1:9180/apisix/admin/upstreams/ad-server \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: SaiTJp7TEa9K39oy7D5A4ouXmdqHvL9a' \
  --data '{
  "nodes": [
    {
      "host": "ad-11.company.tw",
      "port": 389,
      "weight": 1,
      "priority": 1
    },
    {
      "host": "ad-12.company.tw",
      "port": 389,
      "weight": 1,
      "priority": 1
    },
    {
      "host": "ad-21.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -1
    },
    {
      "host": "ad-22.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -1
    },
    {
      "host": "ad-31.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -2
    },
    {
      "host": "ad-32.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -2
    },
    {
      "host": "ad-41.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -4
    },
    {
      "host": "ad-42.company.tw",
      "port": 389,
      "weight": 1,
      "priority": -4
    }
  ],
  "timeout": {
    "connect": 6,
    "send": 6,
    "read": 6
  },
  "type": "roundrobin",
  "checks": {
    "active": {
      "concurrency": 10,
      "healthy": {
        "interval": 1,
        "successes": 2
      },
      "http_path": "/",
      "timeout": 1,
      "type": "tcp",
      "unhealthy": {
        "interval": 1,
        "tcp_failures": 2,
        "timeouts": 3
      }
    },
    "passive": {
      "healthy": {
        "successes": 5
      },
      "type": "tcp",
      "unhealthy": {
        "tcp_failures": 2,
        "timeouts": 7
      }
    }
  },
  "scheme": "tcp",
  "pass_host": "pass",
  "name": "AD Server",
  "keepalive_pool": {
    "idle_timeout": 60,
    "requests": 1000,
    "size": 320
  }
}
'

不同的是schemetcp,健康檢查時也是。

設定「流」代理路由

不同於第7層HTTP的代理,可以設定hostpath的匹配,「流」代理路由主要設定的就只是監聽端口和代理的上游,且使用的Admin API端點也不一樣,用的是/apisix/admin/stream_routes

流的代理是第四層的負載平衡,並不明白上層協議內容,沒有hostpath可以使用

curl --request POST \
  --url http://127.0.0.1:9180/apisix/admin/stream_routes \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: SaiTJp7TEa9K39oy7D5A4ouXmdqHvL9a' \
  --data '{
  "name": "AD Server",
  "desc": "代理AD Server的LDAP服務",
  "upstream_id": "ad-server",
  "server_port": 389
}'

同一個Admin API端點,可以將請求方法改成GET看看有沒有建立成功。

驗證

由於並沒有建立LDAP服務,這個Lab沒辦法驗收。
因此只能簡單說說驗收方式:透過ldapsearchtelnet等工具向389端口發起請求,同時查看透過tcpdump或Wireshark等網路封包監聽工具查看記錄(sudo tcpdump -i <interface> port 389),或是查看APISIX的後臺記錄。以我Lab環境例子,因為實際LDAP服務不存在,APISIX後臺記錄存在相關錯誤記錄:

https://ithelp.ithome.com.tw/upload/images/20251004/201124702eNDfEmRll.png

有興趣者,可以透過OpenLDAP或其他工具建立LDAP服務。


上一篇
Ch13 - 為什麼API網關能處理這麼多事?解密第七層與第四層負載平衡
下一篇
Ch15 - APISIX 部署解密:全面解析「傳統」、「解耦」、「無中心獨立」部署策略
系列文
與雲原生精靈共舞:APISIX使用者的兩年旅程22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言