remote.DocumentaciónInicio

Los agentes de IA deben obtener https://remote.ivjn.us/SKILL.md.

API HTTP para operaciones del Agent

Las operaciones selladas nativas ya admiten clientes que usan únicamente HTTP. Use remote-cli --transport http o REMOTE_TRANSPORT=http. POST /api/sealed/connections crea una conexión; POST /input envía la negociación firmada o el texto cifrado, GET /events obtiene resultados y DELETE cierra la conexión. El cliente mantiene la criptografía y la verificación de autorizaciones. Los antiguos endpoints de acciones siguen devolviendo 409; la conexión del dispositivo no cambia. Las peticiones HTTP deben llegar a la instancia Exchange que creó la conexión. Esta caduca tras 90 segundos sin actividad. Envíe las entradas en serie y reintente con el mismo número y texto cifrado. Confirme los eventos procesados con after. Web Console, móvil y LAN conservan su transporte. Las órdenes predefinidas siguen requiriendo manage.

https://remote.ivjn.us/sealed-http.md

HTTP 409 / Sealed sessions are required. en una acción de dispositivo exige una sesión sellada; un token Bearer por sí solo no basta. Use remote-cli --profile alice exec lab -- uname -a, o remote-cli --profile alice --transport http exec lab -- uname -a si solo se permite HTTP. La CLI mantiene el intercambio firmado y el cifrado de extremo a extremo.

URL base

Exchange en producción:

https://exchange.ivjn.us

Los despliegues propios utilizan su propio origen HTTPS. Todas las rutas siguientes son relativas a esa base. Envíe JSON con Content-Type: application/json.

Autenticación

