1 La necesidad de escenarios intermedios de migración a Cloud Native

En los dos post anteriores de esta serie, (despliegue y networking y NetworkPolicies) hemos hablado de una estrategia de migración a cloud native en la que puedan convivir tanto la virtualización como los contenedores gracias a una tecnología como KubeVirt.

Se ha introducido el concepto de aplicación cloud native híbrida, es decir, aquella cuyo dominio está compuesto de servicios virtualizados y contenedores. Hemos resuelto aspectos como el networking o el despliegue y también hemos demostrado el uso de otros elementos nativos de Kubernetes aplicados a la virtualización, como las Network Policies.

Gracias a KubeVirt es posible simplificar la gestión y eliminar la necesidad de plataformas separadas para los servicios virtualizados y los contenedores. El dominio de una aplicación cloud native híbrida se circunscribe a la Kubernetes, permitiendo una gestión unificada de la aplicación.

Vamos ahora a demostrar la combinación de operadores de Kubernetes con servicios virtualizados, añadiendo TLS a la base de datos MongoDB desplegada como una máquina virtual.

Retomando el ejemplo aplicación cloud native híbrida

Nota: Esta es una aplicación de ejemplo desarrollada para la demostración de este caso de uso. No se han aplicado prácticas de seguridad o gestión de la configuración, entre otros asuntos.

La aplicación que estamos usando en esta serie de posts tiene dos componentes fundamentales:

En este escenario, la aplicación Python ya está migrada a contenedores , pero la base de datos aún está virtualizada.

Aplicación Python migrada a contenedores.

El escenario final simplificado era el siguiente:

Escenario final simplificado

Securizando las comunicaciones con la base de datos virtualizada

La utilización de certificados TLS es crucial para garantizar la seguridad y confidencialidad de la comunicación entre servicios. El uso de TLS minimiza el riesgo de ataques como el "Man-in-the-middle", en el que un atacante podría interceptar y potencialmente alterar los datos en tránsito.

Además, los certificados TLS aseguran la identidad del servicio (autenticación) en caso de que la emisión de certificados se haga por parte de entidades de certificación (CA) de confianza.

Pero si bien la gestión de certificados TLS ya es un desafío en un entorno tan dinámico como Kubernetes, la cuestión se complica especialmente en nuestro escenario en el que se combina virtualización y contenedores.

El reto es encontrar un sistema adecuado para la gestión, distribución, almacenamiento e incluso rotación de certificados que pueda adaptarse a las particularidades de la virtualización y los contenedores.

Operador cert-manager

Cert-manager es un operador de Kubernetes que automatiza y maneja la vida completa de los certificados TLS, proporcionando una solución a los desafíos mencionados anteriormente.

Logo cert manager.

Con cert-manager es posible automatizar la emisión y renovación de certificados. Permite no solo hace un consumo declarativo de Kubernetes, sino también interactuar con las Autoridades de Certificación (CA) para solicitar automáticamente nuevos certificados y renovar los existentes antes de que expiren, lo que elimina la necesidad de hacerlo manualmente y minimiza el riesgo de interrupciones del servicio debido a certificados expirados.

Respecto al almacenamiento, cert-manager ayuda a resolver el problema del almacenamiento seguro de los certificados y las claves privadas. Los certificados y las claves se almacenan como Secretos de Kubernetes. Aunque es importante resaltar las limitaciones de seguridad asociadas a los secretos, ya que por defecto están almacenados en un formato base64, que no es seguro.

Generando certificados TLS para la aplicación híbrida

Entidad para la emisión de certificados

Vamos a ampliar el alcance de nuestra aplicación de ejemplo para demostrar el uso del operador cert-manager como solución unificada para la gestión de certificados en un entorno que combina virtualización y contenedores.

Una vez instalado el operador cert-manager en nuestro clúster de Kubernetes, lo primero es crear un ClusterIssuer, es decir, la entidad que puede emitir certificados.

