In AWS, tags (like Name etc.) are added directly to the Transit Gateway attachment resource. Even if the transit gateway itself is shared across accounts (via AWS Resource Access Manager – RAM), each participating account owns and manages its own attachment (VPC, VPN, Direct Connect, or Peering attachment etc.). Since we have moved to the multi-account Organization, over the time we collected many VPCs from multiple accounts and as they started connecting through a shared Transit Gateway, problem started identifying which attachemnt from which VPC, when looking in the TGW attachments console.
Here’s how we add a Name tag to a shared transit gateway attachment to solve that issue, at zenler.com.
🧩 Problem Have (had)
In our multi-account AWS environment, we rely on Transit Gateway (TGW) to connect shared networking components with application VPCs. However, when a TGW attachment is created across accounts (e.g., by Terraform in a requester account), the attachment in the TGW owner account is created without tags.
This caused several issues:
- Attachments appeared as untagged resources in the TGW owner account.
- Reporting and cost-allocation automation relying on tags failed.
- Manual tagging was error-prone and inconsistent.
We needed an automated, cross-account, event-driven mechanism to apply consistent tags to new TGW attachments as soon as they are created or accepted.
🕵️♀️ Underlying issues
By default:
- The requester’s VPC tags do not propagate to the TGW attachment.
- TGW create/accept API calls are management events, recorded only in CloudTrail, not directly visible to EventBridge unless CloudTrail is integrated.
- The TGW attachment resource resides in the TGW owner account, so tagging must happen there, not in the requester’s account.
⚙️ Solution Overview
We implemented an EventBridge + Lambda automation that listens for CloudTrail management events related to TGW attachments and automatically applies tags from the requester’s VPC.

Event Flow Diagram:
CloudTrail → EventBridge → Lambda → AssumeRole → Tag TGW attachment

Workflow Summary:
- CloudTrail logs CreateTransitGatewayVpcAttachment and AcceptTransitGatewayVpcAttachment events.
- CloudTrail is configured to send events to EventBridge.
- An EventBridge rule filters TGW attachment events.
- The rule triggers a Lambda function in the TGW owner account.
- The Lambda:
- Parses the CloudTrail event (supports both new and old formats).
- Identifies the requester account and VPC ID.
- Assumes a cross-account role into the requester account.
- Retrieves VPC tags and applies them to the TGW attachment, adding a suffix to the Name tag.
🗺️ Architecture Diagram
🧰 Implementation Details
When a requester account creates a TGW VPC attachment, the TGW owner account receives the attachment resource without any inherited tags. This function then listens to CloudTrail → EventBridge events, to identify the new TGW attachment(s), and automatically applies the VPC tags from the requester’s account to the corresponding TGW attachment in the owner’s account.
1️⃣ CloudTrail Configuration:
- Multi-region trail enabled (optional, if needed).
- Management events (
IncludeManagementEvents = true). - Integrated with EventBridge (
event_bus_name = "default").
2️⃣ EventBridge Rule Filter
The EventBridge rule filter defines which AWS CloudTrail events should trigger the Lambda function, making sure ensures it's only invoked when Transit Gateway (TGW) attachments are created or accepted — not for every EC2 API call.
{
"source": ["aws.ec2"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["ec2.amazonaws.com"],
"eventName": [
"CreateTransitGatewayVpcAttachment",
"AcceptTransitGatewayVpcAttachment"
]
}
}
3️⃣ Event Sample
The filter is evaluated each time CloudTrail logs a new management event and inspects each incoming event, like the one below:
{
"version": "0",
"id": "test-id",
"detail-type": "AWS API Call via CloudTrail",
"source": "aws.ec2",
"account": "123456789012",
"time": "2025-09-29T10:05:01Z",
"region": "eu-west-2",
"resources": [],
"detail": {
"eventVersion": "1.10",
"userIdentity": {
"type": "AWSAccount",
"principalId": "AROA6Q5A2P2LQJEJCKFPD:aws-go-sdk-1759140300350852736",
"accountId": "123456789012"
},
"eventTime": "2025-09-29T10:05:01Z",
"eventSource": "ec2.amazonaws.com",
"eventName": "CreateTransitGatewayVpcAttachment",
"awsRegion": "eu-west-2",
"sourceIPAddress": "25.xxx.xxx.xxx",
"userAgent": "APN/1.0 HashiCorp/1.0 Terraform/1.11.0 (+https://www.terraform.io) terraform-provider-aws/6.2.0 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go-v2/1.36.5 ua/2.1 os/linux lang/go#1.24.4 md/GOOS#linux md/GOARCH#arm64 api/ec2#1.229.0 m/i",
"requestParameters": {
"CreateTransitGatewayVpcAttachmentRequest": {
"Options": {
"Ipv6Support": "disable",
"ApplianceModeSupport": "enable",
"DnsSupport": "enable"
},
"TransitGatewayId": "tgw-87654321b6418c2bc",
"VpcId": "vpc-34239b0b1106a01f0",
"TagSpecifications": {
"ResourceType": "transit-gateway-attachment",
"tag": 1,
"Tag": [
{
"Value": "Santanu Das",
"tag": 1,
"Key": "Author"
},
{
"Value": "zenler",
"tag": 2,
"Key": "CostCentre"
},
{ .... }
]
},
"SubnetIds": [
{
"tag": 1,
"content": "subnet-04b7a3c2512345678"
},
{
"tag": 2,
"content": "subnet-12345678017c583d0"
}
]
}
},
"responseElements": {
"CreateTransitGatewayVpcAttachmentResponse": {
"xmlns": "http://ec2.amazonaws.com/doc/2016-11-15/",
"transitGatewayVpcAttachment": {
"tagSet": "HIDDEN_DUE_TO_SECURITY_REASONS",
"creationTime": "2025-09-29T10:05:01.000Z",
"transitGatewayAttachmentId": "tgw-attach-0b575dfd1fcbdbf6b",
"transitGatewayId": "tgw-87654321b6418c2bc",
"vpcId": "vpc-34239b0b1106a01f0",
"options": {
"applianceModeSupport": "enable",
"securityGroupReferencingSupport": "enable",
"dnsSupport": "enable",
"ipv6Support": "disable"
},
"state": "pending",
"vpcOwnerId": "123456789012",
"subnetIds": {
"item": [
"subnet-04b7a3c2512345678",
"subnet-12345678017c583d0"
]
}
},
"requestId": "11a9ce7e-1f5d-4c69-86cf-d4043309f8ad"
}
},
"requestID": "11a9ce7e-1f5d-4c69-86cf-d4043309f8ad",
"eventID": "498e93ad-edc8-415d-be8e-7420d71c4b10",
"readOnly": false,
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "123456789013",
"sharedEventID": "facd716b-20c2-4496-9109-d10c4965bbf3",
"eventCategory": "Management",
"tlsDetails": {
"tlsVersion": "TLSv1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "ec2.eu-west-2.amazonaws.com"
}
}
}
4️⃣ Lambda Function
- Receives only those events related to TGW attachment operations.
- Handles both CreateTransitGatewayVpcAttachmentResponse and AcceptTransitGatewayVpcAttachmentResponse.
- Fallback for legacy format transitGatewayAttachment.
- Detects requester account dynamically via userIdentity.accountId.
- Cross-account role assumption for tag retrieval.
- Appends suffix to Name tag and applies in TGW owner account.
🐍 The Function Code
ℹ️ The code below is written according to the event as sampled above. It may need to readjusting the part under
# Handle new CloudTrail Create or Accept responses
import os
class Config:
def __init__(self):
self.CROSS_ROLE_NAME = os.environ.get('XACC_ROLE_NAME', 'xaccount-tgw-autotag-Role')
self.TAG_NAME_PREFIX = os.environ.get('TAG_NAME_PREFIX', 'TGW-Attachment')
self.TAG_NAME_SUFFIX = os.environ.get('TAG_NAME_SUFFIX', 'xacc-att')
# Usage
config = Config()
=================================================================
import boto3
import os, json, logging
from config import config
logger = logging.getLogger()
logger.setLevel(logging.INFO)
DEFAULT_NAME_PREFIX = config.TAG_NAME_PREFIX
NAME_TAG_SUFFIX = config.TAG_NAME_SUFFIX
CROSS_ROLE_NAME = config.CROSS_ROLE_NAME
def assume_role(account_id, role_name):
sts = boto3.client("sts")
role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"
logger.info(f"Attempting to assume role {role_arn}")
try:
response = sts.assume_role(
RoleArn=role_arn,
RoleSessionName="TGWAutotagSession"
)
creds = response["Credentials"]
assumed_session = boto3.Session(
aws_access_key_id=creds["AccessKeyId"],
aws_secret_access_key=creds["SecretAccessKey"],
aws_session_token=creds["SessionToken"],
)
# Debug identity
identity = assumed_session.client("sts").get_caller_identity()
logger.info(f"Assumed role identity: {identity}")
return assumed_session
except Exception as e:
logger.error(f"Failed to assume role {role_arn}: {str(e)}")
raise
def handler(event, context):
logger.info("Received event: %s", json.dumps(event))
try:
detail = event["detail"]["responseElements"]
# Handle new CloudTrail Create or Accept responses
if "CreateTransitGatewayVpcAttachmentResponse" in detail:
tgw_attach = detail["CreateTransitGatewayVpcAttachmentResponse"]["transitGatewayVpcAttachment"]
elif "AcceptTransitGatewayVpcAttachmentResponse" in detail:
tgw_attach = detail["AcceptTransitGatewayVpcAttachmentResponse"]["transitGatewayVpcAttachment"]
# Otherwise, Fallback: older CloudTrail format
elif "transitGatewayAttachment" in detail:
tgw_attach = detail["transitGatewayAttachment"]
else:
raise KeyError("No recognizable TGW attachment block in event")
attachment_id = tgw_attach["transitGatewayAttachmentId"]
vpc_id = tgw_attach.get("vpcId") or tgw_attach.get("resourceId")
# requester_account from New or fallback to Old format
if "userIdentity" in event["detail"] and "accountId" in event["detail"]["userIdentity"]:
requester_account = event["detail"]["userIdentity"]["accountId"] # new format
else:
requester_account = event["account"] # old format
except Exception as e:
logger.error(f"Could not parse event for attachment: {str(e)}")
return
logger.info(f"Processing TGW Attachment: {attachment_id} from account {requester_account}, VPC {vpc_id}")
# Step 1: Assume requester role
try:
requester_session = assume_role(requester_account, CROSS_ROLE_NAME)
ec2_requester = requester_session.client("ec2")
vpc = ec2_requester.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]
vpc_tags = vpc.get("Tags", [])
logger.info(f"Requester VPC tags: {vpc_tags}")
except Exception as e:
logger.error(f"Failed to fetch VPC tags from requester account {requester_account}: {str(e)}")
return
# Step 2: Build tags
if vpc_tags:
tags_to_copy = []
for tag in vpc_tags:
key = tag["Key"]
value = tag["Value"]
# If it's the Name tag, add suffix
if key == "Name":
value = f"{value}-{NAME_TAG_SUFFIX}"
tags_to_copy.append({"Key": key, "Value": value})
else:
tags_to_copy = [{"Key": "Name", "Value": f"{DEFAULT_NAME_PREFIX}-{vpc_id}"}]
# Print out final tags before applying
logger.info(f"Final tags_to_copy for TGW attachment {attachment_id}: {tags_to_copy}")
# Step 3: Apply tags in TGW owner account
try:
ec2_owner = boto3.client("ec2")
ec2_owner.create_tags(Resources=[attachment_id], Tags=tags_to_copy)
logger.info(f"✅ Successfully tagged TGW attachment {attachment_id}")
except Exception as e:
logger.error(f"❌ Failed to tag TGW attachment {attachment_id}: {str(e)}")
⚙️ Configuration Parameters
| Variable | Description | Example |
|---|---|---|
TAG_NAME_PREFIX |
Default prefix if no VPC name exists | auto-tgw |
TAG_NAME_SUFFIX |
Suffix appended to “Name” tag | -xacc-att |
CROSS_ROLE_NAME |
IAM role assumed in requester account | TGWAutoTagCrossAccountRole |
🧩 Core Functions
1️⃣ assume_role(account_id, role_name)
Assumes the specified IAM role in the target account and returns a boto3 session object.
Responsibilities:
- Builds role ARN dynamically: arn:aws:iam:::role/
- Uses sts.assume_role for temporary credentials.
- Returns a boto3.Session with assumed credentials.
Logging:
- Logs identity confirmation from
sts.get_caller_identity()for verification.
2️⃣ handler(event, context)
Main entry point for the Lambda function.
Primary Responsibilities:
- Parse CloudTrail event for:
- TGW Attachment ID
- VPC ID
- Requester Account ID
- Assume cross-account IAM role into requester account.
- Retrieve tags from requester VPC.
- Construct tags_to_copy list.
- Apply tags to TGW attachment in the TGW owner account.
🔍 Code Flow Explanation
Event Parsing
1️⃣ Handles multiple CloudTrail formats for backward compatibility:
if "CreateTransitGatewayVpcAttachmentResponse" in detail:
tgw_attach = detail["CreateTransitGatewayVpcAttachmentResponse"]["transitGatewayVpcAttachment"]
elif "AcceptTransitGatewayVpcAttachmentResponse" in detail:
tgw_attach = detail["AcceptTransitGatewayVpcAttachmentResponse"]["transitGatewayVpcAttachment"]
elif "transitGatewayAttachment" in detail: # legacy format
tgw_attach = detail["transitGatewayAttachment"]
2️⃣ Determines requester account:
if "userIdentity" in event["detail"] and "accountId" in event["detail"]["userIdentity"]:
requester_account = event["detail"]["userIdentity"]["accountId"]
else:
requester_account = event["account"]
Cross-Account Tag Retrieval
requester_session = assume_role(requester_account, CROSS_ROLE_NAME)
ec2_requester = requester_session.client("ec2")
vpc = ec2_requester.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]
vpc_tags = vpc.get("Tags", [])
Tag Construction
1️⃣ Adds suffix to Name tag:
for tag in vpc_tags:
if tag["Key"] == "Name":
tag["Value"] = f"{tag['Value']}-{NAME_TAG_SUFFIX}"
2️⃣ Fallback when no tags exist:
tags_to_copy = [{"Key": "Name", "Value": f"{DEFAULT_NAME_PREFIX}-{vpc_id}"}]
🔐 IAM Requirements
TGW Owner Account (Lambda Execution Role)
1️⃣ MUST allow:
{
"Action": [
"ec2:CreateTags",
"sts:AssumeRole"
],
"Resource": "*"
}
Requester Account (Cross-Account Role)
1️⃣ MUST trust TGW Owner account’s Lambda role:
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<tgw-owner-account>:role/<lambda-exec-role>" },
"Action": "sts:AssumeRole"
}
2️⃣ and MUST allow:
{
"Action": "ec2:DescribeVpcs",
"Resource": "*"
}
🧪 Testing Approach
- Use Lambda test console with real CloudTrail event payloads.
- Verify:
- Event parsing logs (attachment ID, requester account, VPC ID)
- Successful assume role identity.
- Correct tag propagation in TGW console.
🧱 Putting Together in Terraform
The code-snippet below are purely for reference purpose. Our TF projects are bootstraped from Terragrunt and it's a modified version of copy/paste from there, which may not exactly fit in every other development environment.
ℹ️ This work-flow is part of our in-house VPC module, where it does the TGW attachemnt during the VPC creation. The condition
var.run_acc != var.tgw_owner_accdetermins whether to execute the associated TF resource in the currently running_account or not.provider = aws.tgwswitches to the TGW owner's account for the deployment
1️⃣ Lambda Excution Role/Policy
# ------------------------------------------------------
# Policy to attach to Lambda excution Role
# ------------------------------------------------------
data "aws_iam_policy_document" "tgw_owner_acc" {
count = var.run_acc != var.tgw_owner_acc ? 1 : 0
statement {
actions = [
"ec2:CreateTags",
"ec2:DescribeTransitGatewayAttachments"
]
effect = "Allow"
resources = ["*"]
sid = "AllowLambdaCreateTgwAttachmentTags"
}
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
resources = [aws_iam_role.tgw_this_acc[local.vpc_name_pfx].arn]
sid = "AllowLambdaToAssumeRole"
}
}
// Generate the policy from above document
resource "aws_iam_policy" "tgw_owner_acc" {
for_each = var.run_acc != var.tgw_owner_acc ? toset(
values(var.env_tgw_info)[*].owner_name
) : []
name = "${lower(each.value)}-${upper(var.run_acc)}-tgw-autotag-Policy"
path = "/"
description = "IAM policy for logging from a lambda"
policy = data.aws_iam_policy_document.tgw_owner_acc[0].json
tags = merge(
var.extra_tags,
{
Name = "${each.value}-tgw-autotag-Policy"
Module = var.tf_module_name
Resource = "aws_iam_policy.tgw_xacc_autotag"
}
)
provider = aws.tgw
}
// Attach policy to lambda excution Role
resource "aws_iam_role_policy_attachment" "tgw_owner_acc" {
for_each = var.run_acc != var.tgw_owner_acc ? toset(
values(var.env_tgw_info)[*].owner_name
) : []
role = "${lower(each.value)}-${var.lambda_role_suffix}"
policy_arn = aws_iam_policy.tgw_owner_acc[each.value].arn
provider = aws.tgw
}
2️⃣ IAM Role in Requester's Account
# ------------------------------------------------------
# Local IAM role for Lambda to assume
# ------------------------------------------------------
resource "aws_iam_role" "tgw_this_acc" {
for_each = var.run_acc != var.tgw_owner_acc ? toset(
[local.vpc_name_pfx]
) : []
#name = "${each.value}-tgw-autotag-Role"
name = var.xacc_tgw_role_name
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
AWS = [
for ID in values(var.env_tgw_info)[*].owner_id :
"arn:aws:iam::${ID}:root"
]
},
Action = "sts:AssumeRole"
}
]
})
tags = merge(
var.extra_tags,
{
Name = "${each.value}-tgw-autotag-Role"
Module = var.tf_module_name
Resource = "aws_iam_role.tgw_xacc_autotag"
}
)
}
//
data "aws_iam_policy_document" "tgw_this_acc" {
statement {
actions = ["ec2:DescribeVpcs"]
effect = "Allow"
resources = ["*"]
sid = "AllowLambdaReadOnlyVpcAccess"
}
}
resource "aws_iam_policy" "tgw_this_acc" {
for_each = var.run_acc != var.tgw_owner_acc ? toset(
[local.vpc_name_pfx]
) : []
name = "${each.value}-tgw-autotag-Policy"
path = "/"
description = "IAM policy for logging from a lambda"
policy = data.aws_iam_policy_document.tgw_this_acc.json
tags = merge(
var.extra_tags,
{
Name = "${each.value}-tgw-autotag-Policy"
Module = var.tf_module_name
Resource = "aws_iam_policy.tgw_xacc_autotag"
}
)
}
// Attach policy to lambda Role
resource "aws_iam_role_policy_attachment" "tgw_this_acc" {
for_each = var.run_acc != var.tgw_owner_acc ? toset(
[local.vpc_name_pfx]
) : []
role = aws_iam_role.tgw_this_acc[each.value].name
policy_arn = aws_iam_policy.tgw_this_acc[each.value].arn
}
3️⃣ Core Lambda Resources
# ------------------------------------------------------
# Lambda function resource
# ------------------------------------------------------
data "archive_file" "tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
type = "zip"
output_path = "${path.module}/autotag_lambda.zip"
source {
content = file("${path.module}/lambda/function.py")
filename = "function.py"
}
source {
content = file("${path.module}/lambda/config.py")
filename = "config.py"
}
}
// CloudWatch for Lambda function
resource "aws_cloudwatch_log_group" "tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
name = "/aws/lambda/${local.vpc_name_pfx}-tgw-autotag"
#
retention_in_days = 14
}
// Lambda-function
resource "aws_lambda_function" "tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
filename = data.archive_file.tgw_autotag[0].output_path
function_name = "${local.vpc_name_pfx}-tgw-autotag"
handler = "function.handler"
role = var.acc_lambda_role_arn
runtime = "python3.12"
timeout = 12
source_code_hash = data.archive_file.tgw_autotag[0].output_base64sha256
# Advanced logging controls (optional)
logging_config {
log_format = "JSON"
application_log_level = "INFO"
system_log_level = "WARN"
}
# Environment variables for config.py
environment {
variables = {
TAG_NAME_PREFIX = local.vpc_name_pfx
TAG_NAME_SUFFIX = "att-xacc"
XACC_ROLE_NAME = var.xacc_tgw_role_name
}
}
# Direct dependency
depends_on = [
aws_cloudwatch_log_group.tgw_autotag,
]
}
4️⃣ CW Logs Group for CloudTrail
resource "aws_cloudwatch_log_group" "ct_tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
name = "/aws/cloudtrail/${local.vpc_name_pfx}-tgw-autotag"
#
retention_in_days = 14
}
// IAM role for CloudTrail to write to CloudWatch Logs
resource "aws_iam_role" "ct_tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
name = "${lower(var.aws_acc_name)}-cloudtrail-assume-Role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
}
//
resource "aws_iam_role_policy" "ct_tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
role = aws_iam_role.ct_tgw_autotag[0].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "${aws_cloudwatch_log_group.ct_tgw_autotag[0].arn}:*"
}
]
})
}
5️⃣ CloudTrail + EventBridge Integration
resource "aws_cloudtrail" "tgw_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
name = "${lower(var.aws_acc_name)}-tgw-attachment-trail"
enable_logging = true
s3_bucket_name = var.svc_logging_bucket.name
is_multi_region_trail = false
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.ct_tgw_autotag[0].arn}:*"
cloud_watch_logs_role_arn = aws_iam_role.ct_tgw_autotag[0].arn
include_global_service_events = true
# This is the key part: enable EventBridge integration
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::Lambda::Function"
values = [aws_lambda_function.tgw_autotag[0].arn]
}
}
tags = {
Module = var.tf_module_name
Resource = "aws_cloudtrail.tgw_autotag"
}
}
6️⃣ EventBridge Rule & Target
resource "aws_cloudwatch_event_rule" "acer_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
name = "${lower(var.aws_acc_name)}-tgw-attachment-created"
description = "Triggers when TGW attachments are created"
event_pattern = jsonencode({
source = ["aws.ec2"],
detail-type = ["AWS API Call via CloudTrail"],
detail = {
eventSource = ["ec2.amazonaws.com"],
eventName = [
"AcceptTransitGatewayVpcAttachment",
"CreateTransitGatewayVpcAttachment",
]
}
})
}
// Lambda target
resource "aws_cloudwatch_event_target" "acet_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
rule = aws_cloudwatch_event_rule.acer_autotag[0].name
target_id = "TGWAutotagLambda"
arn = aws_lambda_function.tgw_autotag[0].arn
}
// allow EventBridge
resource "aws_lambda_permission" "alp_autotag" {
count = var.run_acc == var.tgw_owner_acc ? 1 : 0
statement_id = "AllowExecutionFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.tgw_autotag[0].function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.acer_autotag[0].arn
}
🌱 Conclusion: Benefits
| Benefit | Description |
|---|---|
| Operational Consistency | Ensures every TGW attachment inherits tags from its requester VPC. |
| Cross-Account Governance | Uses IAM roles and least-privilege policies to enforce separation of duties. |
| Accurate Cost Allocation | Tags align with Cost Center, Environment, and Ownership standards. |
| Event-Driven Scalability | Fully serverless; no polling or scheduled jobs. |
| Future-Proof | Handles both legacy and new CloudTrail event formats. |
📊 End Result

The ones with -att are the native to the TGW owner's account and the ones with -att-xacc are cross-account from requesters' account.
🚀 Future Enhancements
- Add SNS notifications for tagging failures.
- Extend to tag DeleteTransitGatewayVpcAttachment cleanup events.
- Integrate with AWS Service Catalog TagOptions for standardization.
- Publish metrics via CloudWatch Insights for audit reporting.

Top comments (0)