Запускаем Packer в CI в AWS
Всегда любил CI.
С одной стороны, нет ничего сложного в том, чтобы запустить билды и сложить куда-то артефакты. С другой, CI это всегда разработка UI, что интересно и содержит в себе множество тонкостей и нюансов.
Например, буквально недавно делал пайплайн, включающий в себя Packer. Казалось бы, Packer, эка невидаль, но…
Если верить официальной доке, то минимальные необходимые права для запуска сборки имеджа пакером это
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CopyImage",
"ec2:CreateImage",
"ec2:CreateKeypair",
"ec2:CreateSecurityGroup",
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteKeypair",
"ec2:DeleteSecurityGroup",
"ec2:DeleteSnapshot",
"ec2:DeleteVolume",
"ec2:DeregisterImage",
"ec2:DescribeImageAttribute",
"ec2:DescribeImages",
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSnapshots",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:GetPasswordData",
"ec2:ModifyImageAttribute",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySnapshotAttribute",
"ec2:RegisterImage",
"ec2:RunInstances",
"ec2:StopInstances",
"ec2:TerminateInstances"
],
"Resource" : "*"
}]
}
Я бы не назвал особо удачным решением слепое повторение подобного ужаса :) Давать широкие полномочия обычному CI-агенту — безалаберность. К тому же это минимально рабочие права.
Лучшим решением будет создать отдельную VPC, поместить в неё CI сервер и его агенты, которым уже дать права на создание инстансов с определённой ролью именно в этой VPC. Для простоты поддержки можно использовать Terraform.
Чтобы сформировать политику для агентов, нужно понять, какие права им будут нужны.
Если мы скачаем исходники packer отсюда и немного пороемся в его коде, то заметим, что всё для работы с амазоном находится в каталоге packer/builder/amazon
.
Ребята из Hashicorp, кстати, не стали изобретать колесо, а заюзали отличный sdk амазона для golang. Грепаем по исходникам все вызовы методов ec2conn:
grep -Roh 'ec2conn\.[^(]\+' ./ | grep -v Config | sort -u
И получаем отличный список методов:
ec2conn.AttachVolume
ec2conn.AuthorizeSecurityGroupIngress
ec2conn.CancelSpotInstanceRequests
ec2conn.CopyImage
ec2conn.CreateImage
ec2conn.CreateKeyPair
ec2conn.CreateSecurityGroup
ec2conn.CreateSnapshot
ec2conn.CreateTags
ec2conn.CreateVolume
ec2conn.DeleteKeyPair
ec2conn.DeleteSecurityGroup
ec2conn.DeleteSnapshot
ec2conn.DeleteVolume
ec2conn.DeregisterImage
ec2conn.DescribeImageAttribute
ec2conn.DescribeImages
ec2conn.DescribeInstances
ec2conn.DescribeSnapshots
ec2conn.DescribeSpotInstanceRequests
ec2conn.DescribeSpotPriceHistory
ec2conn.DescribeSubnets
ec2conn.DescribeVolumes
ec2conn.DetachVolume
ec2conn.GetPasswordData
ec2conn.ModifyInstanceAttribute
ec2conn.RegisterImage
ec2conn.RequestSpotInstances
ec2conn.RunInstances
ec2conn.StopInstances
ec2conn.TerminateInstances
Список мы расширили. Теперь давайте его сузим и оставим только то, что нужно нашему агенту.
Если пойти логическим путём, то в каталоге amazon/common
должен быть общий для всех код. Грепнем и уберём все упоминания о Spot (для простоты):
ec2conn.AuthorizeSecurityGroupIngress
ec2conn.CreateKeyPair
ec2conn.CreateSecurityGroup
ec2conn.CreateTags
ec2conn.DeleteKeyPair
ec2conn.DeleteSecurityGroup
ec2conn.DeleteSnapshot
ec2conn.DeregisterImage
ec2conn.DescribeImages
ec2conn.GetPasswordData
ec2conn.ModifyInstanceAttribute
ec2conn.RunInstances
ec2conn.StopInstances
ec2conn.TerminateInstances
Больше похоже на правду. Повторим то же самое в каталоге amazon\ebs
(или любом из списка):
ec2conn.CopyImage
ec2conn.CreateImage
ec2conn.CreateTags
ec2conn.DeleteSnapshot
ec2conn.DeleteVolume
ec2conn.DeregisterImage
ec2conn.DescribeImageAttribute
ec2conn.DescribeImages
ec2conn.DescribeSnapshots
ec2conn.DescribeSubnets
ec2conn.DescribeVolumes
На основании этого уже можно выбрать то, что будет использоваться.
Приступим к формированию политики.
Сначала найдём в этом списке те действия (actions), для которых нельзя выполнять условия (conditions):
CopyImage
CreateImage
CreateKeyPair
CreateSecurityGroup
CreateTags
DeleteKeyPair
DeleteSnapshot
DeregisterImage
DescribeImageAttribute
DescribeImages
DescribeSnapshots
DescribeSubnets
DescribeVolumes
GetPasswordData
ModifyInstanceAttribute
Соответственно, в терраформе надо создать следующий оператор (statement):
statement {
sid = "NonResourceBasedReadOnlyPermissions"
actions = [
"CopyImage",
"CreateImage",
"CreateKeyPair",
"CreateSecurityGroup",
"CreateTags",
"DeleteKeyPair",
"DeleteSnapshot",
"DeregisterImage",
"DescribeImageAttribute",
"DescribeImages",
"DescribeSnapshots",
"DescribeSubnets",
"DescribeVolumes",
"GetPasswordData",
"ModifyInstanceAttribute",
]
resources = ["*"]
}
Необязательно разрешать всё вышеописанное. Некоторые права типа GetPasswordData
могут не использоваться. Другие, вроде CreateTags
, не обязательны для успешного завершения пакера (если вам плевать на ворнинги).
После чего пробежимся по оставшимся:
AuthorizeSecurityGroupIngress
DeleteSecurityGroup
DeleteVolume
RunInstances
StopInstances
TerminateInstances
Воспользовавшись этой ссылкой, можно относительно легко создать недостающие операторы таким образом, чтобы гарантировать создание и запуск инстансов только в нужнойс VPC и только с определённой ролью. Для этого надо найти нужные действия и выбрать для них подходящие условия (conditions)
Отдельно стоит обратить внимание на ec2:RunInstances
, так как он затрагивает множество разных ресурсов, которые попадают под разные условия.
Дальше всё проще: создаём VPC, сабнеты, роли, политики. Если необходимо, то пирим это всё с основной VPC, настраиваем роуты и вуаля, мы можем спокойно пользоваться пакером в CI в амазоне.