El Custom Resource ClusterIssuer proporcionado por cert-manager es una entidad que es válida en todo el clúster, a diferencia del recurso Issuer, que solo es válido en el namespace en el que se declara.

Es importante destacar que en este ejemplo usaremos certificados autofirmados, ideales para entornos de desarrollo o testing. Este ClusterIssuer debería ajustarse en un entorno productivo para emplear bien una CA propia o soluciones como Vault o Let’s Encrypt.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}

Consumo declarativo de certificados

El Custom Resource “Certificate” es utilizado por cert-manager para generar un certificado TLS que será utilizado posteriormente para activar protocolo seguro de comunicación en la base de datos MongoDB.

El resultado queda automáticamente almacenado como un “Secret” de Kubernetes dentro del namespace, donde se encontrarán tanto el certificado como la clave privada. La clave privada es sensible y debe ser manejada cuidadosamente para evitar su exposición.

Los secretos en Kubernetes pueden ser montados en los pods según sea necesario, sin exponer la información a usuarios o aplicaciones no autorizadas.

Las secciones más relevantes del manifiesto son:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: mongodb-cert
  namespace: kubevirt-networking-poc
spec:
  commonName: mongodb.kubevirt-networking-poc.svc
  dnsNames:
  - mongodb.kubevirt-networking-poc.svc.cluster.local
  - mongodb-vm
  - mongodb
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
  secretName: mongodb-cert-tls

Almacenamiento del certificado y claves como Secrets

El Custom Resource “Certificate” genera un Secret que contendrá tres elementos:

Añadiendo TLS a la aplicación híbrida

Una vez que tenemos un emisor de certificados disponible y hemos declarado que requerimos un certificado (almacenado como Secret), debemos adecuar todos los componentes para aplicar la comunicación segura con los certificados TLS.Para ello:

TLS en la base de datos virtualizada

Con el objetivo de habilitar TLS en la base de datos MongoDB que está desplegada en una máquina virtual, aplicaremos estos cambios sobre nuestra aplicación:

Montar el secreto en la Máquina Virtual:

En el manifiesto de VirtualMachine, se debe configurar un volumen que haga referencia al secreto generado por el certificado. Este volumen se montará en un directorio específico dentro de la VM.

volumes:
  - secret:
      secretName: mongodb-cert-tls
    name: secret-disk

Además, se debe configurar un disk que haga referencia a este volumen:

devices:
  disks:
    - disk: {}
      name: secret-disk
      serial: SERIAL

Configurar MongoDB para utilizar TLS:

En el script de cloud-init, se debe configurar MongoDB para que utilice los archivos del certificado y de la clave que se encuentran en el directorio montado anteriormente. Esto se hace modificando el archivo de configuración de MongoDB, /etc/mongod.conf, para incluir las opciones de TLS y los caminos a los archivos del certificado y de la clave.

Vamos a utilizar un script Python para modificar el fichero de configuración de MongoDB:

content: |
  import ruamel.yaml
  yaml = ruamel.yaml.YAML()
  with open('/etc/mongod.conf', 'r') as file:
      config = yaml.load(file)
  if 'net' not in config:
      config['net'] = {}
  config['net']['bindIp'] = '0.0.0.0'
  if 'tls' not in config['net']:
      config['net']['tls'] = {}
  config['net']['tls']['mode'] = 'requireTLS'
  config['net']['tls']['certificateKeyFile'] = '/etc/ssl/mongodb.pem'
  config['net']['tls']['CAFile'] = '/etc/ssl/ca.crt'
  with open('/etc/mongod.conf', 'w') as file:
      yaml.dump(config, file)

Con esta configuración la base de datos mongoDB tendrá TLS activado cuando se levante el servicio.

Es importante señalar que la cadena de conexión a la base de datos debe cambiarse para agregar todos los parámetros y archivos TLS.

