Introduction#

GitHub this repository shows

  • Build REST APIs with API Gateway and Lambda
  • Authentication using Cognito UserPool
  • Protect APIs using WAF and CloudFront
  • Manage APIs with KEY and usage plan
  • Load test with Artellery
  • Experiment 429 throttling error

Overall architecture of the app

Next Polly App Arch

Exchange cognito id token for credentials

Cognito Exchange Token Credential

API Gateway#

First create role for the API Gateway which enable logging to CloudWatch Logs

Detail Role
const role = new aws_iam.Role(this, 'RoleForApiGwInvokeLambda', {
roleName: 'ApiGwInvokeLambda',
assumedBy: new aws_iam.ServicePrincipal('apigateway.amazonaws.com')
})
role.addToPolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ['lambda:InvokeFunction'],
resources: [
read_image_ddb_func.functionArn,
write_image_ddb_func.functionArn,
write_ddb_func.functionArn,
read_ddb_func.functionArn,
polly_func.functionArn
]
})
)
role.addToPolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:DescribeLogGroups',
'logs:DescribeLogStreams',
'logs:PutLogEvents',
'logs:GetLogEvents',
'logs:FilterLogEvents'
],
resources: ['*']
})
)

Then create a API Gateway

// create an api prod stage
const apigw = new aws_apigateway.RestApi(this, 'DevApigwDemo', {
restApiName: 'pollyapi',
deploy: false,
cloudWatchRole: true
})
// integrate lambda with apigw
const message = apigw.root.addResource('message')
const image = apigw.root.addResource('image')
const book = apigw.root.addResource('book')
const polly_api_resource = apigw.root.addResource('polly')

Integrate the API Gateway with Lambda functions

book.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(test_lambda_func, {
proxy: true
}),
{
authorizationType: aws_apigateway.AuthorizationType.COGNITO,
authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
this,
'TestAuthorizer',
{
cognitoUserPools: [userPool]
}
)
}
)
message.addMethod(
'POST',
new aws_apigateway.LambdaIntegration(write_ddb_func, {
proxy: true,
allowTestInvoke: false,
credentialsRole: role,
integrationResponses: [
{
statusCode: '200'
}
]
}),
{
methodResponses: [{ statusCode: '200' }],
authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
this,
'messagePostAuthorizer',
{
cognitoUserPools: [userPool]
}
),
authorizationType: aws_apigateway.AuthorizationType.COGNITO
}
)
message.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(read_ddb_func, {
credentialsRole: role
}),
{
authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
this,
'messageGetAuthorizer',
{
cognitoUserPools: [userPool]
}
),
authorizationType: aws_apigateway.AuthorizationType.COGNITO
}
)
polly_api_resource.addMethod(
'POST',
new aws_apigateway.LambdaIntegration(polly_func, {
proxy: true,
allowTestInvoke: false,
credentialsRole: role
// integrationResponses: [
// {
// statusCode: "200",
// },
// ],
})
// {
// methodResponses: [{ statusCode: "200" }],
// authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
// this,
// "messagePostAuthorizer",
// {
// cognitoUserPools: [userPool],
// }
// ),
// authorizationType: aws_apigateway.AuthorizationType.COGNITO,
// }
)
image.addMethod(
'GET',
new aws_apigateway.LambdaIntegration(read_image_ddb_func, {
credentialsRole: role
}),
{
authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
this,
'ImageGetAuthorizer',
{
cognitoUserPools: [userPool]
}
),
authorizationType: aws_apigateway.AuthorizationType.COGNITO
}
)
image.addMethod(
'POST',
new aws_apigateway.LambdaIntegration(write_image_ddb_func, {
proxy: true,
allowTestInvoke: false,
credentialsRole: role,
integrationResponses: [
{
statusCode: '200'
}
]
}),
{
methodResponses: [{ statusCode: '200' }],
authorizer: new aws_apigateway.CognitoUserPoolsAuthorizer(
this,
'ImagePostAuthorizer',
{
cognitoUserPools: [userPool]
}
),
authorizationType: aws_apigateway.AuthorizationType.COGNITO
}
)

CORS enable and preflight

