Skip to main content

Pre-created IAM roles

Some teams do not let the deployment pipeline create IAM roles. For those teams, the aws-ecs-runtime module ships a small IAM module. A team with IAM rights applies it first to create the roles. The pipeline then passes the role ARNs to the runtime module through iam_roles. With iam_roles set, the runtime module creates no IAM roles, policies or attachments and makes no IAM API calls.

New deployments only

Use pre-created roles for new deployments. Do not switch an existing deployment to them. The module already manages the roles of an existing deployment, and the switch needs manual state surgery. See Existing deployments.

How it fits together​

There are two steps. Usually they run as separate Terraform states, applied by different teams.

  1. Security team, with IAM rights. Apply the IAM module. It creates the roles and outputs their ARNs and an iam:PassRole policy.
  2. Deployment pipeline, without IAM rights. Deploy the runtime module with iam_roles set to the ARNs from step 1.

Use the same module version for both steps.

Step 1: Create the roles​

The IAM module ships in the same archive as the runtime module. Point the source at the modules/iam subdirectory.

module "upwind_iam" {
source = "https://get.upwind.io/terraform/modules/aws-ecs-runtime/aws-ecs-runtime-1.5.1.tar.gz//modules/iam"

create_cluster_manager_task_role = true
create_registry_credentials_lambda_role = true
}

output "upwind_roles" {
value = module.upwind_iam.roles
}

output "upwind_pass_role_policy" {
value = module.upwind_iam.pass_role_policy_json
}

The inputs must match the runtime configuration in step 2. The example above fits a deployment that runs the cluster manager and creates its own registry secret, which is the default. Each input controls one optional role:

InputDefaultSet to true when
create_cluster_manager_task_rolefalseThe module deploys the cluster manager. That is, cluster_manager.create is true and cluster_manager.domain is empty.
create_registry_credentials_lambda_rolefalseThe module creates the registry credentials Lambda function. That is, credentials.registry_auth_enabled is true and credentials.registry_secret_arn is not set.

The IAM module has these other inputs:

InputDefaultDescription
ecs_cluster_namenullUsed only in the role descriptions. Leave it unset when you share the roles across clusters.
registry_auth_enabledtrueWhether the task execution role gets access to the registry credentials secret. Match credentials.registry_auth_enabled.
credentials_secret_arnnullExact ARN of the Upwind credentials secret. When unset, the policies use a name wildcard.
registry_secret_arnnullExact ARN of the registry credentials secret. When unset, the policies use a name wildcard.
regionnullLimits the secret wildcards to one region. See Secret access.
tags{}Tags for the roles.

Step 2: Deploy with the roles​

Give the pipeline the roles output, for example through a terraform_remote_state data source or a variable. Pass the ARNs to iam_roles.

variable "upwind_client_id" {
type = string
}

variable "upwind_client_secret" {
type = string
sensitive = true
}

module "upwind_ecs_runtime" {
source = "https://get.upwind.io/terraform/modules/aws-ecs-runtime/aws-ecs-runtime-1.5.1.tar.gz"

ecs_cluster_name = "my-ecs-cluster"
vpc_id = "vpc-0123456789abcdef0"
subnets = ["subnet-0123456789abcdef0", "subnet-0fedcba9876543210"]

credentials = {
client_id = var.upwind_client_id
client_secret = var.upwind_client_secret
}

# The roles from step 1
iam_roles = {
task_execution_role_arn = "arn:aws:iam::111122223333:role/upwind-task-execution-example"
cluster_manager_task_role_arn = "arn:aws:iam::111122223333:role/upwind-cluster-manager-task-example"
registry_credentials_lambda_role_arn = "arn:aws:iam::111122223333:role/upwind-credentials-loader-example"
}
}

The iam_roles object has three keys:

KeyRequiredUsed by
task_execution_role_arnYesThe sensor, scanner and cluster manager tasks.
cluster_manager_task_role_arnWhen the module deploys the cluster managerThe cluster manager task.
registry_credentials_lambda_role_arnWhen the module creates the registry credentials Lambda functionThe Lambda function that refreshes the registry credentials.

If the configuration needs a role that iam_roles does not provide, terraform plan fails and names the missing key. Nothing is created. Every value must be an IAM role ARN in the form arn:aws:iam::<account-id>:role/<name>.

What the roles allow​

RoleUsed byPermissions
Task executionSensor, scanner and cluster manager tasksAmazonECSTaskExecutionRolePolicy, plus read access to the Upwind credentials secret and the registry secret
Cluster manager taskCluster managerecs:DescribeTasks
Registry credentials LambdaThe Lambda function that refreshes the registry credentialsAWSLambdaBasicExecutionRole, write access to the registry secret, read access to the credentials secret

IAM is global, so you can create the roles from any region.

Secret access​

The secrets do not exist when the security team creates the roles. By default, the policies cover the names the module gives its secrets, in your account:

  • upwind-sensor-* for the credentials secret
  • upwind-registry-* for the registry secret

The wildcards match these secrets in every region of the account. The roles work for clusters in any region and with replicated secrets. To limit the wildcards to one region, set region on the IAM module.

To scope the policies to exact secrets, set credentials_secret_arn and registry_secret_arn. The region input has no effect on exact ARNs.

The pipeline needs iam:PassRole​

When you give an AWS service a role, AWS checks that the caller may pass that role. Registering an ECS task definition, creating the Lambda function and creating the managed daemon stacks all do this. The pipeline therefore needs iam:PassRole on the pre-created roles, even though it cannot create them.

If the pipeline's role does not have this permission yet, the pass_role_policy_json output writes a policy for it. Attach the policy to the pipeline's role. It is a convenience. You can write an equivalent policy yourself.

The policy allows passing only the pre-created roles, each only to its own service:

{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::111122223333:role/upwind-task-execution-example",
"arn:aws:iam::111122223333:role/upwind-cluster-manager-task-example"
],
"Condition": { "StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" } }
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": ["arn:aws:iam::111122223333:role/upwind-credentials-loader-example"],
"Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } }
}

The ECS roles use ecs-tasks.amazonaws.com. This also covers the managed daemons that the module deploys with CloudFormation. The Lambda role uses lambda.amazonaws.com. When you do not create the Lambda role, the policy has no second statement.

Share roles across clusters​

One set of roles can serve many clusters. Leave ecs_cluster_name unset on the IAM module, and pass the same roles output to every cluster. Choose one of two secret patterns.

Per-cluster secrets​

Each cluster keeps its own secrets and registry Lambda function, which is the default. The name wildcards cover every cluster's secrets, so the roles need no secret ARNs. Create all the roles you need, including the Lambda role.

module "upwind_iam" {
source = "https://get.upwind.io/terraform/modules/aws-ecs-runtime/aws-ecs-runtime-1.5.1.tar.gz//modules/iam"

create_cluster_manager_task_role = true
create_registry_credentials_lambda_role = true
}

Shared secrets​

All clusters use one credentials secret and one registry secret. Scope the roles to those exact ARNs.

One deployment owns the registry secret: it leaves credentials.registry_secret_arn unset, so it creates the secret and the Lambda function that refreshes it every hour. See Shared registry secret. The owner still needs the Lambda role, and its registry secret ARN exists only after its first apply. So create_registry_credentials_lambda_role = false below applies only to the roles of the non-owner clusters. For the owner, use the per-cluster roles from the previous section.

module "upwind_iam_shared" {
source = "https://get.upwind.io/terraform/modules/aws-ecs-runtime/aws-ecs-runtime-1.5.1.tar.gz//modules/iam"

create_cluster_manager_task_role = true
create_registry_credentials_lambda_role = false

credentials_secret_arn = "arn:aws:secretsmanager:us-east-1:111122223333:secret:upwind-sensor-credentials-AbCdEf"
registry_secret_arn = "arn:aws:secretsmanager:us-east-1:111122223333:secret:upwind-registry-shared-AbCdEf"
}

Each non-owner cluster then sets the two ARNs in credentials (the registry ARN is the owner's registry_credentials_secret_arn output), and the shared roles in iam_roles:

module "upwind_ecs_runtime" {
source = "https://get.upwind.io/terraform/modules/aws-ecs-runtime/aws-ecs-runtime-1.5.1.tar.gz"

ecs_cluster_name = "cluster-a"
vpc_id = "vpc-0123456789abcdef0"
subnets = ["subnet-0123456789abcdef0", "subnet-0fedcba9876543210"]

credentials = {
secret_arn = "arn:aws:secretsmanager:us-east-1:111122223333:secret:upwind-sensor-credentials-AbCdEf"
registry_secret_arn = "arn:aws:secretsmanager:us-east-1:111122223333:secret:upwind-registry-shared-AbCdEf"
}

iam_roles = {
task_execution_role_arn = "arn:aws:iam::111122223333:role/upwind-task-execution-example"
cluster_manager_task_role_arn = "arn:aws:iam::111122223333:role/upwind-cluster-manager-task-example"
}
}

For more on these two credentials keys, see Credentials and secrets. For running several clusters against one cluster manager, see Multi-cluster mode.

Existing deployments​

An existing deployment owns its roles through the module. Setting iam_roles on it would make Terraform destroy those roles. To switch anyway, first remove the module-created roles from state, so Terraform does not delete them:

terraform state rm 'module.<name>.module.iam[0]'

Replace <name> with the name of your runtime module call. If the module call uses for_each, include the key in the address, for example 'module.upwind_ecs_runtime["a"].module.iam[0]'. If you plan to reuse the roles, note their ARNs first, for example with terraform state show 'module.<name>.module.iam[0].aws_iam_role.cluster_manager_task[0]', because they are no longer in state afterwards. Then set iam_roles to the ARNs of those roles, or to new ones. Review the plan before you apply it.