光阴冢 赛博空间的自留地

使用 OpenResty 替代 Nginx

为了让自己部署的一些站点能有更好的认证体验(只给自己或授权用户访问,并且不需要频繁输入密码),然后又不想自己写一套漏洞百出的认证程序,于是一直以来在尝试搜索可行的方案。

之前一直在使用的是 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 ~= "someone@mail.com" 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 的时候发现弄起来是真麻烦,索性没有再搞了。