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.
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.
- Security team, with IAM rights. Apply the IAM module. It creates the roles and outputs their ARNs and an
iam:PassRolepolicy. - Deployment pipeline, without IAM rights. Deploy the runtime module with
iam_rolesset 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:
| Input | Default | Set to true when |
|---|---|---|
create_cluster_manager_task_role | false | The module deploys the cluster manager. That is, cluster_manager.create is true and cluster_manager.domain is empty. |
create_registry_credentials_lambda_role | false | The 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:
| Input | Default | Description |
|---|---|---|
ecs_cluster_name | null | Used only in the role descriptions. Leave it unset when you share the roles across clusters. |
registry_auth_enabled | true | Whether the task execution role gets access to the registry credentials secret. Match credentials.registry_auth_enabled. |
credentials_secret_arn | null | Exact ARN of the Upwind credentials secret. When unset, the policies use a name wildcard. |
registry_secret_arn | null | Exact ARN of the registry credentials secret. When unset, the policies use a name wildcard. |
region | null | Limits 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:
| Key | Required | Used by |
|---|---|---|
task_execution_role_arn | Yes | The sensor, scanner and cluster manager tasks. |
cluster_manager_task_role_arn | When the module deploys the cluster manager | The cluster manager task. |
registry_credentials_lambda_role_arn | When the module creates the registry credentials Lambda function | The 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
| Role | Used by | Permissions |
|---|---|---|
| Task execution | Sensor, scanner and cluster manager tasks | AmazonECSTaskExecutionRolePolicy, plus read access to the Upwind credentials secret and the registry secret |
| Cluster manager task | Cluster manager | ecs:DescribeTasks |
| Registry credentials Lambda | The Lambda function that refreshes the registry credentials | AWSLambdaBasicExecutionRole, 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 secretupwind-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.