Cómo Configurar Login con Office 365 (Microsoft Entra ID) en Authentik

Como ya vimos en la anterior entrada es bastante sencillo instalar un sistema SSO como authentik en nuestra infraestructura. De todas formas, crear un login centralizado no es más que una manera de tener que recordar menos contraseñas, pero el objetivo final es no tener que recordar más que unas pocas para poder acceder a todos los servicios, ¿qué hacemos si ya tenemos cuenta en alguno de los servicios más populares como office 365 o google? Lo suyo sería poder hacer login con estas credenciales en cualquier servicio, sea online o sea auto hospedado. Por suerte, authentik puede hacer uso de estos servicios externos de autenticación de manera «relativamente» sencilla. En este post os voy a explicar paso a paso (que sobre todo la parte a realizar con office es un poco liosa) cómo integrar el login de office 365 con authentik y así poder usarlo para acceder a nuestras aplicaciones (eso ya lo explicaremos en próximos posts).

Para poder realizar este tutorial debes tener:

  • Servidor authentik instalado y configurado (con acceso como administrador)
  • Cuenta de administrador office365 de tu organización
  • Un poco de paciencia (esto nunca viene mal)

Paso 1: registrar una aplicación de microsoft entra

Entramos como administrador en portal.office.com

Pedimos que nos muestre todas las opciones (estos de MS suelen ocultarnos cosas)

Seleccionamos la opción Identidad (nos llevará a entra)

Seleccionamos la opción Registro de aplicaciones

Y seleccionamos Nuevo registro

Y rellenamos los datos básicos (yo voy a llamar authentik a la nueva aplicación)

Hemos elegido que solo se pueda entrar con cuentas de nuestra organización, que será lo normal, aunque si queremos que cualquiera que tenga una cuenta de office365 pueda hacer logn tendremos que escoger una de las otras opciones. Atento a la ui de redirección, en principio tendrá el formato https://<direccion-de-tu-authentik>/source/oauth/callback/<nombre-de-fuente> Si no lo configuras ahora lo podrás hacer después cuando termines la configuración en authentik.

Anotamos los datos que vamos a necesitar de la aplicación y creamos un nuevo secreto:

Es muy importante que copiemos el valor del secreto que lo vamos a necesitar luego

El valor del secreto no se va a poder copiar en otro momento, mejor que lo guardes ahora.

Con esto hemos terminado (en principio) con la configuración necesaria en el portal de office. Lo que hemos hecho, básicamente, es configurar los endpoints de oauth2 que podemos consultar en la opción.

Paso 2: crear un nuevo login social

Las siguientes acciones las haremos como administrador en nuestro servidor authentik

El primer paso es crear un inicio de sesión federado

del tipo azure AD

Ahora deberemos introducir todos los datos que hemos guardado al crear la aplicación en entra:

Los endpoint serán como estos (revisar los que vimos en la pantalla del portal de office):

Cambiaremos la URL del perfil a https://graph.microsoft.com/oidc/userinfo

Y damos a terminar por ahora. Esto nos configura el método para entrar usando las credenciales de Microsoft, pero todavía nos falta un par de cosas para que sea útil del todo.

Paso 3: mapeo de atributos

Ahora necesitamos hacer la equivalencia entre los atributos que vienen de office con los que nosotros vamos a utilizar en nuestro directorio, para eso crearemos una política de expresión:

La llamamos (por ejemplo) azure-ad-mapping y ponemos este contenido:

# save existing prompt data
current_prompt_data = context.get('prompt_data', {})
# make sure we are used in an oauth flow
if 'oauth_userinfo' not in context:
  ak_logger.warning(f"Missing expected oauth_userinfo in context. Context{context}")
  return False
oauth_data = context['oauth_userinfo']
# map fields directly to user left hand are the field names provided by
# the microsoft graph api on the right the user field names as used by authentik
required_fields_map = {
  'name': 'username',
  'email': 'email',
  'given_name': 'name'
}
missing_fields = set(required_fields_map.keys()) - set(oauth_data.keys())
if missing_fields:
  ak_logger.warning(f"Missing expected fields. Missing fields {missing_fields}.")
  return False
for oauth_field, user_field in required_fields_map.items():
  current_prompt_data[user_field] = oauth_data[oauth_field]
