使用 Nginx、Apache、Envoy 或其他反向代理将相互 TLS 转换为 OAuth 客户端凭证

Converting Mutal TLS to OAuth ClientCredentials with Ngnix, Apache, Envoy or other Reverse Proxy

我有许多使用 OAuth Bearer 令牌进行身份验证的服务。我可以使用 OAuth 客户端凭据授权或资源所有者凭据授权来获取令牌。

但是我有许多现有系统只能使用相互 TLS 身份验证连接进行身份验证的调用。

与其更新所有调用应用程序以获取 OAuth 不记名令牌,不如构建一个网关代理:

  1. 接收经过 TLS 身份验证的连接
  2. 使用证书的主题来识别系统参与者
  3. 使用客户端凭证或资源所有者授权代表该系统获取令牌
  4. 调用底层服务并return将结果发送给客户端

本质上,我想对旧客户端隐藏 OAuth 正在使用的事实,并允许它们专门使用 Mutal TLS 身份验证。

是否存在适用于 Ngnix、Apache、Envoy 或类似 HTTP 反向代理的现有反向代理或方法或模块,无需构建整个代理即可实现此目的?

我发现很多模块可以处理将 Apache 和 Ngnix 设置为 OAuth 中继方或资源服务器的情况,使用各种模块,例如

但找不到任何示例说明它们充当 OAuth 或 Open ID Connect 客户端作为相互 TLS 身份验证客户端的代理。

特别是我想避免写代理部分。即使我必须编写实际的 OAuth 交互脚本。我发现的最接近的是 blog post on implementing an OAuth RP in Envoy lua scripts.

我很难想象这是一个独特的需求,所以我想知道是否有任何我还没有找到的这种模式的标准实现。

事实证明,使用 Envoy Proxy 很容易做到这一点。特别是通过使用 External Authorization HTTP Filters.

Envoy 代理可以配置为执行 SSL 终止并通过在侦听器上设置 Downstream TLS Context 并将 require_client_certificate 设置为 true 来要求客户端证书。

可以配置 HTTP Connection Manager Network Filter to set the x-forwarded-client-cert header on the request to the upstream service. Specifically setting forward-client-cert-details to SANITIZE_SET will cause envoy to set the header. What is included in the header can be configured by setting set-current-client-cert-details

但要真正让 Envoy 进行令牌交换,我们需要配置外部授权过滤器。这允许 envoy 调用带有请求详细信息(包括证书)的服务,并且该服务可以决定是否允许该请求。至关重要的是,当允许时,它能够将 headers 添加到对上游服务的请求中,从而允许它添加 oauth 所需的承载令牌。

外部授权过滤器有网络过滤器和 HTTP 过滤器两种版本。如果要将 headers 添加到上游请求,则必须使用 HTTP。

生成的 envoy 配置如下所示:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          stat_prefix: ingress_http
          forward_client_cert_details: SANITIZE_SET # Include details of the client certificate in the x-forwarded-client-cert header when calling the upstream service
          set_current_client_cert_details:
            subject: True # Include the subject of the certificate in the x-forwarded-client-cert header rather than just the certificate hash.
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: service_backend
          http_filters:
          - name: envoy.ext_authz # Call an authorization service to do the OAuth token exchange
            config:
              grpc_service:
                envoy_grpc:
                  cluster_name: auth_service
                timeout: 5s # The timeout before envoy will give up waiting for an auth service response and deny access
          - name: envoy.router
      tls_context:
        require_client_certificate: True # Require downstream callers to provide a client certificate
        common_tls_context:
          validation_context:
            trusted_ca:
              filename: /etc/envoy/certs/ca-chain.cert.pem # CA certificate that client certificate must be signed with to be accepted
          tls_certificates:
          - certificate_chain:
              filename: /etc/envoy/certs/server-cert.pem
            private_key:
              filename: /etc/envoy/certs/server-key.pem
            password:
              inline_string: password
  clusters:
  - name: auth_service
    connect_timeout: 1s # The timeout before envoy will give up trying to make a TCP  connectio to an auth service
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {} # GRPC services must be HTTP/2 so force HTTP/2
    load_assignment:
      cluster_name: auth_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: localhost
                port_value: 8080

然后的诀窍是实现一个 GRPC 服务,该服务实现 External Authorization Protcol 以进行令牌交换并拒绝请求或提供 Authorization header 以包含不记名令牌在上游请求中。

如果您仍然需要它,我构建了一个使用本地身份验证数据库工作的授权服务器。很容易将其转换为调用 OIDC 并接收要注入的令牌。 https://github.com./fams/tlsjwt-gw