Azure Bastion na Landing Zone sem IP Público

Neste artigo você vai configurar o Azure Bastion sem IP público na Landing Zone Hub-Spoke, permitindo acesso RDP e SSH às VMs sem expor nenhum endereço público nas máquinas virtuais. O Azure Bastion sem IP público nas VMs é o modelo recomendado para ambientes corporativos: o Bastion atua como jump host gerenciado diretamente no Hub, eliminando a superfície de ataque de IPs expostos. Vamos implantar dois Bastions — um em Brazil South e outro em East US — com SKU Standard, tunneling nativo e IP connect habilitados, usando Terraform e a AzureBastionSubnet já criada no artigo anterior.
- 📖 Art. 01: Planejamento de DR Azure e Landing Zone
- 📖 Art. 02: Hub-Spoke Landing Zone com Terraform
- 📖 Art. 03: Azure Firewall e NSGs na Landing Zone
- ⚙️ Art. 04 (este): Azure Bastion na Landing Zone sem IP Público
- 🔒 Art. 05: Azure Front Door e Traffic Manager para Failover
- 🔒 Art. 06: DNS Privado Multi-Região no Azure
- 🔒 Art. 07: Azure Site Recovery para VMs Multi-Região
- 🔒 Art. 08: Azure Storage com Geo-Replicação para DR
- 🔒 Art. 09: AKS Multi-Região com Failover no Azure
- 🔒 Art. 10: Velero no AKS para Backup e Restore Cross-Region
- 🔒 Art. 11: Runbooks de Failover no Azure Automation
- 🔒 Art. 12: Simular um DR no Azure sem Impacto em Produção
- 🔒 Art. 13: Monitoramento do DR no Azure com Azure Monitor
Sumário
- O que é Azure Bastion e por que usar sem IP público
- SKU Basic vs Standard — qual escolher
- Arquitetura do lab
- Pré-requisitos
- Variáveis de ambiente
- Passo 1 — Código Terraform
- Passo 2 — Executar o Terraform
- Passo 3 — Configurar RBAC
- Passo 4 — Acessar VMs pelo portal
- Passo 5 — Acesso via cliente nativo (SSH/RDP)
- JIT Access com Azure Bastion
- Verificar a configuração
- Troubleshooting
- Limpeza dos recursos
- Próximos passos
O que é Azure Bastion e por que usar sem IP público
O Azure Bastion sem IP público nas VMs é um serviço PaaS gerenciado pela Microsoft que fornece conectividade RDP e SSH segura para máquinas virtuais diretamente pelo portal do Azure ou por clientes nativos. Em vez de abrir portas RDP/SSH na internet, todo o acesso passa pelo Bastion — que é o único recurso exposto publicamente, com IP público próprio e protegido por TLS.
O conceito central do azure bastion sem ip publico nas VMs é simples: as máquinas virtuais do Spoke ficam sem nenhum IP público. Isso significa que não há como acessá-las diretamente pela internet, mesmo que o NSG permita — simplesmente não existe endereço de destino. O acesso sempre passa pelo Bastion no Hub, que usa peering para alcançar as VMs nos Spokes.
| Modelo de acesso | Com IP público na VM | Com Azure Bastion sem IP público |
|---|---|---|
| Superfície de ataque | IP da VM exposto na internet | Apenas o Bastion exposto |
| Porta RDP/SSH aberta | 3389/22 na internet | Porta 443 via TLS no Bastion |
| Auditoria de acesso | Dependente de logs de VM | Logs centralizados no Azure Monitor |
| Custo adicional | IP público por VM (~R$ 20/mês cada) | Uma instância Bastion por Hub |
| Compatibilidade | Qualquer cliente RDP/SSH | Portal, cliente nativo (SKU Standard) |
SKU Basic vs Standard — qual escolher
O Azure Bastion tem dois SKUs principais. Para um ambiente de Landing Zone corporativo com DR multi-região, o SKU Standard é obrigatório pois habilita tunneling nativo e IP connect — recursos essenciais para integrar ferramentas como VS Code Remote SSH e Windows Terminal.
| Recurso | Basic | Standard |
|---|---|---|
| Acesso via portal | ✅ | ✅ |
| Clipboard | ✅ | ✅ |
| Tunneling nativo (SSH/RDP via CLI) | ❌ | ✅ |
| IP Connect (IP privado direto) | ❌ | ✅ |
| Sessões simultâneas (scale units) | Fixo (2) | 2–50 (escalável) |
| Shareable Link | ❌ | ✅ |
| Custo (por hora, EastUS 2024) | ~$0.19 | ~$0.29 + scale units |
Neste lab usamos SKU Standard com 2 scale units — suficiente para testes. Em produção, ao operar Azure Bastion sem IP público em múltiplas regiões, aumente os scale units conforme o número de sessões simultâneas esperadas.
Arquitetura do lab
Cada Hub VNet — vnet-hub-blog-castilho-brazilsouth e vnet-hub-blog-castilho-eastus — já possui uma AzureBastionSubnet criada no Art. 02, com prefixo /26. O Azure Bastion sem IP público nas VMs precisa obrigatoriamente desta subnet com esse nome exato. Vamos implantar um Bastion em cada Hub, criando apenas o IP público e o host Bastion.
| Recurso | Região | Subnet |
|---|---|---|
| pip-bastion-hub-blog-castilho-brazilsouth | Brazil South | — (IP público do Bastion) |
| bastion-hub-blog-castilho-brazilsouth | Brazil South | AzureBastionSubnet (10.0.0.128/26) |
| pip-bastion-hub-blog-castilho-eastus | East US | — (IP público do Bastion) |
| bastion-hub-blog-castilho-eastus | East US | AzureBastionSubnet (10.1.0.128/26) |
O fluxo de acesso com Azure Bastion sem IP público nas VMs é: operador → portal Azure (HTTPS 443) → Bastion no Hub → peering → VM no Spoke. A VM nunca é atingida diretamente pela internet.
Pré-requisitos
- Hub-Spoke Landing Zone implantada (Art. 02) — VNets e
AzureBastionSubnetexistentes - Terraform ≥ 1.5 instalado
- Azure CLI autenticado:
az login - Permissão Contributor nos Resource Groups de rede (
rg-blog-castilho-network-*) - Storage Account de remote state configurado (bootstrap do Art. 02)
.env local (não versionado) para armazenar esses valores.Variáveis de ambiente
Defina as variáveis antes de executar qualquer comando Terraform ou Azure CLI:
# Identidade Azure
export AZURE_SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
# Remote state (criado no bootstrap do Art. 02)
export TF_STATE_RG="rg-blog-castilho-tfstate"
export TF_STATE_SA="<storage-account-name>"
export TF_STATE_CONTAINER="tfstate"
Passo 1 — Código Terraform
Cada Bastion tem sua própria pasta de Terraform com três arquivos: providers.tf, variables.tf e main.tf. Veja a estrutura do Bastion de Brazil South:
providers.tf
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
backend "azurerm" {}
}
provider "azurerm" {
subscription_id = var.subscription_id
features {}
}
O bloco backend "azurerm" {} vazio é intencional — os parâmetros de conexão com o Storage Account são passados via backend.hcl no momento do terraform init, mantendo o código sem credenciais hardcoded.
variables.tf
variable "subscription_id" {
type = string
sensitive = true
}
variable "sku" {
type = string
default = "Standard"
}
variable "scale_units" {
type = number
default = 2
}
variable "tags" {
type = map(string)
default = {
project = "blog-castilho"
managed_by = "terraform"
artigo = "art-04-bastion"
}
}
main.tf — Brazil South
O main.tf usa data sources para referenciar o Resource Group e a subnet criados no Art. 02, sem precisar de remote state:
data "azurerm_resource_group" "this" {
name = "rg-blog-castilho-network-brazilsouth"
}
data "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet"
virtual_network_name = "vnet-hub-blog-castilho-brazilsouth"
resource_group_name = data.azurerm_resource_group.this.name
}
resource "azurerm_public_ip" "this" {
name = "pip-bastion-hub-blog-castilho-brazilsouth"
resource_group_name = data.azurerm_resource_group.this.name
location = data.azurerm_resource_group.this.location
allocation_method = "Static"
sku = "Standard"
tags = var.tags
}
resource "azurerm_bastion_host" "this" {
name = "bastion-hub-blog-castilho-brazilsouth"
resource_group_name = data.azurerm_resource_group.this.name
location = data.azurerm_resource_group.this.location
sku = var.sku
scale_units = var.scale_units
tunneling_enabled = true
ip_connect_enabled = true
tags = var.tags
ip_configuration {
name = "ipconfig1"
subnet_id = data.azurerm_subnet.bastion.id
public_ip_address_id = azurerm_public_ip.this.id
}
}
Os atributos tunneling_enabled e ip_connect_enabled requerem SKU Standard. O ip_connect_enabled permite acessar qualquer VM pelo endereço IP privado, mesmo que ela não esteja visível no portal — útil para VMs recém-criadas ou em Spokes sem peering direto com o Hub do Bastion.
main.tf — East US
O Bastion de East US segue a mesma estrutura, apenas trocando os nomes dos recursos e referenciando a VNet correta:
data "azurerm_resource_group" "this" {
name = "rg-blog-castilho-network-eastus"
}
data "azurerm_subnet" "bastion" {
name = "AzureBastionSubnet"
virtual_network_name = "vnet-hub-blog-castilho-eastus"
resource_group_name = data.azurerm_resource_group.this.name
}
resource "azurerm_public_ip" "this" {
name = "pip-bastion-hub-blog-castilho-eastus"
resource_group_name = data.azurerm_resource_group.this.name
location = data.azurerm_resource_group.this.location
allocation_method = "Static"
sku = "Standard"
tags = var.tags
}
resource "azurerm_bastion_host" "this" {
name = "bastion-hub-blog-castilho-eastus"
resource_group_name = data.azurerm_resource_group.this.name
location = data.azurerm_resource_group.this.location
sku = var.sku
scale_units = var.scale_units
tunneling_enabled = true
ip_connect_enabled = true
tags = var.tags
ip_configuration {
name = "ipconfig1"
subnet_id = data.azurerm_subnet.bastion.id
public_ip_address_id = azurerm_public_ip.this.id
}
}
Passo 2 — Executar o Terraform
Cada pasta de recurso tem seu próprio estado Terraform no remote state. O fluxo é: gerar o backend.hcl, inicializar e aplicar:
# Brazil South
cd scripts/art-04-bastion/bastion-hub-blog-castilho-brazilsouth
cat > backend.hcl <<EOF
resource_group_name = "$TF_STATE_RG"
storage_account_name = "$TF_STATE_SA"
container_name = "$TF_STATE_CONTAINER"
key = "bastion-hub-blog-castilho-brazilsouth.tfstate"
EOF
terraform init -backend-config=backend.hcl -reconfigure
terraform apply -var subscription_id="$AZURE_SUBSCRIPTION_ID"
Repita para East US alterando o diretório e a chave do state. O provisionamento leva entre 5 e 10 minutos — o Bastion é um serviço que exige inicialização da instância gerenciada pela Microsoft.
# East US
cd scripts/art-04-bastion/bastion-hub-blog-castilho-eastus
cat > backend.hcl <<EOF
resource_group_name = "$TF_STATE_RG"
storage_account_name = "$TF_STATE_SA"
container_name = "$TF_STATE_CONTAINER"
key = "bastion-hub-blog-castilho-eastus.tfstate"
EOF
terraform init -backend-config=backend.hcl -reconfigure
terraform apply -var subscription_id="$AZURE_SUBSCRIPTION_ID"
Ao concluir, o Terraform mostra as informações dos dois recursos criados:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
# Recursos criados:
# azurerm_public_ip.this → pip-bastion-hub-blog-castilho-brazilsouth
# azurerm_bastion_host.this → bastion-hub-blog-castilho-brazilsouth
Passo 3 — Configurar RBAC
O Azure Bastion sem IP público depende de RBAC bem configurado para controlar quem pode abrir sessões. São necessárias permissões em dois escopos: no Bastion (para usar o serviço) e na VM (para autenticar na máquina).
| Role | Escopo | Permite |
|---|---|---|
| Reader | Bastion Host | Visualizar e usar o Bastion no portal |
| Reader | VM ou Resource Group | Ver a VM na lista do portal |
| Virtual Machine Administrator Login | VM | Login com privilégios de administrador |
| Virtual Machine User Login | VM | Login como usuário padrão |
Atribua as roles necessárias com Azure CLI. Substitua <OBJECT_ID> pelo Object ID do usuário ou grupo no Azure AD:
SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
OBJECT_ID="<OBJECT_ID>"
VM_RG="rg-blog-castilho-workload-brazilsouth"
VM_NAME="vm-drlab-brazilsouth" # nome da VM de teste
BASTION_RG="rg-blog-castilho-network-brazilsouth"
BASTION_NAME="bastion-hub-blog-castilho-brazilsouth"
# Reader no Bastion
az role assignment create \
--assignee "$OBJECT_ID" \
--role "Reader" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$BASTION_RG/providers/Microsoft.Network/bastionHosts/$BASTION_NAME"
# Virtual Machine Administrator Login na VM
az role assignment create \
--assignee "$OBJECT_ID" \
--role "Virtual Machine Administrator Login" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$VM_RG/providers/Microsoft.Compute/virtualMachines/$VM_NAME"
Passo 4 — Acessar VMs pelo portal
Com o Bastion provisionado e o RBAC configurado, o acesso pelo portal do Azure funciona sem instalar nada na máquina cliente:
- No portal, navegue até a VM desejada no Spoke
- No menu lateral, clique em Connect → Bastion
- O Azure detecta automaticamente o Bastion disponível no Hub via peering
- Informe usuário e senha (ou chave SSH para VMs Linux)
- A sessão RDP ou SSH abre diretamente no browser via WebSocket sobre TLS 443
Nenhuma porta RDP (3389) ou SSH (22) precisa estar aberta nos NSGs para o tráfego externo. O Bastion se comunica internamente com a VM pela VNet, então apenas o tráfego de rede interna é necessário.
Passo 5 — Acesso via cliente nativo (SSH/RDP)
O SKU Standard com tunneling_enabled = true permite usar clientes nativos como ssh, Windows Terminal ou VS Code Remote SSH. Isso é feito criando um túnel local pelo Azure CLI:
# Criar túnel local para SSH (porta 2222 local → VM)
az network bastion tunnel \
--name bastion-hub-blog-castilho-brazilsouth \
--resource-group rg-blog-castilho-network-brazilsouth \
--target-resource-id "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/rg-blog-castilho-workload-brazilsouth/providers/Microsoft.Compute/virtualMachines/vm-drlab-brazilsouth" \
--resource-port 22 \
--port 2222
Com o túnel ativo, conecte pelo cliente SSH em outro terminal:
ssh azureuser@127.0.0.1 -p 2222
Para RDP, o processo é o mesmo: substitua --resource-port 22 por 3389 e --port 2222 por qualquer porta local livre, depois conecte com o cliente RDP apontando para 127.0.0.1:<porta>.
JIT Access com Azure Bastion
O Just-In-Time (JIT) Access é uma funcionalidade do Microsoft Defender for Cloud que complementa o Azure Bastion. Com JIT, as portas de gerenciamento (3389, 22) ficam bloqueadas por padrão nos NSGs, e são abertas temporariamente apenas quando um usuário solicita acesso — com aprovação, horário e IP de origem controlados.
Combinando JIT com Azure Bastion sem IP público, o acesso às VMs exige: autenticação no Azure AD + permissão JIT aprovada + sessão pelo Bastion. São três camadas de controle sem nenhum IP público exposto nas VMs.
Para habilitar JIT no Microsoft Defender for Cloud:
# Habilitar Defender for Cloud no Resource Group de workload
az security pricing create \
--name VirtualMachines \
--tier "Standard"
# Habilitar JIT em uma VM específica
az security jit-policy create \
--resource-group rg-blog-castilho-workload-brazilsouth \
--name default \
--virtual-machines '[
{
"id": "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/rg-blog-castilho-workload-brazilsouth/providers/Microsoft.Compute/virtualMachines/vm-drlab-brazilsouth",
"ports": [
{"number": 22, "protocol": "TCP", "allowedSourceAddressPrefix": "*", "maxRequestAccessDuration": "PT3H"},
{"number": 3389, "protocol": "TCP", "allowedSourceAddressPrefix": "*", "maxRequestAccessDuration": "PT3H"}
]
}
]'
Verificar a configuração
Após o deploy, confirme que os recursos de Azure Bastion sem IP público foram criados corretamente nas duas regiões:
# Listar Bastions no Resource Group
az network bastion list \
--resource-group rg-blog-castilho-network-brazilsouth \
--output table
# Exemplo de output esperado:
# Name Location ProvisioningState Sku
# -------------------------------------- ------------ ------------------- --------
# bastion-hub-blog-castilho-brazilsouth brazilsouth Succeeded Standard
# Verificar IP público associado
az network public-ip show \
--resource-group rg-blog-castilho-network-brazilsouth \
--name pip-bastion-hub-blog-castilho-brazilsouth \
--query "{name:name, ip:ipAddress, allocationMethod:publicIPAllocationMethod}" \
--output table
# Exemplo de output esperado:
# Name Ip AllocationMethod
# -------------------------------------------- ------------- ----------------
# pip-bastion-hub-blog-castilho-brazilsouth <IP_PÚBLICO> Static
# Confirmar que nenhuma VM do Spoke tem IP público
az network public-ip list \
--resource-group rg-blog-castilho-workload-brazilsouth \
--output table
# Output deve estar vazio — nenhum IP público nas VMs
# Verificar East US
az network bastion list \
--resource-group rg-blog-castilho-network-eastus \
--output table
# Exemplo de output esperado:
# Name Location ProvisioningState Sku
# --------------------------------- ---------- ------------------- --------
# bastion-hub-blog-castilho-eastus eastus Succeeded Standard
Troubleshooting
| Erro | Causa | Solução |
|---|---|---|
BastionShareableLinkNotEnabled |
Feature não habilitada no SKU | Use SKU Standard e shareable_link_enabled = true |
SubnetSizeNotValid |
AzureBastionSubnet menor que /26 | Subnet precisa ser /26 ou maior — recrie a subnet no Art. 02 |
| Botão “Connect via Bastion” não aparece na VM | Bastion em VNet diferente sem peering | Confirme que o peering Hub-Spoke está ativo e allow_forwarded_traffic = true |
ProvisioningState: Failed |
Timeout ou conflito de recurso | Execute terraform apply novamente — o Bastion leva até 10 min para provisionar |
| Autenticação falha no Bastion | Falta de permissão RBAC na VM | Atribua Virtual Machine Administrator Login ao usuário no escopo da VM |
| Túnel nativo fecha imediatamente | SKU Basic ou tunneling não habilitado | Confirme tunneling_enabled = true e SKU Standard no Terraform |
Limpeza dos recursos
Para destruir os Bastions e IPs públicos sem afetar o restante da Landing Zone, execute terraform destroy em cada pasta:
cd scripts/art-04-bastion/bastion-hub-blog-castilho-brazilsouth
terraform destroy -var subscription_id="$AZURE_SUBSCRIPTION_ID"
cd scripts/art-04-bastion/bastion-hub-blog-castilho-eastus
terraform destroy -var subscription_id="$AZURE_SUBSCRIPTION_ID"
Isso remove apenas o Bastion e o IP público. A AzureBastionSubnet e a VNet Hub continuam existindo, pois pertencem ao estado Terraform do Art. 02.
Próximos passos
Com o Azure Bastion sem IP público configurado nos dois Hubs, o acesso seguro às VMs está resolvido. O próximo artigo aborda o Azure Front Door e Traffic Manager para controlar o tráfego de entrada externo e orquestrar o failover entre regiões — completando a camada de entrada da Landing Zone.
- 📖 Art. 01: Planejamento de DR Azure e Landing Zone
- 📖 Art. 02: Hub-Spoke Landing Zone com Terraform
- 📖 Art. 03: Azure Firewall e NSGs na Landing Zone
- ⚙️ Art. 04 (este): Azure Bastion na Landing Zone sem IP Público
- 🔒 Art. 05: Azure Front Door e Traffic Manager para Failover
- 🔒 Art. 06: DNS Privado Multi-Região no Azure
- 🔒 Art. 07: Azure Site Recovery para VMs Multi-Região
- 🔒 Art. 08: Azure Storage com Geo-Replicação para DR
- 🔒 Art. 09: AKS Multi-Região com Failover no Azure
- 🔒 Art. 10: Velero no AKS para Backup e Restore Cross-Region
- 🔒 Art. 11: Runbooks de Failover no Azure Automation
- 🔒 Art. 12: Simular um DR no Azure sem Impacto em Produção
- 🔒 Art. 13: Monitoramento do DR no Azure com Azure Monitor
Interessado em saber mais sobre artigos relacionados ao Microsoft Azure CLIQUE AQUI
🚀 Vamos nos conectar?
Não perca nenhuma oportunidade! Cadastre-se nas minhas redes e no canal do YouTube para receber conteúdos de TI, Cloud, Azure, Kubernetes e DevOps em primeira mão.
Dica: No Facebook, todos os artigos do blog são publicados automaticamente. Vale a pena curtir!
💬 Dúvidas ou Problemas?
Com o intuito de ajudar a comunidade, caso você tenha dúvidas ou encontre problemas na execução dos comandos deste artigo, deixe um comentário abaixo. Responderei o mais breve possível!
Muito obrigado pela visita e até o próximo post!
Jefferson Castilho Especialista em Cloud & DevOps.Este guia técnico é exclusivo do Blog do Castilho. Explore nossa para mais conteúdos sobre IA e Cloud.


