Отказ от необходимости включения заголовка x-amz-cf-id в аутентификацию S3 в облачной среде

У меня не совсем ортодоксальная настройка CF->S3. Соответствующие компоненты здесь:

  1. Распределение Cloudfront с происхождением s3.ap-southeast-2.amazonaws.com

  2. Функция Lambda@Edge (Исходный запрос), которая добавляет строку запроса авторизации S3 (версия 2) (Подписано с использованием политики S3, используемой функцией).

Запрос, возвращенный из Lambda, полностью верен. Если я регистрирую URI, хост и строку запроса, я получаю файл, который запрашиваю. Однако, если я обращаюсь к нему через ссылку Cloudfront напрямую, запрос не выполняется, поскольку он больше не использует AWSAccessKeyID, вместо этого он выбирает использовать x-amz-cf-id (но использует ту же подпись, Amz-Security-Token и т. д.). ИСПРАВЛЕНИЕ: оно не может заменить, но может потребоваться в дополнение к.

Я знаю, что это так, потому что я вернул оба StringToSign и SignatureProvided, Они оба соответствуют лямбда-ответу, за исключением AWSAccessKeyID, который был заменен на x-amz-cf-id,

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

4 ответа

Решение

Так что похоже на аутентификацию V2 или V4, x-amz-cf-id заголовок, который добавляется к запросу источника и недоступен функцией запроса источника Lambda@Edge, должен быть включен в строку аутентификации. Это невозможно.

Простое решение состоит в том, чтобы использовать встроенную интеграцию S3 в Cloudflare, использовать функцию запроса происхождения Lambda@Edge, которая переключает ведро, если, как и я, это ваша желаемая цель. Для каждого сегмента, который вы хотите использовать, добавьте следующую политику, чтобы ваш дистрибутив CF имел доступ к объектам в сегменте.

{ "Version": "2008-10-17", "Id": "PolicyForCloudFrontPrivateContent", "Statement": [ { "Sid": "1", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <CloudfrontID>" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::<bucket-name>/*" } ] } CloudfrontID ссылается на идентификатор в Origin Access Identity, а не на Amazon S3 Canonical ID.

У меня была аналогичная задача вернуть подписанный S3 URL-адрес из запроса источника CloudFront [email protected].[защищенный электронной почтой][email protected] Вот что я нашел:

Если в имени вашей корзины S3 нет точек, вы можете использовать происхождение S3 в CloudFront, использовать доменное имя в форме <bucket_name>.s3.<region>.amazonaws.com и сгенерировать подписанный URL-адрес, например, через getSignedUrl из @ aws-sdk/s3-request-presigner. CloudFront должен быть настроен для передачи URL-запроса в источник. В этом случае не предоставляйте CloudFront доступ к корзине S3 : предварительно заданный URL-адрес предоставит доступ к корзине.

Однако, если в имени вашей корзины есть точки, URL-адрес со знаком, созданный функцией, будет иметь URL -адрес в стиле пути, и вам потребуется использовать пользовательский источник CloudFront с доменом s3.<region>.amazonaws.com. При использовании пользовательского источника CloudFront добавляет заголовок «x-amz-cf-id» в запрос к источнику. Довольно неудобно, что значение заголовка должно быть подписано . Однако, если вы не меняете исходный домен в возвращаемом значении , CloudFront, похоже, использует то же значение для заголовка «x-amz-cf-id», которое передается в событие лямбда вevent.Records[0].cf.config.requestIdполе. Затем вы можете сгенерировать подписанный S3 URL-адрес со значением заголовка. С помощью AWS JavaScript SDK v3 это можно сделать с помощью S3Client.middlewareStack.add.

Вот пример JavaScript , создающий подписанный S3 URL-адрес с заголовком «x-amz-cf-id»:

      const {S3Client, GetObjectCommand} = require("@aws-sdk/client-s3");
const {getSignedUrl} = require("@aws-sdk/s3-request-presigner");

exports.handler = async function handler(event, context) {
    console.log('Request: ', JSON.stringify(event));

    let bucketName = 'XXX';
    let fileName = 'XXX';
    let bucketRegion = 'XXX';
    // Pre-requisite: this Lambda@Edge function has 's3:GetObject' permission for bucket ${bucketName}, otherwise you will get AccessDenied

    const command = new GetObjectCommand({
                                             Bucket: bucketName, Key: fileName,
                                         });

    const s3Client = new S3Client({region: bucketRegion});
    s3Client.middlewareStack.add((next, context) => async (args) => {
        args.request.headers["x-amz-cf-id"] = event.Records[0].cf.config.requestId;
        return await next(args);
    }, {
                                     step: "build", name: "addXAmzCfIdHeaderMiddleware",
                                 });

    let signedS3Url = await getSignedUrl(s3Client, command, {
        signableHeaders: new Set(["x-amz-cf-id"]), unhoistableHeaders: new Set(["x-amz-cf-id"])
    });


    let parsedUrl = new URL(signedS3Url);

    const request = event.Records[0].cf.request;
    if (!request.origin.custom || request.origin.custom.domainName != parsedUrl.hostname) {
        return {
            status: '500',
            body: `CloudFront should use custom origin configured to the matching domain '${parsedUrl.hostname}'.`,
            headers: {
                'content-type': [{key: 'Content-Type', value: 'text/plain; charset=UTF-8',}]
            }
        };
    }
    request.querystring = parsedUrl.search.substring(1);  //drop '?'
    request.uri = parsedUrl.pathname;

    console.log('Response: ', JSON.stringify(request));
    return request;
}

Я верю AWSAccessKeyID => x-amz-cf-id Замена является результатом двух механизмов:

Во-первых, вам необходимо настроить CloudFront для пересылки параметров запроса источнику. Без этого будут удалены все параметры. Если вы используете подписанные URL-адреса S3, не забудьте также кэшировать на основе всех параметров, иначе вы останетесь без какого-либо контроля доступа.

Во-вторых, CloudFront подключает x-amz-cf-idк запросам , которые не отправляются в источник S3. Вы можете дважды проверить в консоли CloudFront тип источника и убедиться, что он указан как S3. У меня есть сообщение в блоге, в котором это подробно описано.

Но добавление подписи S3 ко всем запросам с помощью Lambda@Edge лишает смысла. Если вы хотите сохранить сегмент приватным и разрешить доступ к нему только CloudFront, используйте Origin Access Identity, это как раз для случая использования.

X-amz-cf-id - это зарезервированный заголовок CF, который может быть получен событием как event['Records'][0]['cf']['config']['requestId']. Вам не нужно рассчитывать аутентификацию V4 с X-amz-cf-id.

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