helderpinto
15 TopicsAzure Identities and Roles Governance Dashboard At Your Fingertips
I am writing about Azure Identities and Roles and how the Azure Optimization Engine can help you gain better visibility about your Azure users and applications as well as the roles they own both in Azure and Microsoft Entra. I built a solution that periodically exports to Log Analytics all Microsoft Entra identities (users, groups, and applications) and the roles they own in Azure and Microsoft Entra. With this information available in an easily queryable repository, we can build an interesting Azure Monitor Workbook21KViews12likes30CommentsAugmenting Azure Advisor Cost Recommendations for Automated Continuous Optimization – Part 1
Here is Helder Pinto, Customer Engineer at Microsoft, presenting the first post of a series dedicated to the implementation of automated Continuous Optimization with Azure Advisor Cost recommendations. This post described how Azure Advisor Cost recommendations can be augmented with a fit score generated with the help of Azure Resource Graph, Automation and Log Analytics.24KViews12likes20CommentsAzure Network Security Hygiene with Traffic Analytics
Sharing some tips about how to leverage NSG Flow Logs and Traffic Analytics to improve your Azure network security hygiene and, at the end, simplify your NSG rules and, more importantly, uncover security vulnerabilities. Custom workbook included.Augmenting Azure Advisor Cost Recommendations for Automated Continuous Optimization – Part 3
This is the third post of a series dedicated to the implementation of automated Continuous Optimization with Azure Advisor Cost recommendations. For a contextualization of the solution here described, please read the introductory post for an overview of the solution and also the second post for the details and deployment of the main solution components.Deploying Microsoft Defender for Servers in Network-Restricted Environments
Microsoft Defender for Servers requires the deployment of several agents to achieve its multiple protection capabilities. As many of our customers run their Windows/Linux server environments without direct Internet outbound connectivity, there is the need for guidance on how to successfully deploy Defender for Servers with such restrictions. This article aims thus to bring additional clarity by summarizing all the considerations that must be taken when deploying each Defender for Servers component in network-restricted environments.Estimating Azure Diagnostics Cost
Azure resource logging (with Azure Diagnostics) is recommended as part of the Operational Excellence and Security pillars of the Well Architected Framework. Furthermore, you’ll also increase your Azure Secure Score, as enabling auditing and logging is one of the assessed controls of your security posture. This blog post helps you estimate the cost of enabling Azure Diagnostics at scale.Augmenting Azure Advisor Cost Recommendations for Automated Continuous Optimization – Part 2
This is the second post of a series dedicated to the implementation of automated Continuous Optimization with Azure Advisor Cost recommendations. For a contextualization of the solution described in this and following posts, please read the introductory post.How to identify Azure resources using default outbound Internet access?
The default outbound access problem [UPDATE, May 28th: There will be no change to existing virtual networks. This means that there will be no change in the operation of existing or new virtual machines in these subnets.] [UPDATE, September 16th: The default outbound access retirement date was postponed to March 2026.] On 31 March 2026, default outbound access connectivity for virtual machines in Azure will be retired (see announcement). After this date, new virtual networks will default to using private subnets, meaning that an explicit outbound method must be enabled in order to reach public endpoints on the Internet and within Microsoft. These are the explicit outbound methods available: Azure NAT Gateway, Azure Load Balancer outbound rules, an Azure Firewall/NVA, or a directly attached Azure public IP address. Existing VMs and virtual networks that use default outbound access will continue to work after this retirement, however, we strongly recommend transitioning to an explicit outbound method so that: Your workloads won’t be affected by public IP address changes. You have greater control over how your VMs connect to the internet. Your VMs use traceable IP resources that you own. Explicit outbound solutions You can find a complete guidance on how default outbound access in Azure works and how to transition to an explicit outbound connectivity method in the VNet IP services documentation and also on this very detailed blog post. The options can be summarized as follows (any option works, but they are ordered by preference): Route the subnet Internet-bound traffic to an Azure Firewall or marketplace NVA, via a custom User-Defined Route. For enterprise-grade customers, this is the recommended approach, as it allows you to centralize egress and increase network security with features like packet filtering, FQDN-based rules, TLS inspection, or IDPS. Associate a NAT Gateway to the subnet of your virtual machines. Consider you need to create one NAT gateway per Virtual Network, because NAT Gateways cannot be reused across multiple Virtual Networks. Associate a Public Standard Load Balancer configured with outbound rules. Associate a Standard Public IP to any of the virtual machine's network interfaces. For HTTP traffic only, forward outbound requests to a centralized HTTP proxy machine, provided this machine has one of the explicit outbound access methods above enabled. Identifying Azure resources using default outbound Internet access Now, you may ask “which of my Azure Virtual Machines or Scale Sets are using default outbound Internet access?” This question is helpful for characterizing your current compute estate, but also to plan the transition to an explicit outbound method, for the reasons outlined above. Luckily, there are some helpers. First, there is an Azure Advisor Operational Excellence recommendation (“Add explicit outbound method to disable default outbound”) that helps you pinpoint network interfaces that might be in this situation. However, this recommendation does not currently cover Virtual Machine Scale Sets or machines behind a Public Basic Load Balancer. The second option is to use Azure Resource Graph and query the Azure control plane for resources in this situation. The two queries below address Virtual Machine and Virtual Machine Scale Set use cases. If there are resources with explicit outbound access via a Public Basic Load Balancer, those are also flagged, because Basic Load Balancers are also on the retirement path on the same date. If you already have Azure Firewalls or NVAs to which you are routing egress traffic, don’t forget to replace the <firewall private IP> placeholders (line 14 of each query) by the private IP address of the firewall(s); otherwise, leave the query as is. NOTE: the query below does not consider Azure Virtual WAN scenarios where Virtual Hubs have route tables forcing Internet-bound traffic to flow into an NVA, as Virtual Hubs route tables are not exposed yet in Azure Resource Graph. Virtual Machines resources | where type =~ 'microsoft.network/virtualnetworks' | mvexpand subnet = properties.subnets | project vnetName=tolower(name), resourceGroup, subscriptionId, location, subnetName = tolower(subnet.name), subnetId=tolower(subnet.id), routeTable = tolower(subnet.properties.routeTable.id), hasNatGW = iif(isnotempty(subnet.properties.natGateway.id), true, false) | join kind=inner ( resourcecontainers | where type =~ 'microsoft.resources/subscriptions' | project subscriptionId, subscriptionName = name) on subscriptionId | project-away subscriptionId, subscriptionId1 | order by subnetId | join kind=leftouter ( resources | where type == 'microsoft.network/routetables' | project routeTable=tolower(id), routeTableProperties=properties ) on routeTable | mv-expand route = routeTableProperties.routes | project-away routeTable1, routeTableProperties | extend routesToFW = iif(route.properties.addressPrefix == '0.0.0.0/0' and route.properties.nextHopType == 'VirtualAppliance' and route.properties.nextHopIpAddress in ('<firewall private IP>'), true, false) | extend routeName = tolower(route.name) | project-away route | join kind=leftouter ( resources | where type == 'microsoft.network/networkinterfaces' | extend vmId = tolower(properties.virtualMachine.id) | where isnotempty(vmId) | mv-expand ipConfiguration = properties.ipConfigurations | mv-expand backendPool = ipConfiguration.properties.loadBalancerBackendAddressPools | extend loadBalancerId = tolower(substring(backendPool.id, 0, indexof(backendPool.id, '/backendAddressPools'))) | join kind=leftouter ( resources | where type == 'microsoft.network/loadbalancers' | mv-expand frontEndIpConfiguration = properties.frontendIPConfigurations | extend lbType = iif(isnotempty(frontEndIpConfiguration.properties.publicIPAddress.id), 'Public', 'Internal') | mv-expand loadBalancingRule = properties.loadBalancingRules | extend lbOutboundSnat = iif(lbType =='Public', iif(sku.name == 'Standard', not(tobool(loadBalancingRule.properties.disableOutboundSnat)), true), false) | project loadBalancerId=tolower(id), lbSku=tostring(sku.name), lbType, lbOutboundSnat | union ( resources | where type == 'microsoft.network/loadbalancers' | mv-expand frontEndIpConfiguration = properties.frontendIPConfigurations | extend lbType = iif(isnotempty(frontEndIpConfiguration.properties.publicIPAddress.id), 'Public', 'Internal') | mv-expand outboundRule = properties.outboundRules | extend lbOutboundSnat = iif(isnotempty(outboundRule) or (sku.name == 'Basic' and lbType == 'Public'), true, false) | project loadBalancerId=tolower(id), lbSku=tostring(sku.name), lbType, lbOutboundSnat ) | summarize make_set(lbOutboundSnat) by loadBalancerId, lbSku, lbType | extend lbOutboundSnat = iif(array_length(set_lbOutboundSnat) == 1, set_lbOutboundSnat[0], true) ) on loadBalancerId | project nicId=tolower(id), vmId, subnetId=tolower(ipConfiguration.properties.subnet.id), hasPublicIP=iif(isnotempty(ipConfiguration.properties.publicIPAddress.id), true, false), lbOutboundSnat, lbSku ) on subnetId | where isnotempty(vmId) | extend lbOutboundSnat = iif(isnotempty(lbOutboundSnat), lbOutboundSnat, false) | extend hasPublicIP=iif(isnotempty(hasPublicIP), hasPublicIP, false) | summarize hasNatGW=make_set(hasNatGW), routesToFW=make_set(routesToFW), hasPublicIP=make_set(hasPublicIP), lbOutboundSnat=make_set(lbOutboundSnat), lbSku=make_set(lbSku) by vnetName, resourceGroup, subscriptionName, location, vmId | extend hasNatGW = iif(array_length(hasNatGW) == 1, hasNatGW[0], true) | extend routesToFW = iif(array_length(routesToFW) == 1, routesToFW[0], true) | extend hasPublicIP = iif(array_length(hasPublicIP) == 1, hasPublicIP[0], true) | extend lbOutboundSnat = iif(array_length(lbOutboundSnat) == 1, lbOutboundSnat[0], true) | extend lbSku = tostring(lbSku[0]) | summarize by vmId, vmName=tostring(split(vmId,'/')[-1]), resourceGroup=tostring(split(vmId,'/')[4]), subscriptionName, location, vnetName, routesToFW, hasNatGW, lbOutboundSnat, lbSku, hasPublicIP //| summarize vmCount=dcount(vmId) by routesToFW, hasNatGW, lbOutboundSnat, lbSku, hasPublicIP //uncomment this line and comment the last one to get only stats | where not(hasNatGW) and not(routesToFW) and not(hasPublicIP) and (not(lbOutboundSnat) or lbSku == 'Basic') Virtual Machine Scale Sets resources | where type =~ 'microsoft.network/virtualnetworks' | mvexpand subnet = properties.subnets | project vnetName=tolower(name), resourceGroup, subscriptionId, location, subnetName = tolower(subnet.name), subnetId=tolower(subnet.id), routeTable = tolower(subnet.properties.routeTable.id), hasNatGW = iif(isnotempty(subnet.properties.natGateway.id), true, false) | join kind=inner ( resourcecontainers | where type =~ 'microsoft.resources/subscriptions' | project subscriptionId, subscriptionName = name) on subscriptionId | project-away subscriptionId, subscriptionId1 | order by subnetId | join kind=leftouter ( resources | where type == 'microsoft.network/routetables' | project routeTable=tolower(id), routeTableProperties=properties ) on routeTable | mv-expand route = routeTableProperties.routes | project-away routeTable1, routeTableProperties | extend routesToFW = iif(route.properties.addressPrefix == '0.0.0.0/0' and route.properties.nextHopType == 'VirtualAppliance' and route.properties.nextHopIpAddress in ('<firewall private IP>'), true, false) | extend routeName = tolower(route.name) | project-away route | join kind=leftouter ( resources | where type == 'microsoft.compute/virtualmachinescalesets' and properties.orchestrationMode == 'Uniform' | mv-expand nicConfiguration = properties.virtualMachineProfile.networkProfile.networkInterfaceConfigurations | mv-expand ipConfiguration = nicConfiguration.properties.ipConfigurations | mv-expand backendPool = ipConfiguration.properties.loadBalancerBackendAddressPools | extend loadBalancerId = tolower(substring(backendPool.id, 0, indexof(backendPool.id, '/backendAddressPools'))) | join kind=leftouter ( resources | where type == 'microsoft.network/loadbalancers' | mv-expand frontEndIpConfiguration = properties.frontendIPConfigurations | extend lbType = iif(isnotempty(frontEndIpConfiguration.properties.publicIPAddress.id), 'Public', 'Internal') | mv-expand loadBalancingRule = properties.loadBalancingRules | extend lbOutboundSnat = iif(lbType =='Public', iif(sku.name == 'Standard', not(tobool(loadBalancingRule.properties.disableOutboundSnat)), true), false) | project loadBalancerId=tolower(id), lbSku=tostring(sku.name), lbType, lbOutboundSnat | union ( resources | where type == 'microsoft.network/loadbalancers' | mv-expand frontEndIpConfiguration = properties.frontendIPConfigurations | extend lbType = iif(isnotempty(frontEndIpConfiguration.properties.publicIPAddress.id), 'Public', 'Internal') | mv-expand outboundRule = properties.outboundRules | extend lbOutboundSnat = iif(isnotempty(outboundRule) or (sku.name == 'Basic' and lbType == 'Public'), true, false) | project loadBalancerId=tolower(id), lbSku=tostring(sku.name), lbType, lbOutboundSnat ) | summarize make_set(lbOutboundSnat) by loadBalancerId, lbSku, lbType | extend lbOutboundSnat = iif(array_length(set_lbOutboundSnat) == 1, set_lbOutboundSnat[0], true) ) on loadBalancerId | project vmssId=tolower(id), subnetId=tolower(ipConfiguration.properties.subnet.id), hasPublicIP=iif(isnotempty(ipConfiguration.properties.publicIPAddressConfiguration), true, false), lbOutboundSnat, lbSku ) on subnetId | where isnotempty(vmssId) | extend lbOutboundSnat = iif(isnotempty(lbOutboundSnat), lbOutboundSnat, false) | extend hasPublicIP=iif(isnotempty(hasPublicIP), hasPublicIP, false) | summarize hasNatGW=make_set(hasNatGW), routesToFW=make_set(routesToFW), hasPublicIP=make_set(hasPublicIP), lbOutboundSnat=make_set(lbOutboundSnat), lbSku=make_set(lbSku) by vnetName, resourceGroup, subscriptionName, location, vmssId | extend hasNatGW = iif(array_length(hasNatGW) == 1, hasNatGW[0], true) | extend routesToFW = iif(array_length(routesToFW) == 1, routesToFW[0], true) | extend hasPublicIP = iif(array_length(hasPublicIP) == 1, hasPublicIP[0], true) | extend lbOutboundSnat = iif(array_length(lbOutboundSnat) == 1, lbOutboundSnat[0], true) | extend lbSku = tostring(lbSku[0]) | summarize by vmssId, vmssName=tostring(split(vmssId,'/')[-1]), resourceGroup=tostring(split(vmssId,'/')[4]), subscriptionName, location, vnetName, routesToFW, hasNatGW, lbOutboundSnat, lbSku, hasPublicIP //| summarize vmssCount=dcount(vmssId) by routesToFW, hasNatGW, lbOutboundSnat, lbSku, hasPublicIP //uncomment this line and comment the last one to get only stats | where not(hasNatGW) and not(routesToFW) and not(hasPublicIP) and (not(lbOutboundSnat) or lbSku == 'Basic') Acknowledgments Special thanks to my colleagues Pedro Pereira and Fernando Loureiro for having triggered the idea for this blog post.Get Azure Reservations and Savings Plans Insights with the Azure Optimization Engine
When adopting Azure commitments, customers face typically two types of challenges: 1) Estimating the quantities or amount to commit for – e.g., how many VM Reservations of a given size and region, what Saving Plan hourly commitment, and for how long? and 2) Reviewing how effective and efficient commitments have been – e.g., which resources have been leveraging a specific commitment, is a commitment fully consumed, how many savings have been achieved over a period? The Azure Optimization Engine (AOE) provides you with reporting that complements Azure-native tooling for your Azure commitment purchase decision and review processes. This article covers the several AOE Workbooks available for this purpose.