IAM Factory

Streamlining and standardizing IAM creation

Overview

Identity Access Management (IAM) is extensively utilized and may look simple at first glance, yet it is surprisingly complex. IAM users, roles, groups, and policies form the backbone of restricting and allowing access between people, applications, databases, servers, and more.

Developers who lack experience with Identity Access Management (IAM) may spend countless hours trying to create effective policies. Even those with IAM experience can unintentionally misconfigure settings without realizing it. These accidental misconfigurations can lead to serious consequences, such as compromised data, malfunctioning applications, or malicious actions by unauthorized individuals.

Resourcely’s configuration platform is a great way to make deploying IAM simple, while ensuring that IAM resources that are created meet your company’s requirements. Below, we’ll create an IAM factory that gives developers a simple UI for creating IAM users, roles, policies, and groups. Your company can customize these Blueprints and Guardrails based on your access standards by signing up for Resourcely for free: get your account here.

Creating an IAM Blueprint

We'll focus on creating modular IAM Blueprints in this tutorial.

There are 4 primary resources needed to create an IAM group, associate users, and attach a policy:

Terraform vs Blueprint

Terraform

A Terraform example might look like this:


# Define an IAM policy
resource "aws_iam_policy" "example_policy" {
  name        = "example-policy"
  description = "Example policy for demo purposes"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action   = "s3:ListBucket"
        Effect   = "Allow"
        Resource = "arn:aws:s3:::example-bucket"
      },
      {
        Action   = "s3:GetObject"
        Effect   = "Allow"
        Resource = "arn:aws:s3:::example-bucket/*"
      }
    ]
  })
}

# Attach the policy to the group
resource "aws_iam_group_policy_attachment" "example_attachment" {
  group      = aws_iam_group.example_group.name
  policy_arn = aws_iam_policy.example_policy.arn
}

# Create IAM users
resource "aws_iam_user" "example_user1" {
  name = "example-user1"
}

resource "aws_iam_user" "example_user2" {
  name = "example-user2"
}

# Assign users to the group
resource "aws_iam_group_membership" "example_group_membership" {
  name  = "example-group-membership"
  group = aws_iam_group.example_group.name
  users = [
    aws_iam_user.example_user1.name,
    aws_iam_user.example_user2.name,
  ]
}

Blueprint

You can copy this Blueprint into your Resourcely account. For them to work properly, you need to add Global Values Collections (available here).

---
constants:
  __group_name: "{{ group_name }}_{{ __guid }}"
variables:
  group_name:
    desc: "Name for the IAM group. This name should be unique within the AWS account."
    required: true
    group: IAM Group Settings
  users:
    desc: "List of IAM user names to add to this IAM group."
    required: false
    group: IAM Group Membership
    links_to: resource.aws_iam_user.name
  iam_policies.iam_action_list:
    global_value: aws_iam_actions_collection
    group: IAM Group Policy
    desc: "Select what permission sets you'd like this policy to have"
  iam_policies.iam_effect:
    global_value: iam_effect
    group: IAM Group Policy
    desc: "Effect to apply to the actions"
  iam_policies.policy_name:
    group: IAM Group Policy
    desc: "Name of the policy to create."
  iam_policies.resources.resource:
    group: IAM Group Policy
    desc: "Select the resources you'd like to associate with this policy. Link to a resource you are creating from the dropdown, or specify an arn in the format aws:servicename:::your-specific-resource."
  iam_policies:
    group: IAM Group Policy
groups:
  IAM Group Settings:
    order: 1
    desc: "Settings for defining the IAM group, including its name."
  IAM Group Policy:
    order: 2
    desc: "Policy to create to attach to the IAM group. Choose the various effects "
  IAM Group Membership:
    order: 3
    desc: "Membership for the IAM group. List any IAM users that should belong to this group."
---

resource "aws_iam_group" "{{__group_name}}" {
  name = {{ group_name }}
}

