- Docker para ciberseguridad – Parte 1: Instalación e imágenes
- Docker para ciberseguridad – Parte 2: Crear imágenes. Dockerfile
- Docker para ciberseguridad – Parte 3: Gestión de contenedores
- Docker para ciberseguridad – Parte 4: docker volume y docker network
- Docker para ciberseguridad – Parte 5: Docker Compose
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.