В Layer3, мы запускаем всю нашу платформу в прекрасно организованном full-stack окружении на Vercel. Весь серверный и клиентский код написан на TypeScript и использует многие модули и типы.
Все шло хорошо, пока однажды…

У Vercel есть ограничение на переменную окружения в 4 КБ. Это вызвано базовой инфраструктурой AWS Lambda, но, хотя у AWS есть некоторые решения для правильного управления секретами, Vercel в основном говорит, что вам нужно создать собственное управление секретами.
В это время, у платформы Vercel, есть так много плюсов, и мы сохраняем так много времени, поскольку нам не нужно настраивать сложную облачную инфраструктуру на AWS.
Поэтому мы решили исправить это.
Наше решение сфокусировано на двух вещах:
Безопасное управление и развертывание секретов при просмотре и производстве
Сохраняйте отличный опыт разработчика перенося ключи разработки в локальные окружения
Посмотрите пример репозитория здесь: larskarbo/next-env-encrypt-decrypt
Doppler - это сервис, который специализируется на управлении переменными окружения. Это звучало идеально для нашего варианта использования, у них даже есть интеграция с Vercel!

Однако мы быстро осознали, что хотя у Doppler есть интеграция с Vercel, это вовсе не решает проблему 4 КБ. На самом деле, он просто - вроде как способствует этому... Добавляя больше переменных DOPPLER_.
Однако, интерфейс Doppler и API удивительные, и мы подумали, что могли бы создать работающее решение с некоторыми взломами.
Как только вы добавите все свои переменные окружения в Doppler вместо Vercel, вы сможете довольно легко обойти ограничение в 4 КБ, извлекая секреты из Doppler вместо Vercel.

Единственная переменная окружения, которая вам нужна в Vercel, - это токен Doppler. Самый простой способ добавить токены для developmentи preview - production это установить Vercel CLI и Doppler CLI и сгенерировать три разных ключа из терминала:
echo -n "$(doppler configs tokens create vercel-gitops --config dev --plain)" | vercel env add DOPPLER_TOKEN development
echo -n "$(doppler configs tokens create vercel-gitops --config stg --plain)" | vercel env add DOPPLER_TOKEN preview
echo -n "$(doppler configs tokens create vercel-gitops --config prd --plain)" | vercel env add DOPPLER_TOKEN production
Затем мы создадим скрипт fetchSecrets.ts, который извлекает эти переменные во время создания и записывает их в .env.
import fs from "fs/promises";
import secrets from "@larskarbo/gitops-secrets";
async function main() {
const payload = await secrets.providers.doppler.fetch();
let envFile = "";
Object.entries({
...payload,
}).forEach(([key, value]) => {
envFile += `${key}=${value}\n`;
});
envFile += `DOPPLER_TOKEN=${process.env.DOPPLER_TOKEN}\n`;
await fs.writeFile(".env", envFile);
}
void main();
Изменения в package.json:
"scripts": {
...
"build": "npm run fetch-secrets && nextjs build",
"fetch-secrets": "ts-node fetchSecrets.ts"
}
Да, это все, что вам нужно.
В разработке, вы просто запустите npm run fetch-env. Этот процесс не добавляет много движущихся частей и ощущается очень схоже на рабочий процесс vercel env pull.
Теперь, когда мы создаем собственное управление секретами, почему бы не сделать шаг вперед и улучшить безопасность?
Текущая настройка переменной окружения может представлять угрозу безопасности. Мошеннический npm пакет мог бы сбросить все свободно доступные переменные process.env и отправить их на отдаленный сервер. И помните, это также могло бы быть зависимостью одной из ваших зависимостей. У большинства npm приложений есть куча зависимостей, когда вы смотрите на дерево зависимостей, поэтому область поверхностного риска может быть больше, чем вы думаете.
Нашей целью будет создание системы, где:
Секреты всегда защифрованы, как при передаче, так и при хранении.
Секретам сложно непреднамеренно утечь, когда они потребляются конечным приложением.
У многих платформ для этого есть сложные решения такие, как AWS KMS и Docker Secrets. Идея в том, что эти инструменты хранят секрет в зашифрованной форме и предоставляют его приложению во время выполнения.
Мы решим это простым и индивидуальным способом с некоторыми уникальными соображениями:
Нам нужно, чтобы переменные
NEXT_PUBLIC_были доступны в окружении.Мы хотим быть в состоянии переопределять секреты с
.env.localдля наших локальных окружений разработки.