# Define fields that should be mapped as extra user attributes
attributes_map = {
  'name': 'upn',
  'sub': 'sn',
  'name': 'name'
}
missing_attributes = set(attributes_map.keys()) - set(oauth_data.keys())
if missing_attributes:
  ak_logger.warning(f"Missing attributes: {missing_attributes}.")
  return False
# again make sure not to overwrite existing data
current_attributes = current_prompt_data.get('attributes', {})
for oauth_field, user_field in attributes_map.items():
  current_attributes[user_field] = oauth_data[oauth_field]
current_prompt_data['attributes'] = current_attributes
context['prompt_data'] = current_prompt_data
return True

Paso 4: configurar el flujo de alistamiento

Queremos que cualquiera que tenga cuenta en el dominio office pueda entrar a nuestras aplicaciones, por lo que necesitamos registrar los usuarios nuevos cuando entran por primera vez, para ello vamos a crear uun flujo nuevo.

Le ponemos un nombre

Y una vez creado, editarlo, ir a la sección d Vinculaciones de Políticas / Grupos / Usuarios y ahí a Bind existing policy

Y ahí ligar defaultsource-enrollment-if-sso

Ir ahora a vinculos de etapa -> Bind Existing stage

Añadir default-source-enrollment-write (orden 0) y default-source-enrollment-login (orden 10)

Desplegar luego la etapa con orden cero y añadir una política existente azur-ad-mapping que creamos antes:

Por último editarmos la fuente que creamos al principio (Directory -> Federation ..) y le añadimos en la configuración de flujo el flujo de inscrpción a azure-ad-enrollment (o el nombre que le hayamos puesto)

Paso 5: hacer que se vea en el login

Pues ya solo nos faltaría hacer que aparezca una opción para hacer login con este proveedor, esto se hace editando el flujo default-authentication-flow

Y editas la etapa

Y añades desde las fuentes disponibles a Fuentes seleccionadas

Ahora cuando entres en tu authentik ya te aparecerá la opción nueva para acceder. La primera vez que accedas debes aprobar unos permisos especiales a Microsoft:

Ha sido un proceso un poco largo, pero ahora ya tenéis centralizada la información de seguridad y cualquier aplicación que pueda conectarse a authentik podrá usar los usuarios de nuestro dominio… Ahora queda que lo probéis vosotros.

El mejor gestor de fotos autohospedado

Durante mucho tiempo he ido guardando las fotos que hacía, primero con varias cámaras digitales, posteriormente con distintos móviles en un directorio (o varios) de mi ordenador (posteriormente de alguno de los NAS que he montado). Preservar las fotos es algo que es muy importante para mi, son recuerdos enlatados que te permiten revivir los buenos (o no tan buenos) momentos del pasado y que sería traumático perder del todo.

El caso es que para poder clasificarlas, al menos por fecha, he estado usando varias opciones, google nos permitía guardar las fotos en una resolución menguante, aunque decía que así eran ilimitadas, hubo un momento en que Amazon ofreció lo mismo, pero se perdió en la bruma del tiempo, flickr y 500px se volvieron de pago y solo eran para fotos públicas. Finalmente termine utilizando el programa shotwell para ubuntu que me permitía importar las fotos nuevas y guardarlas en local… Synology también tenía su propio programa de fotografías pero era… muy malo.

Hasta que finalmente descubrí immich. Este sistema, open source completo, te permite guardar, organizar y gestionar tus fotos y videos fácilmente sin sacrificar privacidad. Te comento aquí los pasos más sencillos para tenerlo funcionando:

Básicamente tienes que tener un host o máquina virtual que tenga, al menos, 4Gb de memoria, un montón de disco duro para guardar todas las imágenes, docker y docker compose instalados.

Luego hay ciertas cosas que podrías querer hacer con IA y para las que podría venir bien tener una tarjeta gráfica… Pero eso es otra historia que no vamos a contar aquí.

Este sería el docker-compose.yml


name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - '2283:2283'
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    container_name: immich_redis
    image: docker.io/valkey/valkey:8-bookworm@sha256:fec42f399876eb6faf9e008570597741c87ff7662a54185593e74b09ce83d177
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    restart: always

volumes:
  model-cache:

Luego hay que crear un .env con los valores personalizados para nuestra instalación:


UPLOAD_LOCATION=./library
DB_DATA_LOCATION=./postgres
TZ=Europe/Madrid
IMMICH_VERSION=release
DB_PASSWORD=pon-aqui-un-password-para-postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

