Ещё один пример работы с CloudFormation.
Задача — добавить создание AWS S3 корзины, со следующими параметрами:
AccessControl
: полный доступ владельцу (документация тут>>>) и IAM пользователюLifecycleConfiguration
: архивировать данные в AWS Glacier по истечении заданного кол-ва дней (документация тут>>>)Tags
: само собойNotificationConfiguration
: отправлять сообщения через AWS SNS при удалении объектов из корзины (документация тут>>>)VersioningConfiguration
: вкусно, но не сейчас (документация тут>>>)
Кроме ресурса самой корзины — добавим IAM пользователя, группу и политику доступа с разрешением доступа к этой корзине.
Приступим.
AWS::S3::Bucket
Сначала создадим минимальный шаблон:
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "AWS CloudFormation S3 testing stack", "Parameters": { "BucketName": { "Description": "Bucket name", "Type": "String", "Default": "ads-test-bucket" }, "ClientTag": { "Description": "Client tag value", "Type": "String", "Default": "TestClient" } }, "Resources" : { "myS3Bucket" : { "Type" : "AWS::S3::Bucket", "Properties": { "AccessControl": "BucketOwnerFullControl", "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "s3-bucket"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "AWS::StackName"} }, {"Key" : "Client", "Value" : {"Ref" : "ClientTag"} } ] } } } }
Проверяем его:
ОК — теперь можно создавать сам стек, запускаем:
Проверяем:
И корзины:
ОК, работает, продолжаем.
AWS::IAM::User
Следующим шагом добавим IAM пользователя для работы с корзиной.
Тут потребуются:
- пользователь с доступом и в AWS Console, для пользователей, и через API, для скриптов —
AWS::IAM::User
- Access и Secret ключи для API —
AWS::IAM::AccessKey
- группа —
AWS::IAM::Group
- что бы избежать «Circular dependency between resources» — отдельным ресурсом выполним добавление пользователя к группе, ресурс
AWS::IAM::UserToGroupAddition
- политика доступа к корзине —
AWS::IAM::Policy
В Parameters
добавляем параметр Password, указываем NoEcho == True
:
... "AdsTestS3UserPassword": { "Description": "User password", "Type": "String", "NoEcho": "true", "MinLength": "8", "MaxLength": "41", "AllowedPattern" : "[a-zA-Z0-9]*", "ConstraintDescription" : "must contain only alphanumeric characters.", "Default": "Gaquoe9ziu0p" } }, ...
В Resources
добавляем создание пользователя:
... "Resources" : { "AdsTestS3User" : { "Type" : "AWS::IAM::User", "Properties" : { "LoginProfile": { "Password": { "Ref" : "AdsTestS3UserPassword" } } } }, ...
Имя пользователя (параметр UserName
) можно указать, но:
If you specify a name, you cannot perform updates that require replacement of this resource. You can perform updates that require no or some interruption. If you must replace the resource, specify a new name.
Так что не будем.
Ключи доступа:
... "AdsTestS3UserKeys": { "Type": "AWS::IAM::AccessKey", "Properties": { "UserName": { "Ref": "AdsTestS3User" } } }, ...
Далее — группа:
... "AdsTestS3Group" : { "Type" : "AWS::IAM::Group" }, ...
Добавляем пользователя к группе:
... "AddTestS3UserToTestS3Group": { "Type": "AWS::IAM::UserToGroupAddition", "Properties": { "GroupName": { "Ref": "AdsTestS3Group" }, "Users": [{ "Ref": "AdsTestS3User" }] } }, ...
Описываем политику доступа к корзине:
... "AdsTestS3Policy": { "Type" : "AWS::IAM::Policy", "Properties" : { "PolicyName" : "AdsTestS3Policy", "PolicyDocument" : { "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": { "Fn::Join": [ "", [ "arn:aws:s3:::", {"Ref" : "AdsTestS3Bucket"} ]]} }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject" ], "Resource": { "Fn::Join": [ "", [ "arn:aws:s3:::", {"Ref" : "AdsTestS3Bucket"}, "/*" ]]} } ] }, "Groups" : [ { "Ref" : "AdsTestS3Group" } ] } }, ...
Проверяем шаблон:
Создаём стек:
Используем «--capabilities CAPABILITY_IAM
«, что бы избежать ошибки «(InsufficientCapabilitiesException) when calling the CreateStack operation: Requires capabilities : [CAPABILITY_IAM]«.
Проверяем доступ к корзине.
Добавим именованный профиль AWS CLI:
Проверяем доступы.
Копируем файл в корзину (Action:s3:PutObject
):
Проверяем содержимое корзины (Action:s3:ListBucket
):
Загружаем файл из корзины к себе на машину (Action:s3:GetObject
):
А вот удаление я забыл:
Обновляем полиси:
... "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", ], ...
Обновляем стек:
Пробуем ещё раз:
ОК, с этим вроде всё.
AWS::SNS::Topic
Начнём с добавления SNS.
В Parameters
добавляем NotificationEmail
:
... "NotificationEmail": { "Description": "Email to use for S3 deletion notifications", "Default": "test@domain.kiev.ua", "Type": "String" } ...
В Resources
добавляем сам SNS топик:
... "AdsTestSNSTopicpic": { "Type": "AWS::SNS::Topic", "Properties": { "DisplayName": { "Fn::Join": [ "", [ { "Ref": "AdsTestS3Bucket" }, "-deletions" ] ] }, "Subscription": [ { "Protocol": "email", "Endpoint": { "Ref": "NotificationEmail" } } ] } }, ...
AWS::SNS::TopicPolicy
Добавляем политику доступа к этому топику — какой ресурс может обращаться к нему, что бы отправлять уведомления:
... "SNSTopicPolicy": { "Type": "AWS::SNS::TopicPolicy", "Properties": { "PolicyDocument": { "Id": "S3BucketPolicy", "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "s3.amazonaws.com" }, "Action": [ "SNS:Publish" ], "Resource": "*", "Condition": { "ArnLike": { "aws:SourceArn": { "Fn::Join": [ "", [ "arn:aws:s3:*:*:", { "Ref": "BucketName" } ] ] } } } } ] }, "Topics": [ { "Ref": "AdsTestSNSTopic" } ] } }, ...
Обновляем ресурс AdsTestS3Bucket
— в Properties
добавляем параметр NotificationConfiguration
:
... "NotificationConfiguration": { "TopicConfigurations": [ { "Event": "s3:ObjectRemoved:Delete", "Topic": { "Ref": "AdsTestSNSTopic" } } ] } ...
И используем созданный в самом начале параметр BucketName
:
... "Properties": { "BucketName": { "Ref": "BucketName" }, ...
В результате ресурс корзины сейчас выглядит так:
... "AdsTestS3Bucket" : { "Type" : "AWS::S3::Bucket", "Properties": { "BucketName": { "Ref": "BucketName" }, "AccessControl": "BucketOwnerFullControl", "NotificationConfiguration": { "TopicConfigurations": [ { "Event": "s3:ObjectRemoved:Delete", "Topic": { "Ref": "AdsTestSNSTopic" } } ] }, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join": [ "-", [ {"Ref" : "AWS::StackName"}, "s3-bucket"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "AWS::StackName"} }, {"Key" : "Client", "Value" : {"Ref" : "ClientTag"} } ] } } ...
Теперь проверим синтаксис шаблона, обновим стек, проверим ресурсы, если всё ОК — перейдём к LifecycleConfiguration
.
Проверяем:
Обновляем стек:
Amazon S3 Bucket LifecycleConfiguration
Последнее, что добавим в этот шаблон — это Lifecycle.
Про Glacier постараюсь написать отдельный пост, сейчас добавим одну политику, которая будет указывать на смену типа хранилища для объектов старше 0 дней со Standart на Glacier.
В Parameters
шаблона добавим параметр, в котором будем определять — через сколько дней выполнять архивирование:
... "ArchiveTransitionInDays": { "Description": "Days becfore change storage type from Standart to Glacier", "Type": "Number", "Default": 0 }, ...
В Parameters
ресурса корзины добавляем LifecycleConfiguration
:
... "LifecycleConfiguration": { "Rules": [ { "Id": "GlacierArchive", "Status": "Enabled", "Transitions": [ { "StorageClass": "GLACIER", "TransitionInDays": { "Ref": "ArchiveTransitionInDays" } } ] } ] }, ...
Теперь ресурс корзины выглядит так:
... "AdsTestS3Bucket" : { "Type" : "AWS::S3::Bucket", "Properties": { "BucketName": { "Ref": "BucketName" }, "AccessControl": "BucketOwnerFullControl", "NotificationConfiguration": { "TopicConfigurations": [ { "Event": "s3:ObjectRemoved:Delete", "Topic": { "Ref": "AdsTestSNSTopic" } } ] }, "LifecycleConfiguration": { "Rules": [ { "Id": "GlacierArchive", "Status": "Enabled", "Transitions": [ { "StorageClass": "GLACIER", "TransitionInDays": { "Ref": "ArchiveTransitionInDays" } } ] } ] }, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join": [ "-", [ {"Ref" : "AWS::StackName"}, "s3-bucket"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "AWS::StackName"} }, {"Key" : "Client", "Value" : {"Ref" : "ClientTag"} } ] } } ...
Проверяем:
Обновляем:
Проверяем корзину:
ОК — архивирование готово.
Вроде всё?
Удалим стек полностью:
Создаём его заново:
Проверяем, всё есть, всё работает — готово.
Шаблон, немного изменённый в плане некоторых имён, можно взять тут>>>.
Ссылки по теме
S3 Lifecycle Policies, Versioning & Encryption: AWS Security
Lifecycle Configuration Elements
Transitioning Objects: General Considerations
Expiring Objects: General Considerations
How to Move Amazon S3 Data to Glacier
aws-cloudformation/IAM/IAM.template