Skip to content

Instantly share code, notes, and snippets.

@numtel
Created June 24, 2024 21:42
Show Gist options
  • Save numtel/7492ee983bcf27501411c9e4769786a5 to your computer and use it in GitHub Desktop.
Save numtel/7492ee983bcf27501411c9e4769786a5 to your computer and use it in GitHub Desktop.
S3 bucket with Cloudfront and a lambda for uploads with basic auth
# Invoke using a command like this:
curl -X POST "https://klho5x33jfupy46mmaeayx5lbi0ywrdm.lambda-url.us-west-2.on.aws/" \
-H "Content-Type: application/json" \
-d '{
"file": "Rk9PQkFSMjAwMAo=",
"filename": "foo.txt",
"secret": "foobar2000"
}'
AWSTemplateFormatVersion: '2010-09-09'
Description: Template to create an S3 bucket, CloudFront, and a Lambda function for file uploads.
Parameters:
BucketName:
Type: String
Description: Name of the S3 bucket to be created.
Default: mybucket
CloudfrontCNAME:
Type: String
Description: Name of the S3 bucket to be created. (See todo below to enable this)
Default: snark-artifacts.pse.dev
AuthSecret:
Type: String
Description: To be passed by the client (example simple auth)
Default: foobar2000
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
BucketName: !Ref BucketName
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Statement:
- Sid: PublicGetObject
Effect: Allow
Principal: "*"
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: "LambdaExecutionPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "*"
- PolicyName: LambdaS3Access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3Client = new S3Client({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
const data = JSON.parse(event.body);
// TODO make this auth more secure
if(data.secret !== process.env.AUTH_SECRET) {
return {
statusCode: 400,
body: JSON.stringify({ message: 'Invalid Auth Secret' }),
};
}
const base64Data = Buffer.from(data.file, 'base64');
const params = {
Bucket: process.env.BUCKET,
Key: data.filename,
Body: base64Data,
ContentEncoding: 'base64',
ContentType: 'application/octet-stream'
};
try {
const response = await s3Client.send(new PutObjectCommand(params));
return {
statusCode: 200,
body: JSON.stringify({ message: 'File uploaded successfully', response }),
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({ message: 'Failed to upload file', error: err.message }),
};
}
};
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: nodejs20.x
MemorySize: 128 # Adjust as needed
Timeout: 60 # Adjust as needed
Environment:
Variables:
BUCKET: !Ref S3Bucket
AUTH_SECRET: !Ref AuthSecret
LambdaUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn: !GetAtt LambdaFunction.Arn
Cors:
AllowOrigins:
- '*'
LambdaFunctionUrlPermission:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunctionUrl"
FunctionName: !Ref LambdaFunction
Principal: "*"
FunctionUrlAuthType: NONE
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: S3Origin
DomainName: !GetAtt S3Bucket.DomainName
S3OriginConfig:
OriginAccessIdentity: ""
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD]
CachedMethods: [GET, HEAD]
ForwardedValues:
QueryString: false
Cookies:
Forward: none
Enabled: true
# To Enable CNAME, see the docs
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements
# Aliases:
# - !Ref CloudfrontCNAME
Outputs:
LambdaFunctionUrl:
Description: URL for the Lambda function
Value: !GetAtt LambdaUrl.FunctionUrl
CloudFrontURL:
Description: HTTPS URL to the CloudFront distribution
Value: !Sub 'https://${CloudFrontDistribution.DomainName}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment