Geçen kasım Ankara’da bir bankanın iç denetiminde oturuyorduk. ISO 27001 dış denetçisi tabloyu masaya koydu: “32 üretim VM’inden 19’unun NSG’sinde 0.0.0.0/0 → 3389 kuralı var. Birinin sahibi belli değil. Açıklayın.” Ekip üç gün boyunca kim ne zaman hangi VM’e RDP ile bağlandı sorusunu yanıtlayamadı çünkü loglar dağınıktı. O denetimden çıkarken aldıkları karar şuydu: tüm production VM’lerden public IP kalkacak, erişim sadece Azure Bastion üzerinden olacak. Bu yazıda o projeden ve sonraki dört müşteriden öğrendiklerimi anlatıyorum, çünkü Bastion’u “Microsoft yönetiyor, kuruyorsun, çalışıyor” sananlar fatura geldiğinde veya ilk session recording isteğinde sürpriz yaşıyor.
Bastion Aslında Ne Çözüyor (ve Ne Çözmüyor)
Azure Bastion, sanal ağınızda kendine ait bir alt ağda (AzureBastionSubnet) konuşlanan, tarayıcı tabanlı ya da yerel istemci üzerinden RDP/SSH bağlantısı kurmanızı sağlayan tam yönetilen bir PaaS hizmeti. Trafik 443 üzerinden TLS ile gelir, Bastion içeride ilgili VM’in özel IP’sine bağlanır. Public IP gerekmez, NSG’de internete açık 3389/22 kuralı gerekmez, jump box’ı sen yamalama derdinden kurtulursun.
Çözmediği şeyler de var, baştan yazayım: Bastion, Azure dışındaki bir veri merkezindeki sunucuya bağlanmaz. Cross-tenant erişimi yok. Linux’ta SSH dosya transferi tek yönlü ve sınırlı (Standard SKU + native client şart). Ve en önemlisi: Bastion oturum kayıtlarını kendisi tutmaz. Kim kime bağlandı bilgisi Activity Log + AAD sign-in log + (eğer kurarsan) Bastion diagnostic log’larında dağınık durur. Compliance ekibinin “session recording” istediği yerde ya üçüncü parti ürün eklersin ya da Premium SKU’ya geçersin.
AzureBastionSubnet Boyutu: /27 mi /26 mı?
Microsoft minimum /26 diyor (64 IP). Birçok ekip “biz 5 kişiyiz, /29 yeter” der ve deploy başarısız olur. Sebebi şu: Bastion ölçeklenebilen bir hizmet, yatay ölçeklendirme için kendine IP havuzu lazım. Pratikte:
- Basic SKU: minimum
/26ama ölçek yok (2 instance fixed). Küçük ekipler için yeter. - Standard SKU:
/26yine minimum ama 2-50 instance arası ölçeklenebiliyor. Eğer ileride Premium’a geçeceksen veya 100+ eşzamanlı oturum bekliyorsan/25tahsis et, sonra subnet büyütemezsin. - Premium SKU: 2026 başında çıktı. Session recording, private-only deployment ve özel DNS gerekiyorsa bunu seç. Subnet ihtiyacı yine
/26.
Subnet’i baştan büyük tahsis etmenin maliyeti sıfır. Sonradan büyütmenin maliyeti ise: Bastion sil, subnet sil, subnet yeniden oluştur, Bastion yeniden deploy. Üretim ortamında en az iki saat downtime. Ben artık her yeni hub VNet’te /25 ile başlıyorum.
# Hub VNet içine AzureBastionSubnet ekle (Bicep yerine az cli örneği)
az network vnet subnet create
--resource-group rg-network-prod-trwest
--vnet-name vnet-hub-prod
--name AzureBastionSubnet
--address-prefixes 10.10.255.0/25
# Standard SKU Bastion deploy
az network bastion create
--name bastion-hub-prod
--resource-group rg-network-prod-trwest
--vnet-name vnet-hub-prod
--public-ip-address pip-bastion-hub-prod
--sku Standard
--scale-units 4
--enable-tunneling true
--enable-ip-connect true
--location turkeywest
Burada --enable-tunneling true kritik: Bunu açmazsan native RDP/SSH istemcisinden bağlanamazsın, sadece tarayıcı çalışır. scale-units default 2; biz bankada 4’ten başladık çünkü iş saatlerinde 60-70 eşzamanlı oturum oluyordu.
SKU Kararı: Basic / Standard / Premium
Sahada gördüğüm pratik kural:
| İhtiyaç | SKU | Aylık tahmini (West Europe) |
|---|---|---|
| 5-15 kullanıcı, sadece tarayıcıdan RDP | Basic | ~140 USD |
| Native client, file copy, IP-based connect, AAD auth | Standard (2 unit) | ~220 USD |
| Session recording, private-only, shareable link | Premium | ~600 USD |
Türkiye West’te birim fiyat bir miktar daha düşük ama oran benzer. Yukarıdaki bankada Standard + 4 scale unit ile aylık ~520 USD ödüyoruz, üzerine outbound data transfer geliyor. Ekip “neden bu kadar pahalı, jump box 80 USD’ydi” dediğinde gösterdiğim hesap şu: Eski jump box’ın aylık yama emek maliyeti 8 saat × 600 TL/saat = 4.800 TL, denetim hazırlık emeği 16 saat = 9.600 TL, ek olarak bir kez ihlal yaşamış olsalardı kayıp tahmini 250.000 TL+. 520 USD ucuz kaldı.
Just-in-Time + Bastion Birleşimi
Bastion tek başına “kim ne zaman bağlandı” sorusunu kısmen yanıtlar. Ama “neden bağlandı, onayı kim verdi” sorusuna yanıt vermez. Defender for Cloud’un Just-in-Time (JIT) VM Access özelliğini Bastion ile birlikte kullanmak burada işi tamamlıyor. Akış şöyle oluyor:
- Operatör Azure Portal’da VM’in “Connect → Bastion” adımına gider.
- JIT açıksa portal “request access” der; operatör süre (30 dk – 3 saat), kaynak adres aralığı (auto: my IP), ve gerekçe yazar.
- Eğer policy approver isterse Logic Apps üzerinden Teams’e onay kartı gider; manager onaylar.
- NSG kuralı tam olarak o portu, o IP’den, o süreyle açar; süre dolunca otomatik kapanır.
- Operatör Bastion oturumunu açar, işini yapar.
Bu akış kurulduktan sonra denetim tablosu temizlendi. Activity Log → JIT request → kim onayladı → kim bağlandı zinciri tutarlı oldu.
Native Client ve File Copy
Tarayıcı içi RDP güzel, ama 3 GB’lık dump dosyasını veritabanı sunucusundan çekmen gerektiğinde yetmez. Standard SKU + --enable-tunneling ile native istemciye geçişi şöyle yapıyoruz:
# Operatörün kendi makinesinden
az login
az account set --subscription "Production"
# RDP için (Windows VM)
az network bastion rdp
--name bastion-hub-prod
--resource-group rg-network-prod-trwest
--target-resource-id /subscriptions/.../virtualMachines/sql-prod-01
# SSH için (Linux VM)
az network bastion ssh
--name bastion-hub-prod
--resource-group rg-network-prod-trwest
--target-resource-id /subscriptions/.../virtualMachines/app-prod-04
--auth-type AAD
--auth-type AAD ayrı bir kazanç: Linux sunucularda lokal user/password yönetimini bırakıyorsun, Azure AD ile SSH oluyor. Operatör işten çıktığında AAD’den disable et, bütün sunuculara erişimi otomatik biter. Bunun için VM’de aadsshlogin extension’ı yüklü olmalı ve kullanıcıya “Virtual Machine Administrator Login” veya “Virtual Machine User Login” rolü atanmalı.
Loglama: Diagnostic Settings’i Açmadıysan Bastion’un Yarısı Çalışmıyor Gibi
Default kurulumda Bastion log atmaz. Audit’te “kim kime bağlandı, hangi protokolden, ne zaman” raporlaması istendiğinde Diagnostic Settings’i Log Analytics workspace’e yönlendirmek şart:
az monitor diagnostic-settings create
--name bastion-diag
--resource $(az network bastion show -n bastion-hub-prod -g rg-network-prod-trwest --query id -o tsv)
--workspace law-security-prod
--logs '[{"category":"BastionAuditLogs","enabled":true,"retentionPolicy":{"days":90,"enabled":true}}]'
Sonra KQL ile sorgu:
AzureDiagnostics
| where Category == "BastionAuditLogs"
| where TimeGenerated > ago(7d)
| project TimeGenerated, userName_s, targetVMIPAddress_s, protocol_s, sourceIPAddress_s
| order by TimeGenerated desc
Bunu Workbook’a çevirip compliance ekibine “her ay 1’inde otomatik mail gelir” dedik, denetimde bir daha aynı soruyla karşılaşmadık.
Hub-Spoke Mimaride Tek Bastion ile Tüm VNet’lere Erişim
Yaygın hata: Her spoke VNet’e ayrı Bastion deploy etmek. Yapma. Hub VNet’e bir Bastion kur, spoke’ları hub ile peer et, spoke peering’inde “Allow gateway transit” ve “Use remote gateways” ayarlarını doğru ver, tek Bastion’dan onlarca VNet’in VM’ine bağlan.
Önemli: Spoke VNet ile peering’de “Allow forwarded traffic” açık olmalı. Bu kapalıysa Bastion bağlantı dener, “host unreachable” verir, sebebi anlamak yarım gün sürer (sahada bana üç defa oldu, hâlâ ezbere bilmiyorum).
Sahada Düşülen Üç Tuzak
- NSG’yi AzureBastionSubnet’e takmamak: Bastion’un kendi alt ağına NSG koyabilirsin, ama doğru kuralları yazman gerekir. Yanlış NSG = Bastion deploy olur ama kimse bağlanamaz. Microsoft dokümanındaki NSG template’i tam olarak uygulanmalı (TCP 443 inbound from Internet, TCP 8080/5701 inbound from VirtualNetwork, vb).
- Boş kalan Bastion’ın faturası: Bastion saatlik ücretlendirilir, kullansan da kullanmasan da. Dev/test ortamında “ihtiyacımız olunca açarız” mantığıyla aylık 200 USD’yi yastık altı yapmamak için auto-stop uygulanmaz, bunun yerine tek bir hub Bastion’ı non-prod ortamlarda da paylaş. Veya Basic SKU ile dev için kur.
- Bastion’a doğru IP-based connect denenmesi: Standard SKU’da “IP-based connect” özelliği var; on-prem’deki bir sunucuya da Bastion üzerinden bağlanırım sananlar oluyor. Hayır. Bastion sadece aynı veya peered VNet’teki IP’lere gider. On-prem için yine ExpressRoute/VPN + jump VM gerekir.
Kapanış: Bastion Mimari Bir Karar, Tekil Servis Değil
Bastion’u “RDP’yi tarayıcıdan açan şey” diye anlatınca yöneticiler “süslü VPN” sanıyor ve bütçe için zorlanıyor. Doğru çerçeveleme şu: Bastion, üretim VM’lerinden public IP kaldırma kararının operasyonel maliyetini taşıyabilir hâle getiren bileşen. Tek başına alındığında pahalı, JIT + Diagnostic Logs + AAD SSH + Hub-Spoke topology ile birlikte kurgulandığında karşılığı ciddi: hem denetim hem güvenlik hem de operasyon basitliği. Sahada artık yeni bir Azure landing zone kurarken Bastion’u “ileride bakarız” listesinden çıkardım, ilk gün hub’a koyuyorum.
CloudSpark Cloud (Virtuozzo Hybrid Infrastructure tabanlı) tarafında benzer sorunu OpenStack üzerinden çözüyoruz: Floating IP’siz VM + bir VPN gateway veya bastion-host imajı ile yönetim. Mantık aynı: production yüzeyini internete açma, kontrollü tek kapıdan geç, logla. Hangi platformda olursan ol, “RDP/SSH’a public IP’den girilen” mimari 2026’da artık savunulamaz.



