Ещё один пример работы с 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
