无服务器框架似乎包含原生AWS SSM 集成:
functions:
myfunc:
# other config
environment:
TWITTER_ACCESS_TOKEN: ${ssm:myFunc}
但是,正如您所指出的,GCP 上没有类似的功能,因此您需要自己推出其中的一些功能。您可能对无服务器中的秘密中概述的一些策略感兴趣:
你需要这些秘密吗?
问总是很重要——我真的需要这些秘密吗?您能否利用云提供商 IAM(甚至跨云 OIDC)而不是将秘密注入我的应用程序?在可能的情况下,尝试利用各种云提供的 IAM 解决方案。显然,仍然有相当多的情况需要秘密。
加密环境变量
在启动该功能之前,您将本地明文机密加密为密文(加密字符串)。这是 gcloud 的示例,但您也可以使用 API 或其他工具,例如HashiCorp Vault:
$ gcloud kms encrypt \
--ciphertext-file=- \
--plaintext-file=/path/to/my/secret \
--key=my-kms-key \
--key-ring=my-kms-keyring \
--location=us-east4 \
| base64
这将输出一个 base64 编码的加密字符串,然后您将其存储在您的config.js
:
CiQAePa3VBJLbunLSqIJT+RS4nYiKdIaW6U69Y...
启动时,将您的应用程序配置为:
- Base64解码字符串
- 使用 Cloud KMS 解密密文
- 只要需要秘密,就将明文存储在内存中
我不确定您使用的是什么语言,但这里有一个 nodejs 示例。您可以在 GitHub 上的sethvargo/secrets-in-serverless找到更多示例:
const cryptoKeyID = process.env.KMS_CRYPTO_KEY_ID;
const kms = require('@google-cloud/kms');
const client = new kms.v1.KeyManagementServiceClient();
let username;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_USER,
}).then(res => {
username = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
let password;
client.decrypt({
name: cryptoKeyID,
ciphertext: process.env.DB_PASS,
}).then(res => {
password = res[0].plaintext.toString().trim();
}).catch(err => {
console.error(err);
});
exports.F = (req, res) => {
res.send(`${username}:${password}`)
}
谷歌云存储
由于您使用 GCP,另一种选择是直接使用 Google Cloud Storage (GCS) 来存储机密。这将从无服务器框架中移除您的耦合。
做一个桶:
$ gsutil mb gs://${GOOGLE_CLOUD_PROJECT}-serverless-secrets
将存储桶设为私有:
$ gsutil defacl set private gs://${GOOGLE_CLOUD_PROJECT}-serverless-secrets
$ gsutil acl set -r private gs://${GOOGLE_CLOUD_PROJECT}-serverless-secrets
将一些秘密写入存储桶。尽管它们以明文形式提交,但它们在静态时被加密,并且通过 IAM 严格控制访问。
$ gsutil -h 'Content-Type: application/json' cp - gs://${GOOGLE_CLOUD_PROJECT}-serverless-secrets/app1 <<< '{"username":"my-user", "password":"s3cr3t"}'
然后创建一个有权从存储桶中读取的服务帐户,并将该服务帐户分配给您的函数。
最后,在函数开始时从存储桶中读取(这次是 Python 示例):
import os
import json
from google.cloud import storage
blob = storage.Client() \
.get_bucket(os.environ['STORAGE_BUCKET']) \
.get_blob('app1') \
.download_as_string()
parsed = json.loads(blob)
username = parsed['username']
password = parsed['password']
def F(request):
return f'{username}:{password}'