// cors per resources
message.addCorsPreflight({
allowOrigins: ['*'],
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['*']
})
image.addCorsPreflight({
allowOrigins: ['*'],
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['*']
})
polly_api_resource.addCorsPreflight({
allowOrigins: ['*'],
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['*']
})

Integrate with CloudWatch Log Group

// access log group
const logGroup = new aws_logs.LogGroup(this, 'AccessLogApi', {
logGroupName: 'AccessLogApiDevClass',
removalPolicy: RemovalPolicy.DESTROY,
retention: RetentionDays.ONE_WEEK
})

Finally create deployment stage

const deployment = new aws_apigateway.Deployment(this, 'Deployment', {
api: apigw
})
const prodStage = new aws_apigateway.Stage(this, 'ProdStage', {
stageName: 'prod',
deployment,
dataTraceEnabled: true,
accessLogDestination: new aws_apigateway.LogGroupLogDestination(logGroup),
accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields()
})

Cognito Stack#

Let create a cognito userpool

const userPool = new aws_cognito.UserPool(this, 'UserPoolDemo', {
userPoolName: 'UserPoolDemo',
selfSignUpEnabled: true,
signInAliases: {
email: true
},
autoVerify: {
email: true
},
removalPolicy: RemovalPolicy.DESTROY
})

Then create a client without secret for client application integration

const clientWithoutSecret = new aws_cognito.UserPoolClient(
this,
'ClientWithoutSecret',
{
userPool: userPool,
authFlows: {
userPassword: true,
adminUserPassword: true,
custom: true,
userSrp: true
},
userPoolClientName: 'ClientWithoutSecret'
}
)

And create a client with secret for server side application which use cognito hosted ui later on

const clientWithSecret = new aws_cognito.UserPoolClient(
this,
'ClientWithSecret',
{
userPool: userPool,
authFlows: {
userPassword: true,
adminUserPassword: true,
custom: true,
userSrp: true
},
userPoolClientName: 'ClientWithSecret',
generateSecret: true,
oAuth: {
flows: {
authorizationCodeGrant: true
},
callbackUrls: props.callbackUrls,
logoutUrls: props.logoutUrls,
scopes: [
aws_cognito.OAuthScope.EMAIL,
aws_cognito.OAuthScope.OPENID,
aws_cognito.OAuthScope.PHONE
]
}
}
)

Create a domain for cognito hosted ui

const domain = userPool.addDomain('domain', {
cognitoDomain: {
domainPrefix: 'tech-sharing'
}
})
domain.signInUrl(clientWithSecret, {
redirectUri: ''
})

Finally create a identity pool and associate with the two userpools above

const identityPool = new IdentityPool(this, 'IdentityPoolDemo', {
identityPoolName: 'IdentityPoolDemo',
authenticationProviders: {
userPools: [
new UserPoolAuthenticationProvider({
userPool,
userPoolClient: clientWithSecret
}),
new UserPoolAuthenticationProvider({
userPool,
userPoolClient: clientWithoutSecret
})
]
}
})

Create a S3 bucket and grant permission to the identity pool

const bucket = new aws_s3.Bucket(this, 'CognitoDemoBucket', {
bucketName: `cognito-demo-bucket-${this.account}-1`,
removalPolicy: RemovalPolicy.DESTROY,
// so webapp runnning local host can access s3
cors: [
{
allowedHeaders: ['*'],
allowedMethods: [
aws_s3.HttpMethods.GET,
aws_s3.HttpMethods.PUT,
aws_s3.HttpMethods.DELETE,
aws_s3.HttpMethods.POST
],
allowedOrigins: ['*'],
exposedHeaders: [
'x-amz-server-side-encryption',
'x-amz-request-id',
'x-amz-id-2',
'ETag'
],
maxAge: 3000
}
]
})

Grant permissions

// this identity pool can access s3
bucket.grantReadWrite(identityPool.authenticatedRole)
bucket.grantRead(identityPool.authenticatedRole)

WAF Stack#

Let integrate WAF to protect API endpoints

import { v4 as uuidv4 } from 'uuid'
import { aws_wafv2, Stack, StackProps } from 'aws-cdk-lib'
import { Construct } from 'constructs'
interface WafApigwProps extends StackProps {
resourceArns: string[]
}
export class WafApigwStack extends Stack {
constructor(scope: Construct, id: string, props: WafApigwProps) {
super(scope, id, props)
/**
* 1. AWS managed WAF rule
* block IP addresses typically associated with bots
* from Amazon internal threat intelligence
*/
const awsMangedRuleIPReputationList: aws_wafv2.CfnWebACL.RuleProperty = {
name: 'AWSManagedRulesCommonRuleSet',
priority: 10,
statement: {
managedRuleGroupStatement: {
name: 'AWSManagedRulesCommonRuleSet',
vendorName: 'AWS'
}
},
overrideAction: { none: {} },
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AWSIPReputationList'
}
}
/**
* 2. Geo restrict rule. Block from a list.
*/
const ruleGeoRestrict: aws_wafv2.CfnWebACL.RuleProperty = {
name: 'RuleGeoRestrict',
priority: 2,
action: {
block: {}
},
statement: {
geoMatchStatement: {
countryCodes: ['SG']
}
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'GeoMatch'
}
}
/**
* 3. Rate limite rule. in five-minute period,
* if number of requests over the limit 100,
* block the IP.
*/
const ruleLimiteRequestsThreshold: aws_wafv2.CfnWebACL.RuleProperty = {
name: 'LimiteRequestsThreshold',
priority: 1,
action: {
block: {}
},
statement: {
// 2000 requests within 5 minutes
rateBasedStatement: {
limit: 2000,
aggregateKeyType: 'IP'
}
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'LimitRequestsThreshold'
}
}
// Push rules into ACL
const webAcl = new aws_wafv2.CfnWebACL(this, 'WafToProtectApigwDemo', {
defaultAction: { allow: {} },
// scope: "CLOUDFRONT",
scope: 'REGIONAL',
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'waf-regional-apigw',
sampledRequestsEnabled: true
},
description: 'WAFv2 ACL for CloudFront',
name: 'WafToProtectApigwDemo',
// push all rules into an ACL
rules: [
awsMangedRuleIPReputationList,
ruleLimiteRequestsThreshold,
ruleGeoRestrict
]
})
//
props.resourceArns.map(arn => {
new aws_wafv2.CfnWebACLAssociation(this, `WafProtectApi-${uuidv4()}`, {
resourceArn: arn,
webAclArn: webAcl.attrArn
})
})
}
}

Cognito Client#

Here are some functions using cognito client to sign up, confirm, log in and exchange token for credentials

