Gestione sicura dei segreti in Next.js e Node.js con HashiCorp Vault

Passo dopo passo mostreremo come costruire un solido processo di gestione dei segreti nelle applicazioni Next.js e Node.js utilizzando HashiCorp Vault.

Bezpieczne zarządzanie sekretami w Next.js i Node.js z HashiCorp Vault

Le moderne applicazioni web basate su Next.js e Node.js elaborano sempre più dati sensibili – chiavi API, token JWT, credenziali di database. Una gestione inadeguata di questi segreti porta a perdite che possono costare all'organizzazione milioni di euro. In questo articolo presentiamo un modello di minaccia completo, descriviamo il funzionamento di HashiCorp Vault e mostriamo frammenti di codice pratici – dalla versione vulnerabile a quella corretta – oltre a una checklist di mitigazione basata su OWASP, CWE e sul principio del least‑privilege.

Modello di minaccia – dove si presenta il rischio?

Il tipico flusso di un segreto in un progetto Next.js/Node.js è il seguente: lo sviluppatore salva il valore in un file .env, il repository CI/CD preleva il file e lo inietta nel container Docker, e l'applicazione legge la variabile d'ambiente al momento dell'esecuzione. In questo modello esistono tre punti critici di attacco:

  • memorizzazione del segreto nel repository (anche in un repository privato può essere clonato o trapelare);
  • trasmissione del segreto come stringa non crittografata nelle variabili d'ambiente del container;
  • assenza di limitazioni di accesso dell'applicazione al segreto specifico (mancanza del principio least‑privilege).

Tutti questi scenari sono descritti nell'OWASP Top 10 come Security Misconfiguration (A05) e nel CWE‑522 – Insufficiently Protected Credentials. Il modo migliore per ridurre il rischio è la gestione centralizzata dei segreti, che garantisce audit, rotazione e ambito minimo di autorizzazioni.

HashiCorp Vault – come funziona?

Vault è un sistema di gestione dei segreti che conserva i valori crittografati in un backend sicuro (ad esempio uno storage basato su consenso). L'accesso ai segreti avviene dopo l'autenticazione tramite metodi come AppRole, Kubernetes Auth o certificati TLS. Dopo un'autenticazione riuscita, Vault rilascia un token a breve durata (TTL da pochi secondi a diverse ore) e consente la lettura o la generazione di credenziali dinamiche (ad esempio utenti temporanei di database).

Caratteristiche chiave che soddisfano i requisiti OWASP e CWE:

  • encryption‑at‑rest e encryption‑in‑transit (TLS);
  • controllo rigoroso delle policy (policy‑as‑code) che definisce quali percorsi e operazioni sono consentiti;
  • rotazione automatica e revoca dei token.

Integrazione di HashiCorp Vault con Node.js – esempio di codice

Di seguito è riportato un frammento di codice semplice ma vulnerabile, in cui i segreti vengono letti dal file .env e usati direttamente nelle chiamate API:

require('dotenv').config();
const axios = require('axios');

const apiKey = process.env.API_KEY; // vulnerabile: chiave in .env

async function callExternal() {
  const resp = await axios.get('https://api.example.com/data', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });
  return resp.data;
}

Il problema è che API_KEY può finire nei log CI, nell'immagine Docker o nella memoria del container, senza alcun controllo su quali processi possano leggerla.

La versione corretta utilizza il Vault SDK, si autentica tramite AppRole e recupera il segreto in tempo reale. In questo modo il token ha un TTL breve e la chiave non rimane in memoria più a lungo del necessario:

const vault = require('node-vault')({
  endpoint: process.env.VAULT_ADDR,
  token: process.env.VAULT_APPROLE_TOKEN // token breve, non permanente
});

async function getApiKey() {
  const result = await vault.read('secret/data/app/api-key');
  return result.data.data.key;
}

async function callExternal() {
  const apiKey = await getApiKey();
  const resp = await axios.get('https://api.example.com/data', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });
  return resp.data;
}

È importante notare che il Vault SDK gestisce automaticamente il refresh del token e la policy app/api-key può limitare l'accesso esclusivamente alla lettura di questa chiave specifica.

Docker e Kubernetes – archiviazione sicura dei token JWT

