Saltar a contenido

Cómo documentar y mantener sistemas en NetBox

Guía operativa para incorporar recursos nuevos y actualizar recursos existentes en el inventario de NetBox de iort.


Índice

  1. Antes de empezar
  2. Cómo autenticarse en la API
  3. Decidir qué objeto NetBox crear
  4. Campos obligatorios mínimos
  5. Custom fields disponibles
  6. Tags disponibles y cuándo usarlas
  7. Flujo de trabajo para LLMs y operadores
  8. Plantillas por tipo de sistema
  9. Qué no meter en NetBox
  10. Referencia de IDs útiles
  11. Checklist final para LLMs

1. Antes de empezar

NetBox en iort es el inventario estructurado, no la fuente de verdad operativa de todo.

Antes de crear cualquier objeto, responde estas preguntas:

Pregunta Fuente real
¿Cómo se desplegó? Pulumi stack
¿Dónde están los secretos? Google Secret Manager (proyecto iort-secrets)
¿Dónde está el código? Repo git local o GitHub
¿Quién es el responsable operativo? Definir owner en NetBox
¿A qué tenant pertenece? Ver tenants disponibles

NetBox debe recoger: identidad, relaciones, owner, tenant, estado, clasificación y enlaces externos. No duplicar lo que ya está en esas otras fuentes.


2. Cómo autenticarse en la API

URL base

https://iort-netbox.tailf75db9.ts.net

Solo accesible desde la tailnet de Tailscale. Verificar conectividad con:

curl -Ik "https://iort-netbox.tailf75db9.ts.net/api/"

Comportamiento esperado sin autenticación:

curl -Ik "https://iort-netbox.tailf75db9.ts.net/api/"

Devuelve 403, que en este despliegue significa que la API está accesible por HTTPS dentro de la tailnet pero exige autenticación.

Autenticación por token v2 (recomendada)

En NetBox 4.5.2 los tokens v2 sí se usan por cabecera HTTP. El formato correcto es:

Authorization: Bearer nbt_<key>.<token>

Ejemplo de lectura:

NETBOX_TOKEN="nbt_<key>.<token>"

curl -s "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Accept: application/json" \
  | python3 -m json.tool

Ejemplo de escritura:

curl -s -X POST "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{...}'

Con autenticación por token no hace falta X-CSRFToken.

Autenticación por sesión (opcional)

Si ya estás operando desde navegador o necesitas reproducir el flujo web con cookies, también puedes usar sesión:

CSRF="<valor_csrftoken>"
SESSION="<valor_sessionid>"
COOKIES="csrftoken=$CSRF; sessionid=$SESSION"

# Para peticiones de escritura (POST/PATCH/DELETE), añadir siempre:
#    -H "X-CSRFToken: $CSRF"
#    -H "Referer: https://iort-netbox.tailf75db9.ts.net/"

Patrón recomendado para LLMs y agentes

Dentro de la tailnet, el patrón correcto es este:

  1. usar MCP para leer, explorar y localizar el objeto canónico;
  2. usar la REST API con token v2 para crear o editar;
  3. volver a leer tras la escritura y verificar que el resultado final es el esperado;
  4. añadir una entrada de journal con el contexto del cambio.

MCP en este despliegue es de solo lectura. No lo uses como canal de escritura.

Credenciales de admin

Las credenciales de admin están en GSM bajo el secreto iort-netbox-prod-netbox-admin-password del proyecto iort-secrets. Úsalas para entrar a la UI por Tailscale y, si hace falta, generar un token v2 dedicado para automatización.

gcloud secrets versions access latest \
  --secret=iort-netbox-prod-netbox-admin-password \
  --project=iort-secrets

3. Decidir qué objeto NetBox crear

Tipo de sistema Objeto NetBox a usar
VPS / instancia cloud VirtualMachine dentro de un Cluster
Hardware físico o robot Device
Web/app serverless (Cloudflare Pages, Firebase, etc.) Site en grupo "Cloud"
Red / prefijo / VPN Prefix, IPAddress, Tunnel
Componente sin identidad propia InventoryItem
Puerto/endpoint publicado sobre una VM/Device Service

