O blog da AWS

Automatize tags de subnet em VPCs compartilhadas com AWS RAM

Por Lucas Leme Lopes, Consultor de Delivery e atua no atendimento de clientes de Professional Services Brasil e Jonathan Pereira Cruz, Consultor de Delivery e atua no atendimento de clientes de Professional Services Brasil.

Ao implementar estratégias de Virtual Private Cloud (VPC) Compartilhada usando AWS Resource Access Manager (RAM) , organizações frequentemente encontram um desafio específico: o serviço não propaga automaticamente os metadados dos recursos para as contas consumidoras.

Em um cenário típico de compartilhamento de VPC, o AWS Resource Access Manager (RAM) permite compartilhar subnets entre contas de forma eficiente, com a conectividade de rede garantida. Como o RAM foca especificamente no compartilhamento de recursos e suas permissões, as tags e metadados não são automaticamente propagados para as contas consumidoras. Para endereçar essa característica do serviço, apresentaremos uma solução automatizada que complementa o compartilhamento de VPC, para que todos os metadados necessários estejam disponíveis nas contas consumidoras de forma consistente e eficiente.

Este comportamento padrão do AWS Resource Access Manager (RAM) impõe desafios operacionais críticos às equipes, que precisam investir tempo significativo em consultas manuais entre contas para identificar recursos, lidam com atrasos em auditorias de compliance pela falta de visibilidade dos metadados, e enfrentam obstáculos na implementação de Infraestrutura como código (IaC), pois a ausência de identificadores claros nas subnets compartilhadas impossibilita a automatização baseada em sua finalidade.

Visão geral da solução

Pré-requisitos

Antes de implementar, você precisa ter:

Detalhes da solução

A solução apresentada detecta automaticamente quando recursos são compartilhados via RAM e aplica as tags apropriadas com informações centralizadas no Parameter Store.

Arquitetura da Solução

Figura 1: Arquitetura da solução

Funcionamento

A solução opera em um cenário onde temos duas contas AWS: uma conta de rede (Network Account) que possui e compartilha as subnets, e uma conta de workload (Workload Account) que consome esses recursos compartilhados. O processo funciona da seguinte forma:

Quando a equipe de redes compartilha uma subnet através do AWS RAM, um evento é gerado no Amazon EventBridge. Este evento aciona automaticamente a função Lambda da solução na conta consumidora, que executa as seguintes ações:

  1. Identifica o ID da conta proprietária e o ID do recurso compartilhado
  2. Consulta o AWS Systems Manager Parameter Store na conta proprietária para obter os metadados corretos da subnet (como nome, tipo e tags específicas)

Por fim, aplica automaticamente esses metadados à subnet compartilhada na conta consumidora.

Por exemplo, em uma organização que utiliza VPCs compartilhadas para workloads de diferentes times, quando uma subnet destinada a serviços de banco de dados é compartilhada, a solução automaticamente aplica tags como ‘Name=prod-database-subnet’ e ‘Type=database’ na conta consumidora, e mantém a consistência com a configuração original da conta de rede.

Este processo automatizado mantém de forma simples as identificações apropriadas entre as contas.

Detecção e validação de eventos

A função Lambda proposta nesta solução implementa uma lógica específica de validação que processa apenas eventos relevantes do AWS RAM, como mudanças no estado de compartilhamento de recursos e novas associações de subnets, garante que apenas as ações necessárias sejam executadas para a propagação de tags.

Na primeira parte do código da função Lambda que implementa esta validação inicial dos eventos. Este trecho é responsável por receber o evento do EventBridge e determinar se ele é relevante para o processamento de tags:

import json
import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(f"Evento recebido: {json.dumps(event)}")
    
    # Verificar se o evento é de compartilhamento de recursos RAM
    detail_type = event.get('detail-type', '')
    source = event.get('source', '')
    
    logger.info(f"Tipo de evento: {detail_type}, Fonte: {source}")
    
    if source == 'aws.ram':
        if detail_type == 'Resource Sharing State Change':
            # Processar evento específico de mudança de estado
            detail = event.get('detail', {})
            resource_arn = detail.get('resourceARN', '')
            event_type = detail.get('event', '')
            status = detail.get('status', '')
            
            if 'subnet' in resource_arn and event_type == 'Resource Share Association' and status == 'associated':
                process_subnet_association(event)
        elif 'Resource Share' in detail_type:
            process_shared_resources()

Este código representa apenas o início da implementação. As funções auxiliares e demais componentes são detalhados nas seções seguintes.

Idempotência e validação de recursos

Antes de aplicar tags, a solução verifica se o recurso existe e está acessível, garante operações idempotentes. Esta validação é realizada pela mesma função Lambda, através de um trecho específico do código que verifica a existência e acessibilidade das subnets antes de aplicar as tags. Abaixo está a parte do código responsável por esta verificação:

def tag_subnets(subnet_info, subnet_type):
    for subnet in subnet_data:
        subnet_id = subnet.get('id')
        subnet_name = subnet.get('name')
        
        if not subnet_id or not subnet_name:
            logger.warning(f"Informações incompletas para subnet: {subnet}")
            continue
        
        try:
            # Verificar se a subnet existe antes de tentar aplicar tags
            try:
                ec2_client.describe_subnets(SubnetIds=[subnet_id])
            except Exception as e:
                logger.warning(f"Subnet {subnet_id} não encontrada ou não acessível: {str(e)}")
                continue
                
            # Aplicar tags apenas se a subnet for válida
            apply_tags_to_subnet(subnet_id, subnet_name, subnet_type)
            
        except Exception as e:
            logger.error(f"Erro ao processar subnet {subnet_id}: {str(e)}")

Descoberta inteligente de recursos compartilhados

A solução consulta dinamicamente o AWS RAM para identificar recursos compartilhados: A função Lambda também é responsável por descobrir automaticamente os recursos compartilhados via AWS RAM. O trecho de código abaixo é parte da mesma função Lambda e demonstra como a descoberta é realizada usando o AWS SDK (boto3) para consultar a API do AWS RAM:

def get_shared_vpcs():
    shared_vpcs = []
    
    try:
        # Listar compartilhamentos de recursos
        response = ram_client.get_resource_shares(
            resourceOwner='OTHER-ACCOUNTS'
        )
        
        # Para cada compartilhamento, obter os recursos
        for share in response.get('resourceShares', []):
            share_arn = share.get('resourceShareArn')
            
            resources = ram_client.list_resources(
                resourceOwner='OTHER-ACCOUNTS',
                resourceShareArns=[share_arn]
            )
            
            # Filtrar e extrair VPC IDs
            for resource in resources.get('resources', []):
                if resource.get('type') == 'ec2:vpc':
                    arn = resource.get('arn', '')
                    vpc_id = extract_vpc_id_from_arn(arn)
                    if vpc_id:
                        shared_vpcs.append(vpc_id)
                        
    except Exception as e:
        logger.error(f"Erro ao obter VPCs compartilhadas: {str(e)}")
    
    return shared_vpcs

Consulta parametrizada ao Parameter Store

A função Lambda consulta o AWS Systems Manager Parameter Store na conta proprietária do recurso para acessar os metadados originais da subnet, como nome, tipo e tags específicas que foram definidos durante sua criação. Esta consulta possibilita que a função replique fielmente estas informações na conta consumidora, mantendo a consistência da identificação dos recursos entre as contas:

Importante: Os parâmetros devem ser criados na conta proprietária dos recursos (onde as VPCs/subnets foram originalmente criadas) e compartilhados com as contas consumidoras via RAM. A função Lambda na conta consumidora acessa os parâmetros usando o ARN completo que inclui o account ID da conta proprietária.

def get_parameter_types(vpc_id, owner_account_id):
    parameter_types = []
    region = boto3.session.Session().region_name
    
    # Obter tipos de subnet da configuração
    snet_types_str = os.environ.get('SNET_TYPES', '[]')
    specific_types = json.loads(snet_types_str)
    
    base_path = f"/vpc/shared/{vpc_id}"
    
    for type_suffix in specific_types:
        specific_path = f"{base_path}/{type_suffix}"
        param_arn = f"arn:aws:ssm:{region}:{owner_account_id}:parameter{specific_path}"
        
        try:
            response = ssm_client.get_parameter(Name=param_arn)
            parameter_types.append({
                'type': type_suffix,
                'path': specific_path,
                'value': response['Parameter']['Value']
            })
        except botocore.exceptions.ClientError as e:
            if e.response.get('Error', {}).get('Code') == 'ParameterNotFound':
                logger.info(f"Parâmetro não encontrado para tipo {type_suffix}")
                continue
    
    return parameter_types

Estrutura no Parameter Store:

A solução armazena os metadados das subnets de forma hierárquica seguindo o padrão/vpc/shared/{vpc-id}/{tipo}. Esta estrutura organizada traz benefícios importantes:

  • Facilita a localização rápida de informações específicas por VPC e tipo de subnet
  • Simplifica a manutenção e atualização dos metadados por VPC ou categoria de subnet
  • Possibilita escalar o gerenciamento de múltiplas VPCs e centenas de subnets de forma organizada

O exemplo abaixo mostra como os metadados são armazenados no Parameter Store em formato JSON. Neste caso, temos duas subnets em uma VPC corporativa (corp) com AWS Transit Gateway (tgw), distribuídas em diferentes zonas de disponibilidade (AZs) para alta disponibilidade.

[
{
"id": "subnet-02b7a2e5d1eb57872",
"name": "aws-vpc-tgw-corp-001-az1a"
},
{
"id": "subnet-0b1f8e88c11204caa", 
"name": "aws-vpc-tgw-corp-002-az1b"
}
]

Aplicação de Tags

O código abaixo, que é parte da mesma função Lambda, demonstra como as tags são aplicadas de forma segura:

def tag_subnets(subnet_info, subnet_type):
    tagged_subnets = []
    
    for subnet in subnet_data:
        subnet_id = subnet.get('id')
        subnet_name = subnet.get('name')
        
        if not subnet_id or not subnet_name:
            logger.warning(f"Informações incompletas para subnet: {subnet}")
            continue
        
        try:
            # Verificar se a subnet existe antes de aplicar tags
            ec2_client.describe_subnets(SubnetIds=[subnet_id])
            
            # Preparar as tags base
            tags = [
                {'Key': 'Name', 'Value': subnet_name},
                {'Key': 'snet-type', 'Value': subnet_type}
            ]
            
            # Aplicar tags à subnet
            ec2_client.create_tags(
                Resources=[subnet_id],
                Tags=tags
            )
            
            tagged_subnets.append({
                'subnet_id': subnet_id,
                'subnet_name': subnet_name,
                'tags': tags
            })
            
        except Exception as e:
            logger.error(f"Erro ao aplicar tags à subnet {subnet_id}: {str(e)}")
    
    # Logs
    if tagged_subnets:
        logger.info(f"Tags aplicadas com sucesso às seguintes subnets do tipo {subnet_type}:")
        for tagged_subnet in tagged_subnets:
            logger.info(f"Subnet {tagged_subnet['subnet_id']} ({tagged_subnet['subnet_name']})")

Infraestrutura como Código

A solução utiliza Terraform para automatizar o provisionamento e gerenciamento de toda a infraestrutura necessária de forma consistente e versionada. Esta abordagem possibilita que toda a configuração da função Lambda, regras do EventBridge e demais recursos seja definida em código.

O código Terraform abaixo demonstra a configuração necessária para implantar os componentes principais da solução na conta consumidora (Workload Account). Isso inclui a função Lambda que processará os eventos de compartilhamento e a regra do EventBridge que detectará as mudanças no AWS RAM:

  • Uma função Lambda que executa o código de propagação de tags
  • Uma regra do EventBridge para capturar eventos do AWS RAM
  • As permissões IAM necessárias para a Lambda acessar os recursos

A configuração requer apenas que você especifique:

  • Arquivo ZIP contendo o código da função Lambda, neste exemplo: lambda_vpc_tagger.zip
  • Os tipos de subnets que serão monitoradas
  • Nível de log desejado para troubleshooting

Abaixo a estrutura básica necessária para implementar a solução. Note que este é um exemplo e requer configurações adicionais para funcionar em seu ambiente:

Terraform

# Função Lambda
resource "aws_lambda_function" "vpc_tagger" {
  filename         = "lambda_vpc_tagger.zip"
  function_name    = "vpc_tagger"
  role            = aws_iam_role.lambda_role.arn
  handler         = "lambda_vpc_tagger.lambda_handler"
  runtime         = "python3.9"
  timeout         = 300

  environment {
    variables = {
      LOG_LEVEL  = var.log_level
      SNET_TYPES = jsonencode(var.subnet_types)
    }
  }
}

# EventBridge Rule
resource "aws_cloudwatch_event_rule" "ram_events" {
  name = "vpc_tagger_ram_events"
  
  event_pattern = jsonencode({
    source      = ["aws.ram"]
    detail-type = [
      "AWS RAM Resource Share Available",
      "AWS RAM Resource Share Association Changed", 
      "Resource Sharing State Change"
    ]
  })
}

Para implementar esta configuração, você precisará:

  • Criar uma IAM Role com as permissões mínimas necessárias para a função Lambda:
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "ec2:DescribeVpcs",
                "ec2:DescribeSubnets",
                "ec2:CreateTags",
                "ssm:GetParameter",
                "ssm:GetParametersByPath",
                "ram:ListResources",
                "ram:GetResourceShares",
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords"
  • Criar o arquivo lambda_vpc_tagger.zip agrupando todo o código Python apresentado nas seções anteriores deste blog post. Para isso, basta compactar o arquivo lambda_vpc_tagger.py em um arquivo ZIP mantendo o arquivo Python na raiz do ZIP.
  • Definir as variáveis log_level (ex: INFO) e subnet_types (ex: [‘app’, ‘data’]) no seu arquivo terraform.tfvars

