Descripción general
AgileTrust Tokenization reemplaza datos sensibles con tokens que preservan el formato: valores que lucen idénticos al original (misma longitud, misma estructura, mismo conjunto de caracteres) pero son sustituciones criptográficamente seguras.
A diferencia del enmascaramiento tradicional o el hashing, la tokenización es completamente reversible. Con la misma clave, el valor original puede recuperarse en cualquier momento sin necesidad de almacenar una tabla de correspondencias.
AgileTrust Tokenization está construido sobre FF3-1 (NIST SP 800-38G Revisión 1) con AES-128, AES-192 o AES-256 — el mismo estándar utilizado por los principales procesadores de pago y bóvedas de datos de salud.
Propiedades clave
- Preserva el formato: un número de tarjeta de 16 dígitos tokeniza en otro de 16 dígitos; un RUT chileno como
13301430-6tokeniza en35240589-5. - Determinista: la misma entrada + misma clave + misma codificación + mismo tweak siempre produce el mismo token.
- Reversible:
/detokenizerecupera el valor original con 100% de fidelidad. - Preserva símbolos: espacios, guiones,
@,.,()y todos los caracteres no alfanuméricos permanecen en sus posiciones exactas. - Transparente al esquema: no requiere cambios en la base de datos, ni actualizar reglas de validación, ni reescribir expresiones regulares.
Cómo funciona
Cada solicitud de tokenización pasa por tres etapas:
- Normalizar — La entrada se normaliza a NFC (Forma de Normalización C de Unicode) para garantizar una representación de bytes consistente entre plataformas y locales.
- Dividir — La cadena se separa en dos listas: caracteres tokenizables (letras y dígitos del alfabeto de la codificación elegida) y caracteres de paso (todo lo demás: espacios, guiones, puntuación, símbolos). Las posiciones de paso se registran.
- Cifrar — Los caracteres tokenizables se cifran con FF3-1/AES. Para entradas más cortas que el mínimo de FF3 (6 para numérico, 3 para texto), se usa una sustitución con SHA-256 basada en la clave. Para entradas muy largas, los caracteres tokenizables se procesan en segmentos. Los caracteres de paso se reinsertan en sus posiciones originales.
La destokenización ejecuta exactamente el mismo proceso en sentido inverso — el cifrado opera de forma idéntica en ambas direcciones.
Modos de codificación
El parámetro encoding controla qué caracteres se consideran "tokenizables"
y establece el alfabeto de cifrado.
| Modo | Alfabeto | Longitud máxima | Ideal para |
|---|---|---|---|
| numeric | 0123456789 (base 10) |
16 caracteres | RUTs, teléfonos, tarjetas de crédito, IDs numéricos |
| latin1 | ~127 letras y dígitos Latin-1 (U+0021–U+00FF) | 256 caracteres | Nombres en Europa occidental, direcciones |
| utf8 (por defecto) | ~256 letras y dígitos Unicode (BMP) | 256 caracteres | Cualquier nombre Unicode, texto libre, email, datos multilingüe |
Se debe usar la misma codificación en /tokenize y en
/detokenize. Usar una codificación diferente al destokenizar devolverá
texto plano incorrecto sin ningún error — esta es una propiedad de
seguridad de los algoritmos FPE.
Cómo elegir el modo correcto
- Use
numericpara cualquier valor compuesto principalmente de dígitos, incluso si contiene separadores como guiones o paréntesis (estos se preservan, no se cifran). - Use
utf8(por defecto) para nombres, texto libre, correos electrónicos y cualquier cadena con caracteres no latinos. - Use
latin1para nombres en Europa occidental cuando necesite tokens que permanezcan dentro de la página de códigos Latin-1 (sistemas legados que no soportan caracteres por encima de U+00FF).
Inicio rápido
1. Ejecutar el contenedor
export TOKENIZATION_KEY="$(python3 -c 'import os,base64; print(base64.b64encode(os.urandom(32)).decode())')" export API_KEY="$(openssl rand -hex 24)" docker run -d \ --name tokenizer \ -p 8000:8000 \ -e TOKENIZATION_KEY="$TOKENIZATION_KEY" \ -e API_KEY="$API_KEY" \ agiletrust/tokenization:0.3
El contenedor inicia un servidor FastAPI en el puerto 8000. TOKENIZATION_KEY
debe ser una clave AES en base64 — 16, 24 o 32 bytes (AES-128/192/256).
API_KEY es el secreto compartido requerido en el header X-API-Key.
2. Tokenizar un valor
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "13301430-6", "encoding": "numeric"}' | jq .
{
"token": "35240589-5",
"algorithm": "FF3-1/AES",
"encoding": "numeric"
}
3. Destokenizar para recuperar el original
curl -s -X POST http://localhost:8000/detokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"token": "35240589-5", "encoding": "numeric"}' | jq .
{
"plaintext": "13301430-6"
}
El guion de 13301430-6 se preserva en la posición 8, tanto en el
texto original como en el token. Solo los dígitos son cifrados.
Autenticación
Todas las solicitudes a /tokenize y /detokenize requieren el
encabezado X-API-Key con la clave API compartida.
El endpoint /health es abierto (requerido para sondas de liveness).
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "13301430-6", "encoding": "numeric"}' | jq .
| Condición | Estado HTTP | Cuerpo de respuesta |
|---|---|---|
| Encabezado ausente | 401 Unauthorized | {"error": "Missing API key."} |
| Encabezado presente pero valor incorrecto | 403 Forbidden | {"error": "Invalid API key."} |
| Clave correcta | 200 OK | Token / texto en claro |
En modo single-tenant, la clave API se configura mediante la variable de
entorno API_KEY y es compartida por todos los llamantes. La clave AES se
configura mediante TOKENIZATION_KEY. Ninguna de las dos aparece en registros
ni en respuestas — los clientes nunca envían material de clave en los cuerpos de solicitud.
En modo multi-tenant (MULTI_TENANT=true), las API keys se
generan por aplicación desde la consola de administración. Cada clave se almacena como hash
SHA-256 y resuelve su propia clave AES desde la base de datos en cada solicitud.
Consulte la sección Multi-Tenant y Consola para los detalles de configuración.
Restrinja el acceso de red al contenedor de tokenización. Nunca debe estar expuesto a internet. Despliegue dentro de una VPC privada o detrás de un API gateway con mTLS.
GET /health
Devuelve el estado del servicio y la versión actual. Utilice este endpoint para sondas de liveness y monitoreo de disponibilidad.
Respuesta — 200 OK
{
"status": "ok",
"version": "0.3"
}
POST /tokenize
Convierte una cadena de texto en claro en un token que preserva el formato.
Cuerpo de la solicitud
| Campo | Tipo | Descripción | |
|---|---|---|---|
| plaintext | requerido | string | El valor a tokenizar. Puede ser vacío (devuelve token vacío). |
| encoding | opcional | string enum | utf8 (por defecto), latin1 o numeric. |
| tweak | opcional | string | 14 caracteres hex (7 bytes). Contexto a nivel de campo. Por defecto: 00000000000000. |
Respuesta — 200 OK
| Campo | Tipo | Descripción |
|---|---|---|
| token | string | Token que preserva el formato. Misma longitud que la entrada normalizada NFC. |
| algorithm | string | Siempre "FF3-1/AES". |
| encoding | string | El modo de codificación usado (devuelto en la respuesta). |
Respuesta — 422 Unprocessable Entity
Se devuelve cuando hay errores de validación. El campo error describe el problema:
| Causa | Mensaje de error |
|---|---|
Falta plaintext | plaintext: Field required |
| Codificación desconocida | encoding: Value error, 'encoding' must be one of ['latin1', 'utf8', 'numeric']; got 'ascii'. |
| Entrada numérica > 16 caracteres | plaintext: Value error, 'plaintext' exceeds maximum length of 16 characters. |
| Texto > 256 caracteres | plaintext: Value error, 'plaintext' exceeds maximum length of 256 characters. |
| Formato de tweak inválido | tweak: Value error, 'tweak' must be exactly 14 hex characters (7 bytes). |
POST /detokenize
Invierte un token producido por /tokenize, recuperando el texto original.
Una clave o codificación incorrecta devuelve HTTP 200 con texto incorrecto. No hay indicador de error. Esta es una propiedad de seguridad fundamental de los algoritmos FPE — un atacante no puede saber si la destokenización tuvo éxito o falló.
Cuerpo de la solicitud
| Campo | Tipo | Descripción | |
|---|---|---|---|
| token | requerido | string | Token devuelto por /tokenize. |
| encoding | opcional | string enum | Debe coincidir con la codificación usada en la tokenización. Por defecto: utf8. |
| tweak | opcional | string | Debe coincidir con el tweak usado en la tokenización. Por defecto: 00000000000000. |
Respuesta — 200 OK
| Campo | Tipo | Descripción |
|---|---|---|
| plaintext | string | El valor original recuperado del token. |
RUT y valores numéricos
Use "encoding": "numeric" para cualquier identificador con dígitos. Los separadores se preservan automáticamente.
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "13301430-6", "encoding": "numeric"}' | jq .
{
"token": "35240589-5",
"algorithm": "FF3-1/AES",
"encoding": "numeric"
}
El guion en la posición 8 se preserva. Solo los 9 dígitos son cifrados. El token sigue siendo un RUT válido en formato.
curl -s -X POST http://localhost:8000/detokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"token": "35240589-5", "encoding": "numeric"}' | jq .
{
"plaintext": "13301430-6"
}
Recuperación perfecta del RUT original.
# Teléfono chileno
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "+56 9 8765 4321", "encoding": "numeric"}' | jq .
{
"token": "+56 9 2341 8907",
"algorithm": "FF3-1/AES",
"encoding": "numeric"
}
El prefijo +56, los espacios y la estructura del número se preservan. Solo los dígitos variables se cifran.
Nombres y texto libre
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "Juan Pérez-García"}' | jq .
{
"token": "Ñkrp Çmevq-Ĥapcw",
"algorithm": "FF3-1/AES",
"encoding": "utf8"
}
El espacio y el guion permanecen en sus posiciones. Todas las letras son cifradas con el alfabeto Unicode.
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "Juan Pérez-García", "encoding": "latin1"}' | jq .
{
"token": "YrfãröxÍ-Ý",
"algorithm": "FF3-1/AES",
"encoding": "latin1"
}
El token permanece dentro del rango Latin-1 (U+0000–U+00FF). Ideal para sistemas legados.
Correo electrónico
El símbolo @ y el punto . siempre se preservan, haciendo que
los correos tokenizados sean sintácticamente válidos en la mayoría de los validadores.
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "juan.perez@empresa.cl"}' | jq .
{
"token": "ĭğĬħ.ŋĕĮĕž@ĩźĝŋĪĮĩ.ĕĝ",
"algorithm": "FF3-1/AES",
"encoding": "utf8"
}
Uso del Tweak
Un tweak es una cadena hexadecimal de 14 caracteres (7 bytes) que aporta contexto a nivel de campo. El mismo texto en claro tokenizado con tweaks diferentes produce tokens completamente distintos, impidiendo que un atacante correlacione tokens entre campos.
# Tokenizar como campo "nombre"
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "Maria Lopez", "tweak": "6e6f6d62726500"}' | jq .token
# Tokenizar como campo "alias"
curl -s -X POST http://localhost:8000/tokenize \
-H "Content-Type: application/json" \
-H "X-API-Key: su-clave-api-aqui" \
-d '{"plaintext": "Maria Lopez", "tweak": "616c6961730000"}' | jq .token
Valores de tweak comunes
| Campo | Tweak (hex) | Origen |
|---|---|---|
| nombre / name | 6e616d65000000 | ASCII "name" + relleno cero |
| rut | 72757400000000 | ASCII "rut" + relleno cero |
656d61696c0000 | ASCII "email" + relleno cero | |
| telefono / phone | 70686f6e650000 | ASCII "phone" + relleno cero |
| dirección / addr | 61646472000000 | ASCII "addr" + relleno cero |
Ejemplo completo en Python
import requests
BASE_URL = "http://localhost:8000"
ENCABEZADOS = {"X-API-Key": "su-clave-api-aqui"}
def tokenizar(texto, codificacion="utf8", tweak=None):
"""Convierte un texto en un token que preserva el formato."""
payload = {"plaintext": texto, "encoding": codificacion}
if tweak:
payload["tweak"] = tweak
respuesta = requests.post(f"{BASE_URL}/tokenize", json=payload, headers=ENCABEZADOS)
respuesta.raise_for_status()
return respuesta.json()["token"]
def destokenizar(token, codificacion="utf8", tweak=None):
"""Recupera el texto original a partir de un token."""
payload = {"token": token, "encoding": codificacion}
if tweak:
payload["tweak"] = tweak
respuesta = requests.post(f"{BASE_URL}/detokenize", json=payload, headers=ENCABEZADOS)
respuesta.raise_for_status()
return respuesta.json()["plaintext"]
# ── Ejemplos de uso ───────────────────────────────────────
# RUT chileno
token_rut = tokenizar("13301430-6", codificacion="numeric")
print(f"RUT token: {token_rut}") # 35240589-5
print(f"RUT original: {destokenizar(token_rut, 'numeric')}") # 13301430-6
# Nombre completo
token_nombre = tokenizar("María González López")
print(f"Nombre token: {token_nombre}")
print(f"Nombre original: {destokenizar(token_nombre)}")
# Email
token_email = tokenizar("maria@empresa.cl")
print(f"Email token: {token_email}")
# Con tweak por campo
tweak_rut = "72757400000000"
tweak_email = "656d61696c0000"
t1 = tokenizar("maria@empresa.cl", tweak=tweak_email)
t2 = tokenizar("maria@empresa.cl", tweak=tweak_rut)
print(f"Con tweak email: {t1}")
print(f"Con tweak rut: {t2}")
print(f"Son distintos: {t1 != t2}") # True
Límites y restricciones
| Codificación | Longitud máxima total | Nota |
|---|---|---|
numeric | 16 caracteres | Incluye separadores (guiones, paréntesis, etc.) |
latin1 | 256 caracteres | Longitud normalizada NFC |
utf8 | 256 caracteres | Longitud normalizada NFC |
- Las cadenas vacías son aceptadas y devuelven un token vacío.
- La entrada se normaliza a NFC antes de verificar la longitud. Pre-normalice si los límites exactos son críticos.
- El tweak debe tener exactamente 14 caracteres hexadecimales (sin distinción entre mayúsculas y minúsculas). Por defecto:
00000000000000.
Respuestas de error
Todos los errores devuelven JSON con un único campo error:
{ "error": "plaintext: Value error, 'plaintext' exceeds maximum length of 16 characters." }
| Estado HTTP | Significado |
|---|---|
| 200 | Éxito. También se devuelve con clave/codificación incorrecta en destokenización (fallo silencioso por diseño). |
| 422 | Error de validación — campo requerido faltante, codificación desconocida, entrada muy larga o formato de tweak inválido. |
| 500 | Error interno del servidor. Consulte los registros del servidor. |
Preguntas frecuentes
¿Dos textos distintos pueden producir el mismo token?
No. FF3-1 es una permutación pseudoaleatoria (PRP), por lo que la tokenización es una biyección: cada texto en claro corresponde a un token único y viceversa, para una misma clave + tweak + codificación.
¿La longitud del token siempre iguala la del texto original?
Sí. La longitud del token iguala la longitud de la entrada normalizada NFC. Si la entrada ya está en NFC, las longitudes son idénticas.
¿Qué ocurre si tokenizo el mismo valor dos veces?
Obtendrá el mismo token. La tokenización es determinista. Si necesita tokens distintos para el mismo valor en contextos diferentes, use tweaks distintos.
¿Puedo usar el modo numérico para números de tarjeta de crédito?
Sí. Un PAN de 16 dígitos como 4532015112830366 tokenizará en otro número de 16 dígitos. El número tokenizado no pasará la verificación Luhn — esto es esperado e intencional.
¿Cómo sé si la destokenización fue exitosa?
No hay indicador criptográfico. Si necesita verificar la corrección, almacene un hash del texto original y compárelo después de destokenizar.
¿El servicio guarda estado?
La lógica de tokenización es sin estado — no se almacena ninguna tabla de búsqueda y el valor original se deriva matemáticamente del token + clave + tweak + codificación. En modo multi-tenant, el servicio se conecta a PostgreSQL para resolver API keys y configuraciones de aplicaciones, pero cada solicitud sigue siendo independiente sin estado de sesión.
¿Es compatible con la normativa PCI DSS o HIPAA?
AgileTrust Tokenization implementa FPE estándar NIST, que es un componente técnico reconocido en esquemas de tokenización PCI DSS y HIPAA. Consulte a su equipo de cumplimiento para determinar si satisface los requisitos específicos de su organización.
Modo Single-Tenant
El modo de operación por defecto. Una única clave AES se carga al inicio y se comparte entre todas las solicitudes. Adecuado para una sola aplicación o cuando la aislación se gestiona a nivel de infraestructura.
Variables de entorno requeridas
| Variable | Requerida | Descripción |
|---|---|---|
| TOKENIZATION_KEY | Sí | Clave AES en base64 (16, 24 o 32 bytes) |
| API_KEY | Sí | Secreto compartido para el header X-API-Key |
| KEY_PROVIDER | No | Fuente de la clave: env (por defecto), aws_secretsmanager, aws_kms, azure_keyvault, gcp_secretmanager, oci_vault, ciphertrust |
| DEFAULT_TWEAK | No | 14 caracteres hexadecimales (7 bytes). Sobreescribe el tweak por defecto (ceros). |
export TOKENIZATION_KEY="$(python3 -c 'import os,base64; print(base64.b64encode(os.urandom(32)).decode())')" export API_KEY="$(openssl rand -hex 24)" docker run -d -p 8000:8000 \ -e TOKENIZATION_KEY="$TOKENIZATION_KEY" \ -e API_KEY="$API_KEY" \ tokenizer:0.3
Modo Multi-Tenant y Consola de Administración
Con MULTI_TENANT=true, el tokenizer resuelve las claves desde PostgreSQL
en cada solicitud. Cada aplicación tiene su propia clave AES, configurada a través de la
Consola AgileTrust — una interfaz de administración Next.js con autenticación SSO.
Arquitectura
- Consola (Next.js, puerto 3000): crear tenants, aplicaciones y API keys. Acceso con Google, Microsoft u Okta.
- Tokenizer (FastAPI, puerto 8000): resuelve la API key desde la base de datos, descifra el
provider_configde la aplicación y cachea la clave porKEY_CACHE_TTLsegundos. - PostgreSQL: base de datos compartida. La consola tiene acceso lectura-escritura; el tokenizer usa una conexión de solo lectura.
MASTER_ENCRYPTION_KEY debe ser idéntica en la consola y el tokenizer.
Es la clave AES-256 usada para cifrar el provider_config de cada aplicación en la base de datos.
Nunca la almacene en la propia base de datos.
Variables de entorno multi-tenant (tokenizer)
| Variable | Requerida | Descripción |
|---|---|---|
| MULTI_TENANT | Sí | "true" para activar |
| DATABASE_URL | Sí | Connection string de PostgreSQL (se recomienda solo lectura) |
| MASTER_ENCRYPTION_KEY | Sí | Clave AES-256 de 32 bytes, en base64. Igual que en la consola. |
| KEY_CACHE_TTL | No | Segundos de caché por aplicación. Por defecto: 300. |
Inicio rápido (Docker Compose)
export POSTGRES_PASSWORD="$(openssl rand -hex 16)" export NEXTAUTH_SECRET="$(openssl rand -base64 32)" export MASTER_ENCRYPTION_KEY="$(python3 -c 'import os,base64; print(base64.b64encode(os.urandom(32)).decode())')" export GOOGLE_CLIENT_ID="your-client-id" export GOOGLE_CLIENT_SECRET="your-client-secret" # Ejecutar migración Prisma (solo la primera vez) cd console && npm install && npx prisma migrate deploy && cd .. # Levantar los 3 servicios docker compose -f docker-compose.full.yml up -d
Flujo del primer inicio de sesión
- Abrir
http://localhost:3000e iniciar sesión con su proveedor OAuth. - El primer usuario del dominio de email se convierte en owner de un nuevo tenant. Los usuarios siguientes del mismo dominio se añaden como viewers.
- Ir a Applications → New Application. Elegir un proveedor de clave (System Vault es el predeterminado — no necesita vault externo).
- Abrir la aplicación y hacer clic en New Key. Copiar la API key — se muestra una única vez.
- Usar la API key en el header
X-API-Keyal llamar al tokenizer.
Rotación de API key vs. rotación de clave de cifrado
- Rotación de API key: genera una nueva clave
tok_…para la misma aplicación. La clave de cifrado y todos los tokens permanecen válidos. - Rotación de clave de cifrado (solo System Vault): genera una nueva clave AES. Todos los tokens existentes se vuelven irrecuperables — FPE con una clave incorrecta devuelve un valor plausible pero erróneo, sin ninguna señal de error.
Proveedores de clave
En modo single-tenant, seleccione el proveedor con la variable KEY_PROVIDER en el tokenizer.
En modo multi-tenant, cada aplicación selecciona su propio proveedor en la consola — no se necesitan variables de entorno adicionales en el tokenizer.
| Proveedor | Descripción | Parámetros de config |
|---|---|---|
| env | Clave AES desde la variable TOKENIZATION_KEY | — |
| system_vault | Clave AES almacenada cifrada en PostgreSQL (predeterminado multi-tenant) | generada automáticamente por la consola |
| aws_secretsmanager | AWS Secrets Manager | secret_id, region |
| aws_kms | AWS KMS (blob cifrado con KMS) | key_ref (base64), region |
| azure_keyvault | Azure Key Vault | vault_url, secret_name |
| gcp_secretmanager | GCP Secret Manager | secret_resource |
| oci_vault | OCI Vault | secret_id |
| ciphertrust | Thales CipherTrust Manager | Guía de configuración próximamente |
Los SDKs de proveedores cloud no se instalan por defecto. Construya la imagen con el argumento de build correspondiente:
docker build --build-arg KEY_PROVIDER_DEPS=aws .
Valores válidos: aws, azure, gcp, oci, ciphertrust.
env — Variable de entorno (por defecto)
La clave AES se suministra directamente como variable de entorno en base64. Adecuado para despliegues single-tenant o desarrollo local.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=envTOKENIZATION_KEY | Clave AES en base64 (16, 24 o 32 bytes) |
| Multi-tenant | Campo de config: key | Clave AES en base64 |
system_vault — Clave almacenada en base de datos (predeterminado multi-tenant)
La consola genera una clave AES-256 aleatoria al crear la aplicación, la cifra con MASTER_ENCRYPTION_KEY
usando AES-256-GCM, y almacena el texto cifrado en la columna applications.provider_config.
No se requieren credenciales de vault externo ni cloud.
Es el punto de partida recomendado para la mayoría de los despliegues. La clave nunca sale de la base de datos sin cifrar.
Rotación de clave (Applications → [app] → Rotate Key) genera una nueva clave AES. Todos los tokens emitidos con la clave anterior se vuelven irrecuperables — FPE con la clave incorrecta devuelve un valor plausible pero erróneo, sin señal de error.
aws_secretsmanager — AWS Secrets Manager
El tokenizer llama a GetSecretValue al inicio (single-tenant) o en fallo de caché (multi-tenant) para obtener la clave AES en base64 almacenada como secreto de texto plano.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=aws_secretsmanagerAWS_SECRET_IDAWS_REGION | ARN o nombre del secreto p.ej. us-east-1 |
| Multi-tenant | secret_idregion | ARN o nombre del secreto Región de AWS |
Permiso IAM requerido: secretsmanager:GetSecretValue sobre el ARN del secreto.
Use un task role (ECS), instance profile (EC2) o las variables AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
KEY=$(python3 -c 'import os,base64; print(base64.b64encode(os.urandom(32)).decode())') aws secretsmanager create-secret \ --name agiletrust/tokenization-key \ --secret-string "$KEY" \ --region us-east-1
aws_kms — AWS KMS
La clave AES se cifra con una Customer Managed Key (CMK) de AWS KMS y se almacena como blob en base64.
El tokenizer llama a kms:Decrypt para recuperar la clave en texto plano en tiempo de ejecución.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=aws_kmsAWS_KMS_KEY_REFAWS_REGION | Texto cifrado en base64 de aws kms encryptRegión de AWS |
| Multi-tenant | key_refregion | Texto cifrado en base64 Región de AWS |
Permiso IAM requerido: kms:Decrypt sobre la CMK.
KEY=$(python3 -c 'import os,base64; print(base64.b64encode(os.urandom(32)).decode())') CIPHERTEXT=$(aws kms encrypt \ --key-id alias/my-cmk \ --plaintext "$KEY" \ --query CiphertextBlob \ --output text \ --region us-east-1) # Almacenar CIPHERTEXT como AWS_KMS_KEY_REF
azure_keyvault — Azure Key Vault
La clave AES se almacena como secreto en Azure Key Vault. La autenticación usa DefaultAzureCredential de azure-identity, que soporta managed identity, service principal y credenciales de desarrollador automáticamente.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=azure_keyvaultAZURE_VAULT_URLAZURE_SECRET_NAME | https://my-vault.vault.azure.netNombre del secreto en Key Vault |
| Multi-tenant | vault_urlsecret_name | URL HTTPS del Key Vault Nombre del secreto |
RBAC: Otorgue a la identidad del tokenizer el rol Key Vault Secrets User sobre el vault.
Para autenticación con service principal, establezca AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID.
gcp_secretmanager — GCP Secret Manager
La clave AES se almacena como versión de secreto en GCP Secret Manager. La autenticación usa Application Default Credentials (ADC) — se otorga acceso a la cuenta de servicio que ejecuta el contenedor.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=gcp_secretmanagerGCP_SECRET_RESOURCE | Nombre completo del recurso, p.ej.projects/my-project/secrets/tok-key/versions/latest |
| Multi-tenant | secret_resource | Nombre completo del recurso |
IAM: Otorgue a la cuenta de servicio el rol Secret Manager Secret Accessor (roles/secretmanager.secretAccessor) sobre el secreto.
oci_vault — OCI Vault
La clave AES se almacena como secreto en Oracle Cloud Infrastructure Vault. La autenticación usa el archivo de configuración OCI o instance principal al ejecutarse en OCI Compute.
| Modo | Variable / campo | Valor |
|---|---|---|
| Single-tenant | KEY_PROVIDER=oci_vaultOCI_SECRET_ID | OCID del secreto, p.ej.ocid1.vaultsecret.oc1.phx.xxx |
| Multi-tenant | secret_id | OCID del secreto |
IAM: Otorgue al instance principal o al dynamic group el permiso manage secret-family en el compartimento del vault.
Variables de entorno de la consola
La consola de administración es una aplicación Next.js configurada completamente mediante variables de entorno.
Establézcalas en un archivo .env en la raíz de console/, o páselas directamente a Docker.
| Variable | Requerida | Descripción |
|---|---|---|
| DATABASE_URL | Sí | Connection string de PostgreSQL. Ejemplo: postgresql://user:pass@host:5432/agiletrust |
| NEXTAUTH_URL | Sí | URL base pública de la consola. Ejemplo: https://console.ejemplo.com. Se usa para construir las URLs de callback OAuth. |
| NEXTAUTH_SECRET | Sí | Secreto aleatorio para firmar tokens de sesión. Generar con openssl rand -base64 32. |
| MASTER_ENCRYPTION_KEY | Sí | Clave AES-256 de 32 bytes en base64. Cifra el provider_config de aplicaciones en la base de datos. Debe ser idéntica en la consola y el tokenizer. |
| GOOGLE_CLIENT_ID | Opcional* | Client ID de OAuth 2.0 de Google. |
| GOOGLE_CLIENT_SECRET | Opcional* | Client Secret de OAuth 2.0 de Google. |
| AZURE_AD_CLIENT_ID | Opcional* | ID de aplicación (client) de Microsoft Entra ID. |
| AZURE_AD_CLIENT_SECRET | Opcional* | Secreto de cliente de Microsoft Entra ID. |
| AZURE_AD_TENANT_ID | Opcional* | ID de tenant (directorio) de Microsoft Entra ID. |
| OKTA_CLIENT_ID | Opcional* | Client ID de la aplicación OIDC de Okta. |
| OKTA_CLIENT_SECRET | Opcional* | Client Secret de la aplicación OIDC de Okta. |
| OKTA_ISSUER | Opcional* | URL del servidor de autorización de Okta. Ejemplo: https://su-org.okta.com/oauth2/default |
| KEYCLOAK_CLIENT_ID | Opcional* | Client ID del cliente Keycloak. |
| KEYCLOAK_CLIENT_SECRET | Opcional* | Client Secret del cliente Keycloak. |
| KEYCLOAK_ISSUER | Opcional* | URL del realm de Keycloak. Ejemplo: https://keycloak.ejemplo.com/realms/myrealm |
| POSTGRES_PASSWORD | No | Usado solo en docker-compose.full.yml para establecer la contraseña del superusuario PostgreSQL. |
* Debe configurarse al menos un proveedor OAuth.
Las cuatro variables principales (DATABASE_URL, NEXTAUTH_URL, NEXTAUTH_SECRET, MASTER_ENCRYPTION_KEY) son siempre obligatorias. Las variables de proveedor OAuth son obligatorias solo para los proveedores que desee activar — puede activar varios simultáneamente.
SSO / Proveedores de identidad
La consola autentica usuarios mediante OAuth 2.0 / OIDC a través de NextAuth.js v5. Configure al menos uno de los cuatro proveedores soportados. Todos los proveedores activados aparecen simultáneamente en la página de inicio de sesión.
- Abra Google Cloud Console → APIs y servicios → Credenciales.
- Haga clic en Crear credenciales → ID de cliente OAuth 2.0.
- Establezca el tipo de aplicación como Aplicación web.
- En URIs de redireccionamiento autorizados, agregue:
{NEXTAUTH_URL}/api/auth/callback/google - Copie el Client ID y el Client Secret en sus variables de entorno.
| Variable | Dónde encontrarla |
|---|---|
| GOOGLE_CLIENT_ID | Credenciales → OAuth 2.0 → Client ID |
| GOOGLE_CLIENT_SECRET | Credenciales → OAuth 2.0 → Client Secret |
Microsoft Entra ID (Azure AD)
- Abra el portal de Azure → Microsoft Entra ID → Registros de aplicaciones.
- Haga clic en Nuevo registro. Asigne un nombre; en Tipos de cuenta admitidos elija el ámbito deseado.
- En URI de redireccionamiento, seleccione Web e ingrese:
{NEXTAUTH_URL}/api/auth/callback/azure-ad - Tras la creación, anote el ID de aplicación (client) y el ID de directorio (tenant).
- Vaya a Certificados y secretos → Nuevo secreto de cliente. Copie el valor del secreto (no el ID).
| Variable | Dónde encontrarla |
|---|---|
| AZURE_AD_CLIENT_ID | Registro de aplicación → Información general → ID de aplicación (client) |
| AZURE_AD_CLIENT_SECRET | Certificados y secretos → Valor |
| AZURE_AD_TENANT_ID | Registro de aplicación → Información general → ID de directorio (tenant) |
Okta
- Abra la Consola de administración de Okta → Applications → Create App Integration.
- Seleccione OIDC – OpenID Connect como método de inicio de sesión y Web Application como tipo de aplicación.
- En Sign-in redirect URIs, agregue:
{NEXTAUTH_URL}/api/auth/callback/okta - Copie el Client ID y el Client Secret de la configuración de la aplicación.
- La URL del Issuer se encuentra en Security → API → Authorization Servers. Por defecto:
https://su-org.okta.com/oauth2/default.
| Variable | Dónde encontrarla |
|---|---|
| OKTA_CLIENT_ID | Configuración de la aplicación → Client ID |
| OKTA_CLIENT_SECRET | Configuración de la aplicación → Client Secret |
| OKTA_ISSUER | Security → API → Authorization Servers → Issuer URI |
Keycloak
- Abra la Consola de administración de Keycloak → seleccione su realm → Clients → Create client.
- Establezca el Client type como OpenID Connect e ingrese un Client ID.
- Active Client authentication (tipo de acceso confidencial).
- En Valid redirect URIs, agregue:
{NEXTAUTH_URL}/api/auth/callback/keycloak - Guarde. Vaya a la pestaña Credentials y copie el Client Secret.
- La URL del issuer es la URL de su realm:
https://<host>/realms/<nombre-realm>.
| Variable | Dónde encontrarla |
|---|---|
| KEYCLOAK_CLIENT_ID | Clients → su cliente → Settings → Client ID |
| KEYCLOAK_CLIENT_SECRET | Clients → su cliente → Credentials → Client Secret |
| KEYCLOAK_ISSUER | https://<host>/realms/<nombre-realm> |
Si Keycloak se ejecuta detrás de un proxy inverso, asegúrese de que KEYCLOAK_ISSUER coincida con la URL pública. El endpoint de descubrimiento OIDC debe ser accesible desde la consola en {KEYCLOAK_ISSUER}/.well-known/openid-configuration.
Aprovisionamiento de tenant: El primer usuario que inicie sesión con un dominio de correo electrónico determinado se convierte automáticamente en owner de un nuevo tenant (con el nombre del dominio). Todos los usuarios siguientes del mismo dominio se añaden como viewer. El owner puede posteriormente promover a cualquier usuario a admin.
Roles y permisos
La consola aplica una jerarquía de tres niveles de roles. Un usuario con un rol de rango superior hereda todos los permisos de los roles de rango inferior.
| Rol | Rango | Capacidades |
|---|---|---|
| owner | 3 | Control total: gestionar configuración del tenant, crear/editar/eliminar aplicaciones, gestionar API keys, gestionar usuarios (cambiar roles, eliminar), ver registro de auditoría. |
| admin | 2 | Crear/editar/eliminar aplicaciones, gestionar API keys (crear, revocar, rotar). Ver usuarios y registro de auditoría. No puede gestionar roles de usuarios. |
| viewer | 1 | Acceso de solo lectura a todos los recursos. No puede crear ni modificar nada. |
- El primer usuario en iniciar sesión para un dominio de correo determinado se convierte en
owner. - Los usuarios siguientes del mismo dominio se unen como
viewer. - Un owner puede promover un viewer a admin, un admin a owner, o degradar a cualquier usuario.
- Un owner no puede eliminarse ni degradarse a sí mismo si es el último owner del tenant.
Gestionar aplicaciones
Una aplicación representa un servicio lógico que llamará a la API del tokenizer.
Cada aplicación tiene su propia clave AES de cifrado y genera sus propias API keys.
Requiere el rol admin u owner.
Crear una aplicación
- Navegue a Applications en la barra lateral.
- Haga clic en New Application.
- Ingrese un Nombre (obligatorio) y una Descripción opcional.
- Seleccione un Proveedor de clave.
system_vaultes el predeterminado — la consola genera y almacena la clave AES automáticamente; no se necesitan credenciales de vault externo. - Para proveedores cloud, complete los campos de configuración requeridos (p.ej.,
secret_idyregionpara AWS Secrets Manager). - Haga clic en Create. La aplicación se crea y su clave se aprovisiona inmediatamente.
Campos de configuración por proveedor
| Proveedor | Campos mostrados en el formulario de la consola |
|---|---|
| system_vault | Ninguno — la clave se genera automáticamente |
| env | key (clave AES en base64, campo de contraseña) |
| aws_secretsmanager | secret_id, region |
| aws_kms | key_ref (texto cifrado en base64, campo de contraseña), region |
| azure_keyvault | vault_url, secret_name |
| gcp_secretmanager | secret_resource |
| oci_vault | secret_id |
Editar o eliminar una aplicación
Abra la página de detalle de la aplicación y use los botones Edit o Delete.
Eliminar una aplicación revoca inmediatamente todas sus API keys — cualquier solicitud en curso que use esas claves recibirá 403 Forbidden.
Gestionar API keys
Las API keys autentican las llamadas al tokenizer. Cada clave está asociada a una sola aplicación y resuelve la clave AES de esa aplicación.
Requiere el rol admin u owner.
Crear una clave
- Abra la página de detalle de una aplicación.
- Haga clic en New Key.
- Opcionalmente ingrese una Etiqueta (p.ej., producción, staging) y una fecha de expiración.
- Haga clic en Generate. La clave completa se muestra exactamente una vez — cópiela inmediatamente.
El texto en claro de la API key nunca se almacena. Solo su hash SHA-256 se guarda en la base de datos. Si pierde la clave, revóquela y cree una nueva.
Formato de la clave: tok_{tenant_slug}_{32 caracteres aleatorios}
Ejemplo: tok_acmecorp_K7mP2xQnRvW9jLdF8bTsZeYhAcGiNuXo
Revocar una clave
Haga clic en Revoke junto a la clave. La clave deja de funcionar inmediatamente — el tokenizer la rechaza con 403 Forbidden en la siguiente solicitud.
Rotar una clave
Haga clic en Rotate. Esto genera atómicamente una nueva clave y revoca la anterior. El nuevo texto en claro se muestra una vez. Use la rotación para reemplazar una clave que pueda haber sido expuesta sin interrumpir el servicio entre la generación y el despliegue.
Gestionar usuarios
Los usuarios se unen automáticamente — cualquier persona que inicie sesión con una dirección de correo electrónico perteneciente al dominio del tenant se añade como viewer.
No se requiere ningún paso de invitación.
Ver usuarios
Navegue a Users en la barra lateral para ver todos los usuarios, sus roles, la fecha del último inicio de sesión y el proveedor OAuth.
Cambiar el rol de un usuario
Haga clic en el badge de rol junto a un usuario para abrir el selector de rol. Requiere el rol owner. Transiciones disponibles:
viewer→adminoowneradmin→vieweroownerowner→adminoviewer(solo si existe al menos otro owner)
Eliminar un usuario
Haga clic en Remove junto al usuario. Requiere el rol owner. La sesión del usuario se invalida y no puede volver a iniciar sesión a menos que se reautentique (en cuyo caso se uniría nuevamente como viewer).
Un tenant debe tener siempre al menos un owner. El último owner no puede degradarse ni eliminarse a sí mismo.
Registro de auditoría
Cada acción de mutación realizada a través de la consola se registra en el log de auditoría. Navegue a Audit Log en la barra lateral para ver y paginar las entradas.
Cada entrada registra: fecha/hora, actor (nombre y correo del usuario), dirección IP, tipo de acción, recurso objetivo y un blob JSON de detalles opcional.
| Acción | Objetivo | Descripción |
|---|---|---|
| tenant.update | Tenant | Nombre o configuración del tenant modificada |
| application.create | Aplicación | Nueva aplicación creada |
| application.update | Aplicación | Nombre, descripción o config del proveedor modificados |
| application.delete | Aplicación | Aplicación y todas sus API keys eliminadas |
| application.rotate_key | Aplicación | Clave de cifrado (system_vault) rotada — los tokens existentes se vuelven irrecuperables |
| api_key.create | API Key | Nueva API key generada para una aplicación |
| api_key.revoke | API Key | API key revocada |
| api_key.rotate | API Key | API key rotada (nueva clave generada, anterior revocada) |
| user.invite | Usuario | Usuario invitado (reservado para flujo de invitación futuro) |
| user.role_change | Usuario | Rol de usuario modificado |
| user.remove | Usuario | Usuario eliminado del tenant |
Las entradas del registro de auditoría son de solo escritura y no pueden eliminarse a través de la consola. Requiere rol admin o superior para visualizarlas.