Framework/Helpers/ComplianceReportHelper.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
Set-StrictMode -Version Latest

class ComplianceReportHelper: ComplianceBase
{
    hidden [string] $ScanSource
    hidden [System.Version] $ScannerVersion
    hidden [string] $ScanKind 
    
    ComplianceReportHelper([SubscriptionContext] $subscriptionContext,[System.Version] $ScannerVersion):
    Base([SubscriptionContext] $subscriptionContext) 
    {
        $this.ScanSource = [RemoteReportHelper]::GetScanSource();
        $this.ScannerVersion = $ScannerVersion
        $this.ScanKind = [ServiceScanKind]::Partial;
    } 
    
    hidden [ComplianceStateTableEntity[]] GetSubscriptionComplianceReport()
    {
        return $this.GetSubscriptionComplianceReport($null,$null);
    }

    hidden [ComplianceStateTableEntity[]] GetSubscriptionComplianceReport([string[]] $PartitionKeys)
    {        
        [ComplianceStateTableEntity[]] $finalResults = @();
        if($PartitionKeys.Length -gt 0)
        {
            $limit = 15;
            if($PartitionKeys.Length -le $limit)
            {
                $queryStringParam = $this.ConstructPartitionKeysFilterQueryString($PartitionKeys);
                return $this.GetSubscriptionComplianceReport($queryStringParam, $null);
            }
            else {
                $counter = 1;
                $subPartitionKeys = @();
                $totalCount = $PartitionKeys.Length;
                foreach($partitionKey in $PartitionKeys)
                {
                    $subPartitionKeys += $partitionKey;
                    if($counter % $limit -eq 0 -or $totalCount -eq $counter)
                    {
                        $queryStringParam = $this.ConstructPartitionKeysFilterQueryString($subPartitionKeys);
                        $finalResults += $this.GetSubscriptionComplianceReport($queryStringParam, $null);
                        $subPartitionKeys = @();                        
                    }
                    $counter += 1;
                }                        
            }
        }        
        return $finalResults;                
    }

