Skip to content

Commit 6018e00

Browse files
committed
saving masking helper.
1 parent dccef8f commit 6018e00

1 file changed

Lines changed: 77 additions & 6 deletions

File tree

tests/integration/all-resource-types/MaskingHelpers.psm1

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,69 @@
33
#
44
# Import-Module (Join-Path $PSScriptRoot 'MaskingHelpers.psm1') -Force
55
#
6-
# All exported functions use approved PowerShell verbs.
76

87
# Module-scoped flag. Flip to $false locally if you need to see raw values
98
# while debugging — never commit that change.
109
$script:EnableMasking = $true
1110

11+
# Built-in regex redactions applied by Protect-LogLine on every line, in
12+
# addition to caller-supplied exact-match Replacements. These cover sensitive
13+
# values that the caller cannot enumerate up front (ARM async-operation
14+
# signing material, request IDs, etc.).
15+
#
16+
# IMPORTANT: keep these patterns conservative. We deliberately do NOT mask
17+
# every GUID, because well-known role-definition IDs and template hashes are
18+
# legitimately public and useful in debugging. Anchor patterns to the path
19+
# segment, header name, or query-parameter context that makes the value
20+
# sensitive.
21+
#
22+
# References:
23+
# - ARM async operations contract (see `Azure-AsyncOperation` /
24+
# `operationStatuses` URL shape with t/c/s/h query parameters):
25+
# https://learn.microsoft.com/azure/azure-resource-manager/management/async-operations
26+
# - Azure correlation / request IDs (`x-ms-correlation-request-id`,
27+
# `x-ms-request-id`, `x-ms-client-request-id`):
28+
# https://learn.microsoft.com/azure/azure-resource-manager/management/request-limits-and-throttling
29+
$script:BuiltinRedactions = @(
30+
# ARM async-operation signing material: t= (timestamp), c= (cert),
31+
# s= (signature), h= (hash) on operationStatuses / operationResults URLs.
32+
# These query parameters are effectively a bearer credential for polling
33+
# the long-running operation result and MUST NOT appear in logs.
34+
@{ Pattern = '([?&])(t|c|s|h)=[^&''"\s]+'
35+
Replacement = '$1$2=<REDACTED:arm-async>' }
36+
37+
# Azure subscription IDs embedded in ARM resource paths.
38+
@{ Pattern = '/subscriptions/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'
39+
Replacement = '/subscriptions/<SUBSCRIPTION_ID>' }
40+
41+
# ARM long-running operation IDs (numeric or alphanumeric, 10+ chars)
42+
# following /operationStatuses/ or /operationResults/.
43+
@{ Pattern = '/(operationStatuses|operationResults)/[A-Za-z0-9._-]{10,}'
44+
Replacement = '/$1/<OPERATION_ID>' }
45+
46+
# x-ms-correlation-request-id, x-ms-request-id, x-ms-client-request-id
47+
# header values. Match Python-repr style (`'x-ms-request-id': 'guid'`),
48+
# HTTP-header style (`x-ms-request-id: guid`), and JSON style.
49+
@{ Pattern = "(?i)(['""]?x-ms-(?:correlation-)?(?:request|client-request)-id['""]?\s*[:=]\s*['""]?)[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
50+
Replacement = '$1<REDACTED:request-id>' }
51+
52+
# x-ms-routing-request-id: '<REGION>:<TIMESTAMP>:<guid>'. Mask the whole
53+
# value because all three components (region, timestamp, GUID) leak
54+
# tenant geography / activity timing.
55+
@{ Pattern = "(?i)(['""]?x-ms-routing-request-id['""]?\s*[:=]\s*['""]?)[A-Z0-9]+:\d{8}T\d{6}Z:[0-9a-fA-F-]{36}"
56+
Replacement = '$1<REDACTED:routing-request-id>' }
57+
58+
# Authorization: Bearer <token> — defense in depth. We do not expect
59+
# bearer tokens to appear because `az --debug` redacts them by default,
60+
# but if Azure CLI ever changes that, this catches them.
61+
@{ Pattern = "(?i)(authorization[:\s=]+bearer\s+)[A-Za-z0-9._\-+/=]+"
62+
Replacement = '$1<REDACTED:bearer>' }
63+
64+
# JWT-shaped tokens (eyJ<base64url>.<base64url>.<base64url>).
65+
@{ Pattern = '\beyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]{8,}'
66+
Replacement = '<REDACTED:jwt>' }
67+
)
68+
1269
function Protect-Identifier {
1370
param(
1471
[string]$Value,
@@ -50,17 +107,31 @@ function Protect-LogLine {
50107
[hashtable]$Replacements
51108
)
52109

53-
if (-not $script:EnableMasking -or [string]::IsNullOrEmpty($Line) -or -not $Replacements) {
110+
if (-not $script:EnableMasking -or [string]::IsNullOrEmpty($Line)) {
54111
return $Line
55112
}
56113

57114
$protectedLine = $Line
58-
foreach ($entry in $Replacements.GetEnumerator()) {
59-
if ([string]::IsNullOrEmpty($entry.Key) -or [string]::IsNullOrEmpty($entry.Value)) {
60-
continue
115+
116+
# Pass 1: caller-supplied exact-string replacements (sub id, RG name, ...).
117+
if ($Replacements) {
118+
foreach ($entry in $Replacements.GetEnumerator()) {
119+
if ([string]::IsNullOrEmpty($entry.Key) -or [string]::IsNullOrEmpty($entry.Value)) {
120+
continue
121+
}
122+
123+
$protectedLine = $protectedLine.Replace($entry.Key, $entry.Value)
61124
}
125+
}
62126

63-
$protectedLine = $protectedLine.Replace($entry.Key, $entry.Value)
127+
# Pass 2: built-in regex redactions for well-known secret shapes that
128+
# callers cannot enumerate up front (ARM signing material, request IDs,
129+
# operation IDs, bearer tokens). Always applied.
130+
foreach ($rule in $script:BuiltinRedactions) {
131+
$protectedLine = [System.Text.RegularExpressions.Regex]::Replace(
132+
$protectedLine,
133+
$rule.Pattern,
134+
$rule.Replacement)
64135
}
65136

66137
return $protectedLine

0 commit comments

Comments
 (0)