Django
背景:
- 我正试图在python39运行时将Django应用程序部署到谷歌应用引擎(GAE)标准环境中
- 数据库配置存储在Secret Manager的秘密版本中,类似于谷歌的GAE Django教程(链接)
- 该应用程序作为用户管理的服务帐户
server@myproject.iam.gserviceaccount.com
运行,该帐户具有访问机密的适当权限,可以使用gcloud secret versions access
进行确认
问题:
- 在Django
settings.py
模块中,当我尝试使用google.cloud.secretmanager.SecretManagerServiceClient.access_secret_version(...)
访问机密时,我得到以下CONSUMER_INVALID
错误:
google.api_core.exceptions.PermissionDenied: 403 Permission denied on resource project myproject. [links {
description: "Google developer console API key"
url: "https://console.developers.google.com/project/myproject/apiui/credential"
}
, reason: "CONSUMER_INVALID"
domain: "googleapis.com"
metadata {
key: "service"
value: "secretmanager.googleapis.com"
}
metadata {
key: "consumer"
value: "projects/myproject"
}
我的调试
- I无法在GAE的外部复制以上的错误;
- 我可以确认SA可以访问机密:
gcloud secrets versions access latest --secret=server_env --project myproject
--impersonate-service-account=server@myproject.iam.gserviceaccount.com
WARNING: This command is using service account impersonation. All API calls will be executed as [server@myproject.iam.gserviceaccount.com].
DATABASE_URL='postgres://django:...'
SECRET_KEY='...'
我还确认我在本地运行django应用程序,模拟服务帐户,并进行上述
access_secret_version(...)
调用在绝望中,我甚至为项目创建了一个API密钥,并将其硬编码到我的
settings.py
文件中,而这也引发了相同的错误我已经确认了项目中的以下设置:
- 应用程序正在使用正确的用户管理SA运行
- 对CCD_ 8的调用是用正确的SA进行的(即凭证是正确地从GAE环境中提取的)
- 该项目已启用
secretmanager.googleapis.com
服务,并且已启用计费,并且计费帐户处于活动状态
如果您对配置或方法有任何建议来帮助调试,我将不胜感激
相关代码段
app.yaml
service_account: server@myproject.iam.gserviceaccount.com
runtime: python39
handlers:
# This configures Google App Engine to serve the files in the app's static
# directory.
- url: /_static
static_dir: _static/
# This handler routes all requests not caught above to your main app. It is
# required when static routes are defined, but can be omitted (along with
# the entire handlers section) when there are no static files defined.
- url: /.*
script: auto
env_variables:
...
inbound_services:
- mail
- mail_bounce
app_engine_apis: true
服务帐户创建&权限
- SA是用Terraform创建的,如下所示
- (SA没有角色
roles/secretmanager.secretAccessor
,但直接在机密本身上具有IAM绑定)
resource "google_service_account" "frontend_server" {
project = google_project.project.project_id
account_id = "server"
display_name = "Frontend Server Service Account"
}
resource "google_project_iam_member" "frontend_server" {
depends_on = [
google_service_account.frontend_server,
]
for_each = toset([
"roles/appengine.serviceAgent",
"roles/cloudsql.client",
"roles/cloudsql.instanceUser",
"roles/secretmanager.viewer",
"roles/storage.objectViewer",
])
project = google_project.project.project_id
role = each.key
member = "serviceAccount:${google_service_account.frontend_server.email}"
}
Djangosettings.py
应用程序settings.py
的相关部分如下所示;CCD_ 14提升
import logging
import environ
from google.cloud import secretmanager
import google.auth
# Load secrets from secret manager; the client is auth'd by SA IAM policies
credentials, project = google.auth.default(
scopes=['https://www.googleapis.com/auth/cloud-platform']
)
secretmanager_client = secretmanager.SecretManagerServiceClient(credentials=credentials)
# Load the database connection string into the environment
secrets = [
f"projects/{GOOGLE_CLOUD_PROJECT}/secrets/server_env/versions/latest",
]
for name in secrets:
try:
logging.info(f"Reading secret {name} into django settings module...")
payload = secretmanager_client.access_secret_version(name=name).payload.data.decode("UTF-8")
env.read_env(io.StringIO(payload))
except Exception as e:
logging.error(f"Encountered error when accessing secret {name}: {e}")
logging.error(f"Client credentials during error: {secretmanager_client._transport._credentials.__dict__}")
raise e from None
叹气。这是一个严重的app.yaml文件中难以阅读的拼写错误。这个项目有一个助记符子串,里面有非常相似的字母,我打错了,就是看不见。
FWIW,如果有人遇到类似的错误,你至少可以避免这一个错误来源:
- 我通过
app.yaml
文件传递了一个项目前缀字符串和一个环境字符串,然后settings.py
文件将这些字符串连接起来,以创建项目 - 当运行
gcloud app deploy
时,(正确的)串联项目字符串也存在于我的shell$GOOGLE_CLOUD_PROJECT
变量中,因此使用正确的项目id进行部署是正确的 - 然而,我删除了
settings.py
中的串联代码,转而使用GAE(docs)中始终存在的GOOGLE_CLOUD_PROJECT
env变量
TLDR:DRY很好。。
您授予的角色不正确。看看那个文档页。
- 秘密查看器角色允许您查看秘密和版本,但不能查看内容
- 秘密访问者角色允许您访问秘密版本的内容