Regla clave: no crees VMs ficticias para representar aplicaciones serverless o funciones cloud. Usa Site como punto de anclaje para proveedores externos sin infraestructura propia.


4. Campos obligatorios mínimos

Todo objeto de producción debe tener como mínimo:

  • name — identificador humano, estable
  • slug — kebab-case, sin cambiar después de crearlo
  • status: active
  • tenant — a qué organización/dominio pertenece
  • description — una línea, qué es y en qué entorno
  • al menos una tag de clasificación (ver sección 6)
  • custom_field.sot_system — quién gestiona la fuente de verdad operativa

Y en comments, contexto operativo relevante (acceso, dependencias, IaC, política de secretos).


5. Custom fields disponibles

Estos custom fields están disponibles en los objetos de tipo site, cluster y virtual_machine:

Campo Tipo Qué poner
sot_system texto quién es la fuente de verdad operativa: pulumi, cloudflare, scaleway, manual...
iac_stack texto nombre del stack: cloudflare-pages, scaleway-vps, nombre del stack Pulumi...
pulumi_stack_url URL enlace directo al stack en Pulumi Cloud
access_model texto public, tailscale, vpn, private
service_tier texto prod, staging, dev
gsm_secret_project texto proyecto de GSM donde están los secretos: iort-secrets
gsm_secret_prefix texto prefijo del bundle de secretos: p.ej. iort-netbox-prod-
backup_policy texto descripción de la política de backup
runbook_ref texto ruta o URL al runbook operativo
operator_portal_url URL dashboard del proveedor o UI de gestión
observed_hostname texto hostname observado en runtime (solo VMs/Devices)
observed_public_ipv4 texto IP pública observada (solo VMs/Devices)
tailscale_dns texto FQDN de Tailscale (solo VMs/Devices)
published_endpoints JSON endpoints públicos en formato JSON

6. Tags disponibles y cuándo usarlas

Tags de gestión (aplicar siempre una):

Tag Cuándo usar
managed-by-pulumi El recurso se gestiona con Pulumi IaC
managed-by-manual Documentado a mano, sin IaC

Tags de acceso:

Tag Cuándo usar
exposure-public Accesible desde Internet sin autenticación de red
access-tailscale Accesible solo desde la tailnet

Tags de entorno:

Tag Cuándo usar
env-prod Producción

Tags de proveedor/plataforma:

Tag Cuándo usar
cloudflare Cualquier recurso en Cloudflare
cloudflare-pages Desplegado en Cloudflare Pages
scaleway Infraestructura en Scaleway
tailscale Recurso con conectividad Tailscale

Tags de proyecto (añadir la del proyecto correspondiente):

iort, custochef, visort, ferrol, ...


7. Flujo de trabajo para LLMs y operadores

Patrón general

Antes de escribir en NetBox:

  1. leer primero;
  2. buscar por varias claves antes de crear nada;
  3. editar el objeto canónico si ya existe;
  4. crear padres antes que hijos;
  5. no sobrescribir comments ni custom_fields sin haber leído el estado actual;
  6. dejar siempre journal de lo que se hizo.

Contrato de trabajo recomendado para un LLM

Antes de ejecutar una escritura, el LLM debería poder responder internamente a esto:

  1. qué tipo de objeto principal va a tocar;
  2. si va a crear o editar;
  3. cuál es el objeto canónico o por qué no existe todavía;
  4. qué campos exactos va a modificar;
  5. qué datos siguen siendo inciertos y por tanto no debe inventar.

Si no puede responder esas cinco preguntas con claridad, no debería escribir todavía.

Información mínima que un LLM debe reunir

