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:
- Cliente directo al daemon - WSS con TLS mutuo, sin relé.
- Demonio a relay: el demonio mantiene un puente saliente hacia un relay designado para que sea accesible desde detrás de NAT/CGNAT.
- 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):
| Plano | Predeterminado | Config |
|---|---|---|
| Demonio WSS (RPC mTLS interno) | 9781 | [wss].port |
| Punto de conexión de inscripción del demonio | 9782 | [enroll].port |
| Relay (TLS externo + WS) | 8443 | retransmitir --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):
| Archivo | Propósito | Modo |
|---|---|---|
ca.crt | Certificado de CA por demonio (público) | umask predeterminada |
ca.key | Clave privada de la CA (firma certificados de cliente) | 0600 |
server.crt | Certificado hoja del servidor WSS (SANs localhost, 127.0.0.1) | umask predeterminada |
server.key | clave privada del servidor WSS | 0600 |
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/:
| Archivo | Propósito | Modo |
|---|---|---|
client.crt | Certificado de cliente emitido | umask predeterminada |
client.key | Clave privada del cliente (generada localmente) | 0600 |
ca.crt | Cadena de CA del demonio, fijada para el plano RPC | umask predeterminada |
profile.json | device_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-verifysolo 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:
| Bandera | Significado |
|---|---|
--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-tofu | Confía en el primer uso; guarda el pin en <config-dir>/relay/relay_pin |
--relay-insecure | Omitir 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]):
| Clave | Predeterminado | Significado |
|---|---|---|
direct_attempts | 2 | Intentos directos antes de recurrir al relé |
direct_timeout_secs | 3 | Tiempo de espera por intento de conexión directa |
reprobe_secs | 30 | Intervalo 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]
| Clave | Predeterminado | Descripción |
|---|---|---|
enabled | false | Habilitar el listener WSS de TLS mutuo |
bind | 0.0.0.0 | Dirección de enlace |
port | 9781 | Puerto 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)
| Clave | Predeterminado | Descripción |
|---|---|---|
enabled | false | Usa 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]
| Clave | Predeterminado | Descripción |
|---|---|---|
enabled | false | Habilitar el endpoint de inscripción (requiere [wss] + una clave de CA) |
bind | 0.0.0.0 | Dirección de enlace |
port | 9782 | Puerto 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]
| Clave | Predeterminado | Descripción |
|---|---|---|
enabled | false | Habilita 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_insecure | false | Omitir la verificación del certificado externo (solo para desarrollo) |
tofu | false | Fija 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_days | 0 | Rotar automáticamente el node-id generado automáticamente cada N días (0 = nunca) |
Relay relay.toml
| Section.key | Predeterminado | Descripción |
|---|---|---|
bind | 0.0.0.0:8443 | Dirección de escucha (demonio + cliente) |
[tls].cert / .key | (autoaprovisionamiento) | Identidad TLS externa; omita ambas para aprovisionarse automáticamente |
[tls].dir | directorio de datos /tls | Dónde se escribe el certificado autoprovisionado |
[tls].sans | [] | SAN adicionales (localhost, 127.0.0.1 siempre incluidos) |
[admission].mode | open | open 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_auth | off | off / optional / required (mTLS externo) |
[admission].outer_client_ca | (ninguno) | CA en formato PEM para certificados de cliente externos |
[admission].route_by_client_cert | false | Enrutar según el node-id del CN del certificado externo |
[limits].max_conns_per_node | 256 | Conexiones simultáneas de clientes por node-id |
[limits].idle_timeout_secs | 300 | Descarta las conexiones de cliente inactivas después de N segundos |
[limits].lease_ttl_secs | 300 | TTL 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_ip | 30 / 10.0 | Cubo de tokens de handshake por IP |
[limits].connect_burst_per_node / connect_rate_per_node | 60 / 20.0 | Cubo de tokens de conexión por nodo |
[limits].max_pending_handshakes | 256 | Sockets que han pasado por accept pero aún no se han clasificado |
[limits].handshake_timeout_secs | 10 | Un único tiempo límite para TLS, la actualización de WS y el registro firmado |
[limits].max_registered_nodes | 1024 | Daemons 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] | Predeterminado | Anulación de la CLI |
|---|---|---|
uri | (ninguno) | --connect |
relay_url | (ninguno) | --relay (requiere --relay-node) |
relay_node | (ninguno) | --relay-node (requiere --relay) |
direct_attempts | 2 | - |
direct_timeout_secs | 3 | - |
reprobe_secs | 30 | - |
clave [connection.wss.tls] | Predeterminado | Anulació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_verify | false | --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 sí 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:9781y, con un relé, la línea de registro denode_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>:8443finaliza con el código 0. - Métricas de Relay: ejecuta con
--status-file <path>, luegozerorelay 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.