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

Krok po kroku pokażemy, jak zbudować solidny proces zarządzania sekretami w aplikacjach Next.js i Node.js przy użyciu HashiCorp Vault.

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

Współczesne aplikacje webowe oparte na Next.js i Node.js przetwarzają coraz więcej wrażliwych danych – klucze API, tokeny JWT, poświadczenia baz danych. Nieodpowiednie przechowywanie tych sekretów prowadzi do wycieków, które mogą kosztować organizację miliony złotych. W artykule przedstawiamy kompletny model zagrożenia, opisujemy mechanizm działania HashiCorp Vault oraz pokazujemy praktyczne fragmenty kodu – od podatnego do poprawionego – a także checklistę mitygacji opartą na OWASP, CWE i zasadzie least‑privilege.

Model zagrożenia – gdzie pojawia się ryzyko?

Typowy przepływ sekretu w projekcie Next.js/Node.js wygląda następująco: deweloper zapisuje wartość w pliku .env, repozytorium CI/CD pobiera plik i wstrzykuje go do kontenera Docker, a aplikacja odczytuje zmienną środowiskową w czasie uruchomienia. W tym modelu istnieją trzy krytyczne punkty ataku:

  • przechowywanie sekretu w repozytorium (nawet w prywatnym repozytorium może zostać sklonowane lub wycieknięte);
  • przekazywanie sekretu jako niezaszyfrowany ciąg w zmiennych środowiskowych w kontenerze;
  • brak ograniczeń dostępu aplikacji do konkretnego sekretu (brak zasady least‑privilege).

Wszystkie te scenariusze są opisane w OWASP Top 10 jako Security Misconfiguration (A05) oraz w CWE‑522 – Insufficiently Protected Credentials. Najlepszym sposobem ograniczenia ryzyka jest centralne zarządzanie sekretami, które zapewnia audyt, rotację i minimalny zakres uprawnień.

HashiCorp Vault – jak działa?

Vault to system zarządzania tajemnicami, który przechowuje zaszyfrowane wartości w bezpiecznym backendzie (np. konsensusowym storage). Dostęp do sekretów uzyskuje się po uwierzytelnieniu przy pomocy metod takich jak AppRole, Kubernetes Auth lub TLS certyfikaty. Po pomyślnym uwierzytelnieniu Vault wydaje krótkotrwały token (TTL od kilku sekund do kilku godzin) i pozwala na odczyt lub generowanie dynamicznych poświadczeń (np. tymczasowych użytkowników bazy).

Kluczowe cechy, które spełniają wymagania OWASP i CWE:

  • encryption‑at‑rest i encryption‑in‑transit (TLS);
  • ścisła kontrola polityk (policy‑as‑code) definiująca, które ścieżki i operacje są dozwolone;
  • automatyczna rotacja i unieważnianie tokenów.

Integracja HashiCorp Vault z Node.js – przykład kodu

Poniżej znajduje się prosty, ale podatny fragment kodu, w którym sekrety są wczytywane z pliku .env i używane bezpośrednio w zapytaniach do API:

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

const apiKey = process.env.API_KEY; // podatny: klucz w .env

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

Problem polega na tym, że API_KEY może trafić do logów CI, do obrazu Docker lub do pamięci kontenera, a nie ma kontroli, które procesy mogą go odczytać.

Poprawiona wersja wykorzystuje Vault SDK, uwierzytelnia się przy pomocy AppRole i pobiera sekret w czasie rzeczywistym. Dzięki temu token ma krótki TTL, a klucz nie jest przechowywany w pamięci dłużej niż to konieczne:

const vault = require('node-vault')({
  endpoint: process.env.VAULT_ADDR,
  token: process.env.VAULT_APPROLE_TOKEN // krótki token, nie stały
});

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;
}

Warto zauważyć, że Vault SDK automatycznie obsługuje odświeżanie tokenu, a polityka app/api-key może ograniczyć dostęp wyłącznie do odczytu tego konkretnego klucza.

