IAM setup on AWS
Lumaft needs one thing from IAM: an identity that can list and read your state namespace and do nothing else. This page creates it with the AWS CLI, for both EC2 and ECS, and shows how to prove the boundary holds.
Five terms
| Term | What it is |
|---|---|
| Policy | A JSON document listing allowed actions on named resources |
| Role | An identity that a service (EC2, ECS) assumes; policies attach to it |
| Trust policy | The part of a role that says which service may assume it |
| Instance profile | The wrapper that lets an EC2 instance carry a role |
| Task role | The role an ECS task assumes at runtime; the execution role is the one ECS itself uses to start the task |
Lumaft reads credentials through the AWS SDK's default chain, so once the role is attached to the instance or task, no Lumaft configuration is needed.
Step 1: Write the read-only policy
Replace BUCKET_NAME and OPTIONAL_PREFIX/. If the backend has no prefix, remove
OPTIONAL_PREFIX/ entirely — do not leave an empty segment.
cat > lumaft-read-only.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListPulumiState",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::BUCKET_NAME",
"Condition": { "StringLike": { "s3:prefix": ["OPTIONAL_PREFIX/.pulumi/*"] } }
},
{
"Sid": "ReadPulumiState",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/OPTIONAL_PREFIX/.pulumi/*"
}
]
}
JSON
aws iam create-policy \
--policy-name LumaftReadOnlyState \
--policy-document file://lumaft-read-only.json \
--query Policy.Arn --output text
Note the ARN it prints; the steps below call it $POLICY_ARN.
Several backends? Add one ListPulumiState/ReadPulumiState pair per bucket or disjoint
prefix to the same policy. The other-engine variant (which scopes to the prefix itself and can
add version reads) is in
Connect a backend.
KMS-encrypted buckets
If Find your backend details
showed aws:kms, add a third statement:
{
"Sid": "DecryptState",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:123456789012:key/KEY_ID"
}
The key policy must also allow the role to decrypt. If the key was created in the console with default settings, its policy delegates to IAM and the statement above is enough. A key with a restrictive custom policy needs the role's ARN added to it.
Step 2: Create the role
EC2 instance profile
cat > trust-ec2.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }
]
}
JSON
aws iam create-role --role-name LumaftInstance --assume-role-policy-document file://trust-ec2.json
aws iam attach-role-policy --role-name LumaftInstance --policy-arn "$POLICY_ARN"
aws iam attach-role-policy --role-name LumaftInstance \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name LumaftInstance
aws iam add-role-to-instance-profile --instance-profile-name LumaftInstance --role-name LumaftInstance
Launch the instance with --iam-instance-profile Name=LumaftInstance, or attach it to a
running instance:
aws ec2 associate-iam-instance-profile \
--instance-id i-0123456789abcdef0 \
--iam-instance-profile Name=LumaftInstance
AmazonSSMManagedInstanceCore is for Session Manager shell access, not for Lumaft. Omit it if
you manage the instance another way.
ECS task role and execution role
ECS needs two roles with the same trust policy:
cat > trust-ecs.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" }
]
}
JSON
# Task role: what Lumaft can do at runtime
aws iam create-role --role-name LumaftTask --assume-role-policy-document file://trust-ecs.json
aws iam attach-role-policy --role-name LumaftTask --policy-arn "$POLICY_ARN"
# Execution role: what ECS needs to start the task
cat > lumaft-execution.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadLumaftSecrets",
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:lumaft/admin-password-*",
"arn:aws:secretsmanager:us-east-1:123456789012:secret:lumaft/backends-*"
]
},
{
"Sid": "CreateLogGroup",
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/lumaft/*"
}
]
}
JSON
aws iam create-role --role-name LumaftExecution --assume-role-policy-document file://trust-ecs.json
aws iam attach-role-policy --role-name LumaftExecution \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam put-role-policy --role-name LumaftExecution \
--policy-name LumaftExecutionExtras --policy-document file://lumaft-execution.json
Secrets Manager ARNs end in a random suffix, which is why the resource patterns end in -*.
Reference the two roles in the task definition as taskRoleArn (LumaftTask) and
executionRoleArn (LumaftExecution). See
Set up on AWS ECS.
Step 3: Prove the boundary
From the instance, or from a shell with the role assumed, run three commands. The first two must succeed; the third must fail.
aws s3 ls s3://BUCKET_NAME/OPTIONAL_PREFIX/.pulumi/stacks/
aws s3 cp s3://BUCKET_NAME/OPTIONAL_PREFIX/.pulumi/stacks/PROJECT/STACK.json - | head -c 100
echo test | aws s3 cp - s3://BUCKET_NAME/OPTIONAL_PREFIX/.pulumi/lumaft-probe # must fail
An AccessDenied on the third command is the result you want. If it succeeds, the identity has
a write permission from somewhere else — another attached policy, a permissive bucket policy —
and you should remove it before Lumaft runs under this identity. Lumaft never writes, but the
permission should not exist.
To assume the role from your workstation for this test:
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/LumaftTask \
--role-session-name lumaft-probe
Export the three credential values it returns as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
and AWS_SESSION_TOKEN, then run the commands above. Assuming a task role from a workstation
also requires its trust policy to allow your user; add that temporarily or run the test from the
task itself with aws ecs execute-command.
Step 4: Keep it that way
- Never attach
AmazonS3FullAccess,AmazonS3ReadOnlyAccess, or any bucket-wide policy to these roles. Read-only across every bucket is still far more than Lumaft needs. - Do not reuse the deployment pipeline's role. That role writes state; Lumaft's must not.
- Do not create an IAM user with access keys for an EC2 or ECS deployment. The role is the credential. Static keys are for platforms without workload identity, such as Azure or VMware, and even there they are scoped to this same policy.
- Integration tokens for runners are a different credential entirely. They are issued inside Lumaft, not in IAM; see Runner integration.