Skip to content

Secrets / Управление секретами

Обзор

В проекте используется двухуровневая система хранения секретов:

  1. Глобальные секреты (Doppler) — общие для всех интеграций
  2. 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_KEYAPI ключ OpenAI
CLAUDE_API_KEYAPI ключ Anthropic (Claude)
GEMINI_API_KEYAPI ключ Google Gemini
GROQ_API_KEYAPI ключ Groq
DATABASE_URLURL Neon PostgreSQL (если используется)
ACCESS_TOKENТокен авторизации для Gateway

Конфиги Doppler

ConfigНазначениеИспользуется
localЛокальная разработка.dev.vars генерация
devDev окружениеCloudflare Workers Secrets
prodProductionCloudflare Workers Secrets

Команды Doppler

bash
# Установка
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-file

Per-integration секреты (D1)

Структура хранения

Секреты хранятся в таблице creds в D1:

sql
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

typescript
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 автоматически)

Команды:

bash
# Генерация .dev.vars из Doppler
pnpm generate:env -- local

# Запуск (автоматически генерирует env)
pnpm start sofa

# Запуск без генерации
SKIP_ENV=true pnpm start sofa

Файл .dev.vars:

env
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)

Команды:

bash
# Синхронизация секретов 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:

bash
# Использование (из директории интеграции)
cd integrations/sofa
tsx ../../scripts/sync-cf-secrets.ts dev
tsx ../../scripts/sync-cf-secrets.ts production

Что делает:

  1. Получает секреты из Doppler (doppler secrets --config <config> --json)
  2. Валидирует обязательные секреты (DATABASE_URL, OPENAI_API_KEY)
  3. Загружает каждый секрет в Cloudflare (wrangler secret put <key> --env <env>)

Обязательные секреты:

typescript
const REQUIRED_SECRETS = ["DATABASE_URL", "OPENAI_API_KEY"];

Добавление новых credentials

Глобальные (Doppler)

  1. Открыть https://dashboard.doppler.com
  2. Выбрать project integ-core
  3. Выбрать config (local / dev / prod)
  4. Добавить секрет
  5. Для локальной разработки: pnpm generate:env -- local
  6. Для dev/prod: pnpm sync-secrets:dev или pnpm sync-secrets:prod

Per-integration (D1)

Через код или CLI:

typescript
// В 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:

bash
# Подключиться к D1 (через CLI нужно вручную зашифровать)
wrangler d1 execute integ-db --command "SELECT * FROM creds"

D1 Setup

Создание баз данных

bash
# Создать отдельные базы для 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 для каждого окружения:

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:

bash
# Установить секрет
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 production

Troubleshooting

"D1Database is required"

Проверить wrangler.toml:

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:

bash
# Генерация надёжного ключа
openssl rand -base64 32

"Failed to decrypt credential"

  • Убедиться что CRYPTO_KEY и CRYPTO_SALT одинаковые в dev и prod
  • Если ключ изменился, нужно перешифровать все credentials

Doppler login issues

bash
# Переавторизация
doppler logout
doppler login

# Проверить текущий проект
doppler configure

Використання секретів у пакетах

Пакети @happ-integ/* автоматично підтримують Doppler секрети через env binding. Не потрібно вручну передавати API ключі — просто передайте env об'єкт:

Приклад використання

typescript
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/llmOPENAI_API_KEY, CLAUDE_API_KEY, GEMINI_API_KEY, GROQ_API_KEY
@happ-integ/openaiOPENAI_API_KEY
@happ-integ/vectorQDRANT_URL, QDRANT_API_KEY, PINECONE_API_KEY, WEAVIATE_URL, WEAVIATE_API_KEY
@happ-integ/qdrantQDRANT_URL, QDRANT_API_KEY
@happ-integ/neonDATABASE_URL
@happ-integ/happ-voiceVOICE_ASSISTANT_URL, VOICE_ASSISTANT_SECRET
@happ-integ/saas-apiSAAS_API_URL, SAAS_ACCESS_TOKEN

Пріоритет конфігурації

Пакети використовують такий пріоритет для отримання секретів:

1. Явно переданий параметр (apiKey, url тощо)
2. Значення з env binding (env.OPENAI_API_KEY тощо)

Примітка: process.env не підтримується — пакети працюють тільки в Cloudflare Workers де секрети доступні через env binding.


Security Best Practices

  1. Никогда не коммитить .env, .dev.vars (добавлены в .gitignore)
  2. CRYPTO_KEY и CRYPTO_SALT должны быть разными для dev и prod
  3. Ротация ключей — при смене CRYPTO_KEY/CRYPTO_SALT нужно перешифровать D1
  4. Минимальные права — каждая интеграция видит только свои credentials
  5. Аудит — D1 хранит created_at, updated_at для каждого секрета

Связанные документы