Aprovisionar un servidor en la infraestructura cloud de Clouding con Ubuntu y Nginx usando Ansible y protocolo seguro HTTPS con Let's Encrypt

Escrito por el .
gnu-linux planeta-codigo patrocinado software-libre
Enlace permanente Comentarios

La computación cloud permite crear un servidor con unos pocos clics, en unos pocos minutos, con la posibilidad de ajustar a las necesidades su capacidad en unidades de cómputo, memoria y almacenamiento y facturado solo por los recursos consumidos por unidad de tiempo y con la flexibilidad de ampliar o reducir la capacidad del servidor cloud en cualquier momento. Si con la computación cloud ya es muy sencillo y rápido disponer de un servidor la mayor dificultad está en aprovisionarlo para que ofrezca el servicio que se desea, para facilitar el aprovisionamiento hay herramientas como Ansible diseñadas con este objetivo y tratar la infraestructura como código. En este artículo muestro como crear y aprovisionar un servidor cloud en la infraestructura cloud de Clouding para un servidor web con Ubuntu y Nginx aprovisionado con Ansible y configurado con el protocolo seguro HTTPS con certificados generados por Let’s Encrypt.

Clouding.io

Para ofrecer un servicio a través de internet es indispensable un servidor que por motivos de fiabilidad entre otros es recomendable contratar a un proveedor de infraestructura. La tendencia es usar servidores cloud con las ventajas de solicitud de creación de los servidores mediante autoservicio con unos pocos clics, de forma rápida en unos pocos minutos y la posibilidad de ampliarlo, o reducirlo, en potencia cómputo, memoria del sistema o almacenamiento, además de una facturación por uso.

La parte de tener un servidor accesible en internet para proporcionar un servicio ya se consigue en unos pocos clics, no hace falta presupuestarlo, ni comprar hardware y pagar su precio de forma completa por adelantado, ni recibirlo y montarlo, ni alojarlo en infraestructura propia. La parte más difícil de un servidor es aprovisionarlo y configurarlo para el propósito que se desee y para facilitar esta tarea hay herramientas de software específicas.

Infraestructura cloud de Clouding

Uno de los proveedores de infraestructura cloud más reconocidos con sede en España es Clouding, con un servicio de grado empresarial que ofrece un servicio de IaaS con oficinas y centro de datos en Barcelona. Como proveedor de computación cloud ofrece las varias importantes ventajas de este modelo de alojar servicios entre los que están disponer de un servidor en unos pocos clics y en pocos minutos, gran flexibilidad en la selección para configuración del servidor en capacidad de cómputo, memoria y almacenamiento permitiendo ajustar el precio a las necesidad del servicio, flexibilidad para cambiar ya sea ampliar o reducir la configuración del servidor y facturación según uso por unidad de tiempo en horas.

Una de las ventajas determinante y diferenciadora de Clouding sobre otros proveedores es que al tener sede en España ofrece soporte de asistencia en caso de necesidad por personas en España. No es habitual requerir de esta asistencia pero en caso de necesitarla por el impacto que tiene si el servicio es de gran importancia para una empresa o persona si de este depende su facturación, clientes o sus proveedores. La asistencia es una característica determinante para la toma de una decisión al elegir un proveedor para en caso de necesidad restaurar el servicio.

Al crear una cuenta en Clouding ofrecen un cupón de 5 € para probar su servicio gratis, este saldo permite crear una instancia de servidor cloud con un tiempo de funcionamiento ininterrumpido de unos 45 días en la configuración más básica.

Selección de capacidad de los servidores

Clouding permite seleccionar los recursos de cómputo para los servidores según las necesidades partiendo de la opción más básica de 1/2 unidades de cómputo, 2 GiB por core y 5 GiB de almacenamiento con la opción de elegir Linux o Windows como sistema operativo y dentro de Linux con varias distribuciones y versiones como Ubuntu, Centos o Debian. Esta opción más básica que ya es apta para algunos servicios parte de los 3 € al mes, la opción más capaz llega a las 48 unidades de cómputo o cores, 192 GiB de memoria y casi 2 TiB de almacenamiento suficiente incluso para las necesidades empresariales más exigentes con un precio de 550 € al mes. Y entre la opción más básica y la más capaz la posibilidad de elegir individualmente cada uno de los tres parámetros principales de configuración como unidades de cómputo, memoria y almacenamiento gracias a la computación cloud y una ventaja de los servidores virtuales privados de la generación anterior que no tenían una configuración tan flexible.

