DEV Community

Jorge
Jorge

Posted on • Originally published at jorge.aguilera.soy on

Apisix Gateway con autentificación Keycloak (y SSL con Caddy)

En este post voy a instalar en una máquina (linux obviamente) desde cuasi-cero un stack que nos permita tener un acceso https a unos microservicios pasando por un api gateway (con Apisix) que dialogará con Keycloack para gestionar la autentificación y autorización del usuario a los mismos.

De forma visual la arquitectura será algo parecido a:

Diagram

Proyecto

La idea del proyecto es crear un stack en Internet de tal forma que se expongan unos servicios a los que el usuario accederá previa identificación.

Dicha identificación podrá ser mediante usuario/password o bien mediante un proveedor de identidades, en este caso Linkedin (de igual forma puede ser Github, Gitlab, Google, etc.)

El acceso a los servicios se realizará mediante https y la gestión de la autentificación residirá en una pieza ajena a los servicios. Estos recibirán una cabecera con un JWT donde podrán extraer la información del usuario sin preocuparse de cómo se ha obtenido.

Requisitos

  • Una máquina Linux (la mia tiene 2GB de memoria y unos pocos gigas de disco)

  • Tener acceso como root (seguramente podrías con otro usuario pero no me voy a complicar)

  • Docker instalado

  • Un dominio en internet (para este post usaré apisix.edn.es)

  • Una entrada en el DNS que apunte a la IP pública de la máquina

  • acceso ssh a la máquina.

  • si se desea probar la integración con Linkedin (o similar) se necesita crear una App en https://developer.linkedin.com/ y generar un ApiClient y SecretClient

Crear un dominio/subdominio suele tardar unos minutos, al igual que la propagación de la entrada DNS. Recomiendo hacer esto lo primero para que se vaya realizando la propagación DNS y así Caddy aprovisionará un certificado sin esperas.

Recomiendo tener todos los puertos de la máquina expuesta a internet cerrados (e incluso cambiar el puerto por defecto 22 de ssh a otro) salvo el 80 y el 443. El puerto 80 es necesario para que Caddy pueda gestionar la provisión del certificado

Servicios Web

La idea es tener nuestros microservicios "limpios" de cualquier lógica de identificar a un usuario. Simplemente recibirán un JWT donde podrá extraer el nombre, email, etc.

Para este ejemplo vamos a usar una imagen httpbin.org que simplemente muestra los parámetros que se le envían en la petición y así comprobar que el servicio recibe dichos parámetros.

Así mismo vamos a crear dos servicios, web1 y web2 para darle más contenido y poder explorar cómo manejarlos con Apisix

docker-compose.yml

services:
  web1:
    image: kennethreitz/httpbin

  web2:
    image: kennethreitz/httpbin
Enter fullscreen mode Exit fullscreen mode

Caddy

Caddy es un reverse proxy que gestiona de forma automática el aprovisionar un certificado SSL con Let’s Encrypt de tal forma que será nuestra "puerta de entrada" al sistema mediante https. Una vez que securiza la conexión con el cliente mediante SSL realiza la labor de reverse proxy y le pasa la petición al servicio que se le indique

docker-compose.yml

  caddy:
   image: caddy
   ports:
     - "80:80"
     - "443:443"
   volumes:
     - ./caddy/data:/data/
     - ./caddy/config:/config/
     - ./caddy/Caddyfile:/etc/caddy/Caddyfile
Enter fullscreen mode Exit fullscreen mode

La configuración es además bastante simple, al menos para el alcance de este artículo:

caddy/Caddyfile

{
  email TU_EMAIL@TU_EMAIL
}

apisix.edn.es {
  reverse_proxy http://apisix:9080
}
Enter fullscreen mode Exit fullscreen mode

En este fichero indicarás tu correo y el dominio a usar ( apisix.edn.es en mi ejemplo)

Una vez que Caddy resuelve el SSL realizará el reverse proxy hacia el servicio apisix que crearemos a continuación

Keycloak

Keycloak es un sistema de autenticación y autorización seguro (Open Source). Es decir, es el encargado de "gestionar los usuarios" y los "accesos a los recursos"

En su uso más básico podremos dar de alta usuarios vía interface web y los servicios podrán dialogar con él para validar si un usuario es quien dice ser y saber a qué tiene acceso.

