Kubernetes Containers
Kubernetes Containers
Terminología
1. Despliegue
Un despliegue es una serie de configuraciones aplicadas a un clúster.
2. Clúster
Red de computadoras / servidores virtuales funcionando en conjunto para poder afrontar tareas más exigentes
que las capacidades individuales.
3. POD
Un POD (también llamado erróneamente aplicación) es un despliegue, conformado por uno o más contene-
dores. Hay que entender que un POD puede contener varios contenedores distintos pero que conforman un
solo despliegue, por ejemplo; un POD puede ser solo uno y estar contenido de un contenedor de frontend,
un contenedor de backend y un contenedor de bases de datos, son 3 contenedores que en un su conjunto son
1 POD.
Resumen; Siendo “N” el número de un recurso.
1 Despliegue => N PODs => N+1 Contenedores
4. Nodo
Un nodo es un servidor virtual que forma parte del clúster, poniendo a disposición sus recursos para todo el
clúster.
5. Nodo maestro
Es el nodo encargado de llevar el registro de todos los despliegues, PODs, nodos y configuraciones para poder
coordinar a todo el clúster de forma apropiada.
6. Nodo trabajador / worker
1
Es el nodo, no único ya que pueden haber tantos como sea necesario, encargado de ejecutar los despliegues
y sus PODs.
7. Endpoint
Un ‘endpoint’ es un U.R.I (‘Uniform Resource Identifier’); Identificador de recursos Uniforme. Es una cadena
de caracteres, generalmente de sintaxis alfanumérica (véase; letras, números y caracteres especiales no usados
en los alfabetos hablados) que identifica todos la información necesaria para poder hacer uso de un recurso,
que generalmente se encuentra fuera del sistema operativo donde se ejecuta.
Un U.R.L. (‘Uniform Resource Location’); Localizador Uniforme de Recurso es una categoría de U.R.I, al
igual que el U.R.N (‘Uniform Resource Name’).
Un U.R.I posee las siguientes características;
[ ESQUEMA ] [ AUTORIDAD ] [ RUTA ] [ CONSULTA ] [ FRAGMENTO ]
• Esquema;
Permite identificar el protocolo de uso/consulta. Por ejemplo; “urn:”, “tag:”, “cid:”, “http:”, “mailto:”,
“ftp:”, entre otros.
• Autoridad;
Indica quien es la autoridad responsable y propietaria del recurso al que se está accediendo. Normalmente
suele estar identificada por una dirección IP o un dominio alfanumérico.
• Ruta;
Indica, de forma jerárquica, el recurso que se desea contactar. A veces se puede referir al recurso específica-
mente como; “recurso principal”.
• Consulta;
Indica, de forma estructurada (de ahí que dependiendo del protocolo y del encoding que se utiliza puede
variar la forma en que se debe pasar el dato. Ya que algunos por protocolo - debido a que requieren de
espacios, como JSON - no pueden ser colocados en la U.R.L.), la información que se le está pasando al
recurso que se quiere contactar.
• Fragmento;
Si el recurso principal tiene una capacidad modular de poder proveer varios servicios diferentes (como el
‘kube-controller-manager’ ), el fragmento permite identificar cual de todos esos se va a utilizar. Cabe aclarar
que no se refiere a opciones/argumentos del programa, si no a funciones diferentes (que pueden usarse por
C.L.I. mediante argumentos, aunque al estar automatizado entre programas no se usa para tal fin).
8. Forks
En informática, un ‘fork’ es un producto derivado de otro, con modificaciones y un proceso de desarrollo
separado (aunque puede seguir estando influenciado muy profundamente por el producto en el que se basan)
con un equipo detrás diferente.
2
Tanto el navegador Google Chrome como Microsoft Edge (desde 2020) se basan en el desarrollo abierto de
chromium para realizar sus modificaciones y sacar su propio producto.
9. Namespace
Una agrupación de recursos, que determinan su uso y características. Se definen y caracterizan por su
identificación mediante un nombre y un aislamiento del resto de grupos.
10. Daemon
Utilizado como sinónimo de aplicación de fondo. El término ‘daemon’ y su origen tiene controversia, ya que no
es del todo oficial, pero se prevee que proviene de una denominación antigua del griego, que debido a su misma
pronunciación a ‘demon’ pueden confundirse. ‘Daemon’ es un término que se refiere al acuñado por Fernando
Corbató en 1963 para la informática, tal describe; “… un proceso de fondo que trabaja incansablemente para
realizar tareas repetitivas “, ya que toma el significado original del griego en referencia a la esencia de algo
que se encuentra de fondo.
Debido a tal posibilidad de confusión, se usan también “service”/”servicio”. En el presente documento se
usan los tres términos de forma igual.
11. OCI – Open Container Initiative
La OCI es una organización sin fines de lucros que tiene por propósito la definición de estándares para
la creación y uso de los ‘runtimes’ así como de las interfaces de los contenedores entre todos los sistemas
operativos. A fin de que tales implementaciones usen un estándar de intercambio de información así como
de automatización y de implementación de forma agnóstica al sistema operativo y orquestador.
12. Sistema HOST
El sistema HOST es el sistema operativo que ejecuta el contenedor o la máquina virtual.
13. Process ID (PID)
Es el identificador único (ID) de un proceso, es siempre un número (generalmente un entero de hasta 32 bits).
Si el PID tiene el número “1” (uno) significa que es el primer proceso que se ejecuta luego de que el kernel.
Si estamos hablando de un contenedor, el PID 1 es el primer proceso que se ejecuta cuando arranca el
contenedor.
14. Modelo O.C.I
3
El modelo O.S.I (‘Open Systems Interconnection’) es una estandarización creada en 1980 con estandarización;
ISO 7498-1, el cual define los protocolos de red (físicos y virtuales) que se deben seguir para poder inter-
conectar sistemas de procedencia distinta para que estos pudieran intercambiar información sin impedimentos.
Cabe aclarar que el estándar no define a un ‘programa’ o ‘servicio’, si no los protocolos que debe seguir un
programa para poder funcionar en esa capa.
El estándar va en capas de profundidad, a mayor profundidad, menor abstracción y más cercano al mundo
físico;
• Capa física:
Es la capa más baja, se encarga de la tpología de la red y de las conexiones globales de la computadora hacia
la red, se refiere tanto al medio físico como a la forma en la que se transmite la información y de las redes.
• Capa de enlace
Es la capa encargada del direccionamiento físico mediante la distribución ordenada de tramas y del control
del flujo.
• Capa de red
Se encarga de identificar el enrutamiento existente entre las diferentes redes conectadas mediante la capa de
enlace y física. Acá ya se empiezan a usar los paquetes de datos; IP, IPX, AppleTalk, RIP, OSPF, BGP,
IGRP, entre otros.
• Capa de transporte
Es la capa encargada de hacer que un paquete llegue a otro sin importar que capa de red se esté utilizando
o si ambas capas no están conectadas directamente.
• Capa de sesión
Es la capa encargada de mantener y controlar el enlace establecido entre dos sistemas independientes.
• Capa de presentación
Es la capa encargada de que la representación de la información sea la misma que la de fuente y que sea
igual independientemente del sistema que lo represente (idioma,codificación, formato, sintaxis, etc).
4
• Capa de aplicación
Es la capa encargada de que otros usuarios y aplicaciones pueda acceder a los servicios de las capas anteriores
/ inferiores. Por ejemplo;
• rust std::net
Es la librería encargada de proveer a otros programas la capacidad de poder interactuar con las capas
de red y transporte pero no interactúa directamente con el usuario.
Reporte 2018-2022
Está bien poder confiar en la seguridad operacional de su infraestructura pero nunca se debe bajar la guardia;
5
6
Contenedores
Los contenedores son un tipo de tecnología, derivada de la virtualización, que permite a un programa (o un
conjunto de tales) el poder ser aislado del resto del mismo. A diferencia de la tecnología ‘sandbox’ común,
el programa contenerizado no puede saber (de buenas a primeras) que se encuentra en un espacio, salvo que
consulte los programas en ejecución ( ‘/proc’ en sistemas Linux, BSD y ‘tasklist.exe’ en sistemas Windows
NT).
El programa dentro del contenedor posee tres características que definen su aislamiento con respecto al resto
7
del sistema;
• Aislamiento de disco;
el programa aislado no puede acceder a los archivos del resto del dispositivo de almacenamiento fuera de tal
aislamiento.
• Aislamiento de RAM;
el programa aislado no puede obtener información en RAM del resto de programas (que programas hay en
ejecución, sockets, entre otros) fuera de tal aislamiento.
• Aislamiento de red;
el programa aislado no puede obtener información de los sockets (Unix y de red) abiertos fuera del aislamiento.
La característica de un contenedor es que tiene una relación binaria y de tipo cero con el sistema sobre
el que se ejecuta, así mismo el administrador de tal contenedor puede realizar modificaciones sobre tales
almacenamientos, permitiendo personalizarlos.
8
Imágenes
Una imagen es un contenedor comprimido, sin despliegue efectivo y sin los aislamientos configurados. El
contenedor es la instancia en ejecución de una imagen.
Runtime
El ‘runtime’ es el programa o librerías encargadas de realizar, gestionar, administrar, modificar y eliminar los
aislamientos que se generan o se usan para la creación del contenedor, así mismo también crean y gestionan
los contenedores ya en funcionamiento.
En los sistemas Linux se encuentran los siguientes runtimes para los contenedores;
• LXC: LinuX Containers
– https://linuxcontainers.org/
Researchgate.net copyright
9
– https://www.docker.com/
Docker.io copyright
10
• PODMAN: Runtime mediante librerías que no necesita un ‘daemon’ de fondo.
– https://podman.io/
RedHat copyright
11
Cri-o copyright
Virtualización vs Contenedores
12
RedHay Copyright
Instalables
Antes de proceder, por favor tenga en cuenta que como cri-o está diseñado y creado específicamente para
Kubernetes, se deben usar las versiones de cri-o diseñadas específicamente para una versión de Kubernetes.
Para tal, verifique que la versión de cri-o sea la misma que en Kubernetes. A tal fin configure la variable de
entorno para tal versión, por ejemplo;
VERSION=1.18
Fedora Linux
• Docker
sudo dnf remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine && \
sudo dnf -y install dnf-plugins-core && sudo dnf config-manager --add-repo https://download.docker.com/l
sudo systemctl enable --now docker
13
• Podman
sudo dnf -y install podman
• CRI-O
dnf module list cri-o && dnf module enable cri-o:$VERSION && dnf install cri-o
RedHat 8
• Podman
sudo yum module enable -y container-tools:rhel8 && sudo yum module install -y container-tools:rhel8
• CRI-O
Configure la variable de entorno “OS” de acuerdo a la versión que desea instalar. De igual manera en RedHat
cri-o viene con Openshift.
• Podman
sudo yum -y install podman
14
Operating system $OS
Centos 8 Stream CentOS_8_Stream
Centos 7 CentOS_7
Debian Linux
• Docker
sudo apt-get remove docker docker-engine docker.io containerd runc && sudo apt-get update && sudo apt-ge
ca-certificates \
curl \
gnupg \
lsb-release && \
sudo mkdir -p /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docke
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
• Podman
sudo apt-get -y install podman
• CRI-O
Configure la variable $OS de acuerdo a su versión del sistema operativo.
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key |
apt-get update
apt-get install cri-o cri-o-runc
Ubuntu Linux
• Docker
sudo apt-get remove docker docker-engine docker.io containerd runc && \
sudo apt-get update && sudo apt-get install ca-certificates curl gnupg lsb-release && \
sudo mkdir -p /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --
15
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docke
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
• Podman
sudo apt-get -y update
sudo apt-get -y install podman
• CRI-O
Configure la variable $OS de acuerdo a su versión del sistema operativo.
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key |
apt-get update
apt-get install cri-o cri-o-runc
Arch Linux
• Docker
pacman -S community/docker community/docker-compose community/runc community/containerd extra/bridge-uti
• Podman
pacman -S podman podman-compose podman-dnsname
Alpine Linux
• Docker
apk add docker docker-compose && addgroup username docker && rc-update add docker boot && service docker
• Podman
sudo apk add podman
• CRI-O
sudo apk add cri-o
Uso de contenedores
El uso de contenedores está, en cierta medida, estandarizada por la O.C.I (Open Container Iniciative) y sus
especificaciones;
16
1. Especificación para las imágenes de contenedores
• https://github.com/opencontainers/image-spec
2. Especificación para el ‘runetime’ del ‘engine’
• https://github.com/opencontainers/runtime-spec
3. Especificación para la distribución de contenedores
• https://github.com/opencontainers/distribution-spec
4. Especificación para los registros de contenedores
• https://github.com/opencontainers/artifacts
• Nota del editor;
Podman, al haber sido creado como una alternativa sin administrador ( ‘rootless’ ) tiene otro programa
llamado;
1. podman-docker
Este programa provee los alises, variables de entorno, binarios y demás archivos/configuraciones para que
todo componente, objeto, programa y/o usuario que llame directamente a Docker sea redireccionado hacia
podman.
2. podman-compose
Este programa provee los alises, variables de entorno, binarios y demás archivos/configuraciones para que
todo componente objeto, programa y/o usuario que utilice los Dockerfile pueda leerlos llamando directamente
a Docker y que sea redireccionado hacia podman de forma transparente.
Debido a tales especificaciones se puede agrupar los comandos.
Iniciar/reiniciar un contenedor
[docker_podman_crictl] [start, restart] [container_id_o_nombre]
Por favor, verifique la imagen superior; usted verá que se emite una alerta; “StopSignal SIGTERM failed to
stop container in 10 seconds, resoting in SIGKILL”.
17
En los sistemas de tipo Unix, toda la comunicación de un cambio de estado en un proceso se maneja
mediante señales, el ‘SIGTERM’ corresponde a un pedido de que el programa se cierre mientras que el
‘SIGKILL’ directamente le informa al núcleo de que mate tal proceso. Por lo que debe tener siempre en
consideración de que si su aplicación tarda más de 10 segundos en cerrarse, el engine o librería encargado de
la ejecución de tal contenedor puede corromper los datos si la aplicación los está escribiendo todavía pasados
esos 10 segundos.
Para evitar tal suceso, se puede personalizar el tiempo que se le da a la aplicación con PID 1 para cerrarse
antes de mandar el ‘SIGKILL’;
[docker_podman_crictl] stop -t [tiempo_en_segundos] [container_id_o_nombre]
Auditar un contenedor
Tanto un contenedor ‘standalone’ (véase, un contenedor que es administrado y manejado por el propio
usuario/a del sistema) como uno parte de un clúster o de otra aplicación, puede auditarse fácilmente en su
punto más escencial con;
[podman_docker_crictl] inspect [container_id_o_nombre]
Por favor verifique la imagen superior, aún estando incompleta así usted puede verificar la información que
se presenta. Entre los puntos técnicos que se pueden auditar están (pero no limitados a);
1. Id del contenedor
2. Fecha de creación
3. Path del PID 1 y el archivo de tal.
4. Argumentos del PID 1. Tenga en consideración que siempre el argumento número cero, por temas del
lenguaje es el propio binario.
5. El estado del contenedor (versión en cumplimiento con el estándar O.C.I), el estado en ejecución, si
fue terminano con ‘SIGNKILL’, el PID del lado del HOST del PID del contenedor, entre otros.
6. La ID de la imagen que se utilizó para crear ese contenedor.
7. El HASH con el algoritmo SHA256 de esa imagen.
8. El nombre identificablemente humano de esa imagen.
9. Si el root del filesystem es un punto externo, como un punto de montaje en red, cual es su PATH.
18
10. A que POD de Kubernetes pertenece.
11. Los PATH del sistema HOST.
12. Los PATH hacia los archivos del HOST que definen; el resolv.conf y el hosts.
13. El PATH donde se guardan los datos del contenedor.
14. El RUNTIME y driver del engine.
15. Nombre del contenedor.
16. Cuantas veces se ha reiniciado el contenedor.
17. El perfil de seguridad de AppArmor.
18. Las capacidades de interacción con el kernel.
Esto es un punto muy importante. Debido a que esto informa con que capacidades del kernel puede hacer
uso el contenedor. Por ejemplo;
Como usted puede apreciar arriba, existen capacidades como “CAP_CHOWN” (que indica que el con-
tenedor puede cambiar el propietario de los archivos a los que tiene acceso, y por ende modificarlos),
“CAP_KILL” (que indica que el contenedor puede solicitar al kernel que mate un deteriminado proceso),
“CAP_NET_BIND_SERVICE” (que indica que el contenedor puede abrir un puerto y hacer uso de él).
No existe un sistema o protocolo específico, rígido e inflexible para este tipo de análisis, ya que debe ser
siempre el administrador (conocedor de que aplicaciones son las desplegadas) el que deba avaluar si cada
contenedor tiene los permisos necesarios o si están excedidos.
19. Los puntos de montaje del sistema host dentro del contenedor.
Esto es otro punto muy importante. Ya que muchas veces, por error humano y falta de tiempo en documentar
el buen procedimiento, se monta el sistema de archivos completo (‘/’) dentro del contenedor. Esto nunca
se debe hacer, debido a que un contenedor comprometido que tenga esta característica es exactament igual
a casos prácticos que el sistema host comprometido.
20. Configuraciones de red propias de Kubernetes como; Endpoint ID, Gateway, IPAddress, entre otros.
21. Configuraciones de red en caso de que se redireccionen puertos del HOST:CONTENEDOR.
22. Nombre que recibe el contenedor en el sistema HOST que se ejecuta.
23. Variables de entorno como; lenguaje y su codificación por defecto (generalmente suele ser C.UTF-8), el
PATH interno de la shell (generalmente; PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin),
la configuración de terminal (se usa XTERM para hacer referencia a terminal externa, ya que el
contenedor por su aislamiento se considera remoto a efectos prácticos), el engine sobre el que se
ejecuta y el HOME del usuario root.
24. ‘Namespace’ en el cual se ejecuta
Otro punto que es realmente importante.
Como se mencionó en “Terminología”, un ‘namespace’ es; “Una agrupación de recursos, que determinan su
uso y características. Se definen y caracterizan por su identificación mediante un nombre y un aislamiento
del resto de grupos.”.
Se entiende “agrupación de recursos” por; recursos operacionales (disco, RAM, CPU, etc) y cualquier otro
tipo de objeto (por ejemplo; contenedores).
19
Por ende; un ‘namespace’ determina como el/los recurso/s que engloban tendrán acceso a los sistemas
operacionales de la red, así como el acceso a sus otros pares (un recurso) en el mismo grupo y como un
usuario de afuera va a poder interactuar con un grupo determinado (‘namespace’) o no.
25. Configuraciones sobre cuantos CPUs y cuanta memoria RAM puede usar
Si, como en la imagen superior, tanto “Memory” como “CpusetCpus” aparecen vacios o con un cero, es
operacionalmente igual a “ilimitado”.
Esto puede arrear serias consecuencias; como un DDOS al saturar un contenedor y hacer caer o todo el
clúster, si es que se utiliza un solo nodo ‘Worker’, o todo el nodo ‘Worker’ si se utilizan más de uno (recuerde
que es una buena práctica tener siempre N+1 nodos ‘Worker’ con respecto a los deployments).
Creación de un contenedor
Los contenedores no deben ser construidos sobre uno ya en funcionamiento (véase; se crea un contenedor,
se entra en él, se modifica y se utiliza tal). Los contenedores son creados desde un archivo que contiene las
especificaciones de creación y modificación (Dockerfile).
Finalidad del contenedor y de su ambiente El contenedor creado debe tener por objetivo la creación
de un entorno minimalista para el único fin de la ejecución del programa aislado. Como tal el mismo debe
proveer solamente las librerías, cabeceras, sockets y configuraciones para la ejecución de tal programa.
### Repository
RUN rm /etc/pacman.d/mirrorlist.pacnew
ADD mirrorlist /etc/pacman.d/mirrorlist
RUN pacman -Syyuuq --noconfirm
### BuildSystem
RUN pacman -Syyuuq base-devel curl rust git go nss nspr protobuf openssl-1.0 --needed --noconfirm
RUN sed -i '#RUSTFLAGS\=\"-C\ opt\-level\=2/RUSTFLAGS\=\"\-C\ opt\-level\=2\ \-C\ debuginfo\=0\ \-C\ tar
ADD build.sh /usr/bin/build
RUN pacman-key --init && pacman-key --populate && pacman-key --refresh-keys
20
### User
EXPOSE 987/tcp
VOLUME /data
#
CMD ["/bin/su","nobody","-"]
• FROM
Especifica la imagen base que se va a utilizar para realizar las modificaciones especificadas. La imagen base
debe ser una autorizada por la organización y de estar previamente auditada. Así mismo no se recomienda la
utilización del TAG “latest” de la misma, en especificación de la última versión del contenedor. Esto debido
a que debe ser probada antes de la utilización de las últimas versiones disponibles, para evitar problemas de
compatibilidad.
Sintaxis;
FROM [repositorio]/[imagen]:[versión]
• RUN Cada sentencia indica la ejecución de un comando (o serie de comandos) dentro del contenedor.
Cada sentencia es ejecutada dentro de un contenedor intermedio, a fin de que si alguna de ellas falla no es
necesario el reinicio desde cero de todo el proceso. Para un mismo comando que contiene nuevas líneas se
debe usar al final de cada una ’’
Sintaxis;
RUN [comando]
• ADD Especifica el copiado de un determinado archivo dentro de un directorio del contenedor. El mismo no
mantiene los permisos de usuario y propietario del sistema host, si no que pasan a formar parte del usuario
root. Sí se mantienen los atributos que se puedan añadir al mismo (como por ejemplo; inmutabilidad).
Sintaxis:
ADD [archivo_a_copiar] [destino_dentro_del_contenedor]
• EXPOSE Especifica el puerto y el protocolo del contenedor que deben ser expuestos en el sistema host. Esto
no determina el puerto del host, si no el puerto que el contenedor verá abierto con un protocolo específico.
Sintaxis;
EXPOSE [PUERTO]/[tcp_udp]
• USER Especifica el usuario y grupo que se debe utilizar para ejecutar el PID 1 del contenedor y todos sus
procesos hijos. Se debe especificar un usuario no root a fin de garantizar otra capa de seguridad operacional
dentro del aislamiento del contenedor.
Sintaxis:
USER [USUARIO]:[GRUPO]
USER [ID_USUARIO]:[ID_GRUPO]
La especificación del “grupo” es opcional. Y el usuario especificado debe existir dentro del contenedor y
tener la capacidad de ejecutar una shell, esto último no es un requerimiento obligatorio para ejecutar un
programa, pero se corre el riesgo de que intente buscar variables de entorno no heredadas y el mismo falle.
• CMD Especifica cual va a ser el primer comando que se va a ejecutar dentro del contenedor. Todos los
contenedores deben utilizar un supervisor de proceso. No se debe utilizar como proceso a SystemD si no a
un supervisor diseñado para tal como “supervisord” que controle que cuando el proceso finalice sea iniciado
nuevamente con determinadas configuraciones. Tampoco se debe utilizar como proceso de inicio a una shell.
21
Sintaxis;
CMD [“[PATH]/[binario]”,”[argumento�]”,”[argumento_n]”]
• ENV Especifica las variables de entorno que serán pasadas al contenedor. Tales variables estarán
disponibles para todos los usuarios de forma indiscriminada. No se deben pasar contraseñas, certificados,
usuarios, llaves y tokens a través de variables de entorno.
Sintaxis;
ENV MY_NAME="John Doe"
• VOLUME Especifica que punto de montaje del sistema host se debe crear dentro del contenedor. Tiene
por argumento la carpeta que debe crearse dentro del contenedor, el runtime creará luego en el sistema host
la carpeta que será montada en el primero.
Sintaxis;
VOLUME [PATH_COMPLETO]
Nomenclatura Una vez que el contenedor es creado desde el comando; “podman build -t [nombre]:[versión]
. “ no se debe utilizar la nomenclatura de versión “latest” si no que se debe especificar la versión. Se
recomienda utilizar la nomenclatura; X.Y.Z en donde;
X: Indica la versión mayor del contenedor. La versión mayor debería indicar cambios de gran signific
Y: Indica el número de características incluidas o puntos del roadmap completados en esa versión. Cad
Imagen base La imagen base sirve como base del contenedor luego personalizado. Como tal, y debido a
las características de tales despliegues el contenedor base debe ser Alpine Linux:
https://hub.docker.com/_/alpine/
Esto es así debido a que;
• Alpine no hace uso de los coreutils, findutils y greputils de GNU. Hace uso de un solo binario que
provee todas las funcionalidades necesarias para el correcto funcionamiento del contenedor con las
funcionalidades básicas de manejo de archivos y de red a través de BusyBox. De esta manera se reduce
la superficie de ataque por dependencias en aproximadamente un 95%.
• Alpine hace uso de la librería musl en vez de glibc para soporte de archivos y red de los programas que
hacen uso de las llamadas en C y C++. Musl a diferencia de Glibc (GNU Library C) fue diseñado
para permitir un enlazado estático de los programas de forma eficaz, a la vez que su implementación
de las llamadas de C y C++ permite evitar ataques de condiciones de carrera así como actualizaciones
de la ABI y la eficacia del tamaño. Así mismo también el uso de musl por sobre glibc proporciona
compatibilidad con el estándar agnóstico de POSIX 2008 y C11 como las extensiones no estándar de
Linux, BSD y glibc.
22
• Así mismo generalmente Alpine Linux compila los programas de forma estática, esto evita que una
vulnerabilidad presente en una librería compartida (archivos con extensión; .o y .a) pueda ser explotada.
Ya que un programa compilado de forma estática no hace uso de dependencias externas a nivel de
sistema operativo, operando de forma totalmente autónoma.
Esta suele ser una característica en común de las distribuciones que utilizan musl.
Todo lo anterior permite que el contenedor base de Alpine ocupe un aproximado de 4MB en su imagen por
defecto, en contraste con Centos 7.9 con 73.15MB, Ubuntu Rolling Release con 29.01MB y Debian Stable
Slim con 30.89MB. Reduciendo así la superficie de ataque en términos de dependencias y librerías dinámicas.
Creación de paquetes personalizados para la imagen La creación de paquetes de Alpine está descrita
con sumo detalle en la documentación oficial;
https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package
Se recomienda la creación de tales paquetes, en conjunto con la sección “Compilación de programas y
endurecimiento” para así crear y automatizar la personalización de los paquetes que van a ir destinados en
el contenedor. Así mismo la personalización y automatización de tal proceso permite el endurecimiento final
del binario a utilizar en los despliegues, con tal de asegurar un nivel de seguridad tan alto como sea posible.
23
Flags de compilación Tanto GCC, como Clang (front-end de LLVM) tienen los siguientes flags de com-
pilación para poder fortalecer la seguridad del binario final;
• GCC:
– -static
Habilita que el binario final sea estático, indicando así que las librerías dinámicas que haría uso un paquete
ortodoxo no las use, si no que las mismas sean integradas dentro del archivo final. Volviendo al mismo;
portable, más rápido y seguro.
• -fstack-protector-all -Wstack-protector –param ssp-buffer-size=4
Habilita todas las protecciones del stack de memoria RAM en el binario final y establece un límite fijo a
cada buffer de memoria temporal.
• -fPIE -pie
Habilita el código de posicionamiento independiente (Position-Independent Code). Esto evita que la carga
de datos en memoria se haga en lugares específicos adyacentes uno al otro, de tal manera que se mitigan
muchas de las técnicas de “buffer overflow” utilizadas.
• -D_FORTIFY_SOURCE=2 O2
Al momento de compilación, verifica si las funciones utilizadas en el programa son susceptibles a un desbor-
damiento de buffer (buffer overflow) y realiza saneamientos que no corrompan el funcionamiento.
• -Wl,dynamicbase
Indica al compilador que utilice ASLR (Address Space Layout Randomization) de forma obligatoria a fin de
que no se pueda adivinar en que sección de memoria guarda los datos el binario final.
• -Wl,nxcompat
Indica al compilador que utilice DEP (Data Execution Protection) para proteger zonas de memoria contra
ejecuciones de código inyectado
• Clang:
– -static
Habilita que el binario final sea estático, indicando así que las librerías dinámicas que haría uso un paquete
ortodoxo no las use, si no que las mismas sean integradas dentro del archivo final. Volviendo al mismo;
portable, más rápido y seguro.
• -fPIE -pie
Habilita el código de posicionamiento independiente (Position-Independent Code). Esto evita que la carga
de datos en memoria se haga en lugares específicos adyacentes uno al otro, de tal manera que se mitigan
muchas de las técnicas de “buffer overflow” utilizadas.
• -fstack-protector-all
Habilita todas las protecciones del stack de memoria RAM en el binario final y establece un límite fijo a
cada buffer de memoria temporal.
• -fsanitize=safe-stack
Este flag lo que hace es introducir un stack adicional en la ejecución del programa que contiene todas las
direcciones de retorno (en memoria) de las funciones. A fin de que una función (en código) finalizada no
pueda ser alterada a donde tiene que volver ni tampoco la ejecución en un stack inseguro desde uno que sí
lo es.
• -fsanitize=cfi -fuse-ld=gold -flto
24
Este flag indica que cuando ocurren llamadas directas e indirectas que conducen a comportamientos especí-
ficos, conocidos por ser vectores de ataques, se aborte el programa a fin de que no se vea comprometido su
ejecución en memoria. Eliminación de símbolos de depuración
Todo programa compilado tiene normalmente los símbolos de depuración. Esto permite que en entornos
de pruebas, en donde se debe ver que ocurre en la memoria, se pueda identificar funcionamientos erróneos
a nivel de sistema operativo y manejo de los diferentes stacks. En un programa final esto supone que el
atacante pueda depurar lo que está pasando a nivel de memoria dentro del sistema operativo.
Para eliminar tales símbolos se usa la herramienta ‘strip’:
/usr/bin/strip [archivo_binario_final]
A modo de ejemplo, se compila el programa nginx (de forma completa, con todos sus módulos) en donde se
puede ver su peso final antes y después de la eliminación de tales símbolos de depuración;
[cloudteam@base4sec nginx-1.23.0]$ ls -lh objs/nginx
-rwxr-xr-x 1 cloudteam cloudteam 3.8M Jul 5 07:43 objs/nginx
[ cloudteam@base4sec nginx-1.23.0]$ strip objs/nginx && ls -lh objs/nginx
-rwxr-xr-x 1 cloudteam cloudteam 795K Jul 5 07:44 objs/nginx
[ cloudteam@base4sec nginx-1.23.0]$
Por lo que los símbolos de depuración representan 79,5693% del peso total.
Superficie de ataque La compilación de un programa permite poder quitarle funciones y así realizar
una de las medidas más efectivas contra un ataque; reducir la superficie de ataque (a menor cantidad de
funciones/características, menos superficie de ataque al cual poder explotar vulnerabilidades).
A modo de ejemplo se pone los comandos por shell para compilar el servidor web y proxy reverso más popular
desde 2021; Nginx (https://github.com/nginx/nginx). En la presente versión, se le han deshabilitado todas
sus funciones adicionales, menos el módulo “rewrite” el cual permite redireccionar a determinados dominios
según la cabecera “Host”.
nginx_version=1.23.0
make -j${nproc}
Esta compilación de Nginx permite que la superficie de ataque sea mucho menor que la que viene por defecto
en cualquier otra distribución con paquetes distribuidos por medio de los repositorios. Así mismo, como es
un ejemplo, si los administradores de la plataforma necesitan un módulo específico se puede indicar que se
mantenga en el módulo final
Uso de secretos
Como se ha especificado anteriormente, no se deben usar variables de entorno para pasar contraseñas, certi-
ficados, usuarios, llaves y/o tokens. En cambio se deben usar los secretos;
Los secretos funcionan transmitiendo de una forma segura tales datos cuando el contenedor los necesita ya
que son cifrados y dados a contenedores específicos con condiciones de acceso específicas. Los secretos deben
ser siempre archivos y nunca son exportados de forma permanente en una imagen de contenedor.
La serie de comandos destinados al manejo de secretos son;
• Creación de secretos;
podman secret create [nombre_del_secreto] [ruta_hacia_el_archivo]
25
• Listado de secretos ya creados
podman secret ls
• Auditar / inspeccionar secretos ya creados
podman secret inspect [nombre_del_secreto]
• Eliminar secretos
podman secret rm [nombre_del_secreto]
Luego la utilización de tal secreto será posible de dos formas;
1. Uso mediante C.L.I. de forma manual;
podman run --secret [nombre_del_secreto] ……..
Luego el secreto estará ubicado en “/run/secrets/[nombre_del_secreto]”
2. Uso mediante Dockerfile
RUN --mount=type=secret,id=[id_del_secreto] source /run/secrets/[secreto_dentro_del_contenedor]
Luego el secreto estará ubicado en “/run/secrets/[secreto_dentro_del_secreto]”
|
:–: |
docker copyright |
Por ende, se deben especificar tanto en el Dockerfile como en la ejecución del contenedor un volumen y así
mismo el programa dentro del contenedor debe leer y guardar la información en tal.
Existen varias formas de utilizar volúmenes para el guardado de la información persistente en contenedores.
1. Uso mediante Dockerfile
VOLUME [nombre_del_volumen]
2. Uso mediante C.L.I. de forma manual;
26
podman run ....... -v [PATH_del_HOST]:[PATH_del_Contenedor] ....
ó
podman run ...... -v [volume]:[PATH_del_Contenedor] ....
PODMAN Tiene soporte tanto para SELinux como para AppArmor. Dependiendo la distribución Linux
se debe utilizar uno u otro (sistemas basados en Fedora o SUSE; Selinux, sistemas basados en Debian o Arch;
AppArmor).
• AppArmor
Se debe ejecutar la siguiente instrucción:
cat << EOF >> /etc/apparmor.d/local/usr.sbin.dnsmasq
owner /run/user/[0-9]*/containers/cni/dnsname/*/dnsmasq.conf r,
owner /run/user/[0-9]*/containers/cni/dnsname/*/addnhosts r,
owner /run/user/[0-9]*/containers/cni/dnsname/*/pidfile rw,
EOF
Con la finalización del anterior comando se evitarán problemas al poner las redes en modo ‘bridge’ con los
contenedores al usar ‘dnsmasq’. Luego se necesita recargar el perfil de AppArmor
apparmor_parser -R /etc/apparmor.d/usr.sbin.dnsmasq && apparmor_parser /etc/apparmor.d/usr.sbin.dnsmasq
• SELinux
Se debe ejecutar la siguiente instrucción compuesta:
yum install -y udica && yum module install -y container-tools && podman run --env container=podman -v /
Con la finalización del anterior comando se tendrá la capacidad de utilización del contexto del usuario para
poder ejecutar contenedores con soporte de SELinux dentro del espacio no root:
podman run --security-opt label=type:my_container.process ...
Registros
Los registros son los lugares desde donde se descargarán las imágenes que luego darán lugar a los contenedores.
Como tal, hay varias formas de crear un registro remoto que alamacene las imágenes.
Debido a que el proceso cambia entre las herramientas que tengan tal disponiblidad, en la presente sección se
detallarán los lineamientos de seguridad que deben cumplirse en todos los escenarios (independientemente de
la plataforma ó aplicación utilizada). Por último se ejemplificará en unas pocas herramientas seleccionadas.
1. Autenticación;
Todos los engine (docker, containerd, cri-o, etc) y librerias autónomas (libpod – podman) permiten tanto la
utilización de repositorios públicos (como dockerhub; https://hub.docker.com/ ) como privados.
Todos los repositorios que utilice la compañía con imágenes propias, deben ser configurados como privados,
a fin de que desde el exterior no se pueda obtener información sobre los contenedores utilizados (y por ende,
la detección no autorizada de vulnerabilidades) y/o hurtar propiedad intelectual de la organización.
27
Así mismo, se recomienda la utilización de un usuario separado por ambiente (Producción, Desarrollo, QA),
a fin de que los despliegues y contenedores especificados en el repositorio interno al desarrollo y pruebas no
puedan llegar a los ambientes de producción sin la respectiva autorización previa.
2. Acceso al repositorio;
Todos los repositorios privados de contenedores de la organización deben ser accedidos mediante un canal
de comunición TLS (en su versión, mínima, 1.2). Así mismo deben ser configuradas las medidas de seguri-
dad necesarias para que solamente los entornos virtuales (en nube o ‘on-premise’) que van a utilizar tales
contenedores puedan acceder con un rango de direcciones IP previamente aprobadas.
3. Almacenamiento interno de las imágenes;
El despliegue y puesta en configuración de un registro se puede realizar de varias formas. En todas ellas
las imágenes a utilizar se guardan en un directorio (determinado por la aplicación con la que se creó ese
registro).
Generalmente tal puesta en marcha es desplegada con un contenedor de fondo ya pre configurado para
tal ( usualmente; https://github.com/distribution/distribution ) por lo que el lugar de almacenamiento
a veces puede ser personalizado (si se utiliza la versión compatible con el estándar OCI, el lugar es;
“/var/lib/registry”).
En todos los casos, tal lugar de guardado en el contenedor debe ser un volumen que apunte hacia un espacio
de almacenamiento dentro del servidor en donde;
• Se tenga estricto control sobre que imágenes (con sus versiones, TAGs, modificaciones, metadatos, etc)
son puestas a disposición de quien tiene acceso a ese registro.
• Se garantice el uso de un sistema de archivos que, en conjunto con una configuración apropiada de
disponibilidad de operaciones I/O, pueda proveer de alta concurrencia sobre un mismo archivo así como
la auto reparación de tal si llega a ocurrir un imprevisto. Dicho lo anterior, se recomienda el sistema
de archivos BTRFS (si se utiliza GNU/Linux) con las siguientes opciones;
relatime,ssd,space_cache=v2
4. Proceso de copia de seguridad de las imágenes;
En concordancia con el punto anterior (punto 2 de la sección ‘Registros’), se debe realizar una programación
de copias de seguridad automáticas que permita la disponibilidad continua e ininterrumpida de cada una
de las imágenes puestas en disposición de la organización, incluidas todas sus versiones/TAGs (en caso de
ser consumido mucho espacio al ser almacenadas todas, se recomienda la verificación de la criticidad de la
imagen y su posterior análisis sobre cuantas versiones no actualizadas mantener por temas de compatibilidad-
seguridad disponibilitaria).
Tal y como se mencionó al inicio de la presente sección, se proveen las documentaciones respectivas de
herramientas específicamente seleccionadas sobre el proceso de; creación, eliminación, personalización y
segurización de registros auto hospedados y privados. En caso de necesitarse documentación y/o acom-
pañamiento de una herramienta adicional se recomienda la puesta en contacto con el equipo de seguridad
de la organización para su análisis.
• Contenedor de Docker – Podman – CRIO puro;
1. Creación de un registro;
https://docs.docker.com/registry/deploying/#run-a-local-registry
2. Configuración y personalización del registro;
https://docs.docker.com/registry/configuration/
3. Restricción mediante autenticación del acceso al repositorio;
https://docs.docker.com/registry/deploying/#restricting-access
28
4. Adición de certificado TLS al repositorio;
https://docs.docker.com/registry/deploying/#support-for-lets-encrypt
https://docs.docker.com/registry/deploying/#get-a-certificate
• Gitlab container registry
1. Creación de un registro;
https://docs.gitlab.com/ee/user/packages/container_registry/#view-the-container-registry
https://docs.gitlab.com/ee/user/packages/container_registry/#use-images-from-the-container-registry
2. Escaneo estático de los contenedores;
https://docs.gitlab.com/ee/user/application_security/container_scanning/
3. Restricción mediante autenticación del acceso al repositorio;
https://docs.gitlab.com/ee/user/packages/container_registry/#authenticate-with-the-container-registry
https://docs.gitlab.com/ee/user/packages/container_registry/#authenticate-by-using-gitlab-cicd
4. Adición de certificado TLS al repositorio;
https://docs.gitlab.com/omnibus/settings/ssl.html
Vulnerabilidades
El escaneo de vulnerabilidades de un contenedor consiste en;
• Escaneo de la imagen base utilizada para la creación del contenedor en base al archivo “Dockerfile”.
Esto asegura que la imagen no tiene una vulnerabilidad así como software malicioso de fondo. Esto
incluye;
1. Librerías compartidas (archivos de extensión “.so”).
2. Archivos de configuración.
3. Librerías estándar como; glibc, musl, etc.
4. Escaneo del contenedor en funcionamiento, buscando los programas instalados así como en ejecución
dentro del primero para asegurarse que no se están ejecutando programas desactualizados.
5. Escaneo de las configuraciones del contenedor tanto en funcionamiento como en parada para así detectar
fallas comunes como; utilización de puertos privilegiados abiertos en paralelo en el servidor ‘host’ como
su par en el contenedor que den problemas una vez desplegados (como un puerto 22 del contenedor en
el mismo del host) o la utilización de la red del host para el contenedor.
Por esto mismo, se encuentran muchas posibilidades (de pago, gratuitas, freeware, open source, entre otras)
y opciones para poder realizar tales operaciones. A continuación se listan opciones, queda a disposición de
los administradores evaluar tales y elegir las mismas o mejores opciones;
• Análisis estático de archivos (librerías, binarios y datos en general);
– ClamAV; https://www.clamav.net/
ClamAV con una serie de modificaciones al daemon se puede habilitar la función de análisis de los archivos
que se abren para la lectura como ejecución. Así mismo al no funcionar a nivel del núcleo del sistema
operativo, puede funcionar dentro del contenedor.
• Análisis de las imágenes en la nube;
– Google Cloud Platform – Container Scanning (“Artifact Registry” y “Container Registry”);
https://cloud.google.com/container-analysis/docs/container-scanning-overview
• Google Cloud Platform – Container Analysis (“Artifact Registry” y “Container Registry”);
29
https://cloud.google.com/container-analysis/docs/container-analysis
• Amazon Web Services – ECR Image Scanning;
https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html
• Microsoft Azure – Defender for Containers;
https://docs.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-introduction
Kubernetes
Kubernetes (abreviado como K8S, por las ocho letras que se encuentran entre la ‘K’ y la ‘S’) es una plataforma
de administración para contenedores funcionando dentro de un clúster, desarrollado originalmente por Google
en el 2014 y donado en su integridad a la ‘Cloud Native computing Foundation’ (parte de la ‘Linux Funda-
tion’).
Uso de kubernetes
Según el reporte de 2021 por parte de Canonical este es el uso actual y operacional de Kubernetes;
30
31
32
33
Para el reporte de 2022 por parte de Canonical se presenta el mismo en un documento de 34 páginas,
disponible en;
• https://assets.ubuntu.com/v1/ee0365d8-Kubernetes+cloud+native+operations+report+2022_10.05.22.pdf
Nodos
En un clúster de Kubernetes auto contenido (K8S, Kubernetes Standalone, Kubernetes The Hard Way, entre
otros ) los nodos del mismo deben ser añadidos o eliminados de forma manual. En un clúster con un nodo
maestro/master administrado por la propia plataforma de terceros (AWS con EKS, Azure con AKS y GCP
con GKE) los nodos se basan en una imagen para ser desplegados con una configuración predeterminada.
En ambos casos, pero principalmente en la nube, la seguridad del nodo puede determinar en gran medida la
seguridad operacional del propio clúster. Por lo que se debe asegurar que siempre los nodos poseen los últimos
parches de seguridad tanto a nivel del sistema operativo como del runtime que controla los contenedores.
34
Se recomienda actualizar a una versión de Kubernetes; n – 1, siendo “n” la última versión estable;
eksctl upgrade nodegroup \
--name=[nombre_del_grupo_de_nodos] \
--cluster=[nombre_del_cluster] \
--kubernetes-version=[versión_estable]
En el escenario de que se maneje un nodo de grupos administrado por uno mismo, el proceso es más complejo;
- https://docs.aws.amazon.com/eks/latest/userguide/update-stack.html
En el caso de que se necesite utilizar una versión modificada de “Amazon Linux”, al tener que ser especificada
la versión de Kubernetes sobre la que se va a utilizar - si se actualiza se volverá a la versión por defecto
-, se debe seguir el siguiente procedimiento especificado oficialmente por los laboratorios de Amazon Web
Services;
• https://docs.aws.amazon.com/AL2/latest/relnotes/relnotes-al2.html
Microsoft Azure
En Microsoft Azure, por cada creación de un clúster AKS se tienen que especificar;
• El publisher de la imagen base del sistema operativo;
– Esto también determinará la distribución del sistema operativo y su versión.
– Así mismo determinará los parches que tenga aplicados, o no, el sistema operativo y las aplicaciones
que estén dentro de un sandbox como; snapd o flatpak.
– El usuario administrador del clúster, no se deben elegir usuarios por defecto (root, admin, etc),
comunes o predecibles (administrator, administrador, kubernetes, kubeadmin, etc).
– Si se permite o no autenticación por contraseña a tal usuario administrador. Siempre debe quedar
deshabilitada; “Disabled”.
35
– No se recomienda que esté habilitada la opción “Automatic OS image upgrades”, sin embargo si
la aplicación contenerizada es lo más independientemente posible de dependencias externas, se
pueden realizar pruebas para verificar que independientemente de la versión del nodo se puedan
ejecutar apropiadamente.
Así mismo, en caso de no habilitar la actualización automática de la imagen de los sistemas operativos
utilizados, se recomienda utilizar un esquema de actualizaciones; n – 1 = u
Siendo “N” la última versión del sistema operativo y “U” la versión del sistema operativo anterior a la última
disponible pero todavía bajo soporte de actualizaciones. Esto garantizará que no se reciban actualizaciones
que pueden quebrar el funcionamiento del programa (generalmente destinadas a la última versión) mientras
aún se sigue bajo soporte con actualizaciones de seguridad. Por esto mismo, se recomienda que sean las
versiones LTS (Long Term Support ) de Ubuntu, o las estables de Debian, CentOS, Rocky Linux o RedHat
pero no de Fedora, Arch o SUSE (Debido a sus naturalezas enfocadas en traer las últimas versiones estables
de cada programa que tengan en sus repositorios, aunque el soporte e integración en el sistema no sea
completo).
Con relación al punto anterior, se provee los comandos para tal función;
• Verificar la última versión del sistema operativo disponible para un clúster AKS específico;
az aks nodepool get-upgrades \
--nodepool-name [NOMBRE_DEL_NODEPOOL] \
--cluster-name [NOMBRE_DEL_AKS] \
--resource-group [NOMBRE_DEL_GRUPO_DE_RECURSOS]
El comando retornará la última versión disponible del sistema operativo con soporte para K8S – AKS, esa
versión listada se debe constrastar con la utilizada para verificar si es la última o si hay actualizaciones
disponibles.
• Obtener información sobre un clúster AKS específico;
az aks nodepool show \
--resource-group [NOMBRE_DEL_GRUPO_DE_RECURSOS] \
--cluster-name [NOMBRE_DEL_AKS] \
--name [NOMBRE_DEL_NODEPOOL \
--query nodeImageVersion
Clúster de Kubernetes
Un clúster de Kubernetes, además de estar formado por hasta 3 servidores ‘Master’ y ‘N’ servidores de tipo
‘Worker’, posee los siguientes componentes internos ( o dependencias ) dentro de cada servidor para poder
prestar sus funciones de forma apropiada.
Cada uno de los siguientes objetos corren como un servicio (‘daemon’ en los sistemas de tipo Unix) dentro
del sistema operativo donde se desarrollan;
• kube-apiserver
Se ejecuta dentro del servidor ‘Master’. Es el encargado de recibir, previa autenticación y autorización del
usuario, y validar las solicitudes para obtener datos o realizar cambios en el servidor ‘Master’ que luego se
verán reflejados en el resto del clúster (como un ‘Deployment’ por ejemplo).
Así mismo, no solamente es el frontend de cara a un usuario/a físico, si no también de cara al resto de
componentes del propio clúster.
• kube-controller-manager
Se ejecuta dentro del servidor ‘Master’. Se ejecuta así mismo en un loop constante, verificando constantemente
( mediante una interacción con el kube-apiserver y con kubelet ) el estado de todo el clúster, de cada POD
desplegado y de las configuraciones existentes, y realizando cambios según pasen determinados eventos o
36
situaciones ( como por ejemplo; que un contenedor se detenga y se tenga que volver a crear/ejecutar con
configuraciones específicas).
• kube-replication-controller
Parte del ‘kube-controller-manager’, suele estar integrado como función adicional dentro del primero, por lo
que el binario es el mismo. Tiene las funciones de controlar cuantas réplicas ( o ‘copias’ ) de un contenedor
o POD hay ejecutándose.
Cuando se está configurado para tener más de una réplica en un contenedor o POD, si existe más de un
nodo ‘Worker’ ( el cual es una buena práctica de seguridad ) cada réplica procede a ejecutarse en un nodo
‘Worker’ diferente, a fin de garantizar que al menos uno de ellos siempre va a estar en funcionamiento y ( si
no ocurren cambios sustanciales para todo el clúster ) accesible.
• kube-endpoint-controller
Parte del ‘kube-controller-manager’, suele estar integrado como función adicional dentro del primero, por lo
que el binario es el mismo. Tiene las funciones de controlar los ‘endponits’
• kube-namespace-controller
Parte del ‘kube-controller-manager’, suele estar integrado como función adicional dentro del primero, por lo
que el binario es el mismo. Es el encargado de dividir a los despliegues en ‘namespaces’ específicos según la
configuración especificada en tal.
Así mismo, según se configure en un despliegue, esto determinará la capacidad del usuario o aplicación de
poder interactuar con el ‘kube-apiserver’ y de consultar datos o de realizar cambios en el clúster.
• kube-serviceaccounts-controller
Parte del ‘kube-controller-manager’, suele estar integrado como función adicional dentro del primero, por lo
que el binario es el mismo. Es el encargado de gestionar que un determinado servicio esté disponible y sea
redireccionado hacia el ‘namespace’ apropiado.
Trabaja en conjunto con ‘kube-proxy’.
• kube-proxy
Se ejecuta dentro del servidor ‘Master’. Es el encargado de recibir las solicitudes de servicios que llegan al
nodo ‘Master’ y redirigir todo eso hacia el contenedor (que no POD) correspondiente, trabajando en conjunto
con ‘kubelet’ para que toda la información saliente del mismo sea dirigido de nuevo hacia el ‘Master’.
• kube-scheduler
Se ejecuta dentro del servidor ‘Master’. Es el encargado de, mediante complejos algoritmos matemáticos,
analizar cada uno de los nodos del clúster y decidir cual de todos ellos (siempre que sean nodos ‘Worker’) es
el más apropiado para correr un contenedor.
Así mismo tiene cierta capacidad de personalización de forma directa, de forma que se le puede indicar si
es que se requiere que un determinado POD (que no un contenedor) debe ser ejecutado en un determinado
nodo o no. Esto último no se recomienda, ya que puede contemplar un problema de seguridad.
• etcd
Se ejecuta dentro del servidor ‘Master’. Es una base de datos de tipo [llave] = [valor], originalmente creado
para el extinto sistema operativo CoreOS (que no Fedora CoreOS, su posterior adaptación) - una distribución
especializada en contenedores - y luego liberado como un componente aparte, que soporta cosas como;
fidelidad ante fallos en red, capacidad de expiración de una llave, paralelismo, uso de un certificado SSL/TLS,
entre otros.
• kubelet
Se ejecuta tanto en el servidor ‘Master’ como en el servidor ‘Worker’. Se ejecuta como un agente que
interactuá con el kube-apiserver, registrando el nodo y su información dentro del clúster. A su vez, en su
37
interacción con el kube-apiserver del ‘Master’, se encarga de obtener la información del POD que el mismo
debe ejecutar ( decidido por el kube-scheduler ), usando un protocolo (de información, no de proceso) llamado
‘PodSpec’ (un archivo YAML o JSON con toda la información de ese POD a ejecutar/crear, un despliegue
en pocas palabras).
• Container Engine
Un contenedor no puede ejecutarse de forma arbitraria y funcionar de forma apropiada con su seguridad e
interconexión con el Kubernetes Master. Un contenedor, y por ende los nodos ‘Master’ y ‘Worker’, necesitan
un motor en el sistema operativo.
Ese motor (ó ‘engine’ en inglés) puede ser alguno de las siguientes opciones, todas deben tener un ‘socket’ de
tipo Unix funcionando para que el propio kubelet pueda crear y administrar correctamente tales contenedores.
Generalmente tales ‘sockets’ se encuentran en; “/var/run/[programa].sock”
– Docker / Containerd
Docker es un set de herramientas a modo de front-end para containerd. Tiene su ‘socket’ en;
“/var/run/docker.sock”.
– PODMAN
Podman es un motor de contenedores que posee una característica intrínseca en su funcionamiento; no
necesita de permisos raíz para funcionar, ni tampoco necesita un ‘daemon’ para funcionar correctamente.
Podman por defecto no posee un ‘socket’, por lo que se debe crear uno con systemd si se desea usar.
– CRI-O
CRI-O es un motor de contenedores diseñado específicamente para funcionar con Kubernetes. Tiene su
‘socket’ en “/var/run/crio/crio.sock”.
A continuación se presenta a modo ilustrativo, todo lo anteriormente mencionado en una infografía;
Kubernetes.io copyright
Vulnerabilidades
El equipo de seguridad debe revisar periódicamente las vulnerabilidades reportadas para asegurarse que la
organización tiene los últimos parches de seguridad;
38
• ‘CVEs’;
– https://www.cvedetails.com/vulnerability-list.php?vendor_id=15867&product_id=34016&version_id=&page=1
• Registro de cambios de Kubernetes;
– https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG
Así mismo el equipo de seguridad debe revisar que las vulnerabilidades existentes, en caso de no existir un
parche oficial para tal, puedan estar mitigadas por la infraestructura en donde está desplegado el clúster. Se
debe tener en consideración que, de manera oficial, las versiones estables de Kubernetes tienen un soporte
de 14 (catorce) meses más 2 (dos) meses de mantenimiento en los cuales se siguen reciviendo actualizaciones
de seguridad y actualizaciones críticas;
Runtime
Histórico Kubernetes a lo largo de su historia a utilizado varios ‘runtimes’ de contenedores así como
conjuntos de herramientas para los mismos; docker como ‘toolkit’ de containerd (en conjunto con dockershim),
podman, etc.
El ‘runtime’ es el encargado de crear los aislamientos respectivos y de proveer las interfaces necesarias para
el intercambio de información y la administración de los contenedores.
Cambios Desde 2021 (versión 1.21 para GCE en Windows, versión 1.24 para todos los nodos GNU/Linux) ,
Kubernetes (y todos los ‘forks’ derivados del K8S puro tales; OpenShift, Rancher, entre otros) a desestimado
el soporte de docker y podman para los nodos ‘master’ y ‘worker’ en favor de cri-o.
Este cambio se debe a que cri-o tiene soporte para el estándar OCI (Open Container Iniative. El estándar que
define como deben funcionar los runtime de cada contenedor para que pueda haber una interpolaridad entre
todas las opciones así como un aseguramiento de la información transmitida, retornada y las modificaciones
de los canales de comunicación) mientras que dockershim no.
Por lo tanto, por seguridad del ‘daemon’ del runtime se debe utilizar cri-o. Tanto cri-o como podman
no utilizan capas de abstracción (como sí utiliza docker para containerd) si no que utilizan las librerías;
libimage, libstorage (cri-o) y libpod-podman (podman), por lo que la seguridad se ve incrementada al reducir
notablemente la superficie de ataque.
CRI-O Hardening
Cri-o da soporte para la utilización de los M.A.C (Mandatory Access Control) SELinux y AppArmor, anteri-
ormente con dockershim no era posible utilizar la seguridad adicional que proporciona el primero. SELinux
y AppArmor proveen la utilización de etiquetas dentro de los archivos ejecutables (SELinux) y según el path
de ejecución (AppArmor), para el control de los accesos, llamadas y funcionalidades del kernel Linux.
Para habilitar el soporte de SELinux y AppArmor el lector/a puede revisar la documentación (sección; “1.3
Configuring CRI-O”):
• https://access.redhat.com/documentation/en-us/openshift_container_platform/3.11/html/cri-
o_runtime/use-crio-engine
Como tal el archivo de configuración “/etc/crio/crio.conf” debe poseer esta sección habilitada:
[crio.runtime]
….
selinux = true
apparmor_profile = "crio-default"
CRI-O Registros Los registros son los repositorios remotos desde donde se descargarán las imágenes de
los contenedores a utilizar. Por ende, un registro de contenedores no confiable puede darle la posibilidad a
un atacante de infectar los mismos y así tener los recursos de la compañía comprometidos a nivel atómico.
39
El archivo de configuración de cri-o; “/etc/crio/crio.conf” contiene la especificación en TOML para los
registros;
[crio.image]
…
registries = [
“docker.io”
]
Como se puede ver en el ejemplo superior, cri-o está configurado para descargar las imágenes de los contene-
dores desde “docker.io” (backend del famoso frontend; https://hub.docker.com/ ).
La organización debe asegurarse de que se están utilizando repositorios autorizados, esto es debido a que en
repositorios públicos (como docker.io) no se conoce quien fue el que contruyó la imagen.
Puertos La presente sección describe y hace una discriminación de cuales son los puertos que se necesitan
para la comunicación en el clúster y desde/hacia el mismo.
Puertos internos Los siguientes puertos deben ser solamente permitidos dentro de los servidores
Linux/Windows (este último mediante capa de virtualización) del clúster y no hacia cualquier dirección IP
o rango CIDR fuera del rango de este último (el clúster).
• https://kubernetes.io/docs/reference/ports-and-protocols/
Nota: Los ‘NodePort Services’ son los puertos utilizados por Kubernetes para los despliegues de aplicaciones
que necesitan ser expuestos.
Puertos externos Los siguientes puertos son los necesarios para que un cliente (tanto desde fuera como
desde dentro del clúster) mediante el comando ‘kubectl’ (o mediante el consumo de la API de Kubernetes)
pueda obtener información o realizar cambios en el clúster.
Así mismo ese puerto debe estar detrás de una solución de firewall (tanto puro como web) que se encuentre
en modo ‘whitelist’ (permitiendo solamente rangos específicos de direcciones y bloquee el resto) como; F5,
IpFire, Forti, OpenWRT, entre otros.
Despliegues - Deployments
En Kubernetes los despliegues son las configuraciones especificadas por el administrador del clúster (véase;
quien tiene acceso al archivo “/etc/kubernetes/admin.conf”) para crear contenedores con configuraciones
específicas (valga la redundancia) entre las que se encuentran;
• Puertos abiertos en el servidor ‘worker’ y a que puertos del contenedor son re-direccionados.
40
• Que imagen y versión utilizar para el despliegue.
• Que path del servidor ‘worker’ utilizar como almacenamiento persistente para el contenedor.
• Que volumen o disco de AWS, Azure, GCP o cualquier nube utilizar como almacenamiento persistente
para el contenedor.
• Que configuraciones de red utilizar para controlar el flujo de información.
• Entre otros.
Una vez que la aplicación, en forma de contenedores, fue desplegada pasa a conformar una unidad mínima
dentro del clúster de Kubernetes llamado POD.
Tales despliegues deben ser creados y leídos (para ser aplicados en el clúster) desde archivos con sintaxis
YAML en sistemas GNU/Linux (si se elige administrar el clúster desde ‘kubectl’) o desde entradas estándar
en sistemas WebGUI (si se elige administrar el clúster desde Rancher, OpenShift u otras interfaces web de
usuario).
Por lo tanto, se debe asegurarse las siguientes características cuando se va a realizar un despliegue;
1. La sintaxis del archivo YAML debe ser el correcto (espacios en vez de tabulaciones, sin espacios al final
de la línea, un espacio luego de cada guion, codificación UTF-8, evitar tildes, entre otros). Para esto
se pueden utilizar tanto herramientas online (no recomendado debido a la posibilidad de filtración de
información) como herramientas locales (recomendado). A tal fin, se provee un listado de programas
y ‘addons’ a modo de ejemplo, los administradores serán los responsables de definir el mejor para sus
necesidades y evaluar que tales adiciones a los IDE cumplen las directivas de la empresa:
1. Visual Studio / VSCodium
https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
2. Eclipse
https://marketplace.eclipse.org/content/yedit
https://marketplace.eclipse.org/content/yaml-editor
3. Jetbrains PyCharm
https://plugins.jetbrains.com/search?products=pycharm_ce&search=yaml
2. La sección “image” de la especificación (spec → template → spec → containers → image) debe contener
las especificaciones de la imagen aprobada para su uso interno por la organización.
3. Si la aplicación que se va a desplegar pertenece al ámbito de producción y es crítica para los servicios
de la organización, debe poseer una cantidad de réplicas en la especificación (spec → replicas) iguales
o superior a la cantidad de nodos ‘worker’. Esto es para asegurar que al menos siempre va a haber una
réplica del POD desplegado en funcionamiento.
4. Todos los archivos de configuración de despliegues deben poseer las etiquetas ( metadata → labels)
correspondientes a la correcta identificación del futuro POD dentro del clúster. Se recomiendan las
siguientes:
1. ‘name’
2. ‘environment’
3. ‘team’
4. ‘clustername’
5. ‘datadisk’
De esta manera tanto los equipos administradores del clúster, como los equipos de desarrollo así como el
Kubernetes ‘master’ (cuando está integrado en el entorno de la nube como AKS, EKS y GKE. Así como en
los escenarios de servicios ) pueden identificar, mediante esas etiquetas, correctamente los PODs y operar en
consecuencia.
La etiqueta ‘app’ debe coincidir tanto en; metadata →labels, como en; spec →matchLabels.
41
5. Los despliegues no deben exponer los puertos directamente en el servidor ‘worker’ si no que deben ser
configurados como Servicios (‘Services’ en inglés) para que puedan ser consumidos por un usuario/a
y/o programa a través del servidor ‘master’.
6. Los despliegues no deben tener configurada la afinidad de nodo, a pesar de que permite asegurarse que
un POD específico va a estar siempre ejecución, esto elimina la característica de alta disponibilidad en
el clúster. Con lo que si el servidor ‘worker’ tiene algún problema del estilo;
• Espacio lleno.
• Fallo del kernel.
• Fallo del sysinit.
• Fallo de red.
• Fallo del runtime.
• Entre otros.
No podrá ejecutar el POD y la aplicación no estará disponible. Por lo que la configuración de afinidad no
debe ser utilizada en los despliegues.
7. Los despliegues no deben poseer especificaciones locales de dispositivos de almacenamiento. Véase; no
se deben especificar guardar en un directorio local del servidor ‘worker’ archivos de forma permanente.
Esto es así por que cuando el POD cambie de servidor ‘worker’ los archivos no se replicarán, perdiendo
así información.
Todos los PODs deben poder leer y guardar información desde un lugar agnóstico y accesible por todos los
nodos del clúster y en el mismo directorio en todos los lugares. A fin de que sin importar el cambio de nodo
puedan seguir operando con normalidad leyendo y escribiendo en el mismo directorio.
Así mismo, se recomienda que tal lugar de almacenamiento esté diseñado y preparado para garantizar la
integridad y disponibilidad de la información desde todos los nodos del clúster con el mayor tiempo y SLA
(Service Level Agreement) posible. Por ejemplo;
1. Debe poseer un sistema de archivos que tenga la capacidad de ser leído y escrito en red por todos los
2. Debe poseer un sistema de archivos que tenga la capacidad de auto repararse si ocurren colisiones de
3. Debe poseer un sistema de archivos que permita la generación de copias de seguridad (‘snapshots’ en i
8. Se deben dividir los despliegues entre los de “sólo lectura” y los de “lectura-escritura” esto para garan-
tizar que ante la vulneración de alguno, las capacidades adquiridas por el atacante puedan estar lo
más mitigadas posibles. Así mismo esto garantiza mayor modularidad al separar por capacidades y
funcionalidades las APIs y servicios.
9. Los despliegues/PODs pueden estar formados por más de un contenedor. Sin embargo, tales objetos
no deben mezclar funcionalidades. Se debe seguir una filosofía de minimalismo operacional;
Un POD/despliegue debe cumplir 1 (una) función. Esto a fin de garantizar la correcta identificación de
problemas según la operación a realizar así como la capacidad de actualización de sus partes sin la afección
al conjunto.
10. Los despliegues/PODs no deben comunicarse entre sí por puertos, si no a través de sockets en puntos
bien definidos. Generalmente esto se traduce en la ubicación de los mismos en el directorio; “/run”
(tenga en consideración que el directorio “/var/run” es un enlace simbólico a “/run” por cuestiones de
compatibilidad heredada).
42
Servicios
En Kubernetes los servicios son despliegues configurados para ser accedidos desde fuera del clúster, general-
mente esto incluye también fuera de la red en donde se encuentra desplegado este último (el clúster).
Todo POD que debe ser accedido desde fuera del clúster debe ser configurado como un servicio. Los puntos
principales de esto son que;
• El servicio es expuesto a través del servidor ‘master’ y no a través del puerto directo del ‘worker’.
Lo que evita que el POD al cambiar de nodo ‘worker’ se vuelva inaccesible, ya que es el ‘master’ el
encargado de estar siguiendo la ejecución del mismo y de redirigir todo el tráfico entrante al POD en
ejecución correspondiente.
Esto evita que; 1. Se expongan los puertos de los servidores de forna innecesaria. 2. Puedan existir cortes
de servicio. 3. Pueda existir pérdidas de información al estar centralizadas en servidores que pueden caer
o ser eliminados, ya que es finalidad de Kubernetes que tanto los PODs como sus servidores ‘worker’ sean
redundantes. 4. Se exponga infraestructura interna de la organización hacia un agente no autorizado. 5. Un
atacante o agente no autorizado realice pruebas no aprobadas pertinentemente por la organización y pueda
comprometer la integridad, confidencialidad y/o disponibilidad de los recursos de la organización al exponer
en alcance servidores indebidamente. 6. El tráfico interno se mantiene así; interno. De tal manera que la
información entre el nodo ‘worker’ y el nodo ‘master’ no sale hacia fuera. Solamente es el nodo ‘master’
el que es expuesto contra el cliente (o las aplicaciones clientes que hacen uso de las APIs), por lo que la
superficie de ataque es reducida a su mínima expresión al ser solamente expuesto un solo servidor. 7. Se
usen URIs para designar un recurso, en forma de POD, de forma permanente. Por lo que se estaría hablando
de una URL de alto nivel, a nivel de red, y de una URN a nivel de recurso (desde fuera del clúster) en vez de
la divulgación de información que conllevaría la utilización de la dirección IP interna del servidor expuesto.
43
El uso de los servicios permite la configuración de que puertos (‘X’) deben ser habilitados en el ‘master’ que
luego, ese tráfico de tal puerto (‘X’) será redirigido al puerto (diferente o no) destino (‘Y’) del POD que
ejecuta tal aplicación. Lo que permite redirigir puertos reservados para procesos privilegiados o específicos
(como 80, 22, entre otros) que en sistemas Linux son los puertos menores o iguales a 1024, desde el ‘master’
hacia puertos no privilegiados (y que por ende no requieren de una ejecución privilegiada) en los servidores
‘worker’.
• La centralización del tráfico de red en un punto fronterizo (el nodo ‘master’) permite un mejor control de
tal y de aplicación de reglas, como firewalls, entre otros.
• El uso de servicios permite el cambio de configuración en los PODs (con configuraciones de alta disponi-
bilidad en funcionamiento) sin tener que forzar a todos los nodos ‘worker’ a frenar la aplicación en fun-
cionamiento.
Control de acceso para despliegues Este sistema se basa en la API de autorización de Kubernetes
para designar a que recursos puede acceder un usuario desde cierta parte de la red. Esto incluye; puntos de
montaje, afinidad de recursos (CPU y RAM), puertos, espacios de nombres (namespaces), entre otros.
La API respectiva de Kubernetes es; “rbac.authorization.k8s.io”. Esta API dependiendo del “fork” utilizado
como frontend está definido como un namespace o un rol. Para poder habilitar tal característica (debido a
que existen varios módulos de autorización), al momento de iniciarlizar el API server (tanto en los servidores
worker como master), se debe utilizar el siguiente argumento;
--authorization-mode=RBAC
Por lo que se debe auditar los despliegues (archivos con extensión; yaml o mediante la configuración WebGUI)
para verificar que rol o namespace está utilizando. Esto va especificado dentro de las sub categorías; “.rules”
(con su variación en formato de arreglo; “.rules[]”), en tales se especifican que políticas va a tomar y con
qué permisos. Generalmente la especificación de objetivo son los “pods” (pueden ser también recursos no
operacionales como; “secrets”, etc), por lo que se debe constatar que sigue la filosofía del; menor privilegio
en la especificación “verbs” (que determina que puede hacer ese recurso operacional o que permisos, roles se
tiene sobre el recurso objetivo).
Un ejemplo sería;
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
Con el ejemplo de arriba, sea crea un rol, que a su vez crea una política llamada “pod-reader” dentro del
namespace “default” (lo que a su vez indica a que despliegues afectará), en donde no afecta a un grupo de
APIs específicas (apiGroups) si no a los pods (resources) permitiéndoles las acciones dentro del clúster de
lectura completas (verbs).
Otro ejemplo para secretos;
apiVersion: rbac.authorization.k8s.io/v1
44
kind: ClusterRole
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
#
# at the HTTP level, the name of the resource for accessing Secret
# objects is "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
En el ejemplo superior se suprimen las etiquetas “namespace” ( ya que los roles de clúster no afectan a los
namespaces si no a todo el conjunto completo) y “objects” (debido a que a nivel de red, para el nodo master
de Kubernetes los secretos son objetos de autoría/propiedad compartida – algo así como el shared ownership
de Rust pero esto heredado del lenguaje Go).
Control de acceso para usuarios, grupos y cuentas de servicios Por defecto el administrador del
cluster tiene privilegios absolutos pero no así los usuarios.
Para crear usuarios, grupos ó cuentas de servicio dentro del clúster, se deben;
1. crear perfiles en minúscula (debido a que son “case sensitive”).
2. sin caracteres especiales de cualquier idioma o alfanuméricos (como; acentos, ñ, {, }, -, _, entre otros).
Esto es debido a que como se utiliza el formato YAML (https://en.wikipedia.org/wiki/YAML), se toma
como parte del dato los caracteres simples del alfabeto latino, pudiendo así dar problemas en el engine
la utilización de caracteres no pertenecientes a tal alfabeto humano.
3. sin espacios. Esto es debido a que como forma parte de sistemas de tipo Unix, el espacio significa un
dato aparte, pudiendo así generar problemas.
Así mismo se debe tener especial control de a quienes se les da permisos para poder editar y/o crear de-
spliegues (en formato de pods) ya que existen diversas formas para poder realizar un escalamiento de privi-
legios;
1. Modificación de que usuarios, grupos o cuentas de servicios están asociados a un namespace específico.
2. Modificación de los puntos de montaje, permitiendo así que puntos de montaje del root del sistema
worker sea accesible desde el contenedor.
3. Modificación de la definición de que imagen utilizar en el despliegue, pudiendo así vulnerar a nivel
atómico los servicios que proveen la organización.
Los usuarios, grupos de tales y cuentas de servicios (que son; “case-sensitive”) deben ser agrupados en función
del namespace al cual requieran acceder. Esto es debido a que los permisos son garantizados a todos los
recursos que están alcanzados por la política dentro del namespace definido.
Por ejemplo;
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: [ROLE_NAME]
namespace: [NAMESPACE_TARGET]
subjects:
- kind: [User/Grupo]
name: [Usuario_o_grupo]
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: [Role/ClusterRole]
45
name: [Nombre_del_rol_creado]
apiGroup: rbac.authorization.k8s.io
Si se quiere acceder a leer un secreto, el tipo de rol debe ser “ClusterRole”.
Kube-apiserver
Kube-apiserver es el componente responsable, en el lado del servidor master, de recibir las solicitudes (de
información, de cambio o de traspaso de información) y de procesarlas de forma respectiva, actualizando la
base de datos propia del clúster; etcd (no es un componente exclusivo de un clúster de Kubernetes pero sí
se usa dentro de un contenedor).
Como tal, al ser un componente principalmente expuesto hacia la red, debe ser correctamente endurecido.
A continuación se provee una serie de argumentos con los que se deben iniciar el servicio en el nodo master
para mejorar tanto las restricciones como el aislamiento.
Argumento Propósito
–watch-cache true Habilita el sistema de caché para las solicitudes del
apiserver.
–tls-min-version VersionTLS13 Habilita solamente TLS en su versión 1.3 a fin de
evitar los ataques que son posibles en la versiones
iguales y/o menores a TLS 1.2
–tls-cipher-suites TLS_AES_128_GCM_SHA256, Una lista de ciphers permitidos que, a día de
TLS_AES_256_GCM_SHA384, creación del presente documento, se consideran
TLS_CHACHA20_POLY1305_SHA256, seguros para la utilización dentro del protocolo de
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, seguridad y cifrado TLS.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_AES_256_GCM_SHA384
–tls-cert-file [ARCHIVO1] –tls-private-key-file Certificado x509 utilizado para el cifrado HTTPS
[ARCHIVO1] con el exterior e interior. El primer parámetro
corresponde al certificado perse y el segundo a la
llave utilizada en tal, generalmente suelen coincidir
en el mismo archivo.
–strict-transport-security-directives Establece las directivas que indican al cliente
‘max-age=31536000,includeSubDomains,preload’ remoto como no usar y/o procesar información
proveniente de una conexión no segura.
46
Argumento Propósito
–shutdown-send-retry-after true Indica que el API Server seguirá funcionando
mientras halla una solicitud todavía en
procesamiento en alguna parte del clúster,
rechazando con un código de estado HTTP 429 a
las nuevas. Esto permite evitar inconsistencias en el
etcd, como corrupción de datos y PODs en estado
de bloqueo.
–shutdown-delay-duration 20 Especifica en 20 segundos el tiempo que esperará el
API Server antes de terminar todas las conexiones
desde la indicación de apagado. Esto se refleja en el
parámetro “/readyz” del endpoint (que se seteará
en “failure”), permitiendo así a balanceadores de
carga y otras herramientas dejar de enviar tráfico.
Esto permite evitar inconsistencias en el etcd, como
corrupción de datos y PODs en estado de bloqueo.
–requestheader-username-headers [CABECERA] Cabecera solicitada para poder autenticar un
usuario, generalmente se suele usar
“X-Remote-User”.
–requestheader-group-headers strings [CABECERA] Cabecera solicitada para poder autenticar a un
grupo, generalmente se suele usar
“X-Remote-Group”.
–request-timeout 45s Especifica que el tiempo en que una solicitud
remota se mantendrá activa (antes de caer en
timeout) es de 45 segundos. Esto permite que
muchas aplicaciones de escaneo automatizadas
fallen por exceso de tiempo, pero lo mismo puede
ocurrir para aplicaciones legítimas, por lo que debe
ser analizado previamente.
–profiling false Deshabilita las capacidades de depuración si falla el
servicio, permitiendo así que no se pueda obtener
información desde fuera. La información sobre el
servicio se puede seguir obteniendo desde dentro del
servidor master mediante;
“systemctl status -l kube-apiserver”
–max-requests-inflight 400 Este valor limita el número máximo de solicitudes
–max-mutating-requests-inflight 200 no-mutables que pueden provenir del exterior. Esto
permite evitar que el clúster caiga ante una
denegación de servicio.
–external-hostname [NAME] El hostname a utilizar cuando se general URLs
externas. Tal nombre debe estar aprobado por la
organización, así como estar registrado y
monitoreado en los DNS internos.
–enable-admission-plugins XXXX Este flag especifica que plugins (además de los
incluidos en la parte “core” de Kubernetes) deben
ser habilitados. Se deben auditar, cada cierto
tiempo, que plugins están habilitados y cuales no. A
fin de verificar que características inseguras no están
siendo habilitadas en la API expuesta.
–cloud-provider [aws/azure/gcp/ovh] Este flag especifica cual es el proveedor de la nube
en donde se está ejecutando el clúster de la nube.
47
Argumento Propósito
–bind-address XXXX Este flag indica en XXXX como la dirección IP en
la que el kube-apiserver debe escuchar. Se debe
auditar que un clúster no público no esté
escuchando para todo internet, especialmente si
tiene varias interfaces de red, si no para el que le
corresponda.
–azure-container-registry-config XXXX Este flag indica en XXXXX cual es la URI del
registro de contenedores de Azure desde donde se
deben descargar los contenedores. En el caso de que
se use Azure Containers Registry se debe especificar
acá.
–authorization-mode RBAC Este flag indica el plugin de autorización para usar
en el puerto usado por kube-apiserver. Se debe
utilizar RBAC en conjunto con lo especificado en el
capítulo de “controles de accesos”. Tomar en
consideración si este documento está en BETA o no.
–audit-policy-file [PATH/archivo] Este flag especifica el archivo con las políticas de
auditoria para el clúster, lo que deriva en la forma
en que registra los eventos en el servidor master y
worker. No se debe utilizar un path relativo. Se
debe utilizar en conjunto con lo especificado en el
capítulo de “Auditoría”. Tomar en consideración si
este documento está en BETA o no.
–audit-log-path [PATH] Este flag especifica el directorio donde se van a
registrar todos los eventos, solicitudes y
características que reciba el kube-apiserver del nodo
master.
–audit-log-mode “batch” Este flag especifica que se deben mandar y registrar
eventos y demás de forma asíncrona, esto puede
aumentar un poco la carga de I/O (inputs-outputs)
en el almacenamiento (aproximadamente un 1,5%
por cada 40 aumentos de cargas de trabajo) pero
evita que usando el modo por defecto; “blocking” se
generen latencias en los servicios o se evite procesar
ciertas solicitudes.
–audit-log-batch-throttle-burst 100 Este flag especifica la cantidad máxima de
solicitudes a enviar para ser registradas. Funciona
en conjunción con el flag de arriba. Un valor de 100
ayuda a equilibrar entre carga y velocidad de
registro.
Así mismo, se provee una lista de argumentos que se deben evitar utilizar por sus características
experimentales y/o inseguras:
Argumento Propósito
–feature-gates XXXXXXX Habilita la característica experimental XXXX
–audit-log-truncate-enabled Habilita que los registros de auditoría puedan ser
truncados.
48
Argumento Propósito
–allow-privileged Habilita que se puedan ejecutar contenedores
privilegiados en cada uno de los nodos del clúster.
Esto se debe evitar a todo coste, debido
principalmente a que si algún contenedor (en su
forma de despliegue o POD) tiene una
vulnerabilidad que le permita modificar al nodo
host, entonces puede darse el escenario en que todo
el clúster pueda quedar comprometido.
Por último, en el caso de que el clúster esté segmentado en diferentes módulos (por ejemplo; que la base de
datos etcd esté en otro servidor y no en conjunto con el nodo master) se proveen los argumentos adicionales
que debe llevar kube-apiserver en el clúster:
Argumento Propósito
–etcd-servers XXXX Lista de los servidores etcd XXXX a los que se debe
conectar el kube-apiserver. En el caso de ser más de
uno, se separan por “coma”. Llevan la sintaxis;
[scheme]://[IP]:[PORT]
–etcd-prefix XXXX Si el servidor etcd está detrás de una URL
compartida, en donde a tal recurso se accede
mediante un PATH específico, se debe especificar
esto último en XXXX
–etcd-keyfile [archivo] La variable “[archivo]” es la llave TLS (en un
archivo) utilizada para segurizar la conexión entre el
master y el servidor etcd.
–etcd-certfile [archivo] La variable “[archivo]” es el certificado TLS (en un
archivo) utilizado para segurizar la conexión entre el
master y el servidor etcd.
–etcd-cafile [archivo] La variable “[archivo]” es el “caertificate authority”
TLS (en un archivo) utilizado para verificar la
conexión segura entre el master y el servidor etcd.
Políticas de terceros
Se debe evaluar si el proveedor de la nube, en caso de que corresponda, o de la plataforma IaaS ó SaaS
en donde se ejecuta provee políticas de clúster que permita tanto a los usuarios como a los responsables
máximos obtener una mejor integración y seguridad en el mismo.
En caso de que esté provista, se debe evaluar las características que provee. Se deben habilitar si provee
características de seguridad como; auditoría, análitica de registros, etc.
En el otro caso en donde no esté provista y se deban crear políticas propias, se deben crear políticas que
permitan realizar acciones según divisiones por grupos. Se debe tener en cuenta que una acción permisiva
tiene prioridad sobre la misma acción prohibitiva, por lo que ante dos políticas tendrá proridad la permisiva.
49
Debido a la gran frecuencia de actualizaciones en la nube se recomienda revisar la documentación oficial de
forma directa previa implementación;
• https://docs.microsoft.com/en-us/azure/governance/policy/concepts/policy-for-kubernetes
AWS - Amazon Web Services En AWS no existe una política propia de la plataforma como tal para
un clúster de EKS (Elastic Kubernetes Service) si no que deben ser los administradores de la plataforma
quienes definan una política de IAM que involucre al EKS. Acá por ejemplo, una política que es absolutamente
permisiva en todos los recursos de EKS:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1661994932100",
"Action": "eks:*",
"Effect": "Allow",
"Resource": "*"
}
]
}
Como se puede apreciar, la sección determinante de las acciones es “Action” con su especificación
“eks:[ACTION]”. Y el posterior efecto que permite o deniega tal acción.
Se recomienda dividir en grupos de políticas y que si una política por defecto del proveedor cumple con los
requerimientos, se utilice tal en vez de una personalizada.
Así mismo se recomienda verificar cada cierta cantidad de tiempo que no esté asignado a un usuario, o un
recurso, dos políticas que tengan “Effect” opuestos.
Recordatorio para el lector/a; Las políticas de IAM son asociadas a un usuario o entidad
externa, mientras que los roles son asociados a un recurso de la nube.
A continuación se proveen los respectivos comandos para poder hacer una solicitud de esas políticas;
1. Solicitar los datos de una política [X] como; la política perse, quienes la tienen adjuntada, etc. Tenga
en consideración que el ARN especificado debe ser el de la política específica, no solo el nombre.
aws iam get-policy --policy-arn arn:aws:iam::123456789012:policy/MySamplePolicy
2. Solicitar que políticas de IAM tiene un usuario determinado;
aws iam list-user-policies --user-name [USUARIO]
3. Solicitar que políticas están asociadas a un rol específico;
aws iam get-role-policy --role-name [NOMBRE_DEL_ROL] --policy-name [ XXX_ó_* ]
Los 3 (tres) ejemplos anteriores al ser comandos, se pueden automatizar redireccionando la salida están-
dar (“stdout”; standard output) ó la salida para errores (“stderr”; standard error) hacia archivos u otros
programas, por lo que se puede integrar tales en procedimientos de continua integración.
Un ejemplo de tal código ejecutado en un AWS Lambda con integraciones con AWS SQS y SNS se puede
encontrar en la siguiente publicación (en formato PDF) de Amazon;
• Serverless CI/CD for the Enterprise on the AWS Cloud
◦ https://aws-quickstart.s3.amazonaws.com/quickstart-trek10-serverless-enterprise-cicd/doc/server
50
GCP - Google Cloud Platform En Google Cloud Platform el esquema de auditorías y administración
de tales en un clúster de Kubernetes queda a cargo del servicio; “Anthos Configuration Manager” y su
componente; “Policy Controller” ( https://console.cloud.google.com/anthos/config_management )
Anthos de GCP se encuentra disponible también para Amazon Web Services (A.W.S.) y Microsoft Azure.
En palabras de Google;
El controlador de políticas permite la aplicación de políticas completamente programables para
tus clústeres. Estas políticas actúan como “recursos de seguridad” y evitan que los cambios en la
configuración de la API de Kubernetes no cumplan con los controles de cumplimiento, operación
o seguridad. Puedes usar este conjunto de políticas para bloquear de forma activa las solicitudes
a la API que no cumplen con los requisitos o solo para auditar la configuración de tus clústeres y
denunciar las infracciones. Policy Controller se basa en el proyecto de código abierto Agente de
política abierta de Gatekeeper y viene con una biblioteca completa de políticas prediseñadas para
controles comunes de seguridad y cumplimiento. Además de controlar de forma activa el entorno
de Kubernetes, puedes usar el Policy Controller como una forma de analizar la configuración
antes de implementarla. Esto ayuda a proporcionar comentarios valiosos durante el proceso de
cambios de configuración y garantiza que no se detecten los cambios que no cumplan con las
políticas antes de que se rechacen durante la aplicación.
Algunas de las políticas y configuraciones que se pueden aplicar desde tal servicio permite cumplir con
diversos puntos de las especificaciones de los estándares de Kubernetes y Contenedores de Base4Security
como; la delimitación de los repositorios desde donde se pueden bajar las imágenes base para los de-
spliegues/contenedores. Esto último no descarta la necesidad del diseño, análisis, realización y aplicación de
mayores políticas de seguridad dentro de un clúster específico, ya que cada uno de tales al ser difeentes en
necesidades y capacidades operacionales puede verse diferentemente afectado de forma diferente.
Se debe implementar tales políticas a fin de garantizar que los administradores puedan realizar los monitoreos
y auditorías justificantes sobre la plataforma.
En específico “Policy Controller” se basa en el proyecto de código abierto; “Gatekeeper” el cual permite
auditar un clúster de Kubernetes para detectar los PODs y usos que no cumplen con las políticas.
La modificación que realiza Google Cloud Platform permite la ejecución de las auditorías ( https://open-
policy-agent.github.io/gatekeeper/website/docs/operations/#audit ) y la definición de políticas (
https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/ y https://cloud.google.com/k
engine/docs/how-to/pod-security-policies-with-gatekeeper?hl=es-419 ) desde la interfaz web – WebGUI de la
propia plataforma ( https://console.cloud.google.com/marketplace/details/google/anthos.googleapis.com?referrer=search&or
ripsaw-306620 ).
Kubeconfig
Los archivos de configuración:
• “/etc/kubernetes/admin.conf”: la ruta por defecto en el Kubernetes master para el usuario root. El
archivo debe poseer al menos los permisos 644, se puede verificar con;
stat -c %a /etc/kubernetes/admin.conf
• “~/.kube/config”: la ruta por defecto en el nodo para usuarios no root. Se recomienda que solamente
los usuarios listados en un grupo específico, listados en “/etc/sudoers” ó “/etc/sudoers.d/[archivo]”
que posean la siguiente sintaxis puedan solamente ejecutar el comando sin pasar a ser root:
Cmnd_Alias kubectlmaster = /usr/bin/kubectl --kubeconfig /etc/kubernetes/admin.conf
%[GRUPO] ALL=(root) NOPASSWD: kubectlmaster
De esa manera los usuarios (pertenecientes al grupo [GRUPO]) solamente podrán ejecutar como root (con
sudo) el comando especificado en la primera línea.
51
Así mismo los archivos de configuración deben ser estrictamente controlados quien tiene acceso, así como
quien lo manipula. Esto es así debido en que tal archivo se encuentran;
• La dirección IP y puerto del servidor Kubernetes ‘master’ en donde debe conectarse el administrador
del clúster.
• Los ‘tokens’ utilizados para autenticarse contra el servidor ‘master’ y poder realizar cambios en el
clúster.
• Otras especificaciones administrativas.
Así mismo tales archivos, al tener formato YAML (sensible a tabulaciones, espacios y posicionamientos de
los caracteres como sensibilidad a la sintaxis del archivo), pueden ser corrompidos muy fácilmente. Por lo
que los mismos deben ser inmutables a todos los usuarios y programas:
chattr +i /etc/kubernetes/admin.conf
Referencias
Si usted desea expandir lo aprendido en el presente documento, por favor refiera a la documentación oficial
de la plataforma;
• https://etcd.io/docs
• https://kubernetes.io/docs/
• https://github.com/opencontainers
• https://www.rfc-editor.org/rfc/rfc3986
• https://docs.docker.com/engine/install/
• https://opencontainers.org/about/overview/
• https://docs.docker.com/engine/reference/builder/
• https://www.redhat.com/en/topics/containers/what-is-docker
• https://www.redhat.com/en/topics/containers/containers-vs-vms
• https://nsfocusglobal.com/technical-report-on-container-security-i/
• https://sysdig.com/blog/analysis-of-supply-chain-attacks-through-public-docker-images/
• https://containerjournal.com/features/container-security-threats-added-to-mitre-attack-framework/
• https://www.researchgate.net/publication/356491534_Threat_Modeling_and_Security_Analysis_of_Containers_A_
• https://access.redhat.com/documentation/es-es/red_hat_enterprise_linux/8/html/building_running_and_managing
the-podman-api-using-systemd-in-rootless-mode_using-the-container-tools-api
52