У меня есть приложение 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>
Вещи, которые я пробовал:
new Storage()
и позволить GCP ввести свои собственные учетные данные, но все равно получил ту же ошибку.Мой код:
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-адресов и т. д.
У моего сервера Go возникли проблемы при следующем развертывании с внедрением переменных env, и именно тогда я обнаружил, что в имени контейнера env был конечный пробел. Я также обнаружил, что использовал для загрузки SignedPostPolicyV4 с formData, что повредило файл (странно, что файл раньше не был поврежден). Итак, теперь я:
new Storage()
и позвольте библиотеке получить учетные данные в экземпляре Cloud Run, а при локальной работе я настрою ADC так, чтобы он указывал на ключ.// 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 без проблем работает в рабочей среде. Кажется, было много мелких сбоев, но, если сложить все вместе, было трудно точно определить, что и когда пошло не так. Это помогло настроить отдельные тестовые серверы, чтобы сузить проблему (спасибо ДазВилкину за его предложения!)
Я решил, что ключи будут более простыми, чем ADC, но ладно, я вошел в систему (олицетворяясь) как учетная запись службы, которая локально запускает службу Cloud Run, и удалил все аргументы для создания new Storage()
, чтобы он мог получать ADC. реквизиты для входа. Когда я перехожу на страницу, которая использует SignedUrls, я получаю сообщение об ошибке «Разрешение 'iam.serviceAccounts.signBlob' отклонено на ресурсе...». У рассматриваемой учетной записи службы есть администратор хранилища и создатель токена учетной записи службы. О каком ресурсе идет речь, о ведре?
Хорошо, решена проблема с разрешением. Мне пришлось предоставить себе (или моему пользователю) роль создателя токена учетной записи службы. После этого я вошел в систему (выдал себя за другое лицо) последовательно как с учетной записью службы, на которой работает облачная служба, так и с другой, которую я использовал, через переменную env. Оба успешно сгенерировали подписанные URL-адреса локально. Так что же происходит на производстве? Нужно ли мне устанавливать специальную среду в Cloud Run, чтобы SDK хранилища работал?
Я печатаю .bucket("name").getServiceAccount()
, и это выглядит так, как и ожидалось: учетные данные, похоже, проходят, и у SA есть соответствующие разрешения. Сейчас я не получаю ошибку 500, используя учетные данные ADC в производстве, но я все еще получаю ошибку несоответствия подписи, которую получал изначально.
Я развернул образец от 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! Действительно не уверен, что здесь произошло. Сервисный аккаунт тот же.
Пожалуйста. Это минимальная копия, поэтому вы можете увидеть, что именно я сделал, чтобы заставить ее работать. Вы не сделали то же самое. Вы описываете, что сделали, но не показываете команды, поэтому оставить отзыв невозможно.
Ваш вопрос не является минимальным воспроизведением и упускает из виду важную разницу, о которой вы говорите (без доказательств), что код работает при локальном запуске, но не при запуске в Cloud Run. Итак, мы знаем, что включенный вами код (может) работать. Непонятно, почему вы не используете ADC. Вы усложняете решение и делаете его менее безопасным, используя ключи в переменных среды. Показательно, что при использовании АЦП вы получаете 500. Я думаю, вам следует вернуться к примеру как есть использовать ADC, а затем обновить Q