📡 AWS CDK 101 ⛄️ — использование конструкции API-шлюза, дроссель, квота, планы использования, api ключи

🔰 Начинающие пользователи AWS CDK, пожалуйста, ознакомьтесь с моими предыдущими статьями в этом цикле.

Если вы пропустили предыдущую статью, найдите ее по ссылкам ниже.

🔁 Оригинальный предыдущий пост 🔗 Dev Post

🔁 Репост предыдущего поста 🔗 dev на @aravindvcyber

В этой статье мы представим API-шлюз перед нашей базовой лямбда-функцией, которую мы создали в нашей последней статье, приведенной выше.

Почему API Gateway в нашем решении ❓

Поскольку наша лямбда доступна только внутри нашей среды aws, нам необходима конфигурация API Gateway, которая поможет открыть публичную конечную точку HTTP, к которой может обратиться любой пользователь в интернете с помощью HTTP-клиента.

Кроме того, API Gateway может блокировать некорректные запросы, не вызывая внутренние функции Lambda, когда проверка авторизации не проходит. Таким образом, API Gateway может сэкономить дорогостоящие затраты на вызов Lambda, а также может разгрузить проверку запросов от вашей Lambda-функции, в зависимости от требований.

Давайте начнем с редактирования файла lib/common-event-stact.ts. Здесь мы импортируем модули из aws-cdk-lib/aws-apigateway как agigw.

 // this defines an new API Gateway REST API resource backed by our "eventEntry" function.
    const eventGateway = new apigw.LambdaRestApi(this, 'EventEndpoint', {
      handler: eventEntry
    });
Вход в полноэкранный режим Выход из полноэкранного режима

Когда вы запустите cdk diff, вы можете ожидать аналогичный журнал о новых ресурсах, которые будут созданы, а также об изменениях политики IAM.

IAM Statement Changes
┌───┬─────────────────────────────────────────────────────┬────────┬───────────────────────┬─────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐
│   │ Resource                                            │ Effect │ Action                │ Principal                                           │ Condition                                           │
├───┼─────────────────────────────────────────────────────┼────────┼───────────────────────┼─────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
│ + │ ${EventEndpoint/CloudWatchRole.Arn}                 │ Allow  │ sts:AssumeRole        │ Service:apigateway.amazonaws.com                    │                                                     │
├───┼─────────────────────────────────────────────────────┼────────┼───────────────────────┼─────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
│ + │ ${EventEntryHandler.Arn}                            │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com                    │ "ArnLike": {                                        │
│   │                                                     │        │                       │                                                     │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-a │
│   │                                                     │        │                       │                                                     │ pi:${AWS::Region}:${AWS::AccountId}:${EventEndpoint │
│   │                                                     │        │                       │                                                     │ 82CBF40A}/${EventEndpoint/DeploymentStage.prod}/*/* │
│   │                                                     │        │                       │                                                     │ "                                                   │
│   │                                                     │        │                       │                                                     │ }                                                   │
│ + │ ${EventEntryHandler.Arn}                            │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com                    │ "ArnLike": {                                        │
│   │                                                     │        │                       │                                                     │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-a │
│   │                                                     │        │                       │                                                     │ pi:${AWS::Region}:${AWS::AccountId}:${EventEndpoint │
│   │                                                     │        │                       │                                                     │ 82CBF40A}/test-invoke-stage/*/*"                    │
│   │                                                     │        │                       │                                                     │ }                                                   │
│ + │ ${EventEntryHandler.Arn}                            │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com                    │ "ArnLike": {                                        │
│   │                                                     │        │                       │                                                     │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-a │
│   │                                                     │        │                       │                                                     │ pi:${AWS::Region}:${AWS::AccountId}:${EventEndpoint │
│   │                                                     │        │                       │                                                     │ 82CBF40A}/${EventEndpoint/DeploymentStage.prod}/*/" │
│   │                                                     │        │                       │                                                     │ }                                                   │
│ + │ ${EventEntryHandler.Arn}                            │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com                    │ "ArnLike": {                                        │
│   │                                                     │        │                       │                                                     │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-a │
│   │                                                     │        │                       │                                                     │ pi:${AWS::Region}:${AWS::AccountId}:${EventEndpoint │
│   │                                                     │        │                       │                                                     │ 82CBF40A}/test-invoke-stage/*/"                     │
│   │                                                     │        │                       │                                                     │ }                                                   │
└───┴─────────────────────────────────────────────────────┴────────┴───────────────────────┴─────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘
IAM Policy Changes
┌───┬─────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                        │ Managed Policy ARN                                                                      │
├───┼─────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${EventEndpoint/CloudWatchRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs │
└───┴─────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::ApiGateway::RestApi EventEndpoint EventEndpoint82CBF40A 
[+] AWS::IAM::Role EventEndpoint/CloudWatchRole EventEndpointCloudWatchRole4E252113 
[+] AWS::ApiGateway::Account EventEndpoint/Account EventEndpointAccount1A8CF478 
[+] AWS::ApiGateway::Deployment EventEndpoint/Deployment EventEndpointDeployment9E6BA6B96f1395a2ccbaf49542e68813f77a19e7 
[+] AWS::ApiGateway::Stage EventEndpoint/DeploymentStage.prod EventEndpointDeploymentStageprod2ED092D9 
[+] AWS::ApiGateway::Resource EventEndpoint/Default/{proxy+} EventEndpointproxyCBF9AFD2 
[+] AWS::Lambda::Permission EventEndpoint/Default/{proxy+}/ANY/ApiPermission.CommonEventStackEventEndpointB06FCC50.ANY..{proxy+} EventEndpointproxyANYApiPermissionCommonEventStackEventEndpointB06FCC50ANYproxy3F9D95E2 
[+] AWS::Lambda::Permission EventEndpoint/Default/{proxy+}/ANY/ApiPermission.Test.CommonEventStackEventEndpointB06FCC50.ANY..{proxy+} EventEndpointproxyANYApiPermissionTestCommonEventStackEventEndpointB06FCC50ANYproxy75C60637 
[+] AWS::ApiGateway::Method EventEndpoint/Default/{proxy+}/ANY EventEndpointproxyANY74421EDC 
[+] AWS::Lambda::Permission EventEndpoint/Default/ANY/ApiPermission.CommonEventStackEventEndpointB06FCC50.ANY.. EventEndpointANYApiPermissionCommonEventStackEventEndpointB06FCC50ANY7F8C8232 
[+] AWS::Lambda::Permission EventEndpoint/Default/ANY/ApiPermission.Test.CommonEventStackEventEndpointB06FCC50.ANY.. EventEndpointANYApiPermissionTestCommonEventStackEventEndpointB06FCC50ANYC6DC815A 
[+] AWS::ApiGateway::Method EventEndpoint/Default/ANY EventEndpointANY8620E9D3 
Вход в полноэкранный режим Выход из полноэкранного режима

Подведение итогов 💥

Чтобы подвести итог вышесказанному, давайте разберемся, что происходит в бэкенде следующим образом и будет опубликовано.

  • Создается новая IAM политика CloudWatchRole и предполагается, что она будет отправлять логи в cloudwatch из apigateway.
  • Настраивается стадия развертывания prod, с ее помощью мы можем иметь несколько стадий для преждевременного тестирования функций, не затрагивая стадию prod.
  • Разрешения ресурсов настроены с необходимыми привилегиями для любого пути Resource, по умолчанию жадный путь ресурсов {proxy+} для обычной конечной точки и тестовой конечной точки для этого rest api.
  • Разрешения ресурса настроены с необходимыми привилегиями для любого метода как для обычной конечной точки, так и для тестового варианта конечной точки для этого rest api.

Глубокое понимание вышеуказанных концепций поможет нам при дальнейшем ограничении шлюза Api в будущем для подобных конечных точек на основе rest api.

Теперь давайте выполним это развертывание и проверим результаты.

Перед этим позвольте мне сделать еще одно изменение в нашей лямбде для отображения соответствующего сообщения.

exports.receiver = async function(event:any) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Message Received in lambda: ${JSON.stringify(event.body)}n`
  };
};

Войти в полноэкранный режим Выход из полноэкранного режима

cdk deploy CommonEventStack



 ✅  CommonEventStack

✨  Deployment time: 57.21s

Outputs:
CommonEventStack.EventEndpointA893C53E = https://lwo***********uvhg.execute-api.ap-south-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:ap-south-1:575066707855:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540

✨  Total time: 68.43s
Войти в полноэкранный режим Выход из полноэкранного режима

🏃 Curl-тесты

Когда мы выполним curl для проверки этой конечной точки отдыха, давайте посмотрим, что мы получим.

curl https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/

Message Received: null

Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы успешно отправили сообщение лямбде, а также получили некоторый результат. Хотя у нас есть null, потому что мы не отправили никакого тела в нашем первоначальном запросе GET.

Теперь давайте отправим какое-нибудь сообщение через тело запроса от клиента в пост-запросе.

curl -H "Content-Type: application/json" -d '{ "message": "client message"}' -X POST https://********.execute-api.ap-south-1.amazonaws.com/prod/

Message Received in lambda: "{ "message": "client message"}"
Вход в полноэкранный режим Выход из полноэкранного режима

Мы успешно настроили шлюз agi, поэтому давайте проверим его в консоли, а также протестируем его в консоли aws.

API-шлюз в консоли AWS ⚡

Проверьте путь к конечной точке и разрешенные методы ⚡

Протестируйте POST-запрос с сообщением ⚡

Доработка Api шлюза, созданного выше ⚡

Теперь давайте уточним созданный api шлюз, чтобы он был доступен только для метода POST и только для определенного пути к ресурсу.

Справочная документация по API CDK 🐼

Прежде чем мы это сделаем, мы должны воспользоваться справочной документацией по cdk api. Отсюда мы можем изучить эту справочную документацию, чтобы выбрать необходимые параметры и опции для каждого построения ресурса cdk stack.

Здесь, если вы помните, мы импортировали aws-cdk-lib/aws-apigateway, нам нужно научиться использовать библиотеку конструкции по адресу https://docs.aws.amazon.com/cdk/api/v2.

В частности, мы должны перейти по адресу https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway-readme.html. Здесь мы найдем необходимую документацию по конструкции apigateway, как использовать ее свойства и методы, с подходящими примерами.

Укажите правильный путь к ресурсу и метод 🔦.

Сначала давайте удалим это жадное определение пути к ресурсам прокси.

const eventGateway = new apigw.LambdaRestApi(this, 'EventEndpoint', {
      handler: eventEntry,
      proxy: false
    });
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь атрибут proxy по умолчанию удаляет жадное определение пути к ресурсам.

Теперь давайте объявим доступный путь к ресурсам, как показано в примере ниже.

 const eventHandler: apigw.LambdaIntegration = new apigw.LambdaIntegration(eventEntry);
    const event = eventGateway.root.addResource('event');

    const eventMethod: apigw.Method = event.addMethod('POST', eventHandler, {  
       apiKeyRequired: false
    });
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы успешно раскрываем и разрешаем только конкретный путь к ресурсу и метод.

Когда мы обращаемся к базовому пути ресурса с запрещенными методами 🔦.

В то время как мы можем успешно обращаться к нашему специфическому api, обратите внимание на разницу в полном пути с разрешенным методом.

План использования и API-ключи 🔔

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

План использует API-ключи для идентификации API-клиентов и соответственно измеряет доступ к связанным API-этапам для каждого ключа. Планы использования также позволяют настраивать ограничения дросселирования и квоты, которые накладываются на отдельные API-ключи клиентов.

В следующем примере показано, как создать и связать план использования и ключ API:

const eventMethod: apigw.Method = event.addMethod('POST', eventHandler, {  
       apiKeyRequired: true,
    });
Вход в полноэкранный режим Выход из полноэкранного режима

Для того чтобы использовать ключ API, необходимо создать план использования, подобный приведенному ниже примеру, а затем мы можем связать его с только что созданным ключом.


const plan = eventGateway.addUsagePlan('UsagePlan', {
        name: 'Workshop',
        description: 'For Workshop',
        throttle: {
          burstLimit: 5,
          rateLimit: 10,
        },
        quota: {
          limit: 100,
          period: apigw.Period.DAY
        }
    });

    const normalUserKey = eventGateway.addApiKey('ApiKey',{
        apiKeyName: 'av-api-key',
        value: 'av-api-key-value-********',
    });
    plan.addApiKey(normalUserKey);
Вход в полноэкранный режим Выход из полноэкранного режима

Тонкая настройка плана использования на уровне стадии развертывания 🌻

Тонкая настройка приведенного выше плана использования на различных этапах развертывания для конкретного метода дополнительно и включение ограничений дросселирования на уровне метода помогает лучше контролировать лимиты потребления api и более эффективно использовать ресурсы с несколькими клиентами на основе контракта.

 plan.addApiStage({
      stage: eventGateway.deploymentStage,
      throttle: [
        {
          method: eventMethod,
          throttle: {
            rateLimit: 3,
            burstLimit: 2
          }
        }
      ]
    });
Вход в полноэкранный режим Выход из полноэкранного режима

Дросселирование api с помощью лимитов скорости и серий 🐇

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

  • Предел разрыва определяет количество запросов, которые ваш API может обрабатывать одновременно.
  • Ограничение скорости определяет количество разрешенных запросов в секунду.
 throttle: {
            rateLimit: 3,
            burstLimit: 2
          }
Вход в полноэкранный режим Выход из полноэкранного режима

Ограничение квоты 🐡

Лимит квоты помогает определить общее количество клиентских запросов, полученных шлюзом api, до превышения лимита шлюза.

Обычно aws предоставляет определенную фиксированную квоту для каждого сервиса в регионе, этим можно эффективно управлять, резервируя часть для каждого плана использования по желанию. Это также можно проверить, чтобы ограничить потребление api внешними пользователями в зависимости от уровня их обслуживания.

 quota: {
      limit: 5,
      period: apigw.Period.DAY
      }
Вход в полноэкранный режим Выход из полноэкранного режима

Как только квота, выделенная для пользователя api ключа, превышена, отправляются ответы об ошибке.

Ограниченный по тарифу API-ключ 🐠

В некоторых сценариях, когда вам нужно создать один api ключ и настроить для него ограничение скорости, вы можете использовать RateLimitedApiKey. Эта конструкция позволяет указать свойства ограничения скорости, которые должны применяться только к создаваемому api ключу. К созданному API-ключу применяются указанные ограничения скорости, такие как квоты и дроссели, даже без привязки к плану использования.

const rateLimitedKey = new apigw.RateLimitedApiKey(this, 'rate-limited-api-key', {
       apiKeyName: 'av-rate-limited-api-key',
        value: 'av-api-key-value-dreatenbwebrwiukjwnrn',
    customerId: 'external-client',
    resources: [eventGateway],
    quota: {
      limit: 5,
      period: apigw.Period.DAY
      },
      throttle: {
        rateLimit: 2,
        burstLimit: 1
      }
    });
    plan.addApiKey(rateLimitedKey);
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем проверить то же самое в консоли api, где мы можем обнаружить, что для вновь созданного api ключа создан новый план. Для дальнейшего тонкого контроля для отдельных пользователей, не входящих в другие планы использования.

Импорт существующих ключей и планов использования 🐝.

Кроме того, мы можем импортировать существующие ключи и планы использования в ресурсы нашего стека, следуя приведенным ниже примерам.

Планы использования могут быть импортированы в приложение CDK с помощью его идентификатора.

const importedUsagePlan = apigateway.UsagePlan.fromUsagePlanId(this, 'imported-usage-plan', '<usage-plan-key-id>');
Вход в полноэкранный режим Выйти из полноэкранного режима

API-ключи также могут быть импортированы в приложение CDK, используя его id.

const importedKey = apigateway.ApiKey.fromApiKeyId(this, 'imported-key', '<api-key-id>');
Войти в полноэкранный режим Выход из полноэкранного режима

Мы добавим больше соединений к этому api шлюзу и лямбде и сделаем его более удобным для использования в следующих статьях, оставайтесь подписанными.

⏭ У нас есть следующая статья по бессерверным технологиям, ознакомьтесь с ней

aws-cdk-101-building-constructs-and-simple-counter-store-in-dynamodb

🎉 Спасибо за поддержку! 🙏

Будет очень здорово, если вы захотите ☕ Купить мне кофе, чтобы поддержать мои усилия.

🔁 Оригинальный пост 🔗 Dev Post

🔁 Reposted at 🔗 dev to @aravindvcyber

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *