docs/en/enterprise/features/secrets-manager/aws-workload-identity.mdx
This guide configures AWS Secrets Manager as a secret provider using Workload Identity Federation: CrewAI Platform mints short-lived OIDC tokens, exchanges them for AWS credentials via STS, and reads your secrets — without a long-lived AWS access key being stored anywhere.
<Note> **Why this path:** secrets are resolved at automation execution time, so **rotated values propagate to the next kickoff with no re-deploy**. If you only need static credentials and don't care about rotation propagation, see the simpler [AWS — static keys / AssumeRole](/en/enterprise/features/secrets-manager/aws) guide. </Note>sts:AssumeRoleWithWebIdentity on the IAM role you set up below, presenting the JWT.secretsmanager:GetSecretValue.OIDC subject tokens are cached for ~1 hour to avoid re-issuing on every kickoff. Secret values are fetched fresh on every kickoff regardless of OIDC cache state, which is what makes this path rotation-aware.
1.14.5 or later.us-east-1.workload_identity_configs: manage and secret_providers: manage permissions. See Permissions (RBAC).Your CrewAI Platform installation publishes an OpenID Connect discovery document at https://<your-platform-host>/.well-known/openid-configuration. The issuer field in that document is the URL AWS will register as a trusted OIDC provider.
Open the URL in a browser (replacing <your-platform-host> with your actual hostname, e.g. app.crewai.com):
https://<your-platform-host>/.well-known/openid-configuration
You should see JSON containing:
{
"issuer": "https://<your-platform-host>",
"jwks_uri": "https://<your-platform-host>/oauth2/jwks",
...
}
Note the exact value of issuer — you'll use it in Step 3.
Open the IAM → Identity providers console and click Add provider.
issuer value from Step 1 (e.g. https://app.crewai.com).sts.amazonaws.comClick Add provider.
Or via CLI:
aws iam create-open-id-connect-provider \
--url "https://<your-platform-host>" \
--client-id-list "sts.amazonaws.com" \
--thumbprint-list "$(echo | openssl s_client -servername <your-platform-host> -connect <your-platform-host>:443 2>/dev/null | openssl x509 -fingerprint -noout -sha1 | cut -d= -f2 | tr -d ':')"
Copy the OpenIDConnectProviderArn from the output (or the provider's ARN from the console). You'll use it in Step 3.
<Note> AWS does not actually validate the thumbprint for STS WebIdentity calls — it always re-fetches the JWKS at validation time — but the API requires the field to be present. </Note>Save as trust-policy.json, replacing <YOUR_ACCOUNT_ID>, <your-platform-host> (the issuer host without https:// or http://, e.g. app.crewai.com), and <YOUR_CREWAI_ORG_UUID> (from the Prerequisites):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<YOUR_ACCOUNT_ID>:oidc-provider/<your-platform-host>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"<your-platform-host>:aud": "sts.amazonaws.com",
"<your-platform-host>:sub": "organization:<YOUR_CREWAI_ORG_UUID>"
}
}
}
]
}
Create the role:
aws iam create-role \
--role-name crewai-secrets-reader \
--assume-role-policy-document file://trust-policy.json
Copy the Role Arn from the output — that's your aws_role_arn. You'll paste it into CrewAI Platform in Step 6.
Save as secrets-policy.json, replacing the placeholders with your account ID, region, secret-name prefix, and the KMS key ARN(s) that encrypt those secrets:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SecretsManagerListForUI",
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
},
{
"Sid": "SecretsManagerRead",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:<REGION>:<YOUR_ACCOUNT_ID>:secret:<SECRET_NAME_PREFIX>-*"
},
{
"Sid": "KMSDecrypt",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": "arn:aws:kms:<REGION>:<YOUR_ACCOUNT_ID>:key/<KMS_KEY_ID>"
}
]
}
SecretsManagerListForUI powers the Secret Name autocomplete in the Environment Variables form and the Test Connection button on the credential. secretsmanager:ListSecrets only accepts Resource: "*" — it is account-scoped at the IAM layer.
Attach the policy to the role using either the CLI (inline policy, simplest) or the console UI; for environments that reuse the same permissions across many roles, use the Managed policy tab for a reusable, named policy.
<Tabs> <Tab title="Inline policy (CLI)"> ```bash aws iam put-role-policy \ --role-name crewai-secrets-reader \ --policy-name SecretsManagerRead \ --policy-document file://secrets-policy.json ```This attaches the policy **inline** to the role. Inline policies are tied to the role and cannot be reused on other roles.
aws iam attach-role-policy \
--role-name crewai-secrets-reader \
--policy-arn "$POLICY_ARN"
```
A managed policy is a standalone IAM resource you can attach to multiple roles.
To create a reusable managed policy instead, use **IAM → Policies → Create policy** and then attach it to the role from the role's **Permissions** tab.
If you don't already have a secret to test against, create one now:
aws secretsmanager create-secret \
--region <REGION> \
--name crewai-test-keyword \
--secret-string "hello from aws"
Or via the AWS Secrets Manager console → Store a new secret.
In CrewAI Platform, navigate to Settings → Workload Identity and click Add Workload Identity Config.
Fill the form:
aws-prod.AWS.us-east-1.Click Create.
Navigate to Settings → Secret Provider Credentials and click Add Credential.
Fill the form:
aws-prod-wi.AWS Secrets Manager.Workload Identity (instead of static keys / AssumeRole).aws-prod).The form will only ask for AWS Region under Workload Identity — the static-credential fields (Access Key ID, Secret Access Key, Role ARN, External ID) are intentionally hidden because they don't apply to this path; the role ARN comes from the linked WI config.
Click Create.
After saving the credential, click Test Connection. For workload-identity credentials this verifies the OIDC handshake: CrewAI Platform mints a JWT, exchanges it with AWS STS via sts:AssumeRoleWithWebIdentity, and confirms the resulting credentials can call sts:GetCallerIdentity against the assumed role. A green result means the federation binding is healthy.
A successful Test Connection proves the trust policy, OIDC provider registration, and audience condition are all wired correctly. It does not prove per-secret IAM is correct — secretsmanager:GetSecretValue on a specific secret ARN is exercised separately when an environment variable resolves at kickoff. See Troubleshooting for handshake failure modes.
Now reference the secret on an automation, exactly as you would for any other Secrets Manager-backed env var. See Using the Secrets Manager for the form fields and behavior.
The only difference between WI-backed and static-keys-backed env vars is when the secret is read:
After the deployment is running, rotate the secret in AWS:
aws secretsmanager update-secret \
--region <REGION> \
--secret-id crewai-test-keyword \
--secret-string "rotated value"
Trigger a new automation kickoff. The kickoff's environment will see "rotated value" — no re-deploy, no worker restart, no waiting on a TTL.
To confirm in logs (if you have access to the worker), look for:
Workload identity config '<id>' (aws): N secret(s) resolved
This line appears for every kickoff and indicates a fresh GetSecretValue call against AWS.
| Symptom | Likely cause |
|---|---|
| Test Connection fails with a handshake error | The sts:AssumeRoleWithWebIdentity call was rejected. Verify the trust policy's federated principal ARN references oidc-provider/<your-platform-host> (host without https:// or http://, no trailing slash), the audience condition is exactly sts.amazonaws.com, the sub condition matches your CrewAI organization UUID, and the platform's OIDC discovery URL is reachable from AWS over the public internet. |
InvalidIdentityToken: Couldn't retrieve verification key from your identity provider | AWS STS can't reach your CrewAI Platform host to fetch JWKS. Confirm the host is internet-accessible from AWS, the OIDC discovery URL returns 200, and the JWKS endpoint is reachable. |
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity | Trust policy mismatch. Re-check Step 3: the federated principal ARN must include oidc-provider/<your-platform-host> (host without https:// or http://, no trailing slash), the audience condition must be exactly sts.amazonaws.com, and the sub condition must equal organization:<YOUR_CREWAI_ORG_UUID>. |
Secret Name autocomplete shows AccessDenied: secretsmanager:ListSecrets | The role is missing secretsmanager:ListSecrets with Resource: "*". Add the SecretsManagerListForUI statement from Step 4. |
| Kickoff fails to resolve a secret even though Test Connection passes | The WI binding is healthy, but resource-scoped IAM is missing on the failing secret. Audit the role's secretsmanager:GetSecretValue and kms:Decrypt permissions for that specific secret's ARN and KMS key. |
RegionDisabledException / no secrets found | The region in the Workload Identity Config doesn't match where the secret lives. Re-check Step 6. |
| Rotated value isn't picked up on the next kickoff | Confirm the env var on the automation is referencing a Workload Identity-backed credential (not a static-keys credential). The static path bakes values into the deploy image. |