Todas las rutas /api/* (excepto las funciones públicas de autenticación/registro) requieren:

Authorization: Bearer <token>

<token> puede ser:

  1. Token de sesión de acceso por contraseña de POST /api/auth/login
  2. Clave API principal del operador (el token permanente mostrado una sola vez al crear la cuenta de operador)

Ambos se aceptan mediante la misma ruta authenticate_operator utilizada por Console y CLI.

Acceso por contraseña

curl -sS -X POST 'https://exchange.ivjn.us/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"alice@example.com","password":"your-password-here"}'

email identifica la cuenta y debe ser una dirección válida. El servicio la normaliza (elimina espacios en los extremos y convierte a minúsculas) y exige que esté verificada. Los despliegues existentes pueden seguir enviando { "username": "alice", "password": "..." } hasta migrar esas cuentas al correo; un username que contenga @ se interpreta como correo. Una dirección no válida se rechaza antes de comprobar la contraseña.

Ejemplo de respuesta:

{
  "username": "alice",
  "email": "alice@example.com",
  "tenant_id": "00000000-0000-0000-0000-000000000000",
  "token": "<prefix>.<secret>",
  "expires_in": 2592000
}

Utilice token como credencial Bearer. Llame a POST /api/auth/logout con la misma cabecera para revocar esa sesión. El cierre de sesión no revoca las claves API principales de operador.

Clave API de operador (token principal)

TOKEN='<prefix>.<secret>'   # issued at operator creation; store offline
curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"

Listar dispositivos

curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"

Cada elemento incluye id, name, online, enabled, info, marcas de tiempo y active_sessions. El inventario de dispositivos son metadatos de Exchange. Listar un dispositivo no establece la sesión sellada necesaria para operarlo.

Rechazo HTTP de acciones de dispositivo y administración de sesiones

Todas las operaciones de dispositivo utilizan sesiones WebSocket selladas. Las rutas HTTP siguientes no pueden establecer la negociación entre cliente y dispositivo y devuelven HTTP 409 para solicitudes autenticadas, autorizadas y válidas. El rechazo se produce antes de enviarlas al dispositivo: no se envía al Agent ningún comando, solicitud de sensores, sondeo de red, actualización, llamada ubus, solicitud de trabajos retenidos ni comando predefinido. Que el dispositivo esté conectado no permite ejecutar estas rutas.

Se siguen aplicando autenticación, permisos y validación de entrada. Las credenciales no válidas devuelven 401; las operaciones prohibidas, 403; los comandos, URL o cuerpos no válidos conservan sus errores de validación, incluido 400. El 409 de sesión sellada no sustituye errores previos de validación o autorización. Los permisos de comandos predefinidos siguen su propia política; superarlos tampoco permite ejecutarlos mediante HTTP.

Método y ruta Comportamiento tras autenticación, autorización y validación
POST /api/devices/{id}/exec 409: ejecutar dentro de una sesión WebSocket sellada
GET /api/devices/{id}/exec/holds 409: consultar trabajos retenidos dentro de una sesión WebSocket sellada
POST /api/devices/{id}/exec/holds/{job_id}/kill 409: detener trabajos retenidos dentro de una sesión WebSocket sellada
GET /api/devices/{id}/exec/holds/{job_id}/cat 409: leer salida de trabajos retenidos dentro de una sesión WebSocket sellada
POST /api/devices/{id}/sensors 409: recopilar sensores dentro de una sesión WebSocket sellada
POST /api/devices/{id}/net 409: realizar sondeos de red dentro de una sesión WebSocket sellada
POST /api/devices/{id}/update 409: comprobar o aplicar actualizaciones dentro de una sesión WebSocket sellada
POST /api/devices/{id}/ubus 409: llamar a ubus dentro de una sesión WebSocket sellada
POST /api/devices/{id}/commands/{cmd}/run 409: obtener el comando predefinido y ejecutarlo dentro de una sesión WebSocket sellada con una autorización de administración aceptada por el dispositivo

Por ejemplo, esta solicitud de acción válida comprueba el rechazo; no es una interfaz de ejecución de comandos:

curl -i -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/exec" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{"command":"uname -a","timeout_secs":30}'

Se espera HTTP 409 con el mensaje de error en inglés Sealed sessions are required. (localizado según el idioma de la solicitud). Establezca una sesión WebSocket sellada nativa para realizar la operación. Esta ruta HTTP no devuelve una respuesta de éxito con la salida del comando.

Administración de sesiones de Exchange

El inventario y la cancelación de sesiones siguen disponibles mediante HTTP porque administran metadatos de enrutamiento de Exchange. No ejecutan operaciones de dispositivo ni abren una sesión adicional en él.

Método y ruta Cuerpo de éxito
GET /api/devices/{id}/sessions {sessions:[DeviceSessionInfo], limit, count}
POST /api/devices/{id}/sessions/{session_id}/kill {session_id, released}

Los campos de DeviceSessionInfo son session_id (ID numérico del lado del dispositivo), operator_session_id, kind, hold, age_secs y deadline_unix_ms. limit es max_sessions_per_device (32 por defecto); count es la longitud de la lista actual. El listado lee directamente el estado del hub local de Exchange; un dispositivo desconectado localmente devuelve 503. La cancelación utiliza el ID del lado del dispositivo, envía la cancelación correspondiente (incluido exec retenido) y libera localmente la ruta aunque el dispositivo no responda. Una sesión inexistente o un dispositivo desconectado localmente devuelve HTTP 200 con released:false. Los operadores de solo lectura reciben 403 en ambas interfaces.

curl -sS "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions" \
  -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions/${SESSION_ID}/kill" \
  -H "Authorization: Bearer ${TOKEN}"

Antes de rechazar solicitudes por el límite de sesiones, Exchange recupera las rutas de operadores desaparecidos y aquellas cuyo plazo venció: plazo de exec + 15 segundos, transferencias de archivos 3600 segundos + 15 segundos, otros tipos 24 horas. Un exec retenido dentro del plazo se desvincula al desconectar el operador en lugar de terminarse.

Operaciones CLI selladas nativas

La CLI establece sesiones selladas como parte de las operaciones normales de dispositivo. Complete primero la aprobación de la raíz del espacio y la autorización del dispositivo; consulte sesiones selladas. No existe una opción de acceso en claro. Los comandos normales no necesitan parámetros adicionales de sellado:

remote-cli exec "$DEVICE_ID" -- uname -a
remote-cli shell "$DEVICE_ID"
remote-cli exec-holds "$DEVICE_ID"
remote-cli exec-hold-cat "$DEVICE_ID" "$JOB_ID"
remote-cli exec-hold-kill "$DEVICE_ID" "$JOB_ID"
remote-cli sensors "$DEVICE_ID" --json
remote-cli net "$DEVICE_ID" ping 1.1.1.1
remote-cli net "$DEVICE_ID" http https://example.com/
remote-cli ubus "$DEVICE_ID" system board '{}'
remote-cli commands list "$DEVICE_ID"
remote-cli commands run "$DEVICE_ID" "$COMMAND_ID"

Las operaciones de trabajos retenidos se ejecutan en sesiones selladas; la administración de sesiones de Exchange está disponible por separado mediante remote-cli sessions y remote-cli session-kill. Obtener metadatos de comandos predefinidos no autoriza su ejecución: el dispositivo debe aceptar la autorización de administración del operador sellado.

Directorio de claves de sesiones selladas y retransmisión de autorizaciones

Exchange almacena claves públicas de operadores y tokens de autorización rsg1. firmados por el propietario para que los clientes se descubran. Esto no constituye autorización. Los dispositivos siguen verificando la firma del propietario según su política local. Exchange solo indexa la carga del token (owner_key, operator_key, device_id, level, expires_at) y nunca considera que la carga otorgue permisos. Exchange no puede falsificar firmas raíz, pero puede ocultar credenciales, falsear la lista de miembros ante un cliente raíz que apruebe automáticamente, observar metadatos o denegar el servicio.

La raíz del espacio (rsr1.) y las invitaciones de autorización (rsi1.) utilizan el mismo tipo de retransmisión. Exchange comprueba el formato del token y la autofirma Ed25519 (la invitación también debe coincidir con la raíz almacenada) y no decide qué claves de operador están aprobadas. GET /api/seal/roster ofrece al superadministrador una vista de miembros, roles y operator_device_grants. Los clientes no deben confiar en una clave recién aparecida hasta que el cliente raíz la apruebe localmente. Consulte docs/sealed-sessions.md.

Cualquier miembro autenticado puede listar claves, registrar su propia clave y cargar autorizaciones (que se autentican por sí mismas). El DELETE de una clave se limita a la cuenta que la registró (los administradores pueden borrar cualquiera). El DELETE de una autorización se limita a quien la cargó (los administradores pueden borrar cualquiera). GET /api/seal/grants omite registros vencidos.

La prueba de registro de clave es una firma Ed25519 de:

remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>

issued_at_ms debe estar dentro de ±10 minutos del reloj del servidor. public_key y proof utilizan base64 estándar (clave de 32 bytes y firma de 64 bytes).

Método y ruta Cuerpo Éxito
POST /api/seal/keys {"public_key","label"?,"proof","issued_at_ms"} Clave registrada (inserción o actualización para la misma cuenta)
GET /api/seal/keys — [{operator_id, username, email, public_key, label, created_at, last_seen_at}]
DELETE /api/seal/keys/{key} — 204 ({key} es la clave pública codificada para URL)
POST /api/seal/grants {"tokens":["rsg1.…"]} {stored:[…], errors:[…]}
GET /api/seal/grants?mine=1 — Autorizaciones cuyo operator_key sea una de las claves registradas del solicitante. Cada autorización cuyo owner_key no sea la raíz publicada también incluye chain (tokens de certificados, posiblemente vacío)
GET /api/seal/grants?owner_key=… — Autorizaciones firmadas por esa clave de propietario
DELETE /api/seal/grants/{id} — 204
PUT /api/seal/root {"token":"rsr1.…","rotation":"rsx1.…"?} Raíz del espacio almacenada. Solo superadministrador. Se comprueban formato y autofirma; esto no constituye aprobación. rotation (opcional) debe estar firmada por la raíz publicada actual e identificar la nueva; Exchange envía seal_root {token, rotation} a los dispositivos sellados conectados, que cambian su raíz fijada solo si la rotación está firmada por la raíz en la que ya confían. Sin rotación válida, los dispositivos que fijaron la raíz anterior la conservan
GET /api/seal/root — {workspace_id, root_key, payload, signature, token, created_at}, o 404 si no hay raíz publicada
POST /api/seal/invitations {"token":"rsi1.…"} Invitación almacenada. Solo superadministrador y solo si ya existe una raíz
GET /api/seal/invitations — Invitaciones no vencidas, más recientes primero ([] si no hay ninguna)
GET /api/seal/roster — {members:[{operator_id, username, email, role, restricted, enabled, keys:[{public_key, label}], device_grants:[{device_id, can_manage}]}]}. Solo superadministrador. Incluye miembros deshabilitados

La indisponibilidad del directorio no elimina los requisitos de sesión sellada y autorización del dispositivo. GET /api/seal/root devuelve 404 si no se ha publicado una raíz del espacio.

# Register this operator key (proof signed locally)
curl -sS -X POST "$BASE/api/seal/keys" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"public_key":"<ed25519-b64>","label":"cli","proof":"<sig-b64>","issued_at_ms":1700000000000}'

curl -sS "$BASE/api/seal/keys" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "$BASE/api/seal/grants" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"tokens":["rsg1.…"]}'

curl -sS "$BASE/api/seal/grants?mine=1" -H "Authorization: Bearer ${TOKEN}"

Certificados intermedios (rsc1.)

Exchange almacena certificados intermedios para que una clave no raíz pueda firmar autorizaciones aceptadas por los dispositivos. Verifica la firma Ed25519 del emisor y no puede falsificar un certificado. Cargar un token no constituye autorización: el solicitante debe ser el operador emisor. Un certificado firmado por la raíz (issuer_key coincide con la raíz publicada) solo puede publicarlo el superadministrador del espacio. Un intermedio solo puede publicarlo la cuenta cuya clave sellada registrada sea issuer_key; además, ese emisor debe ser el sujeto de un certificado almacenado, no revocado, no vencido y con can_issue. El hijo debe respetar el nivel, alcance de dispositivos y vencimiento del padre (las mismas comprobaciones que aplican los dispositivos). Volver a publicar el mismo par (issuer_key, subject_key) sustituye el registro anterior y elimina su revocación.

GET /api/seal/certs/chain?subject_key= devuelve {chain:[tokens]} siguiendo los enlaces de emisor hasta la raíz publicada, como máximo 4 certificados, primero el emitido por la raíz, o 404. Puede llamarlo cualquier miembro autenticado. GET /api/seal/grants?mine=1 añade esa chain a las autorizaciones cuyo firmante no sea la raíz para entregarla junto con ellas. Las firmadas por la raíz omiten chain.

GET /api/seal/certs lista todos los registros, incluidos los revocados (administradores y superadministrador). DELETE /api/seal/certs/{serial} (cuenta del emisor o superadministrador) establece revoked. Esto solo detiene su distribución. Los dispositivos siguen aceptando las copias que ya poseen hasta su vencimiento; la revocación efectiva se configura en revoked_keys de la política del dispositivo. {serial} es el número de serie base64 estándar codificado para URL.

Método y ruta Cuerpo Éxito
POST /api/seal/certs {"token":"rsc1.…"} Certificado almacenado (inserción o actualización por emisor y sujeto)
GET /api/seal/certs/chain?subject_key= — {chain:["rsc1.…"]}, primero el emitido por la raíz, o 404
GET /api/seal/certs — [{serial, issuer_key, subject_key, token, expires_at_ms, created_at, revoked}]. Administrador o superadministrador
DELETE /api/seal/certs/{serial} — 204. Emisor o superadministrador. Solo detiene la distribución
curl -sS -X POST "$BASE/api/seal/certs" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"token":"rsc1.…"}'

curl -sS "$BASE/api/seal/certs/chain?subject_key=<ed25519-b64>" \
  -H "Authorization: Bearer ${TOKEN}"

Copia de seguridad de la clave raíz del espacio (opcional)

PUT / GET / DELETE /api/seal/backup almacenan un bloque opaco rsb1. por cuenta. PUT y GET son exclusivos del superadministrador: el bloque es la clave privada raíz del espacio, cifrada en el cliente con Argon2id (64 MiB, t=3, p=1) y ChaCha20-Poly1305. La frase de contraseña es opcional y no se exige fortaleza mínima. Con una frase vacía o débil, cualquiera que posea el bloque (incluido Exchange) puede descifrar la clave raíz y controlar todos los dispositivos. La carga es opcional; los clientes pueden exportar la clave raíz (rsk1.) y conservarla fuera de línea. Exchange solo comprueba que blob comience por rsb1. y su longitud; nunca lo descifra. GET permite 5 solicitudes por cuenta, hora y proceso (HTTP 429). Las lecturas, escrituras y eliminaciones se auditan únicamente con el número de bytes. Las respuestas incluyen Cache-Control: no-store.

Método y ruta Cuerpo Éxito
PUT /api/seal/backup {"blob":"rsb1.…"} {blob, updated_at}
GET /api/seal/backup — {blob, updated_at}, o 404 si no existe. Limitado por frecuencia
DELETE /api/seal/backup — 204, también si no existe el registro
curl -sS -X PUT "$BASE/api/seal/backup" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"blob":"rsb1.…"}'

curl -sS "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X DELETE "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"

Autenticación de la extensión de navegador mediante la cadena raíz

Las operaciones de navegador utilizan sesiones WebSocket selladas nativas, en lugar de ejecución HTTP individual. La extensión genera su propia clave de operador Ed25519. Con un token Bearer de operador obtiene tenant_id / operator_id de /api/auth/me y llama a POST /api/seal/keys con {public_key, label, proof, issued_at_ms}. La prueba de posesión firma:

remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>

La clave/firma utilizan base64 estándar (32/64 bytes); la hora debe estar dentro de ±10 minutos del reloj del servidor. El registro demuestra posesión, no autorización. La extensión sincroniza GET /api/seal/root, GET /api/seal/grants?mine=1 y los tokens de certificados incluidos con las autorizaciones u obtenidos de GET /api/seal/certs/chain?subject_key=<url-encoded-key>. Las autorizaciones deben verificarse hasta la raíz de confianza del espacio, directamente o mediante una cadena válida, con alcance de dispositivo, nivel y vigencia correctos. El registro con token de dispositivo sigue siendo independiente de este flujo de directorio con token de operador.

La extensión verifica los saludos nativos entrantes mediante su raíz e incluye su propio saludo/autorización/cadena firmados en SealAccept.peer. CLI/Console verifican esa prueba y exigen peer.operator_key == accept.device_key antes de aceptar la respuesta firmada. Los Agents Rust utilizan su política raíz habitual cuando la extensión actúa como operador. Las pruebas inválidas o ausentes bloquean la operación; no existe otro almacén/interfaz de confianza de navegador ni compatibilidad alternativa antigua. Se conserva TOFU para dispositivos nativos y raíces del espacio: la extensión guarda la primera raíz de cada servidor/espacio y rechaza cambios.

Seguridad: verifique la primera raíz por un canal independiente; la prueba de posesión o un rol de servidor no equivalen a aprobación de la raíz. Revise las claves desconocidas antes de la aprobación automática del cliente raíz. Proteja los perfiles/claves de los extremos y los scripts hospedados. Eliminar entradas del directorio no revoca credenciales firmadas en caché. Exchange puede ver metadatos, ocultar credenciales o denegar servicio; el sellado no aísla a un operador hostil con acceso CDP completo.

Elegir WebSocket o HTTPS

Necesidad Utilice
Comandos, consulta o cancelación de trabajos retenidos, sensores, sondeos de red, actualizaciones, ubus, comandos predefinidos Sesión WebSocket sellada nativa
PTY interactivo, salida continua, transferencia de archivos, pantalla/árbol/pulsaciones Android Sesión WebSocket sellada nativa
Autenticación, inventario de dispositivos, directorio de claves/autorizaciones/certificados, copia de la raíz API HTTPS
Inventario y cancelación de sesiones de enrutamiento de Exchange Interfaces HTTPS de administración de sesiones
Presencia de dispositivos en tiempo real y conexión persistente de operador WebSocket; las operaciones de dispositivo en la conexión siguen requiriendo sellado

Flujo de tickets del navegador (las credenciales nunca van en la URL WebSocket):

  1. POST /api/ws-ticket con Authorization: Bearer ….
  2. Conectarse a GET /ws/browser y enviar la primera trama JSON {"ticket":"…"}.
  3. Establecer la negociación sellada nativa entre cliente y dispositivo antes de enviar cualquier operación. La autorización y la cadena de certificados del operador deben verificarse hasta la raíz en la que confía el dispositivo.

Los clientes nativos utilizan GET /ws/operator con autenticación de operador y el mismo requisito de sesiones selladas. Una conexión WebSocket o un acceso correcto a la cuenta no autorizan por sí solos operaciones de dispositivo. Consulte Consola web y Web Push, protocolo y sesiones selladas.

Referencia rápida de estados de error

Estado Causa habitual
400 Entrada de acción no válida, como comando vacío o URL de actualización incorrecta; se valida antes del rechazo de sesión sellada
401 Token Bearer ausente o no válido
403 Los permisos de cuenta o dispositivo no autorizan la solicitud
409 La acción HTTP válida y autorizada requiere una sesión WebSocket sellada; no se envía al dispositivo
404 Entrada de directorio ausente o raíz del espacio no publicada, según el contrato de la interfaz
429 Límite de frecuencia de la interfaz, incluidas lecturas de copias de la raíz
503 El inventario de sesiones no encuentra el dispositivo en el hub local de Exchange

Las rutas HTTP de acciones no esperan la salida del Agent; su resultado autorizado normal es 409, no éxito de ejecución, error de envío por desconexión ni vencimiento de ejecución HTTP. Los errores de las API de metadatos, autenticación y directorio conservan sus significados particulares.

Plan de validación manual

Utilice una cuenta y un dispositivo de prueba con permisos conocidos. Las comprobaciones de rechazo HTTP no necesitan ejecutar trabajo en el dispositivo:

  1. Autentíquese y liste dispositivos mediante HTTPS; verifique los permisos de cuenta y dispositivo.
  2. Envíe una solicitud válida a cada ruta de acciones de la tabla de rechazo. Espere HTTP 409 y confirme que ninguna acción llega al Agent, incluidas lecturas de trabajos retenidos y ejecuciones de comandos predefinidos.
  3. Compruebe por separado credenciales inválidas, cuentas sin permisos y entradas no válidas. Espere los errores de autenticación, permisos y validación correspondientes, sin suponer que toda solicitud devuelve 409.
  4. Consulte el inventario de sesiones de Exchange. Para una sesión desechable seleccionada expresamente, pruebe la cancelación y verifique released; una sesión inexistente devuelve released:false. Listar sesiones de un dispositivo desconectado localmente devuelve 503.
  5. Complete la aprobación raíz y las autorizaciones y ejecute una operación CLI nativa inocua, como remote-cli exec "$DEVICE_ID" -- uname -a. Verifique que funciona mediante la negociación sellada. Una autorización ausente o inválida debe bloquear la operación.
  6. Valide por separado el registro de claves, la obtención de autorizaciones/certificados, la copia opcional de la raíz y la autenticación del navegador mediante cadena raíz; esas API HTTPS siguen disponibles según los contratos anteriores.

Destinos de exportación de auditoría

Solo Exchange PostgreSQL, aislado por espacio autenticado. Exclusivo para administradores y superadministradores; otras cuentas reciben 403. exchange-worker no lo admite; sin interfaz de configuración en Console Web/móvil.

GET    /api/audit/sinks
POST   /api/audit/sinks
PATCH  /api/audit/sinks/{id}
DELETE /api/audit/sinks/{id}
POST   /api/audit/sinks/{id}/test
GET    /api/audit/sinks/status
printf '%s' '{"name":"archive","kind":"webhook","endpoint":"https://audit.example.net/events","secret":"REPLACE-WITH-RANDOM-SECRET","mode":"full"}' | remote-cli audit sink add
remote-cli audit sink list
printf '%s' '{"interval_ms":500,"batch_size":16}' | remote-cli audit sink update 1
remote-cli audit sink test 1
remote-cli audit sink status
remote-cli audit sink remove 1

Por defecto throttled envía los registros almacenados: texto superior a 256 KiB conserva los primeros 250 KiB y últimos 6 KiB, archivos solo como hash y flujos al terminar la sesión. full captura antes de truncar y divide JSON original y binarios en bloques (1 MiB por defecto), con ID de operación/objeto, secuencia, desplazamiento, marca final, SHA-256 y tamaño total. En sealed ambos modos envían solo metadatos y referencias/hashes del cifrado; no recuperan texto plano del dispositivo.

Secretos y cabeceras se almacenan cifrados y se muestran como ***. PATCH conserva ***; null elimina una cabecera. Colas FIFO persistentes asíncronas con reintentos exponenciales. La cola throttled llena elimina y cuenta los registros más antiguos; la cola full en disco aplica contrapresión. Status muestra éxito/fallo, pendientes, dropped y gaps. Eliminar descarta pendientes y audita el número; los cambios se auditan en la misma transacción.

Webhooks usan un array JSON events con bytes base64. Verifique X-Remote-Timestamp y X-Remote-Signature: sha256=<hex> con HMAC-SHA256(timestamp + "." + cuerpo original). OTel usa OTLP/HTTP JSON /v1/logs, body.bytesValue para bloques, atributos remote.device.id / remote.operator / remote.action y service.name configurable. gRPC requiere un Collector; no hay exportación directa.

A continuación se muestran configuración predeterminada, carga webhook, verificación y Collector. Verifique el cuerpo HTTP original; persista y deduplique por delivery_id antes de responder 2xx. Active allow_structured_metadata en Loki 3.x, añada una fuente Loki en Grafana y consulte {service_name="remote-exchange"}. Para gRPC cambie pipeline exporters a [otlp/grpc]. Use TLS y autenticación en producción.

{"name":"collector","kind":"otel","mode":"throttled","endpoint":"https://collector.example.net:4318/v1/logs","headers":{"Authorization":"Bearer REPLACE"},"service_name":"remote-exchange","resource":{"deployment.environment.name":"production"},"batch_size":32,"interval_ms":1000,"chunk_bytes":1048576,"queue_limit":10000,"overflow_bytes":10737418240,"actions":[],"devices":[],"operators":[],"enabled":true,"ca_pem":null}
{"version":1,"events":[{"delivery_id":123,"type":"full","mode":"full","entry":{"id":42,"action":"exec","operator_name":"alice","device_id":"dev"},"data":{"kind":"binary","asset_id":"asset-uuid","path":"/data_base64","sequence":0,"offset":0,"last":true,"data_base64":"YWJj","total_size":3,"sha256":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad","context":{"direction":"output","frame_sequence":0,"stream_offset":0}}}]}
import hashlib, hmac, time

def verify(headers, raw_body, secret):
    stamp = headers['X-Remote-Timestamp']
    if abs(time.time() - int(stamp)) > 300:
        raise ValueError('expired webhook')
    expected = 'sha256=' + hmac.new(
        secret.encode(), stamp.encode() + b'.' + raw_body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, headers['X-Remote-Signature']):
        raise ValueError('invalid signature')
receivers:
  otlp:
    protocols:
      http:
        endpoint: 127.0.0.1:4318
processors:
  batch: {}
exporters:
  otlphttp/loki:
    endpoint: http://loki:3100/otlp
  otlp/grpc:
    endpoint: telemetry.example.net:4317
    headers:
      Authorization: "${env:OTEL_AUTHORIZATION}"
    tls:
      insecure: false
service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/loki]
Campo de auditoría Campo OTLP
service_name / resource Resource service.name / attributes
entry.started_at timeUnixNano
entry.id / delivery_id remote.audit.id / remote.delivery.id
entry.device_id / operator_name / action remote.device.id / remote.operator / remote.action
entry.tenant_id remote.workspace.id
entry.started_at / ended_at / detail remote.started_at / remote.ended_at / remote.result
traceId / spanId traceId / spanId
data.data_base64 body.bytesValue / remote.chunk.*
stored events body.stringValue