Punto de acceso automatizado con Docker y Raspberry Pi Zero W Follow The White Rabbit

Tabla de contenido

Buenas a todos!! Vuelvo por aquí con un pequeño post acerca de cómo podemos de forma fácil automatizar la creación de un punto de acceso inalámbrico tomando ventaja de las portabilidad de una Raspberry Pi Zero W y contendores Docker.

Recordaréis entradas anteriores relacionadas con el tema, como Hiding the Rabbit o la primera parte del proyecto AIRE. En ambos casos hicimos uso de una Raspberry Pi 3, cuya interfaz inalámbrica nos permitía crear de forma rápida un punto de acceso de alta portabilidad, haciendo uso de las herramientas hostapd como tecnología de punto de acceso e isc-dhcp-server como servidor DHCP.

En este caso llevamos nuestro dropbox un poco más allá y recogeremos este proceso dentro de un contenedor Docker, de forma que la instalación de las herramientas y dependencias se realiza de forma automática y casi inmediata, así como la configuración de seguridad específica de la red.

Os dejo el repositorio de GitLab en el que he recopilado el proyecto:

https://gitlab.com/hartek/autowlan

Prerrequisitos

Dado que haremos uso de un contendor Docker para gestionar las dependencias y configuración de forma rápida y automatizada, los prerrequisitos son escasos, pero existen.

Debemos asegurarnos de que la característica de IPv4 Forwarding está activada en nuestro sistema operativo, la cual permite la redirección de paquetes entre diferentes redes conectadas. En mi caso, como suelo hacer, utilizo Raspbian sobre mi Raspberry Pi Zero W. En cualquier caso, en casi cualquier distribución podremos comprobar si esta característica con el comando:

sysctl net.ipv4.ip_forward

En caso de que el valor arrojado sea 0, debemos activarlo con:

sysctl net.ipv4.ip_forward=1

Y para hacer el cambio persistente, asegurarse de que existe y está descomentada en el fichero /etc/sysctl.conf la línea:

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1

Para evitar problemas con el servicio dhcpcd, que en Raspbian y otros sistemas Linux modernos es el que gestiona la autoconfiguración de las interfaces de red mediante DHCP, agregaremos al final del fichero /etc/dhcpcd.conf la siguiente línea, haciendo que dicho servicio no gestione la interfaz inalámbrica wlan0 (pero sí la interfaz Ethernet eth0):

denyinterfaces wlan0

Asimiso, evidentemente tendremos que tener el sistema Docker instalado. Podemos hacerlo de forma automática descargando y ejecutando el script de instalación oficial:

curl -sSL https://get.docker.com | sh 

Instalaremos también docker-compose, herramienta que nos facilitará más tarde gestionar los contenedores, instalando algunas dependencias e instalándola desde Pip:

apt-get install libffi-dev libssl-dev
apt-get install -y python python-pip
pip install docker-compose 

En este punto tenemos las dependencias necesarias para continuar con nuestro pequeño proyecto 🙂

Creación y configuración de la imagen

Una vez instaladas las dependencias anteriores, podemos empezar a dar forma a nuestra imagen de Docker, a partir de la cual crearemos el contenedor. Crearemos para el proyecto un directorio con un nombre cualquiera; yo, de forma descriptiva, lo he llamado autowlan. Dentro del mismo, podemos crear un directorio confs que contendrá los ficheros de configuración que serán introducidos en la imagen, y un subdirectorio hostapd_confs con diversas configuraciones para el punto de acceso que veremos más adelante.

Dockerfile

Para definir la imagen podemos crear un nuevo fichero Dockerfile con el siguiente contenido:

from arm32v6/alpine # Install packages
RUN apk update && apk add hostapd iw dhcp vim iptables # Configure Hostapd (default will be open)
ADD confs/hostapd_confs/open.conf /etc/hostapd/hostapd.conf
# Configure DHCPD
ADD confs/dhcpd.conf /etc/dhcp/dhcpd.conf
RUN touch /var/lib/dhcp/dhcpd.leases # Configure networking
ADD confs/interfaces /etc/network/interfaces
ADD confs/iptables.sh /iptables.sh
ADD confs/iptables_off.sh /iptables_off.sh # Copy and execute init file
ADD confs/start.sh /start.sh
CMD ["/bin/sh", "/start.sh"]

Vamos por partes:

  • Comenzamos, en la primera línea, heredando de la imagen arm32v6/alpine. ¿Por qué no simplemente de alpine, como suele hacerse? Porque estamos desarrollando la imagen para una Raspberry Pi Zero W, cuyo procesador es un ARMv6, por lo que necesitamos una imagen compatible con esta arquitectura.
  • Actualizamos los repositorios de la imagen con la herramienta apk y con la misma instalamos los paquetes hostapd (software de punto de acceso), iw (para hacer debug de la interfaz inalámbrica de ser necesario), dhcp (contiene el servicio dhcpd para otorgar direcciones a los clientes), vim (para editar ficheros en caliente de ser necesario) e iptables (para gestionar el enrutado entre redes).
  • Seguidamente, añadimos a la imagen los ficheros de configuración necesarios para que hostapd y dhcpd funcionen correctamente (los veremos más adelante). De forma predeterminada se agrega el fichero open.conf, cuyo uso y razón veremos a continuación, como fichero hostapd.conf.
  • Añadimos también los ficheros de configuración de red (los veremos también más adelante).
  • Por último, añadimos y ejecutamos un script de inicio start.sh que se ejecutará al inicio del contenedor (también lo veremos).

Servicio hostapd – hostapd.conf

Éste fichero se encarga de configurar la herramienta hostapd, que creará y gestionará el punto de acceso en sí. Vamos a crear tres diferentes. ¿Por qué? Porque así podremos gestionar la imagen de forma que nos sirva para crear tres tipos diferentes de punto de acceso inalámbrico:

  • Un punto de acceso tipo OPEN, sin seguridad ni autenticación. Es decir, una red abierta. Tendremos entonces un fichero de configuración confs/hostapd_confs/open.conf con el siguiente contenido:
interface=wlan0
driver=nl80211
ssid=raspi_open
hw_mode=g
ieee80211n=1
channel=6
auth_algs=1
ignore_broadcast_ssid=0
wpa=0
country_code=ES
macaddr_acl=0
  • Un punto de acceso tipo WEP, con Wired Equivalent Protection como sistema de cifrado. En el campo wep_key0 podemos introducir la contraseña con la que queramos configurar el AP; en mi caso y para las pruebas me conformaré con un simple 1234567890 . Tendremos un fichero de configuración confs/hostapd_confs/wep.conf con el siguiente contenido:
interface=wlan0
driver=nl80211
ssid=raspi_wep
hw_mode=g
ieee80211n=1
channel=6
auth_algs=1
ignore_broadcast_ssid=0
wpa=0
country_code=ES
macaddr_acl=0 wep_default_key=0
wep_key0=1234567890
  • Un punto de acceso tipo WPA2, con Wi-Fi Protected Access 2 como sistema de cifrado. En el campo wpa_passphrase podemos establecer la contraseña de acceso y cifrado de la red; en mi caso y para las pruebas me conformaré con un simple password. Tendremos un fichero de configuración confs/hostapd_confs/wpa2.conf con el siguiente contenido:
interface=wlan0
driver=nl80211
ssid=raspi_wpa2
hw_mode=g
ieee80211n=1
channel=6
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
country_code=ES
macaddr_acl=0 wpa_passphrase=password
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP

Podéis encontrar en entrada de la primera parte del proyecto AIRE una explicación de los campos utilizados para estas configuraciones. Simplemente destacaré de forma general que el SSID de la red vendrá dado por el campo ssid de las configuraciones, y la seguridad viene definida de forma específica en cada uno, como hemos podido observar.

Servicio DHCP – dhcpd.conf

En este fichero encontraremos los campos de configuración necesarios para crear un servicio DHCP que nos permita otorgar direcciones IP a los clientes que se conectan a nuestro punto de acceso. Esta configuración será leída por el servicio dhcpd que antes instalamos en la imagen a través del Dockerfile.

authoritative;
subnet 11.0.0.0 netmask 255.255.255.0 { range 11.0.0.10 11.0.0.20; option broadcast-address 11.0.0.255; option routers 11.0.0.1; default-lease-time 600; max-lease-time 7200; option domain-name "local"; option domain-name-servers 8.8.8.8;
}

Podéis ver también en la primera parte del proyecto AIRE una explicación más exhaustiva de los campos, pero en resumen, definimos con authoritative que somos el servidor DHCP principal de la red y definimos una red 11.0.0.0/24 en cuyo rango otorgaremos hasta diez direcciones a clientes.

Configuración de red – interfaces

Como vimos en el Dockerfile, a la imagen se agrega un fichero interfaces cuya ruta completa se ubica en /etc/network/interfaces y que contendrá la configuración de red del punto de acceso creado sobre la interfaz wlan0.

El contenido del mismo es el siguiente:

auto wlan0
iface wlan0 inet static address 11.0.0.1 netmask 255.255.255.0 auto eth0
iface eth0 inet dhcp

Como puede verse, se establece que la interfaz wlan0, que será sobre la que se crea el punto de acceso, tendrá una dirección IP estática 11.0.0.1/24. Además, se reitera que la interfaz cableada eth0 se configurará mediante DHCP cuando se conecte a nuestra puerta de enlace de salida, a través de la cual saldremos a Internet. En los prerrequisitos ya configuramos el fichero /etc/dhcpcd.conf en el sistema host y en la imagen de Alpine no debería haber ningún servicio que nos moleste en el mismo sentido, pero nunca viene mal ser precavido y/o verboso.

Configuración de red – iptables.sh e iptables_off.sh

En estos dos ficheros de configuración definimos las reglas de IpTables a través de las cuales permitiremos la creación de una NAT o Network Address Translation para permitir la comunicación transparente entre la red de nuestro punto de acceso y el exterior. En nuestro caso, por tanto, la NAT se crea entre la red inalámbrica wlan0 y la red cableada eth0, la segunda prestando servicio a la primera.

El script iptables.sh contiene por tanto las reglas necesarias para establecer esta NAT y tiene este contenido:

#/bin/sh
iptables-nft -t nat -C POSTROUTING -o eth0 -j MASQUERADE || iptables-nft -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables-nft -C FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT || iptables-nft -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables-nft -C FORWARD -i wlan0 -o eth0 -j ACCEPT || iptables-nft -A FORWARD -i wlan0 -o eth0 -j ACCEPT

En cada línea, estamos haciendo uso del operador lógico || u «OR» de bash. Lo que estamos declarando es que por cada línea se intenta ejecutar el primer comando, a la izquierda del operador. En caso de que termine con éxito (código de retorno 0) no se ejecutará el segundo comando, a la derecha del operador. En caso de que retorne un error (código de retorno 1), el segundo comando sí se ejecutará.

En cada una de las líneas se está ejecutando a la izquierda del operador un comando iptables-nft, comando utilizado en Alpine para gestionar el nuevo sistema de Firewall de Linux basado en nftables con la sintaxis clásica de IpTables (en Debian, Raspbian y otros el comando iptables es un enlace a iptables-nft de forma predeterminada), para realizar un check (opción -C) de una regla, comprobando si existe. Si no existe, el comando de check retornará un valor 1 (error), y por tanto se ejecutará el segundo comando, que agrega la regla. De esta forma estaremos evitando que la misma regla sea agregada dos veces.

En cuanto a las reglas en sí:

  • La primera de ellas crea una regla en la cadena POSTROUTING en la tabla nat sobre la interfaz eth0, creando así una regla NAT de tipo Masquerade (cambia la IP origen por aquella definida en eth0 en los paquetes que salgan por dicha interfaz).
  • La segunda regla permite la redirección de paquetes que procedan del segmento de red de eth0 hacia la interfaz wlan0 siempre que estén relacionados con una conexión establecida (RELATED,ESTABLISHED).
  • La tercera regla permite la redirección de paquetes que procedan del segmento de red de wlan0 hacia la interfaz eth0 en cualquier caso, permitiendo por tanto la apertura de conexiones desde el interior hacia el exterior.

En caso del fichero iptables_off.sh, tiene el siguiente contenido:

#/bin/sh
iptables-nft -t nat -C POSTROUTING -o eth0 -j MASQUERADE && iptables-nft -t nat -D POSTROUTING -o eth0 -j MASQUERADE
iptables-nft -C FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT && iptables-nft -D FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables-nft -C FORWARD -i wlan0 -o eth0 -j ACCEPT && iptables-nft -D FORWARD -i wlan0 -o eth0 -j ACCEPT

Puede observarse que el contenido es casi el mismo que en el script anterior con dos salvedades. La primera es que sustituye el operador lógico || (OR) por el operador && (AND). Por tanto, ejecutará el segundo comando de cada línea si el primero termina con éxito, es decir, si la regla sí que existe y por tanto debe ser eliminada por el script. La segunda salvedad es que en vez de agregar una regla a una cadena o chain con el parámetro -A estaremos eliminándola, haciendo uso de -D.

Script de inicio – start.sh

Por último, tenemos un script de inicio que será el punto de partida de la ejecución en el contenedor, que llamamos start.sh, y en el que veremos cómo se junta todo lo anterior. Tendrá el siguiente contenido:

#!/bin/sh NOCOLOR='\033[0m'
RED='\033[0;31m'
CYAN='\033[0;36m'
GREEN='\033[0;32m' sigterm_handler () { echo -e "${CYAN}[*] Caught SIGTERM/SIGINT!${NOCOLOR}" pkill hostapd cleanup exit 0
}
cleanup () { echo -e "${CYAN}[*] Deleting iptables rules...${NOCOLOR}" sh /iptables_off.sh || echo -e "${RED}[-] Error deleting iptables rules${NOCOLOR}" echo -e "${CYAN}[*] Restarting network interface...${NOCOLOR}" ifdown wlan0 ifup wlan0 echo -e "${GREEN}[+] Successfully exited, byebye! ${NOCOLOR}"
} trap 'sigterm_handler' TERM INT
echo -e "${CYAN}[*] Creating iptables rules${NOCOLOR}"
sh /iptables.sh || echo -e "${RED}[-] Error creating iptables rules${NOCOLOR}" echo -e "${CYAN}[*] Setting wlan0 settings${NOCOLOR}"
ifdown wlan0
ifup wlan0 echo -e "${CYAN}[+] Configuration successful! Services will start now${NOCOLOR}"
dhcpd -4 -f -d wlan0 &
hostapd /etc/hostapd/hostapd.conf &
pid=$!
wait $pid cleanup

Vamos por partes, que es un poco más complejo 🙂

  • En primer lugar encontarmos algunas declaraciones de códigos de color ANSI para tener una salida un poco más coloreada y por tanto más intuitiva visualmente.
  • Vemos definida una función sigterm_handler. Esta función nos sirve para capturar las señales SIGTERM y SIGINT que puedan llegar al script, ya sea porque se envía una interrupción por teclado al contenedor en modo interactivo (CTRL+C) o porque se detiene el contenedor haciendo uso de docker stop. La función detendrá de forma ordenada la herramienta hostapd en ejecución y ejecutará la función cleanup que describiremos ahora.
    • Unas líneas más adelante vemos cómo se declara que se ejecuta esta función al detectarse estas señales:
trap 'sigterm_handler' TERM INT
  • Se declara una función cleanup que servirá para devolver el sistema anfitrión a su estado normal. Dado que el contenedor hace uso de la capability NET_ADMIN, su gestión de redes se refleja directamente en el host, y querremos deshacer estos cambios al terminar. Estos cambios a retirar son las reglas de IpTables insertadas, que retira haciendo uso del script iptables_off.sh antes descrito, y el estado de la interfaz wlan0, que puede podría quedar en un estado no controlado, pudiendo solucionarse bajando y subiendo la interfaz con ifdown e ifup.
    • Como puede verse, esta función se ejecuta siempre al final del script y siempre que se reciba una señal de terminación o parada (SIGTERM/SIGINT) y se ejecute la función sigterm_handler.
  • Acto seguido empieza la ejecución de las herramientas en sí. Tras declarar la función sigterm_handler como handler de SIGINT/SIGTERM ejecutamos el script iptables.sh descrito anteriormente para crear las reglas de red necesarias, y haciendo uso de ifdown e ifup reconfiguramos la interfaz wlan0 con la configuración de /etc/network/interfaces cargada en el contenedor y que vimos anteriormente.
  • Por último, ejecuta los servicios del punto de acceso en sí.
    • El servicio dhcpd con los parámetros -4 -f -d wlan0, indicando respectivamente que se otorgan direcciones IPv4, se ejecuta en foreground (y por tanto mostrando logs por pantalla) y que se entregan los logs a stderr, ejecutándose específicamente sobre la interfaz wlan0. Además vemos al final que el carácter & del comando lo ejecuta en background. En principio parece contradecir el parámetro -f, pero nos permitirá ver por pantalla todos los logs del servicio mientras se ejecuta en background.
    • El servicio hostapd se ejecutará, también en segundo plano haciendo uso de &, leyendo la configuración encontrada en /etc/hostapd/hostapd.conf. Como vimos en el Dockerfile, allí se encuentra agregada una configuración de red abierta de forma predeterminada. Veremos en el siguiente apartado como hacer uso de otras configuraciones utilizando un bind mount.
  • Por último guarda el PID de hostapd y con él el script queda a la espera de que el proceso termine haciendo uso de wait. Si en algún momento hostapd falla y termina, la función cleanup final permitirá que siempre el contenedor devuelva la configuración de red al estado original.

Creación de la imagen con docker build

Habiendo terminado el recorrido por nuestros diversos ficheros de configuración, tenemos todo listo para crear nuestra imagen. Podemos hacerlo situándonos en el directorio del proyecto y ejecutando el siguiente comando:

docker build . --tag autowlan

Con ésto se comenzará la creación de la imagen, que aparecerá marcada con un tag de nombre autowlan que facilitará su identificación posterior.

Podemos ver como se descarga las imagen base de Alpine necesaria, se instalan las herramientas en la imagen y se copian a la misma los ficheros de configuración (a no ser que, como en mi caso y por ahorrar espacio aquí, los primeros pasos se tomen desde una caché anterior). Al tratarse de una Raspberry Pi Zero W el proceso puede ser algo lento, no le pidáis tanto al cacharro 🙂

Creación y ejecución del contenedor

Entramos ya en la recta final. Podemos con el siguiente comando arrancar el contenedor a partir de la imagen creada:

docker run --name autowlan_open --cap-add=NET_ADMIN --network=host autowlan

Una pequeña explicación de los parámetros en el comando:

  • –name permite crear el contendor con un nombre identificativo, facilitando la gestión.
  • –cap-add=NET_ADMIN añade al contenedor una capability del sistema Linux llamada NET_ADMIN, que permite a un proceso proceso (o en este caso al contenedor) cambiar la configuración de red de la máquina. Es necesario para poder crear las reglas de IpTables necesarias para el punto de acceso.
  • –network=host establece que el contenedor utilizará la red del anfitrión, haciendo uso de sus interfaces y direcciones IP de forma transparente.
  • autowlan es el nombre de la imagen creada en el paso anterior.

Podemos ver cómo se inicia el contenedor y los servicios, mostrando las líneas de log tanto de hostapd como de dhcpd además de líneas en color acerca del proceso de ejecución del contenedor, que escribimos en start.sh. Adicionalmente, podemos ver cómo se ha conectado un dispositivo a la red (¡censurado! :P) y cómo se le ha ofrecido y entregado la dirección IP 11.0.0.10/24 para posteriormente desconectarse.

Si quisiésemos crear y arrancar el contenedor en segundo plano, podemos añadir el parámetro -i a docker run.

Podemos detener el contenedor con una interrupción de teclado (CTRL-C), pudiendo ver el proceso de vuelta a la normalidad en la configuración de red del anfitrión y un mensaje de éxito final. De estar ejecutándose en segundo plano, ejecutaremos docker stop autowlan_open para detener el contenedor.

Si quisiésemos volver a levantar el contendor, solo tendríamos que ejecutar docker start -i autowlan_open para iniciarlo, dado que ya está creado con el comando docker run anterior. Si queremos ejecutarlo en segundo plano, omitiremos el parámetro -i.

Configuraciones WEP y WPA2

Recordaréis que, aunque de forma predeterminada la imagen autowlan ya contiene la configuración necesaria para crear un punto de acceso abierto, hemos creado configuraciones adicionales en el directorio confs/hostapd_confs con configuraciones de seguridad WEP y WPA.

Podemos hacer uso de ellas a la hora de crear el contenedor con docker run añadiendo al comando un bind mount, mediante el cual montaremos un fichero de configuración entre los anteriores en /etc/hostapd/hostapd.conf dentro del contenedor. Ésto hará que en su ejecución hostapd los tome como ficheros de configuración y cree un punto de acceso con la configuración de seguridad indicada.

Por tanto, para crear un contenedor con configuración WEP podemos ejecutar:

docker run --name autowlan_wep --cap-add=NET_ADMIN --network=host -v $(pwd)/confs/hostapd_confs/wep.conf:/etc/hostapd/hostapd.conf autowlan

Como puede verse, hemos variado el nombre del contenedor a autowlan_wep para que sea descriptivo y hemos añadido el parámetro -v $(pwd)/confs/hostapd_confs/wep.conf:/etc/hostapd/hostapd.conf, que realiza el bind mount del fichero de configuración wep.conf sobre el contenedor (se utiliza $(pwd) para crear una ruta absoluta).

De forma similar, para crear un contenedor con la configuración WPA2:

docker run --name autowlan_wpa2 --cap-add=NET_ADMIN --network=host -v $(pwd)/confs/hostapd_confs/wpa2.conf:/etc/hostapd/hostapd.conf autowlan

En ambos casos la gestión se realiza con exactamente los mismos comandos que en el apartado anterior, con la salvedad de que tendremos que utilizar el nombre del contenedor en cuestión.

Si ejecutamos el comando docker container ls podremos ver los tres contenedores creados y listos para funcionar.

Gestión de los contenedores con docker-compose

Por último, y de forma totalmente opcional, podríamos hacer uso de la herramienta docker-compose para crear y gestionar los contenedores. Para ello, podemos crear tres ficheros diferentes para gestionar los tres tipos de contenedor que hemos descrito.

Fichero open-docker-compose.yml para gestionar el contendor sin seguridad:

version: '3.7'
services: wlan: container_name: wlan build: . image: wlan cap_add: - NET_ADMIN stop_grace_period: 2m network_mode: "host" volumes: - ./confs/hostapd_confs/open.conf:/etc/hostapd/hostapd.conf

Fichero wep-docker-compose.yml para gestionar el contenedor con seguridad WEP:

version: '3.7'
services: autowlan: container_name: autowlan_wep build: . image: wlan cap_add: - NET_ADMIN stop_grace_period: 2m network_mode: "host" volumes: - ./confs/hostapd_confs/wep.conf:/etc/hostapd/hostapd.conf

Fichero wpa2-docker-compose.yml para gestionar el contenedor con seguridad WPA2:

version: '3.7'
services: autowlan: container_name: autowlan_wpa2 build: . image: wlan cap_add: - NET_ADMIN stop_grace_period: 2m network_mode: "host" volumes: - ./confs/hostapd_confs/wpa2.conf:/etc/hostapd/hostapd.conf

En cualquiera de los tres casos, podermos ejecutar docker-compose leyendo los ficheros de configuración y gestionando los contenedores con los siguientes comandos:

  • docker-compose -f <fichero_yml> up para levantar un contenedor.
  • docker-compose -f <fichero_yml> up -d para levantar un contenedor en segundo plano.
  • docker-compose -f <fichero_yml> down para bajar un contenedor en segundo plano.
  • docker-compose -f <fichero_yml> logs para consultar los logs de un contenedor en segundo plano.

Y hasta aquí la entrada 🙂 Aunque haya sido un poco tocha, espero que os haya gustado y os sirva para aprender. No seáis muy malos!!

Fuente obtenida de: https://fwhibbit.es/punto-de-acceso-automatizado-con-docker-y-raspberry-pi-zero-w

INFORMACION DEL PUBLICADOR
Picture of Kamal Majaiti
Kamal Majaiti
Administrador de sistemas e informático por vocación.
COMPARTELO EN REDES
Publica un comentario

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.