{"value":"#### **背景**\n\n**Amazon CloudFront** 是一个全球性的内容分发网络 (CDN),您可以借助 CloudFront 以低延迟和高可用性向查看者或者最终用户分发内容。通常来讲,Amazon CloudFront 的客户都拥有**多个 CloudFront Distribution**,每个 Distribution 都包含了一组待加速的内容以及相关的配置信息,例如源站、加速域名、缓存策略、证书、日志、访问控制等。\n\n以下主题说明了一些有关 **CloudFront Distribution 的基础知识**,并提供了有关可选择用来配置 Distribution 以满足业务需求的设置的信息。在 CloudFront 自动化操作的过程中,一个常见的任务是**创建多个 Distribution**,这些 Distribution 的配置在同一类型作业下配置参数完全相同,例如动态加速场景下缓存 TTL 均设置为0。在该场景下,客户往往需要一个克隆已存在的 Distribution 的配置项,并希望通过标准 Restful API 接口调用以实现借助程序快速创建多个域名 Distribution 的目的。而目前亚马逊云科技支持的新建 Distribution 的 API :CreateDistribution,需要提供完整的配置参数信息,暂时不支持复制或者克隆 Distribution 的功能。\n\n本文介绍的方案是利用 **Amazon API Gateway 和 Amazon Lambda**,后端基于**Amazon SDK for Python (Boto3)**,实现基于一个参考 Distribution的配置信息,仅修改域名与源站等变更项,来复制一个新 Distribution 的 Restful API。\n\n### **解决方案简介**\n**CloudFront Distribution** 复制/克隆功能的解决方案基本实现思路如下:\n\n1)复制已存在有一个参考 Distribution 的配置信息;\n2)改造可变项,例如新 Distribution 的加速域名 CNAME 和源站的域名,组成新的完整配置信息;\n3)执行新建 Distribution 的操作。\n\n在此过程中,会调用 **Amazon Certificate Manager** 的相关 API 查找到新 CNAME 所对应的 ACM 证书的 ARN,从而完成加速域名的证书关联操作。其中,查询证书 ARN 的过程对用户是透明的。\n\n#### **解决方案的详细实现流程如下:**\n\n1)**获取参考域名的配置信息做为基准配置(**函数:get_reference_config)\n调用**Amazon Boto3 API get_distribution_config**,输入 distribution id,以 Json 形式获取 distribution 的配置信息 DistributionConfig;\n\n2)**获取账号下 ACM 证书与域名的对应列表**(函数get_certificate_mapping)\n调用Amazon Boto3 API list_certificates,输入 CertificateStatuses=’ISSUED’,查询账号下 ACM 中的已签发的证书与域名的对应列表;\n\n3)**获取该 CNAME 对应的 ACM 证书**(函数get_certificate_arn)\n从步骤3中查出的列表里取得源站 origin 所对应的 ACM 证书的 ARN(Amazon Resource Names,资源名的唯一标识符),用于下一步创建 Distribution 时的证书参数;\n\n4)**构造新Distribution的配置信息**(函数set_config_based_on_ref)\n根据步骤一获取的基准 DistributionConfig,修改域名和源站,添加证书 ARN,创建新的 DistributionConfig\n\n5)**创建新Distribution**(函数create_distribution)\n调用 Amazon Boto3 API create_distribution, 输入步骤4构造的 DistributionConfig,创建所需Distribution。\n\n### **解决方案架构**\n方案用户接口通过 **API Gateway** 和 **Lambda 函数 cf_distribution_clone** 生成一个 Restful API clone_distribution, 函数 cf_distribution_clone 会依据触发 event 中的 queryStringParameters 创建克隆的 Distribution。为了进一步加强安全管理限制 API 的访问,此例中 API Gateway 中将**开启 Cognito 授权**,访问接口的用户需携带 Cognito 令牌才能正常请求 API。方案的架构图如下所示。\n\n\n### **解决方案部署**\n\n#### **1. 部署 Lambda 函数**\n\n创建一个IAM 角色 **cf-clone-distribution-role **供 Lambda 执行时 Assume,为该角色创建如下 IAM 策略,注意需要将与分别替换成 CloudFront 日志所在的 S3 Bucket 的桶名与账号 ID。该策略具有对已有 Distribution 的配置查询权限,新建 Distribution 权限,ACM 证书的列出权限,以及对 CloudFront 日志所在 S3 存储桶的 Bucket ACL 的查询与修改权限。示例以美东区域 us-east-1 作为参考,可以根据实际情况进行替换。\n```\n{ \n \"Version\": \"2012-10-17\", \n \"Statement\": [ \n { \n \"Sid\": \"VisualEditor0\",\n \"Effect\": \"Allow\", \n \"Action\": [ \n \"acm:ListCertificates\", \"cloudfront:CreateDistribution\", \"cloudfront:GetDistributionConfig\" \n ], \n \"Resource\": \"*\" \n }, \n {\n \"Sid\": \"VisualEditor1\", \n \"Effect\": \"Allow\", \n \"Action\": [ \n \"s3:PutBucketAcl\",\n \"s3:GetBucketAcl\", \n \"logs:CreateLogGroup\"\n ], \n \"Resource\": [\n \"arn:aws:s3:::\", \n \"arn:aws:logs:us-east-1::*\" \n ] \n }, \n {\n \"Sid\": \"VisualEditor2\", \n \"Effect\": \"Allow\",\n \"Action\": [ \n \"logs:CreateLogStream\", \n \"logs:PutLogEvents\"\n ], \n \"Resource\": \"arn:aws:logs:us-east-1: ::log-group:/aws/lambda/cf_distribution_clone:*\"\n } \n ] \n }\n```\n创建 Lambda函数 cf_distribution_clone,设置 Lambda 执行角色为上文创建的 **cf-clone-distribution-role**。解决方案使用的运行时环境为 Python 3.9,其对应的完整 Lambda 代码如下所示。\n```\nimport boto3 \nfrom botocore.config import Config \nimport botocore.exceptions \nfrom datetime import datetime, timezone\nimport logging \nimport json \n logging.basicConfig(level=logging.INFO)\n logger = logging.getLogger() \n cf_client = boto3.client('cloudfront') \n my_config = Config( \nregion_name = 'us-east-1'\n ) \nacm_client = boto3.client('acm', config=my_config) \n def lambda_handler(event, context): \nconf_domain = event['queryStringParameters']['domain'] \nconf_origin = event['queryStringParameters']['origin'] \nconf_ref_dist = event['queryStringParameters']['ref_dist']\n# 1) 获取参考域名的配置信息做为基准配置 \nref_config = get_reference_config(conf_ref_dist) \n# 2) 获取账号下ACM证书与域名的对应列表 \ncerts = get_certificate_mapping() \n# 3) 获取该CNAME对应的ACM证书 \ncertArn = get_certificate_arn(certs, conf_domain) \n# 4) 构造新Distribution的配置信息 \nnew_config = set_config_based_on_ref(ref_config, conf_domain, conf_origin, certArn) \n# 5) 创建新Distribution \ndistribution = create_distribution(new_config) response_body = {}\nresponse_body['requestId'] = context.aws_request_id \nresponse_body['distributionId'] = distribution['Distribution']['Id'] \nresponseObject = {} \nresponseObject['statusCode'] = 200 \nresponseObject['body'] = json.dumps(response_body) \nreturn(responseObject) \n def get_reference_config(ref_dist): \ntry: \nreturn cf_client.get_distribution_config(Id=ref_dist) \nexcept botocore.exceptions.ClientError as error: \nlogger.exception(f\"{format(error)}\")\nraise error\ndef get_certificate_mapping(): \ntry: \nresponse = acm_client.list_certificates( \n\nCertificateStatuses=[ \n\n'ISSUED' \n\n], \n\nMaxItems=1000 \n\n) \n\ncerts = response['CertificateSummaryList'] \n\nwhile \"NextToken\" in response: \n\nresponse = acm_client.list_certificates( \n\nCertificateStatuses=[ \n\n'ISSUED' \n\n ], \n\nMaxItems=1000, \n\nNextToken= response['NextToken'] \n\n ) \n\ncerts.extend(response[\"CertificateSummaryList\"]) \n\ncert_dict = {} \n\nfor cert in certs: \n\ncert_dict[cert['DomainName']] = cert['CertificateArn'] \n\nreturn(cert_dict) \n\nexcept botocore.exceptions.ClientError as error: \n\nlogger.exception(f\"{format(error)}\") \n\nraise error\n\n\n\ndef get_certificate_arn(certs, domain): \n\nif domain in certs: \n\ncert = certs[domain] \n\nelse: \n\ncert_domain = '*.' + domain.split(\".\", 1)[-1] \n\nif cert_domain in certs: \n\ncert = certs[cert_domain] \n\nelse: \n\nlogger.info(f\"No certificate for domain - {format(domain)} in ACM. Please create or import one.\") exit(1) logger.info(f\"Use ACM certificate for domain \\'{format(domain)}\\': {format(cert)}.\") return cert \n\n\n\n def set_config_based_on_ref(ref_config, conf_domain, conf_origin, certArn): \n\nref_config['DistributionConfig']['Aliases'] = { \n\n'Quantity': 1, \n\n'Items': [ \n\nconf_domain \n\n] \n\n} \n\nnew_config = ref_config['DistributionConfig'] \n\nnew_config['CallerReference'] = str(datetime.now(tz=None).timestamp()) \n\nnew_config['Origins']['Items'][0]['Id'] = conf_origin \n\nnew_config['Origins']['Items'][0]['DomainName'] = conf_origin \n\nnew_config['DefaultCacheBehavior']['TargetOriginId'] = conf_origin \n\nnew_config['Comment'] = conf_domain \n\nnew_config['ViewerCertificate']['ACMCertificateArn'] = certArn \n\nreturn new_config \n\n\n\n def create_distribution(config): \n\ntry: \n\ndistribution = cf_client.create_distribution(DistributionConfig=config) \n\nlogger.info(f\"Done! Created distribution {format(distribution['Distribution']['Id'])}.\") except botocore.exceptions.ClientError as error: \n\nlogger.exception(f\"{format(error)}\") \n\n raise error \n\n return(distribution)\n```\nLambda 函数部分的操作可以参考下图中的示例。\n\n\n\n#### **2. 创建 API Gateway**\n**创建 API Gateway 执行方法**,添加 URL 查询字符串参数(*注意:以下参数均为小写)。\n\n- domain:创建的 Distribution 所关联的 CNAME,即加速域名;\n- origin:新建的 Distribution 指向的源站域名;\n- ref_dist:参考 Distribution,新建 Distribution 参数参考 Ref_dist 的参数配置,仅修改 CNAME 和 Origin 域名。\n\nAPI Gateway 部分的操作可以参考下图中的示例。\n\n\n#### **3. 部署身份认证服务 Amazon Cognito**\n默认 API Gateway 创建的 API 是公开的,所有人都可以访问,缺少身份认证部分。本方案会创建一个 Cognito 用户池、域名、资源服务器和应用程序客户端用来实现鉴权,即只有鉴权通过后才能访问 API。\n\nCognito 部分的操作可以参考下图中的示例。\n\n\n\n\n\n\n\n\n然后返回到 API Gateway 页面,在 API Gateway 中创建一个 Cognito 授权方,将其配置到相应 API 资源中, Cognito 的令牌需要配置在 Authorization 标头中,如下图所示。\n\n\n\n\n\n#### **4 测试验证**\ncurl -X POST -u <应用程序客户端ID>:<应用程序客户端密钥>\n\n```\n'https://clone-distribution.auth.us-east-1.amazoncognito.com/oauth2/token?grant_type=client_credentials' -H 'Content-Type: application/x-www-form-urlencoded'\n\n```\n执行完命令后会得到访问令牌,如下图所示:\n\n\n\n这里,API 测试工具选择 Postman,打开工具后添加 header(key 为 Authorization,value 为上图中的 access_token),输入 API 链接,并加上查询字符串后发送请求。\n\n链接示例:\n\nhttps://5xx44xx6x0.execute-api.us-east-1.amazonaws.com/prod/?domain=service.yuhong.com&origin=ec2-ip.compute-1.amazonaws.com&ref_dist=E2Z4DXXXXXXXXX\n\n如上图所示,CloudFront Distribution 复制成功,返回新创建的 CloudFront Distribution ID。\n\n#### **总结**\n本文介绍了一种通过 Serverless 服务 Amazon API Gateway 和 Amazon Lambda 以及Amazon CloudFront SDK 来复制/克隆 CloudFront Distribution 的 Restful API 实现,并通过 Cognito 在 API Gateway 访问请求中提供了安全的访问接口。该方案适用于需要将相同配置项应用于多个 Distribution 域名的情形,能实际有效地简化客户的配置管理工作量,减少人工操作出错概率,并且可以达到快速一键部署的目的。\n\n##### **本篇作者**\n\n**马宇红**\n亚马逊云科技技术客户经理,负责企业级大客户的运维与架构优化、成本管理、项目交付、技术咨询等。\n加入亚马逊云科技前曾供职于 IBM 中国软件开发中心,拥有分布式软件开发经验。目前致力于 Edge、DevOps、Serverless 等方向的研究和实践。\n\n**史天**\n亚马逊云科技资深解决方案架构师。拥有丰富的云计算、数据分析和机器学习经验。\n目前致力于数据科学、机器学习、无服务器等领域的研究和实践。译有《机器学习即服务》《基于 Kubernetes的DevOps 实践》《Kubernetes 微服务实战》《Prometheus 监控实战》《云原生时代的 CoreDNS 学习指南》等。\n\n[阅读原文](https://github.com/yuhuiaws/ML-study/tree/main/)","render":"<h4><a id=\"_0\"></a><strong>背景</strong></h4>\n<p><strong>Amazon CloudFront</strong> 是一个全球性的内容分发网络 (CDN),您可以借助 CloudFront 以低延迟和高可用性向查看者或者最终用户分发内容。通常来讲,Amazon CloudFront 的客户都拥有<strong>多个 CloudFront Distribution</strong>,每个 Distribution 都包含了一组待加速的内容以及相关的配置信息,例如源站、加速域名、缓存策略、证书、日志、访问控制等。</p>\n<p>以下主题说明了一些有关 <strong>CloudFront Distribution 的基础知识</strong>,并提供了有关可选择用来配置 Distribution 以满足业务需求的设置的信息。在 CloudFront 自动化操作的过程中,一个常见的任务是<strong>创建多个 Distribution</strong>,这些 Distribution 的配置在同一类型作业下配置参数完全相同,例如动态加速场景下缓存 TTL 均设置为0。在该场景下,客户往往需要一个克隆已存在的 Distribution 的配置项,并希望通过标准 Restful API 接口调用以实现借助程序快速创建多个域名 Distribution 的目的。而目前亚马逊云科技支持的新建 Distribution 的 API :CreateDistribution,需要提供完整的配置参数信息,暂时不支持复制或者克隆 Distribution 的功能。</p>\n<p>本文介绍的方案是利用 <strong>Amazon API Gateway 和 Amazon Lambda</strong>,后端基于<strong>Amazon SDK for Python (Boto3)</strong>,实现基于一个参考 Distribution的配置信息,仅修改域名与源站等变更项,来复制一个新 Distribution 的 Restful API。</p>\n<h3><a id=\"_8\"></a><strong>解决方案简介</strong></h3>\n<p><strong>CloudFront Distribution</strong> 复制/克隆功能的解决方案基本实现思路如下:</p>\n<p>1)复制已存在有一个参考 Distribution 的配置信息;<br />\n2)改造可变项,例如新 Distribution 的加速域名 CNAME 和源站的域名,组成新的完整配置信息;<br />\n3)执行新建 Distribution 的操作。</p>\n<p>在此过程中,会调用 <strong>Amazon Certificate Manager</strong> 的相关 API 查找到新 CNAME 所对应的 ACM 证书的 ARN,从而完成加速域名的证书关联操作。其中,查询证书 ARN 的过程对用户是透明的。</p>\n<h4><a id=\"_17\"></a><strong>解决方案的详细实现流程如下:</strong></h4>\n<p>1)<strong>获取参考域名的配置信息做为基准配置(<strong>函数:get_reference_config)<br />\n调用</strong>Amazon Boto3 API get_distribution_config</strong>,输入 distribution id,以 Json 形式获取 distribution 的配置信息 DistributionConfig;</p>\n<p>2)<strong>获取账号下 ACM 证书与域名的对应列表</strong>(函数get_certificate_mapping)<br />\n调用Amazon Boto3 API list_certificates,输入 CertificateStatuses=’ISSUED’,查询账号下 ACM 中的已签发的证书与域名的对应列表;</p>\n<p>3)<strong>获取该 CNAME 对应的 ACM 证书</strong>(函数get_certificate_arn)<br />\n从步骤3中查出的列表里取得源站 origin 所对应的 ACM 证书的 ARN(Amazon Resource Names,资源名的唯一标识符),用于下一步创建 Distribution 时的证书参数;</p>\n<p>4)<strong>构造新Distribution的配置信息</strong>(函数set_config_based_on_ref)<br />\n根据步骤一获取的基准 DistributionConfig,修改域名和源站,添加证书 ARN,创建新的 DistributionConfig</p>\n<p>5)<strong>创建新Distribution</strong>(函数create_distribution)<br />\n调用 Amazon Boto3 API create_distribution, 输入步骤4构造的 DistributionConfig,创建所需Distribution。</p>\n<h3><a id=\"_34\"></a><strong>解决方案架构</strong></h3>\n<p>方案用户接口通过 <strong>API Gateway</strong> 和 <strong>Lambda 函数 cf_distribution_clone</strong> 生成一个 Restful API clone_distribution, 函数 cf_distribution_clone 会依据触发 event 中的 queryStringParameters 创建克隆的 Distribution。为了进一步加强安全管理限制 API 的访问,此例中 API Gateway 中将<strong>开启 Cognito 授权</strong>,访问接口的用户需携带 Cognito 令牌才能正常请求 API。方案的架构图如下所示。<br />\n<img src=\"https://dev-media.amazoncloud.cn/ec1668ff54b445d6b46e0fed4186d480_image.png\" alt=\"image.png\" /></p>\n<h3><a id=\"_38\"></a><strong>解决方案部署</strong></h3>\n<h4><a id=\"1__Lambda__40\"></a><strong>1. 部署 Lambda 函数</strong></h4>\n<p>创建一个IAM 角色 **cf-clone-distribution-role **供 Lambda 执行时 Assume,为该角色创建如下 IAM 策略,注意需要将与分别替换成 CloudFront 日志所在的 S3 Bucket 的桶名与账号 ID。该策略具有对已有 Distribution 的配置查询权限,新建 Distribution 权限,ACM 证书的列出权限,以及对 CloudFront 日志所在 S3 存储桶的 Bucket ACL 的查询与修改权限。示例以美东区域 us-east-1 作为参考,可以根据实际情况进行替换。</p>\n<pre><code class=\"lang-\">{ \n "Version": "2012-10-17", \n "Statement": [ \n { \n "Sid": "VisualEditor0",\n "Effect": "Allow", \n "Action": [ \n "acm:ListCertificates", "cloudfront:CreateDistribution", "cloudfront:GetDistributionConfig" \n ], \n "Resource": "*" \n }, \n {\n "Sid": "VisualEditor1", \n "Effect": "Allow", \n "Action": [ \n "s3:PutBucketAcl",\n "s3:GetBucketAcl", \n "logs:CreateLogGroup"\n ], \n "Resource": [\n "arn:aws:s3:::", \n "arn:aws:logs:us-east-1::*" \n ] \n }, \n {\n "Sid": "VisualEditor2", \n "Effect": "Allow",\n "Action": [ \n "logs:CreateLogStream", \n "logs:PutLogEvents"\n ], \n "Resource": "arn:aws:logs:us-east-1: ::log-group:/aws/lambda/cf_distribution_clone:*"\n } \n ] \n }\n</code></pre>\n<p>创建 Lambda函数 cf_distribution_clone,设置 Lambda 执行角色为上文创建的 <strong>cf-clone-distribution-role</strong>。解决方案使用的运行时环境为 Python 3.9,其对应的完整 Lambda 代码如下所示。</p>\n<pre><code class=\"lang-\">import boto3 \nfrom botocore.config import Config \nimport botocore.exceptions \nfrom datetime import datetime, timezone\nimport logging \nimport json \n logging.basicConfig(level=logging.INFO)\n logger = logging.getLogger() \n cf_client = boto3.client('cloudfront') \n my_config = Config( \nregion_name = 'us-east-1'\n ) \nacm_client = boto3.client('acm', config=my_config) \n def lambda_handler(event, context): \nconf_domain = event['queryStringParameters']['domain'] \nconf_origin = event['queryStringParameters']['origin'] \nconf_ref_dist = event['queryStringParameters']['ref_dist']\n# 1) 获取参考域名的配置信息做为基准配置 \nref_config = get_reference_config(conf_ref_dist) \n# 2) 获取账号下ACM证书与域名的对应列表 \ncerts = get_certificate_mapping() \n# 3) 获取该CNAME对应的ACM证书 \ncertArn = get_certificate_arn(certs, conf_domain) \n# 4) 构造新Distribution的配置信息 \nnew_config = set_config_based_on_ref(ref_config, conf_domain, conf_origin, certArn) \n# 5) 创建新Distribution \ndistribution = create_distribution(new_config) response_body = {}\nresponse_body['requestId'] = context.aws_request_id \nresponse_body['distributionId'] = distribution['Distribution']['Id'] \nresponseObject = {} \nresponseObject['statusCode'] = 200 \nresponseObject['body'] = json.dumps(response_body) \nreturn(responseObject) \n def get_reference_config(ref_dist): \ntry: \nreturn cf_client.get_distribution_config(Id=ref_dist) \nexcept botocore.exceptions.ClientError as error: \nlogger.exception(f"{format(error)}")\nraise error\ndef get_certificate_mapping(): \ntry: \nresponse = acm_client.list_certificates( \n\nCertificateStatuses=[ \n\n'ISSUED' \n\n], \n\nMaxItems=1000 \n\n) \n\ncerts = response['CertificateSummaryList'] \n\nwhile "NextToken" in response: \n\nresponse = acm_client.list_certificates( \n\nCertificateStatuses=[ \n\n'ISSUED' \n\n ], \n\nMaxItems=1000, \n\nNextToken= response['NextToken'] \n\n ) \n\ncerts.extend(response["CertificateSummaryList"]) \n\ncert_dict = {} \n\nfor cert in certs: \n\ncert_dict[cert['DomainName']] = cert['CertificateArn'] \n\nreturn(cert_dict) \n\nexcept botocore.exceptions.ClientError as error: \n\nlogger.exception(f"{format(error)}") \n\nraise error\n\n\n\ndef get_certificate_arn(certs, domain): \n\nif domain in certs: \n\ncert = certs[domain] \n\nelse: \n\ncert_domain = '*.' + domain.split(".", 1)[-1] \n\nif cert_domain in certs: \n\ncert = certs[cert_domain] \n\nelse: \n\nlogger.info(f"No certificate for domain - {format(domain)} in ACM. Please create or import one.") exit(1) logger.info(f"Use ACM certificate for domain \\'{format(domain)}\\': {format(cert)}.") return cert \n\n\n\n def set_config_based_on_ref(ref_config, conf_domain, conf_origin, certArn): \n\nref_config['DistributionConfig']['Aliases'] = { \n\n'Quantity': 1, \n\n'Items': [ \n\nconf_domain \n\n] \n\n} \n\nnew_config = ref_config['DistributionConfig'] \n\nnew_config['CallerReference'] = str(datetime.now(tz=None).timestamp()) \n\nnew_config['Origins']['Items'][0]['Id'] = conf_origin \n\nnew_config['Origins']['Items'][0]['DomainName'] = conf_origin \n\nnew_config['DefaultCacheBehavior']['TargetOriginId'] = conf_origin \n\nnew_config['Comment'] = conf_domain \n\nnew_config['ViewerCertificate']['ACMCertificateArn'] = certArn \n\nreturn new_config \n\n\n\n def create_distribution(config): \n\ntry: \n\ndistribution = cf_client.create_distribution(DistributionConfig=config) \n\nlogger.info(f"Done! Created distribution {format(distribution['Distribution']['Id'])}.") except botocore.exceptions.ClientError as error: \n\nlogger.exception(f"{format(error)}") \n\n raise error \n\n return(distribution)\n</code></pre>\n<p>Lambda 函数部分的操作可以参考下图中的示例。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/580aaaa3a749477891a9e34823f76c98_image.png\" alt=\"image.png\" /><br />\n<img src=\"https://dev-media.amazoncloud.cn/1281b5e0c62346ad963f0ba4e308f1f1_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"2__API_Gateway_241\"></a><strong>2. 创建 API Gateway</strong></h4>\n<p><strong>创建 API Gateway 执行方法</strong>,添加 URL 查询字符串参数(*注意:以下参数均为小写)。</p>\n<ul>\n<li>domain:创建的 Distribution 所关联的 CNAME,即加速域名;</li>\n<li>origin:新建的 Distribution 指向的源站域名;</li>\n<li>ref_dist:参考 Distribution,新建 Distribution 参数参考 Ref_dist 的参数配置,仅修改 CNAME 和 Origin 域名。</li>\n</ul>\n<p>API Gateway 部分的操作可以参考下图中的示例。<br />\n<img src=\"https://dev-media.amazoncloud.cn/2cc7cf217547484989c0dbf0bb3e45fd_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"3__Amazon_Cognito_251\"></a><strong>3. 部署身份认证服务 Amazon Cognito</strong></h4>\n<p>默认 API Gateway 创建的 API 是公开的,所有人都可以访问,缺少身份认证部分。本方案会创建一个 Cognito 用户池、域名、资源服务器和应用程序客户端用来实现鉴权,即只有鉴权通过后才能访问 API。</p>\n<p>Cognito 部分的操作可以参考下图中的示例。<br />\n<img src=\"https://dev-media.amazoncloud.cn/552d8917724e4f47a9fe5c9b60388813_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/3872096ee9e7482995922719da3e52fa_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/ff24c7f2fc9446da8e8444e6329f2166_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/80b6dad15cc54eb1b3be6ffa1f117c1d_image.png\" alt=\"image.png\" /></p>\n<p>然后返回到 API Gateway 页面,在 API Gateway 中创建一个 Cognito 授权方,将其配置到相应 API 资源中, Cognito 的令牌需要配置在 Authorization 标头中,如下图所示。</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/d38d36c7928b492ebdd04a321ee02f5f_image.png\" alt=\"image.png\" /></p>\n<p><img src=\"https://dev-media.amazoncloud.cn/94ad615ebbc94897a0a318c2252b2aad_image.png\" alt=\"image.png\" /></p>\n<h4><a id=\"4__269\"></a><strong>4 测试验证</strong></h4>\n<p>curl -X POST -u <应用程序客户端ID>:<应用程序客户端密钥></p>\n<pre><code class=\"lang-\">'https://clone-distribution.auth.us-east-1.amazoncognito.com/oauth2/token?grant_type=client_credentials' -H 'Content-Type: application/x-www-form-urlencoded'\n\n</code></pre>\n<p>执行完命令后会得到访问令牌,如下图所示:</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/1818e7cf73e749c9a583430ee0fd53ef_image.png\" alt=\"image.png\" /></p>\n<p>这里,API 测试工具选择 Postman,打开工具后添加 header(key 为 Authorization,value 为上图中的 access_token),输入 API 链接,并加上查询字符串后发送请求。</p>\n<p>链接示例:</p>\n<p>https://5xx44xx6x0.execute-api.us-east-1.amazonaws.com/prod/?domain=service.yuhong.com&origin=ec2-ip.compute-1.amazonaws.com&ref_dist=E2Z4DXXXXXXXXX<br />\n<img src=\"https://dev-media.amazoncloud.cn/1ad671b0ece641c7a9e2a139e154e52a_image.png\" alt=\"image.png\" /><br />\n如上图所示,CloudFront Distribution 复制成功,返回新创建的 CloudFront Distribution ID。</p>\n<h4><a id=\"_288\"></a><strong>总结</strong></h4>\n<p>本文介绍了一种通过 Serverless 服务 Amazon API Gateway 和 Amazon Lambda 以及Amazon CloudFront SDK 来复制/克隆 CloudFront Distribution 的 Restful API 实现,并通过 Cognito 在 API Gateway 访问请求中提供了安全的访问接口。该方案适用于需要将相同配置项应用于多个 Distribution 域名的情形,能实际有效地简化客户的配置管理工作量,减少人工操作出错概率,并且可以达到快速一键部署的目的。</p>\n<h5><a id=\"_291\"></a><strong>本篇作者</strong></h5>\n<p><strong>马宇红</strong><br />\n亚马逊云科技技术客户经理,负责企业级大客户的运维与架构优化、成本管理、项目交付、技术咨询等。<br />\n加入亚马逊云科技前曾供职于 IBM 中国软件开发中心,拥有分布式软件开发经验。目前致力于 Edge、DevOps、Serverless 等方向的研究和实践。</p>\n<p><strong>史天</strong><br />\n亚马逊云科技资深解决方案架构师。拥有丰富的云计算、数据分析和机器学习经验。<br />\n目前致力于数据科学、机器学习、无服务器等领域的研究和实践。译有《机器学习即服务》《基于 Kubernetes的DevOps 实践》《Kubernetes 微服务实战》《Prometheus 监控实战》《云原生时代的 CoreDNS 学习指南》等。</p>\n<p><a href=\"https://github.com/yuhuiaws/ML-study/tree/main/\" target=\"_blank\">阅读原文</a></p>\n"}