También puedes conectarlo con directorios de usuarios típicos en empresa (Kerberos, Active Directory, etc.) e incluso usar identity providers como Google, Linkedin, Github, etc.

Para este ejemplo vamos a crear un usuario con el interface web y además añadir Linkedin como identity provider (es decir, un usuario podrá identificarse mediante user/password o usando su cuenta de Linkedin)

docker-compose.yml

  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    container_name: keycloak
    env_file:
      - .env
    command: start-dev
    depends_on:
      - keycloakdb
    ports:
      - 8080:8080

  keycloakdb:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    env_file:
     - .env
Enter fullscreen mode Exit fullscreen mode

El interface de Keycloak realiza dos funciones:

  • mostrar el dialogo para autentificar un usuario

  • mostrar el dashboard de admin que permite la configuración y gestion del propio Keycloak

Obviamente NO queremos que el dashboard esté accesible a todo el mundo así usaremos las variables de entorno que nos ofrece para configurarle dos URLs diferentes. Una será accesible a todos los usuarios que quieran acceder a nuestro sistema y otra sólo estará disponible para administradores

Para nuestro ejemplo estas URLs serán:

.env

Usamos un fichero .env para tener en un sitio las variables de entorno de configuración, así como usuarios y password de sistema, así que este fichero NO debería ser versionado

.env

KC_DB=postgres
KC_DB_URL=jdbc:postgresql://keycloakdb:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=password

KC_HOSTNAME=https://apisix.edn.es/keycloak
KC_HOSTNAME_PORT=443
KC_HOSTNAME_STRICT=true
KC_HOSTNAME_STRICT_HTTPS=true

KC_HOSTNAME_ADMIN=http://localhost:8080

KC_LOG_LEVEL=info

KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin

POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=password
Enter fullscreen mode Exit fullscreen mode
INFO

Básicamente, las variables que empiezan por KC_ son propias de Keycloak y las que empiezan por POSTGRES son del motor de la base de datos. De ahí que estén duplicados los valores

Preparando Keycloak

A estas alturas estamos listos para levantar la primera fase de nuestro proyecto

docker compose up -d

Si todo va bien, Caddy gestionará el tema del SSL y Keycloak preparará la base de datos …​ pero cómo acceder a la consola de Keycloak si está corriendo en una máquina en nuestro proveedor?

ssh -p 2222 -L 8080:localhost:8080 root@TU.IP

Es decir, abrimos una conexión ssh con nuestra máquina (yo uso el puerto 2222) y hacemos enrutado de puertos 8080 entre la máquina remota y la nuestra.

WARNING

Esta NO es la manera de segurizar un acceso remoto en una máquina en internet, pero para este artículo no me voy a complicar.

Existen tutoriales más detallados sobre cómo manejar Keycloak. En este artículo simplemente voy a enumerar los pasos a realizar, pues son realmente muy fáciles e intuitivos

  • crear un realm apisix_test

  • en este realmn crear un client apisix

  • configurar en este cliente valid redirect URL con https://apisix.edn.es/web1/anything y https://apisix.edn.es/web2/anything

  • asegurarnos en este cliente que está marcada la opción "Client authentication". De esta forma la autentificación en este realm es gestionada entre servicios. Si estuviera OFF sería para aplicaciones Javascript por ejemplo

Una vez creado, Keycloak nos creará un client y un secret para este client que deberemos proporcionar a Apisix (paso siguiente)

  • crearemos un usuario test con password test y marcaremos como que su email ha sido validado y que no necesita cambiar la password en el primer acceso. (Por no complicarnos)

  • Si quieres añadir Linkedin como Identity Provider, en el menú de la izquierda te permitirá elegirlo y añadir las claves creadas desde Linkedin

Apisix

Apisix es un ApiGateway Open Source (la lógica de negocio no reside en él, sino que se dedica a enrutar, transformar, etc. peticiones hacia los servicios).

En este post lo vamos a usar en modo "standalone" de tal forma que la configuración y gestión sea lo más sencilla posible.

INFO

Puedes instalarlo con varias instancias, que use etcd como respaldo de la configuración, puedes instalarlo en kubernetes, etc.

docker-compose.yml

  apisix:
    image: apache/apisix:${APISIX_IMAGE_TAG:-3.10.0-debian}
    volumes:
      - ./apisix_conf/apisix-standalone.yaml:/usr/local/apisix/conf/apisix.yaml
    env_file:
      - .env
    environment:
      - APISIX_STAND_ALONE=true