Obviamente elige el directorio que quieras para la bbdd y de donde vas a guardar las fotos y… Ya estaría.

docker compose up -d

Y ya tendrías una instalación vacía de immich lista para empezar a jugar. Lo primero que tendrás que hacer es crear la primera cuenta del sistema, que será de administración conectando con http://<dir-ip>:2283. A partir de ese momento ya podrás empezar a subir fotos como si no hubiese un mañana… De hecho puedes subir directorios enteros. Eso si, el tratamiento de las fotos tarda un poco porque se dedica a recopilar los datos internos de las fotos y a reconocer caras (para que luego puedas navegar por ellas), pero una vez que ya ha cogido ritmo cada foto subida va más rápido.

¿Qué más?

Es muy recomendable usar la aplicación móvil para sincronizar las fotos del teléfono con immich automáticamente, os dejo las direcciones para Android y iphone:

Cuando arranquéis la app os pedirá que pongáis la dirección de vuestro servidor (si tenéis una ip local solo funcionará dentro de vuestra red) y luego os pedirá que os autentiqueis momento a partir del cual ya podréis hacer copia de seguridad de los albumes que queráis de vuestro móvil (yo solo subo los de la cámara, whatsapp y demás no son demasiado «preservables»).

Y con esto ya estaría todo (salvo que queráis poner un proxy inverso para acceder desde fuera de vuestra red o un tunel de cloudflare o similar)… Eso si, reservad disco suficiente porque vais a necesitarlo…

Instala tu propia perplexity ia (perplexica)

Uno de los sistemas de IA más completos para las actividades diarias es perplexity.ia un sistema que no solo responde preguntas (usando varios proveedores de IA) sino que realiza una búsqueda en internet previa de los conceptos principales por los que estamos preguntando.

El problema principal es que, además de que es un sistema de pago, que todos los datos se los estamos pasando a una empresa que, a pesar de los términos y condiciones que nadie se lee, es posible que esté usando nuestros datos para sus propios fines. Sin embargo, he descubierto que hay un sistema open source que te permite montar un sistema similar a perplexity… El nombre ya da alguna pista: preplexica.

El repositorio principal lo podéis encontar aquí: https://github.com/ItzCrazyKns/Perplexica

En este post vamos a ver cómo utilizar el montaje que hicimos en un post anterior: Como tener tu propia IA en casa donde terminamos teniendo un servidor ollama para tener instalados los modelos open source que queramos y un servidor webui para poder interrogar a los modelos. Lo primero que vamos a hacer es modificar el docker-compose.yml para compartir el puerto por el que hablar con ollama de esta manera:

    ports:
      - "11434:11434"

Esto hace que ya esté disponible desde fuera del contenedor los modelos de ollama. Lo siguiente que tenemos que hacer es bajarnos el repositorio de Perpléxica

git clone https://github.com/ItzCrazyKns/Perplexica
cd Perplexica
cp sample.config.toml config.toml

Dado que el puerto 3000 que es en el que se ejecuta el servidor ya está ocupado por nuestro servicio webui vamos a cambiarlo al 3001 en el archivo docker-compose.yml que hay dentro del directorio Perplexica y lo dejaremos así (el otro puerto que utiliza es el 4000, si ese lo tenemos ocupado podemos cambiarlo en el otro servicio):

    ports:
      - 3001:3000

Luego modificaremos el archivo config.toml que copiamos anteriormente y modificaremos esta parte:

[MODELS.OLLAMA]
API_URL = "http://192.168.1.34:11434"

Poniendo la IP de la máquina en la que se está ejecutando. Ya solo tenemos que levantar el docker… docker compose up -d y conectar con http://localhost:3001 y ya tendremos nuestro intefaz de búsqueda

En la configuración (icono de abajo a la izquierda) podemos configurar los modelos que queremos utilizar:

Y si tenemos Claves de APIs de pago podemos incluirlas igualmente:

Y, con esto ya podremos empezar a preguntar a nuestro nuevo sistema de búsqueda (lo que tarde dependerá más del modelo elegido que de las búsquedas). Lo bueno es que puedes decirle que busque imágenes y videos a la vez:

Así que, ya sabéis, si tenéis una tarjeta gráfica lo suficientemente potente para correr un modelo de AI interesante, podéis mezclarlo con las búsquedas por internet de varias fuentes de una manera muy sencilla..

