目的
・AWS上の静的Webサイトホスティングを有効にしたS3をCloudFrontで公開。
・Basic認証を実装。
前提条件
・SAMを使用してAWS上にリソースを作成する。
・Amazon CloudFront KeyValueStore+CloudFront Functionsを使用して実装する。
完成イメージ
SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
WebBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::StackName}-webbucket-${AWS::AccountId}
VersioningConfiguration:
Status: Enabled
WebsiteConfiguration:
IndexDocument: index.html
PublicAccessBlockConfiguration:
IgnorePublicAcls: false
WebBucketBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Principal:
Service: cloudfront.amazonaws.com
Action:
- s3:GetObject
Effect: Allow
Resource:
- !Sub ${WebBucket.Arn}/*
Condition:
StringEquals:
AWS:SourceArn:
- !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${WebBucketDistribution}
WebBucketDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
TargetOriginId: WebBucketOrigin
Compress: true
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
CachePolicyId: !Ref WebBucketCachePolicy
OriginRequestPolicyId: !Ref WebBucketOriginRequestPolicy
FunctionAssociations:
- EventType: viewer-request
FunctionARN: !GetAtt WebBucketBasicAuthFunction.FunctionARN
PriceClass: PriceClass_200
DefaultRootObject: index.html
Enabled: true
Origins:
- DomainName: !GetAtt WebBucket.RegionalDomainName
Id: WebBucketOrigin
OriginAccessControlId: !GetAtt WebBucketOriginAccessControl.Id
S3OriginConfig:
OriginAccessIdentity: ''
WebBucketOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
SigningBehavior: always
OriginAccessControlOriginType: s3
SigningProtocol: sigv4
Name: !Sub ${AWS::StackName}-OAC-${AWS::AccountId}
WebBucketCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
Name: WebBucketCachePolicy
DefaultTTL: 86400
MaxTTL: 31536000
MinTTL: 3600
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingGzip: true
EnableAcceptEncodingBrotli: true
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
WebBucketOriginRequestPolicy:
Type: AWS::CloudFront::OriginRequestPolicy
Properties:
OriginRequestPolicyConfig:
Name: WebBucketOriginRequestPolicy
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
DeleteCacheLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: LambdaS3Policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
- cloudfront:ListDistributions
Resource: '*'
DeleteCache:
Type: AWS::Serverless::Function
Properties:
Handler: index.lambda_handler
Runtime: python3.11
Role: !GetAtt DeleteCacheLambdaRole.Arn
Environment:
Variables:
Stack_NAME: !Sub ${AWS::StackName}
CodeUri: ./DeleteCache
Events:
WebBucket:
Type: S3
Properties:
Bucket: !Ref WebBucket
Events:
- s3:ObjectCreated:*
- s3:ObjectRemoved:*
Filter:
S3Key:
Rules:
- Name: suffix
Value: index.html
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref DeleteCache
Action: lambda:InvokeFunction
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId
SourceArn: !Sub arn:aws:s3:::${WebBucket}
WebBucketBasicAuthKVS:
Type: AWS::CloudFront::KeyValueStore
Properties:
Name: WebBucketBasicAuthKVS
WebBucketBasicAuthFunction:
Type: AWS::CloudFront::Function
Properties:
Name: WebBucketBasicAuthFunction
FunctionConfig:
Comment: Function with KeyValueStore
Runtime: cloudfront-js-2.0
KeyValueStoreAssociations:
- KeyValueStoreARN: !GetAtt WebBucketBasicAuthKVS.Arn
FunctionCode: !Sub |
import cf from 'cloudfront';
const kvsId = '${WebBucketBasicAuthKVS.Id}';
const kvsHandle = cf.kvs(kvsId);
async function handler(event) {
const request = event.request;
const headers = request.headers;
if (
typeof headers.authorization === 'undefined' ||
typeof headers.authorization.value === 'undefined'
) {
return return401();
}
const encoded = headers.authorization.value.split(' ')[1];
const decoded = Buffer.from(encoded, 'base64').toString();
const userRequest = decoded.split(':')[0];
const passRequest = decoded.split(':')[1];
const exist = await kvsHandle.exists(userRequest);
if (!exist) {
return return401();
}
const passStore = await kvsHandle.get(userRequest);
if (passStore !== passRequest) {
return return401();
}
return request;
}
const return401 = () => {
return {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: { 'www-authenticate': { value: 'Basic' } },
};
};
AutoPublish: true
Outputs:
WebsiteURLonCF:
Description: URL for website hosted on CloudFront
Value: !GetAtt WebBucketDistribution.DomainName
①以下、前回記事より設定内容に変更なし
・WebBucket(S3バケット)
・WebBucketBucketPolicy(バケットポリシー)
・WebBucketOriginAccessControl(OAC)
・WebBucketCachePolicy(キャッシュポリシー)
・WebBucketOriginRequestPolicy(オリジンリクエストポリシー)
・DeleteCacheLambdaRole(Lambda用IAMロール)
・DeleteCache(キャッシュ削除用Lambda)
・LambdaInvokePermission(Lambdaに付与するリソースベースのポリシー)
②WebBucketDistribution(CloudFrontディストリビューション)
・FunctionAssociations:CloudFront Functionsとの関連付け
③WebBucketBasicAuthKVS(CloudFront KeyValueStore)
④WebBucketBasicAuthFunction(CloudFront Functions)
動作確認
①作成したKeyValueStoreでKey・Valueペアを作成
②Webサイトへアクセス
・ユーザ名とパスワードが要求されることを確認
⑤未登録のユーザ・誤ったパスワードでの認証が失敗することを確認
参考(前回記事)