Blog de Amazon Web Services (AWS)

Picturesocial – Cómo delegar el acceso a servicios de AWS desde Kubernetes

Por Jose Yapur – Sr. Developer Advocate, AWS y
Gerardo Castro – AWS Community Builder

 

Desplegar una aplicación en Kubernetes no solo es desplegar una solución “auto contenida”, también debemos considerar las dependencias, que en la mayoría de los casos vivirán fuera del clúster como Bases de Datos, Almacenamiento, Manejadores de Colas, etc. Por esta razón es importante tener en cuenta cómo es que los pods accederán a dependencias externas. Hay múltiples formas de lograrlo, pero hoy Gerardo Castro y yo queremos compartirte la que más usamos y que nos garantiza tener un buen control del flujo de datos.

Pero antes de entrar en detalles, es importante que entendamos algunos aspectos básicos de seguridad en Kubernetes, para ello, sugeriría que nos planteemos algunas preguntas como ¿Quiénes deberían acceder a nuestro clúster? ¿Qué acciones deberían poder realizar? ¿Qué servicios externos deberían poder alcanzar? Esta es una buena forma de entender los posibles riesgos que tiene nuestra arquitectura y cómo mitigarlos. Es aquí donde podemos comenzar a revisar estrategias y servicios de AWS que nos ayudarán a ejecutarlas.

Hablar de las buenas prácticas para asegurar Kubernetes puede resultar una conversación muy larga para un blog post, ya que habría que preguntarse, ¿buenas prácticas a nivel del clúster? a nivel del pod? del container? o incluso a nivel del Service Cloud Provider, pero vamos a resumir algunas de ellas desde una mirada general y con consideraciones claves para todo el que inicia su viaje sobre Kubernetes, para ello vamos a dividir nuestra mirada en 4 controles, dejando de lado la aplicación y centrándonos exclusivamente en Kubernetes en esta oportunidad: 1/ Permisos, 2/ Redes, 3/ Datos y 4/ Orquestador

 

Control de permisos

En este control, debemos garantizar que quienes accedan a nuestro clúster de Kubernetes sean personas o servicios que tienen los permisos apropiados. Esto significa que, si a un usuario se le otorga el permiso de autenticarse en nuestro clúster para ser capaz de crear o actualizar pods, solo sea capaz de hacer eso y no otras acciones. A esto le llamamos mínimo privilegio (PoLP), incluso podemos hacer que ese usuario solo tenga acceso y control exclusivamente de los recursos de un determinado namespace dentro del clúster. Esta segregación de permisos es lo que llamamos Control de Accesos basado en Roles (RBAC)

Lo que me gusta del enfoque de controles es que si profundizas en cada uno de ellos puedes ver que de un mismo control pueden desagregarse múltiples estrategias para cubrir todos los potenciales riesgos. Y es precisamente este control el que usaremos para proteger el acceso no autorizado de los pods a servicios de AWS.

 

Control de red

El Control de red nos ayuda a: 1/ Identificar cómo se comunican los recursos entre si y sus dependencias, incluso mediante protocolos, puertos, etc. 2/ Reducir el circuito de red, frente a una posible exposición que pueda ser aprovechada por un adversario mediante alguna herramienta de escaneo de puertos como tcpdump, wireshark, nmap, netcat, etc.

Dentro de este control priorizamos la observabilidad en la comunicación de los microservicios que existen dentro de nuestro clúster, una que nos permita poder ver en near real-time, de ser posible, la comunicación de red legítima y la posible comunicación de red que pudiera ser sospechosa.

¿Qué mecanismo de red nos ayudan a mitigar los riesgos relevados por este control? un motor de políticas y control de tráfico de red para Kubernetes.

 

Control de los datos

Kubernetes utiliza las unidades de almacenamiento de sus nodos o las unidades asociadas a los pods para almacenar información persistente requerida por la aplicación, pero ¿Qué sucede si los datos que se almacenan son información personal?, o ¿Cualquier otro dato sensible requerido por nuestra aplicación?, entonces debemos establecer mecanismos para que no cualquier otra pod o usuario pueda acceder a ellos. ¿Por qué? Una de las razones más sencillas es, si un atacante toma control de algún usuario con acceso privilegiado podrá acceder a estos datos y provocar una filtración de datos. Entonces, aplicar mecanismos para la protección de datos es crucial, el primero que deberíamos considerar es el uso de cifrado de datos en reposo, te sugiero explorar AWS Key Management Service.

 

Control del Orquestrador

