Fn::GetStackOutput β Referencing CloudFormation Outputs Across AWS Accounts
- Published on
- Authors

- Name
- Sylvain BRUAS
- @sylvain_bruas
In a previous article on shared VPC, we set up an architecture where the network account owns and manages the Amazon Virtual Private Cloud (VPC) β subnets, route tables, NAT Gateways, AWS Transit Gateway β and the other AWS accounts (per team or per project) come to deploy their workloads there using the shared subnets via AWS Resource Access Manager (RAM).
This architecture is elegant from a governance standpoint, but it immediately raises a practical question: how do the teams' AWS CloudFormation templates reference the network account's resources? The VpcId, the SubnetIds, the base security groups β all of this lives in the network account, in a stack that the network team maintains.
Until very recently, the available options were all imperfect: hard-coding the IDs (fragile), going through AWS Systems Manager (SSM) Parameter Store (operational overhead), or using Fn::ImportValue (limited to the same account and same region). AWS has just changed the game with Fn::GetStackOutput, a new intrinsic function that solves exactly this problem.
The context: shared VPC in multi-account
Let's quickly recall the target architecture described in the shared VPC article. The AWS organization is split into several accounts:
- Network Account: owner of the VPC, subnets, Transit Gateway and the entire network layer
- Team Accounts: each team (or project) has its own AWS account; their resources (Amazon Elastic Compute Cloud (EC2), AWS Lambda, Amazon Relational Database Service (RDS), Amazon Elastic Container Service (ECS)β¦) are deployed in the subnets shared from the network account
The network account deploys a NetworkStack that contains all the network resources and exposes Outputs (VpcId, SubnetIds, NatGatewayIdβ¦). The team accounts want to use these values in their own CloudFormation stacks.
The problem: referencing cross-account resources before Fn::GetStackOutput
Before the introduction of Fn::GetStackOutput, three approaches coexisted, each with its limitations:
1. Hard-coding the IDs
The simplest and most dangerous solution. You retrieve the IDs manually and copy them into the templates or into parameter files.
# Before β fragile and not reproducible
Parameters:
VpcId:
Type: String
Default: "vpc-0abc123def456789" # β hard-coded IDs
SubnetPrivateA:
Type: String
Default: "subnet-0123456789abcdef0"
The problem: if the network account recreates the VPC (migration, incident, refactoring), all the teams' templates must be updated manually. It is a time bomb.
2. Cross-account SSM Parameter Store
The network team stores the values in SSM, and the teams consume them via SSM dynamic references.
# Network stack β writing to SSM
VpcIdParam:
Type: AWS::SSM::Parameter
Properties:
Name: /network/vpc-id
Value: !Ref MyVpc
# Team stack β reading from cross-account SSM (not natively possible)
VpcId: "{{resolve:ssm:/network/vpc-id}}" # β does not work cross-account
SSM dynamic references do not work cross-account. The network team would have to publish to SSM in each team account, which generates considerable operational overhead.
3. Fn::ImportValue β limited to the same account
Fn::ImportValue is the native CloudFormation function for sharing outputs between stacks. It creates a strong reference (the producer stack cannot be deleted as long as a consumer stack exists), but it is strictly limited to the same account and same region.
# β Does not work cross-account
VpcId: !ImportValue "NetworkStack-VpcId"
None of these approaches cleanly solves the shared VPC case in multi-account. This is why Fn::GetStackOutput is a significant advance.
Fn::GetStackOutput: the native solution
Fn::GetStackOutput is a CloudFormation intrinsic function that lets you reference the outputs of another stack, potentially in another AWS account or another region. It resolves the value at deployment time (create or update), by calling the CloudFormation DescribeStacks API.
Syntax
The function accepts four parameters:
| Parameter | Required | Description |
|---|---|---|
StackName | Yes | Name of the producer stack |
OutputName | Yes | Logical identifier of the output (not the export name) |
Region | No | AWS region of the producer stack |
RoleArn | No | ARN of the IAM role to assume for cross-account access |
How CloudFormation resolves the value
The mechanism is simple: at deployment time, CloudFormation assumes the AWS Identity and Access Management (IAM) role specified in the network account, calls cloudformation:DescribeStacks on the NetworkStack, retrieves the value of the requested output, and injects it into the template being deployed.
Putting it into practice: CloudFormation templates
Producer stack β network account
The network team declares its outputs normally. No need for Export β this is an important difference from Fn::ImportValue, which requires an export name.
# NetworkStack β network account (111111111111)
AWSTemplateFormatVersion: '2010-09-09'
Description: Shared network infrastructure
Resources:
SharedVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: shared-vpc-production
SubnetPrivateA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SharedVpc
CidrBlock: "10.0.1.0/24"
AvailabilityZone: !Select [0, !GetAZs ""]
SubnetPrivateB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SharedVpc
CidrBlock: "10.0.2.0/24"
AvailabilityZone: !Select [1, !GetAZs ""]
SubnetPrivateC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SharedVpc
CidrBlock: "10.0.3.0/24"
AvailabilityZone: !Select [2, !GetAZs ""]
Outputs:
VpcId:
Description: "ID of the shared VPC"
Value: !Ref SharedVpc
SubnetPrivateA:
Description: "Private subnet β AZ A"
Value: !Ref SubnetPrivateA
SubnetPrivateB:
Description: "Private subnet β AZ B"
Value: !Ref SubnetPrivateB
SubnetPrivateC:
Description: "Private subnet β AZ C"
Value: !Ref SubnetPrivateC
Note the absence of an Export block in the outputs β Fn::GetStackOutput directly references the logical name of the output (VpcId, SubnetPrivateAβ¦), not an export name.
Consumer stack β team account
# AppStack β team account A (222222222222)
AWSTemplateFormatVersion: '2010-09-09'
Description: Application deployed in the shared VPC
Parameters:
NetworkAccountId:
Type: String
Default: "111111111111"
Resources:
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "SG for the application"
VpcId:
Fn::GetStackOutput:
StackName: NetworkStack
OutputName: VpcId
RoleArn: !Sub "arn:aws:iam::${NetworkAccountId}:role/GetStackOutputRole"
AppLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: application
Subnets:
- Fn::GetStackOutput:
StackName: NetworkStack
OutputName: SubnetPrivateA
RoleArn: !Sub "arn:aws:iam::${NetworkAccountId}:role/GetStackOutputRole"
- Fn::GetStackOutput:
StackName: NetworkStack
OutputName: SubnetPrivateB
RoleArn: !Sub "arn:aws:iam::${NetworkAccountId}:role/GetStackOutputRole"
- Fn::GetStackOutput:
StackName: NetworkStack
OutputName: SubnetPrivateC
RoleArn: !Sub "arn:aws:iam::${NetworkAccountId}:role/GetStackOutputRole"
SecurityGroups:
- !Ref AppSecurityGroup
The syntax is identical for each cross-account reference. The OutputName is exactly the name declared in the Outputs block of the network stack.
IAM configuration: the GetStackOutputRole role
This is the central piece of the setup's security. The network team creates an IAM role in its account that CloudFormation (from the team accounts) can assume to read the outputs.
Here is the CloudFormation template to create the IAM role in the network account:
# To deploy in the network account β can be part of NetworkStack
GetStackOutputRole:
Type: AWS::IAM::Role
Properties:
RoleName: GetStackOutputRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS:
- "arn:aws:iam::222222222222:root" # Team Account A
- "arn:aws:iam::333333333333:root" # Team Account B
Action: sts:AssumeRole
Policies:
- PolicyName: DescribeNetworkStack
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- cloudformation:DescribeStacks
Resource:
- !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/NetworkStack/*"
Important points about this role:
- The
cloudformation:DescribeStackspermission is sufficient β no need for access to the network resources themselves - The
Resourceis restricted toNetworkStackonly, not to all the stacks in the network account - The trust policy explicitly lists the authorized accounts β you can refine it with a
Principalper CloudFormation execution role rather than per account (:root)
Strong reference vs weak reference
This is the fundamental difference between Fn::ImportValue and Fn::GetStackOutput:
| Characteristic | Fn::ImportValue | Fn::GetStackOutput |
|---|---|---|
| Scope | Same account + same region | Cross-account, cross-region |
| Requires an Export | Yes | No |
| Reference type | Strong | Weak |
| Deletion of the producer stack | Blocked | Allowed |
| Automatic propagation of changes | No | No |
A weak reference means that if the network team deletes or recreates the NetworkStack, CloudFormation will not prevent it. The consumer stacks will not know immediately β they will only fail on their next deployment.
To compensate, two measures are recommended:
- Enable deletion protection on
NetworkStack:
aws cloudformation update-termination-protection \
--enable-termination-protection \
--stack-name NetworkStack \
--region eu-west-1
- Document the dependencies in the READMEs and runbooks β
Fn::GetStackOutputreferences do not appear in the network account's CloudFormation console.
Integration with AWS CDK
AWS Cloud Development Kit (CDK) natively supports Fn::GetStackOutput for cross-account and cross-region references. With the @aws-cdk/core:defaultCrossStackReferences context key:
strong(default): usesFn::ImportValuefor references within the same account/regionweak: always usesFn::GetStackOutputboth: generates both types simultaneously (progressive migration)
For our SharedVPC architecture, the weak behavior is the right choice: we are necessarily cross-account.
// cdk.json
{
"context": {
"@aws-cdk/core:defaultCrossStackReferences": "weak"
}
}
Key takeaways
Fn::GetStackOutputlets you reference the outputs of a CloudFormation stack in any AWS account and any region, without needing an export name- For the shared VPC pattern, it is the ideal solution: the network team exposes its outputs (VpcId, SubnetIdsβ¦), and the application teams consume them directly from their stacks
- Resolution happens at deployment time via
cloudformation:DescribeStacksβ an IAM role in the network account is required for cross-account access - The reference is weak (no deletion blocking) β compensate with deletion protection on the
NetworkStackand explicit documentation of the dependencies - The value is frozen at deployment time. If the network team modifies an output, the consumer stacks must be explicitly updated to retrieve the new value
Fn::GetStackOutput does not replace Fn::ImportValue for same-account cases β the strong reference retains all its value when you want a guarantee of referential integrity. But for cross-account, it is now the native and cleanest option.



