From ea74b18af63253ba518cb67f237551aade617ba7 Mon Sep 17 00:00:00 2001 From: Megakuul Date: Thu, 12 Sep 2024 23:32:18 +0200 Subject: [PATCH] feat: update structure + start implementing core router --- api/user/registeruser/registeruser.go | 2 +- router/go.mod | 51 ++ router/go.sum | 137 +++ router/main.go | 87 ++ router/routecontext/routecontext.go | 15 + router/routerequest/routerequest.go | 122 +++ template.yaml | 1217 ++++++++++++++----------- 7 files changed, 1107 insertions(+), 524 deletions(-) create mode 100644 router/go.mod create mode 100644 router/go.sum create mode 100644 router/main.go create mode 100644 router/routecontext/routecontext.go create mode 100644 router/routerequest/routerequest.go diff --git a/api/user/registeruser/registeruser.go b/api/user/registeruser/registeruser.go index f29e609..a26baf4 100644 --- a/api/user/registeruser/registeruser.go +++ b/api/user/registeruser/registeruser.go @@ -16,7 +16,7 @@ import ( "github.com/megakuul/battleshiper/lib/model/user" ) -// HandleRegisterUser registers a user in the database (if not existent) based on the cognito user attributes. +// HandleRegisterUser registers a user in the database (if not existent). func HandleRegisterUser(request events.APIGatewayV2HTTPRequest, transportCtx context.Context, routeCtx routecontext.Context) (events.APIGatewayV2HTTPResponse, error) { code, err := runHandleRegisterUser(request, transportCtx, routeCtx) if err != nil { diff --git a/router/go.mod b/router/go.mod new file mode 100644 index 0000000..0f961bb --- /dev/null +++ b/router/go.mod @@ -0,0 +1,51 @@ +module github.com/megakuul/battleshiper/api/user + +go 1.22.4 + +require ( + github.com/aws/aws-lambda-go v1.47.0 + github.com/aws/aws-sdk-go-v2/config v1.27.23 + github.com/megakuul/battleshiper/lib/helper v0.1.10 + github.com/megakuul/battleshiper/lib/model v0.2.4 + github.com/megakuul/battleshiper/lib/router v0.1.0 + go.mongodb.org/mongo-driver v1.16.0 +) + +require ( + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.14.0 // indirect +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.23 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect + github.com/aws/smithy-go v1.20.4 // indirect + github.com/go-playground/webhooks/v6 v6.3.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/go-github/v63 v63.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect +) diff --git a/router/go.sum b/router/go.sum new file mode 100644 index 0000000..b42bdf7 --- /dev/null +++ b/router/go.sum @@ -0,0 +1,137 @@ +github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= +github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= +github.com/aws/aws-sdk-go-v2/config v1.27.23 h1:Cr/gJEa9NAS7CDAjbnB7tHYb3aLZI2gVggfmSAasDac= +github.com/aws/aws-sdk-go-v2/config v1.27.23/go.mod h1:WMMYHqLCFu5LH05mFOF5tsq1PGEMfKbu083VKqLCd0o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.23 h1:G1CfmLVoO2TdQ8z9dW+JBc/r8+MqyPQhXCafNZcXVZo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.23/go.mod h1:V/DvSURn6kKgcuKEk4qwSwb/fZ2d++FFARtWSbXnLqY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 h1:ZoYRD8IJqPkzjBnpokiMNO6L/DQprtpVpD6k0YSaF5U= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1/go.mod h1:GlRarZzIMl9VDi0mLQt+qQOuEkVFPnTkkjyugV1uVa8= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 h1:lCEv9f8f+zJ8kcFeAjRZsekLd/x5SAm96Cva+VbUdo8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-playground/webhooks/v6 v6.3.0 h1:zBLUxK1Scxwi97TmZt5j/B/rLlard2zY7P77FHg58FE= +github.com/go-playground/webhooks/v6 v6.3.0/go.mod h1:GCocmfMtpJdkEOM1uG9p2nXzg1kY5X/LtvQgtPHUaaA= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE= +github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/megakuul/battleshiper/lib/helper v0.1.8 h1:U2KH5fLlunX1dZtPVAwPgrdo4hcb8IiHJITbddABGj8= +github.com/megakuul/battleshiper/lib/helper v0.1.8/go.mod h1:NFTOcsX4NL2j9HdLsG93lRrq28CnD3qKsqQrt7HyYO8= +github.com/megakuul/battleshiper/lib/helper v0.1.10 h1:yW9dpjnGBbfWKuT5FhtFbdGYRQMqBhkbS07Hpn6kqtk= +github.com/megakuul/battleshiper/lib/helper v0.1.10/go.mod h1:GDzLE1DbXzjuh9Z6wg2tYdOLqlRDhXmdFzW05dnlAwo= +github.com/megakuul/battleshiper/lib/model v0.2.0 h1:8re8vA9Q5i2VLZntG7i080VLrhGW1vXFZRgeASkwuOw= +github.com/megakuul/battleshiper/lib/model v0.2.0/go.mod h1:N2fHQkJOezOs4B9m0/lMLAdhaaVahf5zmarhfHLzUvQ= +github.com/megakuul/battleshiper/lib/model v0.2.2 h1:e+4i5B0BlugOWxTbZPiwo+CykeP7gYsPlSK7ZhL7Rds= +github.com/megakuul/battleshiper/lib/model v0.2.2/go.mod h1:N2fHQkJOezOs4B9m0/lMLAdhaaVahf5zmarhfHLzUvQ= +github.com/megakuul/battleshiper/lib/model v0.2.4/go.mod h1:N2fHQkJOezOs4B9m0/lMLAdhaaVahf5zmarhfHLzUvQ= +github.com/megakuul/battleshiper/lib/router v0.1.0 h1:gbVsSiiIkzkbZE/SlVBSU9zUaiV5XHaeertVCV6uTDo= +github.com/megakuul/battleshiper/lib/router v0.1.0/go.mod h1:+YSiwNGSRccnY29LOBU/iq7np+sz8R8S760bUnTHmdA= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= +go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/router/main.go b/router/main.go new file mode 100644 index 0000000..710027f --- /dev/null +++ b/router/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go-v2/config" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/megakuul/battleshiper/lib/helper/auth" + "github.com/megakuul/battleshiper/lib/helper/database" + "github.com/megakuul/battleshiper/lib/model/subscription" + "github.com/megakuul/battleshiper/lib/model/user" + "github.com/megakuul/battleshiper/lib/router" + + "github.com/megakuul/battleshiper/api/user/fetchinfo" + "github.com/megakuul/battleshiper/api/user/registeruser" + "github.com/megakuul/battleshiper/api/user/routecontext" +) + +var ( + REGION = os.Getenv("AWS_REGION") + JWT_CREDENTIAL_ARN = os.Getenv("JWT_CREDENTIAL_ARN") + DATABASE_ENDPOINT = os.Getenv("DATABASE_ENDPOINT") + DATABASE_NAME = os.Getenv("DATABASE_NAME") + DATABASE_SECRET_ARN = os.Getenv("DATABASE_SECRET_ARN") +) + +func main() { + if err := run(); err != nil { + log.Printf("ERROR INITIALIZATION: %v\n", err) + os.Exit(1) + } +} + +func run() error { + awsConfig, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(REGION)) + if err != nil { + return fmt.Errorf("failed to load aws config: %v", err) + } + + databaseOptions, err := database.CreateDatabaseOptions(awsConfig, context.TODO(), DATABASE_SECRET_ARN, DATABASE_ENDPOINT, DATABASE_NAME) + if err != nil { + return err + } + databaseClient, err := mongo.Connect(context.TODO(), databaseOptions) + if err != nil { + return err + } + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + if err = databaseClient.Disconnect(ctx); err != nil { + log.Printf("ERROR CLEANUP: %v\n", err) + } + cancel() + }() + databaseHandle := databaseClient.Database(DATABASE_NAME) + + database.SetupIndexes(databaseHandle.Collection(user.USER_COLLECTION), context.TODO(), []database.Index{ + {FieldNames: []string{"id"}, SortingOrder: 1, Unique: true}, + }) + + database.SetupIndexes(databaseHandle.Collection(subscription.SUBSCRIPTION_COLLECTION), context.TODO(), []database.Index{ + {FieldNames: []string{"id"}, SortingOrder: 1, Unique: true}, + }) + + jwtOptions, err := auth.CreateJwtOptions(awsConfig, context.TODO(), JWT_CREDENTIAL_ARN, 0) + if err != nil { + return err + } + + httpRouter := router.NewRouter(routecontext.Context{ + JwtOptions: jwtOptions, + Database: databaseHandle, + }) + + httpRouter.AddRoute("GET", "/api/user/fetchinfo", fetchinfo.HandleFetchInfo) + httpRouter.AddRoute("POST", "/api/user/registeruser", registeruser.HandleRegisterUser) + + lambda.Start(httpRouter.Route) + + return nil +} diff --git a/router/routecontext/routecontext.go b/router/routecontext/routecontext.go new file mode 100644 index 0000000..a4b5a1c --- /dev/null +++ b/router/routecontext/routecontext.go @@ -0,0 +1,15 @@ +package routecontext + +import ( + "net/http" + + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +// Context provides data to route handlers. +type Context struct { + S3Bucket string + S3Client *s3.Client + HttpSuffix string + HttpClient *http.Client +} diff --git a/router/routerequest/routerequest.go b/router/routerequest/routerequest.go new file mode 100644 index 0000000..71bbb72 --- /dev/null +++ b/router/routerequest/routerequest.go @@ -0,0 +1,122 @@ +package registeruser + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/megakuul/battleshiper/api/user/routecontext" +) + +// HandleRouteRequest routes request either to s3 or to the corresponding lambda http endpoint. +func HandleRouteRequest(request events.ALBTargetGroupRequest, transportCtx context.Context, routeCtx routecontext.Context) (events.ALBTargetGroupResponse, error) { + project, exists := request.Headers["Battleshiper-Project"] + if !exists || project == "" { + return events.ALBTargetGroupResponse{ + StatusCode: 404, + Headers: map[string]string{"Content-Type": "text/plain"}, + Body: "Project not found", + }, nil + } + + if strings.HasSuffix(request.Path, ".html") && request.HTTPMethod == "GET" { + response, code, err := proxyPrerendered(request, transportCtx, routeCtx) + if err != nil { + return events.ALBTargetGroupResponse{ + StatusCode: code, + Headers: map[string]string{"Content-Type": "text/plain"}, + Body: err.Error(), + }, nil + } + response.StatusCode = code + return *response, nil + } + + response, code, err := proxyServer(request, transportCtx, routeCtx) + if err != nil { + return events.ALBTargetGroupResponse{ + StatusCode: code, + Headers: map[string]string{"Content-Type": "text/plain"}, + Body: err.Error(), + }, nil + } + response.StatusCode = code + return *response, nil +} + +func proxyPrerendered(request events.ALBTargetGroupRequest, transportCtx context.Context, routeCtx routecontext.Context) (*events.ALBTargetGroupResponse, int, error) { + objectOutput, err := routeCtx.S3Client.GetObject(transportCtx, &s3.GetObjectInput{ + Bucket: aws.String(routeCtx.S3Bucket), + Key: aws.String(request.Path), + }) + if err != nil { + var nsk *s3types.NoSuchKey + if errors.As(err, &nsk) { + return nil, http.StatusNotFound, fmt.Errorf("prerendered asset not found") + } else { + return nil, http.StatusInternalServerError, err + } + } + + body, err := io.ReadAll(objectOutput.Body) + if err != nil { + return nil, http.StatusInternalServerError, err + } + return &events.ALBTargetGroupResponse{ + Headers: map[string]string{ + "Content-Type": "text/html", + }, + Body: string(body), + }, http.StatusOK, nil +} + +func proxyServer(request events.ALBTargetGroupRequest, transportCtx context.Context, routeCtx routecontext.Context) (*events.ALBTargetGroupResponse, int, error) { + query := url.Values{} + for key, val := range request.QueryStringParameters { + query.Set(key, val) + } + + rawPath := fmt.Sprintf("https://%s.%s/%s", request.Path) // TODO find endpoint + if len(query) > 0 { + rawPath = fmt.Sprintf("%s?%s", rawPath, query.Encode()) + } + + httpRequest, err := http.NewRequestWithContext( + transportCtx, + request.HTTPMethod, + rawPath, + strings.NewReader(request.Body), + ) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + httpResponse, err := routeCtx.HttpClient.Do(httpRequest) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + body, err := io.ReadAll(httpResponse.Body) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + headers := map[string][]string{} + for key, val := range httpResponse.Header { + headers[key] = val + } + + return &events.ALBTargetGroupResponse{ + MultiValueHeaders: headers, + Body: string(body), + }, http.StatusOK, nil +} diff --git a/template.yaml b/template.yaml index c041662..885261e 100644 --- a/template.yaml +++ b/template.yaml @@ -4,10 +4,13 @@ Transform: AWS::Serverless-2016-10-31 Parameters: ApplicationDomain: Type: String - Description: "Domain used for the battleshiper deployment." + Description: "Domain used for the battleshiper endpoints." ApplicationDomainCertificateArn: Type: String Description: "ARN of the ACM certificate for the ApplicationDomain." + ApplicationDomainWildcardCertificateArn: + Type: String + Description: "ARN of the wildcard ACM certificate for '*.ApplicationDomain'." GithubOAuthClientCredentialArn: Type: String Description: "ARN of the Secret containing Github Application App ID, App Secret Key, Client ID & Client Secret ('app_id', 'app_secret', 'client_id' & 'client_secret')." @@ -280,77 +283,6 @@ Resources: RouteTableId: !Ref BattleshiperBuildSubnetRouteTable - # ============================================ - # =========== Database ======================= - # ============================================ - - BattleshiperDbAdminSecret: - Type: AWS::SecretsManager::Secret - Properties: - Name: "battleshiper-db-cluster-admin-credentials" - Description: "Secret for battleshiper db administrator credentials." - GenerateSecretString: - SecretStringTemplate: '{"username": "admin"}' - GenerateStringKey: "password" - PasswordLength: 60 - ExcludeCharacters: '"@/\\' - - BattleshiperDbApiSecret: - Type: AWS::SecretsManager::Secret - Properties: - Name: "battleshiper-db-cluster-api-credentials" - Description: "Secret for battleshiper api access credentials." - GenerateSecretString: - SecretStringTemplate: '{"username": "api"}' - GenerateStringKey: "password" - PasswordLength: 40 - ExcludeCharacters: '"@/\\' - - BattleshiperDbApiSecretAttachment: - Type: AWS::SecretsManager::SecretTargetAttachment - Properties: - SecretId: !Ref BattleshiperDbApiSecret - TargetId: !Ref BattleshiperDb - TargetType: "AWS::DocDB::DBCluster" - - BattleshiperDbApiSecretPolicy: - Type: AWS::IAM::Policy - Properties: - PolicyName: "battleshiper-db-cluster-api-credentials-access" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "secretsmanager:GetSecretValue" - Resource: - - !Ref BattleshiperDbApiSecret - Roles: - - !Ref BattleshiperUserFuncRole - - !Ref BattleshiperAdminFuncRole - - !Ref BattleshiperResourceFuncRole - - !Ref BattleshiperPipelineFuncRole - - BattleshiperDb: - Type: AWS::DocDBElastic::Cluster - Properties: - AdminUserName: !Sub '{{resolve:secretsmanager:BattleshiperDbAdminSecret:SecretString:username}}' - # Admin password is set to the secret's ARN. - # This usage is not documented unfortunaly: https://github.com/aws/aws-cdk/issues/28935. - AdminUserPassword: !Ref BattleshiperDbAdminSecret - AuthType: "SECRET_ARN" - ClusterName: "battleshiper-db-cluster" - ShardCapacity: 2 - ShardCount: 1 - ShardInstanceCount: 3 - SubnetIds: - - !Ref BattleshiperDbSubnet1 - - !Ref BattleshiperDbSubnet2 - - !Ref BattleshiperDbSubnet3 - VpcSecurityGroupIds: - - !Ref BattleshiperDbAccessGroup - - # ============================================ # =========== CloudWatch Logging ============= # ============================================ @@ -444,6 +376,78 @@ Resources: Roles: - !Ref BattleshiperResourceFuncRole + + # ============================================ + # =========== Database ======================= + # ============================================ + + BattleshiperDbAdminSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: "battleshiper-db-cluster-admin-credentials" + Description: "Secret for battleshiper db administrator credentials." + GenerateSecretString: + SecretStringTemplate: '{"username": "admin"}' + GenerateStringKey: "password" + PasswordLength: 60 + ExcludeCharacters: '"@/\\' + + BattleshiperDbApiSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: "battleshiper-db-cluster-api-credentials" + Description: "Secret for battleshiper api access credentials." + GenerateSecretString: + SecretStringTemplate: '{"username": "api"}' + GenerateStringKey: "password" + PasswordLength: 40 + ExcludeCharacters: '"@/\\' + + BattleshiperDbApiSecretAttachment: + Type: AWS::SecretsManager::SecretTargetAttachment + Properties: + SecretId: !Ref BattleshiperDbApiSecret + TargetId: !Ref BattleshiperDb + TargetType: "AWS::DocDB::DBCluster" + + BattleshiperDbApiSecretPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: "battleshiper-db-cluster-api-credentials-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "secretsmanager:GetSecretValue" + Resource: + - !Ref BattleshiperDbApiSecret + Roles: + - !Ref BattleshiperUserFuncRole + - !Ref BattleshiperAdminFuncRole + - !Ref BattleshiperResourceFuncRole + - !Ref BattleshiperPipelineFuncRole + + BattleshiperDb: + Type: AWS::DocDBElastic::Cluster + Properties: + AdminUserName: !Sub '{{resolve:secretsmanager:BattleshiperDbAdminSecret:SecretString:username}}' + # Admin password is set to the secret's ARN. + # This usage is not documented unfortunaly: https://github.com/aws/aws-cdk/issues/28935. + AdminUserPassword: !Ref BattleshiperDbAdminSecret + AuthType: "SECRET_ARN" + ClusterName: "battleshiper-db-cluster" + ShardCapacity: 2 + ShardCount: 1 + ShardInstanceCount: 3 + SubnetIds: + - !Ref BattleshiperDbSubnet1 + - !Ref BattleshiperDbSubnet2 + - !Ref BattleshiperDbSubnet3 + VpcSecurityGroupIds: + - !Ref BattleshiperDbAccessGroup + + # ============================================ # =========== Auth Secrets =================== # ============================================ @@ -495,6 +499,51 @@ Resources: - !Ref BattleshiperResourceFuncRole - !Ref BattleshiperPipelineFuncRole + + # ============================================ + # =========== Core Routing System ============ + # ============================================ + + BattleshiperRouteLoadbalancer: + Type: AWS::EC2::ApplicationLoadbalancer + Properties: + + BattleshiperRouteFunction: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: go1.x + Properties: + CodeUri: router/router + Handler: router + Runtime: provided.al2023 + Architectures: + - x86_64 + VpcConfig: + SubnetIds: + - !Ref BattleshiperApiSubnet1 + - !Ref BattleshiperApiSubnet2 + - !Ref BattleshiperApiSubnet3 + SecurityGroupIds: + - !Ref BattleshiperDbReceiverGroup + - !Ref BattleshiperApiEgressGroup + Role: !GetAtt BattleshiperUserFuncRole.Arn + Events: + User: + Type: HttpApi + Properties: + Path: /api/user/{proxy+} + Method: ANY + ApiId: !Ref BattleshiperApi + Environment: + Variables: + AWS_REGION: !Ref AWS::Region + JWT_CREDENTIAL_ARN: !Ref BattleshiperJwtCredentials + DATABASE_ENDPOINT: !GetAtt BattleshiperDb.ClusterEndpoint + DATABASE_NAME: "battleshiper" + DATABASE_SECRET_ARN: !Ref BattleshiperDbApiSecret + LoggingConfig: + LogGroup: !Ref BattleshiperApiLogGroup + # ============================================ # =========== API ============================ # ============================================ @@ -734,7 +783,7 @@ Resources: DEPLOY_EVENT_SOURCE: "aws.batch" DEPLOY_EVENT_ACTION: "Batch Job State Change" DEPLOY_EVENT_TICKET_TTL: 1000 - CLOUDFRONT_CACHE_ARN: !GetAtt BattleshiperCDNRouteStore.Arn + CLOUDFRONT_CACHE_ARN: !GetAtt BattleshiperProjectCDNRouteStore.Arn LoggingConfig: LogGroup: !Ref BattleshiperApiLogGroup @@ -799,72 +848,83 @@ Resources: LogGroup: !Ref BattleshiperApiLogGroup - # ============================================ - # =========== Project S3 Storage ============= + # ======= Pipeline Ticket Credentials ======== # ============================================ - BattleshiperProjectStaticBucket: - Type: AWS::S3::Bucket - Properties: - Tags: - - Key: "Name" - Value: "battleshiper-static-bucket" - - BattleshiperProjectStaticBucketBucketPolicy: - Type: AWS::S3::BucketPolicy + BattleshiperPipelineTicketCredentials: + Type: AWS::SecretsManager::Secret Properties: - Bucket: !Ref BattleshiperProjectStaticBucket - PolicyDocument: - Version: "2012-10-17" - Statement: - - Action: - - "s3:GetObject" - Effect: Allow - Resource: - - !Sub "${BattleshiperProjectStaticBucket.Arn}/*" - Principal: - Service: cloudfront.amazonaws.com + Name: "battleshiper-pipeline-ticket-credentials" + Description: "Battleshiper pipeline ticket secret used to sign and verify pipeline tickets." + GenerateSecretString: + SecretStringTemplate: '{}' + GenerateStringKey: "secret" + PasswordLength: 40 + ExcludeCharacters: '"@/\\' - BattleshiperProjectStaticBucketPolicy: + BattleshiperPipelineTicketCredentialPolicy: Type: AWS::IAM::Policy Properties: - PolicyName: "battleshiper-static-bucket-access" + PolicyName: "battleshiper-pipeline-ticket-credentials-access" PolicyDocument: Version: "2012-10-17" Statement: - - Action: - - "s3:HeadObject" - - "s3:GetObject" - - "s3:ListBucket" - - "s3:PutObject" - - "s3:DeleteObject" - Effect: Allow - Resource: - - !Sub "${BattleshiperProjectStaticBucket.Arn}/*" - Roles: + - Effect: Allow + Action: + - "secretsmanager:GetSecretValue" + Resource: !Ref BattleshiperPipelineTicketCredentials + Roles: + - !Ref BattleshiperResourceFuncRole + - !Ref BattleshiperPipelineFuncRole - !Ref BattleshiperPipelineDeployFuncRole - - !Ref BattleshiperPipelineDeleteFuncRole + - !Ref BattleshiperPipelineInitFuncRole - BattleshiperProjectBuildAssetBucket: - Type: AWS::S3::Bucket - Properties: - Tags: - - Key: "Name" - Value: "battleshiper-build-asset-bucket" - LifecycleConfiguration: - Rules: - - Id: "delete-assets" - Status: "Enabled" - ExpirationInDays: 1 - Prefix: "" - - BattleshiperProjectBuildAssetBucketPolicy: - Type: AWS::IAM::Policy + # ============================================ + # =========== Pipeline ======================= + # ============================================ + + BattleshiperPipelineCloudformationServiceRole: + Type: AWS::IAM::Role Properties: - PolicyName: "battleshiper-build-asset-bucket-access" - PolicyDocument: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - cloudformation.amazonaws.com + Action: + "sts:AssumeRole" + ManagedPolicyArns: + - arn:aws:iam::aws:policy/IAMFullAccess + - arn:aws:iam::aws:policy/AmazonS3FullAccess + - arn:aws:iam::aws:policy/AWSLambdaFullAccess + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + - arn:aws:iam::aws:policy/AWSBatchFullAccess + - arn:aws:iam::aws:policy/AmazonEventBridgeFullAccess + - arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator + + + BattleshiperPipelineBuildAssetBucket: + Type: AWS::S3::Bucket + Properties: + Tags: + - Key: "Name" + Value: "battleshiper-pipeline-build-asset-bucket" + LifecycleConfiguration: + Rules: + - Id: "delete-assets" + Status: "Enabled" + ExpirationInDays: 1 + Prefix: "" + + BattleshiperPipelineBuildAssetBucketPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: "battleshiper-pipeline-build-asset-bucket-access" + PolicyDocument: Version: "2012-10-17" Statement: - Action: @@ -875,423 +935,110 @@ Resources: - "s3:DeleteObject" Effect: Allow Resource: - - !Sub "${BattleshiperProjectBuildAssetBucket.Arn}/*" + - !Sub "${BattleshiperPipelineBuildAssetBucket.Arn}/*" Roles: - !Ref BattleshiperPipelineDeployFuncRole - !Ref BattleshiperPipelineDeleteFuncRole - # ============================================ - # =========== Project API Gateway ============ - # ============================================ - - # TODO: Add permission to the staticBucket for the api gateway (s3:GetObject) - - BattleshiperProjectApi: - Type: AWS::ApiGatewayV2::Api - Properties: - Name: "battleshiper-project-api" - ProtocolType: HTTP - - BattleshiperProjectApiStage: - Type: AWS::ApiGatewayV2::Stage + BattleshiperPipelineEventBus: + Type: AWS::Events::EventBus Properties: - ApiId: !Ref BattleshiperProjectApi - StageName: "$default" - AutoDeploy: true - - # ============================================ - # =========== CDN Proxy ====================== - # ============================================ + Name: "battleshiper-pipeline-eventbus" - BattleshiperCDNWebCachePolicy: - Type: AWS::CloudFront::CachePolicy + BattleshiperPipelineEventBusPolicy: + Type: AWS::IAM::Policy Properties: - CachePolicyConfig: - Name: "battleshiper-web-cache-policy" - DefaultTTL: 86400 # 1 day - MinTTL: 1 # 1 second - MaxTTL: 31536000 # 1 year - ParametersInCacheKeyAndForwardedToOrigin: - CookiesConfig: - CookieBehavior: "none" - EnableAcceptEncodingBrotli: true - EnableAcceptEncodingGzip: true - HeadersConfig: - HeaderBehavior: none - QueryStringsConfig: - QueryStringBehavior: none + PolicyName: "battleshiper-pipeline-eventbus-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "events:PutEvents" + Resource: !GetAtt BattleshiperPipelineEventBus.Arn + Roles: + - !Ref BattleshiperPipelineFuncRole + - !Ref BattleshiperResourceFuncRole - BattleshiperCDNApiCachePolicy: - Type: AWS::CloudFront::CachePolicy - Properties: - CachePolicyConfig: - Name: "battleshiper-api-cache-policy" - DefaultTTL: 0 - MinTTL: 0 - MaxTTL: 0 - ParametersInCacheKeyAndForwardedToOrigin: - CookiesConfig: - CookieBehavior: "none" - EnableAcceptEncodingBrotli: false - EnableAcceptEncodingGzip: false - HeadersConfig: - HeaderBehavior: none - QueryStringsConfig: - QueryStringBehavior: none - BattleshiperCDNApiOriginRequestPolicy: - Type: AWS::CloudFront::OriginRequestPolicy + BattleshiperPipelineBuildComputeEnvironment: + Type: AWS::Batch::ComputeEnvironment Properties: - OriginRequestPolicyConfig: - Name: "battleshiper-api-origin-policy" - CookiesConfig: - CookieBehavior: all - HeadersConfig: - HeaderBehavior: allExcept - Headers: - - host # exclude host implicitly tells cloudfront to replace it with the api gateway origin host - QueryStringsConfig: - QueryStringBehavior: all + ComputeEnvironmentName: "battleshiper-pipeline-build-computer-environment" + State: ENABLED + Type: MANAGED + ComputeResources: + Type: FARGATE_SPOT + AllocationStrategy: SPOT_PRICE_CAPACITY_OPTIMIZED # also called SCROOGE_MCDUCK_OPTIMIZED + MaxvCpus: 20 + Subnets: + - !Ref BattleshiperBuildSubnet1 + - !Ref BattleshiperBuildSubnet2 + - !Ref BattleshiperBuildSubnet3 + UpdatePolicy: + JobExecutionTimeoutMinutes: 3 - BattleshiperCDNRouteStore: - Type: AWS::CloudFront::KeyValueStore + BattleshiperPipelineBuildQueue: + Type: AWS::Batch::JobQueue Properties: - Name: "battleshiper-cdn-route-store" - Comment: "Store used to lookup the path based on the requested host or prerendered pages." + JobQueueName: "battleshiper-pipeline-build-queue" + State: ENABLED + Priority: 1 + JobStateTimeLimitActions: + - Action: CANCEL + State: RUNNABLE + MaxTimeSeconds: 600 # the timeout is also set on each job definition (BUILD_JOB_TIMEOUT) + Reason: "build job exceeded queue timeout" + ComputeEnvironmentOrder: + - Order: 1 + ComputeEnvironment: !Ref BattleshiperPipelineBuildComputeEnvironment - BattleshiperCDNRouteStorePolicy: + BattleshiperPipelineBuildQueuePolicy: Type: AWS::IAM::Policy Properties: - PolicyName: "battleshiper-cdn-route-store-access" + PolicyName: "battleshiper-pipeline-build-queue-access" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - - "cloudfront-keyvaluestore:DescribeKeyValueStore" - - "cloudfront-keyvaluestore:UpdateKeys" - - "cloudfront-keyvaluestore:PutKey" - Resource: !GetAtt BattleshiperCDNRouteStore.Arn - Roles: - - !Ref BattleshiperResourceFuncRole - - !Ref BattleshiperPipelineDeployFuncRole - - BattleshiperCDNRouteFunc: - Type: AWS::CloudFront::Function - Properties: - Name: "battleshiper-cdn-route-func" - AutoPublish: true - FunctionConfig: - Comment: "Function to route cdn requests to a path based on the requested host and to add .html extension on prerendered pages." - Runtime: cloudfront-js-2.0 - KeyValueStoreAssociations: - - KeyValueStoreARN: !GetAtt BattleshiperCDNRouteStore.Arn - FunctionCode: !Sub | - import cf from "cloudfront"; - - const kvsHandle = cf.kvs("${BattleshiperCDNRouteStore.Id}"); - - const domainSuffix = ".${ApplicationDomain}"; - - async function handler(event) { - let request = event.request; - - try { - const authorityHeader = request.headers[":authority"]; - const hostHeader = request.headers["host"]; - - // extract host (prefer :authority if present) - const host = authorityHeader ? authorityHeader.value : hostHeader.value; - if (!host) { - throw new Error("no host specified"); - } - - // remove the application domain from the host - if (!host.endsWith(domainSuffix)) { - throw new Error("unexpected host"); - } - const alias = host.slice(0, -domainSuffix.length); - - const pathSegments = request.uri.split('/'); - - const project = await kvsHandle.get(alias, { format: "string" }); - pathSegments.splice(1, 0, project); // set the project as first segment in the uri - - request.uri = pathSegments.join('/'); - - const prerenderedPage = await kvsHandle.get(request.uri, { format: "string" }).catch(() => null); - if (prerenderedPage) { - request.uri = prerenderedPage; - } + - "batch:SubmitJob" + Resource: !Ref BattleshiperPipelineBuildQueue - return request; - } catch (err) { - request.uri = "/404.html"; - request.querystring = `error=${err.message}`; - return request; - } - } - BattleshiperCDNOriginAccessControl: - Type: AWS::CloudFront::OriginAccessControl + BattleshiperPipelineInitFuncRole: + Type: AWS::IAM::Role Properties: - OriginAccessControlConfig: - Name: "battleshiper-cdn-origin-access" - OriginAccessControlOriginType: s3 - SigningBehavior: always - SigningProtocol: sigv4 + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - "sts:AssumeRole" + Policies: + - PolicyName: "battleshiper-pipeline-init-cloudformation-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "cloudformation:DescribeStacks" + - "cloudformation:CreateStack" + Resource: "*" + - Effect: Allow + Action: + - "iam:PassRole" + Resource: !GetAtt BattleshiperPipelineCloudformationServiceRole.Arn + # further policies are defined as separate policy objects and attached to the IAM role. - BattleshiperCDN: - Type: AWS::CloudFront::Distribution - Properties: - DistributionConfig: - Enabled: true - PriceClass: "PriceClass_All" - ViewerCertificate: - AcmCertificateArn: !Ref ApplicationDomainCertificateArn - SslSupportMethod: "sni-only" - MinimumProtocolVersion: "TLSv1.2_2021" - Origins: - - Id: "battleshiper-project-static-bucket" - DomainName: !GetAtt BattleshiperProjectStaticBucket.DomainName - OriginAccessControlId: !GetAtt BattleshiperCDNOriginAccessControl.Id - S3OriginConfig: {} - - Id: "battleshiper-api" - DomainName: !GetAtt BattleshiperApi.ApiEndpoint - CustomOriginConfig: - OriginProtocolPolicy: "https-only" - - Id: "battleshiper-project-api" - DomainName: !GetAtt BattleshiperProjectApi.ApiEndpoint - CustomOriginConfig: - OriginProtocolPolicy: "https-only" - DefaultCacheBehavior: - TargetOriginId: "battleshiper-project-api" - AllowedMethods: - - GET - - HEAD - - OPTIONS - - PUT - - PATCH - - POST - - DELETE - ViewerProtocolPolicy: redirect-to-https - CachePolicyId: !Ref BattleshiperCDNApiCachePolicy - OriginRequestPolicyId: !Ref BattleshiperCDNApiOriginRequestPolicy - FunctionAssociations: - - EventType: viewer-request - FunctionARN: !GetAtt BattleshiperCDNRouteFunc.FunctionMetadata.FunctionARN - - CacheBehaviors: - - PathPattern: "/api/*" - TargetOriginId: "battleshiper-api" - AllowedMethods: - - GET - - HEAD - - OPTIONS - - PUT - - PATCH - - POST - - DELETE - Compress: false - ViewerProtocolPolicy: redirect-to-https - CachePolicyId: !Ref BattleshiperCDNApiCachePolicy - OriginRequestPolicyId: !Ref BattleshiperCDNApiOriginRequestPolicy - - - PathPattern: "/_app/*" - TargetOriginId: "battleshiper-project-static-bucket" - AllowedMethods: - - GET - - HEAD - CachedMethods: - - GET - - HEAD - Compress: true - ViewerProtocolPolicy: redirect-to-https - CachePolicyId: !Ref BattleshiperCDNWebCachePolicy - FunctionAssociations: - - EventType: viewer-request - FunctionARN: !GetAtt BattleshiperCDNRouteFunc.FunctionMetadata.FunctionARN - - - PathPattern: "/*.*" - TargetOriginId: "battleshiper-project-static-bucket" - AllowedMethods: - - GET - - HEAD - CachedMethods: - - GET - - HEAD - Compress: true - ViewerProtocolPolicy: redirect-to-https - CachePolicyId: !Ref BattleshiperCDNWebCachePolicy - FunctionAssociations: - - EventType: viewer-request - FunctionARN: !GetAtt BattleshiperCDNRouteFunc.FunctionMetadata.FunctionARN - - Aliases: - - !Sub "*.${ApplicationDomain}" - Tags: - - Key: "Name" - Value: "battleshiper-cdn" - - # ============================================ - # ======= Pipeline Ticket Credentials ======== - # ============================================ - - BattleshiperPipelineTicketCredentials: - Type: AWS::SecretsManager::Secret - Properties: - Name: "battleshiper-pipeline-ticket-credentials" - Description: "Battleshiper pipeline ticket secret used to sign and verify pipeline tickets." - GenerateSecretString: - SecretStringTemplate: '{}' - GenerateStringKey: "secret" - PasswordLength: 40 - ExcludeCharacters: '"@/\\' - - BattleshiperPipelineTicketCredentialPolicy: - Type: AWS::IAM::Policy - Properties: - PolicyName: "battleshiper-pipeline-ticket-credentials-access" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "secretsmanager:GetSecretValue" - Resource: !Ref BattleshiperPipelineTicketCredentials - Roles: - - !Ref BattleshiperResourceFuncRole - - !Ref BattleshiperPipelineFuncRole - - !Ref BattleshiperPipelineDeployFuncRole - - !Ref BattleshiperPipelineInitFuncRole - - - # ============================================ - # =========== Pipeline ======================= - # ============================================ - - - BattleshiperPipelineCloudformationServiceRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Service: - - cloudformation.amazonaws.com - Action: - "sts:AssumeRole" - ManagedPolicyArns: - - arn:aws:iam::aws:policy/IAMFullAccess - - arn:aws:iam::aws:policy/AmazonS3FullAccess - - arn:aws:iam::aws:policy/AWSLambdaFullAccess - - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - - arn:aws:iam::aws:policy/AWSBatchFullAccess - - arn:aws:iam::aws:policy/AmazonEventBridgeFullAccess - - arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator - - - BattleshiperPipelineEventBus: - Type: AWS::Events::EventBus - Properties: - Name: "battleshiper-pipeline-eventbus" - - BattleshiperPipelineEventBusPolicy: - Type: AWS::IAM::Policy - Properties: - PolicyName: "battleshiper-pipeline-eventbus-access" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "events:PutEvents" - Resource: !GetAtt BattleshiperPipelineEventBus.Arn - Roles: - - !Ref BattleshiperPipelineFuncRole - - !Ref BattleshiperResourceFuncRole - - - BattleshiperPipelineBuildComputeEnvironment: - Type: AWS::Batch::ComputeEnvironment - Properties: - ComputeEnvironmentName: "battleshiper-pipeline-build-computer-environment" - State: ENABLED - Type: MANAGED - ComputeResources: - Type: FARGATE_SPOT - AllocationStrategy: SPOT_PRICE_CAPACITY_OPTIMIZED # also called SCROOGE_MCDUCK_OPTIMIZED - MaxvCpus: 20 - Subnets: - - !Ref BattleshiperBuildSubnet1 - - !Ref BattleshiperBuildSubnet2 - - !Ref BattleshiperBuildSubnet3 - UpdatePolicy: - JobExecutionTimeoutMinutes: 3 - - BattleshiperPipelineBuildQueue: - Type: AWS::Batch::JobQueue - Properties: - JobQueueName: "battleshiper-pipeline-build-queue" - State: ENABLED - Priority: 1 - JobStateTimeLimitActions: - - Action: CANCEL - State: RUNNABLE - MaxTimeSeconds: 600 # the timeout is also set on each job definition (BUILD_JOB_TIMEOUT) - Reason: "build job exceeded queue timeout" - ComputeEnvironmentOrder: - - Order: 1 - ComputeEnvironment: !Ref BattleshiperPipelineBuildComputeEnvironment - - BattleshiperPipelineBuildQueuePolicy: - Type: AWS::IAM::Policy - Properties: - PolicyName: "battleshiper-pipeline-build-queue-access" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "batch:SubmitJob" - Resource: !Ref BattleshiperPipelineBuildQueue - - - BattleshiperPipelineInitFuncRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - "sts:AssumeRole" - Policies: - - PolicyName: "battleshiper-pipeline-init-cloudformation-access" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "cloudformation:DescribeStacks" - - "cloudformation:CreateStack" - Resource: "*" - - Effect: Allow - Action: - - "iam:PassRole" - Resource: !GetAtt BattleshiperPipelineCloudformationServiceRole.Arn - # further policies are defined as separate policy objects and attached to the IAM role. - - BattleshiperPipelineInitFunc: - Type: AWS::Serverless::Function - Metadata: - BuildMethod: go1.x + BattleshiperPipelineInitFunc: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: go1.x Properties: CodeUri: pipeline/init Handler: init @@ -1332,7 +1079,7 @@ Resources: DEPLOYMENT_SERVICE_ROLE_ARN: !GetAtt BattleshiperPipelineCloudformationServiceRole.Arn DEPLOYMENT_TIMEOUT: "400s" STATIC_BUCKET_NAME: !Ref BattleshiperProjectStaticBucket - BUILD_ASSET_BUCKET_NAME: !Ref BattleshiperProjectBuildAssetBucket + BUILD_ASSET_BUCKET_NAME: !Ref BattleshiperPipelineBuildAssetBucket EVENT_LOG_GROUP_PREFIX: "/battleshiper/project/event" BUILD_LOG_GROUP_PREFIX: "/battleshiper/project/build" DEPLOY_LOG_GROUP_PREFIX: "/battleshiper/project/deploy" @@ -1429,7 +1176,7 @@ Resources: DATABASE_SECRET_ARN: !Ref BattleshiperDbApiSecret TICKET_CREDENTIAL_ARN: !Ref BattleshiperPipelineTicketCredentials DEPLOYMENT_TIMEOUT: "400s" - CLOUDFRONT_CACHE_ARN: !GetAtt BattleshiperCDNRouteStore.Arn + CLOUDFRONT_CACHE_ARN: !GetAtt BattleshiperProjectCDNRouteStore.Arn LoggingConfig: LogGroup: !Ref BattleshiperApiLogGroup @@ -1498,11 +1245,435 @@ Resources: LogGroup: !Ref BattleshiperApiLogGroup -Outputs: - BattleshiperAPI: - Description: "API Gateway endpoint URL" - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api" + # ============================================ + # =========== S3 Storage ===================== + # ============================================ + + BattleshiperStaticBucket: + Type: AWS::S3::Bucket + Properties: + Tags: + - Key: "Name" + Value: "battleshiper-static-bucket" + + BattleshiperStaticBucketBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref BattleshiperStaticBucket + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - "s3:GetObject" + Effect: Allow + Resource: + - !Sub "${BattleshiperStaticBucket.Arn}/*" + Principal: + Service: cloudfront.amazonaws.com + + # ============================================ + # =========== CDN ============================ + # ============================================ + + BattleshiperCDNWebCachePolicy: + Type: AWS::CloudFront::CachePolicy + Properties: + CachePolicyConfig: + Name: "battleshiper-web-cache-policy" + DefaultTTL: 86400 # 1 day + MinTTL: 1 # 1 second + MaxTTL: 31536000 # 1 year + ParametersInCacheKeyAndForwardedToOrigin: + CookiesConfig: + CookieBehavior: "none" + EnableAcceptEncodingBrotli: true + EnableAcceptEncodingGzip: true + HeadersConfig: + HeaderBehavior: none + QueryStringsConfig: + QueryStringBehavior: none + BattleshiperCDNServerCachePolicy: + Type: AWS::CloudFront::CachePolicy + Properties: + CachePolicyConfig: + Name: "battleshiper-server-cache-policy" + DefaultTTL: 0 + MinTTL: 0 + MaxTTL: 0 + ParametersInCacheKeyAndForwardedToOrigin: + CookiesConfig: + CookieBehavior: "none" + EnableAcceptEncodingBrotli: false + EnableAcceptEncodingGzip: false + HeadersConfig: + HeaderBehavior: "none" + QueryStringsConfig: + QueryStringBehavior: "none" + + BattleshiperCDNServerOriginRequestPolicy: + Type: AWS::CloudFront::OriginRequestPolicy + Properties: + OriginRequestPolicyConfig: + Name: "battleshiper-server-origin-policy" + CookiesConfig: + CookieBehavior: all + HeadersConfig: + HeaderBehavior: all + QueryStringsConfig: + QueryStringBehavior: all + + BattleshiperCDNOriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: "battleshiper-cdn-origin-access" + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + + BattleshiperCDN: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Enabled: true + PriceClass: "PriceClass_100" + ViewerCertificate: + AcmCertificateArn: !Ref ApplicationDomainCertificateArn + SslSupportMethod: "sni-only" + MinimumProtocolVersion: "TLSv1.2_2021" + Origins: + - Id: "battleshiper-static-bucket" + DomainName: !GetAtt BattleshiperStaticBucket.DomainName + OriginAccessControlId: !GetAtt BattleshiperCDNOriginAccessControl.Id + S3OriginConfig: {} + - Id: "battleshiper-server" + DomainName: !GetAtt TODO Alb endpoint + CustomOriginConfig: + OriginProtocolPolicy: "https-only" + - Id: "battleshiper-api" + DomainName: !GetAtt BattleshiperApi.ApiEndpoint + CustomOriginConfig: + OriginProtocolPolicy: "https-only" + DefaultCacheBehavior: + TargetOriginId: "battleshiper-server" + AllowedMethods: + - GET + - HEAD + - OPTIONS + - PUT + - PATCH + - POST + - DELETE + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperCDNServerCachePolicy + OriginRequestPolicyId: !Ref BattleshiperCDNServerOriginRequestPolicy + CacheBehaviors: + - PathPattern: "/api/*" + TargetOriginId: "battleshiper-api" + AllowedMethods: + - GET + - HEAD + - OPTIONS + - PUT + - PATCH + - POST + - DELETE + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperCDNServerCachePolicy + OriginRequestPolicyId: !Ref BattleshiperCDNServerOriginRequestPolicy + - PathPattern: "/_app/*" + TargetOriginId: "battleshiper-static-bucket" + AllowedMethods: + - GET + - HEAD + CachedMethods: + - GET + - HEAD + Compress: true + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperCDNWebCachePolicy + - PathPattern: "/*.*" + TargetOriginId: "battleshiper-static-bucket" + AllowedMethods: + - GET + - HEAD + CachedMethods: + - GET + - HEAD + Compress: true + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperCDNWebCachePolicy + Aliases: + - !Sub "${ApplicationDomain}" + Tags: + - Key: "Name" + Value: "battleshiper-cdn" + + + # ============================================ + # =========== Project S3 Storage ============= + # ============================================ + + BattleshiperProjectStaticBucket: + Type: AWS::S3::Bucket + Properties: + Tags: + - Key: "Name" + Value: "battleshiper-project-static-bucket" + + BattleshiperProjectStaticBucketBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref BattleshiperProjectStaticBucket + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - "s3:GetObject" + Effect: Allow + Resource: + - !Sub "${BattleshiperProjectStaticBucket.Arn}/*" + Principal: + Service: cloudfront.amazonaws.com + + BattleshiperProjectStaticBucketPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: "battleshiper-project-static-bucket-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - "s3:HeadObject" + - "s3:GetObject" + - "s3:ListBucket" + - "s3:PutObject" + - "s3:DeleteObject" + Effect: Allow + Resource: + - !Sub "${BattleshiperProjectStaticBucket.Arn}/*" + Roles: + - !Ref BattleshiperPipelineDeployFuncRole + - !Ref BattleshiperPipelineDeleteFuncRole + + + # ============================================ + # =========== Project CDN Router ============= + # ============================================ + + BattleshiperProjectCDNRouteStore: + Type: AWS::CloudFront::KeyValueStore + Properties: + Name: "battleshiper-cdn-route-store" + Comment: "Store used to lookup the path based on the requested host or prerendered pages." + + BattleshiperProjectCDNRouteStorePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: "battleshiper-cdn-route-store-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "cloudfront-keyvaluestore:DescribeKeyValueStore" + - "cloudfront-keyvaluestore:UpdateKeys" + - "cloudfront-keyvaluestore:PutKey" + Resource: !GetAtt BattleshiperProjectCDNRouteStore.Arn + Roles: + - !Ref BattleshiperResourceFuncRole + - !Ref BattleshiperPipelineDeployFuncRole + + BattleshiperProjectCDNRouteFunc: + Type: AWS::CloudFront::Function + Properties: + Name: "battleshiper-cdn-route-func" + AutoPublish: true + FunctionConfig: + Comment: "Function to route cdn requests to a path based on the requested host and to add .html extension on prerendered pages." + Runtime: cloudfront-js-2.0 + KeyValueStoreAssociations: + - KeyValueStoreARN: !GetAtt BattleshiperProjectCDNRouteStore.Arn + FunctionCode: !Sub | + import cf from "cloudfront"; + + const kvsHandle = cf.kvs("${BattleshiperProjectCDNRouteStore.Id}"); + + const domainSuffix = ".${ApplicationDomain}"; + + async function handler(event) { + let request = event.request; + + try { + const authorityHeader = request.headers[":authority"]; + const hostHeader = request.headers["host"]; + + // extract host (prefer :authority if present) + const host = authorityHeader ? authorityHeader.value : hostHeader.value; + if (!host) { + throw new Error("no host specified"); + } + + // remove the application domain from the host + if (!host.endsWith(domainSuffix)) { + throw new Error("unexpected host"); + } + const alias = host.slice(0, -domainSuffix.length); + + const pathSegments = request.uri.split('/'); + + const project = await kvsHandle.get(alias, { format: "string" }); + pathSegments.splice(1, 0, project); // set the project as first segment in the uri + + request.uri = pathSegments.join('/'); + + const prerenderedPage = await kvsHandle.get(request.uri, { format: "string" }).catch(() => null); + if (prerenderedPage) { + request.uri = prerenderedPage; + } + + return request; + } catch (err) { + request.uri = "/404.html"; + request.querystring = `error=${err.message}`; + return request; + } + } + + # ============================================ + # =========== Project CDN ==================== + # ============================================ + + BattleshiperProjectCDNWebCachePolicy: + Type: AWS::CloudFront::CachePolicy + Properties: + CachePolicyConfig: + Name: "battleshiper-project-web-cache-policy" + DefaultTTL: 86400 # 1 day + MinTTL: 1 # 1 second + MaxTTL: 31536000 # 1 year + ParametersInCacheKeyAndForwardedToOrigin: + CookiesConfig: + CookieBehavior: "none" + EnableAcceptEncodingBrotli: true + EnableAcceptEncodingGzip: true + HeadersConfig: + HeaderBehavior: none + QueryStringsConfig: + QueryStringBehavior: none + + BattleshiperProjectCDNServerCachePolicy: + Type: AWS::CloudFront::CachePolicy + Properties: + CachePolicyConfig: + Name: "battleshiper-project-server-cache-policy" + DefaultTTL: 0 + MinTTL: 0 + MaxTTL: 0 + ParametersInCacheKeyAndForwardedToOrigin: + CookiesConfig: + CookieBehavior: "none" + EnableAcceptEncodingBrotli: false + EnableAcceptEncodingGzip: false + HeadersConfig: + HeaderBehavior: "none" + QueryStringsConfig: + QueryStringBehavior: "none" + + BattleshiperProjectCDNServerOriginRequestPolicy: + Type: AWS::CloudFront::OriginRequestPolicy + Properties: + OriginRequestPolicyConfig: + Name: "battleshiper-project-server-origin-policy" + CookiesConfig: + CookieBehavior: all + HeadersConfig: + HeaderBehavior: all + QueryStringsConfig: + QueryStringBehavior: all + + BattleshiperProjectCDNOriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: Name: "battleshiper-project-cdn-origin-access" + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + + BattleshiperProjectCDN: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Enabled: true + PriceClass: "PriceClass_All" + ViewerCertificate: + AcmCertificateArn: !Ref ApplicationDomainWildcardCertificateArn + SslSupportMethod: "sni-only" + MinimumProtocolVersion: "TLSv1.2_2021" + Origins: + - Id: "battleshiper-project-static-bucket" + DomainName: !GetAtt BattleshiperProjectStaticBucket.DomainName + OriginAccessControlId: !GetAtt BattleshiperProjectCDNOriginAccessControl.Id + S3OriginConfig: {} + - Id: "battleshiper-project-server" + DomainName: !GetAtt TODO Alb endpoint + CustomOriginConfig: + OriginProtocolPolicy: "https-only" + DefaultCacheBehavior: + TargetOriginId: "battleshiper-project-server" + AllowedMethods: + - GET + - HEAD + - OPTIONS + - PUT + - PATCH + - POST + - DELETE + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperProjectCDNServerCachePolicy + OriginRequestPolicyId: !Ref BattleshiperProjectCDNServerOriginRequestPolicy + FunctionAssociations: + - EventType: viewer-request + FunctionARN: !GetAtt BattleshiperProjectCDNRouteFunc.FunctionMetadata.FunctionARN + CacheBehaviors: + - PathPattern: "/_app/*" + TargetOriginId: "battleshiper-project-static-bucket" + AllowedMethods: + - GET + - HEAD + CachedMethods: + - GET + - HEAD + Compress: true + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperProjectCDNWebCachePolicy + FunctionAssociations: + - EventType: viewer-request + FunctionARN: !GetAtt BattleshiperProjectCDNRouteFunc.FunctionMetadata.FunctionARN + - PathPattern: "/*.*" + TargetOriginId: "battleshiper-project-static-bucket" + AllowedMethods: + - GET + - HEAD + CachedMethods: + - GET + - HEAD + Compress: true + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: !Ref BattleshiperProjectCDNWebCachePolicy + FunctionAssociations: + - EventType: viewer-request + FunctionARN: !GetAtt BattleshiperProjectCDNRouteFunc.FunctionMetadata.FunctionARN + + Aliases: + - !Sub "*.${ApplicationDomain}" + Tags: + - Key: "Name" + Value: "battleshiper-project-cdn" + + +Outputs: BattleshiperDbAdminSecret: Description: "Secret where the credentials of the database admin are stored" Value: !Ref BattleshiperDbAdminSecret \ No newline at end of file