Основываясь на настройке Doppler, мы добавим в Vercel другую переменную окружения, SECRETS_KEY.
gen_key () { openssl rand -base64 32 }
gen_key | vercel env add SECRETS_KEY development
gen_key | vercel env add SECRETS_KEY preview
gen_key | vercel env add SECRETS_KEY production
Теперь мы сделаем некоторые изменения в наш скрипт fetch-secrets.ts.
Это нужно, чтобы:
Извлечь секреты из Doppler
Записать все переменные
NEXT_PUBLIC_в.envЗаписать все остальные секреты в специальный файл
.encrypted-secrets
Зафиксируйте этот файл в git вот так, а затем добавьте его в .gitignore. Это позволяет нам запускать приложение независимо от сгенерированного файла.
Наши супер-заряженные fetch-secrets.ts выглядят так:
import Cryptr from "cryptr";
import fs from "fs/promises";
import gitopsSecrets from "@larskarbo/gitops-secrets";
import { ENCRYPTED_SECRETS_FILE } from "../src/utils";
async function main() {
const payload = await gitopsSecrets.providers.doppler.fetch();
if (!process.env.SECRETS_KEY) {
throw new Error("SECRETS_KEY is not set");
}
const cryptr = new Cryptr(process.env.SECRETS_KEY);
const encryptedText = cryptr.encrypt(JSON.stringify(payload));
await fs.writeFile(ENCRYPTED_SECRETS_FILE, encryptedText);
let envFile = "";
Object.entries({
...payload,
})
.filter(([key]) => key.startsWith("NEXT_PUBLIC_"))
.forEach(([key, value]) => {
envFile += `${key}=${value}\n`;
});
envFile += `DOPPLER_TOKEN=${process.env.DOPPLER_TOKEN}\n`;
envFile += `SECRETS_KEY=${process.env.SECRETS_KEY}\n`;
await fs.writeFile(".env", envFile);
}
void main();
Затем нам нужно расшифровать секреты в коде времени выполнения. Мы создадим вспомогательную функцию для этого.
let decryptedSecrets: null | {
[key: string]: string;
} = null;
import { readFileSync } from "fs";
import Cryptr from "cryptr";
import path from "path";
export const ENCRYPTED_SECRETS_FILE = ".encrypted-secrets";
export const getSecret = (key: string) => {
// in case you have some overrides in `.env.local`
if (process.env.NODE_ENV === "development" && process.env[key]) {
return process.env[key];
}
// only decrypt secrets the first time
if (!decryptedSecrets) {
if (!process.env.SECRETS_KEY) {
return undefined;
}
const encryptedSecrets = readFileSync(
path.join(process.cwd(), ENCRYPTED_SECRETS_FILE),
"utf8"
);
const cryptr = new Cryptr(process.env.SECRETS_KEY);
decryptedSecrets = JSON.parse(cryptr.decrypt(encryptedSecrets));
}
return decryptedSecrets?.[key];
};
Вуаля! Теперь вы можете использовать секреты везде в вашем приложении следующим образом:
// back-end
const apiKey = getSecret("API_KEY")
// front-end
const somePublicKey = process.env.NEXT_PUBLIC_KEY
Проверьте рабочую демонстрацию здесь: (ссылка, репозиторий в github).
У Vercel может быть ограничение окружения в 4 КБ, но с некоторой творческой инженерией, вы можете столкнуться с системой, которая будет более удобной для разработчиков и безопасной, чем раньше.
Такой подход может быть правильным, если вы являетесь стартапом на ранней стадии. Когда вы станете больше и у вас будут строже требования к управлению конфиденциальными данными, вы, вероятно, столкнетесь с более сложной облачной инфраструктурой.
В Layer3, мы используем Vercel и Doppler для быстрого перемещения. Если вам понравился этот пост и вам понравилась идея создания новых типов приложений, которые используют преимущества децентрализованной сети, вам следует присоединиться к нашей команде!
