Midnight Sun CTF 2023 Finals - Web Obelix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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