跳转到主要内容
Chinese, Simplified

本文由Heitor Lessa提供,AWS专业解决方案架构师- Serverless

在本博客的第1部分中,我们描述了如何使用AWS WAF保护Amazon API Gateway提供的API。在本博客中,我们将展示如何在Amazon CloudFront分发和API网关之间使用API键,以确保除了已经在API网关中设置的首选授权(AuthZ)机制之外,还可以访问API网关中的API。有关API网关中的AuthZ机制的更多信息,请参见使用Amazon Cognito联合身份、Amazon Cognito用户池和Amazon API网关的安全API访问。

我们还扩展了以前用于自动创建此解决方案的以下必要资源的AWS CloudFormation堆栈:

  1. API网关使用计划——管理专用于CloudFront的API密钥,以及必要时的节流和计量使用
  2. AWS Lambda函数——更新AWS CloudFormation堆栈参数时间戳并触发API键旋转
  3. Amazon CloudWatch事件调度作业——在给定的调度中触发Lambda函数

以下是使用API密钥的替代解决方案,具体取决于您的安全需求:

在CloudFront中使用随机生成的HTTP秘密头,并通过API网关请求验证进行验证

使用Lambda@Edge对传入请求进行签名,并使用API网关Lambda授权器进行验证

需求

要继续,您需要完全的权限来通过AWS CloudFormation创建、更新和删除API网关、CloudFront、Lambda和CloudWatch事件。

扩展现有AWSCloudFormation 堆栈

首先,点击这里下载完整的模板。然后按照以下步骤更新现有的AWS云形成堆栈:

  1. 转到AWS管理控制台并打开AWS CloudFormation控制台。
  2. 选择您在第1部分中创建的堆栈,右键单击它,然后选择Update stack。
  3. 对于选项2,选择“选择文件”并选择下载的模板。
  4. 填写所需的参数,如下图所示。

