Anteriormente: Flask 19. Usar Flask Blueprint para estructurar
aplicaciones. 2ª parte.
Desplegar una aplicación Flask con Nginx y Gunicorn en una Rapsberry
Pi
En este capítulo, lo que vamos a hacer es desplegar nuestra aplicación Flask
en una Rapsberry Pi que actuará como servidor, para que cualquiera pueda
acceder a ella desde cualquier parte del mundo. Para ello utilizaremos los
programas Nginx y Gunicorn. Gunicorn se encargará de traducir nuestro programa
de Flask a Nginx para que este se encargue de servir nuestras plantillas web
como respuesta a las peticiones que se realicen.
Paso 1: Requisitos previos
A) Actualizar los repositorios del sistema.
El sistema operativo que utilizaré en mi Rapsberry Pi es el suyo propio,
Raspbian Pi Os, que es un derivado linux de Debian. En él, ya viene instalado
Python3 con lo que empezaremos actualizando los paquetes del sistema con los
siguientes comandos:
$ sudo apt-get update
$ sudo apt-get full-upgrade
B) IP interna fija.
Es necesario que nuestra Raspberry Pi tenga una IP estática, que no varíe
nunca cuando se reinicie, dentro de nuestra red local.
Cada router es un mundo. Para configurar una IP estática debemos asegurarnos que esté fuera del
rango DHCP (tendréis que verificarlo en la configuración de vuestro router)
y que ningún otro dispositivo tiene asignada la misma IP. En el mío, se empiezan a asignar direcciones desde 192.168.1.1 hasta
192.1.168.199 por lo que no haya problemas de asignación, la Rapsberry Pi
estará estática en la dirección
192.168.1.200
Para asignar una IP estática en Rapsberry Pi Os seguimos los siguientes pasos.
Debemos editar el fichero /etc/dhcpcd.conf con el comando:
sudo nano /etc/dhcpcd.conf
En este fichero hay unas líneas comentadas, que empiezan por #. Buscamos
la que tiene un ejemplo de configuración de IP estática (Example static IP
configuratión) y añadimos la dirección estática que queramos (en mi caso
192.168.1.200/24, con el /24 al final) y la dirección de nuestro router,
en el mío 192.168.1.1
Mi Rapsberry Pi esta conectada directamente al router (eth0), si la
tienes conectada por wifi debes utilizar entonces "interface wlan0"
# Example static IP configuration:
interface eth0
static ip_address=192.168.1.200/24
static routers=192.168.1.1
Guardamos y Cerramos.
ACTUALIZACION.!!! A fecha de hoy 20/03/2024 con las sucesivas actualizaciones del sistema operativo de la pi, esto ya no funciona, concretamente desde la versión Bookworm. Ahora hay que hacerlo a nivel de router que es lo recomendado. No obstante se puede seguir haciendo a nivel de host de forma sencilla. Sigue las instrucciones de este enlace https://access.redhat.com/documentation ... connection.
Si no eres root acuérdate de usar sudo delante. Se resume en dos pasos:
1.- Si no conocemos el nombre del dispositivo de red que estamos usando para la conexión, podemos mostrarlo con:
# nmcli device status
eth0 ethernet connected Wired connection 1
lo loopback connected (externally) lo
wlan0 wifi disconnected --
p2p-dev-wlan0 wifi-p2p disconnected --
2.- Ejecutamos nmtui:
# nmtui
De forma visual, escogemos "Edit a conexion", seleccionamos la conexión activa por su nombre, en mi caso "Wired connection 1" y le damos a "Edit". Vamos al apartado "IPv4 CONFIGURATION" y en "Addresses" ponemos la ip fija que queremos utilizar y en "Gateway" la dirección de acceso al router. Le damos a OK, guardamos y salimos.
Puedes encontrar más información en el forum de rapsberri pi "Static IP con Bookworm".
C) Un poco de seguridad.
Como norma básica de seguridad sería conveniente seguir una serie de pautas
como cambiar el nombre del usuario por defecto, el nombre de la máquina,
configurar un Firewall etc.
NOTA: Por razones prácticas en el resto del capítulo (todo lo que no es
este apartado de seguridad) he seguido usando el usuario pi y rapsberry como
nombre del host, pero lo suyo seria cambiarlos.
Cuando el servidor este en modo de producción tendríamos que haber seguido al
menos las normas más básicas de seguridad que os paso a enumerar. Puedes
encontrar más información
aquí.
1) Root desactivado.
En el sistema operativo que utilizo en la Raspberry Pi, Raspberry Pi OS o
Raspbian antiguamente, el usuario Root ya viene desactivado por defecto. No
obstante si utilizas otra distribución Linux que no ocurra esto
deberías definir un usuario con permisos de Root y luego
desactivarlo.
2) Cambiar la contraseña por defecto del usuario pi.
Aunque lo mejor es utilizar otro usuario que no se pi, lo que haremos en el
próximo paso, si vas a seguir con el usuario "pi" por lo menos
cámbiale la contraseña que viene por defecto. Para ello desde el
terminal puedes utilizar la aplicación de configuración:
sudo raspi-config
y ya de paso cambiar también el nombre de la máquina o host que por
defecto es "raspberrypi" o puedes usar este comando de linux aunque solamente
para cambiar la contraseña por defecto "raspberry"
passwd
3) Eliminar el usuario pi y crear uno nuevo con permisos.
Como los principales ataques por fuerza bruta que vas a recibir en tu servidor
van a intentar conectarse al usuario "pi" que saben que es el que viene por
defecto al menos vamos a intentar complicarles algo las cosas. Para ello
seguimos el ejemplo que viene en el
manual de seguridad
de la fundación Rapsberry creando un nuevo usuario con privilegios y
eliminando finalmente al usuario pi.
Empezaremos creando un nuevo usuario, usa el nombre que quieras, en el ejemplo
utilizaremos alice.
sudo adduser alice
Aquí nos pedirá que introduzcamos la contraseña para este nuevo usuario. Este
usuario tendrá su directorio base en /home/alice
Luego le daremos los permisos necesarios para poder funcionar:
sudo usermod -a -G adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,gpio,i2c,spi alice
Para ver si todos los permisos estan correctos utiliza:
sudo su - alice
Si no te sale ningún error es que todo está bien.
Una vez hecho esto cierra la sesión que teníamos abierta con el usuario pi o
reinicia el sistema y vuelve a entrar en el mismo, en este caso usando el
nuevo usuario creado "alice". Ahora por si queda algún proceso que utilice el
usuario "pi" antes de borrarlo paramos sus procesos con:
sudo pkill -u pi
y finalmente podemos optar por:
- borrar al usuario pi pero dejando todos sus directorios intactos para lo
cual tecleamos:
sudo deluser pi
o si es una instalación limpia nos cargamos al usuario pi y también todos sus
directorios que no nos sirven para nada con:
sudo deluser -remove-home pi
Si utilizas este último comando, te saldrá un mensaje de advertencia
diciéndote que el grupo pi no tiene más usuarios. El comando deluser ha
borrado tanto al usuario pi como el grupo pi así que puedes ignorar esta
advertencia tranquilamente y seguir para adelante.
4) Hacer que el comando "sudo" requiera contraseña.
Poner sudo delante de un comando hace que ese comando se ejecute como si
fueras un superusuario, y en esta distribución por defecto no hace falta
contraseña. Esto generalmente no es un problema, pero imagina que como la pi
va a estar conectada a internet alguien logra entrar y pueda ejecutar comandos
que requieran credenciales de superusuario. Pues tenemos un problema. Para
evitar esto forzaremos al sistema a que cada vez que usemos "sudo" nos pida la
contraseña.
Entramos en el terminal y tecleamos:
sudo visudo /etc/sudoers.d/010_pi-nopasswd
Te encontraras una línea para el usuario pi, parecida que no igual que la de
abajo, que tenemos que cambiar por nuestro nombre de usuario y la siguiente
configuración:
alice ALL=(ALL) PASSWD: ALL
Si tuviésemos otros usuarios con permisos de superusuario también tendríamos
que cambiarles la configuracion a ALL=(ALL) PASSWD: ALL. lo cual no es el caso
si has seguido el ejemplo con una instalación limpia. Solo tenemos a alice.
Guardamos y salimos.
Para que el cambio surja efecto reinicia el sistema.
5) Tener el sistema actualizado.
Básico en todos los sistemas operativos para tener los últimos parches que
surjan para tapar agujeros de seguridad.
Esto conviene hacerlo regularmente y son los comandos:
sudo apt-get update
sudo apt-get full-upgrade
Como esto en un servidor no es muy práctico el hacerlo a mano todos los días
seria conveniente el hacerlo de manera automática. Puedes usar para ello la
aplicación
cron de linux
que permitirá ejecutar estos comandos el día y a la hora que los programes o
utilizar soluciones de terceros como unattended-upgrades
Como yo en mi caso además me conecto con la raspberry no directamente sino
desde mi pc a través de ssh, conviene también tener siempre al día este
protocolo con:
sudo apt install openssh-server
6) Mejorando la seguridad de la conexión SSH
Si como es mi caso, no os estáis tecleando directamente en la
raspberry sino que lo hacéis remotamente a través de una conexión ssh
desde un pc es muy importante seguir las siguientes normas para intentar
evitar que alguien se nos cuele en el sistema por esta vía.
Lo más importante que debes hacer es asegurarte de tener una contraseña muy
sólida. Si tu raspberry va a estar conectada a internet, haciendo de
servidor, lo lógico es que tu contraseña de acceso sea lo más robusta
posible. Esto te ayudará a evitar los ataques por fuerza bruta que por
ejemplo usan un diccionario de palabras para intentar encontrar tu
contraseña.
Existen muchas páginas online donde puedes comprobar la robustez de una
contraseña. Unas de ellas es por ejemplo https://password.kaspersky.com/es/
Si tecleas por ejemplo 1234, verás lo siguiente:
No tienen que ser tampoco muy rebuscadas, (ojo que si metes números,
mayúsculas y símbolos raros pues mejor) si utilizas dos palabras que te digan
algo con un símbolo por medio, pues también está bien. Por ejemplo poniendo
como contraseña "paragüas&rojo":
Si queremos añadir un poquito más de seguridad también podemos
permitir o denegar a determinados usuarios su conexión a la raspberry
por sshd. Como en mi caso, solo voy a querer entrar yo en la raspberry, me
permitiré solamente el acceso a mi usuario mismamente :-)
Esto se hace entrando en el archivo de configuración con el comando:
sudo nano /etc/ssh/sshd_config
y añadiendo, editando o agregando al final del mismo la siguiente línea, que
contiene los nombres de los usuarios a los que quiero permitir el acceso, en
este caso yo mismo. Como en todo este subapartado de seguridad yo soy el
usuario "alice" habría que añadir:
AllowUsers alice
...vamos que solo permito al usuario "alice", que soy yo, el acceso a la
raspberry mediante una conexión ssh.
También puedes usar DenyUsers para evitar específicamente que
algunos nombres de usuario inicien sesión si te interesa:
DenyUsers mihermano hackermaligno loqueseteocurra
Después de guardar los cambios y salir del archivo si queremos que estos
entren ya mismo en funcionamiento tenemos que reiniciar el servicio con:
sudo systemctl restart ssh
o también reiniciar la raspberry valdría para que los cambios tuvieran efecto.
AUNQUE LO MEJOR Y LO MÁS SEGURO, que la gente que sabe de esto recomienda para acceder a la
raspberry por ssh es que te olvides de la contraseña y utilices la
autentificación basada en claves criptográficas (Key-based
authentication)
Vamos con ello.
En el cliente, es decir en el ordenador que estas usando para conectarte a la
raspberry hay que teclear desde el terminal:
>> ssh-keygen
Esto nos generará dos claves criptográficamente seguras, una privada y una
pública que utilizaremos para autenticar nuestro ordenador (cliente) en un
servidor (nuestra pi) a través del prótocolo ssh.
Al ejecutar el comando nos saldrá la opción de poner contraseña aunque por
el momento lo dejaremos en blanco. Creará un directorio oculto
.ssh con la clave privada id_rsa y pública id_rsa.pub. La clave privada la mantendremos a buen recaudo mientras que la clave
pública la tenemos que copiar a la raspberry pi con el siguiente comando,
que tecleamos en la terminal de nuestro pc:
>>> ssh-copy-id <USERNAME>@<IP-ADDRESS>
<USERNAME> es el usuario de tu raspberry (alice en mi caso) y
<IP_ADDRESS> la dirección de red que corresponde a tu raspberry
(192.168.1.200 en mi caso)
Nota. Como lo que estamos haciendo es enviar la clave pública desde
nuestro ordenador al fichero de claves autorizadas en la raspberry pi
(authorized-keys) necesitaras teclear cuando te lo pida la contraseña de
tu usario en la pi.
Yo lo estoy haciendo en una máquina con ubuntu 20.04 pero si este comando
no estuviera disponible en vuestra distribución puedes copiarla directamente
usando:
cat ~/.ssh/id_rsa.pub | ssh <USERNAME>@<IP-ADDRESS> 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
También podrías enviarla manualmente a través de ssh con el comando
scp. Puedes ver
este link
para obtener más información sobre como enviar archivos mediante el comando
scp.
Para finalizar como nos vamos a conectar usando las claves criptográficas ya
no necesitamos que la rapsberry nos pida contraseña al conectarnos mediante
ssh. Entonces para finalizar desactivamos que se requiera contraseña para
conectarnos a la raspberry. ESTO lo tecleamos ya en la raspberry, para editar
el archivo de configuración de la conexión ssh:
sudo nano /etc/ssh/sshd_config
Hay 3 líneas que necesitamos cambiar a
no, si aún no están configuradas de esta manera.
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
Guardamos y salimos.
Como siempre para que vuelva a funcionar reiniciamos el servicio con:
sudo service ssh reload
o reiniciamos el sistema.
7) Desabilitar el acceso como Root.
Aunque en Raspberry Os el root está deshabilitado por defecto no está de mas
impedir el acceso como root al sistema. Solo nos llevará un momento y se hace
en el mismo archivo de configuración que hemos usado antes:
sudo nano /etc/ssh/sshd_config
Buscamos la línea y la cambiamos a no, o si no existe lo tecleamos
directamente:
PermitRootLogin no
Guardamos y salimos.
Igual que en el punto anterior reiniciamos el servicio con:
sudo service ssh reload
o reiniciamos el sistema.
8) Configurar un Firewall Básico.
Como nuestro servidor va a estar abierto al mundo exterior es conveniente que
tengamos algún tipo de Firewall UFW. Rapsberry Pi no tiene ninguno instalado
con lo que procedemos a instalarlo con:
alice@raspberrypi:~ $ sudo apt-get install -y ufw
Cuando usamos un sistema en remoto hay que tener cuidado ya que es muy fácil
bloquearse a uno mismo y no poder entrar en el sistema. Por ejemplo porque
usemos la conexión ssh y hayamos configurado el firewall para que lo bloquee.
Por ello lo primero que vamos a ver es como resetear el firewall a su
configuración original por si algo sale mal. Lo que tendrías que teclear, que
ahora no hace falta es:
alice@raspberrypi:~ $ sudo ufw reset
Si como yo, no tienes monitor y estás accediendo a la rapsberry pi a través
del protocolo ssh, necesitamos asegurarnos de que el cortafuegos permita
conexiones ssh para que podamos conectarnos o iniciar sesión con ella la
próxima vez. Podemos permitir esas conexiones tecleando:
alice@raspberrypi:~ $ sudo ufw allow OpenSSH
o también podríamos utilizar otra forma alternativa que hace los mismo:
alice@raspberrypi:~ $ sudo ufw allow ssh
o tambien alice@raspberrypi:~ $ sudo ufw allow 22
¿Por qué es lo mismo? Pues porque el servicio ssh utiliza por defecto el
puerto 22. Si por más seguridad hubiéramos configurado el puerto por defecto
que usa el servicio ssh a otro puerto distinto tendrías que abrir ese puerto
para poder usar el servicio.
Posteriormente ya podemos activar el cortafuegos tecleando:
alice@raspberrypi:~ $ sudo ufw enable
Teclea "y" de yes y presiona Enter para que continúe. Si quisieras desactivar
el modo interactivo del cortafuegos y que se ejecutará sin que te pregunte
nada añade --force (sudo ufw --force enable)
Tambíen podemos añadir:
alice@raspberrypi:~ $ sudo ufw limit ssh/tcp
que va a hacer que se niegue la conexión a una ip que ha tratado de conectarse
6 o más veces en los últimos 30 segudos.
Para terminar, puedes ver que funciona y que el protocolo ssh esta permitido
tecleando:
alice@raspberrypi:~ $ sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Si por la razón que fuese quisieras desactivar el cortafuegos utiliza el
comando "disable".
Como el cortafuegos actualmente esta bloqueando todas las conexiones excepto
ssh, si instalas y configuras servicios adicionales, necesitaras ajustar la
configuración del mismo.
Puedes encontrar una guía básica de configuración en inglés
aquí.
En resumen:
$ sudo apt-get install -y ufw
$ sudo ufw allow OpenSSH
$ sudo ufw --force enable
$ sudo ufw limit ssh/tcp
# niega la conexion si una ip ha intentado conectarse 6 o más
# veces en los últimos 30 segundos.
$ sudo ufw status
8) Protegerse de ataques por fuerza bruta con Fail2ban
Para terminar con el apartado de "seguridad" vamos a ver como funciona muy
resumidamente Fail2bin.
De la wikipedia:
Fail2ban es una aplicación escrita en Python para la prevención de intrusos en un sistema, que actúa penalizando o bloqueando las conexiones
remotas que intentan accesos por fuerza bruta
Lo primero como siempre es instalar el programa:
sudo apt install fail2ban
Básicamente vamos a instalar el programa con las opciones por defecto, pero
añadiendo unas reglas básicas para ssh. Como vamos a usar unos parámetros
diferentes crearemos el archivo /etc/fail2ban/jail.local para este
propósito e introduciremos ahí los parámetros deseados. Estos
sobrescriben los valores respectivos de jail.conf que no se deben
tocar.
Teclearemos:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Una vez en el editor añadimos la siguiente regla:
[ssh]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = -1
Guardamos y salimos.
Nota:
maxretry son los intentos máximos de conexión erróneos que
permitimos. En nuestro caso 3. OJO si te conectas a la pi por ssh mediante
contraseña que si te confundes tres veces serás baneado y si encima tienes
bantime con un número negativo, como en nuestro ejemplo,
lo hace para siempre.
Podemos quitar bantime y el programa usará el tiempo por baneo por defecto
del programa o si queremos podemos poner un tiempo determinado por nosotros
en segundos. Por ejemplo un baneo de una hora sería bantime=60000 o si
queremos una semana también podemos poner bantime=1w
Si hacemos un cambio en el archivo de configuración, o bien reiniciamos el
sistema o bien usamos:
sudo service fail2ban restart
Podemos ver la actividad de fail2ban con:
sudo fail2ban-client status ssh
o editando el siguiente archivo:
cat /var/log/fail2ban.log
D) IP Pública Dinámica y obtención de nombre de Dominio apuntando a nuestro
servidor.
Otro requisito previo es que necesitamos acceder a la Raspsberry Pi desde
fuera de nuestra red local (DNS DINAMICA) y tenemos que obtener un nombre de
dominio que apunte a nuestro servidor. Ambas cosas las conseguiremos con lo
siguiente.
Hasta ahora todo lo que hemos realizado es para acceder a la aplicación desde
nuestra red local, para que nuestra Pi tenga siempre una dirección fija en
nuestra red. Pero para acceder desde fuera de ella, el primer problema que se
nos plantea es que normalmente solemos tener una dirección IP Pública
variable, que no siempre es la misma, que puede cambiar. Es como si fuéramos a
visitar a un amigo en el extranjero que hace tiempo que no vemos y cada vez
que vamos a verle nos cambian el nombre y número de la calle.
Puedes saber cual es tu ip pública en un momento concreto buscando en
cualquier buscador ¿Cuál es mi ip?
Para solventar esto y que aunque cambie nuestra ip pública el que busque
nuestra página siempre sea redirigido a nuestra raspberry vamos a utilizar un
servicio gratuito de una página muy conocida
llamada https://www.noip.com/, aunque existen otras muchas (Freenom,
Ducks Dns, DNS Exit, dynu, freedns.afraid.org etc)
La filosofía a groso modo es la siguiente. Instalaremos un programa en nuestra
Rapsberry que le dirá a este proveedor cada poco tiempo (pej 5 minutos) cual
es nuestra ip pública. Este proveedor nos facilita de forma gratuita hasta 3
dominios. Cuando nosotros, o el que sea los visite, será redirigido a nuestra
ip pública actual automáticamente. Así ya no nos importa que esta haya
cambiado, porque el dominio siempre hará referencia a nuestra ip actual.
Lo primero de todo es que en tu red local la Rapsberry Pi tenga asignada una
dirección Ip Estática, que sea siempre la misma, tal como se supone que
hiciste si seguiste esta guía, esta concretamente en el paso 1.
Luego nos registraremos en la página de NO-IP y crearemos una cuenta (Sign
up).
En el menú de la izquierda, seleccionamos donde pone Dynamic DNS y creamos el
hostname que queramos.
Para este proyecto imagina que escogimos miproyecto.hopeto.org (este
dominio es ficticio, lo uso solo a modo de ejemplo, tu puedes escoger el
quieras que este disponible)
Clicamos en donde pone Dynamic Update Cliente y seleccionamos el sistema
operativo donde correrá el cliente de actualización de nuestra IP. En mi caso
como estoy desplegando el proyecto en una Rapsberry pi 4 escogeré la opción de
Linux. Hacemos click derecho en la pestaña "Download DUC" y seleccionamos la
opción "Copiar dirección de enlace".
Volvemos al terminal y teleamos wget y le damos a pegar para que nos pegue la
dirección del enlace de la descarga.
A la fecha de este tutorial la instrucción sería:
pi@raspberrypi:~ $ wget
https://www.noip.com/client/linux/noip-duc-linux.tar.gz
Nos habrá descargado el archivo noip-duc-linux.tar.gz. Ya solo nos
queda seguir las instrucciones de instalación.
- Extraemos el archivo comprimido con:
pi@raspberrypi:~ $ tar -zxvf noip-duc-linux.tar.gz
Ahora tenemos un directorio llamado noip-2.1.9-1. Entramos dentro con:
pi@raspberrypi:~ $ cd noip-2.1.9-1/
Una vez dentro procedemos a instalarlo con:
pi@raspberrypi:~/noip-2.1.9-1 $ sudo make
pi@raspberrypi:~/noip-2.1.9-1 $ sudo make install
Hecho esto, el instalador nos preguntará el usuario con el que nos hemos
registrado y la contraseña.
También nos preguntará con que intervalo actualizar nuestra Ip. Por defecto te
aparece 30 minutos pero es más practico ponerlo en 5. Con esto ya tenemos el
archivo de configuración creado.
Lo próximo es configurar el servicio del cliente de NO-IP para que arranque
automáticamente cuando lo haga la raspberry. Para ello creamos el siguiente
archivo:
pi@raspberrypi:~ $ sudo nano /etc/init.d/noip2
Dentro ponemos la siguiente instrucción:
sudo /usr/local/bin/noip2
Guardamos y cerramos el archivo.
Ahora añadimos permisos de ejecución con:
pi@raspberrypi:~ $ sudo chmod +x /etc/init.d/noip2
Modificamos el fichero update-rc con:
pi@raspberrypi:~ $ sudo update-rc.d noip2 defaults
Y para finalizar lo ponemos en marcha con:
pi@raspberrypi:~/noip-2.1.9-1 $ sudo /usr/local/bin/noip2
Ya tenemos configurado el cliente Dinamic DNS Free de NO-IP y hemos obtenido,
de forma gratuita, un dominio que apunta a nuetro servidor, que es nuestra
Rapsberry Pi. (para este ejemplo miproyecto.hopeto.org)
Ahora simplemente hemos de abrir los puertos necesarios en el router (el
puerto que hemos configurado en el hostname que hace una redirección Web.
Normalmente abriremos el puerto 80 en el router), y al teclear en un
navegador nuestro nombre de dominio, accederemos a nuestra Raspberry y a los
servicios activados a través de ese puerto. Este paso lo haremos casi final
del capítulo.
Paso 2: Crear un entorno virtual en Python e instalar la aplicación.
Aunque el paquete básico de Python ya esta instalado en Raspbian Pi Os, puede
que algunos paquetes adicionales no lo estén o no estén actualizados para lo
cual vamos a empezar con ello:
$ sudo apt-get -y update
$ sudo apt-get -y install python3 python3-venv python3-dev
A continuación crearemos un entorno virtual donde pondremos un programa Flask
que nos servirá de muestra. En Rapsberry Pi OS lo necesario para crear un
entorno virtual ya está instalado por defecto, pero si no lo tuvieras lo
puedes hacer con:
sudo apt install python3-venv
A continuación creamos el directorio que contendrá la aplicación, yo lo
llamaré "miproyecto".
$ mkdir ~/miproyecto
y entramos en el:
$ cd ~/miproyecto
Vamos a utilizar git para
descargar y usar el código del capítulo anterior desde mi repositorio de
GitHub. El problema es que no podemos usar directamente git
clone https://github.com/chema-hg/CURSO-DE-FLASK.git porque nos
descargaría todas las carpetas de todos los capítulos que ya hemos visto a
parte del que nos interesa.
Por ello para clonar solamente el directorio que nos interesa, que es el 'POST
19', vamos a seguir los siguientes pasos:
- Inicializamos el proyecto con:
~/miproyecto $ git init
- Procedemos a indicar el repositorio remoto en donde se encuentra el
proyecto:
~/miproyecto $ git remote add origin https://github.com/chema-hg/CURSO-DE-FLASK.git
- Habilitamos la configuración sparse-checkout con:
~/miproyecto $ git config core.sparsecheckout true
- Para clonar solamente un subdirectorio necesitamos definir que
archivo o carpeta es la que queremos clonar dentro de la estructura del
directorio (tenemos que teclear su nombre exactamente), lo cual hacemos de la
siguiente manera:
~/miproyecto $ echo /POST 19/ >> .git/info/sparse-checkout
- Terminamos haciendo un pull al repositorio:
~/miproyecto $ git pull --depth=1 origin master
Si todo va bien tendrás dentro de la carpeta "miproyecto" otra llamada "POST
19". Para evitar confusiones voy a copiar todos los archivos y carpetas de
"POST 19" a la carpeta "miproyecto" y luego borrare este directorio.
~/miproyecto $ cd 'POST 19'/
~/miproyecto/POST 19 $ mv ~/miproyecto/'POST 19'/* ~/miproyecto/
~/miproyecto/POST 19 $ cd ..
~/miproyecto $ rmdir 'POST 19'/
Bien ahora que ya tenemos todos los archivos y capetas necesarios creamos
nuestro entorno virtual, para lo cual tecleamos:
pi@raspberrypi:~/miproyecto $ python3 -m venv miEntorno
La vista del directorio debe ser la siguiente:
Antes de instalar las aplicaciones necesarias en el entorno virtual tenemos
que activarlo. Esto se consigue con el siguiente comando:
pi@raspberrypi:~/miproyecto $ source miEntorno/bin/activate
Verás algo como esto:
(miEntorno) pi@raspberrypi:~/miproyecto $
Lo que indica que ya estamos dentro del entorno virtual. El usuario que tengo
para todo el proyecto es el que viene por defecto, el usuario "pi".
Paso 3: Configurar una aplicación de Flask.
Ahora que estamos dentro del entorno virtual es el momento de instalar Flask,
todos los paquetes requeridos y Gunicorn y empezar a desarrollar la
aplicación.
Nota: Independientemente de la versión de Python que uses, cuando uses el
entorno virtual deberás usar el comando pip (no pip3)
Pero antes de empezar, tenemos que configurar nuestra aplicación de Flask para
que funcione en un entorno de producción. Para ello lo primero de todo es
instalar los paquetes requeridos por la aplicación. Dentro de nuestro entorno
virtual escribimos:
~/miproyecto $ pip install -r requirements.txt
Lo que hará que se instalen todos los paquetes necesarios que hemos usado
previamente para que el programa funcione que están en el archivo
requirements.txt de nuestro directorio. (en el capitulo correspondiente los
habíamos guardado usando $ pip freeze >
requirements.txt)
IMPORTANTE: Ten
en cuenta que ya estamos en un proceso de producción con lo que tendremos que
desactivar el debug de flask ya que sino lo hiciéramos
estaríamos permitiendo el ejecutar código arbitrariamente desde el navegador,
lo cual estarás conmigo en que no es una buena práctica.
Tendremos que modificar el archivo config.py, en el cual están las
configuraciones de nuestro programa. Entramos en el directorio "config" y allí
editamos el archivo config.py del siguiente modo:
# depurador off para modo producción o eliminamos la línea ya que por defecto
# el depurador esta desactivado.
DEBUG = False
Creación de un punto de entrada de WSGI
Cuando ejecutamos una aplicación de Flask estamos utilizando el servidor web
que viene con Flask. Este servidor, que nos viene de maravilla mientras
elaboramos la aplicación, no es una buena opción como servidor de producción
ya que no se creo para ese fin, en cuando a rapidez y solidez del mismo. Para
salir a la web se suele utilizar Gunicorn, que también es un servidor
web escrito en Python, pero que a diferencia del de Flask, es un servidor de
producción robusto que es utilizado por mucha gente y al mismo tiempo es muy
fácil de usar.
Instalamos Gunicorn (porque flask ya estaba instalado con los requirements)
(miEntorno) pi@raspberrypi:~/miproyecto $ pip install gunicorn
Ahora, crearemos también un archivo que servirá como punto de entrada para
la aplicación. Este archivo le va a indicar a nuestro servidor Gunicorn como
interactuar con la aplicación.
Podemos llamar al archivo como queramos. Yo lo llamara wsgi.py.
(miEntorno) pi@raspberrypi:~/miproyecto $ sudo nano wsgi.py
En él, importamos la instancia de Flask desde nuestra aplicación y luego la
ejecutaremos:
~/miproyecto/wsgi.py
Guarda y cierra el archivo.
La estructura del directorio tiene que ser la siguiente:
miproyecto
|_ inicio.py
|_ wsgi.py
|_ miEntorno/
Paso 4: Configurar Gunicorn
Antes de continuar tenemos que comprobar que Gunicorn puede proveer
correctamente la aplicación.
Podemos hacerlo pasándole el nombre de nuestro punto de entrada. Se hace con
el nombre del archivo que hace de punto de entrada a la aplicación (wsgi
menos la extensión py) más el nombre del elemento invocable dentro de la
aplicación principal (app en nuestro caso).
También especificaremos la interfaz y el puerto al que se vinculará para que
la aplicación se inicie de forma que este disponible públicamente.
Como al principio instalamos el firewall y ahora ya esta activado, tenemos
que abrir el puerto 8000 para que funcione la aplicación ya que es el que
voy a utilizar para comunicarme con gunicorn y con la pi desde mi
ordenador.
Lo haremos con la instrucción:
(miEntorno) pi@raspberrypi:~/miproyecto $ sudo ufw allow 8000
Ahora activamos el servidor con:
(miEntorno) pi@raspberrypi:~/miproyecto $ gunicorn --bind 0.0.0.0:8000
wsgi:app
o también mejor con:
(miEntorno) pi@raspberrypi:~/miproyecto $ gunicorn -b 0.0.0.0:8000 -w
4 wsgi:app
Deberías ver algo como esto:
[2021-04-28 10:14:09 +0200] [5866] [INFO] Starting gunicorn 20.1.0
[2021-04-28 10:14:09 +0200] [5866] [INFO] Listening at: http://0.0.0.0:8000 (5866)
[2021-04-28 10:14:09 +0200] [5866] [INFO] Using worker: sync
[2021-04-28 10:14:09 +0200] [5869] [INFO] Booting worker with pid: 5869
[2021-04-28 10:14:09 +0200] [5870] [INFO] Booting worker with pid: 5870
[2021-04-28 10:14:09 +0200] [5871] [INFO] Booting worker with pid: 5871
[2021-04-28 10:14:09 +0200] [5872] [INFO] Booting worker with pid: 5872
Visita la dirección IP de tu servidor con :8000 al final en tu navegador
web.
En mi caso:
http://192.168.1.200:8000
Deberías ver la página de entrada:
Cuando veas que funciona correctamente, pulsa Crtl-C en la ventana del
terminal para cerrar la aplicación.
Expliquemos un poco el comando.
La Opción -b le dice a gunicorn donde escuchar las solicitudes. Le he dicho
que escuche cualquier petición que se le haga en el puerto 8000. Por lo
general, sería una buena idea ejecutar aplicaciones web de Python solo con
acceso local y luego tener un servidor web muy rápido que esté optimizado para
servir archivos estáticos aceptando todas las solicitudes de los clientes.
Este servidor web si que servirá los archivos estáticos directamente aceptando
todas las solicitudes de los clientes. En este ejemplo para ver que todo
funciona correctamente la aplicación esta configurada con "0.0.0.0" aunque más
adelante la dejaremos como localhost.
La opción -w configura cuantos usuarios de forma simultánea atenderá gunicorn.
Tenerla configurada en 4 permite que la aplicación maneje hasta cuatro
clientes simultáneamente, lo que para una aplicación web suele ser suficiente
para manejar una cantidad decente de clientes, ya que no todos los que estén
conectados (que pueden ser más) están pidiendo contenido constantemente.
Dependiendo de la cantidad de ram de tu Raspberry Pi es posible que tengas que
ajustar este número para que no te quedes sin memoria.
El argumento wsgi:app le
dice a gunicorn de donde cargar la aplicación.
De ahora en adelante ya no necesitaremos más el entorno virtual con lo que
podemos desactivarlo.
(miEntorno) pi@raspberrypi:~/miproyecto $ deactivate
En adelante todos los comandos de Python usarán de nuevo el entorno de Python
del sistema.
Paso 5: Ejecutar automaticamente Gunicorn.
Si bien como has visto Gunicorn es muy fácil de configurar, ejecutarlo
manualmente desde una línea de comandos no parece una buena solución para un
servidor en producción. Lo que necesitamos es tener el servidor ejecutándose
en segundo plano y que se ejecute cada vez que se reinicie el
sistema.
A continuación voy a explicar dos opciones de hacerlo.
Ambas son excluyentes
es decir o utilizas una u la otra.
La primera me parece mucho más sencilla y funcional que la segunda porque
nos aporta más ventajas. Pero por ver formas distintas de hacer lo mismo
pondré las dos.
1) Opción.
Supervisor.
Lo último que hemos hecho es instalar Gunicorn y ver que funciona desde el
terminal. Vamos ahora a hacer que todo lo anterior este monitorizado
constantemente, porque si por alguna razón el servidor falla y se cierra,
quiero asegúrame de que un nuevo servidor se ejecute en su lugar. Y
también me quiero asegurar de que si la Raspberry Pi se reinicia, el
servidor se ejecuta automáticamente al iniciarse, sin que tenga que
iniciar sesión y teclear todos los comandos yo mismo.
Esto se consigue utilizando "Supervisor". Como dice
en su documentación
este programa es un sistema cliente servidor que permite monitorizar y
controlar procesos en sistemas basados en Unix.
Para empezar instalémoslo.
$ sudo apt-get -y install supervisor
En el archivo de configuración que utiliza supervisor tenemos que indicarle
que programas debe monitorizar y como reiniciarlos cuando sea necesario. Los
archivos de configuración deben guardarse en /etc/supervisor/conf.d. Abajo
te dejo el archivo de configuración que utilizaremos para nuestro programa,
al que llamaré miproyecto.conf
/etc/supervisor/conf.d/miproyecto.conf:
Configuración de Supervisor.
[program:miproyecto]
command=/home/pi/miproyecto/miEntorno/bin/gunicorn -b localhost:8000 -w 4
directory=/home/pi/miproyecto
user=pi
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
Los parámetros command,
directory y
user le dicen a supervisor
como debe ejecutar la aplicación.
Autostart y
autorestart sirven para que el
servidor se reinicie automáticamente si la Raspberry se reinicia o se bloquea.
Los parámetros stopasgroup y
killasgroup garantizan que
cuando "supervisor" necesite detener la aplicación para reiniciarla, tambien
llegue a los procesos secundarios de gunicorn de nivel superior.
Después de que escribas y guardes el archivo de configuración, debes volver a
cargar el servicio de supervisor para que se importe.
$ sudo supervisorctl reload
Y ya está, ¡el servicio web que nos proporciona gunicorn debería estar
funcionando y estar supervisado!
2) Opción.
Crea un archivo de unidad de servicio systemd
Un archivo en systemd te permitirá que al iniciar la Rapsberry Pi
automáticamente se ejecute Gunicorn y sirva nuestra aplicación Flask siempre
que se inicie el servidor. (pero OJO, no la monitoriza como en la opción
anterior)
Empezaremos por crear un archivo con extensión .service dentro del
directorio
/etc/systemd/system. Para que todo
sea acorde al proyecto se llamará miproyecto.service
$ sudo nano /etc/systemd/system/miproyecto.service
El archivo de forma genérica debe tener el siguiente contenido:
[Unit]# especifica metadatos y dependenciasDescription=Instancia de Gunicorn para mi proyecto
After=network.target# Le decimos al sistema de inicio que comience tras haberse conectado a una red.[Service]# Especificamos el usuario y el grupo con los cuales deseamos que se ejecute el proceso. # Le damos a nuestra cuenta habitual de usuario la propiedad del proceso, ya que posee todos los archivos relevantes.
User=tu_nombre_de_usuario# También otorgamos la propiedad del grupo al grupo www-data para que posteriormente Nginx (nuestro servidor) pueda comunicarse fácilmente con los procesos de Gunicorn.Group=www-data# A continuación especificamos los detalles del directorio de trabajo y estableceremos las variables de entorno en el PATH para que el sistema Init sepa que los ejecutables para el proceso, o sea nuestra aplicación, están ubicados dentro de nuestro entorno virtual.WorkingDirectory=/home/nombre_usuario/directorio_proyecto
Environment="PATH=/home/nombre_usuario/directorio_proyecto/entorno_virtual/bin"Luego le decimos cual es el comando para iniciar el servicioExecStart=/home/nombre_usuario/directorio_proyecto/entorno_virtual/bin/gunicorn --workers 4 --bind unix:miproyecto.sock -m 007 wsgi:app# Este comando hará lo siguiente:# Le decimos que inicie 4 procesos workers (esto se podría ajustar si es necesario)# Además especificamos una mascara 007 para que se cree el archivo de socket, se proporcione acceso al propietario y al mismo tiempo se restrinjan otros accesos.# Le especificamos el nombre del archivo del punto de acceso a WSGI junto con el elemento invocable de Python dentro de ese archivo (wsgi:app)# Si justo al final de toda la instrucción le añadimos el símbolo & ejecutaríamos el servidor en segundo plano.# Esto indicará a systemd a qué deberá vincular este servicio si lo habilitamos para que se cargue en el inicio. Queremos que este servicio se inicie cuando el sistema multiusuario normal esté en funcionamiento:[Install]
WantedBy=multi-user.target
En la práctica lo que debemos copiar a nuestro archivo
miproyecto.service es:
/etc/systemd/system/miproyecto.service
[Unit]
Description=Instancia de Gunicorn para mi proyecto
After=network.target
[Service]
User=
pi
Group=www-data
WorkingDirectory=/home/pi/miproyecto
Environment="PATH=/home/pi/miproyecto/miEntorno/bin"
ExecStart=/home/pi/miproyecto/miEntorno/bin/gunicorn --workers 4 --bind unix:miproyecto.sock -m 007 wsgi:app
[Install]
WantedBy=multi-user.target
Guardamos y cerramos el archivo.
Con esto ya lo tenemos completo y podemos iniciar el servicio Gunicorn y
activarlo para que se cargue al iniciar el sistema.
-
sudo systemctl start
miproyecto
-
sudo systemctl enable miproyecto
Y si comprobamos su estado con el siguiente comando:
sudo systemctl status miproyecto
Deberías ver el siguiente resultado:
Control + C para finalizar el proceso.
Además un nuevo archivo llamado miproyecto.sock se habrá creado en el
directorio de nuestro proyecto automáticamente.
La estructura del directorio quedaría como sigue:
miproyecto
|____ miproyecto.py
|____ wsgi.py
|____ miEntorno
|____ miproyecto.sock
En este punto si detectas algún error es mejor que intentes resolverlos antes
de continuar con el resto del tutorial.
Paso 6: Instalar y configurar Nginx para solicitudes de proxy.
Ahora que ya tenemos Gunicorn funcionando tenemos que configurar e instalar
Nginx para que trasmita las peticiones web al socket. Si has optado
anteriormente por la opción 1 el servidor de mi proyecto que está utilizando
gunicorn se esta ejecutando de forma privativa en el puerto 8000. Si por el
contrario has utilizado la opción 2 estará utilizando miproyecto.sock.
Lo que necesitamos hacer ahora para conectar la Pi al mundo exterior es
instalar Nginx, abrir los puertos del router en las direcciones 80 y 443, que
son los puertos que vamos a abrir en el firewall para manejar el tráfico web
de la aplicación.
Primeramente instalamos Nginx
pi@raspberrypi:~/miproyecto $ sudo apt-get install nginx
Antes de probar Nginx debemos saber que este programa, en relación con el
cortafuegos, se registra a si mismo como un servicio con ufw en el momento de
la instalación lo que permite el acceso al servicio.
Vemos como Nginx está en la lista de las aplicaciones con las que ufw sabe
trabajar en conjunto.
Teclea:
pi@raspberrypi:~ $ sudo ufw app list
Entre otras verás las siguientes aplicaciones:
Available applications:
Nginx Full
Nginx HTTP
Nginx HTTPS
OpenSSH
Como puedes ver hay tres perfiles disponibles para Nginx.
Nginx Full - Este perfil abre el puerto 80 (tráfico web normal sin cifrar) y
el puerto 443 (tráfico cifrado TLS/SSL)
Nginx HTTP - Este perfil solo abre el puerto 80 (el del trafico normal web sin
cifrar)
Nginx HTTPS - Este perfil solo abre el puerto 443 (tráfico cifrado TLS/SSL)
Luego, permitimos el acceso total al servidor de Nginx:
-
$ sudo ufw allow 'Nginx Full' o también (que es lo que he usado yo) podemos
abrir los puertos directamente con estas 2 instrucciones en vez de la
anterior : $ sudo ufw allow http $ sudo ufw allow 443/tcp
Con esto permitiremos el tráfico en los puertos 80 (http) y 443 (https)
Como queremos que nuestro proyecto tenga una implementación segura,
configuraremos el puerto 80 para que reenvíe todo el tráfico que le llegue al
puerto 443 (https) que se cifrará. Lo primero que tenemos que hacer es crear
un certificado SSL. Por ahora vamos a crear un certificado SSL autofirmado, de
andar por casa, que está bien para que probemos que todo funciona, pero que no
es bueno para una implementación real ya que los navegadores web te darán
mensajes de advertencia, porque el certificado no fue emitido por una
autoridad certificadora confiable.
El comando para crear el certificado SSL es el siguiente. Sitúate dentro del
directorio raíz miproyecto, si no lo estás y teclea:
/home/pi/miproyecto
$ mkdir certs
$ openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-keyout certs/key.pem -out certs/cert.pem
Cuando lo ejecutes te pedirá una seria de preguntas que te puedes saltar sobre
tu nombre y organización que son las que se mostrarán si los usuarios
solicitan ver el certificado en los navegadores web.
Además se crearán dos archivos dentro del directorio que hemos creado llamados
key.pen y cert.pen
Para tener un sitio web servido por nginx, este archivo debe estar en el
directorio /etc/nginx/sites-enabled. Nginx, en su instalación, crea un sitio
de prueba en esta dirección que no necesitamos asi que lo borraremos.
$ sudo rm /etc/nginx/sites-enabled/default
Ahora tenemos que enseñarle a Nginx donde esta nuestra app y como tiene
que servirla.
En el directorio /etc/nginx/ es
donde están localizados los archivos de configuración.
Los dos directorios con los que trabajaremos son sites-available y
sites-enabled
- sites-available contiene los archivos de configuración individual de
cada una de tus posibles aplicaciones o páginas web de carácter estático.
- sites-enabled contiene los enlaces a los archivos de configuración
que Nginx leerá y ejecutará.
Vamos a crear un nuevo archivo de configuración en el directorio de los sitios
disponibles de nginx llamado miproyecto, para que se adecue a la denominación
de los archivos del resto del tutorial.
$ sudo nano /etc/nginx/sites-available/miproyecto
A continuación puedes ver el archivo de configuración de nginx que vamos a
utilizar.
/etc/nginx/sites-available/miproyecto: Configuración de Nginx.
server {
# listen on port 80 (http)
listen 80;
server_name _;
location / {
# redirect any requests to the same URL but on https
return 301 https://$host$request_uri;
}
}
server {
# listen on port 443 (https)
listen 443 ssl;
server_name _;
# location of the self-signed SSL certificate
ssl_certificate /home/pi/miproyecto/certs/cert.pem;
ssl_certificate_key /home/pi/miproyecto/certs/key.pem;
# write access and error logs to /var/log
access_log /var/log/miproyecto_access.log;
error_log /var/log/miproyecto_error.log;
location / {
# forward application requests to the gunicorn server
proxy_pass http://127.0.0.1:8000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
# handle static files directly, without forwarding to the application
alias /home/pi/miproyecto/static;
expires 30d;
}
}
IMPORTANTE:
Este archivo de configuración esta preparado para usarse con la
opción 1 del punto anterior, si has usado la opción 2 es lo mismo
pero tienes que sustituir lo rojo, el proxy pass por este:
proxy_pass http://unix:/home/pi/miproyecto/miproyecto.sock;
Guarda y cierra el archivo al finalizar.
Por si quieres indagar más sobre la configuración de Nginx te dejo
este enlace de su
documentación oficial.
Para habilitar la configuración del bloque server que acabamos de crear,
tenemos que vincular el archivo al directorio sites-enabled:
$ sudo ln -s /etc/nginx/sites-available/miproyecto
/etc/nginx/sites-enabled
Podemos comprobar si hay errores tecleando:
pi@raspberrypi:~ $ sudo nginx -t
Si no hay ningún problema, reiniciamos el proceso Nginx para que tenga en
cuenta la nueva configuración.
pi@raspberrypi:~/miproyecto $ sudo systemctl restart nginx
o
pi@raspberrypi:~/miproyecto $ sudo service nginx reload
Y finalmente ya deberíamos tener corriendo la aplicación. En tu navegador web
puedes teclear la dirección IP de su servidor, (192.168.1.200 es la dirección
de red de mi Pi) y se conectará al servidor. Como estamos utilizando un
certificado autorfirmado verás una advertencia del navegador web
que deberás descartar para finalmente ver el resultado de todo el trabajo:
Ya solo nos queda acceder desde fuera de nuestra red. Vamos con ello.
Nota: si usamos la opcion 1, ajustamos el firewall de nuevo. Ya no
necesitamos el acceso a través del puerto 8000, por lo que podemos eliminar
esta regla.
-
sudo ufw delete allow 8000
- Apertura de Puertos.
Para que todo pueda funcionar correctamente necesitamos abrir el puerto 80 y
el 433 en nuestro router. ¿Por que? Pues porque cuando tecleas el nombre de
dominio en un navegador (pej miproyecto.hopeto.org) NO-IP redirige la
petición a nuestra IP-publica. Pero claro, el router como debe de ser, no
deja entrar a cualquiera a nuestro sistema. Por ello tenemos que decirle que
las peticiones web que reciba, las derive a la dirección ip de la Pi en
nuestra red interna (en el ejemplo 192.1.1.200) y a través del puerto 80 o
el 443, ya lo veremos.
Cada router es un mundo, así que tendrás que ver como se hace en el tuyo. En
el mío entras en la dirección del router, tecleas la contraseña de acceso y
entras en una sección que pone "puertos". Allí creas una nueva regla,
abriendo tanto para el puerto 80, como el 443 ya que posteriormente lo
necesitaremos para proteger nuestra aplicación usando HTTPS.
Guardamos y cerramos.
Probamos nuevamente que todo funciona correctamente accediendo al nombre del
dominio que escogiste en NO-IP (En mi caso miproyecto.hopeto.org) desde un
equipo que no este conectado a la red local (por ejemplo desde el móvil sin
que este conectado a la red local claro) y que apunta a nuestro servidor,
que es la Rapsberry, desde nuestro navegador.
Paso 7: Proteger la aplicación.
Para que la navegación hacia nuestro servidor sea seguro necesitamos obtener
un certificado SSL para nuestro dominio. Vamos a utilizar la forma más
sencilla y gratuita que es obtener un certificado gratuito de
Let´s encrypt. Según
la web de su página oficial let´s encrypt es una autoridad certificadora
gratuita, automatizada y abierta, destinada para el beneficio público
ofrecido por el Internet Security Research Group. Nos facilitan certificados
digitales para habilitar https completamente gratuitos.
Los certificados tienen una vigencia de tres meses aunque disponemos de un
sencillo comando en el crontab del sistema para automatizarlo.
Hasta ahora nuestro servidor, si recibe una petición HTTP para comunicarse
(con lo que los datos viajan en internet tal cual y son accesibles para
cualquiera que intercepte la comunicación), lo que hace es redirigirla a
través del protocolo HTTPS, con lo que conseguiremos asegurar nuestra
aplicación ya que los datos viajan de un modo seguro de un lado al otro ya
que están encriptados mediante un cifrado SSL
También hasta ahora, los certificados SSL los habíamos firmado nosotros
mismos. Vamos a ver como hacer para usar certificados "reales".
Cuando solicitamos un certificado a una autoridad certificadora, esta va a
verificar que nosotros somos los que tenemos el control tanto del servidor
como del dominio, pero el como se pasa esta verificación depende de cada
entidad. Si el servidor pasa esta verificación, la entidad nos emitirá un
certificado con su propia firma y nos lo dará para que lo instalemos. El
certificado será valido por un periodo de tiempo que generalmente no excede
del año. La mayoría de la entidades certificadoras cobran por prestar este
servicio, pero hay algunas que lo ofrecen de forma gratuita. La más popular
se llama Let´s Encryp.
Obtener un certificado de Let´s Encryp es muy sencillo ya que todo el
proceso ser realiza de forma automática.
Empezaremos instalando el paquete de Nginx para Certbot:
pi@raspberrypi:~ $ sudo apt install python-certbot-nginx
Podemos comprobar si está correctamente instalado y su versión con:
pi@raspberrypi:~ $ certbot --version
Como vamos a pedir los certificados para
miproyecto.hopeto.org tenemos que hacer 2 pequeñas modificaciones en
el archivo:
sudo nano /etc/nginx/sites-available/miproyecto
server {
# listen on port 80 (http)
listen 80;
server_name miproyecto.hopeto.org;
location / {
# redirect any requests to the same URL but on https
return 301 https://$host$request_uri;
}
}
server {
# listen on port 443 (https)
listen 443 ssl;
server_name miproyecto.hopeto.org;
# location of the self-signed SSL certificate
ssl_certificate /home/pi/miproyecto/certs/cert.pem;
ssl_certificate_key /home/pi/miproyecto/certs/key.pem;
# write access and error logs to /var/log
access_log /var/log/miproyecto_access.log;
error_log /var/log/miproyecto_error.log;
location / {
# forward application requests to the gunicorn server
proxy_pass http://127.0.0.1:8000;
# o proxy_pass http://unix:/home/pi/miproyecto/miproyecto.sock;
# si usas la opcion 2
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
# handle static files directly, without forwarding to the application
alias /home/pi/miproyecto/static;
expires 30d;
}
}
Ahora configuramos el entorno para que sea usado con Nginx. Podemos hacerlo
de dos maneras. La primera sería usando la instrucción $ sudo certbot
--nginx o indicando el nombre de dominio que tenemos directamente en la
línea de comandos. Yo usaré esta última:
pi@raspberrypi:~ $ sudo certbot --nginx -d miproyecto.hopeto.org
nota: sustituye miproyecto.hopeto.org por el nombre de tu dominio.
En el proceso de configuración lo primero que te pide es que introduzcas un
correo con el que puedan contactar contigo. Luego que aceptes las
condiciones del servicio (A), si quieres recibir información o no en ese
correo electrónico y a final nos dan dos opciones que son las que nos
interesan:
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
1) No redirigir. Es decir puedes tener los dos protocolos de manera
independiente. Es decir vas a poder acceder tecleando en el navegador
http://miproyecto.hopto.org o https://miproyecto.hopto.org
2) Redirigidas. Es decir que las peticiones que se realicen a través
de http://miproyecto.hopto.org sean automáticamente redirigidas al protocolo
HTTPS.
Yo escogeré está opción (la número 1)
Si te acuerdas, cuando hicimos nuestras claves autofirmadas, especificamos
donde estaban en el archivo /etc/nginx/sites-available/miproyecto dentro de las claves:
# location of the self-signed SSL certificate
ssl_certificate /home/pi/miproyecto/certs/cert.pem;
ssl_certificate_key /home/pi/miproyecto/certs/key.pem;
Al terminar de ejecutar certbot nos saldrá donde ha guardado let´s
encrypt los certificados:
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/miproyecto.hopeto.org/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/myproyecto.hopeto.org/privkey.pem
Aunque deberíamos cambiar a mano nuestros certificados autofirmados por
estos últimos, estamos de enhorabuena porque el programa de instalación
de certbot ya lo ha hecho por nosotros.
/etc/nginx/sites-available/miproyecto: Nginx configuracion de claves let´s encrypt.
...
# location of the self-signed SSL certificate
ssl_certificate /etc/letsencrypt/live/miproyecto.hopto.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/miproyecto.hopto.org/privkey.pem; # managed by Certbot
...
Si ahora visitas la página veras como arriba a la izquierda en la barra de
direcciones te aparece el candado. ¡Éxito!
Renovación de certificados.
Como los certificados tienen una vigencia de tres meses podemos hacer
la renovación de los mismos de forma manual o automática.
Para comprobar los certificados de nuestro sistema podemos usar el comando:
pi@raspberrypi:~ $ sudo certbot certificates
La salida de este comando es información sobre el certificado entre la cual
está la fecha en la que expira.
La renovación podemos efectuarla:
De forma manual para todos los certificados del sistema.
Si estamos a menos de una semana para que expire el certificado, este estará
disponible para renovar, y podremos hacerlo con el siguiente comando:
Si estamos fuera de ese plazo no hará nada.
De forma automática.
Si queremos que la renovación se realice de forma automática podemos
programar el servidor para que realice esta tarea a través del crontab del
propio sistema.
Por ejemplo que todos los días se intente la renovación a las 3 y 15 de la
madrugada.
$ crontab -e
15 3 * * * /usr/bin/certbot renew --renew-hook "service nginx reload"
Como el proyecto ya está finalizado y en funcionamiento si queremos ver el
grado de seguridad de nuestro servidor podemos visitar el sitio de
Qualys SSL LABS donde podemos obtener un informe sobre la seguridad de nuestro servidor. Lo
más probable es que nos diga que nos queden algunas cosas menores por pulir.
Por ejemplo en mis caso me daba una nota de "B" porque desde 31-01-2020 a
los servidores que soportan los protocolos TSL 1.0 o TSL 1.1 automáticamente
se les asigna la nota "B" y además por defecto la instalación de nginx no
tiene activado el protocolo TSL 1.3.
Para arreglar esto vas a el archivo de configuración de nginx que está
en /etc/nginx/nginx.conf quitamos del parámetro ssl_protocols las versiones TSL 1 y añadimos TSL
1.3 dejándolo de la siguiente forma:
(También lo podemos poner en el archivo de configuración de nuestro
proyecto paa nginx
/etc/nginx/sites-available/miproyecto)
ssl_protocols TLSv1.2 TLSv1.3;
Si ahora pasamos el test a nuestra página, casi está perfecta.
Tambien puedes comprobar y obtener más recomendaciones de seguridad de
tu servidor en la página de
mozilla observatory.
Otra de las áreas en que podemos mejorar es cómo se generan los coeficientes
que se utilizan durante el intercambio de las claves de cifrado, que suelen
tener unos valores predeterminados bastante débiles. En particular los
coeficientes
Diffie-Hellman tardan una cantidad considerable de tiempo en generarse, por lo que
los servidores utilizan números más pequeños de forma predeterminada para
ahorrar tiempo. Sin entrar en detalle, el intercambio de claves
Diffie-Hellman tiene una propiedad interesante que es que aún en el caso de
que un atacante obtuviese la clave privada de nuestro servidor, le sería muy
difícil romper la comunicación entre nuestro servidor y sus clientes. Sin
embargo el tamaño de la clave predeterminada en OpenSSL es de 1024 bits, con
lo que en teoría con la ayuda de un supercomputador se podría romper.
Por suerte podemos pre-generar coeficientes más fuertes y guardarlos en un
archivo que luego Nginx podrá usar. Eso si, ármate de paciencia porque, si
quieres como yo establecer el DH en 4096 bits vas a tener que esperar mucho
tiempo ya que hace falta un uso intensivo de la CPU que la Raspberry no
tiene. Para que te hagas una idea en mi raspberry pi 4 ha tardado
6 horas
en generarse.
Lo primero que haremos será generar nuestro coeficiente Diffie-Hellman de
4096 bits.
cd /etc/ssl/certs
sudo openssl dhparam -out dhparam.pem 4096
* Si quieres que tarde menos, usa
2048
en lugar de
4096
que también está muy bien.
Cuando haya terminado tendremos un archivo llamado
dhparam.pem
Luego añadiremos o modificaremos la siguiente línea en el archivo de
configuración de Nginx dentro de las opciones del server:
ssl_dhparam /etc/ssl/certs/dhparam.pem;
/etc/nginx/sites-available/miproyecto: archivo de configuración de Nginx.
server {
listen 443 ssl;
server_name miproyecto.hopeto.org;
....
ssl_dhparam /etc/ssl/certs/dhparam.pem;
...
Guardamos, salimos y reiniciamos el servidor.
Si ya queremos conseguir el A+ probablemente tendremos que especificar que
tipo de cifrados permite nuestro servidor. La organización Mozilla nos
facilita un
generador de configuraciones SSL
que introduciendo el tipo de servidor (nginx en nuestro caso), la versión
del mismo y la versión de OpenSSL nos facilita un estupendo archivo de
configuración.
Nota: para saber la versión de nginx utiliza: $ nginx -v y para saber la versión de OpenSSL utiliza: $ openssl
version
Esta es entrada que yo tengo a fecha de hoy en mi archivo de
configuración:
/etc/nginx/sites-available/miproyecto: archivo de configuración de Nginx.
server {
listen 443 ssl;
server_name miproyecto.hopeto.org;
....
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
Y con esto ya he conseguido una nota de
A+ en Qualys SLL LABS.
No obstante si te interesa la seguridad puedes entrar en la página de
Mozilla Observatory que te indique antes e ir corrigiendo todos los fallos
que te diga que con seguridad serán unos cuantos. Puedes encontrar ayuda en
esta página.