Skip to main content

How to create a Delegated role for assumption by one or more Primary roles

Status

OUT OF DATE

Problem

You want to configure a new set of fine-grained IAM roles (e.g. for an intern) using iam-primary-roles and iam-delegated-roles and not sure how to go about it.

Solution

Plan the privileges of the new role

A Delegated role grants a collection of privileges. Users associated with a Primary role allowed to assume the Delegated role can access these privileges.

Implement the IAM role

Not to be overlooked is the importance of choosing a good name. It should be informative, and for the sake of automation, consist solely of letters (no symbols). For this example, we will create the role restricted. Because we want restricted to be able to do more than an observer but less than a poweruser, we will need to create a new Delegated role. If we are creating this permission set for use with a Primary role that is not yet defined, we will need to create that too, but for the sake of this example, we will assume we have already created a Primary IAM role, intern, that will be granted permission to assume this new Delegated role.

delegated_roles_config:
admin:
...

restricted:
# role_policy_arns is the list of IAM Policies to attach to this
# role. You can give the full ARN of an existing policy, or you
# can give the name of a custom policy you define elsewhere
# in the project. Here, we use a custom policy name we have already
# defined, to give the restricted role permission to assume roles
# in other accounts.
role_policy_arns: ["delegated_assume_role"]
role_description: "Restricted permissions for AWS and kubernetes"
# We would set this to True were this role to also be used as a Primary role,
# but nobody should be logging in directly as `restricted` so here it is false.
sso_login_enabled: false
# trusted_primary_roles is the list of other roles that can assume this one.
trusted_primary_roles: ["admin", "ops", "poweruser", "intern"]

If we wanted to create a new policy for the new role, we would follow the example of delegated_assume_role. Create a new file with the role name in components/terraform/iam-primary-roles/, create the policy in the file, and add the policy to the custom_policy_map in components/terraform/iam-primary-roles/main.tf, but we do not need to allow our interns anything special in the identity account.

Configure or disable the Delegated role in the other accounts

By default, delegated roles are created in every account. This may or may not be desired behavior. If there are any accounts in which your role is irrelevant or should not be used to grant privileges, disable it by adding it to the exclude_roles list in stacks/gbl-$stage.yaml.

Configure IAM policy in other accounts

By default, Delegated roles will get the same-named policies in other accounts as they got in identity. In practice, this is rarely correct, but if it is correct in this case, then you need not take any of the following steps. Otherwise, you need to collect the policies to apply to the role and list them in the role_policy_arns map (which we will get to after collecting the entries).

Identify one or more AWS managed IAM policies to apply

If you want, you can use AWS managed policies (although there is a case to be made against it, which includes that they are not easily found or comprehensively documented). Their ARNs can be used directly in the role_policy_arns map.

Create a custom policy for the role

Just as with the iam-primary-roles project, we can create a custom policy for the new role. We will create a restricted policy that lets them modify resources that are tagged restrictedModify: allowed. We will combine this with the AWS managed ViewOnly policy so they can look at things. (The AWS ReadOnly policy is more permissive and would let them read secrets, so we do not want to use that.)

  1. Create a new file with the policy name under iam-delegated-roles called restricted-policy.

  2. Define the policy, something like (this has not been vetted/tested)

  data "aws_iam_policy_document" "restricted" {
statement {
sid = "PowerWhenTaggedForRestricted"
effect = "Allow"
actions = [
"cloudwatch:*",
"ec2:*",
"ecr:*",
"eks:*",
# Lambda does not support tag based access "lambda:*",
"rds:*",
"s3:*",
]
resources = ["*"]
condition {
test = "StringEquals"
values = ["allowed"]
variable = "aws:ResourceTag/restrictedModify"
}
}
}
  1. Create the policy

    resource "aws_iam_policy" "restricted" {
    name = format("%s-restricted", module.label.id)
    description = "Allow users to modify specially tagged resources"
    policy = data.aws_iam_policy_document.restricted.json
    }

  2. Add the policy to the custom_policy_map in main.tf

     custom_policy_map = { "restricted" = aws_iam_policy.restricted.arn }
  3. Override the default policy assignment for the new role

    In iam-delegated-roles/default.auto.tfvars the role_policy_arns map is just that: a map whose keys are role names and whose values are lists of policy ARNs (or names in the custom_policy_map) to assign to that role, overriding the assignments made in iam-primary-roles. Add an entry for the new role.

    role_policy_arns = {
    # Put the ARN first, it will make the Terraform output cleaner
    restricted = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess", "restricted"]
    ...
    }
Configure custom access to Delegated role

We need to allow one or more Primary IAM roles to assume this Delegated IAM role in one or more stages of our infrastructure for it to have any effect. As noted above, we will assume here that we have created the intern Primary role. Further, let's assume we wish to allow our new intern access to the restricted role's permissions only in our sbx and dev stages. We can accomplish this by adding a line for our new role to trusted_primary_role_overrides for sbx and dev as shown below.

In stacks/gbl-sbx.yaml add

projects:
terraform:
iam-delegated-roles:
vars:
trusted_primary_role_overrides:
restricted: ["dev", "intern"]

In stacks/gbl-dev.yaml add

projects:
terraform:
iam-delegated-roles:
vars:
trusted_primary_role_overrides:
restricted: ["intern"]

Extra Credit: disable roles entirely in root and audit

Because we have assigned this role the delegated_assume_role policy, it cannot get into root or audit, but to be extra careful, we can prevent this role from being created in those accounts in the first place.

In stacks/gbl-root.yaml and in stacks/gbl-audit.yaml add "restricted" to the list of additionally excluded roles.

Run Terraform to actually provision the accounts and policies

These steps must be run as admin in the root account. We must run plan and apply once for each account that uses delegated roles. Perform as below for each stage, replacing $stage with the literal stage name.

assume-role eg-gbl-root-admin bash -l

# Perform the following for each stage
atmos terraform plan iam-delegated-roles --stack=gbl-$stage
atmos terraform apply iam-delegated-roles --stack=gbl-$stage

# leave the root admin subshell
exit

Implement the Kubernetes role

EKS maps IAM roles to Kubernetes groups. Selecting appropriate permissions for the restricted group in Kubernetes is beyond the scope of this article. We will go through the steps without specifics.

Assign Kubernetes usernames and groups to the Delegated roles in each account

The admin, ops, poweruser, and observer primary IAM roles and the same delegated roles plus the terraform and helm roles required for our automation to function are configured in stacks/eks/default.auto.tfvars. Additional roles can be configured in the same file for universal permissions, or for each account in the relevant account config file. In this example, we need to add a new Kubernetes user and group and assign the new Delegated role to it. We will use the sbx account and the uw2 environment, and add the following to stacks/uw2-sbx.yaml.

Edit stacks/uw2-dev.yaml and add an entry for the restricted role to the projects:terraform:eks:vars:delegated_iam_roles list:

projects:
...
terraform:
...
eks:
vars:
delegated_iam_roles:
role: "restricted"
groups: ["idp:restricted"]

Do this for every account. Be sure to use the correct stage name and account number in the role ARN and username. We use the idp: prefix on the Kubernetes groups we create in order to avoid conflict with any pre-defined groups or roles. "IDP" is for Identity Provider and was chosen because the Kubernetes group membership is assigned based on an identity provided by an outside identity provider.

If you want to create a mapping from the Primary role in identity to a Kubernetes role, you can do that by adding an entry in the primary_additional_iam_roles map in the defaults.auto.tfvars file, but this is not recommended. That feature is provided as a shortcut for operations personnel, is not required for anything, and should not be extended to other roles because it can open up unintended privileges.

Note that we assigned the role to the group "idp:restricted". That group does not exist yet; we must create it. However, you can run the Terraform to apply the changes now if you prefer, or you can wait until after the groups are created.

Create Kubernetes roles and bindings in each account

Kubernetes roles and role mappings are defined in /projects/helmfiles/idp-roles/ The helmfile.yaml uses the Kubernetes "raw chart" to directly specify Kubernetes resources. You can follow the examples in the existing helmfile.yaml for creating ClusterRoles and ClusterRoleBindings, and create new ones as needed. At a minimum, you will need to create a new ClusterRoleBinding to bind the Group "idp:restricted" to a ClusterRole. The examples bind a User as well, but that has no practical effect.

Run Terraform and Helm in each account

Be sure to run both the Terraform under eks and the Helm under helmfiles/idp-roles for each account with an EKS cluster. These can be run the normal way, as eg-gbl-identity-ops.

Update the company AWS config file with the new Delegated role

If you created a new Delegated role, then people who want to use that role need to add it to their AWS config files, normally stored as ~/.aws/config on individual's computers. We keep the shared configuration in the repo as /aws-config.

To update the file,

  • Add "restricted" to the list of delegated_roles in /rootfs/usr/local/bin/aws-config-gen
  • Run the modified aws-config-gen and save the output in /aws-config

Let people know to update their configuration files.