Habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX

Algunas cosas han cambiado en Amazon Web Services recientemente y, para nuestra mala suerte, la forma en la que se podían configurar los certificados SSL en una instancia única (o Single Instance), o bien, una instancia sin un balanceador de cargas, cambió dramáticamente. Por lo tanto, en este post podrás aprender a habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX de una forma bastante amigable.

Antes de empezar te recomiendo que olvides cualquier método anterior para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX. Por ejemplo:

  • Borra cualquier certificado en ACM (Amazon Certificate Manager) relacionado a tu instancia única, pues en el nuevo método, estaremos generando un certificado válido y gratuito a través de Let’s Encrypt 😍
  • Si tu aplicación utiliza un archivo .htaccess asegúrate de borrar la redirección a HTTPS desde ahí. Más adelante pondré un ejemplo de cómo debería quedar tu .htaccess.
  • Si alguna vez seguiste mi tutorial Forzar HTTPS en Elastic Beanstalk con NGINX que publiqué en Septiembre del 2020, también olvídalo 😳.

Para este artículo asumo que:
1. Tienes una instancia de Elastic Beanstalk, si no, checa el artículo “Configurar un servidor con NGINX de instancia única con Elastic Beanstalk
2. Tienes acceso SSH a tu instancia de Elastic Beanstalk. Te puede interesar el artículo “Configurar un acceso SSH a instancias EC2 en AWS
3. Tienes comprado un dominio para el cual necesitas el certificado SSL. Si aun no tienes un dominio comprado, te recomiendo lo compres en mydomain.com
4. Tu dominio está configurado en Route 53 en Amazon Web Services. Si no sabes como configurarlo te recomiendo el siguiente artículo sobre “Cómo configurar un dominio en Route 53

El nuevo método para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX, el cual bautizaré egolatramente como “El método Andrex para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX“, consiste en lo siguiente:

  1. Crear jerarquía de archivos y carpetas que nos permitirán configurar Elastic Beanstalk y NGINX para generar e instalar el certificado SSL gratis en la instancia única
  2. Agregar el contenido del archivo de configuración para Elastic Beanstalk 01-https_certificate_generation.config para generar e instalar el certificado SSL gratis
  3. Define el contenido del archivo de configuración de NGINX base nginx.conf
  4. Ingresa el contenido al archivo https_custom.conf para habilitar el certificado SSL
  5. Configurar apropiadamente el .htaccess para evitar redirecciones infinitas
  6. Prepara el index.php para probar la configuración

El método Andrex para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX

Comencemos pues con el procedimiento para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX. Para la demostración de este proyecto estaré utilizando un proyecto nuevo llamado “metodo-andrex-ssl-eb-nginx”, si quieres acceso al repositorio en gitlab sígueme en Instagram y mándame tu usuario de gitlab por mensaje privado 🤓.

Crear jerarquía de archivos y carpetas que nos permitirán configurar Elastic Beanstalk y NGINX para generar e instalar el certificado SSL gratis en la instancia única

En la raíz de tu proyecto crea la siguiente jerarquía de archivos y carpetas; en estas carpetas vamos a crear los archivos de configuración que vamos a necesitar. Por lo tanto abre una terminal y sigue el siguiente procedimiento de comandos para hacerlo:

# Crea el directorio raíz de tu proyecto
mkdir metodo-andrex-ssl-eb-nginx

# Accede al directorio de tu proyecto
cd metodo-andrex-ssl-eb-nginx

# Crea la carpeta de configuración para las instancias de Elastic Beanstalk
mkdir .ebextensions

# Crea un archivo vacío para el código que generará e instalará el certificado SSL
touch ./.ebextensions/01-https_certificate_generation.config

# Crea las carpetas de configuración para NGINX 
mkdir .platform
mkdir ./.platform/nginx
mkdir ./.platform/nginx/conf.d
mkdir ./.platform/nginx/conf.d/elasticbeanstalk

# Crea un archivo vacío para la configuración base de NGINX
touch ./.platform/nginx/nginx.conf

# Crea un archivo vacío para la configuración personalizada de NGINX sobre Elastic Beanstalk
touch ./.platform/nginx/conf.d/elasticbeanstalk/custom.conf

# Crea un archivo vacío para la configuración personalizada de NGINX sobre HTTPS
touch ./.platform/nginx/conf.d/https_custom.conf

# Crea el archivo .htaccess vacío
touch ./.htaccess

# Crea el archivo índex.php vacío
touch ./index.php

