Como Monitorear Certificados SSL con Golang

@Cristóbal Salazar Guerrero
12 ago. 2023
article_image

Introducción

En el mundo del desarrollo web, la seguridad es fundamental. Los certificados SSL garantizan la confidencialidad e integridad de los datos intercambiados entre un cliente y un servidor. Es esencial tenerlos siempre vigentes, así aseguramos a nuestros clientes que su comunicación con nosotros es segura y privada.

En esta breve guía, vamos a crear un programa que nos dice cuándo nuestros certificados están a punto de expirar con el fin de poder monitorearlos.

Nuestra funcion

Vamos a crear una función llamada CheckSSLExpiry a la cual le vamos a proporcionar una URL del sitio web que contiene nuestro certificado. Esta función nos devolverá la fecha de vencimiento del certificado y cualquier error que haya encontrado.

Tendra la forma siguente:

func CheckSSLExpiry(website string) (time.Time, error)

Vamos a utilizar esta funcion despues para monitorear el sitio.

Estableciendo la conexion

Para obtener el certificado del sitio, necesitamos establecer una conexión con el servidor. Utilizaremos la función tls.Dial del paquete crypto/tls para lograrlo

conn, err := tls.Dial("tcp", address, nil)

Como ven, esta funcion recibe tres argumentos:

  1. network - tcp/udp En nuestro caso, estamos usando el protocolo http. El valor que vamos a utilizar es tcp.

  2. address - Este valor tiene que tener el formato de "host:port".

    • Si el puerto no esta definido hay que usar el puerto defecto de https (443). En el caso de connectarse a https://www.cibera.mx el valor va a ser www.cibera.mx:443.
    • Si el puerto esta definido en la url, hay que usar ese puerto. Si queremos connectarnos a https://www.sitio.com:1234 el formato tiene que ser www.sitio.com:1234
  3. Este valor es de tipo tls.Config. Un valor nulo indica que deseamos utilizar la configuración predeterminada. Si deseas agregar tus propios certificados en el proceso de validación o necesitas más control sobre tu conexión, tendrás que añadir tu propia configuración.

Conversion de URL a Address

En nuestro caso, tenemos todo lo necesario para establecer la conexión, excepto el valor de address.

Para transformar la URL al formato requerido por address, debemos pasarla por url.Parse. Esta función nos brindará acceso a métodos que contienen información más detallada sobre la URL.

Crearemos otra función para mantener nuestro código modularizado.

func parseAddress(website string) (string, error) {
    u, err := url.Parse(website)
    if err != nil {
        return "", err
    }

    port := u.Port()
    scheme := u.Scheme

    if scheme != "https" {
        return "", errors.New("sitio tiene que seguir el esquema https")
    }

    if port == "" {
        port = "443"
    }

    address := net.JoinHostPort(u.Hostname(), port)
    return address, nil
}

Con esto, tenemos todo lo necesario para establecer la conexión.

address, _ := parseAddress(url)
conn, err := tls.Dial("tcp", address, nil)

Luego, abordaremos el manejo de errores en la función finalizada.

Fecha de Vencimiento

Una vez establecida la conexión, procederemos a iterar a través de la cadena de certificados. Estamos buscando el certificado relevante para nuestro sitio, es decir, el certificado obtenido a través de Let’s Encrypt u otro proveedor de certificados. Cuando lo encontremos, extraeremos su fecha.

Para encontrarlo, nos aseguramos que el hostname es igual al hostname de la URL.

for _, cert := range conn.ConnectionState().PeerCertificates {
    if err := cert.VerifyHostname(u.Hostname()); err == nil {
        return cert.NotAfter, nil
    }
}

La propiedad NotAfter contiene la fecha de vencimiento.

Nuestra funcion finalizada

Aquí está nuestra función en su estado final. Manejamos todos los errores y nos aseguramos de posponer el cierre de la conexión.

func CheckSSLExpiry(website string) (time.Time, error) {
    expiry := time.Time{}
    address, err := parseAddress(website)

    if err != nil {
        return expiry, err
    }

    conn, err := tls.Dial("tcp", address, nil)
    if err != nil {
        return expiry, errors.New("no se pudo establecer conexion")
    }

    defer conn.Close()

    for _, cert := range conn.ConnectionState().PeerCertificates {
        if err := cert.VerifyHostname(u.Hostname()); err == nil {
            return cert.NotAfter, nil
        }
    }

    return expiry, errors.New("certificado no fue encontrado")
}

Como Usar la Funcion para Monitorear

Para monitorear nuestro sitio, simplemente verificaremos si la fecha de vencimiento está dentro de una semana. Si está a punto de expirar, imprimiremos un mensaje en la consola. Aquí puedes llevar a cabo lo que desees: enviar un correo, un mensaje de texto o notificar a cualquier integración que tengas (por ejemplo, Slack o Microsoft Teams).

package main

import (
    "time"
    "log"
)

func main() {
    expires, err := CheckSSLExpiry("https://www.cibera.mx")

    if err != nil {
        log.Fatal(err)
    }

    duration := time.Until(expires)

    if duration < time.Hour * 24 * 7 {
        log.Printf("el certificado ssl va expirar dentro de %s", duration)
    }
}

Aqui tenemos algunas ideas de como podriamos expandir nuestro programa:

Conclusion

En el mundo digital actual, la seguridad es esencial y monitorizar tus certificados SSL se vuelve fundamental. Estos certificados no solo aseguran conexiones encriptadas, sino que también protegen datos y mejoran el SEO. Ante amenazas en constante evolución, un monitoreo constante evita brechas y mantiene una reputación sólida. Mantente proactivo para detectar y resolver problemas a tiempo, garantizando la seguridad y confiabilidad de tu sitio web en el panorama digital actual.