Ir al contenido

Cómo proteger tu clúster de Google Kubernetes Engine con Terraform e Istio

por Devoteam G Cloud , el 10 de febrero de 2021 2:03:14 PM

En este artículo,  mostraremos cómo puedes fortalecer tus clústers de Google Kubernetes Engine (GKE) con más seguridad usando Terraform e Istio. Aprenderás qué beneficios aporta cada nueva medida de seguridad y cómo puede configurarlos utilizando Terraform para integrarlos fácilmente en tu infraestructura. Al final de esta publicación, puedes encontrar algunas de nuestras mejores prácticas para seguir al desarrollar con Kubernetes y en GKE. Este artículo es una secuela de nuestras publicaciones anteriores en esta serie sobre cómo configurar canalizaciones CI / CD seguras de múltiples clústeres con Spinnaker en Google Cloud, usando Terraform. 

Este artículo fue escrito por Kevin Serrano, ingeniero de Google Cloud en Devoteam G Cloud y CTO en Nessie

En los artículos anteriores de esta serie, usamos Terraform para configurar clústers de Google Kubernetes Engine (GKE) de múltiples inquilinos en múltiples entornos (Dev, Stagging, Prod). También aislamos recursos en la nube en proyectos dedicados de Google Cloud Platform (GCP) y recursos de Kubernetes dentro de espacios de nombres. Además, configuramos de forma segura las credenciales para cada espacio de nombres y aprovisionamos Spinnaker para administrar las cargas de trabajo en cada clúster de forma independiente.

Esta es una breve descripción general de esta serie de artículos:

¡Ahora es el momento de brindar más seguridad a nuestros clústeres de GKE!

Primero, vamos a explorar los diferentes tipos de clústeres que ofrece GKE. Explicaremos cuáles son las ventajas de usar un clúster privado de GKE y aprenderemos cómo proteger su acceso.

En segundo lugar, presentaremos Istio y explicaremos cómo usar algunas de sus funciones para controlar el tráfico dentro de los clústeres. En tercer lugar, exploraremos tres funciones de seguridad que podemos usar en GKE para controlar nuestra carga de trabajo.

Finalmente, revisaremos algunas de las mejores prácticas de seguridad en Kubernetes, que también se pueden aplicar de manera más general a múltiples arquitecturas.

Tabla de contenido

  • Clúster privado de GKE
    • Reglas de red y firewall
    • Fragmentos de terraform
  • Malla de servicio de Istio
    • Instalación de Istio personalizable con Terraform en GKE
  • Asegurar la carga de trabajo
    • Identidad de carga de trabajo
    • Políticas de red
    • Políticas de seguridad de pod
  • Mejores prácticas

Clústeres de GKE públicos y privados

Clúster público de GKE

GKE ofrece dos accesos de clúster diferentes: clúster público y privado. Un clúster público no implica que el clúster pueda administrarse públicamente. Significa que el maestro y la IP de los nodos son públicos y, por lo tanto, se puede acceder a ellos desde cualquier lugar de Internet. La autenticación y la autorización siguen vigentes para restringir las llamadas al servidor API de Kubernetes en el maestro.

Cuando se crea un clúster en GKE, esta es la opción predeterminada y menos segura disponible. Exponer tanto el maestro como los nodos con IP públicas aumenta el riesgo de comprometer el clúster y las cargas de trabajo. Por ejemplo, será más fácil para un atacante abusar de un exploit que le permita SSH en los nodos si están expuestos en Internet.

Clúster de GKE privado

Por otro lado, un clúster privado no asigna direcciones IP públicas a los nodos. Reduce la superficie de ataque y el riesgo de comprometer las cargas de trabajo. El nodo principal, que está alojado en un proyecto administrado por Google, se comunica con los nodos a través del emparejamiento de VPC.

