Secrets / Управление секретами
Обзор
В проекте используется двухуровневая система хранения секретов:
- Глобальные секреты (Doppler) — общие для всех интеграций
- Per-integration секреты (D1) — уникальные для каждой интеграции, зашифрованы
┌──────────────────────────────────────────────────────────────┐
│ DOPPLER │
│ │
│ Глобальные секреты (одинаковые для всех интеграций): │
│ • CRYPTO_KEY + CRYPTO_SALT (шифрование D1 secrets) │
│ • OPENAI_API_KEY, CLAUDE_API_KEY, GROQ_API_KEY │
│ • DATABASE_URL (Neon PostgreSQL) │
│ │
└────────────────────────────┬─────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ local │ │ dev │ │ prod │
│ │ │ │ │ │
│.dev.vars│ │CF Secret│ │CF Secret│
└─────────┘ └─────────┘ └─────────┘
┌──────────────────────────────────────────────────────────────┐
│ CLOUDFLARE D1 (integ-db) │
│ │
│ Per-integration секреты (зашифрованы CRYPTO_KEY + CRYPTO_SALT): │
│ • sofa/NETHUNT_EMAIL │
│ • sofa/NETHUNT_API_KEY │
│ • sofa/NETHUNT_FOLDER_ID │
│ • sofa/SAAS_ASSISTANT_ID │
│ • booking/API_KEY │
│ • ... │
│ │
└──────────────────────────────────────────────────────────────┘Глобальные секреты (Doppler)
Что хранится в Doppler
| Секрет | Назначение |
|---|---|
CRYPTO_KEY | Ключ AES-256-GCM для шифрования D1 credentials (min 32 chars) |
CRYPTO_SALT | Соль для PBKDF2 key derivation (min 16 chars) |
OPENAI_API_KEY | API ключ OpenAI |
CLAUDE_API_KEY | API ключ Anthropic (Claude) |
GEMINI_API_KEY | API ключ Google Gemini |
GROQ_API_KEY | API ключ Groq |
DATABASE_URL | URL Neon PostgreSQL (если используется) |
ACCESS_TOKEN | Токен авторизации для Gateway |
Конфиги Doppler
| Config | Назначение | Используется |
|---|---|---|
local | Локальная разработка | .dev.vars генерация |
dev | Dev окружение | Cloudflare Workers Secrets |
prod | Production | Cloudflare Workers Secrets |
Команды Doppler
# Установка
brew install dopplerhq/cli/doppler
# Логин
doppler login
# Привязка проекта (в корне репозитория)
doppler setup
# Выбрать: project = integ-core, config = local
# Просмотр секретов
doppler secrets --config local
doppler secrets --config dev
doppler secrets --config prod
# Скачать секреты в env формате
doppler secrets download --config local --format env-no-quotes --no-filePer-integration секреты (D1)
Структура хранения
Секреты хранятся в таблице creds в D1:
CREATE TABLE creds (
id TEXT PRIMARY KEY,
integration_name TEXT NOT NULL, -- 'sofa', 'booking', etc.
key TEXT NOT NULL, -- 'NETHUNT_API_KEY', etc.
encrypted_value TEXT NOT NULL, -- AES-256-GCM encrypted
created_at TEXT,
updated_at TEXT,
UNIQUE(integration_name, key)
);Пакет @happ-integ/creds
import { Creds } from "@happ-integ/creds";
const creds = new Creds({
d1: env.DB,
masterKey: env.CRYPTO_KEY,
salt: env.CRYPTO_SALT,
});
// Получить все credentials для интеграции
interface SofaCreds {
NETHUNT_EMAIL: string;
NETHUNT_API_KEY: string;
NETHUNT_FOLDER_ID: string;
SAAS_ASSISTANT_ID: string;
}
const secrets = await creds.get<SofaCreds>("sofa");
console.log(secrets.NETHUNT_API_KEY);
// Установить credential
await creds.set("sofa", "NEW_KEY", "secret-value");
// Получить один credential
const apiKey = await creds.getOne("sofa", "NETHUNT_API_KEY");
// Удалить credential
await creds.delete("sofa", "OLD_KEY");
// Список всех интеграций
const integrations = await creds.listIntegrations();
// => ['sofa', 'booking']
// Список ключей интеграции
const keys = await creds.listKeys("sofa");
// => ['NETHUNT_EMAIL', 'NETHUNT_API_KEY', 'NETHUNT_FOLDER_ID', 'SAAS_ASSISTANT_ID']Шифрование
Используется AES-256-GCM через Web Crypto API:
CRYPTO_KEY+CRYPTO_SALTиспользуются для derive key через PBKDF2- Каждое значение имеет уникальный IV (initialization vector)
- IV хранится вместе с зашифрованными данными
Workflow по окружениям
Local (Локальная разработка)
Doppler (config: local)
│
▼
generate-env.script.ts
│
├──> .env (корень проекта)
│
└──> integrations/sofa/.dev.vars
│
▼
wrangler dev
(читает .dev.vars автоматически)Команды:
# Генерация .dev.vars из Doppler
pnpm generate:env -- local
# Запуск (автоматически генерирует env)
pnpm start sofa
# Запуск без генерации
SKIP_ENV=true pnpm start sofaФайл .dev.vars:
ENVIRONMENT=local
CRYPTO_KEY=your-32-char-master-key-here!!!
CRYPTO_SALT=your-16-char-salt-here
OPENAI_API_KEY=sk-...
CLAUDE_API_KEY=sk-ant-...
GEMINI_API_KEY=...
GROQ_API_KEY=gsk_...Dev / Production (Cloudflare)
Doppler (config: dev/prod)
│
▼
sync-cf-secrets.ts
│
▼
Cloudflare Workers Secrets
│
▼
wrangler deploy
(секреты доступны в env)Команды:
# Синхронизация секретов Doppler → Cloudflare (dev)
cd integrations/sofa
pnpm sync-secrets:dev
# Синхронизация секретов Doppler → Cloudflare (prod)
pnpm sync-secrets:prod
# Полный деплой (build + sync + deploy)
pnpm deploy:dev # dev
pnpm deploy # productionСкрипт sync-cf-secrets.ts
Синхронизирует секреты из Doppler в Cloudflare Workers Secrets:
# Использование (из директории интеграции)
cd integrations/sofa
tsx ../../scripts/sync-cf-secrets.ts dev
tsx ../../scripts/sync-cf-secrets.ts productionЧто делает:
- Получает секреты из Doppler (
doppler secrets --config <config> --json) - Валидирует обязательные секреты (
DATABASE_URL,OPENAI_API_KEY) - Загружает каждый секрет в Cloudflare (
wrangler secret put <key> --env <env>)
Обязательные секреты:
const REQUIRED_SECRETS = ["DATABASE_URL", "OPENAI_API_KEY"];Добавление новых credentials
Глобальные (Doppler)
- Открыть https://dashboard.doppler.com
- Выбрать project
integ-core - Выбрать config (
local/dev/prod) - Добавить секрет
- Для локальной разработки:
pnpm generate:env -- local - Для dev/prod:
pnpm sync-secrets:devилиpnpm sync-secrets:prod
Per-integration (D1)
Через код или CLI:
// В handler или admin endpoint
const creds = new Creds({ d1: env.DB, masterKey: env.CRYPTO_KEY, salt: env.CRYPTO_SALT });
// Добавить credential
await creds.set("sofa", "NEW_API_KEY", "secret-value");
// Проверить
const keys = await creds.listKeys("sofa");
console.log(keys);Или через wrangler D1:
# Подключиться к D1 (через CLI нужно вручную зашифровать)
wrangler d1 execute integ-db --command "SELECT * FROM creds"D1 Setup
Создание баз данных
# Создать отдельные базы для dev и prod
wrangler d1 create integ-db-dev
wrangler d1 create integ-db-prodПрименение миграций
Миграции (включая создание таблицы creds) применяются автоматически при вызове POST /{integration}/setup. SQL-файлы и ручные команды wrangler d1 execute / wrangler d1 migrations apply не используются.
Все миграции (глобальные и integration-specific) являются code-based и управляются через
@happ-integ/core.
Обновление wrangler.toml
После создания баз, обновить ID в wrangler.toml для каждого окружения:
# Для локальной разработки
[[d1_databases]]
binding = "INTEG_DB"
database_name = "integ-db"
database_id = "00000000-0000-0000-0000-000000000001"
# DEV окружение
[[env.dev.d1_databases]]
binding = "INTEG_DB"
database_name = "integ-db-dev"
database_id = "YOUR_DEV_DATABASE_ID" # ← из вывода wrangler d1 create integ-db-dev
# PRODUCTION окружение
[[env.production.d1_databases]]
binding = "INTEG_DB"
database_name = "integ-db-prod"
database_id = "YOUR_PROD_DATABASE_ID" # ← из вывода wrangler d1 create integ-db-prodРучная установка секретов в Cloudflare
Если нужно установить секреты без Doppler:
# Установить секрет
wrangler secret put CRYPTO_KEY --env production
wrangler secret put CRYPTO_SALT --env production
# Ввести значение интерактивно
# Список секретов
wrangler secret list --env production
# Удалить секрет
wrangler secret delete OLD_SECRET --env productionTroubleshooting
"D1Database is required"
Проверить wrangler.toml:
[[d1_databases]]
binding = "INTEG_DB" # ← должно совпадать с env.INTEG_DB в коде
database_name = "integ-db"
database_id = "valid-uuid""masterKey must be at least 32 characters"
CRYPTO_KEY должен быть минимум 32 символа, CRYPTO_SALT — минимум 16:
# Генерация надёжного ключа
openssl rand -base64 32"Failed to decrypt credential"
- Убедиться что
CRYPTO_KEYиCRYPTO_SALTодинаковые в dev и prod - Если ключ изменился, нужно перешифровать все credentials
Doppler login issues
# Переавторизация
doppler logout
doppler login
# Проверить текущий проект
doppler configureВикористання секретів у пакетах
Пакети @happ-integ/* автоматично підтримують Doppler секрети через env binding. Не потрібно вручну передавати API ключі — просто передайте env об'єкт:
Приклад використання
import { LLMService } from "@happ-integ/llm";
import { VectorService } from "@happ-integ/vector";
import { QdrantClient } from "@happ-integ/qdrant";
// LLM — автоматично бере OPENAI_API_KEY, CLAUDE_API_KEY, GOOGLE_API_KEY, GROQ_API_KEY з env
const llm = new LLMService({
primary: "groq",
env, // ← передаємо env binding
});
// Vector — автоматично бере QDRANT_URL, QDRANT_API_KEY тощо
const vector = new VectorService({
primary: "qdrant",
env, // ← передаємо env binding
});
// Qdrant — автоматично бере QDRANT_URL, QDRANT_API_KEY
const qdrant = new QdrantClient({ env });Підтримувані пакети та їх секрети
| Пакет | Секрети з env |
|---|---|
@happ-integ/llm | OPENAI_API_KEY, CLAUDE_API_KEY, GEMINI_API_KEY, GROQ_API_KEY |
@happ-integ/openai | OPENAI_API_KEY |
@happ-integ/vector | QDRANT_URL, QDRANT_API_KEY, PINECONE_API_KEY, WEAVIATE_URL, WEAVIATE_API_KEY |
@happ-integ/qdrant | QDRANT_URL, QDRANT_API_KEY |
@happ-integ/neon | DATABASE_URL |
@happ-integ/happ-voice | VOICE_ASSISTANT_URL, VOICE_ASSISTANT_SECRET |
@happ-integ/saas-api | SAAS_API_URL, SAAS_ACCESS_TOKEN |
Пріоритет конфігурації
Пакети використовують такий пріоритет для отримання секретів:
1. Явно переданий параметр (apiKey, url тощо)
2. Значення з env binding (env.OPENAI_API_KEY тощо)Примітка: process.env не підтримується — пакети працюють тільки в Cloudflare Workers де секрети доступні через env binding.
Security Best Practices
- Никогда не коммитить
.env,.dev.vars(добавлены в.gitignore) - CRYPTO_KEY и CRYPTO_SALT должны быть разными для dev и prod
- Ротация ключей — при смене CRYPTO_KEY/CRYPTO_SALT нужно перешифровать D1
- Минимальные права — каждая интеграция видит только свои credentials
- Аудит — D1 хранит
created_at,updated_atдля каждого секрета
Связанные документы
- ENVIRONMENTS.md — описание окружений
- DEVELOPMENT.md — локальная разработка
- PRODUCTION.md — production workflow