Benefícios da solução

A solução automaticamente monitora três tipos de eventos através do Amazon EventBridge: a disponibilidade de novos compartilhamentos no AWS RAM, mudanças nas associações de recursos e alterações no estado de compartilhamento das subnets. Esta capacidade de resposta em tempo real permite que as tags sejam aplicadas imediatamente após o compartilhamento, trazendo visibilidade dos metadados na conta consumidora sem necessidade de acesso à conta de network.

Quando um novo compartilhamento é detectado ou uma associação é modificada, a solução automaticamente processa o evento sem necessidade de intervenção manual, aplicando as tags apropriadas conforme a configuração definida para diferentes tipos de subnet. Todo o processo é registrado com logs detalhados no Amazon CloudWatch Logs, permitindo visualização completa das ações executadas.

A arquitetura foi projetada para escalar eficientemente, funcionando de forma consistente com múltiplas VPCs e contas AWS.

Implementação

  • Configure os metadados das subnets no AWS Systems Manager Parameter Store (conta proprietária) seguindo a estrutura /vpc/shared/{vpc-id}/{tipo}, incluindo ID e nome de cada subnet conforme apresentado anteriormente.
  • Crie um arquivo ZIP contendo o código Python mostrado nas seções anteriores deste post:
         zip lambda_vpc_tagger.zip lambda_vpc_tagger.py
  • Crie os arquivos Terraform em um diretório:

main.tf: com as configurações da Lambda e EventBridge apresentadas

iam.tf: com a Role e Policy necessárias para a Lambda

variables.tf: para definir as variáveis subnet_types e log_level

  • Execute os comandos na conta consumidora:
        terraform init

        terraform plan

        terraform apply
  • Na conta proprietária, compartilhe uma subnet usando o AWS Resource Access Manager.
  • Na conta consumidora, verifique no CloudWatch Logs os logs do grupo /aws/lambda/vpc_tagger para confirmar:
    • Recebimento do evento RAM
    • Consulta ao Parameter Store
    • Aplicação das tags na subnet

Monitoramento e Troubleshooting

A função gera logs detalhados para facilitar o troubleshooting:

logger.info(f"VPC compartilhada encontrada: {vpc_id}")

logger.info(f"Aplicando tags à subnet {subnet_id}: {tags}")

logger.info(f"Tags aplicadas com sucesso às subnets do tipo {subnet_type}")

Consideração sobre os custos

A solução utiliza serviços AWS que podem gerar custos. Os principais componentes que afetam o custo são:

  • AWS Lambda: Cobrança por número de invocações e tempo de execução
  • Amazon EventBridge: Cobrança por número de eventos processados
  • AWS Systems Manager Parameter Store: Parâmetros standard são gratuitos, parâmetros avançados têm cobrança por parâmetro e por chamada de API
  • AWS Resource Access Manager: Sem custo adicional pelo uso do serviço

Os custos efetivos variam de acordo com:

  • Frequência de novos compartilhamentos de subnets
  • Número total de subnets sob gestão
  • Total de parâmetros em uso

Conclusão

O desafio de tags perdidas em VPC Shared é real e impacta significativamente a governança de recursos, com times consumidores sem visibilidade dos metadados críticos nas contas compartilhadas. Esta solução automatizada resolve o desafio de forma elegante, mantendo a consistência de metadados entre contas sem intervenção manual.

A combinação de EventBridge, Lambda e Parameter Store cria um sistema robusto que escala com sua infraestrutura e mantém suas subnets compartilhadas devidamente organizadas e identificadas.

Resultado: Suas subnets compartilhadas chegam na conta de destino com todas as tags necessárias, automaticamente.

Recursos Adicionais

EventBridge Event Patterns

EC2 Tagging Best Practices

Autores

Lucas Leme Lopes é Consultor de Delivery e atua no atendimento de clientes de Professional Services Brasil, está há mais de 8 anos trabalhando com soluções de Cloud, sempre focado em ajudar clientes a resolverem suas demandas através da tecnologia.
Jonathan Pereira Cruz é Consultor de Delivery e atua no atendimento de clientes de Professional Services Brasil, está há mais de 7 anos trabalhando com soluções de Cloud.