Negli ambienti containerizzati si usano spesso variabili d'ambiente per passare i token. In alternativa è possibile utilizzare:

  • Vault Agent Injector – inietta automaticamente il segreto come file in /vault/secrets nel container;
  • Container sidecar con Vault Agent, che rinnova il token in background.

Esempio di manifesto Kubernetes con sidecar:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextjs-app
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: app
          image: myregistry/nextjs:latest
          env:
            - name: VAULT_ADDR
              value: "https://vault.mycompany.com"
          volumeMounts:
            - name: vault-secrets
              mountPath: /vault/secrets
        - name: vault-agent
          image: hashicorp/vault-agent:latest
          args:
            - "agent"
            - "-config=/etc/vault/agent-config.hcl"
          volumeMounts:
            - name: vault-config
              mountPath: /etc/vault
            - name: vault-secrets
              mountPath: /vault/secrets
      volumes:
        - name: vault-config
          configMap:
            name: vault-agent-config
        - name: vault-secrets
          emptyDir: {}

Il sidecar salva il token aggiornato in /vault/secrets/jwt, e l'applicazione lo legge all'avvio. In questo modo il token non appare nelle variabili d'ambiente ed è automaticamente revocato al momento della scadenza.

Checklist di mitigazione – da OWASP al principio del minimo privilegio

  • Usa un archivio centrale (Vault) invece di file .env nel repository.
  • Applica TTL brevi per i token e rotazione automatica.
  • Definisci le policy di Vault in stile path "secret/data/app/*" { capabilities = ["read"] } – limita l'accesso a uno specifico namespace.
  • In Docker e Kubernetes inietta i segreti come file o volume, non come env.
  • Esegui audit degli accessi – abilita il device di audit in Vault e integra i log con il SIEM.
  • Verifica i privilegi dell'applicazione (least‑privilege) – l'app dovrebbe avere accesso solo ai segreti necessari.

Errori comuni e compromessi

Even experienced teams commettono alcune trappole:

  • Hard‑coding token – inserire token statici nel codice sorgente sotto la scusa di un prototipo rapido; soluzione: usa sempre AppRole o Kubernetes Auth.
  • Impostare policy troppo ampie – ad es. path "secret/*" { capabilities = ["read"] } concede accesso a tutti i segreti; soluzione: suddividi i segreti per domini e assegna i privilegi minimi.
  • Mancanza di rotazione – mantenere chiavi API immutate per anni; soluzione: configura max_ttl e rotation_period in Vault.
  • Log dei segreti – stampare involontariamente valori nei log dell'applicazione; soluzione: usa un logger che mascheri i campi con nomi *secret*, *key*, *token*.
La sicurezza non è una configurazione una tantum, è un processo continuo di controllo, audit e riduzione dei privilegi.

Esempio pratico – workflow completo

1. Configurazione di Vault: crea il namespace app, abilita il device di audit, definisci la policy app-read con accesso a secret/data/app/*.

2. Creazione di AppRole: role_id e secret_id sono conservati in un Kubernetes Secret (non nel repository).

3. Deploy dell'applicazione: manifest con sidecar Vault Agent, come nell'esempio sopra.

4. In Next.js (pages/api/data.ts):

import { createClient } from '@next-auth/vault';

export default async function handler(req, res) {
  const client = createClient({
    endpoint: process.env.VAULT_ADDR,
    roleId: process.env.VAULT_ROLE_ID,
    secretId: process.env.VAULT_SECRET_ID
  });
  const { data } = await client.read('secret/data/app/jwt');
  const jwt = data.data.token;
  // utilizzo del token per l'autorizzazione al servizio esterno
  res.status(200).json({ status: 'ok' });
}

5. Test di sicurezza: esegui una scansione OWASP ZAP, verifica che non vi siano perdite di variabili d'ambiente nei log del container.

Dopo aver implementato il workflow sopra, il team di ingegneria ottiene:

  • un punto centrale per gestire tutti i segreti;
  • la possibilità di ruotare immediatamente in caso di incidente;
  • un controllo di audit completo conforme a OWASP e CWE.

Se desideri che i tuoi progetti Next.js e Node.js siano resistenti alle perdite di dati, contatta Coderia.it – ti aiuteremo a implementare Vault, a definire le policy e a condurre un audit di sicurezza.

Cominciamo

Hai un progetto in mente?

Descrivilo in poche righe: rispondo entro 24 ore con un preventivo gratuito e una proposta di stack.