Logo Teamwork

Fn::GetStackOutput β€” Referencing CloudFormation Outputs Across AWS Accounts

Published on
Authors
Version audio
Chargement...
Fn::GetStackOutput β€” Referencing CloudFormation outputs across AWS accounts - Guide 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:

Multi-account SharedVPC architecture with Fn::GetStackOutput

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:

ParameterRequiredDescription
StackNameYesName of the producer stack
OutputNameYesLogical identifier of the output (not the export name)
RegionNoAWS region of the producer stack
RoleArnNoARN of the IAM role to assume for cross-account access

How CloudFormation resolves the value

Fn::GetStackOutput 5-step resolution flow

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.

IAM configuration β€” GetStackOutputRole and cross-account trust policy

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:DescribeStacks permission is sufficient β€” no need for access to the network resources themselves
  • The Resource is restricted to NetworkStack only, not to all the stacks in the network account
  • The trust policy explicitly lists the authorized accounts β€” you can refine it with a Principal per CloudFormation execution role rather than per account (:root)

Strong reference vs weak reference

This is the fundamental difference between Fn::ImportValue and Fn::GetStackOutput:

CharacteristicFn::ImportValueFn::GetStackOutput
ScopeSame account + same regionCross-account, cross-region
Requires an ExportYesNo
Reference typeStrongWeak
Deletion of the producer stackBlockedAllowed
Automatic propagation of changesNoNo

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:

  1. Enable deletion protection on NetworkStack:
aws cloudformation update-termination-protection \
  --enable-termination-protection \
  --stack-name NetworkStack \
  --region eu-west-1
  1. Document the dependencies in the READMEs and runbooks β€” Fn::GetStackOutput references 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): uses Fn::ImportValue for references within the same account/region
  • weak: always uses Fn::GetStackOutput
  • both: 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::GetStackOutput lets 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 NetworkStack and 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.

Share this article

XBlueskyReddit