Google configura automáticamente este emparejamiento y no debe modificarse ni eliminarse. La IP del nodo maestro puede ser solo privada o tener una IP pública y privada. Ambos tienen pros y contras.

  • Establecer la IP del nodo maestro en privada. En este caso, el nodo maestro no tiene una IP pública. Esto significa que no se puede acceder a la API de Kubernetes desde Internet. Ésta es la opción más segura. Para acceder a él, necesitamos configurar una conexión a la red. Esto se puede lograr implementando un proxy dentro de la VPC que usaremos para comunicarnos con la API de Kubernetes a través de Cloud VPN o Cloud Interconnect desde una red local. Esta opción puede resultar costosa y compleja de configurar.
  • Establecer la IP del nodo maestro en pública, con la red autorizada habilitada. En este caso, el nodo maestro tiene una IP pública y privada, mientras que los nodos solo tienen privados. El acceso al nodo maestro se puede restringir habilitando las redes maestras autorizadas , que solo permite que IP específicas se conecten al nodo maestro. Esta opción es un buen compromiso para obtener la seguridad adecuada sin tener que usar una conexión dedicada a la API de Kubernetes como se mencionó anteriormente.

Clústeres de GKE privados con un extremo público

En la configuración que definimos para este artículo, elegimos usar una tercera opción. Escribimos el código de Terraform para crear los clústeres privados con un punto final público para el nodo principal. Luego, está protegido por redes maestras autorizadas, donde solo permitimos que las IP conocidas se conecten al clúster.

Esto no reemplaza la autenticación en el clúster. Cada usuario aún debe autenticarse para poder usar la API de Kubernetes que se ejecuta en el nodo principal. La administración de acceso aún se realiza mediante las funciones de IAM de GCP, que se traducen a RBAC predefinido en Kubernetes . Para una mayor granularidad, es necesario utilizar Kubernetes RBAC.

Redes y reglas de firewall

Se requiere un clúster nativo de VPC para configurar nuestros componentes de red deseados.

Definición de rangos de IP privados

Primero necesitamos definir rangos de IP privados para los nodos, servicios y pods. Para hacerlo, definimos una subred en nuestra red de VPC. Una subred en Google Cloud tiene un rango de IP principal y rangos secundarios opcionales (rangos de alias de IP). Crearemos 2 rangos secundarios dentro de nuestra subred. GKE asignará IP a los nodos del rango de IP principal y usará los 2 rangos secundarios para Pods y Servicios respectivamente.

Además, creamos reglas de firewall para permitir el tráfico interno dentro de la subred. En Google Cloud, podemos usar cuentas de servicio como parámetro de origen y destino de una regla de firewall.

Verificación de estado de Google

Se recomienda habilitar el acceso privado de Google, por lo que los servicios de Google son accesibles sin tener que pasar por la Internet pública. Sin embargo, aún debemos permitir que Google Health-check verifique el estado de los nodos. Se debe crear una regla de ingreso de firewall para permitir las verificaciones de estado de Google (actualmente, los rangos de IP para esto son 130.211.0.0/22 ​​y 35.191.0.0/16) 

Para que los nodos puedan recibir tráfico de Internet, lo cual no es posible por defecto debido a la falta de una dirección IP externa, necesitamos definir un Cloud NAT delante de nuestros nodos. El NAT proporciona una IP pública frente a todos los nodos. Se deniega el tráfico de entrada, lo que protege los nodos, pero se permitirán las solicitudes provenientes de algunas cargas de trabajo que se ejecutan en los nodos.

Con un cortafuegos con estado, si una regla de salida del cortafuegos permite una solicitud saliente, el cortafuegos siempre permite la respuesta también, independientemente de las reglas de entrada en algunos lugares.

Luego creamos un Cloud Router para redirigir el tráfico a nuestra red. 

Código de terraform