Selección de los recursos de cómputo y coste

Selección de los recursos de cómputo y coste

Infraestructura

Clouding ofrece una infraestructura de alta disponibilidad, con servicios de copias de seguridad para preservar datos y restauración, toma de instantáneas como medida de seguridad y recuperación, archivado de servidores para ahorrar costes, redimensionado de servidores gracias a la computación cloud y configuración de red privada para mayor seguridad. En el panel de administración de los servidores también se ofrecen detalles para la monitorización y observabilidad con los que comprobar el buen estado de funcionamiento del servidor.

Características de Clouding Características de Clouding Características de Clouding

Características de Clouding Características de Clouding Características de Clouding

Características de Clouding

Para garantizar un buen servicio utilizan una infraestructura moderna y de alto rendimiento, redundante y tolerante a fallos compuesta por hardware, software, red, imágenes y centros de datos. Almacenamiento con discos SSD NVMe de alta velocidad, RAM con corrección de errores ECC, consola de emergencia y monitorización, red de alto rendimiento baja velocidad con protecciones frente ataques DDOS y con dirección IP pública, imágenes para servidores basados en Linux o Windows, paneles de control y aplicaciones preinstaladas, finalmente sus centros de datos ubicados en España son redundantes y con energia 100% renovable.

Infraestructura de Clouding Infraestructura de Clouding Infraestructura de Clouding

Infraestructura de Clouding Infraestructura de Clouding

Infraestructura de Clouding

Son muchas las empresas que confían en Clouding como proveedor de servicios para sus necesidades tecnológicas. Dado el tamaño de estas empresas es garantía de que el servicio de Clouding está a la altura para tenerlos como clientes.

Clientes de Clouding

Clientes de Clouding

Crear un servidor cloud en Clouding

Empezar a usar Clouding es sencillo y rápido, necesitando únicamente crear una cuenta en el servicio y añadir algo de saldo a través de las formas de pago que se ofrecen como pago con tarjeta, domiciliación bancaria o cuenta de PayPal. Dispone de notificaciones de saldo bajo y autorecarga para evitar supervisar el saldo y que un servicio no deje de funcionar por motivos de facturación. Informes con el detalle del coste usado en un periodo de tiempo y en el apartado Cuenta la posibilidad de configurar la muy útil y recomendable medida de seguridad del segundo factor de autenticación.

El primer paso para crear un servidor es crear una llave SSH con la que posteriormente acceder al servidor por línea de comandos, la llave SSH se puede descargar para configurarla en el cliente SSH local. Una vez creada ya es posible crear el servidor donde entre otras se seleccionan las características del mismo como sistema operativo y versión, cantidad de unidades de cómputo, cantidad de memoria y cantidad de almacenamiento, teniendo en cuenta que los tres últimos se pueden ampliar o reducir con posterioridad.

Selección de características para crear instancia de servidor cloud Selección de características para crear instancia de servidor cloud Selección de características para crear instancia de servidor cloud

Selección de características para crear instancia de servidor cloud

Selección de características para crear instancia de servidor cloud

Después de confirmar las características e iniciar la creación del servidor se inicializa y está disponible al cabo de unos pocos segundos o pocos minutos proporcionado entre sus detalles la dirección IP pública que le ha sido asignada y necesaria para la conexión y la configuración DNS del nombre de dominio en el proveedor externo de registro de dominio o en el panel de DNS de Clouding si se configura como servidor administrativo autorizado para el dominio.

Progreso de creación de servidor cloud

Progreso de creación de la servidor cloud, estadísticas y claves SSH

Estadisticas de estado para monitorización y observabilidad Claves SSH para los servidores

Progreso de creación de la servidor cloud, estadísticas y claves SSH

Conexión desde línea de comandos con SSH

Para la conexión al servidor por línea de comandos se utiliza el protocolo seguro SSH, para ello en GNU/Linux como cliente se usa OpenSSH y en Windows una posibilidad es PuTTY. La clave privada es una clave privada RSA en formato pem que para usar con OpenSSH si se desea usar una propia primero hay que convertirla a formato ssh-rsa con el siguiente comando y aprovisionar la clave pública en forma ssh-rsa en el servidor. Para usar la llave hay que configurar el archivo .ssh/config con la dirección IP pública del servidor. Como Clouding ya se encarga de tanto generar la llave privada como de aprovisionarla en el servidor la conversión y aprovisionamiento no es imprescindible.

