この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部の若槻です。
PoCやデモなどで使用するWebサイト(CloudFront + S3)で「Cognitoを使うほどでは無いけど何らかの認証は掛けておきたい」という場合はBasic認証という選択肢があります。
このBasic認証をCloudFront + S3という構成で掛けたい場合の実装は、今まではLambda@Edgeを使う場合が多かったですが、最近リリースされた、よりユーザーに近いロケーションでより高速な処理が可能なCloudFront Functionsでも実装が可能です。
今回は、このCloudFront Functionを使用してWebサイトにBasic認証をかける設定をAWS CDKで実装してみました。
CloudFront Functionコード
lambda/basic-authentication/index.js
function handler(event) {
var request = event.request;
var headers = request.headers;
// echo -n user:pass | base64
var authString = "Basic dXNlcjpwYXNz";
if (
typeof headers.authorization === "undefined" ||
headers.authorization.value !== authString
) {
return {
statusCode: 401,
statusDescription: "Unauthorized",
headers: { "www-authenticate": { value: "Basic" } }
};
}
return request;
}
下記の記事のコードがシンプルでしたのでそのまま活用させて頂きました。
CDKコード
CloudFront + S3 + CloudFront Function から成る静的ホスティングなWebサイトです。
lib/sample-app-stack.ts
import * as cdk from "@aws-cdk/core";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import * as s3deploy from "@aws-cdk/aws-s3-deployment";
import * as iam from "@aws-cdk/aws-iam";
export class SampleAppStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const accountId = cdk.Stack.of(this).account;
const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
bucketName: `${props.stageName}-${props.projectName}-website-${accountId}`,
websiteErrorDocument: "index.html",
websiteIndexDocument: "index.html",
});
const websiteIdentity = new cloudfront.OriginAccessIdentity(
this,
"WebsiteIdentity",
{
comment: `${props.stageName}-${props.projectName}-identity`,
}
);
const webSiteBucketPolicyStatement = new iam.PolicyStatement({
actions: ["s3:GetObject"],
effect: iam.Effect.ALLOW,
principals: [
new iam.CanonicalUserPrincipal(
websiteIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
),
],
resources: [`${websiteBucket.bucketArn}/*`],
});
websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);
// CloudFront Functionリソースの定義
const basicAuthFunction = new cloudfront.Function(
this,
"BasicAuthFunction",
{
functionName: `basic-authentication`,
code: cloudfront.FunctionCode.fromFile({
filePath: "lambda/basic-authentication/index.js",
}),
}
);
const websiteDistribution = new cloudfront.CloudFrontWebDistribution(
this,
"WebsiteDistribution",
{
comment: `${props.stageName}-${props.projectName}-distribution`,
errorConfigurations: [
{
errorCachingMinTtl: 300,
errorCode: 403,
responseCode: 200,
responsePagePath: "/index.html",
},
{
errorCachingMinTtl: 300,
errorCode: 404,
responseCode: 200,
responsePagePath: "/index.html",
},
],
originConfigs: [
{
s3OriginSource: {
s3BucketSource: websiteBucket,
originAccessIdentity: websiteIdentity,
},
behaviors: [
{
isDefaultBehavior: true,
// CloudFront FunctionをDistributionに設定
functionAssociations: [
{
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
function: basicAuthFunction,
},
],
},
],
},
],
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
}
);
new s3deploy.BucketDeployment(this, "WebsiteDeploy", {
sources: [s3deploy.Source.asset("./web/build")],
destinationBucket: websiteBucket,
distribution: websiteDistribution,
distributionPaths: ["/*"],
});
}
}
DistributionへのFunctionの紐付けは、Lambda@Edgeの場合はlambdaFunctionAssociations
を使用しますが、CloudFront Functionの場合はfunctionAssociations
を使用することに注意してください。
動作確認
cdk deploy
でリソースをデプロイします。
WebサイトのURLにアクセスするとブラウザのBasic認証のダイアログが表示されます。
IDとパスワードを入力してログインをクリックします。
S3バケットに静的ホスティングされたWebサイトにアクセスできました。
その他動作としては、IDまたはパスワードを間違えると再度ダイアログが表示され、ダイアログでキャンセルをクリックすると真っ白なページのみ表示される動作となります。
CloudFront Functionでは「Buffer」が使えない?
CloudFront Functionのコード内でBuffer
を使用してIDとパスワードのBase64エンコード出来ないか試してみました。
//var authString = "Basic dXNlcjpwYXNz";
var authString = 'Basic ' + Buffer.from('user:pass').toString('base64');
Functionをデプロイしてテストすると下記のようにBuffer
がReferenceErrorとなりました。
Error Message: The CloudFront function associated with the CloudFront distribution is invalid or could not run. ReferenceError: "Buffer" is not defined in 7
Lambda@Edgeの場合はBuffer
が使えるはずなのですがCloudFront Functionでは使えないようです。高速化の一環としてFunctionへのモジュールのバンドルを最低限にしているようですね。
おわりに
CloudFront Functionを使用してWebサイトにBasic認証をかける設定をAWS CDKで実装してみました。Cognitoの導入などの重い実装もなくサクッとWebサイトに認証機能を導入出来てよかったです。
参考
- CloudFront Function | @aws-cdk/aws-cloudfront module · AWS CDK
- AWS CDK で CloudFront Functions をやってみた | grgr-dkrkのブログ
- CloudFront FunctionsをAWS CDKとCircleCIを用いてE2Eテストおよび自動デプロイに対応する | 超高速 WordPress AMI AMIMOTO
以上