Diseño e implementación de una arquitectura Zero Trust para Elastic Container Service
¿Prefieres leer? El texto completo del artículo, más abajo, es la transcripción de esta narración (disponible solo en inglés).
Zero Trust es una filosofía de seguridad de red basada en la premisa de no confiar en ningún elemento de la red, tanto externo como interno. Inspirado en este enfoque, no solo para la red sino para todos los elementos de la infraestructura, empecé a diseñar e implementar una arquitectura con el objetivo de ser lo más restrictiva, privada y segura posible para desplegar contenedores orquestados con Elastic Container Service (ECS).
A lo largo de este artículo recorro meticulosamente cada paso que he dado al crear esta arquitectura, desglosando y explicando cada elemento de forma secuencial. Desde el diseño y la segmentación de la Amazon Virtual Private Cloud (VPC) hasta el enrutamiento, los Security Groups (SG) y las NetworkACL, los endpoints usados para la comunicación con Amazon Elastic Container Registry (ECR), la configuración de los roles y políticas necesarios, y finalmente, despliego un load balancer junto con los contenedores en ECS.
Visión general del proyecto
Con el objetivo de mantener la simplicidad y ejecutar de forma práctica el diseño conceptual que desarrollo en este post, he creado un servidor web básico con Bun. Este servidor tiene una función fundamental: responder con un «Hello World!». El objetivo de fondo es crear un contenedor para esta aplicación y desplegarlo de forma redundante en dos subredes privadas dentro de un clúster de ECS. La imagen Docker correspondiente se almacenará en ECR, y para rastrear los logs que genera mi servidor, uso Amazon CloudWatch. El siguiente diagrama muestra una representación visual de la arquitectura final.
Infraestructura como Código (IaC) para la arquitectura Zero Trust
En el repositorio de GitHub del proyecto, encontrarás todo el código necesario para implementar la arquitectura Zero Trust descrita arriba. En este repositorio está la Infraestructura como Código (IaC) necesaria para desplegar la infraestructura en AWS, como el servidor web y los ficheros necesarios para crear la imagen de contenedor Docker usada en este ejemplo. La IaC está organizada en seis Stacks de AWS CloudFormation (Networking, Permissions, Security, Endpoints, Balancer y Containers), junto con un Stack maestro que automatiza el despliegue de los otros seis.
Mi concepto de VPC Zero Trust
Una VPC en Amazon Web Services (AWS) es un entorno de red virtualizada que permite crear una red privada y aislada en la nube. Permite definir y controlar la topología de red, configurar reglas de seguridad, y conectar recursos de AWS de forma segura. Una VPC es un servicio regional que abarca todas las zonas de disponibilidad (AZ) existentes en la región donde se ha desplegado.
Decidir el Classless Inter-Domain Routing
Al crear una VPC nueva, hay que indicar el Classless Inter-Domain Routing (CIDR) que usará la red. CIDR es un estándar usado en redes para la asignación de direcciones IP, representado en forma de longitud de prefijo/máscara de subred. El prefijo es la dirección IP de la red, y la longitud de la máscara de subred indica cuántos bits de la dirección IP se usan para identificar hosts en la red.
El estándar Request for Comments (RFC) 1918 define un conjunto de direcciones IP reservadas para uso privado en redes locales. El objetivo principal es evitar conflictos de direcciones IP y permitir que las organizaciones implementen redes privadas sin necesidad de solicitar direcciones IP públicas únicas a los registradores de Internet. Los rangos definidos en este estándar son 10.0.0.0/8, 172.16.0.0/12 y 192.168.0.0/16.
En AWS, el tamaño de bloque permitido va de /16 a /28. Aunque es posible elegir un rango fuera del RFC 1918, no se considera una buena práctica. Es fundamental tener en cuenta que, una vez creada la VPC, no se puede modificar su CIDR. Para el diseño de red de este artículo, uso el rango 10.0.0.0/16, que ofrece un rango amplio para la segmentación de subredes dentro de la VPC.
Segmentación de red
Una subred es una subdivisión de una VPC a la que se le asigna un rango de red más pequeño dentro del rango de red de la VPC. Las subredes se ubican dentro de una zona de disponibilidad concreta de la región de la VPC y me permiten desplegar servicios de AWS dentro de ellas.
Para diseñar la segmentación de subredes de mi VPC, es una buena práctica aislar cada capa de mi infraestructura en una subred separada. Este enfoque permite aplicar medidas de seguridad de red específicas para cada capa, ofrece un control más preciso sobre el acceso a los servicios, y facilita la gestión del entorno.
Las subredes públicas son aquellas con acceso a Internet y accesibles desde Internet. En estas subredes despliego los elementos de red necesarios para establecer un punto de entrada para mi aplicación. Por otro lado, las subredes privadas no pueden establecer una conexión a Internet por sí mismas, y no son accesibles desde Internet sin un intermediario en una subred pública. Uso subredes privadas para desplegar los contenedores de la aplicación.
Como se ve en el diagrama, estas subredes residen dentro de una zona de disponibilidad. Esto significa que, si esta AZ tiene problemas, mi aplicación es susceptible de sufrir downtime. Todo falla todo el tiempo, hay que estar preparado para el fallo, y nada fallará. Por eso es necesario replicar mis subredes en otra AZ distinta, creando así la base para desplegar mi aplicación con alta disponibilidad.
Internet Gateway
Una vez segmentada y definida la red, añado los servicios necesarios para que la aplicación sea accesible desde Internet para los usuarios. Lo primero que hace falta es una Internet Gateway (IG), un componente de la VPC horizontalmente escalable, redundante y de alta disponibilidad que permite la comunicación entre la VPC e Internet. La IG permite que los recursos en subredes públicas se conecten a Internet si el recurso tiene una dirección IPv4 pública. Del mismo modo, los recursos de Internet pueden iniciar una conexión con recursos de la subred usando la dirección IPv4 pública.
Application Load Balancer
Como los contenedores están desplegados en subredes privadas y, por tanto, no son accesibles desde Internet, hace falta usar un elemento intermedio en mis subredes públicas que sí tenga acceso a Internet. Además, necesito repartir las peticiones que recibe mi aplicación entre los contenedores existentes, sobre todo si son redundantes, o enrutar las peticiones según el destino deseado.
El Application Load Balancer (ALB) es un servicio de balanceo de carga de nivel 7 que distribuye el tráfico de red entre aplicaciones específicas. El ALB opera en la capa de aplicación y puede enrutar peticiones según el contenido, como rutas de URL o cabeceras HTTP. Ofrece alta disponibilidad, escalabilidad, y permite una distribución eficiente del tráfico entre múltiples destinos, mejorando así el rendimiento y la fiabilidad de las aplicaciones en la nube.
Al crear un ALB, hace falta indicar dos o más subredes públicas a las que asociarlo. Estas subredes deben tener una máscara de red mínima /27 y disponer de 8 direcciones IP disponibles por subred. En estas subredes se desplegará una Elastic Network Interface (ENI), que permite al ALB enrutar tráfico hacia Internet y hacia mis recursos en subredes privadas.
Tráfico privado con VPC Endpoint
Tener un ALB permite dirigir el tráfico entrante hacia los contenedores de ECS. Sin embargo, los contenedores desplegados en subredes privadas no pueden establecer conexiones con el exterior, ni siquiera con servicios de AWS como S3 o ECR. Estos servicios requieren conexiones a endpoints fuera de la VPC. Aunque desplegar una NAT Gateway en las subredes públicas resuelve este problema, si solo necesitas conectividad a las APIs de servicios de AWS y no necesitas conexiones adicionales a Internet, una alternativa más eficiente es usar VPC Endpoints.
Los VPC Endpoints son una tecnología de alta disponibilidad y escalable que permite conectividad privada entre una VPC y los servicios de AWS, como si estuvieran en tu propia red. Esta opción es más económica, tanto en implementación como en coste por GB procesado, comparada con una NAT Gateway. Dentro de los VPC Endpoints se distinguen dos tipos: Interface Endpoint y Gateway Endpoint.
Interface Endpoint
Los Interface Endpoints funcionan añadiendo una Elastic Network Interface a las subredes especificadas dentro de tu VPC. Una vez desplegadas, estas subredes tendrán una dirección IP privada dentro del CIDR de la subred a la que se pueden lanzar peticiones HTTPS, conectándote al endpoint del servicio de AWS.
En el caso de esta arquitectura, necesito que los contenedores que corren en ambas subredes privadas puedan acceder a ECR para descargar imágenes Docker. ECR tiene dos endpoints igualmente necesarios para usar el servicio correctamente:
- com.amazonaws.{REGION}.ecr.api: este endpoint se usa para las llamadas a la API de Amazon ECR. Llamadas como BatchGetImage o GetDownloadUrlForLayer usan este endpoint.
- com.amazonaws.{REGION}.ecr.dkr: este endpoint se usa para operaciones del cliente Docker. Comandos como Docker Pull o Docker Push usan este endpoint.
Una buena práctica es desplegar Interface Endpoints en ambas subredes privadas en vez de solo en una. Esto consigue alta disponibilidad, ya que el Endpoint es accesible de forma independiente desde cada AZ. Además, evito tráfico entre AZs, lo que supone un ahorro en el coste de transferencia de datos.
Para gestionar el acceso a los endpoints, puedo asignarles una política basada en recursos. Esto me permite, por ejemplo, limitar qué roles pueden hacer llamadas a través de ellos o restringir acciones concretas dentro de la API. Para limitar el acceso a ECR solo al Task Execution Role, un rol de IAM que usan los agentes de ECS para hacer llamadas a la API de AWS en nombre del usuario, aplico la siguiente política:
{
"Statement": [{
"Sid": "AllowPull",
"Principal": {
"AWS": "arn:aws:iam::{ACCOUNT_ID}:role/TaskExecutionRole"
},
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:GetAuthorizationToken"
],
"Effect": "Allow",
"Resource": "*"
}]
}
Además de estos, hace falta un tercer endpoint para establecer una conexión con Amazon CloudWatch: com.amazonaws.{REGION}.logs. Los contenedores usarán este servicio para almacenar los logs generados por las aplicaciones. Para restringir quién puede escribir logs en CloudWatch a través del endpoint y determinar en qué grupo de logs concreto, aplico al endpoint la siguiente política:
{
"Statement": [{
"Sid": "PutOnly",
"Principal": {
"AWS": "arn:aws:iam::{ACCOUNT_ID}:role/TaskExecutionRole"
},
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow",
"Resource": [
"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:{GROUP_NAME}:*"
],
}]
}
Gateway Endpoint
A diferencia de los Interface Endpoints, los Gateway Endpoints no añaden ninguna ENI a las subredes. En su lugar, añaden una regla a la tabla de rutas de las subredes especificadas. Como no hay ENI, la comunicación con el servicio de AWS no se hace apuntando a una IP privada dentro del CIDR de la subred, sino llamando a las IPs de los servicios de AWS. La regla añadida a la tabla de rutas de la subred es una prefix-list gestionada por AWS. Esta lista contiene todos los rangos CIDR en los que S3 da servicio en la región donde está desplegado.
Una sorpresa que me encontré al implementar la infraestructura es que ECR usa S3 por debajo para almacenar las capas de las imágenes Docker. Por eso, para que descargar una imagen de ECR desde ECS funcione, las subredes privadas necesitan acceso al bucket: prod-{REGION}-starport-layer-bucket. Este bucket es transparente para mí, y ni tengo acceso a él ni puedo verlo en la lista de buckets de mi cuenta.
Para conectar con S3, puedo hacerlo con un Interface Endpoint o con un Gateway Endpoint. Hay algunas diferencias sutiles, pero la más importante es que los Gateway Endpoints no tienen coste. Configuro el endpoint para conectar con com.amazonaws.{REGION}.s3 y aplico la siguiente Document Policy para restringir el acceso al bucket específico que usa ECR.
{
"Statement": [
{
"Sid": "Access-to-specific-bucket-only",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::prod-{REGION}-starport-layer-bucket/*"]
}
]
}
La política actual impone restricciones al acceso al prod-{REGION}-starport-layer-bucket, permitiendo solo la acción GetObject. En un esfuerzo por limitar aún más el acceso, intenté especificar el Principal para permitir el acceso al endpoint exclusivamente a ECS. Probé opciones como indicar TaskExecutionRole o TaskRole como Principal, pero esta configuración impedía que los contenedores descargaran las imágenes Docker.
En mi intento de identificar las llamadas a S3, revisé los logs de AWS CloudTrail, pero las llamadas relevantes no quedaban registradas. La opción de habilitar S3 Access Logs no está disponible, ya que AWS no me da acceso a este bucket concreto. Al final concluí que AWS realiza estas llamadas de forma transparente para el usuario, lo que me impide aplicar restricciones adicionales a la política.
A pesar de la ausencia de un Principal específico, verifiqué que otros recursos de las subredes, salvo los contenedores de ECS, no pueden acceder al bucket. Esto se debe a que AWS implementa una Bucket Policy que restringe el acceso únicamente a las llamadas que se originan en operaciones de ECR.
Gestión del tráfico con Route Tables
Las route tables tienen un papel crucial en el control de cómo fluyen los datos dentro de una VPC. Es fundamental establecer reglas que dicten la ruta que deben seguir los paquetes de red, especificando destinos y su enrutamiento.
Para las dos subredes públicas, hace falta configurar dos rutas. La primera debe dirigir todo el tráfico con destino 0.0.0.0/0 hacia la Internet Gateway creada en la VPC. Esta configuración habilita el acceso a Internet para mis subredes públicas. La segunda regla asegura que todo el tráfico con destino 10.0.0.0/16 (el rango de la VPC) se enrute internamente dentro de la VPC.
En cuanto a las subredes privadas donde se despliegan los contenedores, además de la ruta para el tráfico local dentro de la VPC, se añade una regla para enviar tráfico a S3 a través del Endpoint durante la creación del Gateway Endpoint. El destino de esta regla es una AWS Managed Prefix List que contiene todos los rangos IP donde se puede acceder al servicio S3 de forma privada.
Elastic Container Service
ECS es un servicio de orquestación de contenedores desarrollado por AWS para simplificar el despliegue, la gestión y la escalabilidad de aplicaciones en contenedores con Docker. Aunque ECS se parece a Kubernetes y comparte el mismo objetivo general, destaca por ser considerablemente más sencillo y estar gestionado por AWS.
Para desplegar el contenedor de ejemplo que he creado, el primer paso es crear un clúster nuevo, una agrupación lógica de recursos de computación donde se pueden ejecutar tasks y services. Para ello, tengo que indicar un capacity provider. En este caso, elijo Fargate.
ECS Fargate ofrece una opción de despliegue serverless, que permite ejecutar contenedores sin tener que gestionar las instancias EC2 subyacentes. En vez de preocuparse por la infraestructura, los desarrolladores pueden centrarse únicamente en sus contenedores y en la configuración de recursos, ya que AWS gestiona automáticamente la capacidad, la escalabilidad y la infraestructura.
Además, hay que crear una task definition, que especifica cómo se ejecuta la aplicación, incluyendo detalles como la imagen del contenedor almacenada en ECR, la asignación de memoria y CPU, las variables de entorno, y otros parámetros esenciales. Una task es una instancia en ejecución de una task definition y puede consistir en uno o más contenedores que comparten recursos y se ejecutan juntos.
Por último, se crea un nuevo service, una abstracción que permite definir y ejecutar tasks de forma duradera y continua. Los services permiten mantener un número concreto de tasks en ejecución, gestionando automáticamente la escalabilidad, la recuperación ante fallos y la distribución de la carga.
Roles y permisos
En un entorno ECS, asignar roles y permisos es crucial para la gestión de contenedores y la ejecución de tasks. Hay dos roles principales que hay que definir: TaskExecutionRole y TaskRole.
El TaskExecutionRole es un rol que asume el servicio ECS para ejecutar y gestionar las tasks de contenedores. Este rol está diseñado para ser asumido por los agentes de ECS en las instancias del clúster que ejecutan las tasks de contenedores. A continuación, la política asignada a este rol:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PullDockerImage",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Sid": "WriteLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:{GROUP_NAME}:*"
}
]
}
El TaskRole está pensado para ser usado directamente por las aplicaciones que corren dentro de los contenedores. Esto me permite definir, para cada contenedor de mi aplicación, los permisos que tiene para acceder a otros recursos de AWS. En mi ejemplo, como mi aplicación no necesita acceder a ningún recurso, creo el rol sin ninguna política asociada.
Seguridad y aislamiento
El concepto fundamental de Zero Trust implica desconfiar por completo de todo. Por eso, al diseñar la infraestructura de red, mi enfoque fue restringir y autorizar tráfico solo cuando es estrictamente necesario para el correcto funcionamiento de la aplicación. Para gestionar la aprobación o denegación de tráfico en una VPC, tengo dos niveles donde se pueden establecer estas reglas: Security Groups y Network ACL.
Security Groups
Un Security Group es un mecanismo de seguridad a nivel de ENI (Elastic Network Interface). Funciona como un firewall virtual que controla el tráfico de red permitido hacia y desde las ENIs asociadas a él. Estos grupos contienen reglas que especifican los tipos de tráfico permitidos según direcciones IP, puertos y protocolos. Es fundamental entender que son Stateful (con estado): si configuro una regla para permitir tráfico saliente, autorizo automáticamente el tráfico entrante asociado a esa conexión ya establecida. Del mismo modo, si permito tráfico entrante, permito implícitamente el tráfico saliente que mi instancia genera como respuesta.
En mi arquitectura he identificado tres Security Groups distintos. El primero se asociará con las ENIs de las subredes públicas que pertenecen al Application Load Balancer. Este Security Group se encarga de bloquear todo el tráfico excepto el tráfico entrante por el puerto 443 desde cualquier origen. Esta configuración permite que cualquier usuario acceda a mi servidor web. En cuanto al tráfico saliente, debe permitir el acceso al puerto 3000 en las subredes privadas donde están desplegados los contenedores.
El segundo security group está pensado para las tasks de ECS. Estas deben permitir tráfico entrante desde el security group del load balancer en el puerto donde el contenedor expone el servidor web, en este caso el puerto 3000. Además, debe permitir tráfico saliente por el puerto 443 hacia el rango de red de las subredes privadas. Esta configuración se implementa para que las tasks puedan hacer llamadas a través de las ENIs de los endpoints.
Por último, tengo el security group de los endpoints. Estos deben simplemente permitir tráfico por el puerto 443 desde las subredes privadas donde se han desplegado. Esto permite que las tasks hagan llamadas a través de ellos, estableciendo una comunicación efectiva en la arquitectura.
Network ACLs
Una Network ACL es un firewall que opera a nivel de subred. Usa reglas para especificar qué tipo de tráfico se permite o se bloquea según direcciones IP, rangos de puertos y protocolos específicos. A diferencia de los Security Groups, las Network ACLs son stateless (sin estado), lo que significa que no mantienen información sobre el estado de las conexiones. Cada regla de una Network ACL se aplica de forma independiente, sin tener en cuenta el estado de conexiones anteriores. Es fundamental entender que la configuración de reglas en una Network ACL afecta al tráfico entrante y saliente por separado, sin establecer automáticamente permisos mutuos como en el caso de los Security Groups.
Establecer una política de Network ACL para restringir tráfico no es redundante, aunque ya se haya hecho mediante Security Groups. Implementar medidas de seguridad en distintas capas de una arquitectura ofrece una protección más robusta frente a posibles ataques. Al aplicar Network ACLs a nivel de subred, el tráfico no deseado se bloquea antes de llegar a ENIs concretas, evitando así ataques a nivel de red como port scanning o DDoS antes de que lleguen a elementos individuales de la arquitectura. En situaciones donde las reglas de los Security Groups fallan o se modifican por accidente, las ACLs evitan accesos no autorizados y ofrecen una gestión centralizada del tráfico a nivel de subred, facilitando la aplicación consistente de las políticas de seguridad en toda la red.
Como las Network ACLs no tienen estado, sé qué puertos de mis subredes recibirán tráfico, pero no sé desde qué puertos voy a responder. Los puertos efímeros (1024-65535) son temporales y los asigna dinámicamente el sistema operativo para las conexiones de red salientes. No me queda más remedio que habilitar puertos efímeros desde los orígenes deseados o hacia los destinos deseados.
Como mencioné antes, al implementar un Gateway Endpoint se añade una ruta a la tabla de direcciones de la subred correspondiente. Esta ruta tiene como destino una Managed Prefix List que cubre todos los rangos de red del servicio, en este caso S3. Sin embargo, en las Network ACLs no puedo usar directamente una Managed Prefix List como origen o destino. Por eso hace falta revisar las direcciones presentes en la lista y establecer una regla para cada una de ellas.
En las subredes públicas hay que permitir tráfico entrante por el puerto 443 desde cualquier origen, así como puertos efímeros desde las subredes privadas. Además, hay que permitir tráfico saliente por puertos efímeros hacia Internet y desde el puerto 3000 hacia las subredes privadas.
Por otro lado, las subredes privadas deben permitir tráfico entrante por el puerto 3000 desde las subredes públicas y por puertos efímeros desde todos los rangos de direcciones del servicio S3 en la región concreta. También deben permitir tráfico saliente por puertos efímeros hacia la subred pública y por el puerto 443 hacia los rangos de S3.
Cifrado de extremo a extremo
Una vez configurada la infraestructura en AWS, exploro cómo implementar la filosofía Zero Trust en las comunicaciones, tanto con los usuarios de la aplicación como entre los componentes de la arquitectura.
Una primera opción es mantener las comunicaciones usando el protocolo HTTP entre los usuarios y el ALB, así como entre el ALB y el contenedor de ECS. Sin embargo, esta opción no parece la más segura, ya que HTTP transmite datos sin cifrar, exponiendo la información a posibles interceptaciones y comprometiendo la confidencialidad de datos sensibles. Además, carece de autenticación del servidor, lo que la hace susceptible a ataques de man-in-the-middle y suplantación de identidad.
Para asegurar el cifrado de las conexiones entre el usuario y el ALB, hace falta crear o importar un certificado SSL en AWS Certificate Manager (ACM). El balanceador usará este certificado para establecer el túnel SSL con los usuarios. Al usar un ALB y comunicaciones HTTPS, es obligatorio que el balanceador termine la conexión y descifre la petición, ya que opera en la capa 7 (capa de aplicación) y necesita acceder al contenido de la petición para balancear según esa información.
Delegar en el ALB la tarea de terminar las conexiones SSL libera a mis aplicaciones, que están detrás de él, de la responsabilidad de realizar este proceso. Así, las peticiones del ALB a los contenedores se hacen por HTTP, resultando en una comunicación más ágil y ligera para mis aplicaciones.
Cifrar exclusivamente la comunicación entre los usuarios y el ALB es un escenario seguro y adecuado para la gran mayoría de situaciones. Esto es porque la comunicación entre el ALB y los contenedores ocurre de forma privada dentro de mi VPC, evitando la exposición a Internet. Sin embargo, hay situaciones concretas, motivadas por requisitos de cumplimiento normativo o la necesidad de reforzar la seguridad, donde es imprescindible implementar cifrado de extremo a extremo para asegurar las comunicaciones entre el ALB y los contenedores.
Para conseguir esta seguridad adicional, el ALB puede establecer conexiones HTTPS con los contenedores usando certificados instalados directamente en los contenedores. Es relevante señalar que el ALB no valida estos certificados, lo que permite usar certificados autofirmados o incluso caducados. Estar dentro de una VPC garantiza que el tráfico entre el ALB y los contenedores esté autenticado a nivel de paquete. Por tanto, no hay riesgo de ataques man-in-the-middle ni de suplantación, incluso en los casos en los que los certificados de los destinos no sean válidos.
Imagen Docker de un servidor web con Bun
Como mencioné al principio de este artículo, he configurado un servidor web bastante sencillo con Bun, una alternativa a NodeJS para ejecutar JavaScript en el servidor. Esto me permite containerizarlo y usarlo para probar la infraestructura que he implementado en AWS.
const server = Bun.serve({
port: 3000,
fetch(req) {
console.log('METHOD: ' + JSON.stringify(req.method));
console.log('HEADERS: ' + JSON.stringify(req.headers));
let res = new Response("Hello World!");
console.log('STATUS: ' + JSON.stringify(res.status));
return res;
},
tls: {
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
passphrase: "my-secret-passphrase",
}
});
Este servidor responderá con el mensaje «Hello World!» a cada petición que reciba. Como mencioné antes, uso un certificado autofirmado y una clave privada para que el servidor funcione por HTTPS con el ALB. Para generarlos, se pueden ejecutar los siguientes comandos de OpenSSL:
openssl genpkey -algorithm RSA -out key.pem -aes256
openssl req -new -key key.pem -out cert.pem -x509
Para generar la imagen Docker y subirla a un repositorio en ECR para su posterior despliegue en ECS, necesito un Dockerfile. Un Dockerfile es un fichero de configuración que usa Docker para construir imágenes de contenedor, especificando los pasos necesarios para instalar y configurar el entorno de la aplicación.
# Use the official bun image as the base image
FROM oven/bun
# Set the working directory to /webserver
WORKDIR /webserver
# Copy the inde.js and SSL certificates to /webserver
COPY ./index.js /webserver
COPY ./key.pem /webserver
COPY ./cert.pem /webserver
# Expose port 3000 for the web server
EXPOSE 3000
# Start the web server
CMD ["bun", "index.js"]
Hace falta crear un nuevo repositorio privado en ECR en mi cuenta de AWS. Una vez hecho esto, puedo ejecutar los siguientes comandos para construir la imagen Docker y subirla al repositorio:
# Build the Docker image from the current context
docker build -t webserver .
# Tag the local image with the AWS ECR repository address
docker tag webserver:latest ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/REPO_NAME:latest
# Get the AWS ECR access token to authenticate with the repository
aws ecr get-login-password --region REGION | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com
# Push the tagged image to the AWS ECR repository
docker push ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/REPO_NAME:latest
Conclusión
Esta arquitectura propuesta, siguiendo el enfoque Zero Trust e implementada en AWS, ofrece un marco robusto para el despliegue seguro de contenedores. Se ajusta a los principios fundamentales de zero trust, estableciendo un estándar de seguridad alto en la infraestructura y garantizando una protección sólida frente a posibles ataques. Esta estrategia no solo se centra en la protección perimetral, sino que va más allá, aplicando medidas de seguridad en cada capa de la infraestructura.
La segmentación de red en subredes públicas y privadas, combinada con la aplicación de reglas de seguridad en Security Groups y Network ACLs, me permite aislar los componentes de la arquitectura y restringir el acceso a los recursos solo a usuarios y procesos autorizados. El uso de VPC Endpoints para conexiones privadas a servicios de AWS como ECR y CloudWatch evita exponer estos recursos a Internet. Implementar cifrado de extremo a extremo en las comunicaciones entre los usuarios y el ALB, así como entre el ALB y los contenedores, garantiza la confidencialidad de los datos transmitidos.
¡Gracias por leer! Nos vemos en el próximo.