Un blog mas

Bitácora de vuelo

Para verificar una conexión con TLS/SSL publicada con un certificado de servidor firmado por una CA interna se debe incorporar el certificado de esta CA en el parámetro -CAfile

pablo@notebook:~$ echo -e "GET /index.html HTTP/1.1\r\nHost:$(hostname)\r\n\r\n" | openssl s_client -connect vm-linux.local:443 -CAfile ca.pem -quiet 

depth=1 C = AR, ST = CABA, L = CABA, O = Mi Empresa SA, OU = IT, emailAddress = admin@miDominio.com.ar, CN = miDominio-CA
verify return:1
depth=0 C = AR, ST = CABA, L = CABA, O = Mi Organizacion SA, OU = IT, emailAddress = admin@miorganizacion.com.ar, CN = vm-linux.local
verify return:1
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)

Si no definimos dicho parámetro con la cadena certificante obtendríamos un 200 OK con los siguientes errores

pablo@notebook:~$ echo -e "GET /index.html HTTP/1.1\r\nHost:$(hostname)\r\n\r\n" | openssl s_client -connect vm-linux.local:443  -quiet 

depth=0 C = AR, ST = CABA, L = CABA, O = Mi Organizacion SA, OU = IT, emailAddress = admin@miorganizacion.com.ar, CN = vm-linux.local
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = AR, ST = CABA, L = CABA, O = Mi Organizacion SA, OU = IT, emailAddress = admin@miorganizacion.com.ar, CN = vm-linux.local
verify error:num=21:unable to verify the first certificate
verify return:1
HTTP/1.1 200 OK

Ahora el mismo ejemplo pero con CURL

pablo@notebook:~$ curl --cacert ca.pem  https://vm-linux.local:443

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

y el error correspondiente si no enviamos la cadena en el parámetro –cacert

pablo@notebook:~$ curl https://vm-linux.local:443

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Ahora verificamos una conexión incorporando autenticación con mutual SSL (certificado del lado del cliente). Para ello se debe definir el certificado de cliente en el parámetro -cert y la clave privada del cliente en el parámetro -key

pablo@notebook:~$ echo -e "GET /index.html HTTP/1.1\r\nHost:$(hostname)\r\n\r\n" | openssl s_client -connect vm-linux.local:443 -cert cliente1.pem -key cliente1.key -CAfile ca.pem -quiet 

depth=1 C = AR, ST = CABA, L = CABA, O = Mi Empresa SA, OU = IT, emailAddress = admin@miDominio.com.ar, CN = miDominio-CA
verify return:1
depth=0 C = AR, ST = CABA, L = CABA, O = Mi Organizacion SA, OU = IT, emailAddress = admin@miorganizacion.com.ar, CN = vm-linux.local
verify return:1
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 24 Jun 2022 00:00:26 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Sat, 18 Jun 2022 03:00:21 GMT
Connection: keep-alive
ETag: "62ad3fc5-264"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Si no se envían estos parámetros el error de autenticación se vería de la siguiente manera

pablo@notebook:~$ echo -e "GET /index.html HTTP/1.1\r\nHost:$(hostname)\r\n\r\n" | openssl s_client -connect vm-linux.local:443  -CAfile ca.pem -quiet 

depth=1 C = AR, ST = CABA, L = CABA, O = Mi Empresa SA, OU = IT, emailAddress = admin@miDominio.com.ar, CN = miDominio-CA
verify return:1
depth=0 C = AR, ST = CABA, L = CABA, O = Mi Organizacion SA, OU = IT, emailAddress = admin@miorganizacion.com.ar, CN = vm-linux.local
verify return:1
HTTP/1.1 400 Bad Request
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 24 Jun 2022 00:05:51 GMT
Content-Type: text/html
Content-Length: 246
Connection: close

<html>
<head><title>400 No required SSL certificate was sent</title></head>

Mismo caso pero con CURL, definiendo el certificado de cliente en el parámetro –cert y la clave privada en el parámetro –key

pablo@notebook:~$ curl --cacert ca.pem --cert cliente1.pem --key cliente1.key https://vm-linux.local:443

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Y el siguiente es el resultado que obtendríamos si se intenta acceder a una publicación con MUTUAL sin los certificados correspondientes

pablo@notebook:~$ curl --cacert ca.pem https://vm-linux.local:443

