Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Transporte seguro: configuración de extremo a extremo

Esta página es la referencia completa de configuración para conectar un cliente a un demonio de forma segura, en tres topologías:

  1. Cliente directo al daemon - WSS con TLS mutuo, sin relé.
  2. Demonio a relay: el demonio mantiene un puente saliente hacia un relay designado para que sea accesible desde detrás de NAT/CGNAT.
  3. Del cliente al relé y del relé al demonio: el cliente llega al demonio a través de ese relé, mientras que el mTLS real entre cliente<->demonio sigue terminando en el demonio.

Para la guía de inicio rápido de 60 segundos, consulta Configuración remota (WSS); esta página es la guía más detallada, parámetro por parámetro.

Modelo mental: dos sobres, un límite de confianza

Hay dos capas de TLS, y solo una de ellas es el límite de seguridad:

  • mTLS interno (la frontera real). Solo TLS 1.3, con autenticación mutua. El cliente presenta un certificado emitido por el daemon; el daemon presenta su certificado hoja de servidor. Este es el plano RPC. No hay ninguna ruta solo de servidor / no autenticada en él: siempre se requiere un certificado de cliente.
  • TLS externo (un límite de metadatos). Cuando hay un relay en la ruta, el relay termina una sesión externa de TLS + WebSocket y reenvía texto cifrado opaco. Nunca contiene una clave que pueda leer el RPC interno. En la topología directa no hay ninguna capa externa.

Puertos predeterminados (todos configurables):

PlanoPredeterminadoConfig
Demonio WSS (RPC mTLS interno)9781[wss].port
Punto de conexión de inscripción del demonio9782[enroll].port
Relay (TLS externo + WS)8443retransmitir --bind / [bind]

En todo el documento, <data_dir> es el directorio de datos del demonio (normalmente ~/.zeroclaw) y <config-dir> es el directorio de configuración de zerocode del cliente (--config-dir, normalmente ~/.zeroclaw). Los archivos de configuración no expanden ~; usa rutas absolutas.


Topología 1: cliente directo al demonio

zerocode  ===== mutual-TLS WSS (TLS 1.3) =====>  daemon  [wss] :9781

1a. Lado del demonio

Habilita el listener WSS. La opción segura predeterminada es dejar que el demonio genere automáticamente su propia CA y su certificado de servidor durante el primer arranque, para que no tengas que gestionar manualmente ningún material TLS:

[wss]
enabled = true
# bind = "0.0.0.0"   # predeterminado
# port = 9781        # predeterminado
# Deja cert_path/key_path vacíos para generar automáticamente un certificado de servidor en
# <data_dir>/tls/ durante el primer arranque. Configúralos solo para usar tu propio certificado de servidor.

En el primer inicio con [wss].enabled = true, el demonio escribe, en <data_dir>/tls/ (directorio con modo 0700):

ArchivoPropósitoModo
ca.crtCertificado de CA por demonio (público)umask predeterminada
ca.keyClave privada de la CA (firma certificados de cliente)0600
server.crtCertificado hoja del servidor WSS (SANs localhost, 127.0.0.1)umask predeterminada
server.keyclave privada del servidor WSS0600

Las claves privadas se escriben con permisos 0600; los certificados públicos usan la umask del proceso. El directorio tls/ en sí tiene permisos 0700.

La CA nunca se rota silenciosamente: si existen ca.crt y ca.key, se reutilizan. La duración de la CA generada automáticamente es de 10 años; el certificado hoja del servidor es de ~27 meses; los certificados de cliente emitidos son de 30 días.

Abre el puerto (sudo ufw allow 9781/tcp) e inicia el demonio. Deberías ver una línea de registro que indique que el escuchador WSS está activo en 0.0.0.0:9781.

Ahora el cliente necesita un certificado. Hay dos formas de obtenerlo.

1b. Lado del cliente - opción A: inscripción (recomendada)

La inscripción proporciona a un cliente sin certificado su primer certificado a través de un punto de conexión autenticado por el servidor y protegido mediante emparejamiento, sin gestionar certificados manualmente. Actívala:

