使用 OAuth2-Proxy 和 Keycloak 为没有身份验证的系统添加 OAuth2 身份验证

734 阅读5分钟

背景

在平常工作,相信大家都会使用到一些常用的开源软件,像 prometheus , rabbitmq , kibanba 等。这些软件很多在权限设计这块就比较简单,或者是没有,如Prometheus 自带的查询面板,早期的 rocketmq dashboard,kibana 等,相信小伙伴有不少基于这块的安全整改,整改措施一般只是配置简单的 http basic 认证,或者 IP 白名单访问。下面介绍基于 Keycloak 和 OAuth2-Proxy 为系统添加 OAuth2 身份验证的方案。

keycloak 简单介绍

KeyCloak 是一种开源身份和访问管理解决方案,以最少的工作量为应用程序添加身份验证和保护服务。Keycloak 提供用户联合、强身份验证、用户管理、精细授权等。目前支持挺多开源软件单点登录,如 jenkins, redash, grafana 等。

OAuth2-Proxy 简单介绍

oauth2-proxy 是一个反向代理和静态文件服务器,它使用提供程序(Google、Keycloak、GitHub 等)提供身份验证,以通过电子邮件、域或组验证帐户。

方案介绍

我们可以使用 OAuth2-ProxyKeycloak 为应用程序中添加 OAuth2 身份验证,特别是那些没有身份验证的系统,下面以 Prometheus 为例,在我的一台服务器上,有 nginx , keycloak , oauth2_proxy 以及 prometheus 4 个服务,其中 keycloak, oauth2_proxy, prometheus 都是通过 nginx vhost 反向代理访问,域名如下图: image.png

配置 Prometheus 基于 OAuth2-ProxyKeycloak 身份验证的请求过程可以参考下图:

image.png

nginx , keycloak , oauth2_proxy 以及 prometheus 4 个服务这次全部使用 docker 部署。

keycloak docker 部署

keycloak docker-compose.yml

services:
  keycloak:
    image: quay.io/keycloak/keycloak:25.0.4
    ports:
      - "8080:8080"
 #   restart: always
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ftLVBgGK9SP
      KC_HTTP_ENABLED: true
      KC_PROXY_HEADERS: xforwarded  # keycloak 访问通过 nginx 反向代理,proxy headers 支持 forwarded, xforwarded
      KC_HEALTH_ENABLED: true
      KC_HOSTNAME: keycloak.zwade.top
      #KC_HOSTNAME_STRICT: false
      KC_DB: mysql
      KC_DB_URL: jdbc:mysql://mysql:3306/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: Pp123456
    command: 
      - start 
    networks:
      - access-mysql
        
networks:
  access-mysql:
    external: true

默认情况下,服务器使用 dev-file 数据库。这是服务器将用于持久数据的默认数据库,并且仅存在用于开发用例的数据库。 dev-file 数据库不适合生产用例,必须在部署到生产之前替换。keycloak 内置支持不同的数据库,具体可以参考官档。

oauth2-proxy docker 部署

Oauth2-proxy 的配置项可以参考 oauth2-proxy 官档,每个命令行参数都可以指定为环境变量,方法是将其前缀 OAUTH2_PROXY_ ,将其大写,并将连字符 ( - ) 替换为下划线 ( _ )。如果可以多次指定参数,则环境变量应为复数(尾随 S )。注意,环境配置项的值千万不要加引号

生成强 Cookie 密钥,对应环境变量 OAUTH2_PROXY_COOKIE_SECRET

dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_' ; echo

REDIRECT_URL 需要与 keycloak 配置的 client 的 Valid redirect URIs 配置项要对应上,下面是 oauth2-proxydocker-compose.yml

services:
   server:
     image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
     ports:
       - "4180:4180"
     environment:
       - OAUTH2_PROXY_PROVIDER=keycloak-oidc
       - OAUTH2_PROXY_CLIENT_ID=prometheus
       - OAUTH2_PROXY_CLIENT_SECRET=z6rqgRPAZ3QsOIbx02Zq60llHeDGpZnAuo
       - OAUTH2_PROXY_REDIRECT_URL=https://prometheus.zwade.top/oauth2/callback
       - OAUTH2_PROXY_OIDC_ISSUER_URL=https://keycloak.zwade.top/realms/master
       - OAUTH2_PROXY_COOKIE_SECRET=ofonr_QLDW5EaKaVp09P2b46Pycn8pps7bHNpjDEUEk=
       - OAUTH2_PROXY_EMAIL_DOMAINS=*
       - OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true
       - OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
       - OAUTH2_PROXY_REVERSE_PROXY=true
       - OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180

Nginx 和 Prometheus docker 部署

部署过程略

Keycloak 创建新的 OIDC

Keycloak新版控制台按照以下步骤创建:
a. 在 Keycloak realm 下创建新的 OIDC client
Clients -> Create client
Client Type 选择 OpenID Connect,填写 Client ID,* 必填项,其他项也可以进行填写, Next image.png

Client authentication 'On',
Authentication flow , 勾选 Standard flow,取消勾选 Direct access grants, Next image.png

填写Valid redirect URIs,需要与 oauth2_proxyOAUTH2_PROXY_REDIRECT_URL 对应上,Save image.png

b. client 配置专用的 audience mapper
Clients -> <your client's id> -> Client scopes
点击 Configure a new mapper 然后选择 Audience image.png

Name 一般以aud-mapper-<your client's id>格式,Included Client Audience 下拉框选择你对应的 client's id,Add to ID token 'On',Add to access token 'On',其他保持默认就可以,然后 Save image.png

更多请参考:oauth2-proxy.github.io/oauth2-prox…

配置 Nginx 反向代理

创建关于 prometheus 域名 nginx vhost 配置,使用 Nginx auth_request 指令,该指令允许 Nginx 通过 oauth2-proxy 的/auth endpoint 对请求进行身份验证,该 endpoint 仅返回 202 Accepted 响应或 401 Unauthorized 响应,而不通过代理请求。

upstream prometheus {
    server localhost:9090;
    keepalive 1;
}


server {
    listen 80;
    listen 443 ssl http2;
    server_name prometheus.zwade.top;

    ssl_certificate /etc/nginx/conf.d/ssl/zwade.top/zwade.top.cer;
    ssl_certificate_key /etc/nginx/conf.d/ssl/zwade.top/zwade.top.key;

    proxy_buffer_size 32k;
    proxy_buffers 4 32k;

    location /oauth2/ {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Auth-Request-Redirect $request_uri;
        # or, if you are handling multiple domains:
        proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
    }

    location = /oauth2/auth {
        proxy_pass       http://127.0.0.1:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Forwarded-Uri  $request_uri;
        # nginx auth_request includes headers but not body
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
    }

    location / {
        auth_request /oauth2/auth;
        error_page 401 =403 /oauth2/sign_in;

        # pass information via X-User and X-Email headers to backend,
        # requires running with --set-xauthrequest flag
        auth_request_set $user   $upstream_http_x_auth_request_user;
        auth_request_set $email  $upstream_http_x_auth_request_email;
        proxy_set_header X-User  $user;
        proxy_set_header X-Email $email;

        # if you enabled --pass-access-token, this will pass the token to the backend
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;

        # if you enabled --cookie-refresh, this is needed for it to work with auth_request
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
        # limit and so the OAuth2 Proxy splits these into multiple parts.
        # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
        # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.
        auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;

        # Extract the Cookie attributes from the first Set-Cookie header and append them
        # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
        if ($auth_cookie ~* "(; .*)") {
            set $auth_cookie_name_0 $auth_cookie;
            set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
        }

        # Send both Set-Cookie headers now if there was a second part
        if ($auth_cookie_name_upstream_1) {
            add_header Set-Cookie $auth_cookie_name_0;
            add_header Set-Cookie $auth_cookie_name_1;
        }

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass  http://prometheus/;

    }

}
  

测试验证

访问加了认证的Prometheus.gif

访问加了认证的Prometheus.gif

打开浏览器开发者模式,可以看到请求Prometheus 是带有 ouath2_proxy cookies 认证。 oauth2_proxy cookies.png

最后

keycloak + oauth2_proxy 真的基本上可以开箱即用,无需额外开发,我们可以使用 keycloak 做统一用户管理,当然 keycloak 还不止上面这些功能,有兴趣的朋友可以自行查看官档。