Cross-Account Roles, STS, and Resource Policies
Key Takeaways
- Cross-account human and workload access is STS AssumeRole (or a resource policy where the service supports one); the role trust policy is a resource-based policy on the role and is one half of the grant.
- A SaaS vendor must issue a unique external ID per customer and send it as sts:ExternalId; the customer puts that value in the trust policy. The role ARN is not a secret, which is why the external ID exists.
- Session policies and session tags can only narrow the role's identity-based policy. Passing tags requires sts:TagSession on the trust policy. Session policies cannot add actions the role itself lacks.
- Cross-account use of Amazon S3, AWS KMS, and AWS Secrets Manager needs identity permission in the caller account plus a bucket policy, key policy, and/or secret resource policy that names the caller. KMS will not honor IAM alone unless the key policy allows that principal or the account's IAM policies.
- Service-linked roles are owned by the AWS service: administrators can view but not edit their permissions, and you cannot attach a permissions boundary. Custom roles are yours to trust, bound, and delete.
Why cross-account access is not "share the access key"
Once IAM Identity Center (or a pipeline role) can sit in a member account, SAP-C02 still asks how that session reaches another account's logs, keys, and secrets without copying long-term credentials. The primary mechanism is AWS Security Token Service (STS) AssumeRole. You create an IAM role in the trusting (resource) account. The role has two documents: a trust policy that names who may assume it, and identity-based permissions that say what the session may do after assume. The caller in the trusted account also needs identity permission to call sts:AssumeRole on that role ARN. Forget either half and the call fails.
A role session returns temporary credentials. DurationSeconds defaults to one hour. You may request from 15 minutes up to the role's maximum session duration, which you can set between one hour and 12 hours. Role chaining — using one role's temporary credentials to assume a second role — caps the second session at one hour, even if both roles allow 12-hour sessions. That cap is a classic Professional trap in multi-account break-glass and automation paths.
The trust policy is a resource-based policy attached to the role. You cannot put a wildcard in the middle of an ARN in the Principal element. Trusting an entire AWS account ("AWS": "arn:aws:iam::111122223333:root") allows any principal in that account that also has sts:AssumeRole permission, which is broader than trusting a single role ARN. Prefer the narrow principal, then add conditions.
External ID and the SaaS confused deputy
The confused deputy problem is when a more-privileged actor is tricked into using its trust on someone else's behalf. AWS documents the third-party version with a vendor ("Example Corp") that monitors many customer accounts. You create NorthwindAuditRole and give the vendor the role ARN. Role ARNs are guessable. Another tenant could tell the vendor "this is my role" while pointing at your ARN. If the vendor's automation blindly assumes whatever ARN a tenant pasted, the vendor becomes a confused deputy and reads your logs.
The control is an external ID. The vendor generates a unique value per customer (often a GUID) and controls it. You place sts:ExternalId in the role trust policy Condition. The vendor always sends your external ID when it assumes on your tenant record, and the other customer's ID when it assumes on theirs. The other customer cannot make the vendor send your ID. AWS is explicit that you get the external ID from the vendor; you do not invent a cute string the vendor does not send.
Northwind hires a SaaS cost and security vendor. The vendor's AWS account is 999988887777. In the Log Archive account, Northwind creates VendorReadOnly with a trust policy that allows that account to call sts:AssumeRole only when sts:ExternalId equals the vendor-issued value for Northwind, and with identity permissions limited to s3:GetObject on the organization CloudTrail bucket prefix and cloudtrail:LookupEvents if needed. The vendor stores Northwind's role ARN and that external ID in Northwind's tenant record. A different tenant who pastes Northwind's role ARN still causes the vendor to send a different external ID, and AssumeRole fails.
Do not "fix" this with an IAM user access key emailed to the vendor. That is a long-term credential in someone else's laptop, with no external ID semantics and a painful rotation story.
Cross-service confused deputy on resource policies
A second form is cross-service confused deputy. You allow cloudtrail.amazonaws.com to write to a central S3 bucket. The bucket policy that trusts the service principal without conditions can accept PutObject from CloudTrail trails you did not intend, including a trail an outsider creates if they know the bucket name. AWS recommends condition keys on resource policies (and on resource control policies in Organizations) whenever you grant an AWS service principal:
| Condition key | Restricts the service principal to acting on behalf of |
|---|---|
aws:SourceArn | A specific resource (a named CloudTrail trail, an AppStream fleet) |
aws:SourceAccount | A specific AWS account |
aws:SourceOrgID | Your organization ID, so new member accounts are covered without rewriting every bucket |
aws:SourceOrgPaths | A specific OU path in the organization |
Prefer the full source ARN when you know it. If the source ARN does not contain an account ID (Amazon S3 bucket ARNs do not), combine aws:SourceArn with aws:SourceAccount. Central org-log buckets in a Security OU are where this shows up on the exam: the bucket policy is not done when it only lists cloudtrail.amazonaws.com.
Session policies, session tags, and source identity
When you call AssumeRole, you may pass session policies: an inline JSON policy and/or managed policy ARNs. The resulting permissions are the intersection of the role's identity-based policy and the session policy (and a permissions boundary, if the role has one). A session policy cannot grant more than the role already allows. If the role cannot s3:DeleteObject, passing a session policy that allows s3:DeleteObject does nothing useful; the delete still fails. Session policies are how a broker, Identity Center-style portal, or build system hands out a narrower slice of a broader role without maintaining dozens of nearly identical roles.
Session tags are key-value pairs attached to the session for ABAC and for CloudTrail. The trust policy must allow the sts:TagSession permissions-only action or AssumeRole fails when tags are passed. You can mark tags transitive so they continue down a role chain. You can also set source identity (sts:SourceIdentity) so the originating human or workload string is sticky across chaining and shows in CloudTrail — a Professional-level audit control when automation assumes a shared role.
Identity policies versus resource policies
Identity-based policies attach to users, groups, and roles and answer "what may this principal do?" Resource-based policies attach to the resource (S3 bucket, KMS key, Secrets Manager secret, SQS queue, SNS topic, role trust policy) and answer "who may use this resource?"
Same-account evaluation is generous: an allow in either the identity policy or the resource policy can permit the request, subject to explicit denies, SCPs, permissions boundaries, and session policies. AWS documents important boundary nuances for resource policies (a same-account resource policy that names an IAM user ARN is not limited by an implicit deny in that user's permissions boundary; a resource policy that names an IAM role ARN is limited by that role's boundary; a resource policy that names the role session ARN is not). Cross-account evaluation is strict: the calling principal needs identity permission and the resource policy in the other account must allow that principal. "We are in the same organization" does not skip that rule.
Amazon S3
A bucket policy is the usual resource policy. For a role in Account A to read a bucket in Log Archive, Account A's role identity policy allows s3:GetObject on that bucket ARN, and the Log Archive bucket policy allows that role ARN (or a path of roles) as Principal. Object ACLs are the wrong lever at this scale. Directory buckets have their own resource policies; Access Analyzer still treats both families as external-access resources.
AWS KMS
A key policy is required on every customer managed key. The default key policy typically includes a statement that gives the account root kms:* on the key, which is what enables IAM policies in that account to grant KMS access. If you delete that enablement statement and do not name other principals, IAM in the account cannot save you. Cross-account decrypt always needs a key-policy statement that allows the external principal (or a role the external principal can assume in the key account) plus an identity policy on the caller. Grants exist for service integrations; they are not a substitute for thinking about the key policy on a Professional item.
AWS Secrets Manager
Secrets support resource-based policies. Cross-account GetSecretValue needs the secret resource policy to allow the caller, the caller's identity policy to allow the action, and the secret's KMS key to allow kms:Decrypt for that caller. Teams that "share the secret ARN" and forget the key policy see AccessDenied on decrypt, not on naming the secret.
AWS Resource Access Manager can share some of these resources into other accounts; that sharing still does not erase key policies and secret policies. Treat RAM as the sharing control plane from the governance chapter, and keep resource policies correct here.
Service-linked roles versus custom roles versus service roles
A service role is an IAM role you create for a service (Elastic Beanstalk, CodePipeline) with a trust policy you own and permissions you own. You can attach a permissions boundary, rotate it, and delete it when the workload is gone.
A service-linked role (SLR) is a special service role owned by the AWS service. It appears in the account (often named AWSServiceRoleFor...). IAM Identity Center's SLR is AWSServiceRoleForSSO. An IAM administrator can view but not edit SLR permissions. You cannot attach a permissions boundary to an SLR. You typically cannot delete the SLR until related resources are gone, which protects you from ripping out a service's ability to function. If a service starts supporting SLRs, it may create one automatically; do not treat that email as a breach.
Custom cross-account roles are what you build for vendors, for CI/CD in a Shared Services account assuming into workload accounts, and for humans who still switch roles without Identity Center. You own trust, permissions, session duration, external ID, and tags. Do not replace a missing external ID with "we will use the GuardDuty SLR." The SLR cannot be retargeted at a SaaS account.
Enterprise scenario: vendor AssumeRole into Log Archive
Northwind's Security OU holds Log Archive. CloudTrail organization trails land in arn:aws:s3:::northwind-org-trail. A SaaS posture vendor needs 12-hour read sessions overnight. The architect creates SaaSPostureRead in Log Archive: maximum session duration 12 hours; trust policy principal = vendor account; condition StringEquals sts:ExternalId = vendor GUID; identity policy = list/get on that bucket prefix only. The bucket policy allows cloudtrail.amazonaws.com PutObject only when aws:SourceOrgID equals Northwind's organization ID, and separately allows the vendor role to GetObject on the prefix. The vendor is never given an IAM user. Northwind's application account encryption uses a KMS key in Security Tooling: the app role in Prod is named in the key policy and in the Secrets Manager resource policy for the database secret. No RAM share is required for that pattern, and no Cognito identity pool is involved.
Exam traps: treating the role ARN as confidential instead of using an external ID; session policies that "add" permissions the role lacks; same-account mental math on a cross-account KMS call; editing an SLR; 12-hour sessions after role chaining; bucket policies that trust a service principal with no source condition; sending IAM user keys to a vendor.
A SaaS cost-optimization vendor needs nightly read-only access to CloudTrail logs in Northwind's Log Archive account. The vendor operates from its own AWS account and already monitors other customers. How should Northwind grant access?
An application role in a Prod member account must call secretsmanager:GetSecretValue on a database secret in the Security Tooling account and kms:Decrypt on that secret's customer managed key, which also lives in Security Tooling. Both accounts are in the same AWS organization. What must be true?
A deployment role in Shared Services can s3:GetObject and s3:PutObject on a release bucket but cannot s3:DeleteObject. A pipeline wants to assume that role for a single job that should only upload a prefix. Which statement is correct?