1
2
$ chmod 0600 clouding.pem
$ ssh-keygen -y -f clouding.pem > clouding.pub
ssh-key-conversion.sh
1
2
Host 27.0.174.19
    IdentityFile ~/.ssh/clouding.pem
ssh-config
1
2
$ ssh ubuntu@27.0.174.19

ssh.sh

Conexión SSH a instancia de servidor cloud

Conexión SSH a instancia de servidor cloud

Cómo aprovisionar un servidor cloud

Una vez la conexión al servidor por línea de comandos funciona ya es posible configurar el servidor, no se diferencia en ningún aspecto a configurar un servidor por linea de comandos según el sistema operativo elegido. El aprovisionamiento del servidor y configuración consiste básicamente en la instalación de paquetes, configuración de servicios editando archivos de configuración y reinicio de servicios para que la configuración modificada tome efecto. Los paquetes a instalar dependen del propósito o propósitos del servicio para el servidor puede ser un servidor web con Nginx o Apache HTTPD, un servidor de base de datos con PostgreSQL o MySQL, un servidor de documentos personales con Nextcloud o servidor de archivos entre otras muchas otras funcionalidades.

Aunque es posible configurar un servidor introduciendo los comandos uno a uno es tedioso además de una tarea repetitiva en caso de tener que aplicarlos en varios servidores, no queda automatizado ni los comandos están bajo un sistema de control de versiones en el que queden descritos los comandos de configuración. Hay herramientas de software con el objetivo de aprovisionar servidores que permiten realizar el aprovisionamiento de un servidor de forma automatizada con la ventaja de ser más rápido, reproducible y la posibilidad de utilizar un sistema de control de versiones para los scripts de aprovisionamiento con sus propias ventajas como historial de cambios y colaboración entre personas en su edición. Una herramienta de aprovisionamiento muy conocida es Ansible, en esencia esta herramienta ejecuta los comandos en el servidor, permite hacerlo de forma remota con únicamente una conexión SSH sin necesidad de instalar software de servidor y la posibilidad de aplicar los cambios a un grupo completo de servidores. La herramienta de aprovisionamiento y configuración Ansible entre dentro del grupo de herramientas para tratar a la infraestructura como código.

En un primer momento para desarrollar los scripts de aprovisionamiento es posible crear una máquina virtual en local y tratarla como si de un servidor se tratase. Vagrant permite crear máquinas virtuales de forma automatizada con una de sus posibilidades crear máquinas virtuales en VirtualBox. Una vez que el script de aprovisionamiento funciona ya es posible lanzarlo contra el servidor cloud.

Ejemplo de aprovisionamiento de un servidor cloud de Clouding con Ansible

En este ejemplo muestro como aprovisionar un servidor cloud de Clouding de forma automatizada con la herramienta Ansible. El servidor cloud creado en el paso anterior tiene el sistema operativo Ubuntu en la versión LTS 20.04, la funcionalidad del servidor es la de un servidor web con Nginx instalado como paquete de software de Ubuntu por más sencillez para el ejemplo que instalarlo con Docker, para que el servidor utilice el protocolo HTTPS es necesario un certificado que con el servicio de Let’s Encrypt permite obtenerlo de forma rápida y automatizada.

Let’s Encrypt ofrece la herramienta certbot y siguiendo sus instrucciones para Ubuntu permite obtener el certificado que posteriormente hay que usar en la configuración de Nginx al configurar el protocolo HTTPS y para que los navegadores clientes validen correctamente el dominio del servidor. Por motivos de seguridad usar el protocolo HTTPS es un requerimiento indispensable sin embargo es necesario un certificado que identifique el dominio usado en el servidor que en algunos proveedores tiene un coste significativo de unos 100 € o más y se tarda unos días en obtenerlo. Let’s Encrypt permite obtener un certificado para el dominio en unos pocos segundos de forma automatizada y completamente gratuito ni ningún coste.

Además, de la configuración básica mostrada en este artículo es posible configurar otras opciones en Nginx para variar su comportamiento, de las que he escrito en otros artículos con la etiqueta web.

Organización de roles en Ansible

