megrxu

使用 OpenResty 替代 Nginx

Apr 7, 2021  「Web」  
雖然 openresty 可程式設計能力更強,但 dexidp 的維護成本較大(時常需要自行編譯和部署,且需要獨立伺服器),目前我使用的方案是 Cloudflare Access 來保護網域後的私人服務。

為了讓自己部署的一些站點能有更好的認證體驗(只給自己或授權使用者訪問,並且不需要頻繁輸入密碼),然後又不想自己寫一套漏洞百出的認證程式,於是一直以來在嘗試搜尋可行的方案。

之前一直在使用的是 HTTP Basic Authentication,然而這種彈出視窗式認證無法被記住密碼,也沒辦法手動延長認證時間。

給實驗室部署的服務中,使用了 GitLab 提供的 SSO 介面來進行統一認證。也挺方便的,基本上只需要多點選一次就能自動認證。然而自己不會專門為此搭建一個 GitLab,同時即使搭建了配置起來也不是很方便,還需要手寫一些程式碼。

最終經過搜尋,準備嘗試一下 OpenResty 上的 openidc 模組 + dex-idp 來實現認證。

Dex

Dex 使用了 OpenID Connect 來為其他應用提供身份認證功能。相當於是作為一個聯結器把許多的其他的認證集合在一起。

Dex 和兩種應用程式有關,一種是消耗consume這個認證的(如需要使用認證資訊來授權的一些應用程式),另一種是提供這個認證的(如 GitHub,SAML Server,Google 等)。除此之外,Dex 也可以使用 Email-密碼 進行認證。

Dex 方面提供了詳細的文件來指導如何配置伺服器,並且 AUR 裡也有 dex-idp (雖然被廢棄了)可以來抄,稍微改改就能用了(注意他提供的 dex.service 裡有個 typo)。

2.28.1 版本的 PKGBUILD
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pkgname='dex-idp'
pkgdesc='OpenID Connect Identity (OIDC) and OAuth 2.0 Provider with Pluggable Connectors'
pkgver='2.28.1'
pkgrel='1'
url="https://github.com/dexidp/dex"
license=('Apache')
arch=('x86_64')
makedepends=('go')
depends=('glibc')
backup=('etc/dex.yaml')
source=("git+https://github.com/dexidp/dex#tag=v${pkgver}" "dex.service" "dex.sysusers")
sha256sums=('SKIP'
            'SKIP'
            '610ae818f2ff08ac41f6beb227510bff5c55699041e94cbcfec44dfa5553e688')

prepare () {
  export GOPATH="${srcdir}"
  mkdir -p src/github.com/dexidp/
  mv "dex" "src/github.com/dexidp/dex"
  export PACKAGE_ROOT="${GOPATH}/src/github.com/dexidp/dex"
}

build () {
  cd "$PACKAGE_ROOT"
  make
}

package () {
  cd "$PACKAGE_ROOT"
  install -Dm755 bin/dex "${pkgdir}/usr/bin/dex" &&
  install -Dm644 examples/config-dev.yaml "${pkgdir}/etc/dex.yaml" &&
  install -Dm644 "${srcdir}/dex.sysusers" "${pkgdir}/usr/lib/sysusers.d/dex.conf" &&
  install -Dm644 "${srcdir}/dex.service" "${pkgdir}/usr/lib/systemd/system/dex.service" &&
  install -dm755 "${pkgdir}/usr/share/dex" &&
  cp -r web "${pkgdir}/usr/share/dex"
}

resty-openidc

openidc 這個專案使得在 Nginx 的配置中直接使用 OpenID 提供的認證成為可能,不需要再去寫一個 Wrapper 程式了。按照官方例子,配置好 Dex 的 OpenID Connect Discovery 連結,然後再配置好對應的 App Secrets 和 App ID 即可(這部分也需要同時在 Dex 的配置檔案的 staticClients 鍵中配置)。

當然,得到使用者授權資訊之後(即下面配置中的 res.user 變數),怎麼用相應的資訊來決定伺服器的響應就隨開發者來寫 Lua 邏輯了。

openresty 部分配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
location / {
    access_by_lua_block {
        local opts = {
            redirect_uri_path = "$REDIR_URI",
            discovery = "https://default-server/.well-known/openid-configuration",
            client_id = "$CLIENT_ID",
            client_secret = "$CLIENT_SECRET",
        }

        local res, err = require("resty.openidc").authenticate(opts)
        -- 認證失敗的話
        if err then
               ngx.status = 500
               ngx.say(err)
               ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end

        -- 認證成功,但是不是被授權的某個使用者的話
        if res.user.email ~= "[email protected]" then
               ngx.status = 403
               ngx.exit(ngx.HTTP_FORBIDDEN)
        end

        ngx.req.set_header("X-USER", res.id_token.sub)
    }
}

自此,配置好之後,直接去訪問某一個隱私頁面,會先被 resty-openidc 轉到 dex 的授權頁面;再根據配置, dex 則會重新轉向 GitHub/Google 的認證頁面去,透過這些 Account Providers 的認證後,又轉回了原始的訪問頁面,根據 Lua 指令碼中的邏輯判斷是否滿足要求,來生成相應的響應。

整個過程如果網速好的話,基本上是無感的。還算是比較滿意吧。

同時藉此機會也把 nginx 換成了程式設計能力更強的 openresty (其實也嘗試過去使用基於 njs 或者更加原生的方案,比如 nginx-openid-connect,發現後者竟然需要商業版 Nginx,於是就放棄了)。基礎使用上似乎沒什麼區別,遇到想用 QUIC 的時候發現弄起來是真麻煩,索性沒有再搞了。