こんにちは、ゲームソリューショングループのsoraです。
今回は、OACでのCloudFrontからS3の接続+Lambda@Edgeでの認証をTerraformで作成してみたことについて書いていきます。
構成
CloudFrontでアクセスを受けると、Lambda@Edgeで認証して、認証が通ればS3にアクセスできるという構成です。
フォルダ構成は以下です。
$ tree
.
├── front
│ └── front.html
├── lambda
│ └── lambda.py
└── main.tf
AWSサービスの作成
タイトルにある通り、Terraformを使ってAWS側で必要なサービスを作成します。
解説もコード内のコメントにある程度は記載しています。
特にLambda@Edgeを使用できるリージョンが決まっていること(12-13行目)と、S3に配置するHTMLファイルのcontent-typeを指定すること(25-26行目)に注意してください。
main.tf
terraform {
#AWSプロバイダーのバージョン指定
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.51.0"
}
}
}
#AWSプロバイダーの定義
provider aws {
#Lambda@Edgeがバージニアリージョンでのみ使用可能なため
region = "us-east-1"
}
#S3
resource aws_s3_bucket origin_contents {
bucket = "cloudfront-origin-contents"
}
#ファイルアップロード
resource aws_s3_object object {
bucket = aws_s3_bucket.origin_contents.id
key = "index.html"
source = "front/front.html"
#content-typeを指定しない場合、ページが表示されずにダウンロードになる場合があるため指定する
content_type = "text/html"
}
#Lambda用IAMロールの信頼関係の定義
data aws_iam_policy_document assume_role {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com"
]
}
actions = ["sts:AssumeRole"]
}
}
#Lambda用IAMロールの作成
resource aws_iam_role iam_for_lambda {
name = "cloudfront_access_lambda"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
inline_policy {
name = "my_inline_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"lambda:InvokeFunction",
"lambda:GetFunction",
"lambda:EnableReplication",
"cloudfront:UpdateDistribution"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
}
data archive_file lambda {
type = "zip"
source_file = "lambda/lambda.py"
output_path = "lambda_handler.zip"
}
resource aws_lambda_function lambda {
filename = "lambda_handler.zip"
function_name = "IPAuth"
role = aws_iam_role.iam_for_lambda.arn
handler = "lambda.lambda_handler"
source_code_hash = data.archive_file.lambda.output_base64sha256
runtime = "python3.8"
}
#CloudFrontディストリビューション
resource aws_cloudfront_distribution cf_distribution {
enabled = true
default_root_object = "index.html"
#オリジンの設定
origin {
domain_name = aws_s3_bucket.origin_contents.bucket_regional_domain_name
origin_id = aws_s3_bucket.origin_contents.id
origin_access_control_id = aws_cloudfront_origin_access_control.main.id
}
viewer_certificate {
cloudfront_default_certificate = true
}
#キャッシュの設定
default_cache_behavior {
target_origin_id = aws_s3_bucket.origin_contents.id
viewer_protocol_policy = "redirect-to-https"
cached_methods = ["GET", "HEAD"]
allowed_methods = ["GET", "HEAD"]
forwarded_values {
query_string = false
headers = []
cookies {
forward = "none"
}
}
#ビューワーリクエストにLambdaを設定する
lambda_function_association {
event_type = "viewer-request"
lambda_arn = aws_lambda_function.lambda.qualified_arn
include_body = false
}
}
#国ごとのコンテンツ制限がある場合はここで設定(今回はなし)
restrictions {
geo_restriction {
restriction_type = "none"
}
}
}
# OACを作成
resource aws_cloudfront_origin_access_control main {
name = "cloudfront-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
#S3バケットポリシー(OACのみから許可する)
##ポリシーの定義
data aws_iam_policy_document allow_access_from_cloudfront {
statement {
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
actions = [
"s3:GetObject"
]
resources = [
"${aws_s3_bucket.origin_contents.arn}/*"
]
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.cf_distribution.arn]
}
}
}
##バケットポリシーのアタッチ
resource aws_s3_bucket_policy allow_access_from_cloudfront {
bucket = aws_s3_bucket.origin_contents.id
policy = data.aws_iam_policy_document.allow_access_from_cloudfront.json
}
Lambdaで使用するコード(Python)
IPアドレスにより制限をかける関数としています。
今回はGoでなくPythonを使います。
以下のページをとても参考にさせていただきました。
CloudFrontとLambda@Edge ( Python3 )とS3で静的ページにIPアドレス制限とBasic認証を設定する
lambda.py
import base64
#自分のIPアドレス
ALLOW_IP = ["X.X.X.X"]
ERROR_RESPONSE_AUTH = {
'status': '401',
'statusDescription': 'Unauthorized',
'body': 'Authentication Failed',
'headers': {
'www-authenticate': [
{
'key':'WWW-Authenticate',
'value':'IP address fault'
}
]
}
}
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
client_ip = request['clientIp']
if client_ip in ALLOW_IP:
return request
else:
return ERROR_RESPONSE_AUTH
静的ページとして表示させるHTMLも簡単に作成します。
front.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>sora</h1>
<h2>所属</h2>
<p>営業統括本部 ゲームソリューショングループ ソリューションアーキテクト</p>
<h2>今後ブログにしようと思っていること</h2>
<ul>
<li>CDK(Python)で出たエラー</li>
<li>Goで色々作ってみた成果物</li>
<li>構築したことのない構成やサービスをCDKで構築</li>
</ul>
</body>
</html>
デプロイ
作成したTerraformのコードを実行して構築します。
terraform init
terraform apply
CloudFrontで提供されているドメイン名を確認して、アクセスするとページが表示されています。
最後に
今回は、OACでのCloudFrontからS3の接続+Lambda@Edgeでの認証をTerraformで作成してみたことを記事にしました。
どなたかの参考になると幸いです。