部署 vouch-proxy, 为 nginx 添加 OAuth2 鉴权
写在前面
最近写了些东西,但是不想直接放在公网上给所有人看。
提供权限控制的类似 wiki 的应用,虽然也有开源的 bookstack 和 wiki.js 之类,但都有我不怎么喜欢的 anti-feature.
结果沦落到用静态框架(docsify/mkdocs-material)和 basic auth, 比用 hexo 还要丢人。
然后想到自己有个私有的 gitea 啊,可以充当身份服务器。虽然权限管理确实非常粗放,但是我想没人会真的用我的 gitea 存东西吧,就直接拿来用也可以。
于是找到了 vouch-proxy 和 oauth2-proxy 这两个项目(后来折腾 code-server 的时候还发现了 pomerium, 但那是后话了)。这两个都是利用 nginx 的 auth_request 模块来鉴权的,都很轻(毕竟都是 go 写的),比较了一番以后,感觉还是 vouch 更容易上手。
安装方式
编译安装
vouch 支持很多安装方式。
俺一开始用的是 docker, 但后来发现 vouch 所在的服务器在初次授权时要访问 gitea, 而它的特征被互联网皇帝认成 bot, 就会吃 Bot Fight Mode, 于是 vouch 日志疯狂报 400, 就用 wireguard 打隧道,从内网走。
也就换成编译安装了。
$ git clone https://github.com/vouch/vouch-proxy.git
$ cd vouch-proxy
$ ./do.sh goget
$ ./do.sh build
$ doas mkdir -p /opt/vouch-proxy/config
$ doas cp vouch-proxy /opt/vouch-proxy/
部署在 Docker 容器内
可以试试[用 docker 运行](https://github.com/vouch/vouch-proxy#installation-and-configuration),compose 挺好玩的。可以给 vouch 容器单独开一个 bridge 网络,然后让 nginx 监听网关,就不需要在公网上绕路了。对于部署在同一台机器上的 CI/CD Runner 也是一样的。
如果 gitea 也是用 docker 部署的,那可以试试在 gitea 的 compose 文件里面开个网络:
networks:
gitea-net:
external: false
name: gitea_network
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/24
gateway: 172.30.0.1
网段自己看着改,避免冲突就行。
在底下的 service
添加 network 键:
services:
gitea:
+ networks:
+ gitea-net:
确保两个 network 的 name
名字一样。vouch 的 compose file 里 external
属性为 true.
此外,用 extra_hosts
把 gitea 实例的 hostname 解析到网关(主机)上的 IP 地址,但同时需要在 nginx 中监听该地址。
比如这样:
# docker-compose.yml
version: '3.0'
+networks:
+ gitea-net-vouch:
+ external: true
+ name: gitea_network
services:
vouch-proxy:
container_name: vouch
image: quay.io/vouch/vouch-proxy:latest
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./vouch/config:/config
restart: unless-stopped
+ extra_hosts:
+ - "gitea.yourdomain.com:172.30.0.1"
+ networks:
+ gitea-net-vouch:
然后把 vouch 的容器起好,就可以了。
配置文件
路径
编译安装,默认
/opt/vouch-proxy/config/config.yml
, 但是命令行可以指定配置文件路径。对于 docker 安装,先启动一次容器再关掉,然后修改持久化文件路径里面的
config.yml
就可以了。
作者给出了很多示例,可以参考。
之前尝试了他们给的 gitea 配置示例,但是反复碰壁。想到 gitea 的身份服务是基于 OpenID Connect 的,那为什么不用呢?
前往 gitea 实例的 /.well-known/openid-configuration
就能看到要填的参数。
一些可能要踩的坑
配置白名单太慢了,所以直接
allowAllUsers
也没关系。vouch.jwt.secret
我直接加在配置里了,反正也没法加密存储。所以记得改权限到 600.另外还需要添加 code challenge method, 不然 PKCE 过不去。
maxAge
是很重要的参数。如果不设置,就会在 gitea 的数据库保存一个永不失效的 OAuth authorization code, 还是相当危险的。scope
是 vouch 通过 OIDC 获取的 gitea 用户信息。此处的group
即是 gitea 中的 organization.
client_id
和client_secret
直接到 gitea 的 Settings/Applications 那里新建一个 OAuth Application 就行。
# Vouch Proxy configuration
# bare minimum to get Vouch Proxy running with Gitea
vouch:
listen: 10.0.0.1 # 0.0.0.0
port: 9090
# domains:
# - yourdomain.com
# set allowAllUsers: true to use Vouch Proxy to just accept anyone who can authenticate at Gitea
allowAllUsers: true
cookie:
# Set `secure: false` when protecting a non-https site such as http://app.yourdmain.com - VOUCH_COOKIE_SECURE
secure: true
# optionally force the domain of the cookie to set
# vouch.cookie.domain must be set when enabling allowAllUsers
domain: yourdomain.com
# Number of minutes until session cookie expires - VOUCH_COOKIE_MAXAGE
# Set cookie maxAge to 0 to delete the cookie every time the browser is closed.
# Must not be longer than jwt.maxAge
maxAge: 20160
jwt:
# secret - VOUCH_JWT_SECRET
# a random string used to cryptographically sign the jwt when signing_method is set to HS256, HS384 or HS512
# Vouch Proxy complains if the string is less than 44 characters (256 bits as 32 base64 bytes)
# if the secret is not set here then Vouch Proxy will..
# - look for the secret in `./config/secret`
# - if `./config/secret` doesn't exist then randomly generate a secret and store it there
# in order to run multiple instances of vouch on multiple servers (perhaps purely for validating the jwt),
# you'll want them all to have the same secret
secret: <YOUR SECRET> # you can generate it by executing `openssl rand -base64 32`
# number of minutes until jwt expires - VOUCH_JWT_MAXAGE
maxAge: 20160 # 14d
oauth:
# replace "gitea.yourdomain.com" with the domain your Gitea instance runs on
# create a new OAuth application at:
# https://gitea.yourdomain.com/user/settings/applications
provider: oidc
# PKCE method if enabled, S256 is currently supported (check https://www.oauth.com/oauth2-servers/pkce/)
code_challenge_method: S256
scopes:
- openid
- email
- profile
- groups
client_id: <YOUR GITEA ID>
client_secret: <YOUR GITEA SECRET>
auth_url: https://gitea.yourdomain.com/login/oauth/authorize
token_url: https://gitea.yourdomain.com/login/oauth/access_token
user_info_url: https://gitea.yourdomain.com/login/oauth/userinfo
callback_url: https://vouch.yourdomain.com/auth
(编译安装)启用进程守护
填好配置就可以启动进程守护了。作者已经给了完整的示例。如果工作目录和安装路径没有变化就不用改了。
[Unit]
Description=Vouch Proxy
After=network.target
[Service]
Type=simple
User=vouch-proxy
WorkingDirectory=/opt/vouch-proxy
ExecStart=/opt/vouch-proxy/vouch-proxy -config /opt/vouch-proxy/config/config.yml
Restart=on-failure
RestartSec=5
StartLimitInterval=60s
StartLimitBurst=3
[Install]
WantedBy=default.target
配置 nginx
接下来就是配置 auth_request
连接到 vouch.
作者也给出了很好的示例,基本不用改。
proxy_pass
直接指向 vouch 自身监听的 IP 和端口。
把这段配置夹在开头和真正的后端之间。单独写个文件然后 include 进来也可以。
# send all requests to the `/validate` endpoint for authorization
auth_request /validate;
location = /validate {
# forward the /validate request to Vouch Proxy
proxy_pass http://127.0.0.1:9090/validate;
# be sure to pass the original host header
proxy_set_header Host $http_host;
# Vouch Proxy only acts on the request headers
proxy_pass_request_body off;
proxy_set_header Content-Length "";
# optionally add X-Vouch-User as returned by Vouch Proxy along with the request
auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
# these return values are used by the @error401 call
auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
}
# if validate returns `401 not authorized` then forward the request to the error401block
error_page 401 = @error401;
location @error401 {
# redirect to Vouch Proxy for login
return 302 https://vouch.yourdomain.com/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
}
然后在后端配置里面加上 X-Vouch-User 就差不多了。
# proxy pass authorized requests to your service
location / {
proxy_pass http://app2.yourdomain.com:8080;
+ # may need to set
+ # auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
+ # in this bock as per https://github.com/vouch/vouch-proxy/issues/26#issuecomment-425215810
+ # set user header (usually an email)
+ proxy_set_header X-Vouch-User $auth_resp_x_vouch_user;
}
执行 nginx -t
检查配置,然后 reload 一下就应该能用了。
最后
反正作者的文档写得详细,有什么问题直接去看项目主页就行了。