Ещё один пример работы с 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"} } ] } } } }
Проверяем его:
[simterm]
$ aws --profile ads cloudformation validate-template --template-body file://ads-s3.json { "Parameters": [ { "ParameterKey": "BucketName", "DefaultValue": "ads-test-bucket", "NoEcho": false, "Description": "Bucket name" }, { "ParameterKey": "ClientTag", "DefaultValue": "TestClient", "NoEcho": false, "Description": "Client tag value" } ], "Description": "AWS CloudFormation S3 testing stack" }
[/simterm]
ОК — теперь можно создавать сам стек, запускаем:
[simterm]
$ aws --profile ads cloudformation create-stack --stack-name ads-s3-test --template-body file://ads-s3.json { "StackId": "arn:aws:cloudformation:us-west-1:159***787:stack/ads-s3-test/9c2232b0-208b-11e8-aa51-503f2f3941c5" }
[/simterm]
Проверяем:
[simterm]
$ aws --profile ads cloudformation describe-stacks --stack-name ads-s3-test --query 'Stacks[*].StackStatus' --output text CREATE_COMPLETE
[/simterm]
И корзины:
[simterm]
$ aws --profile ads s3api list-buckets --query 'Buckets[*]' --output table | grep s3-test | 2018-03-05T15:41:09.000Z | ads-s3-test-mys3bucket-e7fz1ix67ukt |
[/simterm]
ОК, работает, продолжаем.
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" } ] } }, ...
Проверяем шаблон:
[simterm]
$ aws --profile ads cloudformation validate-template --template-body file://ads-s3.json { "Parameters": [ { "ParameterKey": "BucketName", "DefaultValue": "ads-test-bucket", "NoEcho": false, "Description": "Buclet name" }, { "ParameterKey": "AdsTestS3UserPassword", "DefaultValue": "Gaquoe9ziu0p", "NoEcho": true, "Description": "User password" }, { "ParameterKey": "ClientTag", "DefaultValue": "TestClient", "NoEcho": false, "Description": "Client tag value" } ], "Description": "AWS CloudFormation S3 testing stack", "Capabilities": [ "CAPABILITY_IAM" ], "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::User]" }
[/simterm]
Создаём стек:
[simterm]
$ aws --profile ads cloudformation create-stack --capabilities CAPABILITY_IAM --stack-name ads-s3-test --template-body file://ads-s3.json { "StackId": "arn:aws:cloudformation:us-west-1:159***787:stack/ads-s3-test/ab947ef0-2131-11e8-bb11-50a686e19fae" }
[/simterm]
Используем «--capabilities CAPABILITY_IAM
«, что бы избежать ошибки «(InsufficientCapabilitiesException) when calling the CreateStack operation: Requires capabilities : [CAPABILITY_IAM]«.
Проверяем доступ к корзине.
Добавим именованный профиль AWS CLI:
[simterm]
$ aws configure --profile ads-s3-test AWS Access Key ID [None]: AKIA***NHNQ AWS Secret Access Key [None]: PZsM***5LKFr Default region name [None]: us-west-1 Default output format [None]: json
[/simterm]
Проверяем доступы.
Копируем файл в корзину (Action:s3:PutObject
):
[simterm]
$ touch test.file $ aws --profile ads-s3-test s3 cp test.file s3://ads-s3-test-adstests3bucket-132udo8uein4x upload: ./test.file to s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file
[/simterm]
Проверяем содержимое корзины (Action:s3:ListBucket
):
[simterm]
$ aws --profile ads-s3-test s3 ls s3://ads-s3-test-adstests3bucket-132udo8uein4x 2018-03-06 14:00:44 0 test.file
[/simterm]
Загружаем файл из корзины к себе на машину (Action:s3:GetObject
):
[simterm]
$ aws --profile ads-s3-test s3 cp s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file test2.file download: s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file to ./test2.file $ ls -l | grep test -rw-r--r-- 1 setevoy setevoy 0 Mar 6 14:00 test2.file -rw-r--r-- 1 setevoy setevoy 0 Mar 6 13:50 test.file
[/simterm]
А вот удаление я забыл:
[simterm]
$ aws --profile ads-s3-test s3 rm s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file delete failed: s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied
[/simterm]
Обновляем полиси:
... "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", ], ...
Обновляем стек:
[simterm]
$ aws --profile ads cloudformation update-stack --stack-name ads-s3-test --capabilities CAPABILITY_IAM --template-body file://ads-s3.json { "StackId": "arn:aws:cloudformation:us-west-1:159***787:stack/ads-s3-test/0340d7b0-2133-11e8-a777-500c28cbde61" }
[/simterm]
Пробуем ещё раз:
[simterm]
$ aws --profile ads-s3-test s3 rm s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file delete: s3://ads-s3-test-adstests3bucket-132udo8uein4x/test.file
[/simterm]
ОК, с этим вроде всё.
AWS::SNS::Topic
Начнём с добавления SNS.
В Parameters
добавляем NotificationEmail
:
... "NotificationEmail": { "Description": "Email to use for S3 deletion notifications", "Default": "[email protected]", "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
.
Проверяем:
[simterm]
$ aws --profile ads cloudformation validate-template --template-body file://ads-s3.json { "Parameters": [ { "ParameterKey": "NotificationEmail", "DefaultValue": "[email protected]", "NoEcho": false, "Description": "Email to use for S3 deletion notifications" }, { "ParameterKey": "BucketName", "DefaultValue": "ads-test-bucket", "NoEcho": false, "Description": "Bucket name" }, { "ParameterKey": "AdsTestS3UserPassword", "DefaultValue": "Gaquoe9ziu0p", "NoEcho": true, "Description": "User password" }, { "ParameterKey": "ClientTag", "DefaultValue": "TestClient", "NoEcho": false, "Description": "Client tag value" } ], "Description": "AWS CloudFormation S3 testing stack", "Capabilities": [ "CAPABILITY_IAM" ], "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::User]" }
[/simterm]
Обновляем стек:
[simterm]
$ aws --profile ads cloudformation update-stack --stack-name ads-s3-test --capabilities CAPABILITY_IAM --template-body file://ads-3.json
[/simterm]
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"} } ] } } ...
Проверяем:
[simterm]
$ aws --profile ads cloudformation validate-template --template-body file://ads-s3.json { "Parameters": [ { "ParameterKey": "NotificationEmail", "DefaultValue": "[email protected]", "NoEcho": false, "Description": "Email to use for S3 deletion notifications" }, { "ParameterKey": "BucketName", "DefaultValue": "ads-test-bucket", "NoEcho": false, "Description": "Bucket name" }, { "ParameterKey": "AdsTestS3UserPassword", "DefaultValue": "Gaquoe9ziu0p", "NoEcho": true, "Description": "User password" }, { "ParameterKey": "ArchiveTransitionInDays", "DefaultValue": "0", "NoEcho": false, "Description": "Days becfore change storage type from Standart to Glacier" }, { "ParameterKey": "ClientTag", "DefaultValue": "TestClient", "NoEcho": false, "Description": "Client tag value" } ], "Description": "AWS CloudFormation S3 testing stack", "Capabilities": [ "CAPABILITY_IAM" ], "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::User]" }
[/simterm]
Обновляем:
[simterm]
$ !561 aws --profile ads cloudformation update-stack --stack-name ads-s3-test --capabilities CAPABILITY_IAM --template-body file://ads-s3.json { "StackId": "arn:aws:cloudformation:us-west-1:159***787:stack/ads-s3-test/0340d7b0-2133-11e8-a777-500c28cbde61" }
[/simterm]
Проверяем корзину:
ОК — архивирование готово.
Вроде всё?
Удалим стек полностью:
[simterm]
$ aws --profile ads cloudformation delete-stack --stack-name ads-s3-test
[/simterm]
Создаём его заново:
[simterm]
$ aws --profile ads cloudformation create-stack --stack-name ads-s3-test --capabilities CAPABILITY_IAM --template-body file://ads-s3.json
[/simterm]
Проверяем, всё есть, всё работает — готово.
Шаблон, немного изменённый в плане некоторых имён, можно взять тут>>>.
Ссылки по теме
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