ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Epic Stack 密钥安全管理实战:环境变量规范与 `fly secrets` 生产部署指南

Epic Stack 密钥安全管理实战:环境变量规范与 `fly secrets` 生产部署指南 Epic Stack 密钥安全管理实战环境变量规范与fly secrets生产部署指南【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack在 Epic Stack 全栈应用起步框架中所有敏感信息密钥、令牌、API Key的存储与管理遵循环境变量 Fly.io Secrets的统一方案本地开发通过.env.example/.env文件配合 MSW 模拟服务完成生产与预发环境通过fly secrets set命令注入。本文以 docs/secrets.md 为核心骨架结合仓库中的环境变量校验源码、Mock 实现与部署配置系统讲解 Epic Stack 的密钥管理规范、完整环境变量清单与生产发布流程帮助你在使用该框架时建立安全、可离线开发、可上生产的密钥管理习惯。核心原则一句话绝不把任何真实密钥硬编码进源码即使你的源码仓库是私有的也不行——Epic Stack 默认在生产构建中生成 Source Maps硬编码的密钥会随之暴露给公众。一、为什么不能把密钥写进源码Source Maps 的公开暴露风险Epic Stack 的 secrets 文档 开篇就给出了强烈警告不要在任何源代码中硬编码密钥。原因并不只在于源码可见性而是与框架的构建产物直接相关。根据仓库的决策记录 docs/decisions/016-source-maps.mdEpic Stack默认在生产环境开启 Source Maps该决策后续由 docs/decisions/034-source-maps.md 演进补充。构建时会出现如下警告 remix build --sourcemap Building Remix app in production mode... ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ You have enabled source maps in production. This will make your server-side code visible to the public and is highly discouraged! If you insist, please ensure you are using environment variables for secrets and not hard-coding them into your source! ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️该决策文档给出的理由是开启 Source Maps 对生产排障和 Sentry 等错误监控工具至关重要没有 Source Maps 时你只能看到压缩后的混淆代码而 Remix 这类框架允许在同一文件中混写服务端与浏览器端代码客户端 Source Maps 中会包含部分服务端代码一旦其中硬编码了密钥就会全部公之于众。决策的结论非常明确与其依赖源码不可见这种不安全感不如从源头遵守密钥只用环境变量承载的良好卫生习惯。这也解释了 Epic Stack 在 docs/decisions/016-source-maps.md 的 Consequence 部分所承诺的为开发者提供本文这份环境变量密钥使用规范文档。硬编码密钥 默认开启 Source Maps 密钥公开泄露这就是秘密管理方案存在的根本原因。二、环境变量的运行期强制校验env.server.ts的 Zod Schema在深入本地开发流程之前先理解 Epic Stack 如何强制环境变量的存在——这不是约定而是代码层面的硬约束。仓库中的 app/utils/env.server.ts 使用 Zod 定义了一份环境变量 Schema并在应用启动时进行safeParse校验const schema z.object({ NODE_ENV: z.enum([production, development, test] as const), DATABASE_PATH: z.string(), DATABASE_URL: z.string(), SESSION_SECRET: z.string(), INTERNAL_COMMAND_TOKEN: z.string(), HONEYPOT_SECRET: z.string(), CACHE_DATABASE_PATH: z.string(), // If you plan on using Sentry, remove the .optional() SENTRY_DSN: z.string().optional(), // If you plan to use Resend, remove the .optional() RESEND_API_KEY: z.string().optional(), // If you plan to use GitHub auth, remove the .optional() GITHUB_CLIENT_ID: z.string().optional(), GITHUB_CLIENT_SECRET: z.string().optional(), GITHUB_REDIRECT_URI: z.string().optional(), GITHUB_TOKEN: z.string().optional(), ALLOW_INDEXING: z.enum([true, false]).optional(), // Tigris Object Storage Configuration AWS_ACCESS_KEY_ID: z.string(), AWS_SECRET_ACCESS_KEY: z.string(), AWS_REGION: z.string(), AWS_ENDPOINT_URL_S3: z.string().url(), BUCKET_NAME: z.string(), })对应地init()函数会在entry.server.ts与root.tsx中调用确保环境变量在应用启动前就已校验完毕export function init() { const parsed schema.safeParse(process.env) if (parsed.success false) { console.error( ❌ Invalid environment variables:, parsed.error.flatten().fieldErrors, ) throw new Error(Invalid environment variables) } }从这份 Schema 可以得到两个重要结论必须项与可选项边界清晰SESSION_SECRET、HONEYPOT_SECRET、INTERNAL_COMMAND_TOKEN、数据库路径、Tigris 对象存储配置等是必填项缺失即启动失败而SENTRY_DSN、RESEND_API_KEY、GITHUB_*等第三方服务凭据以.optional()标注配合注释说明一旦接入对应服务就去掉.optional()做到最小化配置摩擦对应 docs/guiding-principles.md 中的 Minimize Setup Friction 原则。客户端可见变量被严格隔离文件末尾的getEnv()只暴露MODE、SENTRY_DSN、ALLOW_INDEXING三个变量给浏览器端并明确注释Donotadd any environment variables in here that you do not wish to be included in the client防止SESSION_SECRET等敏感值泄漏到客户端 bundle。三、本地开发.env.example放假值、.env放真值3.1 双文件分工本地开发阶段Epic Stack 采用双 .env 文件策略职责完全分离文件是否提交仓库存放内容用途.env.example是git 跟踪占位假值 / Mock 值让协作者知道需要哪些环境变量.env否被 .gitignore 忽略真实密钥值本地与真实第三方服务交互时使用对应源码可见仓库根目录的 .env.example.env因被忽略而不会出现在仓库中。当需要新增一个密钥时规范做法是在.env.example中添加一行占位值必须是假值因为这个文件会被提交在本地.env中放入真实值。3.2.env.example中的占位值约定仓库的 .env.example 给出了完整的占位示例其中值得注意的约定包括LITEFS_DIR/litefs/data DATABASE_PATH./prisma/data.db DATABASE_URLfile:./data.db?connection_limit1 CACHE_DATABASE_PATH./other/cache.db SESSION_SECRETsuper-duper-s3cret HONEYPOT_SECRETsuper-duper-s3cret RESEND_API_KEYre_blAh_blaHBlaHblahBLAhBlAh SENTRY_DSNyour-dsn # this is set to a random value in the Dockerfile INTERNAL_COMMAND_TOKENsome-made-up-token # the mocks and some code rely on these two being prefixed with MOCK_ # if they arent then the real github api will be attempted GITHUB_CLIENT_IDMOCK_GITHUB_CLIENT_ID GITHUB_CLIENT_SECRETMOCK_GITHUB_CLIENT_SECRET GITHUB_TOKENMOCK_GITHUB_TOKEN GITHUB_REDIRECT_URIhttps://example.com/auth/github/callback # set this to false to prevent search engines from indexing the website # default to allow indexing for seo safety ALLOW_INDEXINGtrue # Tigris Object Storage (S3-compatible) Configuration AWS_ACCESS_KEY_IDmock-access-key AWS_SECRET_ACCESS_KEYmock-secret-key AWS_REGIONauto AWS_ENDPOINT_URL_S3https://fly.storage.tigris.dev BUCKET_NAMEmock-bucket关键约定解读MOCK_前缀是开关注释明确说明 the mocks and some code rely on these two being prefixed withMOCK_。以GITHUB_CLIENT_ID为例tests/mocks/github.ts 中的判断逻辑为const passthroughGitHub !process.env.GITHUB_CLIENT_ID?.startsWith(MOCK_) process.env.NODE_ENV ! test即只要GITHUB_CLIENT_ID以MOCK_开头或处于测试环境MSW 就会拦截 GitHub API 请求返回模拟数据一旦换成真实 Client IDMSW 的 handler 会执行passthrough()放行真实请求。这意味着本地切换到真实服务只需修改.env中的值无需改动任何源码。INTERNAL_COMMAND_TOKEN在 Dockerfile 中随机生成注释指向 other/Dockerfile其中通过openssl rand -hex 32生成随机令牌并写入.env由 dotenv 加载RUN INTERNAL_COMMAND_TOKEN$(openssl rand -hex 32) \ echo INTERNAL_COMMAND_TOKEN$INTERNAL_COMMAND_TOKEN .env3.3 离线开发原则用 MSW Mock 一切第三方服务Epic Stack 的指导原则 docs/guiding-principles.md 中明确列出一条Offline Development尽量让开发完全离线进行对于必须使用第三方服务的能力如邮件提供可 mock 的实现供本地使用。因此当你接入一个新的外部服务时除了在.env.example中登记占位值还应尽量在 tests/mocks 目录下为其编写 MSW handler。仓库已经内置了四组 Mocktests/mocks/github.ts — GitHub OAuth 登录与用户信息 APItests/mocks/resend.ts — Resend 邮件发送 APItests/mocks/tigris.ts — Tigris 对象存储S3 兼容API将上传文件落到本地 fixtures 目录tests/mocks/pwned-passwords.ts — Pwned Passwords 泄露密码查询 API它们统一在 tests/mocks/index.ts 中聚合导出import { handlers as githubHandlers } from ./github.ts import { handlers as pwnedPasswordApiHandlers } from ./pwned-passwords.ts import { handlers as resendHandlers } from ./resend.ts import { handlers as tigrisHandlers } from ./tigris.ts这套机制与MOCK_前缀约定配合让你在本地开发时完全不触碰真实第三方服务、不需要真实密钥也能跑通全部功能只有确实需要联调时才把真实值写进.env。四、生产环境用fly secrets set注入密钥4.1 基本用法生产与预发staging环境的安全密钥发布统一使用 Fly.io 的fly secrets set命令其行为是将环境变量写入平台加密存储并以新环境变量重新部署应用。原文档以接入titoAPI 为例fly secrets set TITO_API_SECRETsome_secret_value fly secrets set TITO_API_SECRETsome_secret_value --app [YOUR_STAGING_APP_NAME]第一条命令将密钥设置到当前 Fly 应用生产第二条命令通过--app [YOUR_STAGING_APP_NAME]显式指定 staging 应用保证两个环境各自持有独立密钥。执行后 Fly 会触发一次重新部署让新环境变量在应用实例中生效。注意fly secrets set是平台级秘密存储与把变量写进fly.tomlfly.toml 中只包含应用名、region、端口、健康检查等非敏感配置完全不同——敏感值绝不应进入任何会被提交的配置文件。4.2 仓库中各服务的真实fly secrets实战示例Epic Stack 的文档体系在多处演示了相同模式可直接对照使用核心会话与安全密钥docs/deployment.mdfly secrets set SESSION_SECRET$(openssl rand -hex 32) HONEYPOT_SECRET$(openssl rand -hex 32) --app [YOUR_APP_NAME] fly secrets set SESSION_SECRET$(openssl rand -hex 32) HONEYPOT_SECRET$(openssl rand -hex 32) --app [YOUR_APP_NAME]-staging这里展示了两个技巧用openssl rand -hex 32生成高强度随机值避免手敲弱密码以及一条命令可同时设置多个密钥。SESSION_SECRET与HONEYPOT_SECRET正是 app/utils/env.server.ts 中校验的必填项前者用于会话签名详见 docs/decisions/007-sessions.md后者用于蜜罐反垃圾防护详见 docs/decisions/033-honeypot.md。邮件服务 Resenddocs/email.mdfly secrets set RESEND_API_KEYre_blAh_blaHBlaHblahBLAhBlAh --app [YOUR_APP_NAME] fly secrets set RESEND_API_KEYre_blAh_blaHBlaHblahBLAhBlAh --app [YOUR_APP_NAME]-staging错误监控 Sentrydocs/monitoring.mdfly secrets set SENTRY_DSNyour_dsnSEO 索引开关docs/deployment.mdfly secrets set ALLOW_INDEXINGfalse --app [YOUR_APP_NAME]-stagingALLOW_INDEXINGfalse可阻止搜索引擎索引 staging 环境页面其值会在 app/utils/env.server.ts 中被getEnv()暴露给客户端并驱动 app/routes/_seo/robots[.]txt.ts 等 SEO 路由的生成逻辑。4.3 与安全文档的呼应docs/security.md 中同样强调These secrets need to also be set on Fly using thefly secretscommand——进一步印证了fly secrets是生产密钥注入的唯一入口它与本地.env本地开发、.env.example变量清单共同构成 Epic Stack 三层密钥管理模型阶段载体是否入库密钥真实性本地离线开发.env MSW MockMOCK_前缀否.gitignore假值即可跑通本地真实联调.env否.gitignore真实值变量清单/协作.env.example是必须是假值生产 / stagingfly secrets set平台加密存储否真实值--app区分环境五、密钥管理最佳实践小结新增密钥三步走先在 .env.example 登记假值占位 → 按需在本地.env写入真实值 → 生产/预发用fly secrets set含--app区分环境注入。遵守MOCK_前缀约定涉及可模拟的第三方服务让占位值以MOCK_开头MSW 即自动接管请求保证离线开发对应 tests/mocks/github.ts 的passthroughGitHub判断。接入新服务时同步补 Mock在 tests/mocks 目录新增 handler 并在 tests/mocks/index.ts 中聚合注册与 Offline Development 指导原则保持一致。永远不要硬编码Source Maps 默认开启见 docs/decisions/016-source-maps.md硬编码密钥等于公开泄露SESSION_SECRET、HONEYPOT_SECRET等必填项缺失时app/utils/env.server.ts 的 Zod 校验会在启动阶段直接报错从机制上杜绝漏配。客户端可见变量最小化只有getEnv()白名单内的MODE、SENTRY_DSN、ALLOW_INDEXING会进入客户端 bundle其余敏感变量始终停留在服务端环境。这套环境变量 fly secrets MSW Mock的组合方案让 Epic Stack 开发者既能享受本地零配置离线开发的流畅体验又能在生产环境以平台级加密存储安全托管全部密钥真正做到密钥不进代码、代码不进风险。【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表