Cognito Client
// cognito userpool and identity pool
// haimtran 10/10/2023
// 1. create a cognito userpool
// 2. signup a user
// 3. setup password
// 4. get credentials for guest and auth users
import { config } from './config'
import {
PutObjectCommand,
GetObjectCommand,
S3Client
} from '@aws-sdk/client-s3'
import {
CognitoIdentityProviderClient,
AdminSetUserPasswordCommand,
ConfirmSignUpCommand,
InitiateAuthCommand,
SignUpCommand
} from '@aws-sdk/client-cognito-identity-provider'
import { fromCognitoIdentityPool } from '@aws-sdk/credential-providers'
import { decode } from 'next-auth/jwt'
import { CognitoJwtVerifier } from 'aws-jwt-verify'
import axios from 'axios'
const cognitoClient = new CognitoIdentityProviderClient({
region: config.REGION
})
const signUp = async (username: string, password: string) => {
try {
const response = await cognitoClient.send(
new SignUpCommand({
ClientId: config.CLIENT_ID,
Username: username,
Password: password
})
)
console.log(response)
} catch (error) {
console.log(error)
}
}
const confirmSignUp = async (username: string, code: string) => {
try {
const response = await cognitoClient.send(
new ConfirmSignUpCommand({
ClientId: config.CLIENT_ID,
Username: username,
ConfirmationCode: code
})
)
} catch (error) {
console.log(error)
}
}
const setPass = async () => {
const response = await cognitoClient.send(
new AdminSetUserPasswordCommand({
Password: 'Demo@2023',
Username: '6a7533f5-ea77-462e-a8b0-07d34a1c238b',
UserPoolId: config.USER_POOL_ID,
Permanent: true
})
)
console.log(response)
}
export const signIn = async (username: string, password: string) => {
try {
const response = await cognitoClient.send(
new InitiateAuthCommand({
AuthFlow: 'USER_PASSWORD_AUTH',
AuthParameters: {
USERNAME: username,
PASSWORD: password
},
ClientId: config.CLIENT_ID
})
)
console.log('cognito auth: ', response)
return response
} catch (error) {
console.log(error)
return null
}
}
// for authenticated user and guest user
const getCredentials = async (username: string, password: string) => {
// login and get idtoken
const response = await cognitoClient.send(
new InitiateAuthCommand({
AuthFlow: 'USER_PASSWORD_AUTH',
AuthParameters: {
USERNAME: username,
PASSWORD: password
},
ClientId: config.CLIENT_ID
})
)
console.log(response)
const IdToken = response['AuthenticationResult']!['IdToken'] as string
const cognitoPoolId = config.COGNITO_POOL_ID
// exchange toke for credentials
const credentials = fromCognitoIdentityPool({
clientConfig: { region: config.REGION },
identityPoolId: config.IDENTITY_POOL_ID as string
// should not specify for guest users
// logins: {
// [config.COGNITO_POOL_ID]: IdToken,
// },
})
const retrievs = await credentials.call(this)
console.log(retrievs)
}
const decodeToken = async () => {
// const token = "";
// const response = (await decode({
// token: token,
// secret: "b10cda68fe67233283a06a30a76eb161",
// })) as any;
// console.log(response.id_token);
// const idToken = response.id_token;
const idToken = ''
const verifier = CognitoJwtVerifier.create({
userPoolId: config.USER_POOL_ID,
tokenUse: 'id',
clientId: config.CLIENT_ID
})
try {
const payload = await verifier.verify(idToken, {
tokenUse: 'id',
clientId: config.CLIENT_ID
})
console.log('Token is valid. Payload:', payload)
} catch {
console.log('Token not valid!')
}
}
const getImages = async () => {
const token = ''
const { data, status } = await axios.get(config.API_URL_IMAGE, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
console.log(data)
}
const KEY = ''
const ENDPOINT = ''
const testApiKey = async () => {
const { data, status } = await axios.get(ENDPOINT, {
headers: {
'x-api-key': KEY
}
})
console.log(data)
console.log(status)
}
const loadTestApi = async () => {
for (let i = 0; i < 100; i++) {
const { data, status } = await axios.get(ENDPOINT, {
headers: {
'x-api-key': KEY
}
})
console.log(data)
}
}
// loadTestApi();
// testApiKey();
// getImages();
// decodeToken();
// getCredentials("htranminhhai20@gmail.com", "Demo@2023");
// setPass();
// signIn("hai@entest.io", "Demo@2023");
// signUp("demo@entest.io", "Demo@2023");
// confirmSignUp("demo@entest.io", "777502");

API Gateway and Cognito#

Let write simple api request with id token from cognito

const token = ''
const { data, status } = await axios.get(config.API_URL_IMAGE, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
})

Exchange id token for credentials

const getCredentials = async (username: string, password: string) => {
// login and get idtoken
const response = await cognitoClient.send(
new InitiateAuthCommand({
AuthFlow: 'USER_PASSWORD_AUTH',
AuthParameters: {
USERNAME: username,
PASSWORD: password
},
ClientId: config.CLIENT_ID
})
)
console.log(response)
const IdToken = response['AuthenticationResult']!['IdToken'] as string
const cognitoPoolId = config.COGNITO_POOL_ID
// exchange toke for credentials
const credentials = fromCognitoIdentityPool({
clientConfig: { region: config.REGION },
identityPoolId: config.IDENTITY_POOL_ID as string
// should not specify for guest users
// logins: {
// [config.COGNITO_POOL_ID]: IdToken,
// },
})
const retrievs = await credentials.call(this)
console.log(retrievs)
}

API Key and Usage Plan#

  • create api key
  • create usage plan
  • associate the api key with the usage plan
  • enable KEY in api resource and deploy to the sage
  • call api with KEY in the header

Use curl with x-api-key in the header

curl -H "x-api-key: XSHqSZ7RfB8ENqmwOcXBB8SybyFeqrB21CBXRMP5" https://sf9awstayc.execute-api.us-east-1.amazonaws.com/prod/book

