Docker para ciberseguridad – Parte 2: Crear imágenes. Dockerfile

por Alberto Jódar
0 comentario 11 minutos lectura

En esta entrada vamos a ir un paso más con Docker. Si en la anterior vimos qué eran los contendores y las imágenes y vimos cómo descargar una imagen de Apache de DockerHub para luego levantar un contenedor. En esta vamos a ver cómo podemos crear imágenes personalizadas con un archivo conocido como Dockerfile.

En la entrada anterior creamos un contenedor para la aplicación Apache usando la imagen que descargamos del DockerHub. Pero levantamos una aplicación plana, que no tenía nada dentro. ¿Cómo hacemos para levantar contenedores de imágenes personalizadas? Pues tendremos que construirla. Y el fichero que sirve para construir imágenes personalizadas es el que se conoce como Dockerfile. Vamos a ello.

Qué es el Dockerfile

Ya vimos que una imagen está formada por capas que no son modificables. El Dockerfile es un archivo de texto que contiene las instrucciones que dan forma a cada capa. Cada línea en el Dockerfile es una instrucción que define una capa en la imagen final. Estas capas incluyen la copia de archivos, la ejecución de instrucciones en la máquina, la creación de volúmenes, operaciones de red, creación de usuarios…

Empecemos. Creamos en la Ubuntu una carpeta y creamos un fichero de texto vacío que se llame «Dockerfile».

# Creamos y accedemos a una carpeta
$> mkdir dockerImages/apacheCustom && cd dockerImages/apacheCustom

# Creamos un fichero vacio con nombre Dockerfile
$> touch Dockerfile

# Lo editamos con el editor que queramos
$> nano Dockerfile

La sintaxis básica es añadir el código de la instrucción y a continuación los parámetros que esta instrucción acepta. Vamos a repasar las instrucciones básicas. Al final del artículo os dejaré un cheatsheet con todas las instrucciones posibles en un Dockerfile.

FROM: punto de partida

La instrucción FROM es el punto de partida. Indicamos a partir de qué imagen ya existente vamos a construir nuestra imagen personalizada. Para este ejemplo vamos a usar la imagen de Apache que descargamos en la entrada 1.

FROM httpd:latest

RUN: comandos ejecutados durante la construcción

RUN se utiliza para ejecutar comandos durante la construcción de la imagen. Cada comando RUN crea una nueva capa en la imagen. Se usa principalmente para instalar software y configurar el entorno dentro de la imagen. En este ejemplo vamos a instalar la herramienta nmap. Importante: añadimos la opción «-y». El comando se va a ejecutar sin que nosotros tengamos interacción con la consola, por lo que si no añadimos «-y», apt nos va a solicitar confirmación y la construcción de la imagen va a fallar.

FROM httpd:latest
RUN apt-get update && apt-get install -y nmap

COPY: moviendo ficheros al contenedor

Vamos a usar ese nmap. Para ello vamos a copiar un script dentro del contenedor y lo vamos a ejecutar. La instrucción que nos va a permitir copiar ficheros de nuestra máquina al contenedor es la instrucción COPY. Dejo un script en la misma ruta que está el Dockerfile con nombre «nmap.sh» y contenido:

#!/bin/bash
echo "<h3>Escaneo de 127.0.0.1 </h3>" > /usr/local/apache2/htdocs/nmap_output.html
nmap -A 127.0.0.1 >> /usr/local/apache2/htdocs/nmap_output.html

#Lanzamos Apache
httpd-foreground &

#Añadimos esto para que el script no termine y siga el contenedor en ejecucion
tail -f /dev/null

Este script lo que hace es crear un fichero html en la ruta de Apache con una cabecera y el resultado del escaneo con nmap. Ahora añadimos la instrucción COPY que mueva este script al contenedor. E importante, le damos permisos de ejecución.

FROM httpd:latest
RUN apt-get update && apt-get install -y nmap
COPY nmap.sh /usr/local/bin/nmap.sh
RUN chmod +x /usr/local/bin/nmap.sh

ENV: Variables

Ahora mismo nuestro script lanza un escaneo con nmap a la dirección local. Pero lo ideal sería que la dirección IP sea una variable que podamos definir cuando lanzamos el contenedor. Para esto tenemos la instrucción ENV.

FROM httpd:latest
RUN apt-get update && apt-get install -y nmap
ENV direcIP 127.0.0.1
COPY nmap.sh /usr/local/bin/nmap.sh
RUN chmod +x /usr/local/bin/nmap.sh

Se va a construir la imagen con una variable que se llama «direcIP», que de primeras será «127.0.0.1» pero que cuando lancemos el contenedor, podremos sobrescribir. Ahora solo tenemos que modificar el script para que haga uso de esta variable.

#!/bin/bash
echo "<h3>Escaneo de $direcIP </h3>" > /usr/local/apache2/htdocs/nmap_output.html
nmap -A $direcIP >> /usr/local/apache2/htdocs/nmap_output.html

#Lanzamos Apache
httpd-foreground &

#Añadimos esto para que el script no termine y siga el contenedor en ejecucion
tail -f /dev/null

CMD: Ejecución tras construir el contenedor

Al contrario que RUN, CMD se usa para ejecutar comandos una vez la imagen está construida. Por este motivo, como norma no escrita, solo debe haber una instrucción CMD en todo el Dockerfile y suele ser la instrucción final. En este caso vamos a lanzar el script que hemos copiado.

FROM httpd:latest
RUN apt-get update && apt-get install -y nmap
ENV direcIP 127.0.0.1
COPY nmap.sh /usr/local/bin/nmap.sh
RUN chmod +x /usr/local/bin/nmap.sh
CMD ["/usr/local/bin/nmap.sh"]

docker build: Construir la imagen

Ya tenemos el Dockerfile listo. Hemos partido de Apache, instalamos nmap, lanzamos escaneo y exponemos los resultados a través del servicio web. Pero el Dockerfile por sí solo no es una imagen, hay que construirlo. Y para ello disponemos del comando «docker build«. Ejecutamos en la ruta donde tenemos el Dockerfile:

# Indicamos "." porque estamos en la misma ruta donde esta el Dockerfile. Si no, indicar la ruta. Ponemos como nombre a la imagen "apachecustom"
$> docker build -t "apachecustom" .

Recordad que con «docker image ls» podemos listar las imágenes disponibles, donde ya vemos nuestra «apachecustom».

Y aunque entraremos en mayor detalle sobre la ejecución de contenedores en próximas entradas, con lo que ya sabemos de la parte 1, podemos lanzar un contendor donde la imagen sea «apachecustom».

$> docker run -dti -p8080:80 apachecustom

# O para lanzarlo modificando la variable de entorno
$> docker run -d -p8080:80 -e "direcIP=\"scanme.nmap.org\""  apachecustom

Si accedemos a la raíz está la página inicial de Apache, ya que ahí no hemos subido nada. El resultado del nmap está en el recurso «nmap_output.html».

No vemos que se llegue a escanear ninguna máquina, pero es por un tema de red que resolveremos en siguientes posts.

CheatSheet de Dockerfile

Ya hemos visto algunas instrucciones, pero no son todas. Os dejo una especie de cheatsheet para que veáis todas las instrucciones disponibles y su uso.

# FROM: define la imagen que es punto de partida de esta imagen
FROM centos

# LABEL : Suele ir al inicio. Son metadatos de la imagen. Con un cambio en el label vaa  provocar que se contruya de nuevo
LABEL version =1.0
LABEL descripcion="My container" 

# ENV : Define una variable de entorno que estara disponible en todo el Dockerfile
ENV contenido prueba
ENV contenido=prueba
RUN echo "$contenido" >> /var/www/html/prueba.html

# WORKDIR : Define la ruta a partir de la que trabajamos 
WORKDIR /var/www/html
COPY hello.html .

# RUN: Ejecuciones sobre la imagen base
RUN yum install httpd -y

# COPY: Copia un archivo del host al contenedor 
COPY hello.html /var/www/html

# ADD : Aunque también sirve para ficheros, se usa más para URLs
ADD https://github.com/mdn/html-examples/blob/main/aria-annotations/index.html /var/www/html

# EXPOSE : expone un puerto del contenedor. Importante entender que esto no mapea ningún servicio
EXPOSE 8080

# USER : Define el usuario que va a ejecutar las acciones a continuación
RUN useradd dfirpills
RUN CHOWN dfirpills/var/html/www -R
USER dfirpills
RUN echo "$(whoami)" >> /var/www/html/user.html

# VOLUME : Datos que no se eliminan cuando se muere el contenedor
VOLUME /var/www/html

# CMD : Ejecuta procesos y acciones en el contenedor. Defne la acción que se ejecuta cuando el contenedor se lanza
CMD apachectl -DFOREGROUND_

Conclusiones

Hasta ahora ya sabemos que:

  • Docker nos sirve para virtualizar aplicaciones
  • Que estas se ejecutan en unos entornos aislados y con todas las dependencias, que llamamos contenedores
  • Que los contenedores se lanzan a partir de una plantilla, que llamamos imagen
  • Que la imagen está formada por una serie de capas que son read only
  • Que el repositorio oficial de imágenes es el DockerHub
  • Que podemos partir de una imagen para construir imágenes personalizadas
  • Que las imágenes personalizadas se construyen con un archivo llamado Dockerfile

Casi nada. Seguimos avanzando en la próxima entrada.

Artículos relacionados

Deja un comentario

* Al utilizar este formulario usted acepta el almacenamiento y tratamiento de sus datos por parte de este sitio web.

Este sitio web utiliza cookies para mejorar su experiencia Aceptar Leer más