Ir al contenido

FundamentosLección 11

Secretos y cadena de suministro

Dónde viven las contraseñas, claves y tokens de tu aplicación, qué hacer cuando se filtran, y cómo protegerte del código de terceros que compone el 90 % de tu software.

Intermedio15 min de lectura

Al terminar sabrás

  • Sacar los secretos del código y saber dónde guardarlos según el entorno.
  • Reaccionar correctamente ante un secreto filtrado.
  • Inventariar y vigilar dependencias con SBOM y escáneres.
  • Endurecer el pipeline de CI/CD frente a ataques de cadena de suministro.

Puedes escribir un código impecable y aun así ser vulnerable por dos vías que no están en tu código: un secreto (una contraseña de base de datos, una clave de firma, un token de API) que acaba donde no debe, y una dependencia comprometida o vulnerable. Las dos categorías están en el OWASP Top 10:2025 (A02 configuración y A03 cadena de suministro).

  • En el código o en application.yml versionado. Borrarlo en el siguiente commit no sirve: sigue en el historial de git, en los forks y en las copias locales.
  • En el bundle del frontend. Cualquier variable que llegue al navegador es pública, aunque se llame API_SECRET.
  • En imágenes de contenedor (ENV DB_PASSWORD=… en el Dockerfile): quedan en las capas de la imagen.
  • En logs, mensajes de error o URLs (?token=… acaba en logs de proxies e historiales).
EntornoOpciónComentario
Desarrollo localFichero .env fuera de git, perfiles localesValores de prueba, nunca los de producción
Contenedores / KubernetesSecretos montados como ficherosSpring Boot los lee con configtree
NubeGestor de secretos (AWS Secrets Manager, OCI Vault, Azure Key Vault, HashiCorp Vault)Auditoría, rotación, permisos finos
CI/CD y nubeIdentidad federada (OIDC) en lugar de claves de larga duraciónEl pipeline obtiene credenciales temporales; no hay nada que filtrar

En Spring Boot, el código solo conoce nombres, nunca valores:

application.yml
spring:
datasource:
url: jdbc:postgresql://db:5432/notes
username: ${DB_USER}
password: ${DB_PASSWORD} # de una variable de entorno
config:
import: optional:configtree:/run/secrets/ # ficheros montados por Kubernetes/Docker
  1. Rota primero: revoca el secreto y emite uno nuevo. Asume que ya se usó; los bots escanean GitHub en segundos.
  2. Revisa el uso: logs de acceso del proveedor, de la base de datos, de la nube.
  3. Limpia después: reescribir el historial de git es útil, pero no sustituye a la rotación.
  4. Previene: escaneo de secretos en el repositorio (GitHub push protection, gitleaks) y en CI.

Tu aplicación Spring tiene decenas de dependencias directas y cientos de transitivas. Cada una es código que se ejecuta con tus permisos.

  1. Tu código
  2. Dependenciasdirectas y transitivasCVEtyposquattingdependency confusion
  3. Build / CIacciones, plugins, runnersacciones sin fijartokens con demasiados permisos
  4. Artefactojar, imagenimagen basefirma y procedencia
Cada eslabón es un punto de ataque: una dependencia maliciosa, una acción de CI comprometida, una imagen base vulnerable.

Lo que funciona:

  • Inventario (SBOM): genera un Software Bill of Materials en cada build, por ejemplo con el plugin de CycloneDX para Maven o Gradle. Cuando salga el próximo Log4Shell sabrás en minutos si te afecta.
  • Escaneo continuo: Dependabot o Renovate para actualizar; OWASP Dependency-Check, Snyk o Grype para detectar CVE. Mejor aún: usa el BOM de Spring Boot y actualiza la versión de parche con regularidad.
  • Versiones fijadas: evita rangos ([1.0,), latest). Un build reproducible es un build auditable.
  • Desconfía de los nombres: paquetes con nombres casi idénticos a los populares (typosquatting) y paquetes públicos con el mismo nombre que tus paquetes internos (dependency confusion).
  • CI con mínimo privilegio: permisos mínimos del token del pipeline, acciones de terceros fijadas por SHA de commit (no por etiqueta), secretos solo en los jobs que los necesitan.
  • Imágenes mínimas: imágenes base pequeñas (distroless, Alpine, imágenes de Paketo/Buildpacks), escaneadas, ejecutando como usuario no root.

Evita

.github/workflows/build.yml
permissions: write-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: some-org/setup-tool@main
- run: ./mvnw deploy
env:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_KEY }}

Prefiere

.github/workflows/build.yml
permissions:
contents: read
id-token: write # para OIDC con la nube
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: some-org/setup-tool@3f1c2a9b8e7d6c5b4a39281706f5e4d3c2b1a098 # v4.2.0
- run: ./mvnw verify

Mini lab · 15 min

Auditoría de un repositorio

Objetivo: Encontrar los problemas de secretos y de cadena de suministro en tres ficheros de un proyecto.

application-prod.yml
spring:
datasource:
password: Pr0d-2026!
app:
jwt-secret: c3VwZXItc2VjcmV0LWtleQ==
Dockerfile
FROM eclipse-temurin:latest
ENV STRIPE_KEY=sk_live_51H...
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
pom.xml (extracto)
<dependency>
<groupId>com.example</groupId>
<artifactId>internal-utils</artifactId>
<version>[1.0,)</version>
</dependency>

Encuentra al menos cinco problemas y di cómo corregirías cada uno. Bonus: el secreto de producción lleva tres meses en el repositorio, ¿qué haces primero?

Pista

Revisa cada fichero con dos preguntas: ¿hay algún valor secreto escrito? ¿Hay algo que no esté fijado a una versión concreta?

Mostrar respuestaOcultar respuesta
  1. Contraseña de base de datos en el YAML versionado → variable de entorno o gestor de secretos.
  2. Secreto JWT en el YAML (y Base64 no es cifrado) → gestor de secretos; mejor aún, firmar con clave asimétrica guardada en un KMS.
  3. Clave de Stripe en el Dockerfile (ENV) → queda en las capas de la imagen; inyectarla en tiempo de ejecución.
  4. Imagen base latest → versión fija (y a ser posible por digest), una imagen mínima, escaneada, y ejecutar como usuario no root.
  5. Rango de versiones [1.0,) → build no reproducible y riesgo de dependency confusion si un paquete público usa ese nombre. Versión fija y repositorio interno configurado para esos groupId.

Bonus: rotar ya la contraseña de la base de datos, el secreto JWT (invalidará los tokens emitidos) y la clave de Stripe; después revisar los registros de uso de esos tres meses, y solo entonces limpiar el historial y activar el escaneo de secretos.