Antes de crear o editar, intenta reunir como mínimo:

  • nombre estable del sistema o proyecto;
  • tipo de objeto principal esperado: Site, VirtualMachine, Device, Service, Prefix o similar;
  • tenant;
  • owner o equipo responsable;
  • entorno: prod, staging, dev;
  • modelo de acceso: public, tailscale, vpn, private;
  • proveedor o fuente de verdad operativa: pulumi, cloudflare, scaleway, manual;
  • hostname, dominio, URL pública o FQDN de Tailscale si aplica;
  • stack IaC, repo, runbook y prefijo de secretos si existen.

Si faltan varios de esos datos, es mejor parar y pedir contexto adicional que inventarlos.

Orden recomendado de modelado

Crear o editar en este orden cuando aplique:

  1. tenant, owner, contact si faltan;
  2. region, site group, site;
  3. cluster;
  4. device o virtual machine;
  5. interfaces, IPs, prefijos o direccionamiento;
  6. service;
  7. tags, custom fields, comments y journal.

Errores comunes:

  • crear una VM sin site ni cluster cuando ambos existen;
  • modelar una web serverless como VirtualMachine;
  • meter el endpoint como texto libre en comments sin crear Service cuando sí hay host real;
  • crear objetos hijos antes de saber cuál es el padre correcto.

Cómo buscar sin duplicar

Si no estás seguro de si algo ya existe, busca varias veces con criterios distintos y compara resultados. Ejemplo con lectura completa y filtrado local:

curl -s "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/?limit=200" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  | python3 - <<'PY'
import json, sys

needle = "nombre-o-fragmento".lower()
data = json.load(sys.stdin)
for item in data.get("results", []):
    haystack = " ".join([
        str(item.get("name") or ""),
        str(item.get("slug") or ""),
        str(item.get("description") or ""),
    ]).lower()
    if needle in haystack:
        print(item["id"], item.get("name"), item.get("slug"))
PY

Repite el patrón para sites, devices, virtual-machines o clusters antes de decidir crear.

Flujo A — documentar un proyecto nuevo

Paso 1 — Autenticarse

Exportar un token v2 según la sección 2.

export NETBOX_TOKEN="nbt_<key>.<token>"

Paso 2 — Buscar si ya existe algo relacionado

Antes de crear nada, buscar candidatos por:

  • nombre;
  • slug;
  • hostname;
  • dominio o URL;
  • tenant;
  • stack Pulumi;
  • tags;
  • comentarios existentes.

Patrón mínimo de lectura por API:

# Sites existentes
curl -s "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); [print(t['id'],t['name'],t['slug']) for t in d['results']]"

# Tenants
curl -s "https://iort-netbox.tailf75db9.ts.net/api/tenancy/tenants/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); [print(t['id'],t['name']) for t in d['results']]"

Si el LLM tiene acceso al MCP del workspace, este es el orden recomendado:

  1. consultar por MCP si ya existe un Site, VirtualMachine, Device o Service relacionado;
  2. usar la API solo cuando ya esté claro qué objeto hay que crear o editar.

Paso 3 — Decidir si hay que crear o editar

Crear un objeto nuevo solo si no existe ya una identidad operativa equivalente.

Normalmente se edita si:

  • el proyecto es el mismo y solo cambian descripción, tags, enlaces, comentarios o custom fields;
  • la VM o device sigue siendo el mismo recurso lógico;
  • cambió el dominio, el runbook o el stack, pero no la identidad principal.

Normalmente se crea un objeto nuevo si:

  • aparece un nuevo entorno separado;
  • el hostname o identidad operativa cambia de verdad;
  • se sustituye un hardware por otro con lifecycle distinto;
  • antes no existía el recurso en inventario.

Paso 4 — Crear el objeto principal

Crear primero el objeto principal y después sus relaciones o metadatos complementarios.

Ver plantillas en la sección 8.

Si el proyecto nuevo necesita más de un objeto, orden recomendado:

  • proyecto cloud/serverless: Site y después, si aplica, servicios o enlaces operativos;
  • VPS o nodo virtual: Site -> Cluster -> VirtualMachine -> Service;
  • robot o hardware edge: Site -> Device -> interfaces/IPs -> Service.