[enroll]
enabled = true
# bind = "0.0.0.0"   # predeterminado
# port = 9782        # predeterminado
# Requiere [wss] habilitado y una clave de CA del daemon (generada automáticamente arriba o BYO+key).
# Si falta la clave de CA, el endpoint falla de forma segura y los certificados deben aprovisionarse
# por un canal externo.

El demonio imprime un código de emparejamiento de un solo uso y una cadena de autenticación corta (SAS) en su consola/registro al iniciarse. El código es de un solo uso y caduca 10 minutos después de generarse; es la única credencial de tipo portador para la emisión de certificados y aparece en consolas y registros, por lo que un código copiado debe dejar de funcionar poco después de que el operador lo haya utilizado. Un código caducado se rechaza y se elimina; genera un reemplazo mediante la API de emparejamiento de la puerta de enlace cuando necesites uno nuevo. Luego, en la estación de trabajo:

# Interactivo: un cliente sin certificado se registra automáticamente en la primera conexión.
zerocode --connect wss://<remote-host>:9781

# O explícitamente / de forma no interactiva:
zerocode --enroll --connect wss://<remote-host>:9781

zerocode solicita el código de emparejamiento, genera localmente una clave P-256 y una CSR (la clave privada nunca sale del dispositivo) y muestra el SAS. Confirma que el SAS coincide con el que imprimió el demonio (esto detecta una CA de intermediario) y almacena en caché, en <config-dir>/tls/:

ArchivoPropósitoModo
client.crtCertificado de cliente emitidoumask predeterminada
client.keyClave privada del cliente (generada localmente)0600
ca.crtCadena de CA del demonio, fijada para el plano RPCumask predeterminada
profile.jsondevice_id, not_after y perfil de retransmisión almacenados en cachéumask predeterminada