以下是关于这些参数的更多信息:

  1. 发送流量的API网关——除了没有URL模式(https://)之外,我们使用与第1部分相同的API网关URL: cxm45444t9a.execute-api.us- east2.amazonaws.com/prod
  2. 旋转API键——我们定义了Daily并使用2018-04-03作为时间戳值来追加API键名

继续使用AWS CloudFormation控制台完成操作。更新堆栈可能需要几分钟,因为CloudFront需要时间在所有存在点上传播更改。

 

在示例宠物商店API中启用API键

当堆栈在后台完成时,让我们在CloudFront将发送流量到的API中启用API键。

  1. 转到AWS管理控制台并打开API网关控制台。
  2. 选择您在第1部分中创建的API并选择Resources。
  3. 在/pets下,选择GET,然后选择Method Request。
  4. 对于所需的API键,选择下拉菜单并选择true。
  5. 要保存此更改,请选择突出显示的复选标记,如下图所示。

接下来,我们需要部署这些更改,以便在没有API密钥时发送给/pets的请求失败。

  • 选择Actions并选择Deploy API。

  • 选择Deployment stage下拉菜单,并选择在第1部分中创建的舞台。
  • 添加一个部署描述,例如“在/pets下需要API键”,然后选择Deploy。

当部署成功时,您将被重定向到API Gateway Stage页面。在那里,您可以使用Invoke URL来测试以下请求是否由于没有API密钥而失败。

这一失败是预料之中的,并证明我们部署的更改正在工作。接下来,让我们尝试访问相同的API,但这次是通过CloudFront分发。

  1. 从AWS管理控制台打开AWS Cloudformation控制台。
  2. 选择您在第1部分中创建的堆栈,并在左下角选择output。
  3. 在CFDistribution行上,复制URL。在粘贴新浏览器选项卡或窗口之前,请在其中添加' /pets '。

与我们第一次尝试不使用API键相反,我们从PetStore API接收JSON响应。这是因为CloudFront在将请求转发到PetStore API之前注入了一个API密钥。下图演示了这两种测试:

  1. 通过CloudFront访问API时请求成功
  2. 通过API的调用URL直接访问API时,请求失败

这是CloudFront和API网关之间的一个秘密,它可以是任何约定的随机秘密,可以像API密钥一样旋转。但是,重要的是要知道API key是一个跟踪或度量API使用者使用情况的特性。它不是一种安全的授权机制,因此只能与API网关授权器一起使用。

 

旋转的API密钥

API键会根据更新AWS CloudFormation堆栈时选择的时间表(例如,每日或每月)自动旋转。这就不需要您进行维护或干预。在本节中,我们将解释这个过程是如何工作的,以及如果您想手动触发API键旋转,您可以做些什么。

除了第1部分之外,我们下载并用于更新堆栈的AWS CloudFormation模板还执行了以下操作。

引入一个附加到API密钥名的时间戳参数

Parameters:
  Timestamp:
    Type: String
    Description: Fill in this format <Year>-<Month>-<Day>
    Default: 2018-04-02

创建一个API网关密钥,API网关使用计划,将新密钥与作为参数给出的API网关关联,并配置CloudFront分发,以便在将流量转发到API网关时发送一个自定义头

CFDistribution:
  Type: AWS::CloudFront::Distribution
  Properties:
    DistributionConfig:
      Logging:
        IncludeCookies: 'false'
        Bucket: !Sub ${S3BucketAccessLogs}.s3.amazonaws.com
        Prefix: cloudfront-logs
      Enabled: 'true'
      Comment: API Gateway Regional Endpoint Blog post
      Origins:
        -
          Id: APIGWRegional
          DomainName: !Select [0, !Split ['/', !Ref ApiURL]]
          CustomOriginConfig:
            HTTPPort: 443
            OriginProtocolPolicy: https-only
          OriginCustomHeaders:
            - 
              HeaderName: x-api-key
              HeaderValue: !Ref ApiKey
              ...

ApiUsagePlan:
  Type: AWS::ApiGateway::UsagePlan
  Properties:
    Description: CloudFront usage only
    UsagePlanName: CloudFront_only
    ApiStages:
      - 
        ApiId: !Select [0, !Split ['.', !Ref ApiURL]]
        Stage: !Select [1, !Split ['/', !Ref ApiURL]]

ApiKey: 
  Type: "AWS::ApiGateway::ApiKey"
  Properties: 
    Name: !Sub "CloudFront-${Timestamp}"
    Description: !Sub "CloudFormation API Key ${Timestamp}"
    Enabled: true

ApiKeyUsagePlan:
  Type: "AWS::ApiGateway::UsagePlanKey"
  Properties:
    KeyId: !Ref ApiKey
    KeyType: API_KEY
    UsagePlanId: !Ref ApiUsagePlan

正如在ApiKey资源中所示,我们将给定的时间戳附加到Name中,并在API网关使用计划密钥资源中使用它。这意味着,无论何时时间戳参数发生变化,AWS CloudFormation都会触发资源替换,并更新依赖于该API键的每个资源。在本例中,这包括AWS CloudFront配置和API网关使用计划。

但是在这个例子中,您在本博客开头所选择的轮换计划意味着什么呢?

创建一个计划好的活动来触发给定计划中的Lambda函数

Parameters:
...
  ApiKeyRotationSchedule: 
    Description: Schedule to rotate API Keys e.g. Daily, Monthly, Bimonthly basis
    Type: String
    Default: Daily
    AllowedValues:
      - Daily
      - Fortnightly
      - Monthly
      - Bimonthly
      - Quarterly
    ConstraintDescription: Must be any of the available options

Mappings: 

  ScheduleMap: 
    CloudwatchEvents: 
      Daily: "rate(1 day)"
      Fortnightly: "rate(14 days)"
      Monthly: "rate(30 days)"
      Bimonthly: "rate(60 days)"
      Quarterly: "rate(90 days)"

Resources:
...
  RotateApiKeysScheduledJob: 
    Type: "AWS::Events::Rule"
    Properties: 
      Description: "ScheduledRule"
      ScheduleExpression: !FindInMap [ScheduleMap, CloudwatchEvents, !Ref ApiKeyRotationSchedule]
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt RotateApiKeysFunction.Arn
          Id: "RotateApiKeys"

资源RotateApiKeysScheduledJob显示,在更新AWS CloudFormation堆栈时,通过下拉菜单选择的调度实际上转换为CloudWatch事件规则。这进而触发在相同模板中定义的Lambda函数。

 

RotateApiKeysFunction:
      Type: "AWS::Lambda::Function"
      Properties:
        Handler: "index.lambda_handler"
        Role: !GetAtt RotateApiKeysFunctionRole.Arn
        Runtime: python3.6
        Environment:
          Variables:
            StackName: !Ref "AWS::StackName"
        Code:
          ZipFile: !Sub |
            import datetime
            import os

            import boto3
            from botocore.exceptions import ClientError

            session = boto3.Session()
            cfn = session.client('cloudformation')
            
            timestamp = datetime.date.today()            
            params = {
                'StackName': os.getenv('StackName'),
                'UsePreviousTemplate': True,
                'Capabilities': ["CAPABILITY_IAM"],
                'Parameters': [
                    {
                      'ParameterKey': 'ApiURL',
                      'UsePreviousValue': True
                    },
                    {
                      'ParameterKey': 'ApiKeyRotationSchedule',
                      'UsePreviousValue': True
                    },
                    {
                      'ParameterKey': 'Timestamp',
                      'ParameterValue': str(timestamp)
                    },
                ],                
            }

            def lambda_handler(event, context):
              """ Updates CloudFormation Stack with a new timestamp and returns CloudFormation response"""
              try:
                  response = cfn.update_stack(**params)
              except ClientError as err:
                  if "No updates are to be performed" in err.response['Error']['Message']:
                      return {"message": err.response['Error']['Message']}
                  else:
                      raise Exception("An error happened while updating the stack: {}".format(err))          
  
              return response

 

这个Lambda函数所做的一切就是通过API触发AWS CloudFormation堆栈更新(与您通过控制台所做的完全相同,但是以编程方式进行的),并更新时间戳参数。因此,它会旋转API键和CloudFront分发配置。

这为您提供了足够的灵活性,可以在任何时候更改API key rotation schedule,而无需维护或编写任何代码。您还可以手动更新堆栈,并通过更新AWS CloudFormation堆栈的时间戳参数来旋转键。

下一个步骤

我们希望你觉得这个博客的信息有帮助。您可以使用它来了解如何创建一种机制,只允许从CloudFront到API网关的通信,并避免绕过第1部分设置的AWS WAF规则。

关于这个解决方案,请记住以下几点:

  • 它假设您已经拥有一个由API网关管理的强大AuthZ机制来控制对API的访问。
  • 在此解决方案中创建的API网关使用计划和其他资源只适用于在相同帐户中创建的API (ApiUrl参数)。
  • 如果您已经使用API键来跟踪API的使用情况,请考虑使用以下任何一种解决方案作为替代:
    • 在CloudFront源配置中使用随机HTTP头值,并使用API网关请求模型验证来验证它,而不是单独使用API键。
    • 结合Lambda@Edge和API网关自定义授权器,使用只有二者知道的共享秘密对传入的请求进行签名和验证。这是一种更先进的技术。

原文:https://aws.amazon.com/cn/blogs/compute/protecting-your-api-using-amazon-api-gateway-and-aws-waf-part-2/

本文:http://pub.intelligentx.net/protecting-your-api-using-amazon-api-gateway-and-aws-waf-part-2

讨论:请加入知识星球或者小红圈【首席架构师圈】

Article
知识星球
 
微信公众号
 
视频号