Ansible define unas convenciones para los nombres de archivos necesarios, estructura de directorios y formato para los archivos. El archivo hosts define el inventario de máquinas a las que Ansible puede conectarse y las credenciales de conexión además de poder definir variables asociadas a las máquinas y definir grupos de máquinas según un rol como servidor web o servidor de base de datos si hay varias instancias con ese rol, tiene un formato INI.

Otra de las convenciones de Ansible y una forma de organizar las tareas de configuración son lo que se denomina como roles que contienen además de las tareas a aplicar a las máquinas que utilicen ese rol de Ansible, los archivos de configuración, plantillas y disparadores o handlers a ejecutar como reacción a la ejecución de alguna tarea. Finalmente, están los playbooks que son las recetas a ejecutar que indican que roles se aplican a una instancia o grupo de instancias del inventario. Dentro de un rol los archivos se organizan en varias carpetas.

  • tasks: contiene la definición de las tareas que se realizan al aplicar el rol. Permiten asociales etiquetas para filtrar su ejecución, añadirles condiciones de ejecución realizar la acción sobre una lista de elementos. Ansible posee una amplia colección de tareas para cualquier acción desde ejecución de comandos como instalación y actualización de paquetes hasta funcionalidades para trabajar con Docker. Las tareas se definen en archivos en formato YAML con la ventaja a diferencia de scripts de Bash ser independientes de la distribución.
  • handlers: contiene la definición de tareas que se ejecutan como consecuencia de la ejecución de otras tareas en puntos concretos de las tareas normales del rol.
  • files: son archivos estáticos que permiten aprovisionarlos en las instancias de las máquinas, pueden ser cualquier tipo de archivo como archivos de configuración de un servicio o recursos estáticos para el caso de un servidor web.
  • templates: son archivos que al procesarse junto variables en tiempo de ejecución producen como resultado un archivo estático. Aunque el resultado final es un archivo estático este se genera dinámicamente.

Para empezar a aprender está la documentación oficial con la guía de usuario de Ansible junto con los conceptos de introducción a los playbooks, el inventario, tareas, roles y los módulos además de otros capítulos de su amplia tabla de contenidos.

Tengo que decir que no soy un experto en Ansible y es muy posible que parte de la configuración de Ansible mostrada en este artículo sea posible mejorarla para hacerla más reutilizable los roles y sus tareas o estructurarlos algo mejor. Esta es la estructura completa de archivos y directorios del ejemplo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$ tree .
.
├── ansible-clouding-content-update.sh
├── ansible-clouding-ping.sh
├── ansible-clouding-system-init.sh
├── ansible-clouding-system-install.sh
├── ansible-clouding-system-update.sh
├── ansible-env.conf
├── ansible-env-default.conf
├── ansible-mkdpasswd.sh
├── ansible-raspberrypi-ping.sh
├── ansible-raspberrypi-system-init.sh
├── ansible-raspberrypi-system-install.sh
├── ansible-raspberrypi-system-update.sh
├── ansible-raspberrypi-update-content.sh
├── ansible-vagrant-content-update.sh
├── ansible-vagrant-ping.sh
├── ansible-vagrant-system-init.sh
├── ansible-vagrant-system-install.sh
├── ansible-vagrant-system-update.sh
├── hosts
├── roles
│   ├── picodotdev.certbot
│   │   └── tasks
│   │       ├── configure.yml
│   │       └── main.yml
│   ├── picodotdev.goaccess
│   │   └── tasks
│   │       └── main.yml
│   ├── picodotdev.nginx
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── main.yml
│   │   │   └── restart.yml
│   │   └── templates
│   │       └── https-domain-template.conf
│   ├── picodotdev.site
│   │   ├── files
│   │   │   └── index.html
│   │   └── tasks
│   │       ├── main.yml
│   │       └── nginx-content.yml
│   ├── picodotdev.system
│   │   └── tasks
│   │       ├── install.yml
│   │       ├── main.yml
│   │       ├── ssh.yml
│   │       ├── update.yml
│   │       └── users.yml
│   └── picodotdev.ufw
│       └── tasks
│           └── main.yml
├── site-content-update.yml
├── site-system-init.yml
├── site-system-install.yml
└── site-system-update.yml

16 directories, 39 files
ansible-role-structure.txt

El contenido del archivo host para el servidor cloud de Clouding es el siguiente, que incluye la dirección IP pública proporcionada al crearlo y visible en los detalles del servidor y la llave privada que Ansible utiliza en su conexión por SSH al servidor y a través de las cuales ejecuta los comandos de forma remota. En el ejemplo se incluyen los datos para la conexión para pruebas en un entorno con Vagrant en una máquina virtual local y una Raspberry Pi, además de la instancia en la infraestructura de Clouding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[vagrant]
192.168.56.10

[raspberrypi]
192.168.1.101

[clouding]
27.0.174.19

[vagrant:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/pico.dev@gmail.com

[raspberrypi:vars]
ansible_user=pi
ansible_ssh_private_key_file=~/.ssh/pico.dev@gmail.com

[clouding:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/clouding.pem
hosts

Los roles de Ansible permiten agrupar las tareas por funcionalidad a aplicar a un servidor, unos roles quizá sean comunes para cualquier servidor como como el rol de picodotdev.system que realiza unas configuraciones básica en el sistema como instalar paquetes comunes, crear usuarios y configuración SSH y actualizan los paquetes, Otros roles son los siguientes.

  • picodotdev.system: configuración común del servidor independiente de su servicio.
  • picodotdev.ufw: tareas para configurar el cortafuegos UFW para limitar puertos abiertos.
  • picodotdev.nginx: tareas para configurar una máquina con la función de servidor web Nginx.
  • picodotdev.site: tareas para configurar un sitio web en el servidor de Nginx.
  • picodotdev.certbot: tareas para utilizar certbot y generar los certificados de Let’s Encrypt para configurar en Nginx.
  • picodotdev.goaccess: GoAccess es una herramienta para analizar los logs de Nginx.

El primer paso es comprobar que Ansible puede establecer correctamente la conexión con el servidor para ello hay un módulo que hace la función. Si el comando funciona Ansible es capaz de lanzar el resto de tareas administrativas definidas en los roles a esa instancia del inventario.

1
2
#!/usr/bin/env bash
ansible -i hosts -m ping clouding
ansible/ansible-clouding-ping.sh
1
2
3
4
5
6
7
27.0.174.19 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
ansible-ping.out

Estos son los archivos del rol picodotdev.system y picodotdev.ufw de Ansible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
---
- name: Create users
  include_tasks:
    file: users.yml
    apply:
      tags:
        - system-init
        - system-install
  tags:
    - system-init
    - system-install
- name: Configure SSH
  include_tasks:
    file: ssh.yml
    apply:
      tags:
        - system-init
        - system-install
  tags:
    - system-init
    - system-install
- name: Update system
  include_tasks:
    file: update.yml
    apply:
      tags:
        - system-install
        - system-update
  tags:
    - system-install
    - system-update
- name: Install system
  include_tasks:
    file: install.yml
    apply:
      tags:
        - system-install
  tags:
    - system-install
ansible/picodotdev.system/tasks/main.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- name: Install packages
  package:
    name: "{{ item }}"
    state: present
  become: true
  with_items:
    - python3
  tags:
    - system-install
ansible/picodotdev.system/tasks/install.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---
- name: Set authorized keys
  authorized_key:
    user: ubuntu
    state: present
    key: "{{ lookup('file', item) }}"
  become: true
  with_items:
    - "{{ lookup('env', 'SSH_KEY_PUB') }}"
  tags:
    - system-install
- name: Disable SSH password authentication
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#PasswordAuthentication yes'
    line: 'PasswordAuthentication no'
  become: true
  tags:
    - system-install
ansible/picodotdev.system/tasks/ssh.yml
1
2
3
4
5
6
7
8
9
---
- name: Update system
  apt:
    upgrade: full
    update_cache: yes
  become: true
  tags:
    - system-install
    - system-update
ansible/picodotdev.system/tasks/update.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- name: Create users
  user:
    name: "{{ item }}"
    groups: "sudo"
    state: present
  become: true
  with_items:
    - ubuntu
  tags: system-install
ansible/picodotdev.system/tasks/users.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---
- name: Install packages
  package:
    name: "{{ item }}"
    state: present
  become: true
  with_items:
    - ufw
  tags: system-install
- name: Configure ufw
  ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  become: true
  with_items: "{{ ports }}"
  tags: system-install
- name: Enable ufw
  ufw:
    state: enabled
    default: deny
  become: true
  tags: system-install
ansible/picodotdev.ufw/tasks/main.yml

Comandos de ejecución

Para configurar el servidor en el ejemplo la aplicación de los roles están divididas en cuatro playbooks con sus cuatro archivos bash para su ejecución.

  • system-init: aplica las tareas del rol picodotdev.system.
  • system-install: aplica las tareas del rol picodotdev.site.
  • system-update: aplica las tareas del rol picodotdev.site etiquetadas con system-update que permite filtrar las tareas del rol a ejecutar omitiendo el resto que no tienen esa etiqueta.
  • content-update: aplica las tareas del rol picodotdev.site etiquetadas con content-update.

Estos son la definición de los playbooks y los comandos de ejecución en los que se especifican las etiquetas de las tareas a ejecutar del playbook y roles.

1
2
3
#!/usr/bin/env bash
source ansible-env.conf
ansible-playbook -i hosts -l clouding --tags system-init --extra-vars "ansible_user=root ansible_ssh_private_key_file=~/.ssh/clouding.pem" site-system-init.yml
ansible/ansible-clouding-system-init.sh
1
2
3
#!/usr/bin/env bash
source ansible-env.conf
ansible-playbook -i hosts -l clouding --tags system-install site-system-install.yml
ansible/ansible-clouding-system-install.sh
1
2
3
#!/usr/bin/env bash
source ansible-env.conf
ansible-playbook -i hosts -l clouding --tags system-update site-system-update.yml
ansible/ansible-clouding-system-update.sh
1
2
3
#!/usr/bin/env bash
source ansible-env.conf
ansible-playbook -i hosts -l clouding --tags content-update site-content-update.yml
ansible/ansible-clouding-content-update.sh
1
2
3
4
---
- hosts: all
  roles:
    - picodotdev.system
ansible/site-system-init.yml
1
2
3
4
---
- hosts: all
  roles:
    - picodotdev.site
ansible/site-system-install.yml
1
2
3
4
---
- hosts: all
  roles:
    - picodotdev.site
ansible/site-system-update.yml
1
2
3
4
---
- hosts: all
  roles:
    - picodotdev.site
ansible/site-content-update.yml

Además en el archivo ansible-env.conf se definen algunos datos como variables de entorno que probablemente podrían definirse también como alternativa en el archivo hosts como variables del inventario.

1
2
3
4
5
export UBUNTU_PASSWORD="$6$G7g9dNWz$H4FDdICBYP9oo0ILOAQEQnTbqXXuUFd0iuEbC4PQr3TenRHUaiDqcbu01fPQPk4tPuIbG1LFIk0JPhpey29Xo/"
export SSH_KEY_PUB="~/Documentos/clouding.pub"
export DOMAIN_DEFAULT="default"
export DOMAIN_SITE="www.27.0.174.19.sslip.io"

ansible/ansible-env.conf

Certificado de seguridad con Let’s Encrypt en servidor web Nginx

Para generar el certificado para el servidor web con Let’s Encrypt está la herramienta certbot disponible para Nginx y usando Ubuntu a través del paquete en formato de aplicación snap. El rol picodotdev.certbot contiene las tareas para realizar la configuración de Nginx una vez este está al menos configurado e iniciado para funcionar con el protocolo HTTP.

Para validar un dominio y comprobar que el propietario del sitio web es el dueño del dominio Let’s Encrypt con la ayuda de certbot genera unos archivos en la raíz de documentos del sitio en la ubicación /.well-known/acme-challenge/, el proceso de validación de Let’s Encrypt accede al sitio web usando el dominio con el protocolo HTTP y si encuentra los archivos que espera valida el dominio y genera los certificados que certbot instala en el directorio /etc/letsencrypt/live/{{ domain }}/fullchain.pem.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
- name: Install packages
  package:
    name: "{{ item }}"
    state: present
  become: true
  with_items:
    - snapd
  tags:
    - system-install
- name: Install snap base packages
  community.general.snap:
    name:
      - core
      - hello-world
    state: present
  become: true
  tags:
    - system-install
- name: Install snap packages
  community.general.snap:
    name:
      - certbot
    state: present
    classic: yes
  become: true
  tags:
    - system-install
ansible/picodotdev.certbot/tasks/main.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
- name: Configure certbot
  shell: |
    ln -sf /snap/bin/certbot /usr/bin/certbot
    certbot certonly --webroot --non-interactive -m "pico.dev@gmail.com" --agree-tos -w /var/www/{{ domain }} -d {{ domain }}
    certbot renew --dry-run    
  become: true
  tags:
    - system-install
    - system-cerbot
ansible/picodotdev.certbot/tasks/configure.yml

Estos son unos ejemplos de certificados generados por Let’s Encrypt y certbot. Los certificados tienen un tiempo de validez de unos pocos meses con lo que cada cierto tiempo hay que renovarlos de lo que se encarga certbot de forma automática.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ubuntu@nginx:~$ sudo ls -lh /etc/letsencrypt/live/www.27.0.174.19.sslip.io/
total 4.0K
lrwxrwxrwx 1 root root  48 Dec 21 13:50 cert.pem -> ../../archive/www.27.0.174.19.sslip.io/cert1.pem
lrwxrwxrwx 1 root root  49 Dec 21 13:50 chain.pem -> ../../archive/www.27.0.174.19.sslip.io/chain1.pem
lrwxrwxrwx 1 root root  53 Dec 21 13:50 fullchain.pem -> ../../archive/www.27.0.174.19.sslip.io/fullchain1.pem
lrwxrwxrwx 1 root root  51 Dec 21 13:50 privkey.pem -> ../../archive/www.27.0.174.19.sslip.io/privkey1.pem
-rw-r--r-- 1 root root 692 Dec 21 13:50 README
ubuntu@nginx:~$ sudo ls -lh /etc/letsencrypt/archive/www.27.0.174.19.sslip.io/
total 20K
-rw-r--r-- 1 root root 1.9K Dec 21 13:50 cert1.pem
-rw-r--r-- 1 root root 3.7K Dec 21 13:50 chain1.pem
-rw-r--r-- 1 root root 5.5K Dec 21 13:50 fullchain1.pem
-rw------- 1 root root 1.7K Dec 21 13:50 privkey1.pem
certbot-certificates.sh

Configuración del servidor web Nginx

Antes de generar los certificados de Let’s Encrypt es necesario configurar el servidor web Nginx e iniciar el servicio, la configuración incluye modificar los archivos de configuración de Nginx para crear en este caso un servidor web virtual y su contenido. Un servidor web virtual permite devolver un contenido u otro en función del nombre del dominio por el que se acceda al servidor. Las tareas también crean varios directorios y archivos como por ejemplo de directorio raíz que el servidor web utiliza para obtener los recursos estáticos que devuelve.

Configurado Nginx con el protocolo HTTP y el servidor web virtual ya es posible iniciar la validación y generación de certificados de Let’s Encrypt y certbot. Las tareas de Ansible del rol picodotdev.nginx usan las tareas del rol picodotdev.certbot.

Entre los archivos del rol de Nginx está un archivo que es la plantilla de configuración del servidor para Nginx que procesándolo junto ciertas variables permite generar el contenido final, el contenido generado se guarda en el directorio /etc/nginx/sites-available/{{ item }} y posteriormente se crea un enlace simbólico a este archivo en el directorio /etc/nginx/sites-enabled/{{ item }} del que Nginx lee la configuración de los servidores web virtuales.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
---
- name: Install packages
  package:
    name: "{{ item }}"
    state: present
  become: true
  with_items:
    - nginx
  tags:
    - system-install
- name: Create directories
  file:
    path: "{{ item }}"
    state: directory
  with_items:
    - /etc/nginx/conf.d
    - /etc/nginx/sites-available
    - /etc/nginx/sites-enabled
    - "/var/www/{{ domain }}"
  become: true
  tags:
    - system-install
- name: "Copy nginx configuration (http) ({{ domain }})"
  template:
    src: "{{ template }}"
    dest: "/etc/nginx/sites-available/{{ item }}"
    owner: root
    group: root
    mode: '0644'
  vars:
    template: "nginx-virtual-server.conf"
    default_server: default_server
    domain: domain
    apex: apex
    ssl: false
  with_items:
    - "{{ domain }}"
  become: true
  tags:
    - system-install
- name: "Enable nginx configuration (http) ({{ domain }})"
  file:
    src: "/etc/nginx/sites-available/{{ item }}"
    path: "/etc/nginx/sites-enabled/{{ item }}"
    state: link
    owner: root
    group: root
    mode: '0644'
  with_items:
    - "{{ domain }}"
  become: true
  tags:
    - system-install
- name: Restart nginx
  include_tasks:
    file: restart.yml
    apply:
      tags: system-install
  tags:
    - system-install
- name: "Configure certbot ({{ domain }})"
  include_tasks:
    file: roles/picodotdev.certbot/tasks/configure.yml
    apply:
      tags:
        - system-install
        - system-cerbot
  when: "'clouding' in group_names and ssl"
  tags:
    - system-install
    - system-cerbot
- name: "Copy nginx configuration (https) ({{ domain }})"
  template:
    src: "{{ template }}"
    dest: "{{ item }}"
    owner: root
    group: root
    mode: '0644'
  vars:
    template: "nginx-virtual-server.conf"
    default_server: default_server
    domain: domain
    apex: apex
    ssl: ssl
  with_items:
    - "/etc/nginx/sites-available/{{ domain }}"
  notify:
    - Restart nginx
  become: true
  when: "'clouding' in group_names and ssl"
  tags:
    - system-install
ansible/picodotdev.nginx/tasks/main.yml
1
2
3
4
5
6
---
- name: Restart nginx
  systemd:
    state: restarted
    name: nginx
  become: true
ansible/picodotdev.nginx/tasks/restart.yml
1
2
3
4
---
- name: Restart nginx
  include_tasks:
    file: restart.yml
ansible/picodotdev.nginx/handlers/main.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{% if default_server %}
    {% set _default_server = " default_server" %}
{% else %}
    {% set _default_server = "" %}
{% endif %}

log_format nginx_vcombined_{{ domain | replace('.', '_') }} '$host:$server_port ' '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';

server {
    listen 80{{ _default_server }};
    listen [::]:80{{ _default_server }};
    server_name {{ domain }};

    location ~ /.well-known/acme-challenge/ {
        root   /var/www/{{ domain }};
        index  index.html index.htm;

        add_header Cache-Control "no-store";
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

{% if ssl %}
server {
    listen 443 http2{{ _default_server }} ssl;
    listen [::]:443 http2{{ _default_server }} ssl;
    server_name {{ domain }};

    gzip on;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    ssl_certificate      /etc/letsencrypt/live/{{ domain }}/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/{{ domain }}/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

    access_log /var/log/nginx/access.log nginx_vcombined_{{ domain | replace('.', '_') }};
    error_log /var/log/nginx/error.log;

    {% if apex %}
        return 301 https://www.$host$request_uri;
    {% else %}
        location /404.html {
            root   /var/www/{{ domain }};
            index  index.html index.htm;

            expires 10m;
            add_header Cache-Control "public";
        }

        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
            return 404;
        }

        location / {
            root   /var/www/{{ domain }};
            index  index.html index.htm;

            #rewrite ^/(.*)index-amp.html$ http://{{ domain }}/$1 permanent;

            expires 1d;
            add_header Cache-Control "public";

            error_page 404 /404.html;
        }
    {% endif %}
}
{% endif %}
ansible/picodotdev.nginx/templates/nginx-virtual-server.conf

En este ejemplo el contenido del servidor web es simplemente un archivo html con un mensaje de bienvenida inicial que permite comprobar que el servidor funciona correctamente. Este se copia al servidor con Ansible, otra forma de aprovisionamiento del contenido del servidor web virtual podría haber sido utilizar la herramienta rsync para sincronizar una carpeta remota en la raíz de documentos del servidor web y otra opción podría ser hacer un clonado de un repositorio de Git que es por ejemple como funciona GitHub Pages que es simplemente un rama en un repositorio donde se añade el contenido del sitio web.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Clouding.io!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to Clouding.io!</h1>
<p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>
<p>
  For online documentation and support please refer to
  <a href="http://nginx.org/">nginx.org</a>.<br/>
  Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.
</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
ansible/picodotdev.site/files/index.html

Servidor web público

Para configurar el servidor he utilizado como nombre de dominio uno proporcionado por sslip de forma que sea posible configurar el servidor web virtual en Nginx. Con el servidor configurado con los pasos anteriores el y accediendo con el navegador web a la dirección del servidor se obtiene la página con el mensaje de bienvenida, utiliza el protocolo seguro HTTPS con el certificado de Let’s Encrypt y que el navegador Firefox reconocer como autoridad de certificación válida y sin mostrar ningún error en la barra de direcciones.

Sitio web

Sitio web
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub.


Comparte el artículo: