NBOX es un servicio backend escrito en Go, diseñado para actuar como una solución centralizada y segura para la administración de variables de entorno, secretos y plantillas de configuración en entornos de desarrollo modernos.
- Almacén Centralizado: Gestiona variables y secretos para múltiples servicios y entornos (desarrollo, QA, producción) desde un único lugar.
- Integración Nativa con AWS:
- Variables: Almacenadas en AWS DynamoDB, con historial de cambios para auditoría.
- Secretos: Guardados de forma segura en AWS Parameter Store utilizando una clave de cifrado propia de AWS KMS.
- Plantillas: Versionadas y almacenadas en AWS S3 (por ejemplo, definiciones de tareas de ECS, archivos de configuración, etc.).
- Procesamiento Dinámico de Plantillas: Reemplaza variables (
{{...}}) y marcadores de posición (:...) dentro de las plantillas al momento de solicitarlas, permitiendo la generación de configuraciones dinámicas. - Seguridad Robusta:
- Autenticación: Soporta tanto HTTP Basic Auth como JWT para proteger los endpoints.
- Autorización: Utiliza Open Policy Agent (OPA) para un control de acceso granular y basado en roles.
- Go 1.24+
- Docker
- Credenciales de AWS configuradas en el entorno.
-
Clonar el repositorio:
git clone <tu-repositorio> cd nbox
-
Configurar variables de entorno: Crea un archivo
.envo exporta las siguientes variables. Consulta la sección de Configuración para más detalles.export AWS_REGION=us-east-1 export NBOX_ENTRIES_TABLE_NAME=nbox-entries-development export NBOX_BOX_TABLE_NAME=nbox-box-development export NBOX_BUCKET_NAME=tu-bucket-nbox-development export NBOX_BASIC_AUTH_CREDENTIALS='{"user":{"password": "$2a$10$...", "roles": ["admin"], "status": "active"}}'
Nota: Para generar el hash de la contraseña, puedes usar la herramienta
hasherincluida encmd/hasher. -
Instalar dependencias y herramientas:
make install-all-deps tools
-
Ejecutar el servicio:
go run cmd/nbox/main.go
El servicio estará disponible en
http://localhost:7337.
A continuación se muestran los endpoints principales y ejemplos de uso.
Genera un token JWT para autenticar las siguientes peticiones.
curl -X POST -H "Content-Type: application/json" \
-d '{"username": "user", "password": "pass"}' \
http://localhost:7337/api/auth/tokenCrea o actualiza un lote de variables. Si secure es true, el valor se almacena en AWS Parameter Store.
PAYLOAD='[
{ "key": "global/example/email_password", "value": "super-secret-password", "secure": true },
{ "key": "global/example/email_user", "value": "test@gmail.com" }
]'
curl -X POST -v "http://localhost:7337/api/entry" \
-H "Content-Type: application/json" \
-d "${PAYLOAD}" \
--user "user:pass"Lista todas las variables bajo un prefijo (ej: stage/service)
curl -X GET "http://localhost:7337/api/entry/prefix?v=global/example" \
--user "user:pass" | jqObtiene el valor de una variable específica.
curl -X GET "http://localhost:7337/api/entry/key?v=global/example/email_user" \
--user "user:pass" | jqObtiene el valor de un secreto específico.
curl -X GET "http://localhost:7337/api/entry/secret-value?v=global/example/email_password" \
--user "user:pass" | jqCrea o actualiza una plantilla para un servicio en uno o más entornos. El valor de la plantilla debe estar codificado en Base64.
# task-definition.json (contenido de ejemplo)
# TEMPLATE_B64=$(cat task-definition.json | base64)
TEMPLATE_B64=$(cat <<EOF | base64
{
"requiresCompatibilities": [
"EC2"
],
"containerDefinitions": [
{
"name": "nginx",
"image": ":image-name",
"memory": 256,
"cpu": 256,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"protocol": "tcp"
}
],
"secrets": [
{
"name": "EMAIL_PASSWORD",
"valueFrom": "{{global/example/email_password}}"
}
],
"environment": [
{
"name": "ENVIRONMENT_NAME",
"value": ":stage"
},
{
"name": "EMAIL_USER",
"value": "{{ global/example/email_user }}"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/nginx_:stage",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "nginx"
}
},
"healthCheck": {
"command": [
"CMD-SHELL",
"wget --no-verbose --tries=1 -O /dev/null --quiet http://localhost || exit 1"
],
"interval": 30,
"timeout": 10,
"retries": 3,
"startPeriod": 10
}
}
],
"volumes": [],
"placementConstraints": [],
"family": "nginx"
}
EOF
)
PAYLOAD=$(<<EOF
{
"payload": {
"service": "example",
"stage": {
"development": {
"template": { "name": "task_definition.json", "value": "${TEMPLATE_B64}" }
}
}
}
}
EOF
)
curl -X POST "http://localhost:7337/api/box" \
-H "Content-Type: application/json" \
-d "${PAYLOAD}" \
--user "user:pass" | jqObtiene el contenido de una plantilla almacenada.
curl "http://localhost:7337/api/box/example/development/task_definition.json" \
--user "user:pass" | jqProcesa una plantilla, reemplazando las variables con sus valores correspondientes. Puedes pasar variables adicionales como query parameters.
curl "http://localhost:7337/api/box/example/development/task_definition.json/build?image-name=nginx:latest" \
--user "user:pass" | jqEl servicio se configura mediante variables de entorno:
| Variable | Descripción | Valor por Defecto |
|---|---|---|
NBOX_ALLOWED_PREFIXES |
Lista de prefijos de entorno permitidos, separados por comas. | development/,qa/,beta/,... |
NBOX_DEFAULT_PREFIX |
Prefijo por defecto si no se especifica uno (global/). |
global |
NBOX_BASIC_AUTH_CREDENTIALS |
JSON con las credenciales de usuario para la autenticación básica. | - |
NBOX_BOX_TABLE_NAME |
Nombre de la tabla DynamoDB para la metadata de las plantillas. | nbox-box-table |
NBOX_BUCKET_NAME |
Nombre del bucket S3 para almacenar las plantillas. | nbox-store |
NBOX_ENTRIES_TABLE_NAME |
Nombre de la tabla DynamoDB para las variables. | nbox-entry-table |
NBOX_TRACKING_ENTRIES_TABLE_NAME |
Nombre de la tabla DynamoDB para el historial de cambios. | nbox-tracking-entry-table |
NBOX_PARAMETER_STORE_KEY_ID |
ID de la clave KMS para cifrar los secretos en Parameter Store. | - |
NBOX_PARAMETER_STORE_SHORT_ARN |
true para almacenar el nombre del parámetro, false para el ARN completo. |
false |
HMAC_SECRET_KEY |
Clave secreta para firmar los tokens JWT. | Una clave predeterminada |
-
Pre-commit: Configurado para ejecutar linters y formateadores antes de cada commit.
./scripts/setup-precommit.sh
-
Makefile
make lint: Ejecuta todos los lintersmake format: Formatea el códigomake test: Ejecuta las pruebas unitariasmake tools: Instala las herramientas de desarrollo
make docs(Open API)[https://github.com/swaggo/swag?tab=readme-ov-file#the-swag-formatter]
// UpsertBox
// @Summary Upsert templates
// @Description insert or update templates on s3
// @Tags templates
// @Accept json
// @Produce json
// @Param data body models.Box true "Upsert template"
// @Success 200 {object} []string ""
// @Failure 400 {object} problem.ProblemDetail "Bad Request"
// @Failure 401 {object} problem.ProblemDetail "Unauthorized"
// @Failure 403 {object} problem.ProblemDetail "Forbidden"
// @Failure 404 {object} problem.ProblemDetail "Not Found"
// @Failure 500 {object} problem.ProblemDetail "Internal error"
// @Router /api/box [post]Descripción de las anotaciones
- @Summary y @Description • @Summary: Describe brevemente lo que hace el endpoint. • @Description: Proporciona una explicación más detallada.
- @Tags • Úsalo para categorizar endpoints, por ejemplo, “usuarios”, “productos”, etc.
- @Accept y @Produce • @Accept: Especifica el tipo de contenido esperado (en este caso, JSON). • @Produce: Especifica el tipo de contenido que el endpoint devolverá (en este caso, JSON).
- @Param • Define los parámetros de la solicitud. • body: Indica que el parámetro está en el cuerpo. • CreateRequest: Estructura esperada. • true: Especifica si es obligatorio.
- @Success y @Failure • @Success: Describe una respuesta exitosa. • @Failure: Describe posibles respuestas de error.
- @Router • Especifica la ruta y el método HTTP (en este caso, POST).
docker buildx build --platform=linux/amd64 --target production -t nbox:1 --progress=plain .{
"user": {
"password": "$2a$10$KHqB91a8nSKF8ppAGt4BHeszuAGK5GGvrrXPR94Pl8FKLEK1hkoYa",
"roles": [
"admin"
],
"status": "active"
}
}---
config:
layout: dagre
theme: base
---
flowchart TD
%% External Services
subgraph EXT["☁️ Servicios AWS"]
S3[("S3<br/>Templates")]
DDB[("DynamoDB<br/>Entries/Tracking")]
SSM[("SSM<br/>Secrets")]
end
%% Clients
subgraph CLI["🔧 Herramientas"]
HASHER["Hasher CLI<br/>Password Gen"]
CLIENT["HTTP Client<br/>API Consumer"]
end
%% Presentation Layer
subgraph PRES["🌐 Capa de Presentación"]
WEBUI["Web UI<br/>Events/Assets"]
AUTH["Auth Layer<br/>JWT/Basic/OPA"]
API["REST API<br/>Box/Entry/Static"]
SSE["SSE Events<br/>Real-time"]
end
%% Application Layer
subgraph APP["⚙️ Capa de Aplicación"]
BOXUC["BoxUseCase<br/>Template Builder"]
ENTRYUC["EntryUseCase<br/>Config Manager"]
PATHUC["PathUseCase<br/>Key Utils"]
EVENTUC["EventUseCase<br/>Notifications"]
end
%% Domain Layer
subgraph DOM["🏛️ Capa de Dominio"]
MODELS["Domain Models<br/>Entry | Box | User<br/>Template | Event"]
PORTS["Interfaces<br/>EntryAdapter<br/>TemplateAdapter<br/>SecretAdapter"]
end
%% Infrastructure Layer
subgraph INFRA["🔌 Adaptadores"]
S3ADAPTER["S3 Template Store<br/>JSON Templates"]
DDBADAPTER["DynamoDB Backend<br/>Entries/Tracking"]
SSMADAPTER["SSM SecureStore<br/>Encrypted Secrets"]
MEMORY["InMemory UserRepo<br/>Auth Credentials"]
SSEADAPTER["SSE Broker<br/>Event Publisher"]
end
%% Health & Monitoring
subgraph HEALTH["📊 Observabilidad"]
STATUS["Health Checks<br/>Ready/Live"]
LOGS["Structured Logs<br/>Zap Logger"]
end
%% Connections - External
CLIENT --> AUTH
WEBUI --> SSE
%% Connections - Flow
AUTH --> API
API --> BOXUC
API --> ENTRYUC
API --> EVENTUC
BOXUC --> PATHUC
ENTRYUC --> EVENTUC
%% Use Cases to Ports
BOXUC --> PORTS
ENTRYUC --> PORTS
EVENTUC --> PORTS
%% Ports to Models
PORTS --> MODELS
%% Adapters to Ports
S3ADAPTER -.-> PORTS
DDBADAPTER -.-> PORTS
SSMADAPTER -.-> PORTS
MEMORY -.-> PORTS
SSEADAPTER -.-> PORTS
%% Infrastructure to External
S3ADAPTER --> S3
DDBADAPTER --> DDB
SSMADAPTER --> SSM
%% Health Connections
STATUS --> S3ADAPTER
STATUS --> DDBADAPTER
STATUS --> SSMADAPTER
%% Styling
classDef external fill:#232F3E,stroke:#FF9900,stroke-width:3px,color:#fff
classDef cli fill:#2D3748,stroke:#4FD1C7,stroke-width:2px,color:#fff
classDef presentation fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
classDef application fill:#E8F5E8,stroke:#4CAF50,stroke-width:2px,color:#000
classDef domain fill:#FFF3E0,stroke:#FF9800,stroke-width:3px,color:#000
classDef infrastructure fill:#F3E5F5,stroke:#9C27B0,stroke-width:2px,color:#000
classDef health fill:#FFF5F5,stroke:#E53E3E,stroke-width:2px,color:#000
class S3,DDB,SSM external
class HASHER,CLIENT cli
class WEBUI,AUTH,API,SSE presentation
class BOXUC,ENTRYUC,PATHUC,EVENTUC application
class MODELS,PORTS domain
class S3ADAPTER,DDBADAPTER,SSMADAPTER,MEMORY,SSEADAPTER infrastructure
class STATUS,LOGS health
- anonymous: Acceso público (health checks)
- viewer: Solo lectura, ambientes no productivos
- viewer_prod: Solo lectura, incluye producción
- editor: Lectura y escritura
- secrets_reader: Puede leer valores plain de secrets (combinar con otros roles)
- maintainer: Puede eliminar entries
- cicd: Acceso de automatización
- admin: Acceso completo
https://htmx.org/extensions/sse
- Editar los roles desde una UI
- Evitar reiniciar el servicio para recargar los cambios en los roles a los users
- En la UI invalidar cache de los secretos despues de actualizar
- implementar kebab-case para la keys