<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>

ca.pem contiene la cadena certificadora asociada a los certificados de servidor con los que se publican los servicios web.

cliente1.key es la clave privada generada por el cliente que se quiere autenticar al servicio publicado con Mutual SSL.

cliente1.pem es un certificado firmado por la CA definida en el servidor para autenticar a los clientes.

TIP: Verificar que el CSR y el certificado firmado por la CA pertenecen a la misma clave privada comparando los hash SHA256 de cada uno

pablo@notebook:~$ openssl pkey -in cliente1.key -pubout -outform pem | sha256sum
528c0019e27100d2879759fdb3072951e555f1ef9a1c61f98e3dca747c092eed  -
pablo@notebook:~$ openssl x509 -in cliente1.pem -pubkey -noout -outform pem | sha256sum 
528c0019e27100d2879759fdb3072951e555f1ef9a1c61f98e3dca747c092eed  -
pablo@notebook:~$ openssl req -in cliente1.csr -pubkey -noout -outform pem | sha256sum
528c0019e27100d2879759fdb3072951e555f1ef9a1c61f98e3dca747c092eed  -

Es necesario contar con la aplicación Google Authenticator en el celular para obtener el segundo factor. Esta aplicación esta disponible en los repos de IOS y Android.

El primer paso es instalar el módulo de Google Authenticator de PAM

sudo apt-get install libpam-google-authenticator

Modificar la autenticación del servicio de SSH en PAM para requerir el 2FA editando el archivo /etc/pam.d/sshd agregando la línea auth required pam_google_authenticator.so luego del common-auth.

# PAM configuration for the Secure Shell service
#auth required pam_google_authenticator.so 
# Standard Un*x authentication.
@include common-auth

auth required pam_google_authenticator.so

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so
.....

Cambiar a yes el parámetro KbdInteractiveAuthentication en el archivo de configuración del servicio de SSH para solicitar el 2FA, editando el archivo /etc/ssh/sshd_config

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
KbdInteractiveAuthentication yes

# Kerberos options

y reiniciar el servicio de SSH

sudo service ssh restart

Ahora debemos generar la clave para el 2FA ejecutando con nuestro usuario el comando google-authenticator . Durante el proceso de configuración se generará un código QR que debemos escanear con la aplicación Google Authenticator instalada en nuestro celular para obtener el TOTP y completar la instalación (luego de varios yes como respuesta).

Para finalizar solo resta probar el acceso en donde se nos solicitará la clave de usuario y el código de un solo uso generado por la aplicación.

pablo@pc:~$ ssh pablo@vm-linux
Password: ************
Verification code: 123 456

Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-37-generic x86_64)

pablo@vm-linux:~$ 

FluentD es un reenviador de datos rápido y ligero el cual se puede configurar como complemento de salida de Loki para enviar los registros «interesantes» y poder monitorearlos con Grafana. A continuación el docker-compose para correr estos tres componentes con docker.

version: "3"

volumes:
  grafana-data:
  loki-data:

networks:
  lokinet:

Se definen la versión del docker file, volúmenes permanentes y red

  loki:
    image: grafana/loki:latest
    volumes:
     - ./conf/loki-config.yaml:/etc/config/loki.yaml
    command: 
      - -config.file=/etc/config/loki.yaml
      - loki-data:/loki
    networks:
      - lokinet

El contenedor Loki se genera a partir de la imagen oficial de Grafana. Se define el archivo de configuración y el volumen permanente donde se almacenarán los datos. El archivo de configuración ejemplo se puede descargar del siguiente link: loki-config.yaml

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana-data:/var/lib/grafana
      - ./conf/grafana.ini:/etc/grafana/grafana.ini
    ports:
      - 3000:3000
    networks:
      - lokinet

El contenedor de Grafana se genera a partir de la imagen oficial de Grafana. Se definen el volumen permanente donde se almacenan los datos de Grafana y el archivo grafana.ini donde se pueden agregar los parámetros de configuración que sobre escriben a la configuración default. Se expone el puerto 3000 al Host para poder accederlo desde un browser. Un archivo de configuración de Grafana ejemplo se puede dercargar desde aquí: grafana.ini

  fluentd:
    image: grafana/fluent-plugin-loki:master
    command:
      - "fluentd"
      - "-v"
      - "-p"
      - "/fluentd/plugins"
    volumes:
      - "./conf/fluent.conf:/fluentd/etc/fluent.conf:ro"
    environment:
      LOKI_URL: http://loki:3100
      LOKI_USERNAME:
      LOKI_PASSWORD:
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    networks:
      - lokinet

