Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 075 — Secretos y configuración#

⬅️ 074 · 📚 Parte 5 · 🎓 Clases · 076 ➡️ Parte 5 — Identidad y seguridad · Nivel 🟡 intermedio · Pista plataformaClase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Sacar del código lo que no puede estar en el repositorio. Un secreto en el código es un secreto publicado: vive en el historial de git para siempre, lo ve todo el que clona, y rotarlo obliga a un commit. La configuración —secretos incluidos— entra por el entorno, no por el fuente [twelve-factor].

🧩 La situación#

La aplicación necesita dos cosas para arrancar: en qué entorno corre y un secreto. Las dos vienen de variables de entorno. Si falta alguna, la aplicación no arranca — y lo dice claro, nombrando lo que falta. Si están, arranca y reporta su configuración sin revelar nunca el secreto.

🧮 El contrato#

Petición Respuesta Qué mide
GET /configuracion entorno: "pruebas", secreto_presente: true la config viene del entorno
GET /configuracion no contiene el valor del secreto se reporta presencia, no valor
POST /validar con las dos claves valida: true el validador acepta lo completo
POST /validar sin APP_SECRETO 422, nombra APP_SECRETO falla claro y dice qué falta
POST /validar con {} 422, nombra las dos enumera todo, no solo lo primero

El validador de /validar es el mismo que corre en el arranque: la regla que decide si la aplicación puede levantar es la que responde el endpoint. Eso hace medible por HTTP algo que ocurre antes de la primera petición — que la aplicación no habría arrancado sin las variables es, de hecho, lo que prueba que las lee: el verificador les da valor por su campo env, y si no lo hiciera, ninguna de las cuatro escucharía.

