WebRTC en producción:
el bug del certificado DTLS
El endpoint PJSIP estaba en la base de datos. El softphone enviaba credenciales correctas. El registro SIP fallaba con "No matching endpoint found" después del 401. Horas de diagnóstico para descubrir que Asterisk descartaba el endpoint silenciosamente por no poder leer la clave privada del certificado DTLS.
El contexto: PBX multi-tenant con WebRTC
Estaba construyendo un PBX SaaS multi-tenant sobre Asterisk 20 + Kamailio + PJSIP Realtime. Los endpoints viven en PostgreSQL, sin ningún archivo pjsip.conf con extensiones. Kamailio maneja el tenant-routing inyectando el X-Tenant-ID en el header SIP, y Asterisk valida contra la DB via sorcery.
El softphone es JsSIP corriendo en el browser, conectando via WebSocket a Asterisk. Para WebRTC el audio va cifrado con DTLS-SRTP. Asterisk necesita un certificado X.509 para hacer el handshake DTLS con el browser.
El síntoma
JsSIP conectaba al WebSocket perfectamente. Enviaba el REGISTER. Asterisk respondía con 401 Unauthorized (normal: desafía credenciales). JsSIP re-enviaba con las credenciales correctas. Y ahí:
[ERROR] No matching endpoint found for 'REGISTER' from '[email protected]'
La fila id='1001' existía en ps_endpoints. Las credenciales en ps_auths eran correctas. pjsip show endpoint 1001 en la CLI de Asterisk devolvía: "Unable to find object 1001 in container".
El endpoint estaba en la base de datos pero Asterisk no lo "veía". Como si nunca se hubiera cargado en memoria.
Diagnóstico: core debug + pjsip logger
La clave estuvo en habilitar debug en runtime:
asterisk -rx 'core set debug 3'
asterisk -rx 'pjsip set logger on'
En los logs apareció algo que nunca había visto antes:
[DEBUG] res_pjsip_endpoint_identifier_user.c: Endpoint '1001' failed to instantiate
[ERROR] res_config_pgsql.c: Failed to load DTLS certificate
[ERROR] /var/lib/asterisk/keys/asterisk.key: Permission denied
Sorcery intentaba instanciar el endpoint desde la DB, cargaba la configuración DTLS, intentaba leer la clave privada del certificado y fallaba con Permission denied. Ante ese error, descartaba el endpoint completo. Sin warning visible en logs normales. Sin entrada en el container de objetos.
La causa raíz: el certificado creado como root
El certificado DTLS lo había generado durante el setup inicial del servidor, corriendo como root:
openssl req -new -x509 -days 3650 -nodes \
-out /var/lib/asterisk/keys/asterisk.crt \
-keyout /var/lib/asterisk/keys/asterisk.key \
-subj '/CN=aria-pbx'
El resultado: ambos archivos con owner: root y permisos 600. Asterisk corre como el usuario asterisk, no puede leer una clave privada de root con permisos 600.
Lo que lo hace especialmente traicionero: Asterisk no levanta ningún error durante el startup. Solo falla silenciosamente al instanciar el endpoint la primera vez que alguien intenta registrarse.
El fix en vivo
# Corregir ownership y permisos
docker exec asterisk chown asterisk:asterisk \
/var/lib/asterisk/keys/asterisk.key \
/var/lib/asterisk/keys/asterisk.crt
docker exec asterisk chmod 640 \
/var/lib/asterisk/keys/asterisk.key
# Recargar PJSIP para que vuelva a intentar instanciar endpoints
docker exec asterisk asterisk -rx 'module reload res_pjsip.so'
Después del reload, pjsip show endpoint 1001 mostraba el endpoint correctamente. El registro funcionó en el primer intento.
El fix permanente: Dockerfile
La solución correcta es generar el certificado durante el build de la imagen con el ownership correcto desde el inicio:
RUN mkdir -p /var/lib/asterisk/keys \
&& openssl req -new -x509 -days 3650 -nodes \
-out /var/lib/asterisk/keys/asterisk.crt \
-keyout /var/lib/asterisk/keys/asterisk.key \
-subj '/CN=aria-pbx' \
&& chown asterisk:asterisk \
/var/lib/asterisk/keys/asterisk.crt \
/var/lib/asterisk/keys/asterisk.key \
&& chmod 640 /var/lib/asterisk/keys/asterisk.key
Config PJSIP para WebRTC
Para que el endpoint soporte WebRTC con DTLS correctamente, los campos en ps_endpoints deben incluir:
media_encryption = dtls
dtls_auto_generate_cert = no
dtls_cert_file = /var/lib/asterisk/keys/asterisk.crt
dtls_private_key = /var/lib/asterisk/keys/asterisk.key
dtls_verify = fingerprint
dtls_setup = actpass
use_avpf = yes
force_avp = yes
ice_support = yes
bundle = yes
rtcp_mux = yes
transport = transport-ws
Si el endpoint tiene transport=transport-udp (el default), Asterisk rechaza los REGISTER que llegan via WebSocket. Los endpoints WebRTC deben apuntar explícitamente al transport WebSocket.
La lección de fondo
Sorcery Realtime en Asterisk tiene un comportamiento que hay que conocer: cuando falla al instanciar un objeto (por cualquier razón: error de permisos, campo inválido, dependencia faltante), lo descarta silenciosamente. El objeto simplemente no existe en memoria. El síntoma siempre es el mismo: "No matching endpoint found" aunque la fila esté perfectamente en la DB.
El diagnóstico correcto siempre empieza por:
core set debug 3para ver los intentos de instanciación.pjsip set logger onpara ver el flujo completo del registro.- Buscar explícitamente errores de carga de recursos (certs, archivos de configuración).
- Verificar que Asterisk pueda leer todos los archivos que referencia, no solo que existan.
En sistemas Realtime con Sorcery, la presencia de un objeto en la DB no garantiza que Asterisk lo cargue en memoria. Siempre verificar que todos los recursos referenciados (certs, archivos) sean legibles por el usuario que corre el proceso — no solo por root.