D’abord un (tout petit) peu de théorie
Amazon Cloudfront, c’est quoi ?
Amazon Cloudfront est un CDN (Content Delivery Network), idéal pour servir des sites web statiques hébergés dans Amazon S3, type SPA (Single Page Application) et donc site codé par exemple en Angular, ReactJS ou encore VueJS. La doc du service est ici.
Un schéma vaut mille mots
L’utilisateur navigue sur le site site web depuis son ordinateur : il accède à celui-ci au travers de la distribution CloudFront et pas directement au bucket S3, afin de bénéficer des de CloudFront (mise en cache, DNS custos, WAF intégré, …).
En pratique !
Objectif
Le but de cet article est démontrer comment on peut servir plusieurs sites WEB, à partir d’une même distribution CloudFront (et donc d’une base url commune), comme illustré ci-dessous :
On déploie d’abord un seul site, avec terraform
- Création d’un bucket S3
- Upload de notre site web
- Création de notre distribution CloudFront & configuration de celle-ci
- On teste !
Création du bucket S3
Voici le code minimal pour créer le bucket S3 :
resource "aws_s3_bucket" "this" {
bucket = "blog-site1-example"
tags = {
Name = "blog-site1-example"
}
}
Upload du site
Notre site web est composé d’un seul fichier index.html contenant le code suivant :
<h1>Accueil du Site 1 !</h1>
Création de la distribution CloudFront
Encore une fois, il n’y a que le code minimal nécessaire pour créer la distribution.
resource "aws_cloudfront_distribution" "this" {
enabled = true
default_root_object = "index.html"
origin {
domain_name = aws_s3_bucket.this.bucket_regional_domain_name
origin_id = aws_s3_bucket.this.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.this.id
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
Permissions
On configure ensuite les permissions entre le bucket S3 et la distribution CloudFront pour que celle-ci puisse lire le site.
On ajoute une “Origin Access Control” côté CloudFront et on met à jour la bucket policy :
resource "aws_cloudfront_origin_access_control" "default" {
name = "s3-access"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_s3_bucket_policy" "site1" {
bucket = aws_s3_bucket.site1.id
policy = jsonencode({
"Version" : "2008-10-17",
"Id" : "PolicyForCloudFrontPrivateContent",
"Statement" : [
{
"Sid" : "AllowCloudFrontServicePrincipal",
"Effect" : "Allow",
"Principal" : {
"Service" : "cloudfront.amazonaws.com"
},
"Action" : "s3:GetObject",
"Resource" : "${aws_s3_bucket.site1.arn}/*",
"Condition" : {
"StringEquals" : {
"AWS:SourceArn" : aws_cloudfront_distribution.this.arn
}
}
}
]
})
}
En utilisant “Distribution domain name”, on est désormais en mesure d’accéder au site. Les deux URLs suivantes sont valides : https://_distribution-id_.cloudfront.net et https://_distribution-id_.cloudfront.net/index.html
Déployons maintenant le second site et essayons d’y accéder avec CloudFront
- On crée un second bucket S3 > je ne vous remets pas le code de création, c’est le même
- On met à jour la distribution CloudFront avec une second “origin” pointant vers le second bucket S3 et on ajoute un second “cache behavior” notamment pour préciser à CloudFront que toutes les requêtes arrivant sur la route /site2 doivent être redirigées vers cette seconde “origin”
resource "aws_cloudfront_distribution" "this" {
enabled = true
default_root_object = "index.html"
origin {
domain_name = aws_s3_bucket.site1.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.default.id
origin_id = aws_s3_bucket.site1.id
}
origin {
domain_name = aws_s3_bucket.site2.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.default.id
origin_id = aws_s3_bucket.site2.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.site1.id
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
ordered_cache_behavior {
path_pattern = "/site2/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.site2.id
viewer_protocol_policy = "allow-all"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
resource "aws_cloudfront_origin_access_control" "default" {
name = "s3-access"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
On teste l’accès au deuxième site avec les URL suivantes : https://_distribution-id_.cloudfront.net/site2 ou https://_distribution-id_.cloudfront.net/site2/index.html ; et là, patatras !, “Access denied” :
Pourquoi ? Alors que tout est configuré de la même façon ?
Et bien en fait c’est très simple et c’est bien entendu lié à la façon dont fonctionne CloudFront et dont il construit sa redirection : il concatène l’URL des origin (= “origin domain”) avec le path de l’URL.
Dans notre exemple :
- La première origin possède comme URL : blog-site1-example.s3.eu-west-3.amazonaws.com et la seconde blog-site2-example.s3.eu-west-3.amazonaws.com
- La première origin est associée au chemin par défaut, ainsi la redirection est construire comme ceci : blog-site1-example.s3.eu-west-3.amazonaws.com/* et pointe donc vers la racine du bucket S3 hébergeant le premier site
- En revanche, la seconde origin est associé au path /site2/*, la redirection est donc construire comme ceci : blog-site2-example.s3.eu-west-3.amazonaws.com/site2/* et pointe donc vers un sous dossier site2 dans le bucket hébergeant le second site
- Or nos deux sites ont été déployés de la même façon, à la racine des buckets S3, il n’existe pas de sous dossier et S3 nous renvoie un Access Denied !
Comment on corrige alors ?
Hé bien, il suffit de déployer notre second site dans un sous dossier nommé site2 dans son bucket S3 :
Et cette fois ci, on accède au second site avec succès : https://_distribution-id_.cloudfront.net/site2/index.html :
En conclusion
On sert désormais deux sites différents, à partir de la même distribution CloudFront.
Une fois qu’on a compris comment ça fonctionne, c’est vraiment simple, mais pas évident de prime abord.
J’espère que ces quelques minutes de lecture vous permettront d’éviter de longues minutes (heures !) de debug, de vérification des permissions et autres joyeusetés.