Trying Out Cross-Account References with CloudFormation's Fn::GetStackOutput
This page has been translated by machine translation. View original
The previous article verified the basic behavior of Fn::GetStackOutput and cross-region references. I had left cross-account references unverified, and this time I'm presenting the results of that verification.
Verification Scenario
For the cross-account reference verification scenario, I chose EventBridge cross-account forwarding.
- Aggregator account (= Producer): The side that has a custom event bus and receives events. The side that provides the Output via
Fn::GetStackOutput - Sender account (= Consumer): The side that references the aggregator account's event bus ARN via
Fn::GetStackOutputand forwards events
Fn::GetStackOutput is used in two places.
eb-role.yaml: Specifies the event bus ARN in the policy resource of the forwarding roleeb-consumer.yaml: Specifies the event bus ARN in the target ARN of the forwarding rule
At deploy time (ARN resolution via Fn::GetStackOutput)
At runtime (EventBridge event forwarding)
CloudFormation Templates
eb-producer.yaml (Aggregator account)
This creates a custom event bus and CloudWatch Logs. EventBusPolicy allows events:PutEvents from the sender account. EventBridge cross-account forwarding requires both the EventBusPolicy on the receiving bus side and the forwarding role on the sender side (described later). I plan to touch on this point in another article as well.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ConsumerAccountId:
Type: String
Resources:
EventBus:
Type: AWS::Events::EventBus
Properties:
Name: getstackoutput-cross-account-bus
EventBusPolicy:
Type: AWS::Events::EventBusPolicy
Properties:
EventBusName: !Ref EventBus
StatementId: AllowCrossAccountPutEvents
Action: events:PutEvents
Principal: !Ref ConsumerAccountId
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/events/getstackoutput-cross-account
RetentionInDays: 7
LogGroupPolicy:
Type: AWS::Logs::ResourcePolicy
Properties:
PolicyName: EventBridgeToCloudWatchLogs
PolicyDocument: !Sub |
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "events.amazonaws.com"},
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/events/getstackoutput-cross-account:*"
}]
}
Rule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref EventBus
EventPattern:
source:
- my.test
Targets:
- Id: CloudWatchLogs
Arn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/events/getstackoutput-cross-account'
Outputs:
EventBusArn:
Value: !GetAtt EventBus.Arn
cfn-role.yaml (Aggregator account)
This is the IAM role used by Fn::GetStackOutput. It trusts roles in the sender account that have the cfn-getstackoutput- prefix via prefix matching, and grants the cloudformation:DescribeStacks permission.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ConsumerAccountId:
Type: String
Resources:
GetStackOutputRole:
Type: AWS::IAM::Role
Properties:
RoleName: cfn-getstackoutput-cross-account-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${ConsumerAccountId}:root'
Action: sts:AssumeRole
Condition:
ArnLike:
aws:PrincipalArn: !Sub 'arn:aws:iam::${ConsumerAccountId}:role/cfn-getstackoutput-*'
GetStackOutputPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: cfn-getstackoutput-describe-stacks-policy
Roles:
- !Ref GetStackOutputRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: cloudformation:DescribeStacks
Resource:
- !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/cfn-getstackoutput-eb-producer/*'
Outputs:
RoleArn:
Value: !GetAtt GetStackOutputRole.Arn
About the trust policy design: root (the entire account) is used as the Principal, but the ArnLike condition restricts it to roles with the cfn-getstackoutput- prefix. Specifying a specific role ARN directly would be stricter, but there is a tradeoff: the aggregator account side would need to be updated every time the deployment method changes.
cfn-exec-role.yaml (Sender account)
This is the CFn execution role used in the sender account. By unifying names with the cfn-getstackoutput- prefix, it matches the prefix-matching trust policy of the aggregator account.
cfn-exec-role.yaml (click to expand)
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
AggregatorAccountId:
Type: String
Resources:
CfnExecRole:
Type: AWS::IAM::Role
Properties:
RoleName: cfn-getstackoutput-cfn-exec-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
CfnExecPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: cfn-getstackoutput-cfn-exec-policy
Roles:
- !Ref CfnExecRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudformation:CreateStack
- cloudformation:UpdateStack
- cloudformation:DeleteStack
- cloudformation:DescribeStacks
- cloudformation:DescribeStackEvents
- cloudformation:CreateChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:GetTemplate
Resource: '*'
- Effect: Allow
Action:
- iam:CreateRole
- iam:DeleteRole
- iam:GetRole
- iam:PutRolePolicy
- iam:DeleteRolePolicy
- iam:GetRolePolicy
- iam:AttachRolePolicy
- iam:DetachRolePolicy
- iam:PassRole
Resource:
- !Sub 'arn:aws:iam::${AWS::AccountId}:role/cfn-getstackoutput-*'
- !Sub 'arn:aws:iam::${AWS::AccountId}:policy/cfn-getstackoutput-*'
- Effect: Allow
Action:
- events:CreateEventBus
- events:DeleteEventBus
- events:DescribeEventBus
- events:PutRule
- events:DeleteRule
- events:DescribeRule
- events:PutTargets
- events:RemoveTargets
- events:ListTargetsByRule
- events:TagResource
- events:UntagResource
Resource: '*'
- Effect: Allow
Action: sts:AssumeRole
Resource: !Sub 'arn:aws:iam::${AggregatorAccountId}:role/cfn-getstackoutput-*'
Outputs:
RoleArn:
Value: !GetAtt CfnExecRole.Arn
eb-role.yaml (Sender account)
This is the first place Fn::GetStackOutput is used. In the policy resource of the EventBridge forwarding role, the aggregator account's event bus ARN is obtained and specified via Fn::GetStackOutput.
The reason for separating eb-role.yaml and eb-consumer.yaml is to show that Fn::GetStackOutput can be used in different places: the IAM policy Resource and the EventBridge rule target ARN. In actual operation, combining them into one stack also works.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
GetStackOutputRoleArn:
Type: String
Resources:
EventBridgeForwardRole:
Type: AWS::IAM::Role
Properties:
RoleName: cfn-getstackoutput-eb-forward-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: events.amazonaws.com
Action: sts:AssumeRole
EventBridgeForwardPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: cfn-getstackoutput-eb-forward-policy
Roles:
- !Ref EventBridgeForwardRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: events:PutEvents
Resource:
Fn::GetStackOutput: # ← Get the aggregator account's EventBusArn here
StackName: cfn-getstackoutput-eb-producer
OutputName: EventBusArn
Region: ap-northeast-1
RoleArn: !Ref GetStackOutputRoleArn
Outputs:
ForwardRoleArn:
Value: !GetAtt EventBridgeForwardRole.Arn
eb-consumer.yaml (Sender account)
This is the second place Fn::GetStackOutput is used. In the target ARN of the EventBridge rule, the aggregator account's event bus ARN is obtained and specified via Fn::GetStackOutput.
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
GetStackOutputRoleArn:
Type: String
ForwardRoleArn:
Type: String
Resources:
EventBus:
Type: AWS::Events::EventBus
Properties:
Name: getstackoutput-cross-account-consumer-bus
ForwardRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref EventBus
EventPattern:
source:
- my.test
Targets:
- Id: CrossAccountBus
Arn:
Fn::GetStackOutput: # ← Get the aggregator account's EventBusArn here
StackName: cfn-getstackoutput-eb-producer
OutputName: EventBusArn
Region: ap-northeast-1
RoleArn: !Ref GetStackOutputRoleArn
RoleArn: !Ref ForwardRoleArn
Outputs:
EventBusName:
Value: !Ref EventBus
Deployment Steps
Set the following shell variables before running the commands.
AGGREGATOR_ACCOUNT_ID=111111111111 # Aggregator account ID
CONSUMER_ACCOUNT_ID=222222222222 # Sender account ID
# Verify variables
echo "Aggregator: ${AGGREGATOR_ACCOUNT_ID:?未設定}"
echo "Consumer: ${CONSUMER_ACCOUNT_ID:?未設定}"
Steps 1 and 2: Deploy to the aggregator account
# After switching to the aggregator account
aws cloudformation deploy \
--stack-name cfn-getstackoutput-eb-producer \
--template-file eb-producer.yaml \
--parameter-overrides ConsumerAccountId=${CONSUMER_ACCOUNT_ID} \
--region ap-northeast-1
aws cloudformation deploy \
--stack-name cfn-getstackoutput-cfn-role \
--template-file cfn-role.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides ConsumerAccountId=${CONSUMER_ACCOUNT_ID} \
--region ap-northeast-1
Steps 3, 4, and 5: Deploy to the sender account
# After switching to the sender account
aws cloudformation deploy \
--stack-name cfn-getstackoutput-exec-role \
--template-file cfn-exec-role.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides AggregatorAccountId=${AGGREGATOR_ACCOUNT_ID} \
--region ap-northeast-1
aws cloudformation deploy \
--stack-name cfn-getstackoutput-eb-role \
--template-file eb-role.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
GetStackOutputRoleArn=arn:aws:iam::${AGGREGATOR_ACCOUNT_ID}:role/cfn-getstackoutput-cross-account-role \
--role-arn arn:aws:iam::${CONSUMER_ACCOUNT_ID}:role/cfn-getstackoutput-cfn-exec-role \
--region ap-northeast-1
aws cloudformation deploy \
--stack-name cfn-getstackoutput-eb-consumer \
--template-file eb-consumer.yaml \
--parameter-overrides \
GetStackOutputRoleArn=arn:aws:iam::${AGGREGATOR_ACCOUNT_ID}:role/cfn-getstackoutput-cross-account-role \
ForwardRoleArn=arn:aws:iam::${CONSUMER_ACCOUNT_ID}:role/cfn-getstackoutput-eb-forward-role \
--role-arn arn:aws:iam::${CONSUMER_ACCOUNT_ID}:role/cfn-getstackoutput-cfn-exec-role \
--region ap-northeast-1
Operation Check
Sending an event (Sender account)
aws events put-events \
--entries '[{
"Source": "my.test",
"DetailType": "TestEvent",
"Detail": "{\"message\": \"hello from cross-account\"}",
"EventBusName": "getstackoutput-cross-account-consumer-bus"
}]' \
--region ap-northeast-1
Checking receipt in CloudWatch Logs (Aggregator account)
aws logs filter-log-events \
--log-group-name /aws/events/getstackoutput-cross-account \
--region ap-northeast-1
Result
{
"version": "0",
"id": "9409e3bd-f762-32c4-e773-aa9c78451b39",
"detail-type": "TestEvent",
"source": "my.test",
"account": "222222222222",
"time": "2026-05-04T10:14:29Z",
"region": "ap-northeast-1",
"detail": {"message": "hello from cross-account"}
}
The event sent from the sender account (222222222222) arrived in the CloudWatch Logs of the aggregator account (111111111111). We confirmed that the cross-account EventBusArn was obtained via Fn::GetStackOutput and that EventBridge forwarding works correctly.
Summary
We confirmed that cross-account references with Fn::GetStackOutput work.
There are two key points when using it cross-account.
Aggregator account side (Producer):
- Create an IAM role for
Fn::GetStackOutputand trust the sender account's CFn execution role - The only permission is
cloudformation:DescribeStacks(limited to the target stack)
Sender account side (Consumer):
- Explicitly specify the CFn execution role with
--role-arnwhen deploying - Match the CFn execution role's name to the condition (prefix match) in the aggregator account's trust policy
Through this verification, we found that the cross-account references left unverified in the previous article can be used. While waiting for the official release of Fn::GetStackOutput, I'd like to consider using it in multi-account setups.
Notes and Limitations
This article covers notes specific to cross-account use. For the behavior of stack deletion (such as what happens when you update the Consumer after deleting the Producer), please refer to the previous article.
- Specifying the CFn execution role: If you omit
--role-arn, the role of the user running the deployment callssts:AssumeRole, which may not match the trust policy on the aggregator account side. We recommend explicitly specifying a dedicated CFn execution role with--role-arn - Narrowing down the allowed targets: If an Output may contain sensitive information, we recommend limiting the
Resourceincfn-role.yamlto the target stack (storing sensitive information in Parameter Store or Secrets Manager is the CloudFormation best practice) - Visualizing dependencies: When going cross-account, the aggregator account side cannot see "which account's Consumer is referencing my Output." Since this feature does not have the deletion guard that
Fn::ImportValuehas, be mindful of dependency management in long-term operation
Confused Deputy Protection
EventBridgeForwardRole is a service role that trusts events.amazonaws.com. If configured without conditions, it may be assumed by other event buses or rules within the same AWS account (the Confused Deputy problem). By adding the aws:SourceAccount and aws:SourceArn conditions, you can restrict invocations to only the intended resources.
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
ArnLike:
aws:SourceArn: !Sub 'arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/*'
