403 Запрещено при попытке загрузить PDF как блоб в корзину S3 с помощью PUT - PullRequest
0 голосов
/ 21 июня 2019

Что я пытаюсь сделать

Загрузите файл PDF из браузера клиента, не раскрывая учетные данные или что-либо сомнительное. Основываясь на этом , я думал, что это можно сделать, но мне кажется, это не сработает.

Помещение:

  • вы запрашиваете предварительно подписанный URL-адрес в S3 Bucket на основе набора параметров, предоставленных функции, являющейся частью JavaScript AWS SDK

  • вы передаете этот URL-адрес во внешний интерфейс, который может использовать его для размещения файла в S3 Bucket без необходимости использования каких-либо учетных данных или аутентификации на внешнем интерфейсе.

ПОЛУЧИТЕ предварительно подписанный URL от S3

Эта часть проста и у меня работает. Я просто запрашиваю URL у S3 с этим маленьким слепком JS:

const s3Params = {
    Bucket: uploadBucket,
    Key: `${fileId}.pdf`,
    ContentType: 'application/pdf',
    Expires: 60,
    ACL: 'public-read',
}

let uploadUrl = s3.getSignedUrl('putObject', s3Params);

Используйте предварительно подписанный URL для загрузки файла на S3

Эта часть не работает, и я не могу понять, почему. Этот небольшой фрагмент кода в основном отправляет большой объем данных на предварительно подписанный URL-адрес корзины S3 с помощью запроса PUT.

const result = await fetch(response.data.uploadURL, {
        method: 'put',
        body: blobData,
});

PUT или POST?

Я обнаружил, что использование любых запросов POST приводит к 400 Bad Request, поэтому PUT это так.

Что я смотрел

Content-Type (в моем случае это будет application / pdf, поэтому blobData.type) - они совпадают между внутренним и внешним интерфейсом.

x-amz-acl header

Тип контента

Аналогичный вариант использования . Глядя на это, кажется, что в запросе PUT не нужно указывать заголовки, а сам подписанный URL-адрес - это все, что необходимо для загрузки файла.

Что-то странное , что я не понимаю. Похоже, мне может понадобиться передать длину и тип файла в getSignedUrl вызов S3.

Раскрытие моего ведра для публики (без буэно)

Загрузить файл на s3 с POST

Внешний интерфейс (fileUploader.js, использующий Vue):

...

uploadFile: async function(e) {
      /* receives file from simple input element -> this.file */
      // get signed URL
      const response = await axios({
        method: 'get',
        url: API_GATEWAY_URL
      });

      console.log('upload file response:', response);

      let binary = atob(this.file.split(',')[1]);
      let array = [];

      for (let i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
      }

      let blobData = new Blob([new Uint8Array(array)], {type: 'application/pdf'});
      console.log('uploading to:', response.data.uploadURL);
      console.log('blob type sanity check:', blobData.type);

      const result = await fetch(response.data.uploadURL, {
        method: 'put',
        headers: {
          'Access-Control-Allow-Methods': '*',
          'Access-Control-Allow-Origin': '*',
          'x-amz-acl': 'public-read',
          'Content-Type': blobData.type
        },
        body: blobData,
      });

      console.log('PUT result:', result);

      this.uploadUrl = response.data.uploadURL.split('?')[0];
    }

Backend (fileReceiver.js):

'use strict';

const uuidv4 = require('uuid/v4');
const aws = require('aws-sdk');
const s3 = new aws.S3();

const uploadBucket = 'the-chumiest-bucket';
const fileKeyPrefix = 'path/to/where/the/file/should/live/';

const getUploadUrl = async () => {
  const fileId = uuidv4();
  const s3Params = {
    Bucket: uploadBucket,
    Key: `${fileId}.pdf`,
    ContentType: 'application/pdf',
    Expires: 60,
    ACL: 'public-read',
  }

  return new Promise((resolve, reject) => {
    let uploadUrl = s3.getSignedUrl('putObject', s3Params);
    resolve({
      'statusCode': 200,
      'isBase64Encoded': false,
      'headers': { 
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': '*',
        'Access-Control-Allow-Credentials': true,
      },
      'body': JSON.stringify({
        'uploadURL': uploadUrl,
        'filename': `${fileId}.pdf`
      })
    });
  });
};

exports.handler = async (event, context) => {
  console.log('event:', event);
  const result = await getUploadUrl();
  console.log('result:', result);

  return result;
}

Конфигурация без сервера (serverless.yml):

service: ocr-space-service

provider:
  name: aws
  region: ca-central-1
  stage: ${opt:stage, 'dev'}
  timeout: 20

plugins:
  - serverless-plugin-existing-s3
  - serverless-step-functions
  - serverless-pseudo-parameters
  - serverless-plugin-include-dependencies

layers:
  spaceOcrLayer:
    package:
      artifact: spaceOcrLayer.zip
    allowedAccounts:
      - "*"

functions:
  fileReceiver:
    handler: src/node/fileReceiver.handler
    events:
      - http:
          path: /doc-parser/get-url
          method: get
          cors: true
  startStateMachine:
    handler: src/start_state_machine.lambda_handler
    role: 
    runtime: python3.7
    layers:
      - {Ref: SpaceOcrLayerLambdaLayer}
    events:
      - existingS3:
          bucket: ingenio-documents
          events:
            - s3:ObjectCreated:*
          rules:
            - prefix: 
            - suffix: .pdf
  startOcrSpaceProcess:
    handler: src/start_ocr_space.lambda_handler
    role: 
    runtime: python3.7
    layers:
      - {Ref: SpaceOcrLayerLambdaLayer}
  parseOcrSpaceOutput:
    handler: src/parse_ocr_space_output.lambda_handler
    role: 
    runtime: python3.7
    layers:
      - {Ref: SpaceOcrLayerLambdaLayer}
  renamePdf:
    handler: src/rename_pdf.lambda_handler
    role: 
    runtime: python3.7
    layers:
      - {Ref: SpaceOcrLayerLambdaLayer}
  parseCorpSearchOutput:
    handler: src/node/pdfParser.handler
    role: 
    runtime: nodejs10.x
  saveFileToProcessed:
    handler: src/node/saveFileToProcessed.handler
    role: 
    runtime: nodejs10.x

stepFunctions:
  stateMachines:
    ocrSpaceStepFunc:
      name: ocrSpaceStepFunc
      definition:
        StartAt: StartOcrSpaceProcess
        States:
          StartOcrSpaceProcess:
            Type: Task
            Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-startOcrSpaceProcess"
            Next: IsDocCorpSearchChoice
            Catch:
            - ErrorEquals: ["HandledError"]
              Next: HandledErrorFallback
          IsDocCorpSearchChoice:
            Type: Choice
            Choices:
              - Variable: $.docIsCorpSearch
                NumericEquals: 1
                Next: ParseCorpSearchOutput
              - Variable: $.docIsCorpSearch
                NumericEquals: 0
                Next: ParseOcrSpaceOutput
          ParseCorpSearchOutput:
            Type: Task
            Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-parseCorpSearchOutput"
            Next: SaveFileToProcessed
            Catch:
              - ErrorEquals: ["SqsMessageError"]
                Next: CorpSearchSqsErrorFallback
              - ErrorEquals: ["DownloadFileError"]
                Next: CorpSearchDownloadFileErrorFallback
              - ErrorEquals: ["HandledError"]
                Next: HandledNodeErrorFallback
          SaveFileToProcessed:
            Type: Task
            Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-saveFileToProcessed"
            End: true
          ParseOcrSpaceOutput:
            Type: Task
            Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-parseOcrSpaceOutput"
            Next: RenamePdf
            Catch:
            - ErrorEquals: ["HandledError"]
              Next: HandledErrorFallback
          RenamePdf:
            Type: Task
            Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:#{AWS::StackName}-renamePdf"
            End: true
            Catch:
              - ErrorEquals: ["HandledError"]
                Next: HandledErrorFallback
              - ErrorEquals: ["AccessDeniedException"]
                Next: AccessDeniedFallback
          AccessDeniedFallback:
            Type: Fail
            Cause: "Access was denied for copying an S3 object"
          HandledErrorFallback:
            Type: Fail
            Cause: "HandledError occurred"
          CorpSearchSqsErrorFallback:
            Type: Fail
            Cause: "SQS Message send action resulted in error"
          CorpSearchDownloadFileErrorFallback:
            Type: Fail
            Cause: "Downloading file from S3 resulted in error"
          HandledNodeErrorFallback:
            Type: Fail
            Cause: "HandledError occurred"

Ошибка:

403 Запрещено

PUT Response

Response {тип: "cors", URL: "https://{bucket -name} .s3. {Region-id} .amazonaw… nedHeaders = хост% 3Bx-amz-acl & x-amz-acl = public-read", перенаправлено: false, статус: 403, ok: false,…} тело: (...) bodyUsed: false Заголовки: Заголовки {} хорошо: ложь перенаправлено: нет статус: 403 statusText: «Запрещено» Тип: "Корс" url: "https://{bucket -name} .s3. {region-id} .amazonaws.com / actionID.pdf? Content-Type = application% 2Fpdf & X-Amz-Algorithm = SHA256 & X-Amz-Credential = CREDZ- & X-Amz- Date = 20190621T192558Z & X-Amz-Истекает = 900 & X-Amz-Security-токен = {маркер} и X-Amz-SignedHeaders = хост% 3BX-АМЗ-х и ACL-АМЗ-= ACL общественного чтения» proto : Response

Что я думаю

Я думаю, что параметры, предоставляемые для вызова getSignedUrl с использованием AWS S3 SDK, не верны, хотя они соответствуют структуре, предложенной в документации AWS (объяснение здесь ). Кроме того, я действительно не понимаю, почему мой запрос отклонен. Я даже пытался полностью раскрыть свой Bucket публике, и он все еще не работал.

Редактировать

# 1:

После прочтения этого я попытался структурировать свой запрос PUT следующим образом:

      let authFromGet = response.config.headers.Authorization;      

      const putHeaders = {
        'Authorization': authFromGet,
        'Content-Type': blobData,
        'Expect': '100-continue',
      };

      ...

      const result = await fetch(response.data.uploadURL, {
        method: 'put',
        headers: putHeaders,
        body: blobData,
      });

это привело к 400 Bad Request вместо 403; разные, но все же неправильно. Очевидно, что указывать заголовки в запросе неправильно.

1 Ответ

0 голосов
/ 05 июля 2019

Копаясь в этом, это потому, что вы пытаетесь загрузить объект с общедоступным ACL в корзину, которая не допускает общедоступные объекты.

  1. При необходимости удалите общедоступный оператор ACL или...

  2. Убедитесь, что для сегмента установлено значение

    • Доступно для общего просмотра или
    • Убедитесь, что никакие другие политики не блокируют публичный доступ (например, Doу вас есть политика учетной записи, запрещающая доступ к общедоступным объектам, но пытающаяся загрузить объект с помощью общего ACL?)

По сути, вы не можете загружать объекты с открытым ACL в корзину, гдеэтому препятствуют некоторые ограничения - вы получите ошибку 403, которую вы описали.НТН.

...