Enter fullscreen mode Exit fullscreen mode

Añadiremos en el fichero .env la configuración creada por Keycloak

.env

KC_OIDC_CLIENTID=apisix
KC_OIDC_SECRET=myhg------------
KC_OIDC_ISSUER=apisix_test
Enter fullscreen mode Exit fullscreen mode

Por último el fichero apisix-standalone.yml donde ocurre toda la magia:

apisix-standalone.yml

routes:
  -
      uris: ["/keycloak/js/*", "/keycloak/resources/*", "/keycloak/realms/*", "/keycloak/robots.txt"]
      upstream_id: 3
      plugins:
         proxy-rewrite:
           regex_uri: ["/keycloak/(.*)","/$1"]

  -
      uri: /web1/*
      upstream_id: 1
      plugins:
        proxy-rewrite:
           regex_uri: ["/web1/(.*)"]
        openid-connect:
           client_id: ${{KC_OIDC_CLIENTID}}
           client_secret: ${{KC_OIDC_SECRET}}
           discovery: https://apisix.edn.es/keycloak/realms/${{KC_OIDC_ISSUER}}/.well-known/openid-configuration
           redirect_uri: https://apisix.edn.es/web1/anything
           scope: openid profile

  -
      uri: /web2/*
      upstream_id: 2
      plugins:
        proxy-rewrite:
           regex_uri: ["/web2/(.*)"]
        openid-connect:
           client_id: ${{KC_OIDC_CLIENTID}}
           client_secret: ${{KC_OIDC_SECRET}}
           discovery: https://apisix.edn.es/realms/${{KC_OIDC_ISSUER}}/.well-known/openid-configuration
           redirect_uri: httsp://apisix.edn.es/web2/anything
           scope: openid profile

upstreams:
  -
      id: 1
      nodes:
          "web1:80": 1
      type: roundrobin

  -
      id: 2
      nodes:
          "web2:80": 1
      type: roundrobin

  -
      id: 3
      nodes:
         "keycloak:8080": 1
      type: roundrobin

#END
Enter fullscreen mode Exit fullscreen mode

Lo primero: El fichero debe acabar en "#END" !!!! Esto es así porque estamos usando la versión standalone. En una instalación en producción NO usaríamos este modo y la gestión de rutas, etc las tendríamos en ficheros separados por ejemplo.

Como ves este fichero es donde configuramos tanto las rutas, como los backends (upstreams) y plugins.

Para nuestro ejemplo vamos a usar 3 upstreams:

  • web1

  • web2

  • los endpoints de keycloak "abiertos"

Así mismo en este ejemplo sencillo vamos a configurar 3 tipos de rutas:

  • Los endpoints abiertos de keycloak los dirigimos tal cual

  • Creamos rutas para web1 y web2, y para complicar el ejemplo cada uno podría estar configurado para usar diferentes realm (web1 por ejemplo permitiría accesos a un realm y web2 a otros)

En el caso de web1 (idem para web2) podríamos dirigir las llamadas al endpoint del servicio /api por ejemplo. Como estamos usando la imagen de httpbin.org usamos un endpoint suyo llamado anything que simplemente vuelca los parámetros de la llamada (y así poder comprobar que recibe el JWT entre otros)

Ejecutando

Si levantamos todos los servicios docker compose up -d nuestro stack debería estar completo ahora:

  • caddy recibe una petición https y la pasa a apisix

  • apisix evalúa que route despachar

  • si es para web1 el plugin openid valida si hay una autentificadion valida en la petición

  • si no la hay o es inválida openid se la envía a keycloak

  • keycloak presenta el interface para que el usuario haga login y dialoga con el usuario

  • una vez validado el acceso, keycloak redirige la petición original de web1

  • apisix la recibe y la enruta a web1

  • web1 puede acceder al JWT

Puedes probarlo accediendo a http://apisix.edn.es/web1/index.html (al menos mientras tenga esta máquina de pruebas levantada, que cuesta sus dineros)

apisix keycloak

Conclusión

Este ejemplo NO debería de usarse como tal en producción pero creo que sirve de ejemplo para ver cómo encajan las diferentes piezas al montar una arquitectura de microservicios con un ApiGateway y con autentificadión delegada

Top comments (0)