Пример создания хостинга статического сайта в AWS S3 и подключении к нему AWS CloudFront CDN, к которому подключается SSL сертификат из AWS ACM.
Для сайта используем домен site.azinchenko.com.
Получившийся шаблон доступен в репозитории тут>>>.
Содержание
AWS::S3::Bucket
Начнём с создания корзины, используем ресурс AWS::S3::Bucket:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation S3 website hosting with CloudFront CND stack",
"Parameters": {
"S3SiteBucketName": {
"Description": "S3 website hosting bucket name",
"Type": "String",
"Default": "site.azinchenko.com"
}
},
"Resources": {
"SiteBucket": {
"Type": "AWS::S3::Bucket",
"DeletionPolicy": "Delete",
"Properties": {
"BucketName": {
"Ref": "S3SiteBucketName"
},
"WebsiteConfiguration": {
"IndexDocument": "index.html",
"ErrorDocument": "404.html"
}
}
}
}
}
Тут в DeletionPolicy указываем удаление корзины при удалении стека, а в WebsiteConfiguration — указываем на то, что корзина будет использоваться для хостинга сайта.
В имени корзины используем имя домена, которое будет использоваться для доступа к сайту — site.azinchenko.com.
Создаём стек:
[simterm]
$ aws --profile setevoy-root cloudformation create-stack --stack-name s3-cdn-site-example --template-body file://s3_website_hostig_with_cloudfront.json
[/simterm]
Проверяем корзину:
[simterm]
$ aws --profile setevoy-root s3api list-buckets | jq '.Buckets[] | select(.Name=="site.azinchenko.com")'
{
"Name": "site.azinchenko.com",
"CreationDate": "2018-09-05T07:33:26.000Z"
}
[/simterm]
Создаём в ней индексный файл:
[simterm]
$ echo "This is The Website" > /tmp/index.html $ aws --profile setevoy-root s3 cp /tmp/index.html s3://site.azinchenko.com upload: ../../../../../../tmp/index.html to s3://site.azinchenko.com/index.html
[/simterm]
Проверяем файл:
[simterm]
$ aws --profile setevoy-root s3api list-objects --bucket site.azinchenko.com | jq '.Contents[] .Key' "index.html"
[/simterm]
Но если проверить доступ по HTTP сейчас — получим AccessDenied:
[simterm]
$ curl http://site.azinchenko.com.s3-website-eu-west-1.amazonaws.com <html> <head><title>403 Forbidden</title></head> <body> <h1>403 Forbidden</h1> <ul> <li>Code: AccessDenied</li> <li>Message: Access Denied</li> ...
[/simterm]
AWS::S3::BucketPolicy
Добавляем ещё один ресурс — AWS::S3::BucketPolicy, в котором описываем доступ к корзине — разрешаем метод s3:GetObject ко всем объектам в корзине, и делаем её таким образом Publicly accessible:
...
"BucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"Properties": {
"PolicyDocument": {
"Id": "MyPolicy",
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadForGetBucketObjects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": { "Fn::Join": ["", [ "arn:aws:s3:::", { "Ref": "SiteBucket" }, "/*" ]] }
}]
},
"Bucket": {
"Ref": "SiteBucket"
}
}
}
...
Обновляем стек:
[simterm]
$ aws --profile setevoy-root cloudformation update-stack --stack-name s3-cdn-site-example --template-body file://s3_website_hostig_with_cloudfront.json
Проверяем:
[simterm]
$ curl http://site.azinchenko.com.s3-website-eu-west-1.amazonaws.com This is The Website
[/simterm]
AWS Certificate Manager
Сертификат не будет являться частью стека, делаем его вручную, обязательно в регионе N. Virginia (us-east-1), что бы его можно было использовать в CloudFront:
AWS::CloudFront::Distribution
И последним добавляем ресурс CDN distribution.
Из интересного в его параметрах:
TargetOriginId— черезRefуказываем ID ресурса S3 корзиныViewerProtocolPolicy— редиректим HTTP запросы на HTTPSAliases— в алиасе (поле CNAME в CloudFront distribution) указываем DNS имя, которое используем для сайта, в этом примере это site.azinchenko.com, как и имя корзиныOrigins— тут формируем URL корзины в виде имя-корзины-s3-website.eu-west-1.amazonaws.com, используяFn::JoinиRefViewerCertificate— указываем SSL ARN, который создали выше
ARN сертификата выносим в Parameters шаблона:
...
"SiteSSLARN": {
"Description": "site.azinchenko.com ACM SSL ARN",
"Type": "String",
"Default": "arn:aws:acm:us-east-1:264***286:certificate/601dae7d-***-d29307183727"
}
...
И добавляем ресурс AWS::CloudFront::Distribution:
...
"CloudFrontCDN": {
"Type": "AWS::CloudFront::Distribution",
"Properties": {
"DistributionConfig": {
"Comment": "Example WebSite CDN",
"Enabled": true,
"DefaultRootObject" : "index.html",
"DefaultCacheBehavior": {
"TargetOriginId": {
"Ref": "SiteBucket"
},
"ViewerProtocolPolicy": "redirect-to-https",
"MinTTL": 0,
"AllowedMethods": [
"HEAD",
"GET"
],
"CachedMethods": [
"HEAD",
"GET"
],
"ForwardedValues": {
"QueryString": false,
"Cookies": {
"Forward": "none"
}
}
},
"Aliases" : [ {"Ref": "S3SiteBucketName"} ],
"Origins": [
{
"DomainName": { "Fn::Join": [ ".", [ {"Ref": "SiteBucket"}, "s3-website.eu-west-1.amazonaws.com" ] ] },
"Id": {
"Ref": "SiteBucket"
},
"CustomOriginConfig": {
"HTTPPort": "80",
"HTTPSPort": "443",
"OriginProtocolPolicy": "http-only"
}
}
],
"ViewerCertificate": {
"SslSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1",
"AcmCertificateArn": {
"Ref": "SiteSSLARN"
}
}
},
"Tags" : [
{"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "site-cdn"] ] } }
]
}
}
...
Обновляем стек:
[simterm]
$ aws --profile setevoy-root cloudformation update-stack --stack-name s3-cdn-site-example --template-body file://s3_website_hostig_with_cloudfront.json
[/simterm]
Проверяем:
[simterm]
$ aws --profile setevoy-root cloudfront list-distributions
{
"DistributionList": {
"Items": [
{
"Id": "E15M912IHI41HB",
"ARN": "arn:aws:cloudfront::264***286:distribution/E15M912IHI41HB",
"Status": "InProgress",
"LastModifiedTime": "2018-09-05T08:34:33.994Z",
"DomainName": "d16suo1j0j3qwj.cloudfront.net",
"Aliases": {
"Quantity": 1,
"Items": [
"site.azinchenko.com"
]
},
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "site.azinchenko.com",
"DomainName": "site.azinchenko.com.s3-website.eu-west-1.amazonaws.com",
...
[/simterm]
Ждём, когда дистрибьюция переходит в Status == Enabled, и переходим в DNS.
Route53
Последний шаг — обновить запись site.azinchenko.com в Route53.
Выбираем тип записи IN A, и указываем Alias — CloudFront distribution:
Ждём обновления DNS у провайдера:
[simterm]
$ dig +short site.azinchenko.com 143.204.10.118 143.204.10.134 143.204.10.200 143.204.10.37
[/simterm]
Проверяем:
[simterm]
$ curl -Lv site.azinchenko.com * Rebuilt URL to: site.azinchenko.com/ ... < HTTP/1.1 301 Moved Permanently < Server: CloudFront ... < Location: https://site.azinchenko.com/ ... * Issue another request to this URL: 'https://site.azinchenko.com/' ... * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 ... * Server certificate: * subject: CN=site.azinchenko.com ... < HTTP/1.1 200 OK ... This is The Website
[/simterm]
Готово.