Paso 5 — Añadir entrada en el journal

Siempre añadir una entrada de journal al objeto recién creado documentando el contexto de la creación:

curl -s -X POST "https://iort-netbox.tailf75db9.ts.net/api/extras/journal-entries/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "assigned_object_type": "dcim.site",
    "assigned_object_id": <ID_DEL_OBJETO>,
    "kind": "info",
    "comments": "## Registro inicial\n\nContexto de la creación, estado actual, pendientes."
  }'

Los assigned_object_type válidos son: dcim.site, dcim.device, virtualization.virtualmachine, virtualization.cluster.

Paso 6 — Verificar

Tras crear:

  • releer el objeto por API o MCP;
  • comprobar name, slug, tenant, tags, custom_fields y comments;
  • abrir la display_url si hace falta validación visual;
  • comprobar que no se ha duplicado un objeto existente.

Flujo B — editar un proyecto existente

Paso 1 — Localizar el objeto canónico

No editar “a ciegas”. Primero identifica el objeto exacto y guarda su id.

Busca por más de una clave si hace falta:

  • name
  • slug
  • hostname
  • FQDN de Tailscale
  • URL pública
  • tenant
  • tags
  • referencias en comments o custom_fields

Paso 2 — Leer el estado actual completo

Ejemplo para un Site existente:

SITE_ID="<id>"

curl -s "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/${SITE_ID}/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  | python3 -m json.tool

Haz lo mismo con la ruta del tipo correspondiente si es una VM, device o cluster.

Paso 3 — Definir el alcance del cambio

Antes de editar, separa:

  • datos que deben mantenerse;
  • datos que deben corregirse;
  • datos nuevos que hay que añadir;
  • datos dudosos que no deben tocarse sin confirmación.

Regla práctica: si no estás seguro de un campo, no lo “normalices” por intuición.

Además, decide si el cambio es:

  • descriptivo: descripción, comments, enlaces, tags, custom fields;
  • estructural: cambiar padre, tenant, site, cluster, hostname o identidad principal;
  • de limpieza: corregir duplicados, normalizar nombres, retirar datos erróneos.

Los cambios estructurales son los más peligrosos y deben hacerse con especial cuidado.

Paso 4 — Aplicar un PATCH mínimo

Editar solo los campos necesarios. Ejemplo de PATCH para un Site:

curl -s -X PATCH "https://iort-netbox.tailf75db9.ts.net/api/dcim/sites/${SITE_ID}/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Descripción actualizada",
    "comments": "Comentarios operativos actualizados",
    "tags": [{"id": 34}, {"id": 35}, {"id": 38}],
    "custom_fields": {
      "sot_system": "cloudflare",
      "service_tier": "prod",
      "access_model": "public"
    }
  }'

Recomendaciones:

  • no cambies slug si no hay una razón fuerte;
  • no borres comments útiles: léelos antes y reescríbelos de forma deliberada;
  • no elimines tags o custom fields válidos por omisión accidental;
  • si el cambio es grande, haz varios PATCH pequeños en vez de uno enorme.

Cómo preservar datos existentes al editar

Antes de hacer PATCH, inspecciona explícitamente estas partes:

  • comments
  • tags
  • custom_fields
  • relaciones (tenant, site, cluster, device role, platform)

Regla operativa:

  • si un campo ya contiene información válida, fusiónalo;
  • no lo sustituyas por una versión más corta salvo que sea claramente una corrección;
  • si el campo tiene mezcla de información buena y basura, limpia solo la basura y conserva lo útil.

Especialmente importante:

  • comments debe seguir siendo un resumen operativo, no un dump enorme;
  • custom_fields debe quedar consistente y no perder claves válidas;
  • tags no deben reducirse por accidente al set mínimo del ejemplo.

Estrategia segura para comments

