具有mTLS的GKE gRPC入口健康检查

时间:2018-11-21 11:26:18

标签: grpc kubernetes-ingress google-kubernetes-engine mutual-authentication kubernetes-health-check

我正在尝试使用相互TLS身份验证在GKE(v1.11.2-gke.18)上实现gRPC服务。

当不强制执行客户端身份验证时,GKE自动创建的HTTP2运行状况检查将响应,并且所有连接均会出现问题。

当我打开双向身份验证时,运行状况检查将失败-大概是因为它缺少客户端证书和密钥,因此无法完成连接。

与往常一样,文档内容很少且相互冲突。我需要一个完全编程的解决方案(即没有控制台调整),但是除了手动将运行状况检查更改为TCP之外,我无法找到解决方案。

据我所见 我猜我要么需要:

  • 实施自定义mTLS运行状况检查,这将阻止GKE自动创建HTTP2检查
  • 在不使用service.alpha.kubernetes.io/app-protocols: '{"grpc":"HTTP2"}'专有注释的容器中找到另一种SSL终止方法
  • 找到某种方式向健康检查提供所需的凭据
  • 将我的go实施更改为以某种方式在不需要mTLS的情况下对运行状况进行服务器检查,同时在所有其他端点上强制实施mTLS

也许还有其他我没有考虑的东西?下面的配置非常适合带有TLS的REST和gRPC,但不能与mTLS一起使用。

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: grpc-srv
  labels:
    type: grpc-srv
  annotations:
    service.alpha.kubernetes.io/app-protocols: '{"grpc":"HTTP2"}'
spec:
  type: NodePort
  ports:
  - name: grpc
    port: 9999
    protocol: TCP
    targetPort: 9999
  - name: http
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: myapp

ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: io-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "grpc-ingress"
    kubernetes.io/ingress.allow-http: "true"
