Доступ к SignedUrl хранилища GCP запрещен в производстве

У меня есть приложение next.js, которое использует SDK Google Cloud Storage node.js для загрузки видео и их просмотра путем создания подписанных URL-адресов. Все работает при локальном запуске, но как только я развертываю приложение (Cloud Run), ни одно из моих действий с корзиной не работает, особенно генерация подписанных URL-адресов. Я генерирую SingleUrls на серверной стороне, чтобы клиент мог просматривать видео, которые у меня есть в корзине.

Когда я перехожу по сгенерированной ссылке, я вижу это:

<Code>SignatureDoesNotMatch</Code>
<Message>Access denied.</Message>
<Details>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Details>

Вещи, которые я пробовал:

  • взял весь ключ учетной записи службы, закодировал его в base64 и использовал в качестве переменной env. Это отлично работает локально.
  • тройной проверил значение env секрета на GCP и мое локальное значение.
  • обновил SDK до последней версии (7.11.0)
  • Учитывая учетную запись службы, на которой выполняется роль службы «Создатель токена учетной записи службы».
  • скорректированная корзина CORS: попробовал и «*», и «мой-домен». Независимо от того, что такое Cors, кажется, что он работает нормально локально.
  • попробовал изменить тип контента в функции генерации URL-адреса, но это вернуло только новые ошибки.
  • попытался удалить внедрение env при создании экземпляра new Storage() и позволить GCP ввести свои собственные учетные данные, но все равно получил ту же ошибку.
  • попытался локально выдать себя за учетную запись службы с помощью ADC, и это работает нормально.
  • попытался настроить минимальный экспресс-сервер с новой учетной записью службы. Та же проблема: работает локально, но при развертывании не совпадает сигнатура.

Мой код:

import { Storage } from "@google-cloud/storage";
import { env } from "process";

const rawKey = env.GCP_BUCKET_HANDLER_KEY ?? "";

const creds = rawKey
    ? JSON.parse(Buffer.from(rawKey, "base64").toString())
    : {};

const storage = new Storage({
    projectId: creds.project_id,
    credentials: creds,
});

const PRIMARY_BUCKET_NAME = env.GCP_PRIMARY_BUCKET_NAME ?? "invalid";

export const primaryBucket = storage.bucket(PRIMARY_BUCKET_NAME);

export async function gcGenerateReadSignedUrl({
    fileName,
    id,
}: GcVideoFilePathProps) {
    const filePath = `video/${id}/${fileName}`;
    const options: GetSignedUrlConfig = {
        version: "v4",
        action: "read",
        expires: Date.now() + 15 * 60 * 1000, // 15 minutes
    };
    const [url] = await primaryBucket.file(filePath).getSignedUrl(options);
    return url;
}

Обновлено: Настройка сервера Go для создания подписанного URL-адреса, похоже, работает при развертывании. Не знаю, что пошло не так: учетная запись службы (новая) такая же, как и у моего Next.js. Одна из альтернатив, к которой я настоятельно склоняюсь, — это перенести все мои операции с корзинами на сервер Go и отправлять запросы с сервера Next для получения URL-адресов и т. д.

EDIT2: исправление

У моего сервера Go возникли проблемы при следующем развертывании с внедрением переменных env, и именно тогда я обнаружил, что в имени контейнера env был конечный пробел. Я также обнаружил, что использовал для загрузки SignedPostPolicyV4 с formData, что повредило файл (странно, что файл раньше не был поврежден). Итак, теперь я:

  1. Используйте new Storage() и позвольте библиотеке получить учетные данные в экземпляре Cloud Run, а при локальной работе я настрою ADC так, чтобы он указывал на ключ.
  2. Исправлена ​​переменная env с конечным пробелом. Вероятный главный виновник
  3. Исправлена ​​загрузка, чтобы использовать правильный метод.
  4. Другие исправления процесса загрузки во внешнем интерфейсе.