Cuando trabajamos con Kubernetes tenemos que tener en cuenta los nodos que lo componen, estos tienen Sistemas Operativos y dependencias que necesitan ser actualizadas. Afortunadamente Amazon Elastic Kubernetes Service se encarga de eso por nosotros, sin embargo, queda una pieza importante que es la actualización de la versión de Kubernetes que ejecuta mi Clúster, tenemos que tener en cuenta que cada versión trae consigo mejoras de seguridad y debemos intentar crear un ritmo para siempre mantener el clúster al día. Te sugiero ver las versiones soportadas de Kubernetes sobre Amazon EKS en el siguiente enlace. Así mismo, es importante considerar la seguridad de los contenedores que se despliegan en Kubernetes, porque son ellos quienes pueden convertirse en una puerta trasera para nuestra arquitectura, por lo que es importante en tiempo de construcción utilizar herramientas como Amazon Inspector o la funcionalidad de escaneo de imágenes de Amazon Elastic Container Registry.

Ahora es momento de entender como todos estos controles se usan en la práctica con algo que pasó en la vida real y cómo se resolvió. Todos los nombres y datos son ficticios 😅

Algo que pasó

Recuerdo que en una empresa, el equipo de Plataforma decidió desplegar un cluster EKS dentro de una VPC, en este punto configuraron el OpenID Connect (OIDC) con IAM Provider y expusieron el endpoint con Public Access (significa que desde afuera cualquiera que tenga el API Endpoint podría llamarlo y consumir los recursos) y configuraron los workers nodes con una ingress rule que permitía el puerto 22 y 443 desde cualquier dirección, por si fuera poco este clúster se conectaba a una base de datos que estaba dentro de la misma VPC.

Dentro del clúster tenían un pod que usaban para conectarse a la base de datos usando el MySQL client para Linux. De pronto, algunos pods empezaron a destruirse sin motivo aparente, sin embargo, el equipo de plataforma decidió restarle importancia debido a que el equipo de monitoreo les respondió que detectaron que el consumo de CPU de algunos pods superaba el umbral del horizontal pod autoscaler (HPA) y eso creaba nuevos pods y destruía algunos otros. Todo bien si no fuera porque este comportamiento empezó a repetirse con mayor frecuencia y decidieron involucrar al equipo de seguridad para que determinaran si pasaba algo más allá de lo evidente. 👀

¿Cómo se resolvió?

Un miembro del equipo de seguridad, detectó un hallazgo con AWS GuardDuty de tipo “CryptoCurrency:EC2” relacionado a la instancia EC2 que alojaba a los pods, por lo que realizaron una evaluación completa a la configuración que utilizaron al momento de crear e implementar el clúster de Kubernetes y detectaron algunos problemas como:

  1. Endpoint de Kubernetes con acceso público.
  2. Permisos de ingreso públicos al puerto 22 y 443 de todos los worker nodes.
  3. Base de datos en la misma subnet donde estaban los worker nodes y con permisos de acceso público.
  4. Permisos para consumir la base de datos asignados a las instancias de Amazon EC2 y no a una pod en específico.
  5. Clúster con versión antigua de Kubernetes.
  6. Entre otras joyas 💠

Con todos estos hallazgos es que se decidió hacer la agrupación de cada uno de ellos en el dominio de cada uno de sus controles.

 

Hallazgo Control
1 Permisos, Redes
2 Redes
3 Redes
4 Permisos
5 Orquestador

 

Para la capa de permisos, Propusieron una matriz de usuarios/servicios con el detalle de cuál sería el nivel de permiso y hacia qué recurso, aplicando el principio de menor privilegio y RBAC. Así mismo siguieron los pasos para integrar Amazon IAM con Kubernetes, esos mismos pasos son los que aprenderemos a ejecutar en el paso a paso de este artículo.

A nivel de redes, redujeron la exposición de los puertos y orígenes, en el caso de necesitar acceder remotamente al clúster, crearon una instancia privada que sirva como Bastión Host, a la cual se podía acceder usando System Manager Session Manager y desde esta instancia se lanzaba el kubectl o ekctl para administrar el clúster. Con esto evitaron tener expuesto el puerto 22 a internet. Así mismo, configuraron AWS WAF para proteger las API’s que estaban debajo de un Application Load Balancer. Utilizaron reglas administradas por AWS como la Core Rule Set, SQL Database, y Known Bad Inputs, esto para ganar protección contra las amenazas publicadas en el OWASP Top 10, ataques de SQL injection, contra Cross-Site Scripting, entre otras.

