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:
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
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
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
}
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
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
- INFO
-
Básicamente, las variables que empiezan por
KC_
son propias de Keycloak y las que empiezan porPOSTGRES
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
conhttps://apisix.edn.es/web1/anything
yhttps://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 passwordtest
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
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
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
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 aapisix
apisix
evalúa queroute
despacharsi es para
web1
el pluginopenid
valida si hay una autentificadion valida en la peticiónsi no la hay o es inválida
openid
se la envía akeycloak
keycloak
presenta el interface para que el usuario haga login y dialoga con el usuariouna vez validado el acceso,
keycloak
redirige la petición original deweb1
apisix
la recibe y la enruta aweb1
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)
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)