aws架构师 web工具
My background is as a developer, so when I think of “devops” and “infrastructure as code” I look for the loops and conditionals of a Turing-complete language. Unfortunately for me, popular devops tools lean toward a declarative format: you describe the environment that you want, and the tool makes whatever changes are needed to achieve that goal.
我的背景是作为一名开发人员,所以当我想到“开发人员”和“基础架构即代码”时,我会寻找图灵完备语言的循环和条件。 对我来说不幸的是,流行的devops工具倾向于声明性格式:您描述了所需的环境,并且该工具对实现该目标进行了必要的更改。
In the past, when I felt that I needed to express infrastructure as “real” code, I would turn to cfndsl, a Ruby gem that lets you generate CloudFormation templates. And while it simplifies some tasks, it has a lot of boilerplate and is an oddity for organizations that don’t already use Ruby.
过去,当我感觉需要将基础结构表示为“真实”代码时,我将转向cfndsl ,它是一个Ruby宝石,可让您生成CloudFormation模板。 尽管它简化了一些任务,但它有很多样板,对于尚未使用Ruby的组织来说,这很奇怪。
Which is why I was excited about Amazon’s announcement of the Cloud Development Kit at the 2019 New York Summit. Not only does it provide a programmatic way to generate CloudFormation templates, it can abstract away much of the boilerplate: the demo at the Summit created a 700-line CloudFormation template from a few lines of TypeScript.
这就是为什么我对亚马逊在2019年纽约峰会上发布云开发套件感到兴奋的原因。 它不仅提供了一种生成CloudFormation模板的编程方式,而且还可以抽象出许多样板:Summit上的演示从几行TypeScript创建了一个700行的CloudFormation模板。
Clearly that example was picked for its “wow!” factor, but I was hopeful that other common tasks could be similarly condensed. To test that, I decided to do a head-to-head comparison between CDK, Terraform, and CFNDSL, using a common task: managing the users in your AWS accounts and the roles they can assume.
显然,该示例被选为“哇!” 因素,但我希望其他共同的任务可以类似地简化。 为了测试这一点,我决定使用一项常见任务在CDK,Terraform和CFNDSL之间进行正面对比:管理您AWS账户中的用户以及他们可以扮演的角色。
This is a long post … grab a cup of coffee.
这是一篇很长的文章……抢一杯咖啡。
任务 (The Task)
Create IAM users, assign them to groups, and then allow the groups to assume roles.
创建IAM用户,将其分配给组,然后允许组承担角色。
This is a common infrastructure task for organizations that use multiple accounts to isolate workloads (eg, development and production). When users join or leave the organization, or switch roles, that change must be reflected in the roles that they can assume. Since this happens rather frequently, we want to script the process and maintain a log of all changes in our source repository. I add a slight twist, in that the users are created in a third “operations” account, and then assume roles to gain permissions in the “deployment” accounts.
对于使用多个帐户隔离工作负载(例如,开发和生产)的组织,这是一项常见的基础结构任务。 当用户加入或离开组织或切换角色时,该更改必须反映在他们可以担任的角色中。 由于这种情况经常发生,因此我们希望编写脚本流程并维护源存储库中所有更改的日志。 我稍加修改,是在第三个“操作”帐户中创建用户,然后承担在“部署”帐户中获得权限的角色。
Note: Amazon’s Single Sign-On service provides an alternative way to accomplish this task, bypassing much of the AWS Identity and Access Management (IAM) infrastructure. For some organizations, particularly those that already use Microsoft Active Directory and/or want single sign-on for third party applications, it may be a better choice. However, as-of this writing it’s not scriptable and does not support TOTP-based multi-factor authentication, so I still consider AWS-centric user management a valid choice.
注意: Amazon的单一登录服务提供了另一种方法来完成此任务,而绕过了许多AWS Identity and Access Management(IAM)基础架构。 对于某些组织,尤其是那些已经使用Microsoft Active Directory和/或希望对第三方应用程序进行单点登录的组织,这可能是一个更好的选择。 但是,在撰写本文时,它不是可编写脚本的,并且不支持基于TOTP的多因素身份验证,因此我仍然认为以AWS为中心的用户管理是一个有效的选择。
竞争者 (The Contenders)
CloudFormation, the AWS-provided tool for managing infrastructure. A CloudFormation template describes resources such as EC2 instances, RDS databases, or in this case, IAM users and groups. I’m including CloudFormation in this post as a baseline: it does not provide any native scripting facilities; everything must be explicitly declared.
CloudFormation ,AWS提供的用于管理基础结构的工具。 CloudFormation模板描述了资源,例如EC2实例,RDS数据库,或者在这种情况下为IAM用户和组。 我在本文中将CloudFormation作为基准:它不提供任何本机脚本功能; 一切都必须明确声明。
Terraform: the go-to tool for many devops engineers. Terraform scripts are declarative: you specify the resources that you want to create, and the attributes of those resources, and Terraform does the work of making your deployment look like the declaration. Terraform supports user-defined modules, which gives you a reusable toolbox of standard infrastructure, and it supports a limited form of iteration, where you can specify that a group of similar resources are to be created at the same time. Terraform stores the current state of your deployment in a file that must be checked-in alongside the declaration; it compares the declaration against the recorded state to decide what to do.
Terraform :许多开发人员工程师的必备工具。 Terraform脚本是声明性的:您可以指定要创建的资源以及这些资源的属性,然后Terraform可以使部署看起来像声明。 Terraform支持用户定义的模块,该模块为您提供了可重复使用的标准基础结构工具箱,并支持有限的迭代形式,您可以在其中指定要同时创建一组相似的资源。 Terraform将部署的当前状态存储在必须与声明一起检入的文件中; 它将声明与记录的状态进行比较,以决定要做什么。
CFNDSL: a Ruby gem that generates CloudFormation templates. It is typically invoked as a rake
task, which works well if you’re already building Ruby applications, but is yet another tool to learn for those who don’t. It works by building an in-memory data structure that represents the CloudFormation template, then transforms that structure into JSON so that you can create a stack. Since you’re just manipulating a data structure, it’s easy to use Ruby constructs such as loops and conditionals. However, since the data structure must produce a CloudFormation template, you need to specify all of the information required by the template.
CFNDSL :生成CloudFormation模板的Ruby宝石。 它通常是作为rake
任务来调用的,如果您已经在构建Ruby应用程序,它会很好地工作,但是对于那些不熟悉Ruby的人来说,它也是另一个学习工具。 它通过构建表示CloudFormation模板的内存中数据结构来工作,然后将该结构转换为JSON,以便您可以创建堆栈。 由于您只是在操纵数据结构,因此使用Ruby构造(例如循环和条件)很容易。 但是,由于数据结构必须生成CloudFormation模板,因此您需要指定模板所需的所有信息。
CDK, the AWS Cloud Development Kit, is an Amazon-sponsored open-source tool that lets you build CloudFormation templates programmatically. Like CFNDSL, it does this by manipulating an in-memory model of the template. Unlike CFNDSL, CDK provides “constructs” that have intelligent default configuration values, allowing a relatively small source file to generate a large CloudFormation template. CDK supports several scripting languages, but the documentation uses TypeScript so that’s what I’ll use for this post.
CDK (AWS云开发工具包)是Amazon赞助的开源工具 ,可让您以编程方式构建CloudFormation模板。 像CFNDSL一样,它通过操纵模板的内存模型来做到这一点。 与CFNDSL不同,CDK提供具有智能默认配置值的“结构”,从而允许相对较小的源文件生成较大的CloudFormation模板。 CDK支持多种脚本语言,但是文档使用TypeScript,所以我将在本文中使用。
实施 (The Implementations)
My goal with this post is to give the flavor of these different approaches. So I’m going to assume that (1) you’re familiar with how IAM users, groups, and roles work, and (2) have some experience with CloudFormation and the idea of declarative infrastructure. With these assumptions, I believe that you’ll be able to understand the other versions, even if you aren’t familiar with Terraform, Ruby, or TypeScript. Note that I also leave out parts of the scripts that distract from the points that I’m trying to make. If you want to look at fully functional versions of these scripts, they’re available from GitHub, along with execution instructions.
我在这篇文章中的目标是给出这些不同方法的味道。 因此,我假设(1)您熟悉IAM用户,组和角色的工作方式,(2)对CloudFormation和声明性基础架构的想法有一定的了解。 有了这些假设,即使您不熟悉Terraform,Ruby或TypeScript,我相信您也可以理解其他版本。 请注意,我还忽略了一些脚本,这些脚本分散了我要提出的观点。 如果您想查看这些脚本的功能齐全的版本,可以从GitHub上获得它们以及执行说明。
Since I’m a developer at heart, I want to manage my users via data tables, rather than repeated code blocks. Therefore, I’ve written each implementation (except native CloudFormation) around the following data structures:
因为我是一名开发人员,所以我想通过数据表而不是重复的代码块来管理用户。 因此,我围绕以下数据结构编写了每个实现(本机CloudFormation除外):
- The list of users. 用户列表。
- The list of groups. 组列表。
- A lookup table that associates users with the groups they belong to. 将用户与他们所属的组相关联的查找表。
- A lookup table that associates groups with the roles that group members can assume. 将组与组成员可以承担的角色相关联的查找表。
步骤1:建立使用者 (Step 1: Creating Users)
Creating the users for your organization is a task that many people do manually, via the AWS Console. Partly, that’s because just creating a user is just the start: you also have to enable access (programmatic and/or console), and securely convey the access keys and/or password to the person represented by that user. However, scripting can bring rigor and traceability to user management, especially in the case where a user leaves the organization.
为您的组织创建用户是许多人通过AWS控制台手动完成的任务。 在某种程度上,这是因为仅创建用户才是开始:您还必须启用访问权限(编程和/或控制台),并将访问密钥和/或密码安全地传达给该用户代表的人。 但是,脚本编写可以为用户管理带来严格性和可追溯性,尤其是在用户离开组织的情况下。
For this task I create three users, and attach a pre-existing policy that lets them manage their own account (for example, to change the password). While I could also assign them an initial password, I consider that irrelevant to this post.
为此,我创建了三个用户,并附加了一个预先存在的策略,使他们可以管理自己的帐户(例如,更改密码)。 虽然我还可以为他们分配一个初始密码,但我认为与该帖子无关。
The CloudFormation variant of this task shows why other tools exist: you have to specify each user individually. In a large stack, this can easily become confusing, especially if you intermingle user resources with group assignments and group permissions. And in a larger organization, you will probably run into the limit of 200 resource definitions per stack.
此任务的CloudFormation变体显示了为什么存在其他工具的原因:您必须分别指定每个用户。 在大型堆栈中,这很容易引起混淆,尤其是在将用户资源与组分配和组权限混合在一起的情况下。 在大型组织中,每个堆栈可能会遇到200个资源定义的限制。
I’m using YAML as my stack description language: it’s significantly more compact than JSON, and allows comments. For this first example I show the top-level stack elements; later examples will omit them.
我将YAML用作堆栈描述语言:它比JSON紧凑得多,并且允许注释。 对于第一个示例,我显示了顶级堆栈元素。 后面的示例将忽略它们。
By comparison, creating multiple resources is an area where Terraform shines: you can specify your list of users as a variable, and then declare a single resource that is applied to everyone in the list:
相比之下,创建多个资源是Terraform的亮点:您可以将用户列表指定为变量,然后声明一个应用于列表中每个人的资源:
provider "aws" {}
variable "users" {
type = list
default = [ "user1", "user2", "user3" ]
}
resource "aws_iam_user" "users" {
count = length(var.users)
name = "${var.users[count.index]}"
force_destroy = true
}
This, however, isn’t the entire script: we also need to add the reference to the basic user policy. Unlike CloudFormation, which declares the policy attachment inside the user resource, Terraform requires creating another resource, which is also driven by the users
array. We also need to create a “data” object to gain access to the invoking AWS account ID.
但是,这还不是全部脚本:我们还需要添加对基本用户策略的引用。 与CloudFormation在用户资源中声明策略附件不同,Terraform需要创建另一个资源,该资源也由users
数组驱动。 我们还需要创建一个“数据”对象以获得对调用中的AWS账户ID的访问权。
data "aws_caller_identity" "current" {}
resource "aws_iam_user_policy_attachment" "base_user_policy_attachment" {
count = length(var.users)
user = "${var.users[count.index]}"
policy_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/BasicUserPolicy"
}
Before I leave Terraform, I should note that this script doesn’t always work on the first try. I think it’s an eventual consistency issue: AWS has “created” the user, but it’s not ready to be assigned a policy. Running the script a second time will correct that, but it’s a little annoying.
在离开Terraform之前,我应注意,该脚本并非总是在第一次尝试时就起作用。 我认为这是最终的一致性问题:AWS已经“创建”了用户,但还没有准备好分配策略。 再次运行该脚本将纠正此问题,但这有点烦人。
Moving on to CFNDSL: it looks a lot like the CloudFormation template. You can guess that IAM_User
corresponds to an AWS::IAM::User
resource, and it has the same attributes. However, IAM_User
is actually a function that adds the resource specification onto a tree of resources. Which means that it can be called from inside a loop, so that you only need to specify the resource once. When you run this program using the cfndsl
gem, it will generate a CloudFormation template that is equivalent to the one I showed above.
转到CFNDSL:看起来很像CloudFormation模板。 您可以猜测IAM_User
对应于AWS::IAM::User
资源,并且具有相同的属性。 但是, IAM_User
实际上是一个将资源规范添加到资源树上的函数。 这意味着可以从循环内部调用它,因此您只需指定一次资源。 当您使用cfndsl
gem运行该程序时,它将生成一个CloudFormation模板,该模板与我上面显示的模板相同。
users = [ "user1", "user2", "user3" ]
CloudFormation do
Description "Manages the account's users"
users.each do |user|
IAM_User("#{user}") do
UserName user
ManagedPolicyArns [ FnSub("arn:aws:iam::${AWS::AccountId}:policy/BasicUserPolicy") ]
end
end
end
The CDK version is, surprisingly, the longest. There is a lot of boilerplate to a very simple stack: you have to create a Typescript class that represents the stack, with a constructor function. Not shown here are the files generated by the cdk init
command, which form the “application” that includes your stack.
令人惊讶的是,CDK版本最长。 一个非常简单的堆栈有很多样板:必须使用构造函数创建一个代表堆栈的Typescript类。 此处未显示cdk init
命令生成的文件,这些文件构成包含您的堆栈的“应用程序”。
import cdk = require('@aws-cdk/core');
import iam = require('@aws-cdk/aws-iam');
const userNames : string[] = [ "user1", "user2", "user3" ]
export class UsersStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// since we're creating constructs inside functions, need to cache "this"
let stack = this
userNames.forEach(function(userName) {
const user = new iam.User(stack, userName, {
userName: userName
})
user.addManagedPolicy({
managedPolicyArn: stack.formatArn({
service: "iam",
region: "",
resource: "policy",
resourceName: "BasicUserPolicy"
})
})
})
}
}
This particular example was also lengthened because I had to construct the attached policy ARN from its components. There’s a function that hides this for AWS-managed policies, but not customer-managed policies (although there’s an open issue to fix this, and while I was writing this post a solution was created, but has not yet been released).
由于必须从其组成部分构建附加的策略ARN,因此也延长了此特定示例的时间。 有一个功能为AWS托管策略隐藏了此功能,但没有为客户托管策略隐藏此功能(尽管有一个开放的问题可以解决此问题 ,并且在我撰写本文时,已创建了一个解决方案 ,但尚未发布)。
步骤2:将用户添加到组 (Step 2: Adding Users to Groups)
One of the AWS IAM Best Practices is to assign user permissions via groups. For example, if your team manages two applications, you could create a group for each and then grant permissions (such as reading specific CloudWatch logs) to the group. If a user works on both applications, she is a member of both groups; if she stops working on one, she can be removed from the appropriate group without concern for her ability to work on the other.
AWS IAM最佳实践之一是通过组分配用户权限。 例如,如果您的团队管理两个应用程序,则可以为每个应用程序创建一个组,然后向该组授予权限(例如读取特定的CloudWatch日志)。 如果用户同时在两个应用程序上工作,则她是两个组的成员; 如果她停止在另一人上工作,则可以将她从适当的小组中删除,而不必担心她在另一人上的工作能力。
For this example I create two groups, then divide the users from the previous step between them: user1
belongs to both groups, while user2
and user3
belong to one apiece.
在此示例中,我创建了两个组,然后将上一步中的用户划分为两个组: user1
属于两个组,而user2
和user3
属于一个组。
With CloudFormation, this is, again, all explicit, but not overly onerous: I create a AWS::IAM::Group
resource for each group (only one shown here), then update each AWS::IAM::User
with a Groups
attribute that refers to the appropriate group(s). If you’re creating the users and groups in the same stack, this will also establish an implicit deployment dependency: CloudFormation will create the groups groups first so that they can be referenced by the users.
使用CloudFormation,这再次是所有显式的,但又不会太繁琐:我为每个组创建一个AWS::IAM::Group
资源(此处仅显示一个),然后使用Groups
更新每个AWS::IAM::User
指向适当组的属性。 如果要在同一堆栈中创建用户和组,这还将建立隐式部署依赖关系:CloudFormation将首先创建组组,以便用户可以引用它们。
The Terraform code to create groups looks a lot like the code that created users: a single resource definition that’s driven by an array.
用于创建组的Terraform代码与创建用户的代码非常相似:一个由数组驱动的资源定义。
variable "groups" {
type = list
default = [ "group1", "group2" ]
}
resource "aws_iam_group" "groups" {
count = length(var.groups)
name = "${var.groups[count.index]}"
}
Assigning members to groups is a bit more complex: as with attaching policies to users, you use a separate resource declaration to attach users to groups. And there are two ways to do that: either attach users to groups, or attach groups to users. The approach that you choose depends on how you want to manage your data structures.
将成员分配给组要稍微复杂一点:与将策略附加到用户一样,您可以使用单独的资源声明将用户附加到组。 有两种方法:将用户附加到组,或将组附加到用户。 选择的方法取决于您要如何管理数据结构。
In my view, it’s more readable to list the groups associated with each user, so I chose a mapping between username and a list of groups:
在我看来,列出与每个用户关联的组更易读,因此我选择了用户名和组列表之间的映射:
variable "group_members" {
type = map(list(string))
default = {
"user1" = [ "group1", "group2" ],
"user2" = [ "group1" ],
"user3" = [ "group2" ]
}
}
The resource definition itself is based on the users variable, but extracts the groups for each user from that variable. This is about as tricky as I like to get with Terraform, but it’s undeniably compact and relatively easy to read.
资源定义本身基于用户变量,但是从该变量中提取每个用户的组。 这与我想使用Terraform时一样棘手,但是无可否认它是紧凑的并且相对容易阅读。
resource "aws_iam_user_group_membership" "group-membership" {
count = length(var.users)
user = "${var.users[count.index]}"
groups = "${var.group_members[var.users[count.index]]}"
}
The CFNDSL version again looks a lot like the CloudFormation script: we create the groups via IAM_Group
(again using a loop), and reference the groups in IAM_User
. The one tricky bit is that I take the list of group names, and use the map
function to translate them to references to the group resources.
CFNDSL版本再次看起来很像CloudFormation脚本:我们通过IAM_Group
(再次使用循环)创建组,并在IAM_User
引用这些组。 棘手的一点是,我获取了组名列表,并使用map
函数将其转换为对组资源的引用。
users = [ "user1", "user2", "user3" ]
groups = [ "group1", "group2" ]
group_members = {
"user1" => [ "group1", "group2" ],
"user2" => [ "group1" ],
"user3" => [ "group2" ]
}
CloudFormation do
groups.each do |group|
IAM_Group("#{group}") do
GroupName group
end
end
users.each do |user|
IAM_User("#{user}") do
UserName user
ManagedPolicyArns [ FnSub("arn:aws:iam::${AWS::AccountId}:policy/BasicUserPolicy") ]
Groups group_members[user].map { |group| Ref("#{group}") }
end
end
end
With CDK, I had to specify the user/group relationships using a typed Map
object. It’s more boilerplate than a JavaScript-style nested object, but it keeps the compiler happy.
使用CDK,我必须使用类型化的Map
对象指定用户/组的关系。 它比JavaScript样式的嵌套对象更简单,但是可以使编译器满意。
const userGroups = new Map()
userGroups.set("user1", [ "group1", "group2" ])
userGroups.set("user2", [ "group1" ])
userGroups.set("user3", [ "group2" ])
Keeping the compiler happy was also a big part of creating the user/group relationships. While the underlying IAM API refers to users and groups as strings, CDK scripts deal with objects. The User
object has a method addToGroup()
that takes an IGroup
object, and there’s no way to simply add a group by name. This has several implications, the biggest being that you can’t create your users and groups in separate stacks, since you need the actual objects.
使编译器保持满意也是创建用户/组关系的重要组成部分。 虽然底层的IAM API将用户和组称为字符串,但CDK脚本处理对象。 User
对象具有采用IGroup
对象的addToGroup()
方法,无法简单地通过名称添加组。 这有几个含义,最大的含义是您不能在单独的堆栈中创建用户和组,因为您需要实际的对象。
I resolved this issue by creating two maps: usersByName
and groupsByName
. These are populated by the code that creates the users and groups; the key is the user/group name, and the value is the CDK object.
我通过创建两个映射解决了此问题: usersByName
和groupsByName
。 这些由创建用户和组的代码填充; 键是用户/组名,值是CDK对象。
export class UsersAndGroupsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// since we creating constructs inside functions, we need to preserve "this"
let stack = this
// these two maps let us retrieve constructs for later use
let groupsByName = new Map()
let usersByName = new Map()
groupNames.forEach(function(groupName) {
const group = new iam.Group(stack, groupName, {
groupName: groupName
})
groupsByName.set(groupName, group)
})
userNames.forEach(function(userName) {
const user = new iam.User(stack, userName, {
userName: userName
})
// add BasicUserPolicy
usersByName.set(userName, user)
})
}
}
While I could have attached the groups while creating the users, I decided to write a separate loop to do so: I quickly learned that it was a lot easier to develop a working template if I added small pieces at a time. Since the CDK program is simply building an in-memory data model that it converts to a CloudFormation template, there’s no performance benefit to be gained from one approach over the other.
尽管我可以在创建用户时附加组,但我还是决定编写一个单独的循环来做:我很快了解到,如果一次添加小块,则开发工作模板要容易得多。 由于CDK程序只是在构建内存数据模型并将其转换为CloudFormation模板,因此从一种方法获得的性能优势不会比另一种方法获得。
userGroups.forEach(function(groupNames, userName) {
const user = usersByName.get(userName)
groupNames.forEach(function(groupName) {
const group = groupsByName.get(groupName)
if (user && group) {
user.addToGroup(group)
}
})
})
步骤3:为群组分配权限 (Step 3: Assigning Permissions to Groups)
As I described at the top of this post, groups grant users the ability to assume roles that belong to different accounts within the organization. To make this happen I add an inline policy to each group that grants sts:AssumeRole
for those roles (creating roles is beyond the scope of this post) While I could have created a managed policy rather than an inline policy, I don’t see that it adds much benefit in this case.
正如我在本文开头所述,组使用户能够承担属于组织内不同帐户的角色。 为此,我向每个授予sts:AssumeRole
角色的组添加一个内联策略(创建角色超出了本文的范围)虽然我可以创建一个托管策略而不是一个内联策略,但我看不到在这种情况下,它增加了很多好处。
As always, I’m going to start with CloudFormation, which requires everything to be declared explicitly. I use parameters to hold account IDs to make the script more readable: especially with a large organization, it can be difficult to remember which numeric ID belongs to which account. And for brevity, I again limit myself to one group.
与往常一样,我将从CloudFormation开始,它需要明确声明所有内容。 我使用参数来保存帐户ID,以使脚本更具可读性:尤其是对于大型组织,可能很难记住哪个数字ID属于哪个帐户。 为了简洁起见,我再次将自己限制在一组。
For Terraform I maintain the relationships in a map, similar to the way that I handled user/group relationships. This map, however, has more levels: each group is associated with a list of roles, and each role in turn is composed of an account ID and role name.
对于Terraform,我在地图中维护关系,类似于我处理用户/组关系的方式。 但是,此映射具有更多级别:每个组都与角色列表相关联,并且每个角色又由一个帐户ID和角色名称组成。
variable "group_permissions" {
type = map(list(list(string)))
default = {
"group1" = [
[ "dev", "FooAppDeveloper" ],
[ "prod", "FooAppReadOnly" ]
],
"group2" = [
[ "dev", "BarAppDeveloper" ],
[ "prod", "BarAppReadOnly" ]
]
}
}
I have another lookup table for that associates those account IDs with a human-readable name.
我有另一个查询表,用于将这些帐户ID与人类可读的名称相关联。
variable "account_id_lookup" {
type = map
default = {
"dev" = "123456789012",
"prod" = "234567890123"
}
}
The actual resource definition starts with a “data” object that represents the policy. Data objects serve two very different purposes in Terraform. The first is retrieving information about the environment; you saw that in the first example, when I used one to retrieve the AWS account ID. The second purpose is to allow you to specify hierarchical data within the script using Terraform syntax, and then convert that object into JSON for use in a resource. This is the purpose that I’m using here: for each group, I create a policy object that holds the assumable roles, and then convert it to JSON to attach to the group.
实际的资源定义从代表策略的“数据”对象开始。 数据对象在Terraform中有两个非常不同的用途。 首先是检索有关环境的信息。 您在第一个示例中看到了这一点,当我使用它来检索AWS账户ID时。 第二个目的是允许您使用Terraform语法在脚本内指定分层数据,然后将该对象转换为JSON以在资源中使用。 这是我在这里使用的目的:对于每个组,我创建一个拥有承担角色的策略对象,然后将其转换为JSON以附加到该组。
data "aws_iam_policy_document" "group-policies" {
count = length(var.groups)
statement {
sid = "1"
actions = [
"sts:AssumeRole"
]
resources = [
for acct_role in var.group_permissions[var.groups[count.index]]:
"arn:aws:iam::${var.account_id_lookup[acct_role[0]]}:role/${acct_role[1]}"
]
}
}
As with the previous definitions, group-policies
uses a count to create multiple instances. However, it also makes use of another iteration technique, “for expressions,” which were introduced with version 0.12. A for-expression acts much like Ruby’s map
function: it performs a transformation on each element of an array, producing an array of transformed values. Here I transform the list of account/role pairs associated with the group into ARNs for use in the policy. In terms of trickiness (and difficulty to read), this is past what I’m comfortable with, but it’s the only way I’ve found to make Terraform use nested data structures.
与先前的定义一样, group-policies
使用计数来创建多个实例。 但是,它也利用了另一种迭代技术,即“ 用于表达式 ”,该技术是在0.12版中引入的。 for-expression的行为与Ruby的map
函数非常相似:它对数组的每个元素执行转换,从而生成转换后的值的数组。 在这里,我将与该组关联的帐户/角色对的列表转换为ARN,以便在策略中使用。 就棘手(和难于阅读)而言,这已经超出了我所能接受的范围,但这是我发现使Terraform使用嵌套数据结构的唯一方法。
resource "aws_iam_group_policy" "group-policies" {
count = length(var.groups)
name = "group-policies-${count.index}"
group = "${var.groups[count.index]}"
policy = "${data.aws_iam_policy_document.group-policies[count.index].json}"
}
Like the Terraform example, my CFNDSL example has a nested map defining account/role pairs, and a map defining a name/ID table for accounts.
像Terraform示例一样,我的CFNDSL示例具有定义帐户/角色对的嵌套映射和定义帐户名称/ ID表的映射。
account_lookup = {
"dev" => "123456789012",
"prod" => "234567890123"
}
group_permissions = {
"group1" => [
[ "dev", "FooAppDeveloper" ],
[ "prod", "FooAppReadOnly" ]
],
"group2" => [
[ "dev", "BarAppDeveloper" ],
[ "prod", "BarAppReadOnly" ]
]
}
And like the Terraform example, I create the policy document separately, and transform it to JSON when creating the policy. The Resource
section of this document uses the map
function, which you’ve seen before, to iterate over the list of permissions retrieved from the map. Unlike the Terraform for-in operation, however, this function executes a block of Ruby code. Which means that I can introduce “explanatory variables” that hold pieces of the role array, and make the code easier to understand.
就像Terraform示例一样,我单独创建策略文档,并在创建策略时将其转换为JSON。 本文档的“ Resource
部分使用map
功能(您之前已经看到过)来遍历从映射检索到的权限列表。 但是,与Terraform for-in操作不同,此函数执行一个Ruby代码块。 这意味着我可以引入“解释变量”来保存角色数组的各个部分,并使代码更易于理解。
CloudFormation do
groups.each do |group|
IAM_Group("#{group}") do
GroupName group
end
policy_document = {
"Version" => "2012-10-17",
"Statement" => [{
"Effect" => "Allow",
"Action" => [ "sts:AssumeRole" ],
"Resource" => group_permissions[group].map { |acct_role|
account_id = account_lookup[acct_role[0]]
role_name = acct_role[1]
"arn:aws:iam::#{account_id}:role/#{role_name}"
}
}]
}.to_json
IAM_Policy("#{group}Policy") do
PolicyName "#{group}-AssumeRolePolicy"
PolicyDocument policy_document
Groups [ group ]
end
end
# user creation omitted
end
Last up, CDK. At this point, I don’t think there’s anything new to describe: as with the previous examples I use a map of account names to IDs and a nested map of assignable roles. Then I iterate through that structure, create a policy statement, and add it to the group. Unlike other examples, where the policy statement had to be translated to JSON, CDK provides an object, iam.PolicyStatement
, which manages the document elements and performs that translation internally.
最后,CDK。 在这一点上,我认为没有什么要描述的:与前面的示例一样,我使用帐户名到ID的映射以及嵌套的可分配角色的映射。 然后,我遍历该结构,创建一个策略声明,并将其添加到组中。 与其他必须将策略语句转换为JSON的示例不同,CDK提供了一个对象iam.PolicyStatement
,该对象管理文档元素并在内部执行该转换。
const accountIds = new Map()
accountIds.set("dev", "123456789012")
accountIds.set("prod", "234567890123")
const groupPermissions = new Map<string,Array>()
groupPermissions.set("group1", [
[ "dev", "FooAppDeveloper" ],
[ "prod", "FooAppReadOnly" ]
])
groupPermissions.set("group2", [
[ "dev", "BarAppDeveloper" ],
[ "prod", "BarAppReadOnly" ]
])
export class UsersAndGroupsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// user and group creation/assignment go here; both are added to maps for future use
groupPermissions.forEach(function(roleSpecs, groupName) {
let assumableRoles = roleSpecs.map(function([accountName, roleName]) {
// use a default value to avoid explicit existence test ... should always be valid
let accountId = accountIds.get(accountName) || ""
return stack.formatArn({
service: "iam",
region: "",
account: accountId,
resource: "role",
resourceName: roleName
})
})
let policyStatement = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [ "sts:AssumeRole" ],
resources: assumableRoles
})
let group = groupsByName.get(groupName)
if (group) {
group.addToPolicy(policyStatement)
}
})
}
}
结论 (Conclusions)
I think that, in the end, the debate is between Terraform and CDK. Pure CloudFormation is just too verbose for large deployments, and CFNDSL doesn’t provide a significant level of abstraction. Plus, Ruby may be off-putting for organizations that don’t already use it.
我认为,最终的争论是Terraform和CDK之间的争论。 对于大型部署,Pure CloudFormation太冗长了,CFNDSL并没有提供足够的抽象级别。 另外,对于尚未使用Ruby的组织而言,Ruby可能会令人反感。
CDK is promising, but not ready for prime-time. To be fair, this task did not highlight its strength, which is highly abstracted, reusable constructs. But an infrastructure tool has to be able to do the basics, not just the flash. There are also places where it’s just broken, although I was able to work around those for this post. Perhaps in six months I’ll be willing to re-evaluate it.
CDK很有前途,但还没有准备好迎接黄金时段。 公平地说,此任务并未突出其强度,它是高度抽象的,可重用的结构。 但是,基础架构工具必须具备基本功能,而不仅仅是闪存。 尽管我能够解决本文中提到的那些问题,但也有一些地方刚刚被打破 。 也许六个月后,我会愿意对其进行重新评估。
Terraform remains my go-to for creating infrastructure, even though its declarative nature opposes my imperative nature. It is, however, trying to meet me halfway, with the addition of for-comprehensions in version 0.12 and the promise of more iteration in the future.
尽管Terraform的声明性与我的命令性相反,但Terraform仍然是我创建基础结构的首选。 但是,它正在尝试半途而废,在版本0.12中增加了理解,并承诺将来会进行更多迭代。
This post was originally written for the Chariot Solutions Blog by Keith Gregory, Chariot’s AWS Practice Lead. You can read the original post here.
这篇文章最初是 由 Chariot的AWS实践主管 Keith Gregory 为 Chariot解决方案博客 撰写的 。 您可以在 此处 阅读 原始帖子 。
Starting an AWS project? Chariot Solutions is a certified Consulting Partner for AWS. You can count on us for professional services that help you map out the most efficient route to cloud adoption, modernization, app development and deployment, IoT, machine learning, data and analytics. Read more about our AWS offerings at Chariot, or contact us to see how we can help with your project.
开始一个AWS项目吗? Chariot Solutions是AWS的认证咨询合作伙伴。 您可以依靠我们提供的专业服务,这些服务可以帮助您规划出最有效的方法来实现云采用,现代化,应用程序开发和部署,物联网,机器学习,数据和分析。 在Chariot上 了解 有关我们的AWS产品的 更多 信息,或 与我们联系 以了解我们如何为您的项目提供帮助。
aws架构师 web工具