comments debe quedar legible para humanos y LLMs. Patrón recomendado:

## <nombre del sistema>

<qué es y para qué sirve>

### Acceso
- URL o hostname principal
- FQDN de Tailscale si aplica
- vía de acceso operativa

### IaC / Código
- stack o repo
- dashboard o URL relevante

### Secretos
- proyecto GSM
- prefijo de secretos

### Notas operativas
- dependencias
- límites
- pendientes relevantes

No metas en comments:

  • secretos;
  • logs completos;
  • salidas enteras de CLI;
  • tickets temporales o notas de sprint sin valor duradero.

Paso 5 — Registrar journal del cambio

Tras editar, añade una entrada de journal con:

  • qué cambió;
  • por qué cambió;
  • fuente de verdad usada;
  • pendientes o dudas que sigan abiertas.

Paso 6 — Verificación posterior

Después del PATCH:

  • relee el objeto por API;
  • confirma que no se han perdido tags ni custom fields;
  • si el LLM usa MCP, vuelve a consultarlo por MCP para comprobar que el inventario leído coincide con el escrito.

Abrir en el navegador (desde Tailscale) la URL display_url devuelta en la respuesta de creación para confirmar visualmente.


8. Plantillas por tipo de sistema

Web/app en Cloudflare Pages (Site)

{
  "name": "<nombre-legible> (Cloudflare Pages)",
  "slug": "cf-pages-<proyecto>",
  "status": "active",
  "group": 1,
  "tenant": 1,
  "description": "Descripción breve. SPA/Web desplegada en Cloudflare Pages.",
  "comments": "## <nombre> — Web\n\n**URL pública:** https://dominio.io\n\n### Cloudflare Pages\n- Proyecto: <nombre-proyecto>\n- Account ID: <id>\n- Zone ID: <id>\n\n### Código fuente\n- Repo: <ruta>\n- Build: npm run build\n- Deploy: wrangler pages deploy dist\n\n### DNS\n- dominio.io CNAME → <proyecto>.pages.dev",
  "tags": [
    {"id": 34},
    {"id": 35},
    {"id": 36},
    {"id": 37},
    {"id": 38},
    {"id": 6},
    {"id": 8}
  ],
  "custom_fields": {
    "iac_stack": "cloudflare-pages",
    "sot_system": "cloudflare",
    "access_model": "public",
    "service_tier": "prod",
    "gsm_secret_project": "iort-secrets",
    "operator_portal_url": "https://dash.cloudflare.com/<account-id>/pages/view/<proyecto>"
  }
}

IDs de tags en la plantilla: 34=managed-by-manual, 35=cloudflare, 36=cloudflare-pages, 37=exposure-public, 38=env-prod, 6=iort, 8=infra. Verificar IDs actuales antes de usar (pueden cambiar si se añaden tags).

VPS en Scaleway (VirtualMachine)

{
  "name": "<hostname>",
  "status": "active",
  "cluster": 1,
  "site": 1,
  "tenant": 1,
  "platform": null,
  "description": "Descripción del servicio en una línea.",
  "comments": "## <hostname>\n\n**IP pública:** x.x.x.x\n**Tailscale:** <hostname>.tailf75db9.ts.net\n**Acceso:** tailscale ssh root@<hostname>\n\n### IaC\n- Pulumi stack: mmiguez/prod en proyecto iort-netbox\n- URL stack: https://app.pulumi.com/mmiguez/...\n\n### Secretos\n- Prefijo GSM: <prefijo>-\n- Proyecto GSM: iort-secrets",
  "tags": [
    {"id": 1},
    {"id": 2},
    {"id": 3},
    {"id": 38}
  ],
  "custom_fields": {
    "iac_stack": "<nombre-stack-pulumi>",
    "sot_system": "pulumi",
    "access_model": "tailscale",
    "service_tier": "prod",
    "gsm_secret_project": "iort-secrets",
    "gsm_secret_prefix": "<prefijo>-prod-",
    "tailscale_dns": "<hostname>.tailf75db9.ts.net"
  }
}

Patch mínimo para editar un objeto existente

Cambiar solo la URL del detalle y el payload según el tipo de objeto:

curl -s -X PATCH "https://iort-netbox.tailf75db9.ts.net/api/<ruta-del-objeto>/<ID>/" \
  -H "Authorization: Bearer $NETBOX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Nueva descripción",
    "comments": "Comentarios actualizados",
    "tags": [{"id": 1}, {"id": 2}, {"id": 38}],
    "custom_fields": {
      "sot_system": "pulumi",
      "service_tier": "prod",
      "access_model": "tailscale"
    }
  }'

Úsalo como plantilla para:

  • dcim/sites
  • dcim/devices
  • virtualization/virtual-machines
  • virtualization/clusters

Rutas API útiles

Lectura de listas:

  • /api/dcim/sites/
  • /api/dcim/devices/
  • /api/virtualization/virtual-machines/
  • /api/virtualization/clusters/
  • /api/tenancy/tenants/
  • /api/extras/journal-entries/

Lectura de detalle:

  • /api/dcim/sites/<id>/
  • /api/dcim/devices/<id>/
  • /api/virtualization/virtual-machines/<id>/
  • /api/virtualization/clusters/<id>/

Escritura habitual:

  • POST a la colección para crear;
  • PATCH al detalle para editar;
  • POST /api/extras/journal-entries/ para registrar contexto.

9. Qué no meter en NetBox

❌ No meter ✅ Alternativa
Contraseñas, tokens, claves privadas GSM (iort-secrets)
Outputs completos de CLI o logs Journal con resumen, enlace al log
Configuración completa de un servicio Repo git + enlace en runbook_ref
IPs y DNS de Cloudflare o CDN externo Sí modelar el Site, no hacer IPAM de infra de terceros
VMs ficticias para funciones serverless Site en grupo Cloud
Estado en tiempo real (uptime, métricas) Grafana / observabilidad externa
Tickets efímeros o TODOs de sprint Jira/GitHub Issues; en journal solo hitos relevantes

10. Referencia de IDs útiles

Verificar con GET /api/.../?limit=50 antes de usarlos, pueden cambiar.

Tenants

ID Nombre
1 IORT Shared Services
2 CustoChef

Sites

ID Nombre Uso
1 ES-MAD-SCW-01 VPS Scaleway Madrid (site principal para VMs)
3 scw-fr-par-1 Scaleway París
4 iort.io (Cloudflare Pages) Web corporativa iort.io

Site Groups

ID Nombre
1 Cloud
2 Scaleway / Dedibox

Clusters

ID Nombre Tipo
1 es-mad-scw-vps-01 VPS (Scaleway Madrid)
2 custochef111-service-hosts Edge Service Host

Tags más usadas

ID Slug
1 managed-by-pulumi
2 access-tailscale
3 backup-restic
4 secret-ref-gsm
6 iort
8 infra
32 scaleway
33 tailscale
34 managed-by-manual
35 cloudflare
36 cloudflare-pages
37 exposure-public
38 env-prod

Documentos relacionados

  • ../docs/netbox/information-model.md
  • ../docs/netbox/secrets.md
  • ../docs/netbox/mcp-access.md
  • ../docs/netbox/validation.md
  • Manual de uso completo

11. Checklist final para LLMs

Antes de terminar una tarea de documentación en NetBox, comprueba esto:

  • he confirmado que el acceso es dentro de Tailscale;
  • he identificado si la operación era create o edit;
  • he buscado duplicados por más de una clave;
  • he elegido el tipo de objeto correcto;
  • he preservado información útil ya existente;
  • he evitado meter secretos;
  • he dejado tags y custom fields coherentes;
  • he releído el objeto final;
  • he añadido journal si hubo creación o cambio relevante.

Si una de esas respuestas es “no”, la tarea probablemente no está cerrada del todo.