spec:
  tls:
  - secretName: io-grpc
  - secretName: io-api
  rules:
  - host: grpc.xxx.com
    http:
      paths:
      - path: /*
        backend:
          serviceName: grpc-srv
          servicePort: 9999
  - host: rest.xxx.com
    http:
      paths:
      - path: /*
        backend:
          serviceName: grpc-srv
          servicePort: 8080

2 个答案:

答案 0 :(得分:1)

目前似乎无法使用GKE L7入口来实现。但是我已经成功部署了NGINX Ingress Controller。 Google提供了有关如何部署一个here的不错的教程。

这将安装一个L4 TCP负载平衡器,对服务不进行任何运行状况检查,而让NGINX处理L7终止和路由。这为您提供了更多的灵活性,但是细节在于魔鬼,而细节并不容易掌握。我发现的大部分内容都是通过github问题学习的。

我设法实现的是让NGINX处理TLS终止,并且仍将证书传递给后端,因此您可以通过CN处理诸如用户身份验证之类的事情,或者对照CRL检查证书序列

下面是我的入口文件。批注是实现mTLS身份验证所需的最低要求,并且仍可以在后端访问证书。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: grpc-ingress
  namespace: master
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
    nginx.ingress.kubernetes.io/auth-tls-secret: "master/auth-tls-chain"
    nginx.ingress.kubernetes.io/auth-tls-verify-depth: "2"
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/grpc-backend: "true"
spec:
  tls:
    - hosts:
        - grpc.example.com
      secretName: auth-tls-chain
  rules:
    - host: grpc.example.com
      http:
        paths:
          - path: /grpc.AwesomeService
            backend:
              serviceName: awesome-srv
              servicePort: 9999
          - path: /grpc.FantasticService
            backend:
              serviceName: fantastic-srv
              servicePort: 9999

一些注意事项:

  • auth-ls-chain机密包含3个文件。 ca.crt是证书链,应包括所有中间证书。 tls.crt包含您的服务器证书,tls.key包含您的私钥。
  • 如果此秘密位于与NGINX入口不同的名称空间中,则应在注释中提供完整路径。
  • 我的验证深度为2,但这是因为我正在使用中间证书。如果您使用的是自签名,则深度仅为1。
  • backend-protocol: "GRPCS"是必需的,以防止NGINX终止TLS。如果要让NGINX终止TLS并在不加密的情况下运行服务,请使用GRPC作为协议。
  • grpc-backend: "true"是必须的,以便让NGINX知道将HTTP2用于后端请求。
  • 您可以列出多个路径并定向到多个服务。与GKE入口不同,这些路径不应带有正斜杠或星号后缀。

最好的部分是,如果您具有多个名称空间,或者您也正在运行REST服务(例如gRPC网关),则NGINX将重用同一负载平衡器。与GKE入口相比,这可以节省一些费用,对于每个入口,将使用单独的LB。

以上是来自主名称空间的信息,下面是来自暂存名称空间的REST入口。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  namespace: staging
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
      - api-stage.example.com
      secretName: letsencrypt-staging
  rules:
    - host: api-stage.example.com
      http:
        paths:
          - path: /awesome
            backend:
              serviceName: awesom-srv
              servicePort: 8080
          - path: /fantastic
            backend:
              serviceName: fantastic-srv
              servicePort: 8080

对于HTTP,我使用的是LetsEncrypt,但是有很多关于如何进行设置的信息。

如果您执行ingress-nginx窗格中的内容,您将能够看到NGINX的配置方式:

...
        server {
                server_name grpc.example.com ;
                listen 80;
                set $proxy_upstream_name "-";
                set $pass_access_scheme $scheme;
                set $pass_server_port $server_port;
                set $best_http_host $http_host;
                set $pass_port $pass_server_port;

                listen 442 proxy_protocol   ssl http2;

                # PEM sha: 142600b0866df5ed9b8a363294b5fd2490c8619d
                ssl_certificate                         /etc/ingress-controller/ssl/default-fake-certificate.pem;
                ssl_certificate_key                     /etc/ingress-controller/ssl/default-fake-certificate.pem;

                ssl_certificate_by_lua_block {
                        certificate.call()
                }

                # PEM sha: 142600b0866df5ed9b8a363294b5fd2490c8619d
                ssl_client_certificate                  /etc/ingress-controller/ssl/master-auth-tls-chain.pem;
                ssl_verify_client                       on;
                ssl_verify_depth                        2;

                error_page 495 496 = https://help.example.com/auth;

                location /grpc.AwesomeService {

                        set $namespace      "master";
                        set $ingress_name   "grpc-ingress";
                        set $service_name   "awesome-srv";
                        set $service_port   "9999";
                        set $location_path  "/grpc.AwesomeServices";

                        rewrite_by_lua_block {
                                lua_ingress.rewrite({
                                        force_ssl_redirect = true,
                                        use_port_in_redirects = false,
                                })
                                balancer.rewrite()
                                plugins.run()
                        }

                        header_filter_by_lua_block {
                                plugins.run()
                        }
                        body_filter_by_lua_block {
                        }

                        log_by_lua_block {
                                balancer.log()
                                monitor.call()
                                plugins.run()
                        }

                        if ($scheme = https) {
                                more_set_headers                        "Strict-Transport-Security: max-age=15724800; includeSubDomains";
                        }

                        port_in_redirect off;
                        set $proxy_upstream_name    "master-analytics-srv-9999";
                        set $proxy_host             $proxy_upstream_name;
                        client_max_body_size                    1m;
                        grpc_set_header Host                   $best_http_host;

                        # Pass the extracted client certificate to the backend
                        grpc_set_header ssl-client-cert        $ssl_client_escaped_cert;
                        grpc_set_header ssl-client-verify      $ssl_client_verify;
                        grpc_set_header ssl-client-subject-dn  $ssl_client_s_dn;
                        grpc_set_header ssl-client-issuer-dn   $ssl_client_i_dn;

                        # Allow websocket connections
                        grpc_set_header                        Upgrade           $http_upgrade;
                        grpc_set_header                        Connection        $connection_upgrade;
                        grpc_set_header X-Request-ID           $req_id;
                        grpc_set_header X-Real-IP              $the_real_ip;
                        grpc_set_header X-Forwarded-For        $the_real_ip;
                        grpc_set_header X-Forwarded-Host       $best_http_host;
                        grpc_set_header X-Forwarded-Port       $pass_port;
                        grpc_set_header X-Forwarded-Proto      $pass_access_scheme;
                        grpc_set_header X-Original-URI         $request_uri;
                        grpc_set_header X-Scheme               $pass_access_scheme;
                        # Pass the original X-Forwarded-For
                        grpc_set_header X-Original-Forwarded-For $http_x_forwarded_for;
                        # mitigate HTTPoxy Vulnerability
                        # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                        grpc_set_header Proxy                  "";

                        # Custom headers to proxied server
                        proxy_connect_timeout                   5s;
                        proxy_send_timeout                      60s;
                        proxy_read_timeout                      60s;
                        proxy_buffering                         off;
                        proxy_buffer_size                       4k;
                        proxy_buffers                           4 4k;
                        proxy_request_buffering                 on;
                        proxy_http_version                      1.1;
                        proxy_cookie_domain                     off;
                        proxy_cookie_path                       off;

                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_tries               3;
                        grpc_pass grpcs://upstream_balancer;
                        proxy_redirect                          off;

                }
                location /grpc.FantasticService {

                        set $namespace      "master";
                        set $ingress_name   "grpc-ingress";
                        set $service_name   "fantastic-srv";
                        set $service_port   "9999";
                        set $location_path  "/grpc.FantasticService";

...

这只是生成的nginx.conf的一部分。但是您应该能够看到单个配置如何处理多个名称空间中的多个服务。

最后一部分是我们如何通过上下文获取证书的摘要。从上面的配置中可以看到,NGINX将经过身份验证的证书和其他详细信息添加到gRPC元数据中。

meta, ok := metadata.FromIncomingContext(*ctx)
if !ok {
    return status.Error(codes.Unauthenticated, "missing metadata")
}

// Check if SSL has been handled upstream
if len(meta.Get("ssl-client-verify")) == 1 && meta.Get("ssl-client-verify")[0] == "SUCCESS" {
    if len(meta.Get("ssl-client-cert")) > 0 {
        certPEM, err := url.QueryUnescape(meta.Get("ssl-client-cert")[0])
        if err != nil {
            return status.Errorf(codes.Unauthenticated, "bad or corrupt certificate")
        }
        block, _ := pem.Decode([]byte(certPEM))
        if block == nil {
            return status.Error(codes.Unauthenticated, "failed to parse certificate PEM")
        }
        cert, err := x509.ParseCertificate(block.Bytes)
        if err != nil {
            return status.Error(codes.Unauthenticated, "failed to parse certificate PEM")
        }
        return authUserFromCertificate(ctx, cert)
    }
}
// if fallen through, then try to authenticate via the peer object for gRPCS, 
// or via a JWT in the metadata for gRPC Gateway.

答案 1 :(得分:0)

GKE上的HTTP / 2和gRPC支持尚不可用。请参阅limitation。为了解决这个问题,作品中已经有feature request