El resultado de la ejecución de los comandos anteriores debe resultar en la jerarquía de archivos y carpetas que se muestra en la imagen a continuación:

Resultado de ejecución de comandos mostrados anteriormente.

Si ya comprobaste que tengas esta estructura de archivos y carpetas, continuemos a agregar el contenido a los archivos vacíos que creamos.

Agregar el contenido del archivo de configuración para Elastic Beanstalk 01-https_certificate_generation.config para generar e instalar el certificado SSL gratis

El contenido que vamos a agregar a este archivo llevará acabo el siguiente procedimiento:

  1. Creará un grupo de seguridad para permitir el trafico entrante a la instancia única por medio del puerto 443
  2. Instalará en el contenedor de nuestra instancia el modulo de seguridad mod_ssl cada vez que se cargue una nueva versión de nuestro proyecto
  3. Creará una tarea programada que renueve automáticamente el certificado SSL gratis cada vez que vaya a expirar
  4. Solicitará e instalará el certificado de seguridad SSL de forma gratuita

Para este paso vamos a necesitar que tengas listo lo siguiente:

  1. Un correo electrónico válido, en mi caso pondré el mío iam[at]andresgtz.com
  2. La lista de dominios y subdominios que vamos a certificar, en mi caso utilizare los siguientes que compre para un proyecto que publicaré más adelante.
    1. mostsercurewebsite.com
    2. www.mostsecurewebsite.com

Abre el archivo 01-https_certificate_generation.config en tu editor de código y pon el siguiente código.

Resources:
    sslSecurityGroupIngress:
        Properties:
            CidrIp: 0.0.0.0/0
            FromPort: 443
            GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
            IpProtocol: tcp
            ToPort: 443
        Type: "AWS::EC2::SecurityGroupIngress"

packages:
    yum:
        mod_ssl : []

files:
    "/etc/cron.d/certbot_renew":
        content: "@weekly root certbot renew\n"
        group: root
        mode: "000644"
        owner: root

container_commands:
    10_downloadepel:
        command: "sudo wget -r --no-parent -A 'epel-release-*.rpm' https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/"
    20_installepel:
        command: "sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm --force"
    30_enableepl:
        command: "sudo yum-config-manager --enable epel*"
    40_installcertbot:
        command: "sudo yum install -y certbot"
    50_getcert:
        command: "sudo certbot certonly --debug --non-interactive --email [email protected] --agree-tos --standalone --domain mostsecurewebsite.com --domain www.mostsecurewebsite.com --rsa-key-size 4096 --keep-until-expiring --pre-hook \"sudo service nginx stop\" --post-hook \"sudo service nginx start\""

IMPORTANTE:
ASEGURATE DE CAMBIAR MI CORREO ELECTRÓNICO Y MIS DOMINIOS POR LOS TUYOS ANTES! DE NO HACERLO, NO VAS A PODER CONFIGURAR EL CERTIFICADO SSL.

Define el contenido del archivo de configuración de NGINX base nginx.conf

Ahora, no se puede configurar correctamente una instancia con HTTPS si el servidor que apunta a HTTP no está bien configurado, por lo tanto, agrega el siguiente código al archivo nginx.conf:

# Elastic Beanstalk Nginx Configuration File

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    32136;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    include       conf.d/*.conf;

    map $http_upgrade $connection_upgrade {
        default     "upgrade";
    }

    server {

        large_client_header_buffers 8 64k;
        client_body_buffer_size     32k;
        client_header_buffer_size   8k;

        listen 80 default_server;
        listen [::]:80 default_server;
        # Recuerda cambiar a tu dominio
        server_name  mostsecurewebsite.com www.mostsecurewebsite.com;

        if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
            set $year $1;
            set $month $2;
            set $day $3;
            set $hour $4;
        }

        access_log    /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
        access_log    /var/log/nginx/access.log main;

        charset               utf-8;
        sendfile              on;
        tcp_nopush            on;
        tcp_nodelay           on;
        server_tokens         off;
        log_not_found         off;
        client_header_timeout 60;
        client_body_timeout   60;
        keepalive_timeout     600;
        gzip                  on;
        gzip_comp_level       4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # Security headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-XSS-Protection "1;mode=block" always;

        # Configuration to redirect TO HTTPS
        location / {
            set $redirect 0;
            if ($http_x_forwarded_proto != "https") {
                set $redirect 1;
            }
            if ($http_user_agent ~* "ELB-HealthChecker") {
                set $redirect 0;
            }
            if ($redirect = 1) {
                return 301 https://$host$request_uri;
            }
        }

        # Include the Elastic Beanstalk generated locations
        include conf.d/elasticbeanstalk/*.conf;
    }
}

Ingresa el contenido al archivo https_custom.conf para habilitar el certificado SSL

Ya casi terminamos la configuración necesaria para probar que nuestro certificado SSL funciona, para ello necesitamos agregar el siguiente contenido al archivo https_custom.conf como se muestra a continuación:

upstream nodejs {
    server 127.0.0.1:3030;
    keepalive 256;
}

# HTTPS server
server {
    listen       443 ssl default_server;
    # Asegurate de cambiar mostsecurewebsite.com por tu dominio
    server_name  mostsecurewebsite.com www.mostsecurewebsite.com;

    if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
        set $year $1;
        set $month $2;
        set $day $3;
        set $hour $4;
    }

    access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;
    access_log    /var/log/nginx/access.log main;

    ssl_prefer_server_ciphers   on;
    # Asegurate de cambiar mostsecurewebsite.com en la ruta por tu dominio
    ssl_certificate             /etc/letsencrypt/live/mostsecurewebsite.com/fullchain.pem;
    # Asegurate de cambiar mostsecurewebsite.com en la ruta por tu dominio
    ssl_certificate_key         /etc/letsencrypt/live/mostsecurewebsite.com/privkey.pem;
    ssl_session_timeout         5m;
    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers                 "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";

    location ~ ^/(lib/|img/) {
        root /var/app/current/public;
        access_log off;
    }
    location / {
        proxy_pass              http://localhost:80;
        proxy_set_header        Connection "";
        proxy_http_version      1.1;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto https;
    }
}

Configurar apropiadamente el .htaccess para evitar redirecciones infinitas

En algunos casos se usa que la redirección de HTTP a HTTPS se maneje desde el .htaccess, pero en este procedimiento, es MUY IMPORTANTE que no se haga esta redirección de esta forma pues se ocasionaría un bucle infinito de redirecciones. Por lo tanto, yo sugiero que dejemos el .htaccess con el siguiente contenido:

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} -d [OR]
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^ ^$1 [N]

    # When Elastic Beanstalk instance on AWS + NGINX, redirect should be commented out
    # RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    # RewriteCond %{HTTPS} !=on
    # RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]
</IfModule>

Prepara el index.php para probar la configuración

Lo único que tenemos que hacer en este paso es agregar el siguiente contenido al index.php

<?php 
echo "El método Andrex para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX funciona. 😎"; 
?>

Vamos a probar que nuestra configuración funcione

Para probar que nuestra configuración funcione debemos comprimir nuestro proyecto y subirlo a Elastic Beanstalk. Vamos, que para estar leyendo El método Andrex para habilitar SSL en un Single Instance de Elastic Beanstalk con NGINX deberías estar familiarizado con este procedimiento.

Para comprimir tu proyecto en un .zip para cargarlo a Elastic Beanstalk usa el siguiente comando:

# Dentro del directorio de tu proyecto ejecuta el siguiente comando
cd metodo-andrex-ssl-eb-nginx # Por si las dudas

# Comando para comprimir
zip ../"${PWD##*/}".zip -r * .[^.]*

Una vez que se termine de subir tu código podrás visualizar la siguiente pantalla, lo que quiere decir que todo funcionó correctamente:

Se puede apreciar que se pudo acceder sin problemas a la instancia vía SSL.

Bonus (Aplicaciones Laravel)

Para aplicaciones Laravel debes agregar una configuración extra al archivo AppServiceProvider.php, la cual es la siguiente:

# Dentro del metodo boot
URL::forceScheme('https');

Y listo.

Hasta la próxima!

Espero que te haya gustado y ayudado este artículo, de ser así, suscríbete para que seas notificad@ siempre que suba artículos sobre automatización y seguridad informática, me lo agradecerás después.También puedes seguirme en Instagram en @andres.gtz y hacerme las preguntas que desees. Así mismo, agradezco tu generosidad si deseas donar criptomonedas a alguna de las siguientes direcciones:

Please Add coin wallet address in plugin settings panel

Si te quedaron dudas, puedes escribirlas aquí en los comentarios, o bien, escríbeme a mi Instagram @andres.gtz.

Arquitectura de Alta Disponibilidad en AWS con Elastic Beanstalk paso a paso Configurar un acceso SSH a instancias EC2 en AWS Configurar dominio en Route 53
View Comments
There are currently no comments.