基于k8s Ingress Nginx+OAuth2+Gitlab无代码侵入实现自定义服务的外部验证

1、OAuth和OAuth2.0介绍

OAuth 是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌 token ,用来代替密码,供第三方应用使用。

OAuth 2.0 是用于授权的行业标准协议。 OAuth 2.0 致力于简化客户端开发人员的工作,同时为 Web 应用程序,桌面应用程序,移动电话和客厅设备提供特定的授权流程。

OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。

2、应用场景

很多情况下,许多应用程序不提供内置的身份验证或开箱即用的访问控制。由于这些应用程序处理的敏感数据,这可能是一个主要问题,通常有必要提供某种类型的安全性。基于 k8s 部署的一些服务,并没有自身的访问认证控制机制。例如我们部署一个用于公司内部使用的 web 应用,又不想做基于统一账号 SSO 的认证功能的开发,但是又想在用户访问时加上一层认证功能。这类情况的解决思路一般是在访问入口,例如 Ingress 上添加一层访问认证,可以借助于 basic auth 实现此功能,但 basic auth 存在过于简单、账号权限不好控制、需要手动维护等诸多问题。

于是另外一种相对更为成功的解决办法是使 Ingress 通过 OAuth 对接到能够提供支持 oauth 认证的外部服务,例如 githubgitlab 。这种方式没有对应用程序的代码侵入,仅仅在应用入口添加了配置。

除了上述我描述的这个应用场景,还有很多可以利用 oauth 轻松实现认证的场景。

3、oauth2 proxy介绍

oauth2 proxy 是一个反向代理和静态文件服务器,使用提供程序( GoogleGitHub 和其他提供商)提供身份验证,以通过电子邮件,域或组验证帐户。

项目地址:https://github.com/oauth2-proxy/oauth2-proxy

认证过程的流程如下

4、具体实现

实验环境:

  • k8s 1.15.0
  • Ingress nginx 0.25.0
  • gitlab 13.7.4

4.1 在Gitlab配置OpenID应用

登录到 Gitlab —>管理中心—>应用,创建一个应用

参数:

  • 回调URL:指 GitLab 在用户通过身份验证后应将其发送到的端点,对于 oauth2-proxy 应该是 https:///oauth2/callback
  • 范围:应用程序对 GitLab 用户配置文件的访问级别。对于大多数应用程序,选择 openidprofileemail 即可。

创建完应用后,会生成一对 ID 和密钥,这个在后面会用到。

4.2 生成Cookie密钥

生成 Cookie 密钥。该 Cookie密钥 作为种子字符串以产生 安全的cookie

参考官方说明,使用 base64 编码,可利用以下的 python 脚本生成字符串。

import secrets
import base64

print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))))

4.3 部署oauth2-proxy

k8s 中部署 oauth-proxy ,资源清单 oauth2-gitlab.yaml 和相关参数说明如下。更多的配置参数,可以参考 官方文档

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: oauth2-proxy
  template:
    metadata:
      labels:
        k8s-app: oauth2-proxy
    spec:
      containers:
      - name: oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 4180
          protocol: TCP
        args:
        # OAuth提供者
        - --provider=gitlab
        # 上游端点的http网址
        - --upstream=file:///dev/null
        # 对具有指定域的电子邮件进行身份验证,可以多次给出,使用*验证任何电子邮件
        - --email-domain=*
        # 监听的地址
        - --http-address=0.0.0.0:4180
        # 设置安全(仅HTTPS)cookie标志
        - --cookie-secure=false
        # OAuth重定向URL
        - --redirect-url=https://nginx-test.ssgeek.com/oauth2/callback
        # 跳过登录页面直接进入下一步
        - --skip-provider-button=false
        # 设置X-Auth-Request-User,X-Auth-Request-Email和X-Auth-Request-Preferred-Username响应头(在Nginx auth_request模式下有用)。与结合使用时--pass-access-token,会将X-Auth-Request-Access-Token添加到响应标头中
        - --set-xauthrequest=true
        # 跳过OPTIONS请求的身份验证
        - --skip-auth-preflight=false
        # 绕过OIDC端点发现
        - --skip-oidc-discovery
        # OpenID Connect发行者url,这里是gitlab的url
        - --oidc-issuer-url=https://gitlab.ssgeek.com
        # 认证url
        - --login-url=https://gitlab.ssgeek.com/oauth/authorize
        # token url
        - --redeem-url=https://gitlab.ssgeek.com/oauth/token
        # 用于令牌验证的url
        - --oidc-jwks-url=https://gitlab.ssgeek.com/oauth/discovery/keys
        env:
        - name: OAUTH2_PROXY_CLIENT_ID
          value: '85945b7195ab109377183837b9221bd299bc64b31fe272304a1c777e8e241d83'
        - name: OAUTH2_PROXY_CLIENT_SECRET
          value: '2f9782928b493686f387d18db9138e92607448cef045c81319967cc3e5ce4ba1'
         # 安全cookie的种子字符串,可通过python脚本生成
        - name: OAUTH2_PROXY_COOKIE_SECRET
          value: 'VGlYNVBVOGw4UFgyRURzbERxVTRiZz09'

---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: oauth2-proxy
  name: oauth2-proxy
  namespace: kube-system
spec:
  type: NodePort
  ports:
  - name: http
    port: 4180
    protocol: TCP
    targetPort: 4180
    nodePort: 30020
  selector:
    k8s-app: oauth2-proxy

应用上面的资源清单,创建 deploymentservice

$ kubectl apply -f oauth2-gitlab.yaml
$ kubectl -n kube-system get pods -l k8s-app=oauth2-proxy 
NAME                           READY   STATUS    RESTARTS   AGE
oauth2-proxy-884695869-bkwns   1/1     Running   0          113s

上面通过 nodeport 单独暴露了 oauth2-proxy 应用,可以访问检查以确保浏览器可以正常打开

4.4 创建测试应用并配置Ingress

资源清单文件 nginx.yaml 如下,其中为该 nginx 应用配置了 https 证书

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.15
        imagePullPolicy: IfNotPresent
        name: nginx

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: kube-system
spec:
  selector:
    app: nginx
  ports:
  - name: nginx
    port: 80
    targetPort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  namespace: kube-system
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
    # 指定外部认证url
    nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
    # 指定外部认证重定向的地址
    nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/secure-backends: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - nginx-test.ssgeek.com
    secretName: nginx-test
  rules:
    - host: nginx-test.ssgeek.com
      http:
        paths:
        - path: /
          backend:
            serviceName: nginx
            servicePort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # 将nginx应用的访问请求跳转到oauth2-proxy组件url
    nginx.ingress.kubernetes.io/rewrite-target: "/oauth2"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/secure-backends: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: nginx-oauth2
  namespace: kube-system
spec:
  tls:
  - hosts:
    - nginx-test.ssgeek.com
    secretName: nginx-test
  rules:
  - host: nginx-test.ssgeek.com
    http:
      paths:
      - path: /oauth2
        backend:
          serviceName: oauth2-proxy
          servicePort: 4180

应用上面的资源清单,创建相应资源

$ kubectl apply -f other/nginx.yaml
deployment.extensions/nginx unchanged
service/nginx unchanged
ingress.extensions/nginx unchanged
ingress.extensions/nginx-oauth2 unchanged
$ kubectl -n kube-system get po,svc,ing |grep nginx                        
pod/nginx-5ddcc6cb74-rnjlx                    1/1     Running   0          3m

service/nginx                     ClusterIP   10.68.199.62            80/TCP                      3m

ingress.extensions/nginx               nginx-test.ssgeek.com             80, 443   3m
ingress.extensions/nginx-oauth2        nginx-test.ssgeek.com             80, 443   3m

4.5 测试外部认证

通过访问上面部署的 nginx 应用,在浏览器中进行测试,会被重定向到 Gitlab 登录页面;

输入账号,正确登录后,会被重定向回 nginx 应用。

4.6 流程分析

在请求登录外部认证的过程中查看 oauth2-proxy 的日志如下

172.16.1.110:49976 - - [2021/01/23 17:28:23] nginx-test.ssgeek.com GET - "/oauth2/auth" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 401 21 0.000
172.16.1.110:9991 - - [2021/01/23 17:28:23] nginx-test.ssgeek.com GET - "/oauth2/start?rd=%2F" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 302 341 0.000
172.16.1.110:9991 - admin@example.com [2021/01/23 17:28:32] [AuthSuccess] Authenticated via OAuth2: Session{email:admin@example.com user:root PreferredUsername: token:true id_token:true created:2021-01-23 17:28:32.440915913 +0000 UTC m=+2248.944621207 expires:2021-01-23 17:30:32 +0000 UTC refresh_token:true}
172.16.1.110:9991 - - [2021/01/23 17:28:32] nginx-test.ssgeek.com GET - "/oauth2/callback?code=9b7f5425d48a41f213065a4ff5b3d20d76e6d241ba753c5cd63d1a405f48818e&state=5d868b96b1f539a28da2291404152b7c%3A%2F" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 302 24 0.381
172.16.1.110:5610 - admin@example.com [2021/01/23 17:28:32] nginx-test.ssgeek.com GET - "/oauth2/auth" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 202 0 0.000
  • 访问 nginx 应用的时候, Ingress nginx controller 会向定义的 auth-url 发起认证,该认证由 Ingress nginx controller 发起,所以 Ingress nginx controller 对应的 pod 必须能够访问 auth-url

  • 如果认证没有通过, Ingress nginx controller 将客户端重定向到 auth-signinauth-signin 是目标应用的 oauth2 登录页面即 oauth2-proxy

  • 客户端被重定向到 oauth2 登录页面后,自动进入 Gitlab 的登录页面,

    用户登录 Gitlab 后, Gitlab 再将客户端重定向到在 Gitlab 中配置的应用回调地址。

  • 客户端访问回调地址后, oauth2_proxy 在客户端设置 cookie ,并将客户端重定向到最初的访问地址。

  • 带有 cookie 的客户端再次访问目标应用时,通过了 auth-url 的认证,成功访问到目标服务即 nginx 应用。

5、总结

本文以基于 k8s 部署的 nginx 服务为例,记录如何通过 ingressoauth2 proxy 对接 gitlab 实现对应用没有代码侵入的外部认证。

最后,还要提到的一点是,我这里一开始使用的 Gitlab 是已有的 10.8.4 版本,调试了关于 Oauth2-proxy 的很多参数一直不成功,也没有找到解决办法,但是按照官方的配置与 github 对接时却没有报任何异常。最终通过提交 issue 得到了可能原因,即 GitlabAPI 版本可能不兼容, oauth2-proxy 的开发测试成功版本的 Gitlab12.x 版本以上。详情可参考我提交的 issue

See you ~

参考文档:

https://oauth.net/2/
https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/
https://oauth2-proxy.github.io/oauth2-proxy/docs/