Use axios and typescript

const testApiKey = async () => {
const { data, status } = await axios.get(ENDPOINT, {
headers: {
"x-api-key": KEY,
},
});

Then we can send multiple requests in parallel

const loadTestApi = async () => {
for (let i = 0; i < 100; i++) {
const { data, status } = await axios.get(ENDPOINT, {
headers: {
'x-api-key': KEY
}
})
console.log(data)
}
}

Use Aterllery tool to test the api endpoint

npm install -g artillery

Run a very simple test

artillery quick -n 2100 --count 10 ENDPOINT

API Load Test#

Let write a simple python script with threadpool to test the api endpoint

import time
from concurrent.futures import ThreadPoolExecutor
import boto3
import requests
import json
NUM_CONCUR_REQUEST = 101
KEY = ""
ENDPOINT = ""
def send_request(id: int):
"""
send get request
"""
print(f'send request {id}')
response = requests.get(
url=ENDPOINT
)
print(f'response from request {id}')
print(response)
print(response.status_code)
print(response.text)
def sequential_request():
for k in range(1001):
send_request(k)
def load_test():
"""
load test
"""
with ThreadPoolExecutor(max_workers=NUM_CONCUR_REQUEST) as executor:
for k in range(1, NUM_CONCUR_REQUEST):
executor.submit(send_request, k)
if __name__=="__main__":
# send_request(id=1)
# load_test()
# sequential_request()
while True:
load_test()
time.sleep(5)

Next let write a simple Artellery test scenior. Here duration in seconds and arrival rate 50 means there are 50 users sending 50 request per second.

config:
target: https://abc/book
phases:
- duration: 10
arrivalRate: 50
scenarios:
- flow:
- get:
url: '/book'
headers:
x-api-key: ''

Lambda Invocation Test#

Simple test to see how lambda scale

import time
from concurrent.futures import ThreadPoolExecutor
import boto3
# function name
FUNCTION_NAME = "HelloLambdaTest"
# lambda client
lambda_client = boto3.client("lambda")
# number of concurrent request
NUM_CONCUR_REQUEST = 100
def invoke_lambda(id: int) -> str:
"""
invoke lambda
"""
res = lambda_client.invoke(
FunctionName=FUNCTION_NAME
)
print(f'lamda {id} {res["Payload"].read()}')
print("\n")
return res['Payload'].read()
def test_scale_lambda() -> None:
"""
Test how lambda scale
"""
with ThreadPoolExecutor(max_workers=NUM_CONCUR_REQUEST) as executor:
for k in range(1, NUM_CONCUR_REQUEST):
executor.submit(invoke_lambda, k)
if __name__ == "__main__":
while True:
test_scale_lambda()
time.sleep(5)

Use Aterllery tool to test the api endpoint

npm install -g artillery

Run a very simple test

artillery quick -n 2100 --count 10 ENDPOINT

Amplify Hosting#

Let create a stack to deploy the GitHub repostiory using AmplifyHosting

import { SecretValue, Stack, StackProps, aws_codebuild } from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as Amplify from '@aws-cdk/aws-amplify-alpha'
interface AmplifyHostingProps extends StackProps {
owner: string
repository: string
token: string
envVariables: any
commands: any
}
export class AmplifyHosting extends Stack {
constructor(scope: Construct, id: string, props: AmplifyHostingProps) {
super(scope, id, props)
const amplify = new Amplify.App(this, 'CDKForAamplifyHosting', {
sourceCodeProvider: new Amplify.GitHubSourceCodeProvider({
owner: props.owner,
repository: props.repository,
oauthToken: SecretValue.secretsManager(props.token)
}),
buildSpec: aws_codebuild.BuildSpec.fromObjectToYaml({
version: '1.0',
frontend: {
phases: {
preBuild: {
commands: ['npm ci']
},
build: {
commands: props.commands
}
},
artifacts: {
baseDirectory: '.next',
files: ['**/*']
},
cache: {
path: ['node_modules/**/*']
}
}
}),
platform: Amplify.Platform.WEB_COMPUTE,
environmentVariables: props.envVariables
})
amplify.addBranch('main', { stage: 'PRODUCTION' })
}
}

CloudFront#

Let create a CloudFront stack for caching and pre-signed url for protected content

import {
CfnOutput,
RemovalPolicy,
Stack,
StackProps,
aws_cloudfront,
aws_iam,
aws_s3
} from 'aws-cdk-lib'
import { Construct } from 'constructs'
interface CloudFrontProps extends StackProps {
publicGroupId: string
}
export class CloudFrontSignStack extends Stack {
constructor(scope: Construct, id: string, props: CloudFrontProps) {
super(scope, id, props)
// create s3 bucket
const bucket = new aws_s3.Bucket(this, 'BucketForCloudFrontSignedUrlDemo', {
bucketName: 'bucket-cloudfront-signed-url-demo',
// not production recommended
removalPolicy: RemovalPolicy.DESTROY,
// not production recommended
autoDeleteObjects: true,
// block public read
publicReadAccess: false,
// block public access - production recommended
blockPublicAccess: aws_s3.BlockPublicAccess.BLOCK_ALL
})
// legacy origin access identity
const oai = new aws_cloudfront.CfnCloudFrontOriginAccessIdentity(
this,
'OriginAccessIdentityForSignedUrlDemo',
{
cloudFrontOriginAccessIdentityConfig: {
comment: 'demo'
}
}
)
// new origin access control
// const oac = new aws_cloudfront.CfnOriginAccessControl(
// this,
// "OriginAccessControlForSignedUrlDemo",
// {
// originAccessControlConfig: {
// name: "OriginAccessControlForSignedUrlDemo",
// originAccessControlOriginType: "s3",
// signingBehavior: "always",
// signingProtocol: "sigv4",
// },
// }
// );
// cloudfront keygroup
const keygroup = new aws_cloudfront.CfnKeyGroup(
this,
'CloudFrontKeyGroupDemo',
{
keyGroupConfig: {
items: [props.publicGroupId],
name: 'CloudFrontKeyGroupDemo',
comment: 'demo keygroup to generate signed url'
}
}
)
// create cloudfront districution
const dist = new aws_cloudfront.CfnDistribution(
this,
'CloudFrontDistributionSignedUrlDemo',
{
distributionConfig: {
// aliases: [],
enabled: true,
defaultCacheBehavior: {
targetOriginId: bucket.bucketDomainName,
viewerProtocolPolicy: 'allow-all',
compress: true,
forwardedValues: {
queryString: false,
cookies: {
forward: 'none'
// whitelistedNames: [""],
}
}
},
cacheBehaviors: [
{
pathPattern: '/private-data/*',
targetOriginId: bucket.bucketDomainName,
viewerProtocolPolicy: 'allow-all',
allowedMethods: ['GET', 'HEAD'],
// cachedMethods: [],
cachePolicyId: '658327ea-f89d-4fab-a63d-7e88639e58f6',
compress: true,
trustedKeyGroups: [keygroup.attrId]
// trustedSigners: [],
}
],
origins: [
{
domainName: bucket.bucketDomainName,
id: bucket.bucketDomainName,
// originAccessControlId: oac.attrId,
// originPath: "",
// originAccessControlId: "",
s3OriginConfig: {
originAccessIdentity: `origin-access-identity/cloudfront/${oai.attrId}`
}
}
]
// s3Origin: {
// dnsName: bucket.bucketDomainName,
// originAccessIdentity: oai.ref,
// },
}
}
)
// s3 bucket policy grant oac access
bucket.addToResourcePolicy(
new aws_iam.PolicyStatement({
actions: ['s3:GetObject', 's3:PutObject'],
resources: [bucket.arnForObjects('*')],
principals: [
// new aws_iam.CanonicalUserPrincipal(
// cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId
// ),
// "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E2DWROBFVR8YDR"
new aws_iam.ArnPrincipal(
`arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${oai.attrId}`
)
// new aws_iam.ServicePrincipal("cloudfront.amazonaws.com"),
]
// conditions: {
// StringEquals: {
// "AWS:SourceArn": `arn:aws:cloudfront::${this.account}:distribution/${dist.attrId}`,
// },
// },
})
)
// dist.addDependency(oai);
new CfnOutput(this, 'OAIIdentity', {
exportName: 'OAIIdentity',
value: oai.attrId
})
}
}

Reference#