El último caso mide un detalle que cuesta despliegues reales: enumerar todo lo que falta de una vez. Un validador que se rinde en la primera variable ausente convierte «faltan tres» en tres arranques fallidos seguidos.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Configuración por entorno Que lo que cambia entre desarrollo y producción venga de fuera del artefacto —variables de entorno— y no de un if. Y que falte una variable obligatoria haga fallar el arranque, no la primera petición.
Secreto Un valor que no puede aparecer en el código, ni en el registro, ni en una respuesta: claves de firma, contraseñas de base de datos, credenciales. Un endpoint de configuración reporta su presencia, nunca su valor.

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Express framework web de Node.js (JavaScript) 2010 MIT OpenJS Foundation
FastAPI framework web de Python (Python) 2018 MIT proyecto independiente
Spring Boot framework de aplicación de JVM (Java) 2014 Apache-2.0 Broadcom/VMware y colaboradores
ASP.NET Core framework web de .NET (C#) 2016 MIT Microsoft y .NET Foundation

🔧 Express#

Definió el modelo de middleware encadenado que copiaron casi todos los frameworks de Node.js. Minimalista no significa biblioteca: posee el bucle de peticiones.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 FastAPI#

Deriva validación, serialización y documentación OpenAPI de las anotaciones de tipo. Demostró que el tipado opcional de Python podía ser infraestructura, no adorno.

Arrancarla suelta, sin el verificador:

PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
main.py código Python
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Spring Boot#

Autoconfiguración y servidor incrustado sobre Spring. Convirtió un framework famoso por su configuración XML en uno de arranque inmediato.

Preparar sus dependencias, dentro de su directorio:

mvn -q -B package -DskipTests

Arrancarla suelta, sin el verificador:

PORT=3000 java -jar target/clase-075-1.0.0.jar --server.port=3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
pom.xml manifiesto de Maven: el proyecto, su Java, sus dependencias y cómo se empaqueta
src/main/java/labs/Aplicacion.java código Java
src/main/resources/application.properties configuración de Spring Boot: lo que se ajusta sin tocar el código

🔧 ASP.NET Core#

Reescritura multiplataforma y de código abierto de la pila web de Microsoft. Sus API mínimas trajeron el estilo de los microframeworks al ecosistema .NET.

Preparar sus dependencias, dentro de su directorio:

dotnet build -c Release --nologo -v quiet

Arrancarla suelta, sin el verificador:

PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000

Qué hay dentro de su directorio:

Archivo Qué es
Clase075.csproj proyecto de .NET: el marco de destino y las dependencias
Program.cs código C#
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

Aquí el reparto se invierte respecto a las clases de autorización: el framework con más ceremonia trae más ayuda, y el minimalista te deja la regla entera. Y las cuatro comparten un gesto que es el contenido de la clase — el mismo validador lo usan el arranque y el endpoint.

Express · express/server.mjs — la regla, entera y tuya#

const REQUERIDAS = ["APP_ENTORNO", "APP_SECRETO"];
function validar(fuente) {
  const faltan = REQUERIDAS.filter((clave) => !fuente[clave]);
  return { valida: faltan.length === 0, faltan };
}
const arranque = validar(process.env);
if (!arranque.valida) {
  console.error(`Configuración incompleta, faltan: ${arranque.faltan.join(", ")}`);
  process.exit(1);
}

Nada de esto viene incluido: process.env y seis líneas. Y las seis contienen las dos decisiones que importan.

Ninguna clave tiene valor por omisión. Un secreto con valor por defecto es un secreto que alguien olvidó poner y que corre en producción con la clave del ejemplo.

El validador devuelve todas las que faltan, no la primera. Quien despliega sin tres variables no quiere descubrirlas de una en una, en tres despliegues fallidos seguidos.

Y process.exit(1): el proceso no llega a escuchar. Fallar al arrancar es la única forma de no fallar en la primera petición del primer usuario.

FastAPI · fastapi/main.py — el mejor equipado#

_faltan = validar(dict(os.environ))
if _faltan:
    raise RuntimeError(f"Configuración incompleta, faltan: {', '.join(_faltan)}")

La implementación usa un validador explícito para medir el mismo mensaje en los cuatro, pero conviene saber que la vía idiomática de FastAPI es más corta: pydantic-settings (BaseSettings) lee el entorno, convierte tipos y falla al construirse si falta una obligatoria. La validación es la declaración de la clase — la misma idea que en la clase 013, aplicada a la configuración.

Spring Boot · spring-boot/…/application.properties#

app.entorno=${APP_ENTORNO:}
app.secreto=${APP_SECRETO:}

Y en Aplicacion.java:

    @Value("${app.entorno:}")
    private String entorno;
    @jakarta.annotation.PostConstruct
    void comprobarArranque() {
        if (!faltan.isEmpty()) {
            throw new IllegalStateException("Configuracion incompleta, faltan: " + String.join(", ", faltan));
        }

Dos saltos: el entorno entra en application.properties como app.entorno y @Value lo inyecta en el campo. El : final del marcador declara valor por omisión vacío — deliberadamente vacío, para que sea el validador quien decida, y no un valor por defecto silencioso.

@PostConstruct es la pieza del ciclo de vida (clase 037): corre cuando el objeto ya está construido e inyectado, y lanzar ahí aborta el contexto entero. La aplicación no termina de levantar.

ASP.NET Core · aspnet-core/Program.cs — una fuente ordenada#

List<string> Validar(Func<string, string?> fuente) =>
    requeridas.Where(clave => string.IsNullOrEmpty(fuente(clave))).ToList();
var faltanArranque = Validar(clave => constructor.Configuration[clave]);
if (faltanArranque.Count > 0)
{
    throw new InvalidOperationException(
        $"Configuración incompleta, faltan: {string.Join(", ", faltanArranque)}");
}

IConfiguration unifica variables de entorno, appsettings.json y argumentos de línea de comandos en una sola fuente con orden de precedencia declarado. Es la aportación real de este framework a la clase: el código no necesita saber de dónde vino cada valor, y cambiar el origen no cambia el código.

Fíjate en que Validar recibe una función y no un diccionario: es lo que permite que el mismo validador consulte IConfiguration al arrancar y un diccionario del cuerpo en el endpoint.

Lo que ninguna de las cuatro deja salir#

  respuesta.json({
    entorno: config.entorno,
    secreto_presente: Boolean(config.secreto),
    secreto: "****",
  });

Las cuatro reportan la presencia del secreto, no su valor. Un endpoint de configuración que devuelve el secreto es exactamente el agujero que esta clase viene a cerrar, y aparece más veces de lo que parece: empieza como una ayuda de diagnóstico en desarrollo y nadie la quita.

📊 Comparación#

Framework De dónde lee La validación El secreto en el reporte
FastAPI BaseSettings al construir el objeto enmascarado
Spring Boot application.properties ← entorno @PostConstruct enmascarado
ASP.NET Core IConfiguration (fuentes en capas) antes de Build() enmascarado
Express process.env validador propio enmascarado

La diferencia no es de seguridad —los cuatro fallan al arrancar y ninguno filtra el secreto— sino de cuánto código pones tú. Y el orden de fuentes de ASP.NET (appsettings < entorno < argumentos) es la respuesta a un problema real que las otras resuelven a mano: qué gana cuando la misma clave está en dos sitios.

🔑 Qué es un secreto y qué no#

El .env es una herramienta de desarrollo local —cómodo, y por eso peligroso— que va siempre en .gitignore. En producción, el secreto lo inyecta la plataforma: variables del orquestador, un gestor de secretos (Vault, Secrets Manager), nunca un fichero en la imagen [owasp-cheatsheets].

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 075

Los casos están en contrato.json. El verificador ejecuta las implementaciones que encuentre y declara las que omitió, y les inyecta APP_ENTORNO y APP_SECRETO por el campo env de cada receta.

🧪 Reto de transferencia#

Añade una clave tipada: APP_MAX_CONEXIONES, un entero. Haz que el arranque falle si no es un número ("muchas" → error claro con la clave y el tipo esperado) y añade el caso al contrato. Observa cuál de los cuatro frameworks te da la conversión y la validación de tipo gratis — es la diferencia entre leer process.env y declarar un BaseSettings.

🔗 Enlaces#

Fuentes#