// backend nextjs
const options = {
    version: "v4",
    action: "write",
    expires: Date.now() + 15 * 60 * 1000, // 15 minutes
} satisfies GetSignedUrlConfig;
const [url] = await primaryBucket.file(filePath).getSignedUrl(options);

// frontend nextjs
const upload = await fetch(url, {
    method: "PUT",
    body: file,
});

Теперь исходное решение Nextjs без проблем работает в рабочей среде. Кажется, было много мелких сбоев, но, если сложить все вместе, было трудно точно определить, что и когда пошло не так. Это помогло настроить отдельные тестовые серверы, чтобы сузить проблему (спасибо ДазВилкину за его предложения!)

Ваш вопрос не является минимальным воспроизведением и упускает из виду важную разницу, о которой вы говорите (без доказательств), что код работает при локальном запуске, но не при запуске в Cloud Run. Итак, мы знаем, что включенный вами код (может) работать. Непонятно, почему вы не используете ADC. Вы усложняете решение и делаете его менее безопасным, используя ключи в переменных среды. Показательно, что при использовании АЦП вы получаете 500. Я думаю, вам следует вернуться к примеру как есть использовать ADC, а затем обновить Q

DazWilkin 19.05.2024 22:24

Я решил, что ключи будут более простыми, чем ADC, но ладно, я вошел в систему (олицетворяясь) как учетная запись службы, которая локально запускает службу Cloud Run, и удалил все аргументы для создания new Storage(), чтобы он мог получать ADC. реквизиты для входа. Когда я перехожу на страницу, которая использует SignedUrls, я получаю сообщение об ошибке «Разрешение 'iam.serviceAccounts.signBlob' отклонено на ресурсе...». У рассматриваемой учетной записи службы есть администратор хранилища и создатель токена учетной записи службы. О каком ресурсе идет речь, о ведре?

Firgrep 20.05.2024 12:16

Хорошо, решена проблема с разрешением. Мне пришлось предоставить себе (или моему пользователю) роль создателя токена учетной записи службы. После этого я вошел в систему (выдал себя за другое лицо) последовательно как с учетной записью службы, на которой работает облачная служба, так и с другой, которую я использовал, через переменную env. Оба успешно сгенерировали подписанные URL-адреса локально. Так что же происходит на производстве? Нужно ли мне устанавливать специальную среду в Cloud Run, чтобы SDK хранилища работал?

Firgrep 20.05.2024 17:32

Я печатаю .bucket("name").getServiceAccount(), и это выглядит так, как и ожидалось: учетные данные, похоже, проходят, и у SA есть соответствующие разрешения. Сейчас я не получаю ошибку 500, используя учетные данные ADC в производстве, но я все еще получаю ошибку несоответствия подписи, которую получал изначально.

Firgrep 20.05.2024 17:43
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
0
4
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я развернул образец от Google с небольшими изменениями (для настройки):

const {Storage} = require('@google-cloud/storage');
const storage = new Storage();

const BUCKET = process.env.BUCKET;
const OBJECT = process.env.OBJECT;

async function generateV4ReadSignedUrl() {
  const options = {
    version: 'v4',
    action: 'read',
    expires: Date.now() + 15 * 60 * 1000,
  };

  const [url] = await storage
    .bucket(BUCKET)
    .file(OBJECT)
    .getSignedUrl(options);

  console.info('Generated GET signed URL:');
  console.info(url);
  console.info('You can use this URL with any user agent, for example:');
  console.info(`curl '${url}'`);
}

generateV4ReadSignedUrl().catch(console.error);

Я развернул код как задание Cloud Run, чтобы не возиться с веб-сервером:

BILLING = "..."
PROJECT = "..."
REGION = "..."

BUCKET = "gs://${PROJECT}"
OBJECT = "..."

ACCOUNT = "tester"
EMAIL=${ACCOUNT}@${PROJECT}.iam.gserviceaccount.com

JOB = "..."

SERVICES=(
    "artifactregistry"
    "cloudbuild"
    "run"
)
ROLEs=(
    "iam.serviceAccountTokenCreator"
    "storage.objectViewer"
)
gcloud projects create ${PROJECT}

gcloud billing projects link ${PROJECT} \
--billing-account=${BILLING}

for SERVICE in "${SERVICES[@]}"
do
  gcloud services enable ${SERVICE}.googleapis.com \
  --project=${PROJECT}
done

gcloud storage buckets create ${BUCKET} \
--project=${PROJECT}

gcloud iam service-accounts create ${ACCOUNT} \
--project=${PROJECT}

for ROLE in "${ROLES[@]}"
do
  gcloud projects add-iam-policy-binding ${PROJECT} \
  --member=serviceAccount:${EMAIL} \
  --role = "roles/${ROLE}"
done

# Only to test locally
gcloud iam service-accounts keys create ${PWD}/${ACCOUNT}.json \
--iam-account=${EMAIL} \
--project=${PROJECT}

# Runs as ACCOUNT
gcloud run jobs deploy ${JOB} \
--source=${PWD} \
--set-env-vars=BUCKET=${BUCKET},OBJECT=${OBJECT} \
--service-account=${EMAIL} \
--region=${REGION} \
--project=${PROJECT}
# Create GCS Object
echo "Hello Freddie" > ${OBJECT}
gcloud storage cp \
  ${OBJECT} \
  ${BUCKET}/${OBJECT} \
--project=${PROJECT}
# Invoke Job | Create Signed URL
ID=$(\
  gcloud run jobs execute ${JOB} \
  --region=${REGION} \
  --project=${PROJECT} \
  --format = "value(metadata.name)")

Дайте ему время завершить:

# Get the "curl" command from the logs
FILTER = "labels.\"run.googleapis.com/execution_name\"=\"${ID}\""

gcloud logging read  "${FILTER}" \
--project=${PROJECT} \
--format = "value(textPayload)" \
| grep ^curl

Вызовите команду возврата curl ...:

Hello Freddie

Спасибо, что нашли время сделать это. Но это добавляет мне еще один шаг, чтобы выяснить, как заставить веб-сервер взаимодействовать с заданием, и не дает ответа на вопрос, что пошло не так с моей первоначальной попыткой. Тем не менее, это дало мне идею попробовать что-то новое. Сегодня я попробовал создать быстрый сервер Express, а также новую учетную запись службы, и это была та же проблема. Работает локально, но не в Cloud Run. Затем я попытался создать сервер на Go, и после долгих поисков он действительно успешно обслуживал подписанный URL-адрес в Cloud Run! Действительно не уверен, что здесь произошло. Сервисный аккаунт тот же.

Firgrep 21.05.2024 23:05

Пожалуйста. Это минимальная копия, поэтому вы можете увидеть, что именно я сделал, чтобы заставить ее работать. Вы не сделали то же самое. Вы описываете, что сделали, но не показываете команды, поэтому оставить отзыв невозможно.

DazWilkin 21.05.2024 23:26

Другие вопросы по теме

Установите метаданные при загрузке нового объекта с помощью потока
Spring GCP Storage. Рассчитанная нами подпись запроса не соответствует предоставленной вами подписи. Проверьте свой секретный ключ Google и метод подписи
Каталог Pyiceberg в GCS: я не могу использовать pyceberg с облачным хранилищем Google
Ruby on Rails: ошибка «неизвестное ключевое слово: :soft_deleted» при загрузке файла с помощью google-cloud-storage
Просмотр PDF-файла, хранящегося в сегменте хранилища частного облака
Запрещенная ошибка в облачном хранилище Google при использовании токенов из Workload Identity Federation с Descope
Как безопасно отобразить файл в корзине gcs в Интернете
Проверка загруженного файла в корзину Google Cloud Storage при использовании сканера вредоносных программ (ClamAV)
Как автоматизировать загрузку .txt из GCS в хранилище данных Vertex AI?
Разрешение «storage.buckets.get» отклонено для ресурса (или оно может не существовать)