Movistar Bloquea Cloudflare: Impacto en Webs y Solución

Aunque pareciese que no tiene nada que ver una cosa con otra, lo que es cierto es que estas últimas semanas han ido ligadas de la mano, pero de la manera mala. Dejadme que me explique.

El caso es que una de las aplicaciones que mantenemos empezó en un momento dado a recibir incidencias como si estuviese caída. El servidor estaba bien y yo desde mi oficina llegaba perfectamente, después de mucho indagar descubirmos que no se llegaba desde las conexiones Movistar u O2, además, lo que vimos es que había ya quejas de otros servidores que estaban afectados y solo tenían en común una cosa, que estaban detrás de Cloudflare. La cosa es que media internet utiliza los servicios de cloudflare, como CDN o como protección contra ataques DDOS o como proxy para ahorrar ancho de banda. Entendimos que debía ser un problema de enrutamiento de Movistar y desactivamos cloudflare momentaneamente a la espera de que lo solucionasen… Estamos hablando del día 3 de febrero.

Esto estuvo pasando durante toda la tarde, llegado un momento nos dijeron que ya volvía a funcionar (y que todo debería ser una avería, no nos dijeron otra cosa)

Peeero, casualidades de la vida, el día 8 se jugaba el derbi y, mira por donde, resulta que empezamos a recibir incidencias de nuevo… Vuelta a desactivar cloudflare y esperando de nuevo… Pero esta vez no lo arreglaron, el domingo seguía la cosa igual:

Y esto se quedó así hasta el lunes (qué curioso!) sin que nadie se dignase dar ninguna explicación ni disculpa. Miles de negocios perdidos y webs expuestas a ataques sin remedio por alimentar a las alimañas del deporte. Era tan evidente que ya se publicó como noticia en varios medios (https://hipertextual.com/2025/02/movistar-cloudflare-futbol). El caso es que estuvo funcionando bien hasta los partidos de champions en los que se repitieron cortes de 15 minutos alternos (igual esto había cantado demasiado).

¿Qué hacer?

Ahora nos surge el problema de qué hacer el fin de semana. No podemos irnos a descansar porque si nos vuelven a cortar cloudflare tendríamos que volver a desactivarlo a mano, así que, para todos aquellos que tengáis cloudflare os recomiendo usar algo como esto:

import requests
import subprocess
import os

def check_ip_reachable(ip):
    try:
        result = subprocess.run(['ping', '-c', '4', ip], capture_output=True, text=True, timeout=10)
        return result.returncode == 0
    except subprocess.TimeoutExpired:
        return False

def pause_cloudflare(domain, email, api_key):
    headers = {
        'X-Auth-Email': email,
        'X-Auth-Key': api_key,
        'Content-Type': 'application/json'
    }
    
    # Obtener el Zone ID
    zone_response = requests.get(f'https://api.cloudflare.com/client/v4/zones?name={domain}', headers=headers)
    zone_data = zone_response.json()
    
    if not zone_data['success']:
        print("Error al obtener el Zone ID")
        return False
    
    zone_id = zone_data['result'][0]['id']
    
    # Pausar el servicio
    pause_url = f'https://api.cloudflare.com/client/v4/zones/{zone_id}'
    pause_data = {'paused': True}
    
    response = requests.patch(pause_url, headers=headers, json=pause_data)
    result = response.json()
    
    if result['success']:
        print(f"Servicio pausado para el dominio {domain}")
        return True
    else:
        print(f"Error al pausar el servicio: {result['errors']}")
        return False

# Configuración
ip_to_check = "192.168.1.1"  # Reemplaza con la IP que quieres comprobar
domain = "ejemplo.com"  # Reemplaza con tu dominio
cloudflare_email = "[email protected]"  # Reemplaza con tu email de Cloudflare
cloudflare_api_key = "tu_api_key"  # Reemplaza con tu API key de Cloudflare

# Comprobar si la IP está accesible
if not check_ip_reachable(ip_to_check):
    print(f"La IP {ip_to_check} no está accesible. Pausando el servicio en Cloudflare...")
    if pause_cloudflare(domain, cloudflare_email, cloudflare_api_key):
        print("Servicio pausado exitosamente")
    else:
        print("No se pudo pausar el servicio")
else:
    print(f"La IP {ip_to_check} está accesible")

Este programita básicamente lo que hace es comprobar si una IP responde al ping y si no lo hace pone en pausa cloudflare para el dominio que queramos. Para ello solo hay que configurarlo con la IP, el dominio y los datos del API de cloudflare de nuestra cuenta. Lo recomendable es hacer un bucle que lo chequee cada cierto tiempo y (código que todavía no he hecho) que cuando vuelva la conectividad se vuelva a activar cloudflare.

Para mi estas actuaciones son completamente ilegales y atentan contra la neutralidad de la red. ¿Qué será lo siguiente? ¿Evitar que visitemos blogs de ideologías no aprobadas?, ¿Hacer inaccesible el porno? Por desgracia en mi casa usamos O2, pero igual tengo que cambiarme visto lo visto.

Monta un nodo bitcoin en 5 minutos

Bueno, cinco minutos es lo que dedicarás a configurar el software, pero luego tendrá que bajarse todo el blockchain y tardará una eternidad y se comerá todo el disco duro que tengamos disponible, pero bueno, vamos a intentar montar un nodo completo bitcoin para testnet (estamos probando) de manera que podamos hacerle consultas sin tener que llamar a otros servicios.

En un próximo post ya os contaré como hacer un explorador completo (con las mismas restricciones de tiempo que este). Por ahora vamos a preparar los materiales. Vamos a necesitar una máquina con ubuntu (o el sistema operativo de vuestra preferencia) y unos 200Gb de disco (para testnet, si queremos la blockchain de mainnet serán como 800Gb). Yo, en mi caso lo he hecho sobre un contenedor ubuntu en proxmox (ya os contaré qué tal con proxmox cuando tenga un rato).

Para crear mi contenedor he usado un script de la comunidad (aqui) pero vosotros podéis usar un ordenador de casa o una máquina virtual que pueda ejecutar docker y docker compose.

Yo la he configurado con 4 CPUs y 4Gb de memoria y me ha servido perfectamente. No os olvidéis de tener los 200Gb disponibles y montados en algún sitio de vuestra máquina. Una vez que tenemos docker y docker compose instalados y funcionando solo tenemos que crear un docker-compose.yml con este contenido:

services:
  bitcoin-testnet:
    image: bitcoin/bitcoin:28.0rc1
    container_name: bitcoin-testnet
    ports:
      - "18443:18443"
      - "18444:18444"
    command:
      - -printtoconsole
      - -testnet=1
      - -rest
      - -rpcbind=0.0.0.0
      - -rpcallowip=0.0.0.0/0
      - -rpcport=18443
      - -rpcuser=miusuario
      - -rpcpassword=mipass
      - -server
      - -txindex
    volumes:
      - /bitcoin-data:/home/bitcoin/.bitcoin
    restart: always

En este caso nos vamos a bajar una imagen preconfigurada de bitcoin (aunque si no os fiáis siempre podéis compilar la vuestra sacando el Dockerfile de github. Abriremos los puertos 18443 y 18444 para poder acceder al nodo desde fuera (recordad, si tenéis nat hacer una redirección del puerto en el router). Además, tenemos nuestro disco con 200Gb en el directorio /bitcoin-data y lo montamos en /home/bitcoin/.bitcoin en la imagen.

Las siguientes opciones son para ver el log en consola, usar testnet, activar el RPC por rest y enlazarnos con todas las IPs de la imagen. Además, configuramos que se usará un usuario miusuario con password mipass, se ejecutará como servidor y vamos a mantener índices de todo el blockchain…

Y con esto ya podemos lanzarlo:

docker compose up -d

Si todo va bien en unas cuantas (muchas) horas ya tendríamos una copia de la blockchain de testnet y podremos preguntar, por ejemplo, cual es el último bloque (suponiendo que nuestra máquina tenga la IP 192.168.1.55) usando curl, por ejemplo:

curl --user miusuario:mipass --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockcount", "params": []}' -H 'content-type: text/plain;' http://192.168.1.55:18443

Que nos devolvería algo como esto (el número de bloque, obviamente) será distinto en vuestro caso:

{"result":3774033,"error":null,"id":"curltest"}

Y, lo dicho, intentaré que el próximo post sea de cómo hacer un explorador de testnet que podamos utilizar via web… Id preparando más disco duro.