El contenedor de Fluent se genera a partir de la imagen oficial, pero no de FluentD sino de Grafana, ya que contiene el plugin para conectar a Loki. Se define el volumen permanente al archivo de configuración de Fluent y definen los parametros de acceso al loki. Como está dentro del mismo grupo de contenedores el acceso se logra sin la necesidad de publicar el puerto 3100 del Loki al Host. Sí es necesario exponer los puertos 24224tcp/udp al Host para publicar el servicio de forward del Fluent, esperando logs de otros Hosts que serán reenviados al Loki. Un archivo de configuración de ejemplo de Fluent se puede descargar desde este link: fluent.conf

Resta acceder al Grafana y activar el Data Sources Loki como se ve en la siguiente captura.

El docker-compoose.yaml completo se puede dercargar del siguiente link: https://gitlab.com/pablosarubbi/grafanalokifluentddocker-compose/-/blob/main/docker-compose.yaml

Poste.io es una imagen para implementar un servidor de correo completo, con todas las funciones necesarias, como ser SMTP, IMAP, POP3, webmail, antivirus, antispam, y un panel de administración muy completo desde donde se pueden agregar multiples dominios, cuantas de correo, certificados, y modificar la configuración de cada uno de los componentes.

El único requisito para su instalación es tener Docker corriendo sobre una máquina con 1GB de RAM y una IP pública la cual asignaremos en nuestro DNS a mail.sarubbi.com.ar

pablo@mail:~# sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
pablo@mail:~# sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
pablo@mail:~# sudo apt update
pablo@mail:~# sudo apt install docker.io docker-compose

Es necesario crear un archivo docker-compose.yaml con los siguientes parámetros:

pablo@mail:~# cat /root/docker-compose.yaml 
version: "3.4"
services:
  poste:
    image: analogic/poste.io
    restart: always
    network_mode: "host"
    expose:
      - 25
      - 80
      - 443
      - 110
      - 143
      - 465
      - 587
      - 993
      - 995
    volumes:
      - /mnt/mail:/data
    environment:
      - HTTPS=ON
      - DISABLE_CLAMAV=TRUE

restart: always para que se inicie el contenedor cada vez que la máquina inicie.

netkwork_mode: «host» hace que el contenedor use la IP pública de la máquina y no una IP interna de Docker.

expose: – <port> para publicar todos los puertos que necesitamos.

volumes : – <dir> para almacenar lo archivos de configuración y toda la información de los mails.

Para luego correr la imágen de la siguiente manera:

pablo@mail:~# sudo docker-compose -f /root/docker-compose.yaml up -d

Con docke ps podemos verificar que el contenedor este corriendo

pablo@mail:~# sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                  PORTS               NAMES
b2ab960cdc6c        analogic/poste.io   "/init"             5 seconds ago          Up 5 seconds (healthy)                       root_poste_1

Para terminar la configuración debermos acceder desde unn browser a https://mail.sarubbi.com.ar/

La primera vez fallará el HTTPS porque aún no tenemos un certificado definido. Continuamos aunque diga que no es seguro (porque sabemos que es seguro no?)

Completamos los datos:

Mailserver hostname: mail.sarubbi.com.ar

Administrator email: administrador@sarubbi.com.ar

Password: ******************

Para crear un certificado con letsencrypt accedemos al menú «System settings» -> «TLS Certificate» -> «issue free letsencrypt.org certificate» -> «enabled» -> «Save changes»

Automáticamente se conectará a letsencrypt.org, generará el certificado y lo instalará en nuestro contenedor.

Algo para destacar es revisar el panel de «Server status» -> «Connection diagnostics» desde donde podemos verificar que tengamos todos los servicios funcionando.

Gracias tocayo por la data: https://youtu.be/K4-uD1VHCz0

Se prepara la estructura de directorios de la CA para contener los certificados.
En /etc/ssl/HA/

root@ubuntu-ha:~# mkdir -p {private,servers,clients,csr,crl}

Generación de la CA

