保险库
Managing secrets in Postgres.
Vault 是一个 Postgres 扩展和配套的 Supabase UI,它可以让你安全、轻松地在数据库中存储加密的秘密和其他数据。这为使用 Postgres 提供了很多可能性,让它的用途超出了标准发行版的功能。
🌐 Vault is a Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets and other data in your database. This opens up a lot of possibilities to use Postgres in ways that go beyond what is available in a stock distribution.
在底层,Vault 是一个用认证加密存储在磁盘上的秘密表。通过 Postgres 视图,可以以解密形式访问它们,从而让应用通过 SQL 使用这些秘密。由于这些秘密在磁盘上是加密和认证的,任何备份或复制流也会保留这种加密,无法被解密或伪造。
🌐 Under the hood, the Vault is a table of Secrets that are stored using Authenticated Encryption on disk. They are then available in decrypted form through a Postgres view so that the secrets can be used by applications from SQL. Because the secrets are stored on disk encrypted and authenticated, any backups or replication streams also preserve this encryption in a way that can't be decrypted or forged.
Supabase 为 Vault 提供了一个仪表板界面,使存储秘密变得很简单。点击一个按钮,输入你的秘密,然后保存即可。
🌐 Supabase provides a dashboard UI for the Vault that makes storing secrets easy. Click a button, type in your secret, and save.
你可以使用 Vault 来存储机密——从环境变量到 API 密钥应有尽有。然后你可以在数据库的任何地方使用这些机密:Postgres 函数、触发器,以及 Webhooks。从 SQL 的角度来看,访问机密就像查询一个表(或者在这里是一个视图)一样简单。底层的机密表将以加密形式存储。
🌐 You can use Vault to store secrets - everything from Environment Variables to API Keys. You can then use these secrets anywhere in your database: Postgres Functions, Triggers, and Webhooks. From a SQL perspective, accessing secrets is as easy as querying a table (or in this case, a view). The underlying secrets tables will be stored in encrypted form.
使用 Vault #
🌐 Using Vault
你可以通过界面或使用 SQL 来管理密钥。
🌐 You can manage secrets from the UI or using SQL.
添加秘密 #
🌐 Adding secrets
还有一个方便的函数用来创建密钥,叫做 vault.create_secret():
🌐 There is also a handy function for creating secrets called vault.create_secret():
1select vault.create_secret('my_s3kre3t');这个函数返回新秘密的 UUID。
🌐 The function returns the UUID of the new secret.
显示结果
1-[ RECORD 1 ]-+-------------------------------------2create_secret | c9b00867-ca8b-44fc-a81d-d20b8169be17秘密还可以有一个可选的唯一名称和可选的描述。这些也是 vault.create_secret() 的参数:
🌐 Secrets can also have an optional unique name and an optional description. These are also arguments to vault.create_secret():
1select vault.create_secret('another_s3kre3t', 'unique_name', 'This is the description');显示结果
1-[ RECORD 1 ]-----------------------------------------------------------------2id | 7095d222-efe5-4cd5-b5c6-5755b451e2233name | unique_name4description | This is the description5secret | 3mMeOcoG84a5F2uOfy2ugWYDp9sdxvCTmi6kTeT97bvA8rCEsG5DWWZtTU8VVeE=6key_id |7nonce | \x9f2d60954ba5eb566445736e0760b0e38created_at | 2022-12-14 02:34:23.85159+009updated_at | 2022-12-14 02:34:23.85159+00查看秘密 #
🌐 Viewing secrets
如果你查看 vault.secrets 表,你会看到你的数据是加密存储的。要解密这些数据,可以使用自动创建的视图 vault.decrypted_secrets。这个视图会即时解密秘密数据:
🌐 If you look in the vault.secrets table, you will see that your data is stored encrypted. To decrypt the data, there is an automatically created view vault.decrypted_secrets. This view will decrypt secret data on the fly:
1select * 2from vault.decrypted_secrets 3order by created_at desc 4limit 3;显示结果
1-[ RECORD 1 ]----+-----------------------------------------------------------------2id | 7095d222-efe5-4cd5-b5c6-5755b451e2233name | unique_name4description | This is the description5secret | 3mMeOcoG84a5F2uOfy2ugWYDp9sdxvCTmi6kTeT97bvA8rCEsG5DWWZtTU8VVeE=6decrypted_secret | another_s3kre3t7key_id |8nonce | \x9f2d60954ba5eb566445736e0760b0e39created_at | 2022-12-14 02:34:23.85159+0010updated_at | 2022-12-14 02:34:23.85159+0011-[ RECORD 2 ]----+-----------------------------------------------------------------12id | c9b00867-ca8b-44fc-a81d-d20b8169be1713name |14description |15secret | a1CE4vXwQ53+N9bllJj1D7fasm59ykohjb7K90PPsRFUd9IbBdxIGZNoSQLIXl4=16decrypted_secret | another_s3kre3t17key_id |18nonce | \x1d3b2761548c4efb2d29ca11d44aa22f19created_at | 2022-12-14 02:32:50.58921+0020updated_at | 2022-12-14 02:32:50.58921+0021-[ RECORD 3 ]----+-----------------------------------------------------------------22id | d91596b8-1047-446c-b9c0-66d98af6d00123name |24description |25secret | S02eXS9BBY+kE3r621IS8beAytEEtj+dDHjs9/0AoMy7HTbog+ylxcS22A==26decrypted_secret | s3kre3t_k3y27key_id |28nonce | \x3aa2e92f9808e496aa4163a59304b89529created_at | 2022-12-14 02:29:21.3625+0030updated_at | 2022-12-14 02:29:21.3625+00注意这个视图有一个 decrypted_secret 列,里面包含了解密后的秘密。视图不会存储在磁盘上,它们只会在查询时运行,所以磁盘上的秘密仍然是加密的,在任何备份转储或复制流中也是如此。
🌐 Notice how this view has a decrypted_secret column that contains the decrypted secrets. Views are not stored on disk, they are only run at query time, so the secret remains encrypted on disk, and in any backup dumps or replication streams.
你应该确保始终通过适当的 SQL 权限设置来保护对这个视图的访问,因为任何有访问权限的人都可以访问解密后的机密信息。
🌐 You should ensure that you protect access to this view with the appropriate SQL privilege settings at all times, as anyone that has access to the view has access to decrypted secrets.
更新秘密 #
🌐 Updating secrets
要更新一个密钥,使用 vault.update_secret() 函数。把密钥的 UUID 作为第一个参数,然后提供更新后的密钥、名称或描述:
🌐 To update a secret, use the vault.update_secret() function. Provide the secret UUID as the first argument, followed by an updated secret, name, or description:
1select2 vault.update_secret(3 '7095d222-efe5-4cd5-b5c6-5755b451e223',4 'n3w_upd@ted_s3kret',5 'updated_unique_name',6 'This is the updated description'7 );显示结果
1-[ RECORD 1 ]-+-2update_secret |34postgres=> select * from vault.decrypted_secrets where id = '7095d222-efe5-4cd5-b5c6-5755b451e223';5-[ RECORD 1 ]----+---------------------------------------------------------------------6id | 7095d222-efe5-4cd5-b5c6-5755b451e2237name | updated_unique_name8description | This is the updated description9secret | lhb3HBFxF+qJzp/HHCwhjl4QFb5dYDsIQEm35DaZQOovdkgp2iy6UMufTKJGH4ThMrU=10decrypted_secret | n3w_upd@ted_s3kret11key_id |12nonce | \x9f2d60954ba5eb566445736e0760b0e313created_at | 2022-12-14 02:34:23.85159+0014updated_at | 2022-12-14 02:51:13.938396+00深入探讨 #
🌐 Deep dive
正如我们提到的,Vault 以经过认证的加密形式存储秘密。关于这一点,有些细节你可能会好奇。认证意味着什么?加密密钥存储在哪里?本节将解释这些细节。
🌐 As we mentioned, Vault stores secrets in an authenticated encrypted form. There are some details around that you may be curious about. What does authenticated mean? Where is the encryption key stored? This section explains those details.
带关联数据的认证加密 #
🌐 Authenticated encryption with associated data
第一个重要特点是它使用了一种 带有关联数据的认证加密 加密算法(基于 libsodium)。
加密密钥位置 #
🌐 Encryption key location
认证加密 意味着除了数据被加密之外,它还会被签名,以防被伪造。你可以保证数据是由你信任的人加密的,而单单加密是无法保证这一点的。解密功能会在解密值之前验证签名是否有效。
关联数据 指的是你可以在签名计算中包含同一行的其他列。这并不会加密那些列——而是确保你的加密值只与该行的列相关联。如果攻击者试图将另一行的加密值复制到当前行,签名会被拒绝(前提是你在关联数据中使用了唯一的列)。
另一个重要的特点是加密密钥从不与加密数据一起存储在数据库中。即使攻击者能够获取你整个数据库的转储,他们也只会看到加密数据,永远不会看到加密密钥本身。
🌐 Another important feature is that the encryption key is never stored in the database alongside the encrypted data. Even if an attacker can capture a dump of your entire database, they will see only encrypted data, never the encryption key itself.
这是一个重要的安全预防措施——把加密密钥存储在数据库本身几乎没有意义,就像锁上前门却把钥匙留在锁里一样!把密钥存储在数据库外部就能解决这个问题。
🌐 This is an important safety precaution - there is little value in storing the encryption key in the database itself as this would be like locking your front door but leaving the key in the lock! Storing the key outside the database fixes this issue.
密钥存储在哪里?Supabase 会在我们安全的后端系统中为每个项目创建并管理一个唯一的加密密钥。我们会把这个密钥安全地保存,并且与你的数据分开。你仍然可以控制自己的密钥——管理 API 接口 会返回你项目的 64 位十六进制根密钥,这样你就可以在 Supabase 之外解密数据,或者复制到其他项目。
🌐 Where is the key stored? Supabase creates and manages a unique encryption key for each project in our secured backend systems. We keep this key safe and separate from your data. You remain in control of your key - the Management API endpoint returns your project's 64-character hex root key so you can decrypt your data outside of Supabase or copy it to another project.
应该仔细考虑哪些角色可以访问 vault.secrets 表。一个例子是 postgres 用户明确授予对保险库表的访问权限。
🌐 Which roles should have access to the vault.secrets table should be carefully considered. One example would be the postgres user explicitly granting access to the vault table.
密钥可移植性和迁移 #
🌐 Key portability and migration
每个 Supabase 项目都有自己的根加密密钥。同一项目内的操作——比如暂停和恢复,以及时点或原位恢复——都会使用相同的密钥,因此你的密钥会自动保持可读。恢复到新项目 和 分支 流程也会把密钥复制到新项目中。
🌐 Each Supabase project has its own root encryption key. Same-project operations - pausing and restoring, and Point-in-Time or in-place restores - keep the same key, so your secrets stay readable automatically. The Restore to a new project and Branching flows also copy the key to the new project.
不过,如果你迁移到一个带有手动 pg_dump / pg_restore 的新项目,该项目会用自己的新密钥创建,并且无法解密从旧项目复制过来的密钥。在依赖迁移后的数据之前,把旧项目的根密钥复制到新项目。pgsodium 管理 API 端点会返回并接收 64 位十六进制根密钥,并且仅对活跃(未暂停或移除)的项目可用:
🌐 However, if you migrate to a new project with a manual pg_dump / pg_restore, that project is created with its own fresh key and cannot decrypt secrets copied from the old project. Before relying on the migrated data, copy the old project's root key to the new project. The pgsodium Management API endpoint returns and accepts the 64-character hex root key, and is only available for active (not paused or removed) projects:
1export OLD_PROJECT_REF="<old_project_ref>"2export NEW_PROJECT_REF="<new_project_ref>"3export SUPABASE_ACCESS_TOKEN="<personal_access_token>"45curl "https://api.supabase.com/v1/projects/$OLD_PROJECT_REF/pgsodium" \6 -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" |7curl "https://api.supabase.com/v1/projects/$NEW_PROJECT_REF/pgsodium" \8 -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \9 -X PUT --json @-查看 使用 CLI 进行备份和还原 了解完整的还原步骤。
🌐 See Backup and restore using the CLI for the full restore procedure.
资源 #
🌐 Resources
- 在博客文章中了解更多关于 Supabase Vault 的信息
- GitHub上的Supabase Vault