Проверка работоспособности GKE gRPC с помощью mTLS - PullRequest
0 голосов
/ 21 ноября 2018

Я пытаюсь реализовать службу gRPC в GKE (v1.11.2-gke.18) с взаимной аутентификацией TLS.

Если не применять аутентификацию клиента, проверка работоспособности HTTP2, которую GKE автоматически создает, отвечает, ивсе соединяется с проблемой.

Когда я включаю взаимную аутентификацию, проверка работоспособности завершается неудачей - возможно, потому, что не удается завершить соединение, так как ему не хватает сертификата и ключа клиента.

Как всегда, документация легкаяи противоречивый.Мне требуется полностью программное решение (т. Е. Без настройки консоли), но я не смог найти решение, кроме изменения вручную проверки работоспособности на TCP.

Из того, что я вижу, я догадываюсьчто мне либо потребуется:

  • реализовать пользовательскую проверку работоспособности mTLS, которая помешает GKE автоматически создать проверку HTTP2
  • найти альтернативный способ завершения SSL в контейнере, который неНе используйте service.alpha.kubernetes.io/app-protocols: '{"grpc":"HTTP2"}' проприетарную аннотацию
  • , чтобы найти способ обеспечить проверку работоспособности необходимыми учетными данными
  • изменить мою реализацию go, чтобы каким-либо образом проверять работоспособность сервера, не требуя mTLS, при одновременном примененииmTLS на всех других конечных точках

Или, может быть, есть что-то еще, что я не учел?Конфигурация ниже прекрасно работает для REST и gRPC с TLS, но разрывается с 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 голосов
/ 15 мая 2019

Кажется, что в настоящее время нет способа достичь этого с помощью входа GKE L7.Но я успешно развернул NGINX Ingress Controller .У Google есть неплохое руководство по развертыванию одного здесь .

. Это устанавливает балансировщик нагрузки TCP L4 без проверок работоспособности сервисов, оставляя 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 знал, что для HTTP-запросов бэкэнда используется HTTP2.
  • Вы можете перечислить несколько путей и напрямую к нескольким службам.В отличие от входа GKE, эти пути не должны иметь косую черту или суффикс звездочки.

Самое приятное то, что если у вас несколько пространств имен или вы также используете службу REST (например,gRPC Gateway), 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.
0 голосов
/ 22 ноября 2018

Поддержка HTTP / 2 и gRPC в GKE пока недоступна.Пожалуйста, смотрите ограничение .В работе уже есть запрос функции для решения проблемы.

...