Se general un archivo openssl.cnf para configuración de los certificados

        [ ca ]
            default_ca      = CA_default
        [ CA_default ]
            dir             = /etc/ssl/HA 
            certs           = /etc/ssl/HA
            certificate     = /etc/ssl/HA/ca.pem
            private_key     = /etc/ssl/HA/private/ca.key
            crl_dir         = /etc/ssl/HA/crl
            database        = /etc/ssl/HA/index.txt
            serial          = /etc/ssl/HA/serial
            crlnumber       = /etc/ssl/HA/crlnumber
            crl             = /etc/ssl/HA/ca.crl
            default_md      = sha256

        [req]
            default_bits = 2048
            prompt = no
            default_md = sha256
            req_extensions = req_ext
            distinguished_name = dn
        [ dn ]
            C=AR
            ST=CABA
            L=CABA
            O=Mi Empresa SA
            OU=IT
            emailAddress=admin@miDominio.com.ar
            CN = miDominio-CA
            [ req_ext ]
            subjectAltName = @alt_names
            [ alt_names ]
            DNS.1 =miDominio-CA

Se genera la clave privada y certificado de la CA

root@ubuntu-ha:~# openssl req -newkey rsa:4096 -keyform PEM -keyout ./private/ca.key -x509 -days 3650 -outform PEM -out ./ca.pem -config openssl.cnf

Certificado de cliente

root@ubuntu-ha:~# NOMBRE="cliente1"
root@ubuntu-ha:~# MAIL="cliente1@miDominio.com.ar"

Se crea la clave privada

root@ubuntu-ha:~# openssl genrsa -out ./clients/$NOMBRE.key 4096

Se genera la solicitud de firma (CA) de certificados para la conexión de los clientes

root@ubuntu-ha:~# openssl req -new -key ./clients/$NOMBRE.key -out ./csr/$NOMBRE.csr -subj "/C=AR/ST=CABA/L=CABA/O=Mi Empresa SA/OU=IT/emailAddress=$MAIL/CN=$NOMBRE.miDominio.com.ar"

Se firma (CA) el certificado del cliente que caduca en un año

root@ubuntu-ha:~# openssl x509 -req -in ./csr/$NOMBRE.csr -CA ./ca.pem -CAkey ./private/ca.key -CAcreateserial -extensions client -days 365 -outform PEM -out ./clients/$NOMBRE.pem

Generamos una clave simétrica aleatoria para proteger el certificado P12

root@ubuntu-ha:~# PASS=`openssl rand -base64 9`
root@ubuntu-ha:~# echo $PASS > ./clients/$NOMBRE.txt

Convertir certificado en formato válido para navegador (P12)

root@ubuntu-ha:~# openssl pkcs12 -export -inkey ./clients/$NOMBRE.key -in ./clients/$NOMBRE.pem -out ./clients/$NOMBRE.p12 -passout pass:$PASS

Revocar certificados de clientes

root@ubuntu-ha:~# mv ./clients/$NOMBRE.* ./crl/
root@ubuntu-ha:~# openssl ca -revoke ./crl/$NOMBRE.pem -config openssl.cnf

Generar lista de revocados

root@ubuntu-ha:~# openssl ca -gencrl -out ca.crl -crldays 1095 -config openssl.cnf

Certificado de Servidor con Let’s Encrypt

Instalar certbot

root@ubuntu-ha:~# aptitude install certbot

Se generan los certificados verificados con el registro _acme-challenge del DNS

root@ubuntu-ha:~# certbot certonly --server https://acme-v02.api.letsencrypt.org/directory --manual --preferred-challenges dns -d "haproxy.midominio.com.ar"

Se crea un certificado haproxy.pem on la chain y la clave privada

root@ubuntu-ha:~# cat /etc/letsencrypt/live/haproxy.midominio.com.ar/fullchain.pem /etc/letsencrypt/live/haproxy.midominio.com.ar/privkey.pem > /etc/ssl/HA/server/haproxy.pem

Configuración del HAProxy

Se edita el frontend del HAProxy en el archivo /etc/haproxy/haproxy.cfg con los parámetros crt (certificado de servidor), ca-file (certificado de cliente) y crl-file (lista de revocados) como requeridos.

bind *:443 ssl crt /etc/ssl/HA/server/haproxy.pem verify required ca-file /etc/ssl/HA/ca.pem crl-file /etc/ssl/HA/ca.crl 
Stop SOPA