    hidden [ComplianceStateTableEntity[]] GetSubscriptionComplianceReport($currentScanResults,$selectColumns)
    {
        $filterStringParams = "";
        $selectStringParams = "";
        $partitionKeys = @();
        if(($currentScanResults | Measure-Object).Count -gt 0)
        {
            $currentScanResults | ForEach-Object {
                $currentScanResult = $_;
                $resourceId = $currentScanResult.SubscriptionContext.Scope;
                if($currentScanResult.IsResource())
                {
                    $resourceId = $currentScanResult.ResourceContext.ResourceId;
                }
                $controlsToProcess = @();
                if(($currentScanResult.ControlResults | Measure-Object).Count -gt 0)
                {    
                    $controlsToProcess += $currentScanResult.ControlResults;
                }
                $controlsToProcess | ForEach-Object {
                    $cScanResult = $_;                                                
                    $currentResultHashId_p = [Helpers]::ComputeHash($resourceId.ToLower());
                    $partitionKeys += $currentResultHashId_p;
                }
            }
            $partitionKeys = $partitionKeys | Select -Unique
            $filterStringParams = $this.ConstructPartitionKeysFilterQueryString($partitionKeys);            
        }
        if(($selectColumns | Measure-Object).Count -gt 0)
        {
            $selectStringParams =[String]::Join(",",$selectColumns)
        }
        return $this.GetSubscriptionComplianceReport($filterStringParams,$selectStringParams);
    }
    hidden [ComplianceStateTableEntity[]] GetSubscriptionComplianceReport([string] $filterStringParams, [string] $selectStringParams)
    {
        [ComplianceStateTableEntity[]] $complianceData = @()
        try
        {            
            $storageInstance = $this.azskStorageInstance;
            $TableName = $this.ComplianceTableName
            $AccountName = $storageInstance.StorageAccountName
            $AccessKey = $storageInstance.AccessKey 
            $queryStringParams = "?`$filter=IsActive%20eq%20true"
            if(-not [string]::IsNullOrWhiteSpace($filterStringParams))
            {
                $queryStringParams += "%20and%20(" + $filterStringParams + ")"
            }
            if(-not [string]::IsNullOrWhiteSpace($selectStringParams))
            {
                $queryStringParams += "&`$select=" + $selectStringParams;
            }

            $Uri="https://$AccountName.table.core.windows.net/$TableName()$queryStringParams"
            $Verb = "GET"
            $ContentMD5 = ""
            $ContentType = ""
            $Date = [DateTime]::UtcNow.ToString('r')
            $CanonicalizedResource = "/$AccountName/$TableName()"
            $SigningParts=@($Verb,$ContentMD5,$ContentType,$Date,$CanonicalizedResource)
            $StringToSign = [String]::Join("`n",$SigningParts)
            $sharedKey = [Helpers]::CreateStorageAccountSharedKey($StringToSign,$AccountName,$AccessKey)

            $xmsdate = $Date
            $headers = @{"Accept"="application/json";"x-ms-date"=$xmsdate;"Authorization"="SharedKey $sharedKey";"x-ms-version"="2018-03-28"}
            $tempComplianceData  = ([WebRequestHelper]::InvokeGetWebRequest($Uri,$headers)) 
            $newEntity = [ComplianceStateTableEntity]::new();
            $props = @();
            $item = $null;
            if(($tempComplianceData | Measure-Object).Count -gt 0)
            {
                $item = $tempComplianceData[0];
            }
            if($null -ne $item)
            {
                foreach($Property in $newEntity | Get-Member -type NoteProperty, Property)
                {
                    if([Helpers]::CheckMember($item, $Property.Name, $false))
                    {
                        $props += $Property.Name
                    }
                }
                if(($props | Measure-Object).Count -gt 0)
                {
                    foreach($item in $tempComplianceData)
                    {
                        $newEntity = [ComplianceStateTableEntity]::new()
                        foreach($Property in $props){
                            $newEntity.$($Property) = $item.$($Property)
                        }
                        if(-not [string]::IsNullOrWhiteSpace($newEntity.PartitionKey) -and -not [string]::IsNullOrWhiteSpace($newEntity.RowKey))
                        {
                            $complianceData+=$newEntity
                        }                        
                    }
                }    
            }            
        }
        catch
        {
            #Write-Host $_;
            return $null;
        }
        return $complianceData;        
    }             
        
    hidden [ComplianceStateTableEntity] ConvertScanResultToSnapshotResult($currentSVTResult, $persistedSVTResult, $svtEventContext, $partitionKey, $rowKey, $resourceId)
    {
        [ComplianceStateTableEntity] $scanResult = $null;
        if($null -ne $persistedSVTResult)
        {
            $scanResult = $persistedSVTResult;
        }
        $isLegitimateResult = ($currentSVTResult.CurrentSessionContext.IsLatestPSModule -and $currentSVTResult.CurrentSessionContext.Permissions.HasRequiredAccess -and $currentSVTResult.CurrentSessionContext.Permissions.HasAttestationReadPermissions -and $currentSVTResult.ActualVerificationResult -ne [VerificationResult]::Error -and $currentSVTResult.ActualVerificationResult -ne [VerificationResult]::Disabled)
        if($isLegitimateResult)
        {
            $controlItem = $svtEventContext.ControlItem;
            if($null -eq $scanResult)
            {
                $scanResult = [ComplianceStateTableEntity]::new();
                $scanResult.PartitionKey = $partitionKey;
                $scanResult.RowKey = $rowKey;        
            }                        
            $scanResult.ResourceId = $resourceId;
            $scanResult.FeatureName = $svtEventContext.FeatureName; 
            if($svtEventContext.IsResource())
            {
                $scanResult.ResourceName = $svtEventContext.ResourceContext.ResourceName;
                $scanResult.ResourceGroupName = $svtEventContext.ResourceContext.ResourceGroupName;
            }
            if($scanResult.VerificationResult -ne $currentSVTResult.VerificationResult.ToString())
            {
                $scanResult.LastResultTransitionOn = [System.DateTime]::UtcNow.ToString("s");
                $scanResult.PreviousVerificationResult = $scanResult.VerificationResult;
            }

            if($scanResult.FirstScannedOn -eq [Constants]::AzSKDefaultDateTime)
            {
                $scanResult.FirstScannedOn = [System.DateTime]::UtcNow.ToString("s");
            }

            if($scanResult.FirstFailedOn -eq [Constants]::AzSKDefaultDateTime -and $currentSVTResult.ActualVerificationResult -ne [VerificationResult]::Passed)
            {
                $scanResult.FirstFailedOn = [System.DateTime]::UtcNow.ToString("s");
            }

            $scanResult.ScannedBy = [Helpers]::GetCurrentRMContext().Account
            $scanResult.ScanSource = $this.ScanSource
            $scanResult.ScannerVersion = $this.ScannerVersion
            #TODO check in the case sub control
            $scanResult.ChildResourceName = $currentSVTResult.ChildResourceName             
            $scanResult.ControlId = $controlItem.ControlId             
            $scanResult.ControlIntId = $controlItem.Id 
            $scanResult.ControlSeverity = $controlItem.ControlSeverity.ToString()
            $scanResult.ActualVerificationResult = $currentSVTResult.ActualVerificationResult.ToString(); 
            $scanResult.AttestationStatus = $currentSVTResult.AttestationStatus.ToString();
            if($scanResult.AttestationStatus.ToString() -ne [AttestationStatus]::None -and $null -ne $currentSVTResult.StateManagement -and $null -ne $currentSVTResult.StateManagement.AttestedStateData)
            {
                if($scanResult.FirstAttestedOn -eq [Constants]::AzSKDefaultDateTime)
                {
                    $scanResult.FirstAttestedOn = $currentSVTResult.StateManagement.AttestedStateData.AttestedDate.ToString("s");
                }

                if($currentSVTResult.StateManagement.AttestedStateData.AttestedDate -gt $scanResult.AttestedDate)
                {
                    $scanResult.AttestationCounter = $scanResult.AttestationCounter + 1 
                }
                $scanResult.AttestedBy =  $currentSVTResult.StateManagement.AttestedStateData.AttestedBy
                $scanResult.AttestedDate = $currentSVTResult.StateManagement.AttestedStateData.AttestedDate.ToString("s");
                $scanResult.Justification = $currentSVTResult.StateManagement.AttestedStateData.Justification
            }
            else
            {
                $scanResult.AttestedBy = ""
                $scanResult.AttestedDate = [Constants]::AzSKDefaultDateTime.ToString("s") ;
                $scanResult.Justification = ""                
            }
            if($currentSVTResult.VerificationResult -ne [VerificationResult]::Manual)
            {
                $scanResult.VerificationResult = $currentSVTResult.VerificationResult
            }
            else {
                $scanResult.VerificationResult = $currentSVTResult.ActualVerificationResult.ToString();
            }
            $scanResult.ScannerModuleName = [Constants]::AzSKModuleName
            $scanResult.IsLatestPSModule = $currentSVTResult.CurrentSessionContext.IsLatestPSModule
            $scanResult.HasRequiredPermissions = $currentSVTResult.CurrentSessionContext.Permissions.HasRequiredAccess
            $scanResult.HasAttestationWritePermissions = $currentSVTResult.CurrentSessionContext.Permissions.HasAttestationWritePermissions
            $scanResult.HasAttestationReadPermissions = $currentSVTResult.CurrentSessionContext.Permissions.HasAttestationReadPermissions
            $scanResult.UserComments = $currentSVTResult.UserComments
            $scanResult.IsBaselineControl = $controlItem.IsBaselineControl
            
            if($controlItem.Tags.Contains("OwnerAccess") -or $controlItem.Tags.Contains("GraphRead"))
            {
                $scanResult.HasOwnerAccessTag = $true
            }
            $scanResult.LastScannedOn = [DateTime]::UtcNow.ToString('s')
        }
                        
        return $scanResult
    }

    #new functions
    
    hidden [ComplianceStateTableEntity[]] MergeSVTScanResult($currentScanResults)
    {
        if($currentScanResults.Count -lt 1) { return $null}
        [ComplianceStateTableEntity[]] $finalScanData = @()
        #TODO
        $SVTEventContextFirst = $currentScanResults[0]

        #TODO get specific data
        $complianceReport = $this.GetSubscriptionComplianceReport($currentScanResults, $null);
        # $inActiveRecords = @();
        # $complianceReport | ForEach-Object {
        # $record = $_;
        # if($_.RowKey -eq "EmptyResource")
        # {
        # $record.IsActive = $false;
        # $inActiveRecords += $record;
        # }
        # }
        $foundPersistedData = ($complianceReport | Measure-Object).Count -gt 0
        $currentScanResults | ForEach-Object {
            $currentScanResult = $_
            $resourceId = $currentScanResult.SubscriptionContext.Scope;
            if($currentScanResult.IsResource())
            {
                $resourceId = $currentScanResult.ResourceContext.ResourceId;
            }
            if($currentScanResult.FeatureName -ne "AzSKCfg")
            {
                $controlsToProcess = @();

                if(($currentScanResult.ControlResults | Measure-Object).Count -gt 0)
                {    
                    $controlsToProcess += $currentScanResult.ControlResults;
                }
                
                $controlsToProcess | ForEach-Object {
                    $cScanResult = $_;
                    $partsToHash = $currentScanResult.ControlItem.Id;
                    if(-not [string]::IsNullOrWhiteSpace($cScanResult.ChildResourceName))
                    {
                        $partsToHash = $partsToHash + ":" + $cScanResult.ChildResourceName;
                    }
                    $currentResultHashId_r = [Helpers]::ComputeHash($partsToHash.ToLower());
                    $currentResultHashId_p = [Helpers]::ComputeHash($resourceId.ToLower());
                    $persistedScanResult = $null;
                    if($foundPersistedData)
                    {
                        $persistedScanResult = $complianceReport | Where-Object { $_.PartitionKey -eq $currentResultHashId_p -and $_.RowKey -eq $currentResultHashId_r }
                        # if(($persistedScanResult | Measure-Object).Count -le 0)
                        # {
                        # $foundPersistedData = $false;
                        # }
                    }
                    $mergedScanResult = $this.ConvertScanResultToSnapshotResult($cScanResult, $persistedScanResult, $currentScanResult, $currentResultHashId_p, $currentResultHashId_r, $resourceId)
                    if($null -ne $mergedScanResult)
                    {
                        $finalScanData += $mergedScanResult;
                    }
                }
            }
        }
        # $finalScanData += $inActiveRecords;

        return $finalScanData
    }
    hidden [void] SetLocalSubscriptionScanReport([ComplianceStateTableEntity[]] $scanResultForStorage)
    {        
        $storageInstance = $this.azskStorageInstance;

        $groupedScanResultForStorage = $scanResultForStorage | Group-Object { $_.PartitionKey}
        $groupedScanResultForStorage | ForEach-Object {
            $group = $_;
            $results = $_.Group;
            #MERGE batch req sample
            [WebRequestHelper]::InvokeTableStorageBatchWebRequest($storageInstance.ResourceGroupName,$storageInstance.StorageAccountName,$this.ComplianceTableName,$results,$true, $storageInstance.AccessKey)
            #POST batch req sample
            #[WebRequestHelper]::InvokeTableStorageBatchWebRequest($storageInstance.ResourceGroupName,$storageInstance.StorageAccountName,$this.ComplianceTableName,$results,$false)
        }        
    }
    hidden [void] StoreComplianceDataInUserSubscription([SVTEventContext[]] $currentScanResult)
    {
        $finalScanReport = $this.MergeSVTScanResult($currentScanResult)
        $this.SetLocalSubscriptionScanReport($finalScanReport)
    }

    hidden [string] ConstructPartitionKeysFilterQueryString([string[]] $PartitionKeys)
    {
        if($PartitionKeys.Length -gt 0)
        {
            $template = "PartitionKey%20eq%20'{0}'";
            $tempQS = ""
            $havePartitionKeys = $false;
            $PartitionKeys | ForEach-Object {
                $pKey = $_
                $tempQS = $tempQS + ($template -f $pKey) + "%20or%20";
                $havePartitionKeys = $true;
            }
            if($havePartitionKeys)
            {
                $tempQS = $tempQS.Substring(0,$tempQS.Length - 8);
            }
            return $tempQS;
        }    
        else {
            return "";
        }    
    }
}