El recurso de Terraform google_container_cluster describe los parámetros para configurar un clúster de GKE. Para configurar un clúster GKE privada con el maestro autorizado de la red habilitada, tenemos que configurar el private_cluster_config , master_authorized_networks_config y ip_allocation_policy campos (opcional). De forma predeterminada, GKE reservará rangos de IP privados para los nodos, pods y servicios de la red de VPC. Sin embargo, es mejor optimizar la asignación de direcciones IP en función de las necesidades (futuras).

Debido a que queremos administrar la configuración de cada rango de IP utilizado por la configuración de Master Authorized Networks, usamos bloques dinámicos para mantener el código DRY . También definimos variables locales para combinar la IP creada por nuestra configuración de Terraform (por ejemplo, Spinnaker IP, Cluster IP) con las de nuestra configuración.

Muestra de los archivos de configuración principales, como mgmt.tfvars , dev.tfvars , etc.

recurso «google_container_cluster» «gke_environment» {

… master_authorized_network_config = {   cidr_blocks = [     {       display_name = «office» ,       cidr_block = «100.110.120.130/32»     }   ] … }

 

Esto significa que queremos que este rango de IP, que solo puede contener una IP única, esté autorizado en el clúster correspondiente.

Ejemplo de código para la definición de clúster de GKE, en el módulo de gke:

clientes locales { cidr_blocks = concat (var.master_authorized_network_config.cidr_blocks, [   {     display_name: «GKE Cluster CIDR» ,     cidr_block: formato ( «% s / 32» , google_compute_address.nat_ip.address)   },   {     display_name: «Spinnaker CIDR» ,     cidr_block formato ( «% s / 32» , var.spinnaker_ip)   } ] ) } de recursos «google_container_cluster» «gke_environment» { .. . ip_allocation_policy {

  cluster_secondary_range_name = google_compute_subnetwork.vpc_native_cluster.secondary_ip_range.0.range_name services_secondary_range_name = google_compute_subnetwork.vpc_native_cluster.secondary_ip_range.1.range_name } private_cluster_config {   enable_private_nodes = verdadero   master_ipv4_cidr_block = var.nwr_master_node # debe ser / 28   enable_private_endpoint = false } master_authorized_networks_config {   dinámicas «cidr_blocks» {     for_each = [ para cidr_block en local.cidr_blocks: {       display_name = cidr_block.cidr_block       cidr_block = cidr_block.cidr_block

 

    }]     Contenido {       cidr_block = cidr_blocks.value.cidr_block       display_name = cidr_blocks.value.display_name     }   } } .. . }

 

Istio Service Mesh

Istio es una herramienta muy compleja con muchas características. Llevaría demasiado tiempo explicar todas las posibilidades que Istio tiene para ofrecer aquí. Para obtener más información sobre qué es istio y por qué usarlo , le recomendamos leer su sitio web y ejecutar las muestras proporcionadas.

La arquitectura de Istio está compuesta por istiod, que es parte del plano de control, y un contenedor sidecar llamado envoy-proxy .

El proxy de sidecar se ejecuta en un segundo contenedor dentro del mismo pod que la aplicación. Maneja el tráfico de red y habilita características de seguridad adicionales como la Autenticación mutua (mTLS), que está habilitada de manera predeterminada en Istio versión 1.5+.

Instalación de Istio personalizable con Terraform en GKE

Hay varias formas de instalar Istio en GKE. La más simple es usar la instalación predeterminada de Istio administrada por GKE, que simplemente se puede activar desde la consola. Sin embargo, este método no nos permite personalizar nuestra instalación.

Hasta la versión 1.5 de Istio, era posible utilizar HELM para instalar Istio. Sin embargo, este método está obsoleto y ya no se recomienda.

Instalación recomendada de Istio

A partir de la versión 1.6 de Istio, la forma recomendada de instalar Istio es usar istioctl , que empaqueta las definiciones de recursos personalizados de Istio y el operador de Istio para instalar los recursos de Istio Kubernetes como puerta de enlace, servicio virtual , etc. Esta solución nos permite personalizar completamente Istio instalación y facilita su actualización a medida que las necesidades evolucionan con el tiempo. También viene con perfiles de Istio predeterminados, que son configuraciones de instalación de Istio predefinidas, que se pueden usar como base para las personalizadas.

En general, es posible definir recursos personalizados mediante la definición de recursos personalizados y los operadores en Kubernetes.

Debido a que istioctl es una CLI, podemos usar la imagen oficial de la ventana acoplable para instalar Istio según una configuración que definamos. Podemos integrar la instalación en nuestra configuración de Terraform y agregar recursos adicionales para que istio esté disponible automáticamente.

Instalación de Istio personalizable en nuestros clústeres

Nuestro objetivo es tener una instalación de Istio personalizable en nuestros clústeres, donde podamos acceder de forma segura a Kiali y Grafana, que está conectado a Prometheus. También requerimos que todo el tráfico pase a través de la puerta de enlace de entrada de Istio y no directamente a las aplicaciones dentro de la malla de servicios.

Qué necesitamos hacer:

  • Definir las credenciales utilizadas por nuestro proceso de instalación
  • Definir la configuración de Istio
  • Instale Istio según la configuración
  • Exponga Kiali y Grafana con un balanceador de carga HTTPS global

Primero agregamos un módulo istio en nuestra configuración de Terraform. Luego podemos definir una cuenta de servicio de Google con los roles / contenedores. Rol de administrador, ya que este permiso es necesario para instalar todos los recursos de Istio en el clúster. Luego, asignamos esta cuenta de servicio de GCP a una cuenta de servicio de Kubernetes mediante Workload Identity (consulte la tercera sección para obtener más información sobre Workload Identity).

Luego definimos un perfil personalizado ( istio-config.yaml ) basado en un perfil predefinido. Podemos listar los perfiles predefinidos ejecutando

lista de perfiles de istioctl

y luego volcando su contenido (aquí perfil predeterminado)

istioctl profile dump default

Esta es la forma más sencilla de personalizar un perfil. Por ejemplo, este archivo de configuración habilitará el `istio-ingressgateway` y agregará los componentes Grafana, Prometheus, Kiali y Tracing (Jaeger) 

$ cat istio-config.yaml

apiVersion: install.istio.io/v1alpha1 kind: IstioOperator metadata: namespace: istio-system name: istio-control-plane spec: profile: valores predeterminados :   pilot:     traceSampling: 0.1   tracing:     service:       type: NodePort   meshConfig:     outboundTrafficPolicy:       mode : ALLOW_ANY # https://istio.io/docs/tasks/traffic-management/egress/egress-gateway/ componentes:   egressGateways:     – habilitado:       nombre verdadero : istio-egressgateway   ingressGateways:     – habilitado:       nombre verdadero : istio-ingressgateway

k8s :         hpaSpec:

     

          minReplicas: 1           maxReplicas: 5         service:           type: NodePort           ports:             – nombre: status-port               port: 15021               targetPort: 15021             – nombre: http2               port: 8080               targetPort: 8080             – nombre: tls               port: 15443               targetPort: 15443             – nombre: tcp               port : 31400               targetPort: 31400             – nombre: https               puerto: 443               targetPort: 443 addonComponents:   grafana:

    habilitado: verdadero     k8s:       servicio:         tipo: NodePort   prometheus:     habilitado: verdadero     k8s:       servicio:         tipo: NodePort   kiali:     habilitado: verdadero     k8s:       servicio:         tipo:   seguimiento de NodePort :     habilitado: verdadero

 

Instalación de Istio

Ahora estamos listos para instalar Istio. Para hacerlo, vamos a ejecutar un trabajo de Kubernetes ( recurso kubernetes_job Terraform) con la cuenta de servicio y la configuración adecuadas montadas como un ConfigMap . El trabajo ejecutará los comandos de instalación por nosotros. Usamos múltiples bloques init_container para descargar istioctl , habilitar las credenciales y finalmente instalar istio usando la configuración proporcionada. El trabajo, implementado con Terraform, ejecutará el comando de instalación 

istioctl install -f istio-config.yaml

 

Definición de recursos personalizados adicionales

Finalmente, también definimos recursos personalizados adicionales. Dichos recursos no se pueden crear mediante el proveedor de Kubernetes Terraform en el momento de redactar este documento. En su lugar, estamos creando trabajos de Kubernetes que ejecutarán la aplicación kubectl en los recursos personalizados.

El proveedor de Kubernetes-alpha Terraform puede aplicar cualquier manifiesto, pero no es adecuado para producción en el momento de escribir este artículo.

Definiciones Los recursos habituales requeridas (CRD) son puerta de enlace , servicio virtual (Istio recurso personalizado) y ManagedCertificate (GKE recurso personalizado). Se utilizan para configurar correctamente un Kubernetes Ingress con HTTPS mediante certificados administrados de Google y enrutar el tráfico desde el equilibrador de carga directamente al backend de Istio creado por Istio. Esto asegura que todo el tráfico sea recogido por Istio y redirigido correctamente a la malla de servicios mediante Servicios virtuales. Con el tiempo, nuestra aplicación tendrá más servicios y necesitaremos crear más servicios virtuales para redirigir el tráfico correctamente.

Para utilizar certificados administrados por Google, es importante registrar la IP del balanceador de carga dentro de un servicio DNS, de lo contrario, Google no podrá validar el nombre de host y el balanceador de carga no funcionará. Además, los certificados administrados por Google actualmente no admiten nombres de host comodín, como * .example.com

Asegurar cargas de trabajo

Además de las medidas de seguridad implementadas, podemos aumentar aún más la seguridad del clúster y las cargas de trabajo mediante:

  • Identidad de carga de trabajo (solo GKE)
  • Políticas de seguridad de pod
  • Políticas de red

En esta sección, explicamos qué son y cómo configurarlo con Terraform.

Identidad de carga de trabajo

Workload Identity es una función exclusiva de GKE que administra por completo las credenciales para las aplicaciones que acceden a las API de GCP. Sin Workload Identity, la mejor manera de aprovisionar credenciales para una aplicación que se ejecuta en un Pod es montar las credenciales como un secreto de Kubernetes. En este escenario, el equipo de DevOps debe encargarse de reemplazar los secretos cuando se rotan las claves. Esto crea mucha complejidad y riesgos de seguridad.

Workload Identity resuelve este problema al asignar internamente las cuentas de servicio de GCP a las cuentas de servicio de Kubernetes que usan los pods. Luego, es posible otorgar los roles de IAM que la aplicación necesita a las cuentas de servicio de GCP, y el pod automáticamente tendrá las credenciales correctas de forma predeterminada en el pod que la aplicación puede usar.

Con Workload Identity, no es necesario administrar las credenciales ni montar secretos de Kubernetes. Workload Identity se encarga de esto automáticamente, incluidas las rotaciones de claves.

Habilitar Workload Identity requiere algunos pasos, pero se puede integrar en la configuración de Terraform.

  1. Habilita Workload Identity en el recurso del clúster para un proyecto determinado y habilita GKE_METADATA_SERVER en el grupo de nodos

 

 

«google_container_cluster» «gke_environment» {recurso   …   workload_identity_config {     identity_namespace = «$ {} var.project_id .svc.id.goog»   } } recursos «google_container_node_pool» «auto_scaling» {     …     workload_metadata_config {       node_metadata = «GKE_METADATA_SERVER»     }   } }

 

  1. Cree una cuenta de servicio de GCP y una cuenta de servicio de Kubernetes para cada aplicación que requiera acceso a las API de GCP, como hicimos cuando instalamos Istio.

 

recursos «