resource "aws_iam_group_membership" "{{__group_name}}_group_membership" {
  name = "{{ group_name }}_membership"
  group = aws_iam_group.{{__group_name}}.name
  users = [
    {{# users }}
      "{{ users }}",
    {{/ users }}
  ]
}

{{# iam_policies }}

resource "aws_iam_group_policy_attachment" "{{__group_name}}_policy_attachment_{{ iam_policies.__index }}" {
  group      = aws_iam_group.{{__group_name}}.name
  policy_arn = resource.aws_iam_policy.{{ iam_policies.policy_name }}_{{iam_policies.__index}}.arn
}

resource "aws_iam_policy" "{{iam_policies.policy_name}}_{{ iam_policies.__index }}" {
  name        = "{{ iam_policies.policy_name }}"
  description = "Custom IAM policy for {{ group_name }} group."
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          {{ iam_policies.iam_action_list }}
        ]
        Effect   = [
            {{ iam_policies.iam_effect }}
        ]

        Resource = [
          {{# iam_policies.resources }}
          {{ iam_policies.resources.resource }},
          {{/ iam_policies.resources }}
        ]
      },
    ]
  })
}
{{/ iam_policies }}

With our Blueprint, we get the following form for developers:

Frontmatter

Let’s break this Blueprint down into chunks. First, the frontmatter:

---
constants:
  __group_name: "{{ group_name }}_{{ __guid }}"
variables:
  group_name:
    desc: "Name for the IAM group. This name should be unique within the AWS account."
    required: true
    group: IAM Group Settings
  users:
    desc: "List of IAM user names to add to this IAM group."
    required: false
    group: IAM Group Membership
    links_to: resource.aws_iam_user.name
  iam_policies.iam_action_list:
    global_value: aws_iam_actions_collection
    group: IAM Group Policy
    desc: "Select what permission sets you'd like this policy to have"
  iam_policies.iam_effect:
    global_value: iam_effect
    group: IAM Group Policy
    desc: "Effect to apply to the actions"
  iam_policies.policy_name:
    group: IAM Group Policy
    desc: "Name of the policy to create."
  iam_policies.resources.resource:
    group: IAM Group Policy
    desc: "Select the resources you'd like to associate with this policy. Link to a resource you are creating from the dropdown, or specify an arn in the format aws:servicename:::your-specific-resource."
  iam_policies:
    group: IAM Group Policy
groups:
  IAM Group Settings:
    order: 1
    desc: "Settings for defining the IAM group, including its name."
  IAM Group Policy:
    order: 2
    desc: "Policy to create to attach to the IAM group. Choose the various effects "
  IAM Group Membership:
    order: 3
    desc: "Membership for the IAM group. List any IAM users that should belong to this group."
---

We have three sections in the frontmatter: constants, variables, and groups.

Constants are used to define static values that you want to reference. Variables create input fields in our developer form, and groups allow us to organize our developer form.

The variables section is easy to create: in this example, we actually did it by highlighting various fields and clicking “Generate Tags”.

We define metadata such as description, which group to organize the input in, and even links_to for linking to existing resources.

IAM Group

Next up, we start creating templated Terraform utilizing our variables:

resource "aws_iam_group" "{{__group_name}}" {
  name = {{ group_name }}
}

Here, we created our first resource: the aws_iam_group. We will associate users and policies with this group, giving users that are members the ability to access certain services or resources.

The name = {{ group_name }} line references the group_name variable we defined in our frontmatter:

  group_name:
    desc: "Name for the IAM group. This name should be unique within the AWS account."
    required: true
    group: IAM Group Settings

Defining variables automatically creates a form for developers to interact with. Resourcely automatically populates the variable with choices that make sense, based on the associated field. If the variable is related to a region, relevant region options will be populated. If the variable is related to a boolean, true/false will be populated.

IAM Group Membership

Further inspecting our Blueprint, let’s check out our next resource: IAM group membership.

resource "aws_iam_group_membership" "{{__group_name}}_group_membership" {
  name = "{{ group_name }}_membership"
  group = aws_iam_group.{{__group_name}}.name
  users = [
    {{# users }}
      "{{ users }}",
    {{/ users }}
  ]
}

Here, we utilize the same group_name variable and the __group_name constant that we set in the frontmatter to set the name our aws_iam_group_membership without requiring more inputs. We append “_membership” to the variable to make the name unique. This resource will associate users with the IAM group that we created before.

The line group = aws_iam_group.{{__group_name}}.name lets us reference the name attribute of the group that we created above.

Finally, we associate usernames with the IAM group. Here we utilize section tags {{# }} and {{/ }}. Section tags allow us to take 1 or many inputs in an indexed array that can be referenced with the special __index variable. These inputs are automatically handled in our form like the below:

Section tags can be nested inside each other, which we’ll explore in depth later.

IAM Policy and Group Policy Attachment

In our last section of code, we create IAM policies and attach them to our IAM group using two resources: aws_iam_policy and aws_iam_group_policy_attachment. We wrap both of these resources in section tags, so that we can create 1 or more sets of policies and policy attachments. For more information about creating safe IAM policies, check out this good Hashi documentation on IAM policies using IaC.

{{# iam_policies }}

resource "aws_iam_group_policy_attachment" "{{__group_name}}_policy_attachment_{{ iam_policies.__index }}" {
  group      = aws_iam_group.{{__group_name}}.name
  policy_arn = resource.aws_iam_policy.{{ iam_policies.polcy_name }}_{{iam_policies.__index}}.arn
}

resource "aws_iam_policy" "{{ iam_policies.policy_name }}_{{ iam_policies.__index }}" {
  name        = "{{ iam_policies.policy_name }}"
  description = "Custom IAM policy for {{ group_name }} group."
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          {{ iam_policies.iam_action_list }}
        ]
        Effect   = [
            {{ iam_policies.iam_effect }}
        ]

        Resource = [
          {{# iam_policies.resources }}
          {{ iam_policies.resources.resource }},
          {{/ iam_policies.resources }}
        ]
      },
    ]
  })
}
{{/ iam_policies }}

The policy attachment resource is straightforward - we reference the single IAM group that we created before, and then we reference the IAM policy that we create below it.

The aws_iam_policy resource can be a complex one for developers to understand. IAM actions are not immediately obvious, and it is easy to screw up allowed resources. As a result, we have put strict controls around these inputs: giving guidance while preventing potentially harmful actions. How do we do that? Global values and Guardrails.

Using Global Values

Recall our frontmatter: for the Action and Effect variables, we referenced global values:

  iam_policies.iam_action_list:
    **global_value: aws_iam_actions_collection**
    group: IAM Group Policy
    desc: "Select what permission sets you'd like this policy to have"
  iam_policies.iam_effect:
    **global_value: iam_effect**
    group: IAM Group Policy
    desc: "Effect to apply to the actions"

These reference two global value collections that we have created. These collections are used in this case to simplify selections for developers.

For IAM actions, developers would select DynamoDB Read, and the collection value (["dynamodb:GetItem","dynamodb:Query","dynamodb:Scan","dynamodb:BatchGetItem"]) would be inserted into the Terraform generated for them when they fill out the form. Collections are a great way to simplify and restrict inputs, and they’re also customizable: you could define any profile of permissions that users could choose from and add rules around that using Guardrails.

These Global Values Collection are available here.

Handling multiple IAM policy.statement.resources with nested section tags

For resources, we utilize a nested section tag:

        Resource = [
          {{# iam_policies.resources }}
          {{ iam_policies.resources.resource }},
          {{/ iam_policies.resources }}
        ]

Nested section tags allow us to associate multiple resources with the policy. Resources expect an arn with the format arn:aws:service_name:::object_name. One common (although unsafe) pattern that users will often use is to simply insert a wildcard (*). This would allow the policy to interact with any AWS object in your account.

Restricting behavior with Guardrails

Let’s say you want to restrict this behavior. You would apply the following Resourcely Guardrail to your Blueprint:

GUARDRAIL "[IAM] Disallow IAM policies with a wildcard resource public"
  WHEN aws_iam_policy OR aws_iam_role_policy
    REQUIRE NO policy.statement.resource = "*"
  OVERRIDE WITH APPROVAL @security

If a user were to insert a wildcard into the Resources input, the resulting Terraform change request would be blocked from merging until the security team (or whichever team you specify) approves.

Modular Blueprints

Blueprints can also be modular: broken up into many small pieces, and linked together. Here are 4 modular Blueprints that can be linked together.

With these 4 Blueprints, your organization has a paved road to creating IAM that meets your expectations. Users can create users and attach policies to them, or create an assumable role and attach a policy, or create a group + users + policies. The sky is the limit! To try them out, sign up for your free account at https://portal.resourcely.io, and create the Blueprints you want based on the GitHub links above.

Conclusion

IAM is complex, hard to scale, and dangerous when improperly implemented. Platform, identity, and security teams need ways to streamline IAM configuration that isn’t manual. With Resourcely’s configuration platform, give developers self-service IAM configuration that is safe, fast, and efficient. Get your own IAM factory by signing up for Resourcely for free: get your account here.

Last updated