Docker i Kubernetes – bezpieczne przechowywanie tokenów JWT

W środowiskach kontenerowych najczęściej używa się zmiennych środowiskowych do przekazywania tokenów. Zamiast tego można zastosować:

  • Vault Agent Injector – automatycznie wstrzykuje sekret jako plik w /vault/secrets w kontenerze;
  • Sidecar container z Vault Agent, który odświeża token w tle.

Przykład manifestu Kubernetes z 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: {}

Sidecar zapisuje odświeżany token w /vault/secrets/jwt, a aplikacja odczytuje go przy starcie. Dzięki temu token nie pojawia się w zmiennych środowiskowych i jest automatycznie unieważniany po wygaśnięciu.

Checklista mitygacji – od OWASP do least‑privilege

  • Używaj centralnego magazynu (Vault) zamiast plików .env w repozytorium.
  • Stosuj krótkie TTL tokenów i automatyczną rotację.
  • Definiuj polityki Vault w stylu path "secret/data/app/*" { capabilities = ["read"] } – ogranicz dostęp do konkretnego namespace.
  • W Dockerze i Kubernetes wstrzykuj sekrety jako pliki lub volume, nie jako env.
  • Audytuj dostęp – włącz audit device w Vault i integruj logi z SIEM.
  • Weryfikuj uprawnienia aplikacji (least‑privilege) – aplikacja powinna mieć dostęp tylko do niezbędnych sekretów.

Typowe błędy i kompromisy

Even experienced teams popełniają pewne pułapki:

  • Hard‑coding tokenów – umieszczanie stałych tokenów w kodzie źródłowym pod pretekstem szybkiego prototypu; rozwiązanie: zawsze używaj AppRole lub Kubernetes Auth.
  • Ustawianie zbyt szerokich polityk – np. path "secret/*" { capabilities = ["read"] } daje dostęp do wszystkich sekretów; rozwiązanie: podziel sekrety na domeny i przydziel minimalne uprawnienia.
  • Brak rotacji – pozostawienie niezmiennych kluczy API na lata; rozwiązanie: skonfiguruj max_ttl i rotation_period w Vault.
  • Logowanie sekretów – nieświadome wypisywanie wartości w logach aplikacji; rozwiązanie: używaj loggera, który maskuje pola o nazwach *secret*, *key*, *token*.
Bezpieczeństwo nie jest jednorazową konfiguracją, to ciągły proces kontroli, audytu i ograniczania przywilejów.

Praktyczny przykład – pełny workflow

1. Konfiguracja Vault: utwórz namespace app, włącz audit device, zdefiniuj politykę app-read z dostępem do secret/data/app/*.

2. Utworzenie AppRole: role_id i secret_id są przechowywane w Kubernetes Secret (nie w repozytorium).

3. Deploy aplikacji: manifest z sidecar Vault Agent, jak w przykładzie powyżej.

4. W aplikacji 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;
  // użycie tokenu do autoryzacji zewnętrznego serwisu
  res.status(200).json({ status: 'ok' });
}

5. Testy bezpieczeństwa: uruchom skan OWASP ZAP, sprawdź, czy nie ma wycieków zmiennych środowiskowych w logach kontenera.

Po wdrożeniu powyższego workflow, zespół inżynierski zyskuje:

  • centralne miejsce do zarządzania wszystkimi sekretami;
  • możliwość natychmiastowej rotacji w razie incydentu;
  • pełną kontrolę audytową zgodną z OWASP i CWE.

Jeśli chcesz, aby Twoje projekty Next.js i Node.js były odporne na wycieki danych, skontaktuj się z Coderia.it – pomożemy zaimplementować Vault, dopasować polityki i przeprowadzić audyt bezpieczeństwa.

Zacznijmy

Masz projekt na oku?

Opisz go w kilku zdaniach. Odpiszę w ciągu 24 godzin z bezpłatną wyceną i propozycją stacku.