SecretManagerServiceClient вызывает 403 Permission denied CONSUMER_INVALID в Google App Engine, но `gcloud secrets versions access` работает
Фон:
- Я пытаюсь развернуть приложение Django в стандартной среде Google App Engine (GAE) в среде выполнения python39 .
- Конфигурация базы данных хранится в секретной версии Secret Manager, аналогично учебнику Google по 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"
}
Моя отладка
- Я не могу воспроизвести приведенную выше ошибку вне 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, управляемого пользователем
- вызов
access_secret_version
осуществляется с правильным 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}"
}
Django settings.py
Ниже показаны соответствующие разделы приложения settings.py
; access_secret_version
поднимает
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
Вам предоставлена неправильная роль. Посмотрите страницу документации .
-
Роль
- Secret Viewer позволяет просматривать секрет и версии, но НЕ содержимое.
- Роль Secret Accessor позволяет вам получить доступ к содержимому секретной версии.
Вздох. Это был ужасный случай трудночитаемой опечатки в файле app.yaml. В проекте была мнемоническая подстрока с очень похожими буквами, которую я неправильно набрал и просто не заметил.
FWIW, если кто-то столкнулся с подобной ошибкой, вы можете, по крайней мере, избежать этого источника ошибок:
- Я передавал строку префикса проекта и строку окружения через
app.yaml
файл, а затемsettings.py
файл я конкатенировал эти строки, чтобы сделать проект - При запуске
gcloud app deploy
(правильная) конкатенированная строка проекта также существовала в моей переменной оболочки$GOOGLE_CLOUD_PROJECT
, поэтому развертывание произошло правильно с правильным идентификатором проекта .
- Однако я удалил код конкатенации в
settings.py
в пользу переменной envGOOGLE_CLOUD_PROJECT
, которая всегда присутствует в GAE (docs) .
TLDR: сухость - это хорошо...