Cada ejecución posterior no requiere configuración (zerocode --connect wss://<remote-host>:9781, o simplemente zerocode si uri está en la configuración). El certificado se renueva automáticamente aproximadamente al 50 % de su periodo de validez (unos 15 días) mediante la sesión mTLS activa; un certificado revocado no puede renovarse por sí mismo.

Valores predeterminados del punto de conexión de inscripción: --enroll-host usa de forma predeterminada el host de --connect; --enroll-port usa de forma predeterminada 9782.

La primera versión requiere intencionadamente un código de emparejamiento para cada inscripción. La opción reservada allow_unpaired_enrollment se rechaza durante el inicio del demonio hasta que el cliente disponga de un ancla de confianza explícita que no requiera código, como una huella digital fijada de la CA del demonio. Esto evita que la ruta TLS de inscripción provisional se convierta en una confianza ciega en el primer uso.

1c. Lado del cliente - opción B: certificado emitido por el operador

Si prefiere generar un certificado en el daemon y copiarlo fuera:

# En el host del demonio. --out-dir también escribe un ca.crt/client.crt/client.key de sustitución directa.
zeroclaw security issue-client-cert --name my-laptop --out-dir /tmp/my-laptop-tls
# añade --force para sobrescribir un certificado existente con este nombre

Copia los tres archivos en <config-dir>/tls/ del cliente con los nombres ca.crt, client.crt y client.key (entonces zerocode --connect wss://host:9781 los encontrará automáticamente), o indica sus rutas explícitamente:

zerocode --connect wss://<remote-host>:9781 \
  --tls-ca-cert  /path/ca.crt \
  --tls-client-cert /path/client.crt \
  --tls-client-key  /path/client.key

Configuración equivalente (para que zerocode funcione directamente):

[connection.wss]
uri = "wss://<remote-host>:9781"

[connection.wss.tls]
ca_cert_path     = "/abs/path/ca.crt"
client_cert_path = "/abs/path/client.crt"
client_key_path  = "/abs/path/client.key"

Un cliente sin certificado que llega al plano WSS sin inscribirse recibe un mensaje accionable de «inscríbete primero» (y el daemon registra el cliente no migrado rechazado); nunca se queda bloqueado silenciosamente. --tls-skip-verify solo relaja la verificación del servidor para un daemon de desarrollo autofirmado; el certificado del cliente sigue siendo obligatorio.


Topología 2: del demonio al relé

daemon  ====== outbound: register + bridge ======>  relay  :8443
[relay]                                             (blind forwarder)

El demonio se conecta al relé, demuestra una identidad Ed25519 estable y registra un node-id. Posteriormente, los clientes se conectan a ese node-id (Topología 3). El relé solo reenvía texto cifrado.

2a. Ejecuta el relay (zerorelay)

Configura con relay.toml (consulta apps/zerorelay/relay.example.toml); cada opción de CLI anula el valor correspondiente del archivo. La sección [admission] se recarga en caliente con SIGHUP; se rechaza una recarga que convertiría un relay público en una admisión abierta sin token sin la activación explícita, y la política anterior sigue activa.

# relay.toml
bind = "0.0.0.0:8443"

[tls]
# Omita cert/key para APROVISIONAR AUTOMÁTICAMENTE un certificado TLS externo en dir durante la primera ejecución (sin
# openssl). Configure sans con los nombres de host/IP públicos del relay.
dir  = "/data/tls"
sans = ["relay.example.com"]
# O proporcione su propio cert (p. ej., un certificado de una CA pública):
# cert = "/etc/zerorelay/fullchain.pem"
# key  = "/etc/zerorelay/privkey.pem"

[admission]
# "open" admite cualquier demonio firmado (sujeto a la lista deny); "allowlist" admite
# solo las huellas digitales de las claves públicas de los demonios enumerados. deny siempre tiene prioridad.
mode = "open"
allow = []
deny  = []
# Un relay público (que no sea de loopback) DEBE controlar el registro: establezca aquí un secreto compartido
# (cada demonio lo presenta mediante [relay] relay_token) o use mode = "allowlist".
# De lo contrario, un relay OPEN sin token en un bind público no puede iniciarse, porque
# cualquier demonio en Internet podría registrarse y ocupar node-ids no reclamados. (Un
# bind de loopback para desarrollo local está exento; un relay público configurado como open de forma deliberada
# puede anular esta restricción con allow_public_open = true.)
relay_token = "change-me-to-a-long-random-secret"

[limits]
max_conns_per_node     = 256
idle_timeout_secs      = 300
lease_ttl_secs         = 300
accept_burst_per_ip    = 30
accept_rate_per_ip     = 10.0
connect_burst_per_node = 60
connect_rate_per_node  = 20.0

Ejecútalo:

zerorelay --config /etc/zerorelay/relay.toml
# de forma equivalente, solo opciones (un enlace público necesita un token o una lista de permitidos; de lo contrario, el
# relay refuses to start):
zerorelay --bind 0.0.0.0:8443 --tls-san relay.example.com \
  --relay-token change-me-to-a-long-random-secret

Cuando se omiten --tls-cert/--tls-key, el relay aprovisiona automáticamente una CA + un certificado de servidor en el directorio de TLS (orden de resolución: $ZERORELAY_DATA_DIR/tls, luego $HOME/.zerorelay/tls y, por último, ./zerorelay/tls); localhost y 127.0.0.1 siempre se incluyen en los SAN. El ca.crt aprovisionado automáticamente es en el que confían un demonio/cliente para el TLS externo del relay.

Control de acceso. El modo open, junto con un relay_token opcional, es el mecanismo de control de acceso más sencillo. El modo allowlist usa como clave la huella digital de la clave pública de registro del demonio (el hexadecimal SHA-256 de la clave Ed25519 en <data_dir>/relay/registration.key); añade huellas digitales a allow (y vuelve a cargar con kill -HUP <pid>). Un ID de nodo queda vinculado a la clave pública de su primer registrante, por lo que una clave diferente no puede secuestrar un ID de nodo activo (obtiene node_taken).

Docker. apps/zerorelay/Dockerfile ejecuta distroless con CMD ["--config", "/etc/zerorelay/relay.toml"] y un HEALTHCHECK sin shell zerorelay healthcheck --addr; compose.yaml monta un volumen en /data para que el TLS autoprovisionado persista. Expón 8443.

2b. Dirige el demonio al relé

[wss]
enabled = true          # OBLIGATORIO: el relay reenvía al listener WSS local

[relay]
enabled = true
url = "relay.example.com:8443"
# node_id: dejar VACÍO (recomendado) para generar automáticamente + conservar una
# capacidad aleatoria de 128 bits en <data_dir>/relay/node_id. Establecerlo solo para fijar un id específico.
# token = "change-me"   # debe coincidir con [admission].relay_token del relay, si se establece

# Confianza en el certificado EXTERIOR del relay: elegir UNO:
relay_ca_path = "/path/to/relay/ca.crt"   # confiar en la CA (autofirmada) del relay
# tofu = true                              # O fijar el certificado hoja del relay en el primer uso
# relay_insecure = true                    # O omitir la verificación exterior (solo para desarrollo)
# (dejar los tres sin establecer para usar las raíces públicas integradas, para un relay con CA pública)

[relay] requiere que [wss] esté habilitado (el relay actúa como puente hacia 127.0.0.1:<wss.port>) y falla de forma segura si url está vacío. Al iniciarse, el demonio registra el identificador del nodo con la indicación “proporcione este valor a los clientes como –relay-node”; también puede leerlo de <data_dir>/relay/node_id. La clave de registro estable del demonio se crea en <data_dir>/relay/registration.key (0600).

Precedencia de confianza del certificado externo (de mayor a menor): relay_insecure > relay_ca_path > un pin almacenado en <data_dir>/relay/relay_pin (explícito o TOFU) > tofu > raíces públicas. Por tanto, configurar una CA reemplaza un pin obsoleto sin eliminarlo. Con tofu = true, la huella digital observada del certificado hoja del relay se fija en <data_dir>/relay/relay_pin, y el registro entrega ese mismo pin a los clientes para que fijen la misma hoja.

2c. (opcional) rotación de node-id y mTLS externo

[relay]
node_id_rotation_days = 30   # auto-rotate the auto-minted id every N days (0 = never)

La rotación genera un identificador nuevo, lo mantiene activo junto al anterior durante un periodo de gracia de 10 minutos para que los clientes con operaciones en curso no se desconecten y, después, retira el identificador antiguo; el nuevo identificador llega a los clientes en banda durante su próxima renovación del certificado. Fuerza una ahora con zeroclaw security relay-rotate-node-id (solo en el modo auto-mint; un node_id fijado nunca se rota).

Para un relay que también autentica demonios en la capa externa, configura en el relay [admission].outer_client_auth = "required" + outer_client_ca, y en el demonio [relay].outer_client_cert / outer_client_key. Esto se añade al TLS externo y nunca afecta al mTLS interno.


Topología 3: del cliente al relé y del relé al demonio

zerocode  ==outer TLS+WS==>  relay  ==forwards ciphertext==>  daemon
   \________________ inner mutual-TLS (TLS 1.3) terminates here _______________/

Esto combina las topologías 1 y 2: el cliente necesita un certificado de cliente interno (registro, como en 1b) y las coordenadas del relay (dirección, node-id y confianza en el certificado externo del relay).

3a. La opción sencilla: el registro incluye el perfil de retransmisión

Cuando el demonio tiene [relay] configurado, su respuesta de inscripción incluye un perfil de relay (relay_url, node_id y el relay_cert_pin de hoja del relay). Por lo tanto, una sola inscripción aprovisiona todo:

zerocode --enroll --connect wss://<daemon-host>:9781

zerocode almacena en caché el certificado interno y el perfil del relay en <config-dir>/tls/profile.json. Más tarde, un zerocode sin opciones llega al demonio a través del relay; ya conoce la dirección del relay, el node-id y el PIN.

3b. La ruta manual

Proporcione explícitamente al cliente las coordenadas del relé. El certificado interno sigue procediendo de la inscripción o de --tls-* (Topología 1):

zerocode \
  --relay      relay.example.com:8443 \
  --relay-node <node-id-from-daemon-log> \
  --relay-ca   /path/to/relay/ca.crt
# inner mTLS material: from <config-dir>/tls (after enrolling), or pass --tls-* flags

Elige exactamente un modo de confianza para el certificado externo del relé, igual que en el demonio:

BanderaSignificado
--relay-ca <pem>Confía en la CA (autofirmada) del relé
--relay-pin <sha256>Fija la hoja externa del relé (normalmente entregada durante la inscripción)
--relay-tofuConfía en el primer uso; guarda el pin en <config-dir>/relay/relay_pin
--relay-insecureOmitir la verificación externa (solo para desarrollo/certificados autofirmados)
(ninguno)Usa las raíces públicas integradas (retransmisión de CA pública)
--relay-host <name>Sobrescribe el SAN esperado del certificado externo (de forma predeterminada, el host de --relay)

Equivalente de configuración (para que zerocode funcione sin más):

[connection.wss]
relay_url  = "relay.example.com:8443"
relay_node = "<node-id>"

La confianza externa del relay (--relay-ca / --relay-pin / --relay-tofu / --relay-insecure) se proporciona mediante las opciones o el PIN de inscripción almacenado en caché, no mediante las claves de [connection.wss].

3c. Directo primero con respaldo de retransmisión

Si proporciona al cliente tanto una dirección directa como un relay, este prefiere la ruta directa y recurre al relay; después vuelve a sondear y migra de nuevo:

zerocode --connect wss://<daemon-host>:9781 \
         --relay relay.example.com:8443 --relay-node <node-id>

Ajuste (en [connection.wss]):

ClavePredeterminadoSignificado
direct_attempts2Intentos directos antes de recurrir al relé
direct_timeout_secs3Tiempo de espera por intento de conexión directa
reprobe_secs30Intervalo de re-sondeo para volver al modo directo (0 desactiva)

En el modo de solo retransmisión (sin --connect/uri), la URL WSS interna se establece de forma predeterminada en wss://127.0.0.1:9781 porque el mTLS interno termina en el listener de bucle invertido del demonio; la dirección de retransmisión solo es el destino de conexión TCP.


Referencia de configuración

Demonio [wss]

ClavePredeterminadoDescripción
enabledfalseHabilitar el listener WSS de TLS mutuo
bind0.0.0.0Dirección de enlace
port9781Puerto de escucha
cert_path(vacío)Certificado de servidor proporcionado por el usuario; si está vacío, se genera automáticamente en <data_dir>/tls/
key_path(vacío)Clave de servidor propia; si está vacía, se genera automáticamente

Demonio [wss.client_auth] (opcional; mTLS siempre está activado de todos modos)

ClavePredeterminadoDescripción
enabledfalseUsa una CA propia; cuando es false, el demonio usa su CA generada automáticamente
ca_cert_path(vacío)CA en formato PEM utilizada para verificar certificados de cliente (modo BYO)
pinned_certs[]Si no está vacío, solo se aceptan certificados de cliente que coincidan con estas huellas digitales SHA-256
crl_path(vacío)Archivo de huellas digitales revocadas; si está vacío, usa el <data_dir>/tls/revoked materializado por el registro

Demonio [enroll]

ClavePredeterminadoDescripción
enabledfalseHabilitar el endpoint de inscripción (requiere [wss] + una clave de CA)
bind0.0.0.0Dirección de enlace
port9782Puerto de escucha
allow_unpaired_enrollment(vacío)Reservado; se rechazan los valores no vacíos hasta que exista un anclaje de confianza del cliente sin código

Demonio [relay]

ClavePredeterminadoDescripción
enabledfalseHabilita el puente de retransmisión (requiere [wss])
url(vacío)Dirección del relé host:port; obligatoria cuando está habilitado
node_id(vacío)Si está vacío, genera y persiste automáticamente un identificador de 128 bits; establece uno para fijarlo.
token(vacío, secreto)Secreto compartido presentado durante el registro
relay_ca_path(vacío)CA PEM para el certificado externo del relay; vacío usa las raíces públicas
relay_host(vacío)SAN esperado del certificado externo; si está vacío, se deriva de url
relay_insecurefalseOmitir la verificación del certificado externo (solo para desarrollo)
tofufalseFija la hoja de retransmisión en el primer uso en <data_dir>/relay/relay_pin
outer_client_cert(vacío)Certificado de cliente mTLS externo del daemon para la admisión del relay
outer_client_key(vacío)Clave de outer_client_cert
node_id_rotation_days0Rotar automáticamente el node-id generado automáticamente cada N días (0 = nunca)

Relay relay.toml

Section.keyPredeterminadoDescripción
bind0.0.0.0:8443Dirección de escucha (demonio + cliente)
[tls].cert / .key(autoaprovisionamiento)Identidad TLS externa; omita ambas para aprovisionarse automáticamente
[tls].dirdirectorio de datos /tlsDónde se escribe el certificado autoprovisionado
[tls].sans[]SAN adicionales (localhost, 127.0.0.1 siempre incluidos)
[admission].modeopenopen o allowlist
[admission].allow / .deny[]Huellas digitales de las claves públicas del demonio (la denegación prevalece)
[admission].relay_token(ninguno)Control de acceso opcional mediante secreto compartido
[admission].outer_client_authoffoff / optional / required (mTLS externo)
[admission].outer_client_ca(ninguno)CA en formato PEM para certificados de cliente externos
[admission].route_by_client_certfalseEnrutar según el node-id del CN del certificado externo
[limits].max_conns_per_node256Conexiones simultáneas de clientes por node-id
[limits].idle_timeout_secs300Descarta las conexiones de cliente inactivas después de N segundos
[limits].lease_ttl_secs300TTL de la concesión anunciado durante el registro (informativo en v1: la actividad de WebSocket es la regla de limpieza real)
[limits].accept_burst_per_ip / accept_rate_per_ip30 / 10.0Cubo de tokens de handshake por IP
[limits].connect_burst_per_node / connect_rate_per_node60 / 20.0Cubo de tokens de conexión por nodo
[limits].max_pending_handshakes256Sockets que han pasado por accept pero aún no se han clasificado
[limits].handshake_timeout_secs10Un único tiempo límite para TLS, la actualización de WS y el registro firmado
[limits].max_registered_nodes1024Daemons registrados simultáneamente (N+1 obtiene registry_full)

zerorelay CLI (anula relay.toml)

--config --bind --tls-cert --tls-key --tls-dir --tls-san (repetible) --registration-mode --allow (repetible) --deny (repetible) --relay-token --max-conns-per-node --idle-timeout-secs --lease-ttl-secs --status-file. Subcomandos: healthcheck [--addr 127.0.0.1:8443], status --file <path>.

zerocode [connection.wss] y CLI

clave [connection.wss]PredeterminadoAnulación de la CLI
uri(ninguno)--connect
relay_url(ninguno)--relay (requiere --relay-node)
relay_node(ninguno)--relay-node (requiere --relay)
direct_attempts2-
direct_timeout_secs3-
reprobe_secs30-
clave [connection.wss.tls]PredeterminadoAnulación de la CLI
ca_cert_path<config-dir>/tls/ca.crt--tls-ca-cert
client_cert_path<config-dir>/tls/client.crt--tls-client-cert (requiere una clave)
client_key_path<config-dir>/tls/client.key--tls-client-key (requiere un certificado)
skip_verifyfalse--tls-skip-verify

La confianza externa y la inscripción de Relay solo están disponibles mediante la CLI/caché: --relay-ca --relay-host --relay-insecure --relay-pin --relay-tofu --relay-client-cert --relay-client-key --enroll --enroll-host --enroll-port.

Diseño de archivos

Demonio <data_dir>/:

tls/ca.crt  tls/ca.key            per-daemon CA (key 0600)
tls/server.crt  tls/server.key    WSS server leaf (key 0600)
tls/ledger.db                     issued-cert ledger (SQLite)
tls/revoked                       revoked fingerprints (handshake-checked)
relay/registration.key            Ed25519 relay identity (0600)
relay/node_id                     auto-minted node-id
relay/relay_pin                   pinned relay outer-leaf fingerprint (TOFU)

Cliente <config-dir>/:

tls/client.crt  tls/client.key    client identity (key 0600)
tls/ca.crt                         pinned daemon CA
tls/profile.json                   device_id, not_after, cached relay profile
relay/relay_pin                    relay outer-leaf pin (--relay-tofu)

Relay <tls-dir>/: ca.crt, server.crt, server.key (autoprovisionados).

Actualización de un registro existente de certificados emitidos

tls/ledger.db incluye una versión del esquema. Un registro escrito por una revisión anterior se reconstruye en el mismo lugar la primera vez que el nuevo demonio lo abre, conservando cada certificado emitido y revocado junto con su identificador de dispositivo, validez y actor de auditoría. La reconstrucción se ejecuta en una única transacción: si no puede completarse, el demonio se niega a iniciarse y deja intacto el registro original en lugar de migrarlo parcialmente, y el error indica el nombre del archivo. No se requiere ninguna acción del operador, y los dispositivos registrados no necesitan volver a registrarse.

Certificados no entregados

El demonio registra una emisión en el libro mayor antes de entregar el certificado: al cliente que se está registrando, al cliente que está renovando o a los archivos issue-client-cert del operador. Ese orden es deliberado: la alternativa puede dejar en manos de alguien un certificado válido firmado por la CA sin una fila correspondiente en el libro mayor, y un certificado del que el libro mayor nunca ha tenido constancia no se puede listar ni revocar.

El coste es que un fallo intermedio - un cliente que se desconecta a mitad de la respuesta, un renombrado fallido - deja un registro active para un certificado que nadie recibió. Este registro se sigue por separado como no entregado y se revoca cuando tiene más de una hora, en el primero de:

  • cualquier nueva emisión o renovación de certificados,
  • cualquier conexión de inscripción,
  • cualquier reinicio del demonio u otra apertura del libro mayor.

No es un temporizador en segundo plano. Un demonio que no realiza ningún trabajo relacionado con certificados aplaza el barrido hasta su siguiente actividad de ese tipo; hasta entonces, el certificado permanece fuera de tls/revoked y aún pasaría la negociación de WSS, aunque no puede renovarse. Cualquier tráfico de inscripción o renovación - incluida una conexión que no logra autenticarse - basta para reconciliarlo.

Los certificados reconciliados se revocan, nunca se eliminan: la fila permanece en el libro mayor y la huella digital se registra en tls/revoked, exactamente igual que en una revocación realizada por un operador, con el actor de auditoría reconcile:undelivered para que el historial las distinga. security list-client-certs muestra únicamente certificados ACTIVE, por lo que un certificado reconciliado desaparece de ese listado - durante la respuesta a incidentes, consulta tls/revoked o el registro de auditoría para conocer el historial de revocaciones. Si un dispositivo informa de que un certificado que recibió dejó de funcionar, busca ese actor - significa que se perdió el registro de entrega, y la solución es volver a inscribir el dispositivo o volver a emitir el certificado.

Verificación y solución de problemas

  • Demonio activo: busca el registro del listener WSS en 0.0.0.0:9781 y, con un relé, la línea de registro de node_id.
  • Conexión sin certificado rechazada: es lo esperado en el plano de mTLS: regístrate primero (Topología 1b). El mensaje indica qué hacer; no es un bloqueo.
  • Relay accesible: zerorelay healthcheck --addr <host>:8443 finaliza con el código 0.
  • Métricas de Relay: ejecuta con --status-file <path>, luego zerorelay status --file <path> (solo recuentos, nunca cargas útiles).
  • Revocar un dispositivo perdido: revocarlo en el registro del demonio crea <data_dir>/tls/revoked; el certificado se rechaza en su siguiente protocolo de enlace y no puede renovarse automáticamente.
  • SAS no coincide durante el registro: el cliente se niega a conservar el certificado y cancela. Una discrepancia significa que la CA que recibiste no es la del demonio; investiga un posible ataque de intermediario antes de volver a intentarlo.

Hay un entorno de pruebas autocontenido de extremo a extremo en scripts/dev/mtls-relay-testbed.sh que inicia un demonio y un relé, emite un certificado, realiza la inscripción a través de la red y prueba las tres topologías; léelo como un ejemplo práctico.