Skip to content

Instantly share code, notes, and snippets.

@tyage
Created September 3, 2023 05:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tyage/cf1d2221720776ce3c5562e14189b732 to your computer and use it in GitHub Desktop.
Save tyage/cf1d2221720776ce3c5562e14189b732 to your computer and use it in GitHub Desktop.
Midnight Sun CTF 2023 Finals - Web Obelix
AWSTemplateFormatVersion: 2010-09-09
Description: A kick-ass cloud app for hacker movienights!
Parameters:
AppName:
Type: String
Default: api-movies
Description: Name of application.
StageName:
Type: String
Default: prod
Description: Production ready babyyy!!! (made by 10x cloud architect infrastructure solutions management devops ux engineer)
Resources:
DynamoDBTable:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: Movies
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: moviename
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
- AttributeName: moviename
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
GlobalSecondaryIndexes:
- IndexName: MovieName-Index
KeySchema:
- AttributeName: moviename
KeyType: HASH
- AttributeName: id
KeyType: RANGE
Projection:
ProjectionType: INCLUDE
NonKeyAttributes:
- description
- year
- director
- movielength
- color
- posterpos
- image
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
APIGatewayRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Policies:
- PolicyName: APIGatewayDynamoDBPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'dynamodb:PutItem'
- 'dynamodb:Query'
Resource: !Sub
- '${varTableArn}*'
- varTableArn: !GetAtt DynamoDBTable.Arn
Api:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: !Sub '${AppName}'
ApiKeySourceType: HEADER
MoviesResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref Api
ParentId: !GetAtt Api.RootResourceId
PathPart: 'movies'
MoviesMethodPost:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref Api
ResourceId: !Ref MoviesResource
HttpMethod: POST
ApiKeyRequired: true
AuthorizationType: NONE
Integration:
Type: AWS
Credentials: !GetAtt APIGatewayRole.Arn
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:dynamodb:action/PutItem'
RequestTemplates:
application/json: "{\"TableName\":\"Movies\",\"Item\":{\"id\":{\"S\":\"$context.extendedRequestId\"},\"moviename\":{\"S\":\"$input.path('$.moviename')\"},\"description\":{\"S\":\"$input.path('$.description')\"},\"year\":{\"S\":\"$input.path('$.year')\"},\"director\":{\"S\":\"$input.path('$.director')\"},\"movielength\":{\"S\":\"$input.path('$.movielength')\"},\"color\":{\"S\":\"$input.path('$.color')\"},\"posterpos\":{\"S\":\"$input.path('$.posterpos')\"},\"image\":{\"S\":\"$input.path('$.image')\"}}}"
IntegrationResponses:
- StatusCode: '200'
ResponseTemplates:
application/json: "{}"
MethodResponses:
- StatusCode: '200'
MovieNameResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref Api
ParentId: !Ref MoviesResource
PathPart: '{moviename}'
MovieNameMethodGet:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref Api
ResourceId: !Ref MovieNameResource
HttpMethod: GET
ApiKeyRequired: false
AuthorizationType: NONE
RequestParameters:
method.request.path.moviename: true
Integration:
Type: AWS
Credentials: !GetAtt APIGatewayRole.Arn
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:dynamodb:action/Query'
RequestParameters:
integration.request.path.moviename: method.request.path.moviename
RequestTemplates:
application/json: "{\"TableName\":\"Movies\",\"IndexName\":\"MovieName-Index\",\"KeyConditionExpression\":\"moviename=:moviename\",\"FilterExpression\": \"not contains(#description, :flagstring)\",\"ExpressionAttributeNames\": {\"#description\": \"description\"},\"ExpressionAttributeValues\":{\":moviename\":{\"S\":\"$util.escapeJavaScript($input.params('moviename'))\"},\":flagstring\":{\"S\":\"midnight\"}}}"
IntegrationResponses:
- StatusCode: '200'
ResponseParameters:
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
MethodResponses:
- StatusCode: '200'
ResponseParameters:
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn:
- MovieNameMethodGet
Properties:
RestApiId: !Ref Api
StageName: !Sub '${StageName}'
ApiKey:
Type: 'AWS::ApiGateway::ApiKey'
DependsOn:
- ApiDeployment
Properties:
Enabled: true
Name: !Sub '${AppName}-apikey'
StageKeys:
- RestApiId: !Ref Api
StageName: !Sub '${StageName}'
ApiUsagePlan:
Type: 'AWS::ApiGateway::UsagePlan'
DependsOn:
- ApiDeployment
Properties:
ApiStages:
- ApiId: !Ref Api
Stage: !Sub '${StageName}'
Throttle:
RateLimit: 500
BurstLimit: 1000
UsagePlanName: !Sub '${AppName}-usage-plan'
Quota:
Limit: 10000
Period: MONTH
ApiUsagePlanKey:
Type: 'AWS::ApiGateway::UsagePlanKey'
Properties:
KeyType: API_KEY
KeyId: !Ref ApiKey
UsagePlanId: !Ref ApiUsagePlan
FrontendBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: 'tyage.obelix-movie-app.play.hfsc.tf'
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
BucketPolicy:
Type: 'AWS::S3::BucketPolicy'
Properties:
Bucket: !Ref FrontendBucket
PolicyDocument:
Id: MyPolicy
Version: 2012-10-17
Statement:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: '*'
Action: 's3:GetObject'
Resource: !Sub ${FrontendBucket.Arn}/*
S3DeployFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: !Sub |
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const response = require('cfn-response');
exports.handler = function(event, context) {
var bucket = "${FrontendBucket}";
var indexpage = `
<head>
<meta name="referrer" content="no-referrer">
</head>
<!-- css borrowed from https://codepen.io/simoberny/pen/qxxOqj -->
<link rel='stylesheet' href='https://fonts.googleapis.com/icon?family=Material+Icons'>
<style>
@import url("https://fonts.googleapis.com/css?family=Montserrat:300,400,700,800");
* {
box-sizing: border-box;
margin: 0;
}
html, body {
margin: 0;
background: black;
font-family: "Montserrat", helvetica, arial, sans-serif;
font-size: 14px;
font-weight: 400;
}
.movie_card {
position: relative;
display: block;
width: 800px;
height: 350px;
margin: 100px auto;
overflow: hidden;
border-radius: 10px;
transition: all 0.4s;
}
.movie_card:hover {
transform: scale(1.02);
transition: all 0.4s;
}
.movie_card .info_section {
position: relative;
width: 100%;
height: 100%;
background-blend-mode: multiply;
z-index: 2;
border-radius: 10px;
}
.movie_card .info_section .movie_header {
position: relative;
padding: 25px;
height: 40%;
}
.movie_card .info_section .movie_header h1 {
color: #fff;
font-weight: 400;
}
.movie_card .info_section .movie_header h4 {
color: #9ac7fa;
font-weight: 400;
}
.movie_card .info_section .movie_header .minutes {
display: inline-block;
margin-top: 10px;
color: #fff;
padding: 5px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.13);
}
.movie_card .info_section .movie_header .type {
display: inline-block;
color: #cee4fd;
margin-left: 10px;
}
.movie_card .info_section .movie_header .locandina {
position: relative;
float: left;
margin-right: 20px;
height: 120px;
box-shadow: 0 0 20px -10px rgba(0, 0, 0, 0.5);
}
.movie_card .info_section .movie_desc {
padding: 25px;
height: 50%;
}
.movie_card .info_section .movie_desc .text {
color: #cfd6e1;
}
.movie_card .info_section .movie_social {
height: 10%;
padding-left: 15px;
padding-bottom: 20px;
}
.movie_card .info_section .movie_social ul {
list-style: none;
padding: 0;
}
.movie_card .info_section .movie_social ul li {
display: inline-block;
color: rgba(255, 255, 255, 0.4);
transition: color 0.3s;
transition-delay: 0.15s;
margin: 0 10px;
}
.movie_card .info_section .movie_social ul li:hover {
transition: color 0.3s;
color: rgba(255, 255, 255, 0.8);
}
.movie_card .info_section .movie_social ul li i {
font-size: 19px;
cursor: pointer;
}
.movie_card .blur_back {
position: absolute;
top: 0;
z-index: 1;
height: 100%;
right: 0;
background-size: cover;
border-radius: 11px;
}
@media screen and (min-width: 1px) {
.movie_header {
width: 60%;
}
.movie_desc {
width: 50%;
}
.info_section {
background: linear-gradient(to right, #0d0d0c 50%, transparent 100%);
}
.blur_back {
width: 80%;
background-position: -100% 21%;
}
}
#ave {
margin-bottom: 200px;
}
.link {
text-decoration: none;
color: #fff;
}
</style>
<div class="container" id="container">
</div>
<script>
Object.defineProperty(String.prototype, 'capitalize', {
value: function() {
return this.charAt(0).toUpperCase() + this.slice(1);
},
enumerable: false
});
baseurl = "https://${Api}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/movies/";
movies = ["hackers", "sneakers", "wargames", "matrix"];
async function getMovies(){
for(movie of movies){
fetch(baseurl+movie).then(response => response.json()).then(function(response){
moviename = response.Items[0].moviename.S;
moviedescription = response.Items[0].description.S;
movieyear = response.Items[0].year.S;
moviedirector = response.Items[0].director.S;
movielength = response.Items[0].movielength.S;
moviecolor = response.Items[0].color.S;
movieposterpos = response.Items[0].posterpos.S;
movieimage = response.Items[0].image.S;
moviename = moviename.capitalize();
container.innerHTML += \`<div class="movie_card" id="ave" style='box-shadow: 0px 0px 150px -45px \`+moviecolor+\`;'>
<div class="info_section">
<div class="movie_header">
<img class="locandina" src="\`+movieimage+\`"/>
<h1>\`+moviename+\`</h1>
<h4>\`+movieyear+\`, \`+moviedirector+\`</h4>
<span class="minutes">\`+movielength+\` min</span>
</div>
<div class="movie_desc">
<p class="text">
\`+moviedescription+\`
</p>
</div>
</div>
<div class="blur_back ave_back" style='background-position: \`+movieposterpos+\` !important;background-image: url("\`+movieimage+\`");background-position: 25%;'></div>
</div>\`;
});
}
}
getMovies();
</script>
`;
var errorpage = "error, not found";
if(event.RequestType == "Delete"){
s3.deleteObject({Bucket : bucket, Key : "index.html"}, function(){});
console.log("deleted index");
s3.deleteObject({Bucket : bucket, Key : "error.html"}, function(){});
console.log("deleted error");
}else{
s3.putObject({Bucket : bucket, Key : "index.html", Body : indexpage, ContentType: "text/html"}, function(){});
console.log("updated index");
s3.putObject({Bucket : bucket, Key : "error.html", Body : errorpage, ContentType: "text/html"}, function(){});
console.log("updated error");
}
response.send(event, context, response.SUCCESS, {Response: 'Updated index and error page!'});
return JSON.stringify({Response: 'Updated index and error page!'});
}
Handler: index.handler
Role: !GetAtt S3DeployRole.Arn
Runtime: nodejs16.x
FunctionName: S3DeployFunction
MemorySize: 128
Timeout: 30
S3DeployRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
InvokeS3DeployFunction:
Type: Custom::InvokeS3DeployFunction
Version: '1.0'
Properties:
ServiceToken: !GetAtt S3DeployFunction.Arn
Outputs:
S3Url:
Description: API URL
Value: !GetAtt 'FrontendBucket.WebsiteURL'
ApiRootUrl:
Description: API URL
Value: !Sub
- 'https://${ApiId}.execute-api.${AWS::Region}.amazonaws.com/${StageName}'
- ApiId: !Ref Api
ApiKeyId:
Description: key
Value: !Ref ApiKey
Status:
Description: Status
Value: 'Done! Now you can POST to the backend and insert the movies :)'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment