Bun技术评估 - 24 Secrets

概述

本文是笔者的系列博文 《Bun技术评估》 中的第二十四篇。

在本文的内容中,笔者主要想要来探讨一下Bun中的Secrets机密这个功能特性和相关的管理问题。

这个特性,也是笔者在日常的技术文档阅读过程中发现的,觉得非常有趣,也有很实用的应用场景,值得研讨和分享。

Secret和原理

为什么需要secret? 简单而言,就是提供更好和更安全的凭证管理。

凭证和配置信息的管理,是所有应用系统都会涉及到的,并且直接影响系统安全的一个非常重要的部分。现代化的应用系统越来越复杂,对安全性的要求也日益提高,很多信息和项目,都是敏感和机密的,需要进行保护,比如:

  • 数据库的连接信息(数据库地址、用户名和密码)
  • Redis认证信息
  • 应用初始化密钥/私钥
  • 第三方用户登录账号和密码
  • 第三方应用集成凭证(如微信公众号的appid/appsec)
  • ....

作为开发者,我们应该都熟悉这些内容了。在应用开发过程中,最早的管理方式通常是使用一个配置文件,来保存这些内容,并且程序启动的时候,来读取这些配置信息使用(当然还有很多人是直接硬编码到程序当中的?)。这些操作方式简单方便,通常在开发环境中使用。但如果在生产环境中,也这样操作的话,显然有一些安全隐患。另外,还有一些配置信息,使用用户级的配置文件,如 ~/.bashrc等等,来设置登陆后的环境变量,这个方式,其实和配置文件方式,没有本质区别。它们的风险都是在于,这些机密信息,是以某种明文的方式,永久的存储在操作系统的文件当中,所以,信息泄漏的风险也是持续存在的。

所以,在生产环境中,更加建议的方式是使用"设置环境变量后启动"的方式,就是启动前使用命令行临时设置一个保存机密信息的环境变量,然后启动程序。程序会从环境变量中读取并使用这个信息。这种方式的安全性在于机密信息是作为启动参数的一部分来使用的,所以在此之前,机密信息是不会以任何方式存在于可以访问的机制当中。此方法虽然更安全,但也有不易使用和维护的问题(执行命令前需要输入和设置很多参数)。特别是在运维自动化的场景中,其实是不希望有很多人工参与的环节的。而如果是将机密信息直接写在运维指令当中,其实可能还不如配置文件安全。

笔者认为,这就是secret想要改善和解决的问题。它的原理是,借助操作系统提供的凭证管理机制,可以先将机密信息预置到操作系统环境当中。然后在程序启动的时候,可以基于启动账号,安全的获取这些凭证来支撑应用过程。这个方式的优势在于,在设置完成之后,只有对应的启动账号,才可以安全的获取和使用这些机密信息。而且从操作系统的角度而言,这些信息也是加密存储的,其他人无法绕过操作系统的机制,来访问这些信息,从而保证了机密信息在操作系统环境中的存储安全。

当然,在不同的平台上,它在底层的实现方式略有差异。但基本上都利用了操作系统提供的机密信息的存储和访问机制。具体包括:

  • macOS: Keychain Services,钥匙串服务
  • Linux: libsecret (GNOME Keyring, KWallet, etc.)
  • Windows: Windows Credential Manager, Windows凭证管理器

基于以上原理和设计,我们可以在总体上认为,secret的实现和应用是安全完备的,包括以下要素:

  • Encryption 加密: 凭证使用操作系统提供的凭证管理机制,进行加密存储和解密使用
  • Access Control 访问控制: 只有存储凭证的用户,才能进行访问
  • No Plain Text 非明文: 机密信息永远不会以原文进行存储
  • Memory Safety 内存安全: Bun会在使用后,将对应的内存区域归零
  • Process Isolation 进程隔离: 凭证的访问和使用,是进程隔离的(process.env不是)
  • 可以将机密信息设置和使用分离,使用者可以并不知道机密信息的实际内容,安全性更好

最后,技术文档还提到,虽然在操作系统底层的实现方式不同,但基于更好的抽象和实现,bun的secret模块对于程序在不同平台中的开发和使用而言,基本上是一致的。所有的相关操作,都是异步和非阻塞的,并且都运行在Bun的线程池之内,来保证安全和性能。在将来的版本中,Bun社区可能会提供Provider(供应者)机制,可以更好更灵活的适应更复杂的生产环境。

Secret相关官方技术文档的链接如下:

bun.com/docs/api/se...

使用方式

在Bun中,Secret的使用方式非常简单,基本上就是set和get,当然完整的机密信息的管理周期,还应当增加delete操作。

js 复制代码
import { secrets } from "bun";

// set secret
await secrets.set({
  service: "my-app",
  name: "alice@example.com",
  value: "super-secret-password",
});

// Retrieve for API calls
const token = await secrets.get({
  service: "my-app",
  name: "alice@example.com",
});

// delete 
const deleted = await Bun.secrets.delete({
  service: "my-app",
  name: "alice@example.com",
});
// Returns: boolean

// 参数形式
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");

代码中可以理解到,它基本上就是一个简单的KV操作。唯一需要注意的就是参数名称必须合规。

  • service: 表示使用机密信息的服务或者应用名称,其实就是名字空间,因为Secret可以支持多个应用
  • name: 存储机密信息的键名称
  • value: 机密信息的值,只在设置时使用

相应的,应用的开发和运维流程,可能也需要稍微的改进一下。就是需要一个预部署凭证的过程。就是先将机密配置信息写入操作系统的凭证存储当中。然后在后续的启动中,就可以直接访问了。而且对于比较稳定的生产环境,可以做到一次写入,后续多次持续使用。

注意事项和最佳实践

按照其官方技术文档的说法,secret在Bun中,还只是一个"实验性(experimental)"的功能,当前时间点,并不建议就直接应用到生产环境当中。但如果可以应用在开发环境中,对于保证开发环境的安全,其实也是有一定好处的。

当然,secret在现阶段,还有一些限制:

  • 机密信息长度限制(基于操作系统,一般为2048-4096 bytes)
  • 服务名称和键名长度限制 (< 256字符)
  • 名称字符格式限制,通常是字符和数字,不建议使用特殊符号
  • 可能需要其他操作系统组件支撑
  • Linux: Secret service 服务进程必须是运行状态
  • macOS: Keychain 访问可用
  • Windows: Credential Manager 凭证管理器必须可用

和环境变量机制相比,secret具有以下区别和特点:

✅ 凭证加密,并且对于应用而言是透明的 ✅ 进程内存转储(Process Memeoy Dump)过程不会泄漏机密信息(内存归零) ✅ 应用重启后,任然有效 ✅ 可以动态更新 ✅ 提供名字空间访问控制 ❌ 需要操作系统凭证管理机制 ❌ 开发环境中,略显繁复(更适合生产环境)

文中还提到了一些有用的最佳实践:

  • 合理的名字空间

使用容易理解和分别的名字空间和键名称,如:

js 复制代码
// Good - matches the actual tool
{ service: "com.docker.hub", name: "username" }
{ service: "com.vercel.cli", name: "team-name" }

// Avoid - too generic
{ service: "api", name: "key" }
  • 只存储机密信息

由于使用较复杂的系统级别API,可能会对性能有一定影响,存储容量也有一定的限制。所以,应当只存储需要保证安全的机密信息。

  • 机密信息缓存

相同的理由,应该合理的缓存机密信息和使用过程。避免频繁访问相关系统操作API。

  • 在现阶段,更多的使用在开发和运维场景,如Cli攻击,本地开发服务器,个人API密钥管理等

操作系统细节

下面是secret相关操作系统的行为和细节:

  • Windows

凭据存储于Windows凭据管理器;用户可以在控制面板 → 凭据管理器 → Windows 凭据板块中查看;凭据通过 CRED_PERSIST_ENTERPRISE标志实现持久化,并按用户范围隔离;使用Windows数据保护API进行加解密。

  • Linux

需要Sercet Service Daemon守护程序(如Keying, KWallet等等),而且必须是运行状态;凭证存储在默认集合中;解锁时,可能会提醒用户。

  • Mac OS

凭证存储在当前用户的登录钥匙串(Key Chain)当中; 使用钥匙串时,可能会提示用户访问操作;凭证时持续化的,重启不会丢失;只有存储的账号才能访问凭证。

类定义

Secret的类定义如下,非常简单:

js 复制代码
namespace Bun {
  interface SecretsOptions {
    service: string;
    name: string;
  }

  interface Secrets {
    get(options: SecretsOptions): Promise<string | null>;
    set(options: SecretsOptions, value: string): Promise<void>;
    delete(options: SecretsOptions): Promise<boolean>;
  }

  const secrets: Secrets;
}

笔者遇到的问题

从上面叙述的过程中,看起来还不错,但当笔者在自己的环境中进行简单的实践和测试的时候,出现了问题。问题不在代码本身,而是在环境和支撑组件上。笔者的环境是Windows WSL2中的Debian,当笔者运行本文中的示例代码时,出现的错误是:

js 复制代码
// 需要现安装 libsecret、 gnome-keyring
sudo apt install -y libsecret-1-dev libsecret-tools gnome-keyring

// 然后执行代码
bun t
error: Could not connect: No such file or directory (code: 1)
 code: "ERR_SECRETS_PLATFORM_ERROR"

经过查询,发现这个机制是比较复杂的,涉及dbus,gnome-keyring-daemon等组件。开始的时候,出了很多问题,最后还是通过重新启动系统,才算是配置好了整个环境。

然后,第一次成功的执行,是需要先创建一个keyring:

后续的代码和操作,就可以执行了。这也可能并不是Linux系统本身的问题,而是由于WSL2比较奇怪的操作系统执行方式所限制的。这里还是先要通过一个比较奇怪的对话框操作(其实就是输入keyring的密码):

这个过程让人感觉到,由于运行机制的限制,它的使用需要通过一个用户交互来完成初始化,这个问题在真正的生产环境中(ssh远程连接, 纯CLI环境)会造成什么影响,是否影响正确执行,交互或者运维自动化,笔者还不是很确定。

小结

本文探讨了Bun提供的一个新的凭证管理模块: secret。 简述了开发凭证管理的发展过程,secret的设计缘由,基本原理和安全特点。并提供了简单的示例代码和应用流程。最后扩展讨论了相关注意事项和最佳实践。

相关推荐
计算机学长felix9 小时前
基于SpringBoot的“中学信息技术课程教学网站”的设计与实现(源码+数据库+文档+PPT)_2025-10-17
数据库·spring boot·后端
rechol10 小时前
类与对象(中)笔记整理
java·javascript·笔记
长安城没有风10 小时前
从入门到精通【Redis】Redis 典型应⽤ --- 缓存 (cache)
数据库·redis·后端·缓存
Luffe船长10 小时前
前端vue2+js+springboot实现excle导入优化
前端·javascript·spring boot
Tony Bai10 小时前
释放 Go 的极限潜能:CPU 缓存友好的数据结构设计指南
开发语言·后端·缓存·golang
周杰伦_Jay10 小时前
【Spring Boot从入门到精通】原理、实战与最佳实践
java·spring boot·后端
呼哧呼哧.10 小时前
SpringBoot 的入门开发
java·spring boot·后端
仲夏幻境10 小时前
js利用ajax同步调用如何
开发语言·javascript·ajax
Penge66611 小时前
Hadoop-大数据技术
后端
鹿鹿鹿鹿isNotDefined11 小时前
Pixelium Design:Vue3 的像素风 UI 组件库
前端·javascript·vue.js