A nivel de orquestador, se aseguraron de siempre tener la última versión de Kubernetes, así mismo se usó Amazon Inspector para determinar las posibles vulnerabilidades a nivel de capas de la imagen, con esto podrían prever a lo que se exponen antes de actualizar los pods a una nueva versión de la imagen Docker.

Todos estos controles nos permitieron enfocarnos en los problemas, ordenarlos por categorías y proponer mitigaciones a cada uno de los riesgos. Si bien es cierto, es imposible ser 100% seguros, está en nuestras manos hacer todo lo posible para proteger la confianza de los futuros usuarios de Picturesocial y evitar ser portada mundial de los medios, pero por las razones equivocadas.

Ahora, vamos a comenzar a implementar parte de lo aprendido usando la capa de permisos en nuestro Amazon Elastic Kubernetes Service usando Amazon Identity and Access Management, de esta forma nuestros pods podrán consumir servicios necesarios para Picturesocial como Amazon Rekognition y finalmente vamos a desplegar el API que construimos en el artículo anterior.

 

Pre-requisitos

O

  • Si esta es tu primera vez trabajando con AWS CLI o necesitas un refresh de como setear tus credenciales en el terminal te sugiero seguir este paso a paso: https://thinkwithwp.com/es/getting-started/guides/setup-environment/. Si no quieres instalar todo desde cero, en este mismo link podrás seguir los pasos para configurar Amazon Cloud9 que es un entorno de desarrollo virtual, que incluye casi todo el toolset que necesitas para este paso a paso.

Paso a Paso

  • Dentro de la descripción de nuestro Clúster encontraremos en “Details” la URL de OpenID Connect, esta es la que usaremos para generar una identidad Web en Amazon IAM, lo que finalmente nos permitirá usar policies para darle permisos a los pods de Kubernetes. Copiamos la URL.

  • Lo siguiente que haremos es darle clic en “Add provider”, donde seleccionaremos la opción OpenID Connect y en Provider URL pegaremos la URL que copiamos en el paso anterior.
  • Así mismo, estableceremos “sts.amazonaws.com” como Audience de este identity provider

  • Ahora, vamos a crear la Política que nos permitirá consumir objetos de S3 y usar Amazon Rekognition para luego establecer la relación de confianza entre la política y la identidad que creamos en un Rol.
  • Para crear políticas, tengo un súper truco! Y es esta web que te ayuda a generar Policies usando un Web UI, acepto flores y chocolates. https://awspolicygen.s3.amazonaws.com/policygen.htmlVamos primero a elegir IAM Policy, que es el tipo que generaremos.

  • En el paso 2, vamos a establecer que esta Policy solo habilita la detección de Labels en Amazon Rekognition, acá es donde podemos elegir todos los servicios y acciones que vamos a necesitar. Es importante siempre utilizar un enfoque Zero Trust, que significa dar la menor cantidad de permisos y privilegios necesarios para ejecutar una acción.

  • Adicionalmente, vamos a establecer que el Amazon Resource Name (ARN) es *, esto porque el recurso Rekognition no es un recurso nombrado, por tanto, no establece un nombre al cual referenciar. Esto va a variar con recursos nombrados como Amazon DynamoDB entre otros.
  • Ahora le damos clic a “Add Statement” y repetimos los pasos para S3 Bucket. En este caso usaremos el mismo S3 Bucket que usamos en el episodio anterior, que en mi caso se llama “picturesocial”, sin embargo, recuerda que debe estar armado en formato de ARN, acá te dejo una guía:
arn:aws:s3:::nombre_de_s3bucket
  • Finalmente vamos a generar la Policy dando clic en “Generate Policy” y ya la tenemos lista.

  • Ahora que ya tenemos la estructura de la Policy, vamos a crearla en Amazon IAM, para ello nos vamos a la sección de Policies y luego a “Create policy”, ahí seleccionamos JSON y seguimos los pasos de creación.

  • En mi caso la Policy se llamará picturesocial_eks_policy_rekognition_s3, le damos click en “Create policy”

  • De la misma forma y dentro de Amazon IAM, vamos a crear un Rol usando Web Identity y seleccionando el Identity Provider que creamos junto con la audiencia que indicamos.

  • Vamos a asignarle una Policy a nuestro rol, buscando la que creamos hace unos momentos “picturesocial_eks_policy_rekognition_s3”

  • Este rol tendrá de nombre “picturesocial_eks_role_rekognition”, le damos crear e inmediatamente después ingresamos al rol, donde le daremos Click a “Trust Policy” y luego “Edit Trust Policy”

  • Una vez que estemos dentro de “Edit trust policy” seremos capaces de editar el JSON de los Trusted Entities del rol. En esta vista reemplazaremos la línea 12 del JSON para que se vea parecida a la siguiente imagen. Yo he resaltado en azul oscuro únicamente la zona que debe ser reemplazada.

  • Lo que estoy diciendo con esa modificación es que la política solo permitirá a las pods bajo la cuenta de servicio “pictures-sa” del namespace “dev” de Kubernetes a consumir los servicios establecidos por el rol.  Ahora tenemos que crear nuestro namespace de desarrollo o “dev”, este será usado como ambiente previo para probar que todo funcione bien. Para ello ejecutaremos, tal cual aprendimos en el Episodio 4, el siguiente comando:
kubectl create namespace dev
  • Luego vamos a clonar la branch de este episodio para obtener nuevamente los archivos del API Pictures pero esta vez con los YAML para desplegar el Service Account y los Pod.
git clone git@github.com:aws-samples/picture-social-sample.git -b ep6
  • Ahora nos ubicamos en el directorio picture-social-sample y lo abrimos en VS Code o en nuestro IDE favorito.
cd picture-social-sample
code .
  • Una vez en VS Code vamos a editar el archivo serviceaccount.yml que se encuentra en la raíz, ahi nos enfocaremos en poner el ARN del role que acabamos de crear, ese lo encontramos al entrar al rol, en la zona superior.

  • Y debería quedar parecido a este:
apiVersion: v1
kind: ServiceAccount
metadata:
 name: pictures-sa
 namespace: dev
 annotations:
   eks.amazonaws.com/role-arn: arn:aws:iam::11112223333:role/picturesocial_eks_role_rekognition
  • Vamos a guardar los cambios y regresar a nuestro shell, donde le diremos a Kubernetes que cree ese Service Account.
kubectl apply -f serviceaccount.yml
  • Desde este momento asumiremos que ya revisaste los episodios anteriores donde te enseño como contenerizar y desplegar una aplicación a Kubernetes, si no lo hiciste, revisa el episodio 4 que sirve a manera de resumen.
  • Es importante que crees el container y lo subas al Amazon ECR. Ahora vamos a abrir el manifest.yml que está dentro del directorio pictures y vamos a ver algunos cambios con respecto a los manifest que hemos visto en episodios anteriores.
#########################
# Definicion de las POD
#########################
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pictures
  namespace: dev
  labels:
    app: pictures
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pictures
  template:
    metadata:
      labels:
        app: pictures
    spec:
      serviceAccountName: pictures-sa
      containers:
      - name: pictures
        image: 111122223333.dkr.ecr.us-east-1.amazonaws.com/pictures
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
#########################
# Definicion del servicio
#########################
kind: Service
apiVersion: v1
metadata:
  namespace: dev
  name: pictures-lb
spec:
  selector:
    app: pictures
  ports:
  - port: 80
    targetPort: 5075
  type: LoadBalancer
  • He resaltado en Bold lo nuevo. Básicamente estamos especificando de forma explícita el namespace que vamos a usar, por lo que ya no tenemos que pasarlo en el comando kubectl apply, adicionalmente estamos especificando el Service Account que estamos usando, que en nuestro caso se llama pictures-sa. Ahora vamos a desplegar esta API a Kubernetes y ver si los permisos que configuramos funcionan correctamente.
kubectl apply -f manifest.yml
  • Ahora si llamamos a la URL de nuestro servicio desde el navegador y pasamos el nombre de la imagen contenida en nuestro S3 Bucket, tal como hicimos en el episodio 5. Podremos ver que nuestra pod ya tiene los permisos correctos!

Con estos pasos hemos configurado correctamente los permisos a nivel de namespace y de pod y aprendido sobre los controles básicos de seguridad en Kubernetes. Gerardo y yo estamos muy contentos de que llegues hasta este punto en nuestro viaje para construir Picturesocial, una red social integradora y segura, que prioriza la confianza de nuestros clientes por sobre todo.

En el siguiente episodio aprenderemos a usar DynamoDB para que nuestra aplicación pueda almacenar los resultados de los análisis de las fotos usando una base de datos documental de alto desempeño, ¡Nos leemos en la siguiente oportunidad!

 


Sobre el autor

José Yapur es Senior Developer Advocate en AWS con experiencia en Arquitectura de Software y pasión por el desarrollo especialmente en .NET y PHP. Trabajó como Arquitecto de Soluciones por varios años, ayudando a empresas y personas en LATAM.

 

 

 

Gerardo Castro es AWS Community Builder