Si antes se utilizaba una cadena de conexión como la siguiente:

mongosh --host mongodb-vm --port 27017

Ahora, se deberá agregar parámetros adicionales para especificar que se debe utilizar TLS y proporcionar el certificado de la CA:

/etc/ssl/mongodb.pem --host mongodb-vm --port 27017

Aplicación Python

Respecto a la aplicación Python, también es necesario aplicar algunos cambios para que pueda establecer una conexión segura con la base de datos MongoDB.

Despliegue

El despliegue de la aplicación Python está utilizando el secreto generado previamente por el operador cert-manager para poder conectar a MongoDB utilizando TLS.

El manifiesto del despliegue (Deployment) debe ajustarse para que el secreto mongodb-cert-tls se monte como un volumen en el contenedor de la aplicación:

volumes:
  - name: mongodb-cert
    secret:
      secretName: mongodb-cert-tls

El contenido del secreto estará disponible en el pod en el directorio /certificates, debido a la configuración volumeMounts:

volumeMounts:
  - name: mongodb-cert
    mountPath: /certificates
    readOnly: true

Cliente de MongoDB en Python

La aplicación debe utilizar el secreto generado por el certificado para poder conectar a MongoDB de forma segura utilizando TLS.

En el código de la aplicación, puedes ver que se combinan los archivos tls.crt y tls.key en un único archivo PEM (/tmp/tls.pem) al inicio de cada solicitud. Luego, este archivo PEM se utiliza junto con el archivo ca.crt para establecer una conexión segura a MongoDB:

pem_file_path = "/tmp/tls.pem"
with open(pem_file_path, 'w') as pem_file:
    with open('/certificates/tls.crt', 'r') as crt_file:
        pem_file.write(crt_file.read())
    with open('/certificates/tls.key', 'r') as key_file:
        pem_file.write(key_file.read())

client = MongoClient('mongodb', 27017, 
                        tls=True, 
                        tlsCertificateKeyFile=pem_file_path, 
                        tlsCAFile='/certificates/ca.crt'
                        )

Resultado final

El resultado final para un usuario no varía respecto a lo que vimos en la primera parte.

Cuando un usuario invoque el endpoint que expone el servicio asociado a la aplicación Python, recibirá esta respuesta:

Respuesta final de la aplicación.

Respuesta final

Donde el nombre John Doe se ha recogido de una base de datos MongoDB virtualizada sobre una plataforma Kubernetes.

El cambio que hemos introducido es que la comunicación entre el contenedor con la aplicación Python y la base de datos virtualizada es ahora segura.

Yikes!

comunicación entre el contenedor con la aplicación Python y la base de datos virtualizada es ahora segura.

Show me the code!

Todo el código está disponible en el siguiente repositorio.

Ventajas de la convivencia de virtualización y contenedores en Kubernetes

Combinación de recursos de Kubernetes con virtualización

Esta serie de posts pretende demostrar cómo unificar la operación de una aplicación cloud native híbrida en Kubernetes.

En este caso hemos logrado una comunicación segura entre contenedores y máquinas virtuales con una gestión unificada, gracias a KubeVirt y al operador cert-manager.

No solo permitimos el despliegue conjunto de la aplicación, sino también el consumo declarativo de sus necesidades en cuanto a certificados y su uso aplicado en una máquina virtual para arrancar una base de datos MongoDB con TLS activado y en un contenedor para permitir una conexión segura entre la aplicación y la base de datos.

Conclusiones

Tal y como hemos podido ver en los post previos, KubeVirt introduce la virtualización como ciudadano de primera clase en Kubernetes.

No solo es posible definir la virtualización como un recurso más, sino que es posible combinarla tanto con recursos nativos de Kubernetes como con otros operadores como cert-manager.

Esta solución elimina la necesidad de una plataforma independiente de virtualización y garantiza la transición a cloud-native amortiguando las dificultades relacionadas con el paso a contenedores.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete