Skip to content

Commit d7b7cd3

Browse files
Network - 25550 - Inspection of Outbound TLS Traffic is Enabled on Azure Firewall (#854)
* Feature-25550 * Update src/powershell/tests/Test-Assessment.25550.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/powershell/tests/Test-Assessment.25550.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/powershell/tests/Test-Assessment.25550.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Added Connection and access check validation * Fixed policyID format in output * Fixed the Feedback * Fixed the Output for Keyvault URL --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 504065c commit d7b7cd3

2 files changed

Lines changed: 244 additions & 0 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Azure Firewall Premium offers Transport Layer Security (TLS) inspection to decrypt and inspect outbound and east-west TLS traffic, and inbound TLS traffic when used with Azure Application Gateway. TLS inspection is critical for detecting advanced threats that use encrypted channels to evade traditional security controls.
2+
3+
When TLS inspection is enabled, Azure Firewall uses a customer-provided CA certificate stored in Azure Key Vault to decrypt, inspect, and then re-encrypt traffic before forwarding it to its destination. This enables advanced security capabilities such as IDPS and URL filtering to analyze encrypted traffic and identify malicious activity that would otherwise remain hidden.
4+
5+
This check verifies that Azure Firewall Premium has TLS inspection enabled. Without TLS inspection, the firewall cannot inspect encrypted payloads, significantly limiting visibility into threats that leverage TLS to evade detection.
6+
7+
**Remediation action**
8+
9+
- [Azure Firewall Premium features implementation guide](https://learn.microsoft.com/en-us/azure/firewall/premium-features)
10+
- [Deploy and configure Enterprise CA certificates for Azure Firewall](https://learn.microsoft.com/en-us/azure/firewall/premium-deploy-certificates-enterprise-ca)
11+
- [Azure Firewall Premium certificates](https://learn.microsoft.com/en-us/azure/firewall/premium-certificates)
12+
13+
<!--- Results --->
14+
%TestResult%
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
<#
2+
.SYNOPSIS
3+
Inspection of Outbound TLS Traffic is Enabled on Azure Firewall
4+
.DESCRIPTION
5+
Verifies that Azure Firewall Premium has TLS inspection enabled by checking for global certificate authority configuration
6+
and at least one application rule with terminateTLS enabled.
7+
#>
8+
9+
function Test-Assessment-25550 {
10+
[ZtTest(
11+
Category = 'Azure Network Security',
12+
ImplementationCost = 'Low',
13+
MinimumLicense = ('Azure_Firewall_Premium'),
14+
Pillar = 'Network',
15+
RiskLevel = 'High',
16+
SfiPillar = 'Protect networks',
17+
TenantType = ('Workforce', 'External'),
18+
TestId = 25550,
19+
Title = 'Inspection of Outbound TLS Traffic is Enabled on Azure Firewall',
20+
UserImpact = 'Low'
21+
)]
22+
[CmdletBinding()]
23+
param()
24+
25+
Write-PSFMessage '🟦 Start Azure Firewall TLS Inspection evaluation' -Tag Test -Level VeryVerbose
26+
27+
$activity = 'Checking Azure Firewall TLS Inspection configuration'
28+
29+
#region Data Collection
30+
31+
# Check if connected to Azure
32+
Write-ZtProgress -Activity $activity -Status 'Checking Azure connection'
33+
34+
$azContext = Get-AzContext -ErrorAction SilentlyContinue
35+
if (-not $azContext) {
36+
Write-PSFMessage 'Not connected to Azure.' -Level Warning
37+
Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure
38+
return
39+
}
40+
41+
# Check the supported environment, 'AzureCloud' in (Get-AzContext).Environment.Name maps to 'Global' in (Get-MgContext).Environment
42+
Write-ZtProgress -Activity $activity -Status 'Checking Azure environment'
43+
44+
if ($azContext.Environment.Name -ne 'AzureCloud') {
45+
Write-PSFMessage "This test is only applicable to the Global (AzureCloud) environment." -Tag Test -Level VeryVerbose
46+
Add-ZtTestResultDetail -SkippedBecause NotSupported
47+
return
48+
}
49+
50+
Write-ZtProgress -Activity $activity -Status 'Querying Azure subscriptions'
51+
$subscriptions = Get-AzSubscription
52+
$resourceManagementUrl = $azContext.Environment.ResourceManagerUrl
53+
54+
$firewallPoliciesWithTLS = @()
55+
56+
foreach ($subscription in $subscriptions) {
57+
Write-ZtProgress -Activity $activity -Status "Checking subscription: $($subscription.Name)"
58+
59+
# List all firewall policies in subscription (handle possible pagination via nextLink)
60+
$fwPoliciesUri = "$resourceManagementUrl/subscriptions/$($subscription.Id)/providers/Microsoft.Network/firewallPolicies?api-version=2025-03-01"
61+
$fwPolicies = @()
62+
63+
try {
64+
do {
65+
$fwPoliciesResp = Invoke-AzRestMethod -Method GET -Uri $fwPoliciesUri
66+
67+
if ($fwPoliciesResp.StatusCode -eq 403) {
68+
Write-PSFMessage "The signed in user does not have access to query firewall policies in subscription $($subscription.Name)." -Level Verbose
69+
break
70+
}
71+
72+
if ($fwPoliciesResp.StatusCode -ge 400) {
73+
throw "Firewall policies request failed with status code $($fwPoliciesResp.StatusCode)"
74+
}
75+
76+
$fwPoliciesJson = $fwPoliciesResp.Content | ConvertFrom-Json
77+
if ($fwPoliciesJson.value) {
78+
$fwPolicies += $fwPoliciesJson.value
79+
}
80+
$fwPoliciesUri = $fwPoliciesJson.nextLink
81+
} while ($fwPoliciesUri)
82+
}
83+
catch {
84+
Write-PSFMessage "Unable to list firewall policies in subscription $($subscription.Name): $_" -Tag Test -Level Warning
85+
continue
86+
}
87+
88+
# Filter for Premium SKU policies only
89+
$premiumPolicies = $fwPolicies | Where-Object { $_.properties.sku.tier -eq 'Premium' }
90+
91+
foreach ($policy in $premiumPolicies) {
92+
Write-ZtProgress -Activity $activity -Status "Evaluating policy: $($policy.name)"
93+
94+
# Get detailed policy configuration
95+
try {
96+
$policyDetailResp = Invoke-AzRestMethod -Method GET -Uri "$resourceManagementUrl$($policy.id)?api-version=2025-03-01"
97+
$policyDetail = $policyDetailResp.Content | ConvertFrom-Json
98+
}
99+
catch {
100+
Write-PSFMessage "Unable to get details for policy $($policy.name): $_" -Tag Test -Level Warning
101+
continue
102+
}
103+
104+
# Check if global TLS certificate is configured
105+
$tlsGloballyConfigured = $false
106+
$certName = 'N/A'
107+
$certKeyVaultSecretId = 'N/A'
108+
$certKeyVaultSecretIdDisplay = 'N/A'
109+
110+
$certAuth = $policyDetail.properties.transportSecurity.certificateAuthority
111+
if ($certAuth.name -and $certAuth.keyVaultSecretId) {
112+
$tlsGloballyConfigured = $true
113+
$certName = $certAuth.name
114+
$certKeyVaultSecretId = $certAuth.keyVaultSecretId
115+
$certKeyVaultSecretIdDisplay = $certAuth.keyVaultSecretId
116+
}
117+
118+
# Check for application rules with terminateTLS enabled
119+
$tlsEnabledRulesFound = $false
120+
$ruleCollectionGroups = $policyDetail.properties.ruleCollectionGroups
121+
122+
foreach ($rcgRef in $ruleCollectionGroups) {
123+
if ($tlsEnabledRulesFound) { break }
124+
125+
try {
126+
$rcgDetailResp = Invoke-AzRestMethod -Method GET -Uri "$resourceManagementUrl$($rcgRef.id)?api-version=2025-03-01"
127+
$rcgDetail = $rcgDetailResp.Content | ConvertFrom-Json
128+
}
129+
catch {
130+
Write-PSFMessage "Unable to get details for rule collection group $($rcgRef.id): $_" -Tag Test -Level Warning
131+
continue
132+
}
133+
134+
foreach ($ruleCollection in $rcgDetail.properties.ruleCollections) {
135+
if ($tlsEnabledRulesFound) { break }
136+
if ($ruleCollection.ruleCollectionType -ne 'FirewallPolicyFilterRuleCollection') { continue }
137+
138+
foreach ($rule in $ruleCollection.rules) {
139+
if ($rule.ruleType -eq 'ApplicationRule' -and $rule.terminateTLS -eq $true) {
140+
$tlsEnabledRulesFound = $true
141+
break
142+
}
143+
}
144+
}
145+
}
146+
147+
# Parse policy ID to extract components for portal URL
148+
# Format: /subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{provider}/{resourceType}/{resourceName}
149+
$policyIdParts = $policy.id -split '/'
150+
$subscriptionId = $policyIdParts[2]
151+
$resourceGroupName = $policyIdParts[4]
152+
$resourceName = $policyIdParts[-1]
153+
154+
# Create Azure portal URL for the firewall policy
155+
$portalUrl = "https://portal.azure.com/#@/resource$($policy.id)"
156+
157+
# Store results
158+
$firewallPoliciesWithTLS += [PSCustomObject]@{
159+
SubscriptionId = $subscription.Id
160+
SubscriptionName = $subscription.Name
161+
PolicyName = $policy.name
162+
PolicyId = $policy.id
163+
PortalUrl = $portalUrl
164+
TLSGloballyConfigured = if ($tlsGloballyConfigured) { 'Yes' } else { 'No' }
165+
CertificateAuthorityName = $certName
166+
CertificateKeyVaultSecretId = $certKeyVaultSecretId
167+
CertificateKeyVaultSecretIdDisplay = $certKeyVaultSecretIdDisplay
168+
ApplicationRuleWithTLS = if ($tlsEnabledRulesFound) { 'Yes' } else { 'No' }
169+
PassesCriteria = $tlsGloballyConfigured -and $tlsEnabledRulesFound
170+
}
171+
}
172+
}
173+
174+
#endregion Data Collection
175+
176+
#region Assessment Logic
177+
178+
# Determine pass/fail
179+
$passed = $false
180+
$testResultMarkdown = ''
181+
182+
if ($firewallPoliciesWithTLS.Count -eq 0) {
183+
$testResultMarkdown = "❌ No Azure Firewall Premium policies found in any subscription.`n`n"
184+
}
185+
elseif (($firewallPoliciesWithTLS | Where-Object { $_.PassesCriteria }).Count -gt 0) {
186+
$passed = $true
187+
$testResultMarkdown = "✅ TLS inspection is globally configured in the Azure Firewall policy and at least one application rule explicitly enables TLS inspection with `"terminateTLS: true`".`n`n%TestResult%"
188+
}
189+
else {
190+
$testResultMarkdown = "❌ TLS inspection is not enabled. Either the transportSecurity.certificateAuthority is missing in the firewall policy, or TLS inspection is globally configured but no application rule enables TLS inspection (application rules have `"terminateTLS: false`").`n`n%TestResult%"
191+
}
192+
193+
#endregion Assessment Logic
194+
195+
#region Report Generation
196+
$formatTemplate = @'
197+
198+
## Azure Firewall policies TLS inspection status
199+
200+
| Subscription name | Azure Firewall policy name | TLS inspection globally configured | Certificate authority name | Certificate authority Key Vault secret ID | Application rule with TLS inspection enabled |
201+
| :------------- | :------------------------- | :--------------------------------- | :------------------------- | :------------------------------------- | :------------------------------------------- |
202+
{0}
203+
204+
'@
205+
206+
$tableRows = ''
207+
foreach ($policyInfo in $firewallPoliciesWithTLS) {
208+
$policyName = Get-SafeMarkdown -Text $policyInfo.PolicyName
209+
$portalUrl = $policyInfo.PortalUrl
210+
$policyNameWithLink = "[$policyName]($portalUrl)"
211+
$subName = Get-SafeMarkdown -Text $policyInfo.SubscriptionName
212+
$certName = Get-SafeMarkdown -Text $policyInfo.CertificateAuthorityName
213+
$certKeyVault = Get-SafeMarkdown -Text $policyInfo.CertificateKeyVaultSecretIdDisplay
214+
215+
$tableRows += "| $subName | $policyNameWithLink | $($policyInfo.TLSGloballyConfigured) | $certName | $certKeyVault | $($policyInfo.ApplicationRuleWithTLS) |`n"
216+
}
217+
218+
$mdInfo = $formatTemplate -f $tableRows
219+
$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
220+
221+
#endregion Report Generation
222+
223+
$params = @{
224+
TestId = '25550'
225+
Status = $passed
226+
Result = $testResultMarkdown
227+
}
228+
229+
Add-ZtTestResultDetail @params
230+
}

0 commit comments

Comments
 (0)