Framework/Core/SVT/ADO/ADO.VariableGroup.ps1
Set-StrictMode -Version Latest class VariableGroup: ADOSVTBase { hidden [PSObject] $VarGrp; hidden [PSObject] $ProjectId; hidden [PSObject] $VarGrpId; hidden [string] $checkInheritedPermissionsPerVarGrp = $false hidden [PSObject] $variableGroupIdentities = $null; VariableGroup([string] $organizationName, [SVTResource] $svtResource): Base($organizationName,$svtResource) { $this.ProjectId = ($this.ResourceContext.ResourceId -split "project/")[-1].Split('/')[0]; $this.VarGrpId = $this.ResourceContext.ResourceDetails.id $apiURL = "https://dev.azure.com/$($this.OrganizationContext.OrganizationName)/$($this.ProjectId)/_apis/distributedtask/variablegroups/$($this.VarGrpId)?api-version=6.1-preview.2" $this.VarGrp = [WebRequestHelper]::InvokeGetWebRequest($apiURL); if ([Helpers]::CheckMember($this.ControlSettings, "VariableGroup.CheckForInheritedPermissions") -and $this.ControlSettings.VariableGroup.CheckForInheritedPermissions) { $this.checkInheritedPermissionsPerVarGrp = $true } } hidden [ControlResult] CheckPipelineAccess([ControlResult] $controlResult) { try { $controlResult.VerificationResult = [VerificationResult]::Failed $url = 'https://dev.azure.com/{0}/{1}/_apis/build/authorizedresources?type=variablegroup&id={2}&api-version=6.0-preview.1' -f $($this.OrganizationContext.OrganizationName),$($this.ProjectId) ,$($this.VarGrpId); $responseObj = @([WebRequestHelper]::InvokeGetWebRequest($url)); # # When var grp is shared across all pipelines - the below condition will be true. if([Helpers]::CheckMember($responseObj[0],"authorized") -and $responseObj[0].authorized -eq $true ) { $isSecretFound = $false $secretVarList = @(); # Check if variable group has any secret or linked to KV if ($this.VarGrp.Type -eq 'AzureKeyVault') { $isSecretFound = $true } else { Get-Member -InputObject $this.VarGrp.variables -MemberType Properties | ForEach-Object { #no need to check if isSecret val is true, as it will always be true if isSecret is present if([Helpers]::CheckMember($this.VarGrp.variables.$($_.Name),"isSecret")) { $isSecretFound = $true $secretVarList += $_.Name } } } if ($isSecretFound -eq $true) { $controlResult.AddMessage([VerificationResult]::Failed, "Variable group contains secrets accessible to all YAML pipelines."); $controlResult.AdditionalInfoInCSV = "SecretVarsList: $($secretVarList -join '; ')"; } else { $controlResult.AddMessage([VerificationResult]::Passed, "Variable group does not contain secret."); $controlResult.AdditionalInfoInCSV += "NA" } } else { $controlResult.AddMessage([VerificationResult]::Passed, "Variable group is not accessible to all YAML pipelines."); $controlResult.AdditionalInfoInCSV += "NA" } } catch { $controlResult.AddMessage([VerificationResult]::Error,"Could not fetch authorization details of variable group."); $controlResult.LogException($_) } return $controlResult } hidden [ControlResult] CheckInheritedPermissions([ControlResult] $controlResult) { $url = 'https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1' -f $($this.OrganizationContext.OrganizationName),$($this.ProjectId) ,$($this.VarGrpId); try { $responseObj = [WebRequestHelper]::InvokeGetWebRequest($url); $inheritedRoles = $responseObj | Where-Object {$_.access -eq "inherited"} if(($inheritedRoles | Measure-Object).Count -gt 0) { $roles = @(); $roles += ($inheritedRoles | Select-Object -Property @{Name="Name"; Expression = {$_.identity.displayName}}, @{Name="Role"; Expression = {$_.role.displayName}}); $controlResult.AddMessage("Total number of inherited role assignments on variable group: ", ($roles | Measure-Object).Count); $controlResult.AddMessage([VerificationResult]::Failed,"Review the list of inherited role assignments on variable group: ", $roles); $controlResult.SetStateData("List of inherited role assignments on variable group: ", $roles); $controlResult.AdditionalInfo += "Total number of inherited role assignments on variable group: " + ($roles | Measure-Object).Count; } else { $controlResult.AddMessage([VerificationResult]::Passed,"No inherited role assignments found on variable group.") } } catch { $controlResult.AddMessage([VerificationResult]::Error,"Could not fetch permission details of variable group."); $controlResult.LogException($_) } return $controlResult } hidden [ControlResult] CheckRBACAccess([ControlResult] $controlResult) { <# { "ControlID": "ADO_VariableGroup_AuthZ_Grant_Min_RBAC_Access", "Description": "All teams/groups must be granted minimum required permissions on variable group.", "Id": "VariableGroup110", "ControlSeverity": "High", "Automated": "Yes", "MethodName": "CheckRBACAccess", "Rationale": "Granting minimum access by leveraging RBAC feature ensures that users are granted just enough permissions to perform their tasks. This minimizes exposure of the resources in case of user/service account compromise.", "Recommendation": "Refer: https://docs.microsoft.com/en-us/azure/devops/pipelines/library/?view=azure-devops#security", "Tags": [ "SDL", "TCP", "Automated", "AuthZ", "RBAC" ], "Enabled": true } #> $url = 'https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1' -f $($this.OrganizationContext.OrganizationName), $($this.ProjectId), $($this.VarGrpId); try { $responseObj = [WebRequestHelper]::InvokeGetWebRequest($url); if(($responseObj | Measure-Object).Count -gt 0) { $roles = @(); $roles += ($responseObj | Select-Object -Property @{Name="Name"; Expression = {$_.identity.displayName}}, @{Name="Role"; Expression = {$_.role.displayName}}, @{Name="AccessType"; Expression = {$_.access}}); $controlResult.AddMessage("Total number of role assignments on variable group: ", ($roles | Measure-Object).Count); $controlResult.AddMessage([VerificationResult]::Verify,"Review the list of role assignments on variable group: ", $roles); $controlResult.SetStateData("List of role assignments on variable group: ", $roles); $controlResult.AdditionalInfo += "Total number of role assignments on variable group: " + ($roles | Measure-Object).Count; } else { $controlResult.AddMessage([VerificationResult]::Passed,"No role assignments found on variable group.") } } catch { $controlResult.AddMessage([VerificationResult]::Error,"Could not fetch RBAC details of variable group."); $controlResult.LogException($_) } return $controlResult } hidden [ControlResult] CheckCredInVarGrp([ControlResult] $controlResult) { $controlResult.VerificationResult = [VerificationResult]::Failed if([Helpers]::CheckMember([ConfigurationManager]::GetAzSKSettings(),"SecretsScanToolFolder")) { $ToolFolderPath = [ConfigurationManager]::GetAzSKSettings().SecretsScanToolFolder $SecretsScanToolName = [ConfigurationManager]::GetAzSKSettings().SecretsScanToolName if((-not [string]::IsNullOrEmpty($ToolFolderPath)) -and (Test-Path $ToolFolderPath) -and (-not [string]::IsNullOrEmpty($SecretsScanToolName))) { $ToolPath = Get-ChildItem -Path $ToolFolderPath -File -Filter $SecretsScanToolName -Recurse if($ToolPath) { if($this.VarGrp) { try { $varGrpDefFileName = $($this.ResourceContext.ResourceName).Replace(" ","") $varGrpDefPath = [Constants]::AzSKTempFolderPath + "\VarGrps\"+ $varGrpDefFileName + "\"; if(-not (Test-Path -Path $varGrpDefPath)) { New-Item -ItemType Directory -Path $varGrpDefPath -Force | Out-Null } $this.VarGrp | ConvertTo-Json -Depth 5 | Out-File "$varGrpDefPath\$varGrpDefFileName.json" $searcherPath = Get-ChildItem -Path $($ToolPath.Directory.FullName) -Include "buildsearchers.xml" -Recurse ."$($Toolpath.FullName)" -I $varGrpDefPath -S "$($searcherPath.FullName)" -f csv -Ve 1 -O "$varGrpDefPath\Scan" $scanResultPath = Get-ChildItem -Path $varGrpDefPath -File -Include "*.csv" if($scanResultPath -and (Test-Path $scanResultPath.FullName)) { $credList = Get-Content -Path $scanResultPath.FullName | ConvertFrom-Csv if(($credList | Measure-Object).Count -gt 0) { $controlResult.AddMessage("No. of credentials found:" + ($credList | Measure-Object).Count ) $controlResult.AddMessage([VerificationResult]::Failed,"Found credentials in variables.") $controlResult.AdditionalInfo += "No. of credentials found in variables: " + ($credList | Measure-Object).Count; } else { $controlResult.AddMessage([VerificationResult]::Passed,"No credentials found in variables.") } } } catch { #Publish Exception $this.PublishException($_); $controlResult.LogException($_) } finally { #Clean temp folders Remove-ITem -Path $varGrpDefPath -Recurse } } } } } else { try { if([Helpers]::CheckMember($this.VarGrp[0],"variables")) { $varList = @(); $noOfCredFound = 0; $patterns = @($this.ControlSettings.Patterns | where-object {$_.RegexCode -eq "SecretsInVariables"} | Select-Object -Property RegexList); $exclusions = $this.ControlSettings.Build.ExcludeFromSecretsCheck; $exclusions += $this.ControlSettings.Release.ExcludeFromSecretsCheck; $exclusions = @($exclusions | select-object -unique) if($patterns.Count -gt 0) { #Compare all non-secret variables with regex Get-Member -InputObject $this.VarGrp[0].variables -MemberType Properties | ForEach-Object { if([Helpers]::CheckMember($this.VarGrp[0].variables.$($_.Name),"value") -and (-not [Helpers]::CheckMember($this.VarGrp[0].variables.$($_.Name),"isSecret"))) { $varName = $_.Name $varValue = $this.VarGrp[0].variables.$varName.value <# helper code to build a list of vars and counts if ([Build]::BuildVarNames.Keys -contains $buildVarName) { [Build]::BuildVarNames.$buildVarName++ } else { [Build]::BuildVarNames.$buildVarName = 1 } #> if ($exclusions -notcontains $varName) { for ($i = 0; $i -lt $patterns.RegexList.Count; $i++) { #Note: We are using '-cmatch' here. #When we compile the regex, we don't specify ignoreCase flag. #If regex is in text form, the match will be case-sensitive. if ($varValue -cmatch $patterns.RegexList[$i]) { $noOfCredFound +=1 $varList += $varName; break } } } } } if($noOfCredFound -gt 0) { $varList = @($varList | Select-Object -Unique) $controlResult.AddMessage([VerificationResult]::Failed, "Found secrets in variable group.`nList of variables: ", $varList ); $controlResult.SetStateData("List of variable name containing secret: ", $varList); $controlResult.AdditionalInfo += "Count of variable(s) containing secret: " + $varList.Count; } else { $controlResult.AddMessage([VerificationResult]::Passed, "No credentials found in variable group."); } $patterns = $null; } else { $controlResult.AddMessage([VerificationResult]::Error, "Regular expressions for detecting credentials in variable groups are not defined in your organization."); } } else { $controlResult.AddMessage([VerificationResult]::Passed, "No variables found in variable group."); } } catch { $controlResult.AddMessage([VerificationResult]::Error, "Could not fetch the variable group definition."); $controlResult.AddMessage($_); $controlResult.LogException($_) } } return $controlResult; } hidden [ControlResult] CheckBroaderGroupAccess ([ControlResult] $controlResult) { try { $controlResult.VerificationResult = [VerificationResult]::Failed $restrictedBroaderGroups = @{} $restrictedBroaderGroupsForVarGrp = $this.ControlSettings.VariableGroup.RestrictedBroaderGroupsForVariableGroup; if(@($restrictedBroaderGroupsForVarGrp.psobject.Properties).Count -gt 0){ $restrictedBroaderGroupsForVarGrp.psobject.properties | foreach { $restrictedBroaderGroups[$_.Name] = $_.Value } #Fetch variable group RBAC $roleAssignments = @(); if ($null -eq $this.variableGroupIdentities) { $url = 'https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1' -f $($this.OrganizationContext.OrganizationName), $($this.ProjectId), $($this.VarGrpId); $this.variableGroupIdentities = @([WebRequestHelper]::InvokeGetWebRequest($url)); } if($this.variableGroupIdentities.Count -gt 0) { if ($this.checkInheritedPermissionsPerVarGrp -eq $false) { $roleAssignments = @($this.variableGroupIdentities | where-object { $_.access -ne "inherited" }) } $roleAssignments = @($roleAssignments | Select-Object -Property @{Name="Name"; Expression = {$_.identity.displayName}},@{Name="Id"; Expression = {$_.identity.id}}, @{Name="Role"; Expression = {$_.role.displayName}}); } # Checking whether the broader groups have User/Admin permissions $backupDataObject = @($roleAssignments | Where-Object { ($restrictedBroaderGroups.keys -contains $_.Name.split('\')[-1]) -and ($_.Role -in $restrictedBroaderGroups[$_.Name.split('\')[-1]])}) $restrictedGroups = @($backupDataObject | Select-Object Name,role) if ($this.ControlSettings.CheckForBroadGroupMemberCount -and $restrictedGroups.Count -gt 0) { $broaderGroupsWithExcessiveMembers = @([ControlHelper]::FilterBroadGroupMembers($restrictedGroups, $true)) $restrictedGroups = @($restrictedGroups | Where-Object {$broaderGroupsWithExcessiveMembers -contains $_.Name}) } $restrictedGroupsCount = $restrictedGroups.Count # fail the control if restricted group found on variable group if ($restrictedGroupsCount -gt 0) { $controlResult.AddMessage([VerificationResult]::Failed, "`nCount of broader groups that have excessive permissions on variable group: $($restrictedGroupsCount)"); $controlResult.AddMessage("`nList of groups: ") $controlResult.AddMessage(($restrictedGroups | FT Name,Role -AutoSize | Out-String -Width 512)); $controlResult.SetStateData("List of groups: ", $restrictedGroups) $controlResult.AdditionalInfo += "Count of broader groups that have excessive permissions on variable group: $($restrictedGroupsCount)"; if ($this.ControlFixBackupRequired) { #Data object that will be required to fix the control $controlResult.BackupControlState = $backupDataObject; } $formatedRestrictedGroups = $restrictedGroups | ForEach-Object { $_.Name + ': ' + $_.Role } $controlResult.AdditionalInfoInCSV = ($formatedRestrictedGroups -join '; ' ) } else { $controlResult.AddMessage([VerificationResult]::Passed, "No broader groups have excessive permissions on variable group."); $controlResult.AdditionalInfoInCSV += "NA" } $displayObj = $restrictedBroaderGroups.Keys | Select-Object @{Name = "Broader Group"; Expression = {$_}}, @{Name = "Excessive Permissions"; Expression = {$restrictedBroaderGroups[$_] -join ', '}} $controlResult.AddMessage("Note:`nThe following groups are considered 'broad' and should not have excessive permissions: `n$( $displayObj| FT | out-string)"); } else{ $controlResult.AddMessage([VerificationResult]::Error, "List of restricted broader groups and restricted roles for variable group is not defined in the control settings for your organization policy."); } } catch { $controlResult.AddMessage([VerificationResult]::Error, "Could not fetch the variable group permissions."); $controlResult.LogException($_) } return $controlResult; } hidden [ControlResult] CheckBroaderGroupAccessAutomatedFix ([ControlResult] $controlResult) { try { $RawDataObjForControlFix = @(); $RawDataObjForControlFix = ([ControlHelper]::ControlFixBackup | where-object {$_.ResourceId -eq $this.ResourceId}).DataObject $body = "[" if (-not $this.UndoFix) { foreach ($identity in $RawDataObjForControlFix) { if ($body.length -gt 1) {$body += ","} $body += @" { "userId": "$($identity.id)", "roleName": "Reader" } "@; } $RawDataObjForControlFix | Add-Member -NotePropertyName NewRole -NotePropertyValue "Reader" $RawDataObjForControlFix = @($RawDataObjForControlFix | Select-Object @{Name="DisplayName"; Expression={$_.Name}}, @{Name="OldRole"; Expression={$_.Role}},@{Name="NewRole"; Expression={$_.NewRole}}) } else { foreach ($identity in $RawDataObjForControlFix) { if ($body.length -gt 1) {$body += ","} $body += @" { "userId": "$($identity.id)", "roleName": "$($identity.role)" } "@; } $RawDataObjForControlFix | Add-Member -NotePropertyName OldRole -NotePropertyValue "Reader" $RawDataObjForControlFix = @($RawDataObjForControlFix | Select-Object @{Name="DisplayName"; Expression={$_.Name}}, @{Name="OldRole"; Expression={$_.OldRole}},@{Name="NewRole"; Expression={$_.Role}}) } $body += "]" #Put request $url = "https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1" -f $($this.OrganizationContext.OrganizationName),$($this.ProjectId),$($this.VarGrpId); $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $webRequestResult = Invoke-RestMethod -Uri $url -Method Put -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -Body $body $controlResult.AddMessage([VerificationResult]::Fixed, "Permission for broader groups have been changed as below: "); $display = ($RawDataObjForControlFix | FT -AutoSize | Out-String -Width 512) $controlResult.AddMessage("`n$display"); } catch{ $controlResult.AddMessage([VerificationResult]::Error, "Could not apply fix."); $controlResult.LogException($_) } return $controlResult; } hidden [ControlResult] CheckBroaderGroupAccessForVarGrpWithSecrets([ControlResult] $controlResult) { $controlResult.VerificationResult = [VerificationResult]::Failed; try { $restrictedBroaderGroups = @{} $restrictedBroaderGroupsForVarGrp = $this.ControlSettings.VariableGroup.RestrictedBroaderGroupsForVariableGroup; $restrictedBroaderGroupsForVarGrp.psobject.properties | foreach { $restrictedBroaderGroups[$_.Name] = $_.Value } if([Helpers]::CheckMember($this.VarGrp[0],"variables")) { $secretVarList = @(); $VGMembers = @(Get-Member -InputObject $this.VarGrp[0].variables -MemberType Properties) $patterns = @($this.ControlSettings.Patterns | Where-Object {$_.RegexCode -eq "SecretsInVariables"} | Select-Object -Property RegexList); $VGMembers | ForEach-Object { $varName = $_.Name if([Helpers]::CheckMember($this.VarGrp[0].variables.$varName,"value")) { $varValue = $this.VarGrp[0].variables.$varName.value for ($i = 0; $i -lt $patterns.RegexList.Count; $i++) { #Note: We are using '-cmatch' here. #When we compile the regex, we don't specify ignoreCase flag. #If regex is in text form, the match will be case-sensitive. if ($varValue -cmatch $patterns.RegexList[$i]) { $secretVarList += $varName break } } } elseif (([Helpers]::CheckMember($this.VarGrp[0].variables.$($_.Name),"isSecret"))) { $secretVarList += $varName } } if ($secretVarList.Count -gt 0) { #Fetch variable group RBAC $roleAssignments = @(); $url = 'https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1' -f $($this.OrganizationContext.OrganizationName), $($this.ProjectId), $($this.VarGrpId); $responseObj = @([WebRequestHelper]::InvokeGetWebRequest($url)); if($responseObj.Count -gt 0) { if ($this.checkInheritedPermissionsPerVarGrp -eq $false) { $responseObj = $responseObj | where-object { $_.access -ne "inherited" } } $roleAssignments += ($responseObj | Select-Object -Property @{Name="Name"; Expression = {$_.identity.displayName}}, @{Name="Role"; Expression = {$_.role.displayName}}, @{Name="Id"; Expression = {$_.identity.id}}); } # Checking whether the broader groups have User/Admin permissions $restrictedGroups = @($roleAssignments | Where-Object { ($restrictedBroaderGroups.keys -contains $_.Name.split('\')[-1]) -and ($_.Role -in $restrictedBroaderGroups[$_.Name.split('\')[-1]])}) if ($this.ControlSettings.CheckForBroadGroupMemberCount -and $restrictedGroups.Count -gt 0) { $broaderGroupsWithExcessiveMembers = @([ControlHelper]::FilterBroadGroupMembers($restrictedGroups, $true)) $restrictedGroups = @($restrictedGroups | Where-Object {$broaderGroupsWithExcessiveMembers -contains $_.Name}) } $restrictedGroupsCount = $restrictedGroups.Count # fail the control if restricted group found on variable group which contains secrets if ($restrictedGroupsCount -gt 0) { $controlResult.AddMessage([VerificationResult]::Failed, "Broader groups have excessive permissions on the variable group."); $controlResult.AddMessage("`nCount of broader groups that have excessive permissions on the variable group: $($restrictedGroupsCount)") $controlResult.AdditionalInfo += "Count of broader groups that have excessive permissions on the variable group: $($restrictedGroupsCount)"; $controlResult.AddMessage("`nList of broader groups: ",$($restrictedGroups | FT | Out-String)) $controlResult.AddMessage("`nList of variables with secret: ",$secretVarList) $controlResult.SetStateData("List of broader groups: ", $restrictedGroups) if ($this.ControlFixBackupRequired) { #Data object that will be required to fix the control $controlResult.BackupControlState = $restrictedGroups; } $groups = $restrictedGroups | ForEach-Object { $_.Name + ': ' + $_.Role } $controlResult.AdditionalInfoInCSV = $($groups -join '; ')+"; SecretVarsList: $($secretVarList -join '; ')"; } else { $controlResult.AddMessage([VerificationResult]::Passed, "No broader groups have excessive permissions on the variable group."); $controlResult.AdditionalInfoInCSV += "NA" } $displayObj = $restrictedBroaderGroups.Keys | Select-Object @{Name = "Broader Group"; Expression = {$_}}, @{Name = "Excessive Permissions"; Expression = {$restrictedBroaderGroups[$_] -join ', '}} $controlResult.AddMessage("`nNote:`nThe following groups are considered 'broad' and should not have excessive permissions: `n$( $displayObj| FT | out-string -Width 512)"); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No secrets found in variable group."); $controlResult.AdditionalInfoInCSV += "NA" } } else { $controlResult.AddMessage([VerificationResult]::Passed, "No variables found in variable group."); $controlResult.AdditionalInfoInCSV += "NA" } } catch { $controlResult.AddMessage([VerificationResult]::Error, "Could not fetch the variable group permissions."); $controlResult.LogException($_) } return $controlResult; } hidden [ControlResult] CheckBroaderGroupAccessForVarGrpWithSecretsAutomatedFix ([ControlResult] $controlResult) { try { $RawDataObjForControlFix = @(); $RawDataObjForControlFix = ([ControlHelper]::ControlFixBackup | where-object {$_.ResourceId -eq $this.ResourceId}).DataObject $body = "[" if (-not $this.UndoFix) { foreach ($identity in $RawDataObjForControlFix) { if ($body.length -gt 1) {$body += ","} $body += @" { "userId": "$($identity.id)", "roleName": "Reader" } "@; } $RawDataObjForControlFix | Add-Member -NotePropertyName NewRole -NotePropertyValue "Reader" $RawDataObjForControlFix = @($RawDataObjForControlFix | Select-Object @{Name="DisplayName"; Expression={$_.Name}}, @{Name="OldRole"; Expression={$_.Role}},@{Name="NewRole"; Expression={$_.NewRole}}) } else { foreach ($identity in $RawDataObjForControlFix) { if ($body.length -gt 1) {$body += ","} $body += @" { "userId": "$($identity.id)", "roleName": "$($identity.role)" } "@; } $RawDataObjForControlFix | Add-Member -NotePropertyName OldRole -NotePropertyValue "Reader" $RawDataObjForControlFix = @($RawDataObjForControlFix | Select-Object @{Name="DisplayName"; Expression={$_.Name}}, @{Name="OldRole"; Expression={$_.OldRole}},@{Name="NewRole"; Expression={$_.Role}}) } $body += "]" #Put request $url = "https://dev.azure.com/{0}/_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/{1}%24{2}?api-version=6.1-preview.1" -f $($this.OrganizationContext.OrganizationName),$($this.ProjectId),$($this.VarGrpId); $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $webRequestResult = Invoke-RestMethod -Uri $url -Method Put -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -Body $body $controlResult.AddMessage([VerificationResult]::Fixed, "Permission for broader groups have been changed as below: "); $display = ($RawDataObjForControlFix | FT -AutoSize | Out-String -Width 512) $controlResult.AddMessage("`n$display"); } catch{ $controlResult.AddMessage([VerificationResult]::Error, "Could not apply fix."); $controlResult.LogException($_) } return $controlResult; } } # SIG # Begin signature block # MIIjkwYJKoZIhvcNAQcCoIIjhDCCI4ACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCASvLI/9urR7ART # iQmZILN9w+CtpQI1m7g83v+AXA0TVqCCDYEwggX/MIID56ADAgECAhMzAAAB32vw # LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn # s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw # PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS # yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG # 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh # EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH # tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS # 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp # TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok # t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 # b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao # mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD # Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt # VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G # CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ # Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 # oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVaDCCFWQCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN # BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgzlSUmjuf # oXKJl2n3jJwe3P5hPkjP03SLkdXHeTtpmZgwRAYKKwYBBAGCNwIBDDE2MDSgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g # MA0GCSqGSIb3DQEBAQUABIIBAKCeIGayOrnPw7tAfLlKajhMN36iZ/WK8TN3GmLo # DehsCIi6AFK1UHhWmOR1TUoDDaAlLlpKcvxZOGSK34GRZctigWr8U0sUoxT8F9+2 # jJZ4YW6uiNyUtjz9RyaFnDFh+PcFp3vYX0oVdeIqAr/RdubYmvi/rfd2uzGfhM6v # ltSb/0RmZr0G9ffTpsJ3v4+aI+rQrMcMWTFZ2yJFsHLnxwoMbKThXQcJLRZ7GOKj # jGNPR5tZAxk6bhew9B1bWEMvg+O6O0+L093Ubzb7EoPNapmMK9qY3y+YuWp0y7li # wZf5WVMJulZC0yrCLwZUMTBm60g0NO+ONle2ipXvPe1PDQqhghLwMIIS7AYKKwYB # BAGCNwMDATGCEtwwghLYBgkqhkiG9w0BBwKgghLJMIISxQIBAzEPMA0GCWCGSAFl # AwQCAQUAMIIBVAYLKoZIhvcNAQkQAQSgggFDBIIBPzCCATsCAQEGCisGAQQBhFkK # AwEwMTANBglghkgBZQMEAgEFAAQgKjTHwGpQwY//Cy0gwxQkEOdijzHZn8LTNBfO # dXCYEAICBmFE07YlERgSMjAyMTEwMTMwOTE5MzcuMTFaMASAAgH0oIHUpIHRMIHO # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBN # aWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVz # IFRTUyBFU046NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l # LVN0YW1wIFNlcnZpY2Wggg5EMIIE9TCCA92gAwIBAgITMwAAAVhwWiL3vpbmAwAA # AAABWDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDAeFw0yMTAxMTQxOTAyMTRaFw0yMjA0MTExOTAyMTRaMIHOMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3Bl # cmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046NDYy # Ri1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp # Y2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChHwuXYPWrsNCgBRsL # 9e8jBRvEn6oFFBQvA88GvJq6bNHsoUUNjb/Su/7M/31RNaP9X2aeKuEhorXLIzxr # Tp41seOVSBUyDUKXaDoZrD3Zxct4AV6TBrU316i551BOPlZigtrwITmdOlOr7eQn # NHCaKhCbczlkcBGs/AaF9pwl9UQV5B9z4gLu7Vib91fM4UUjyxZnoifgiMGstOAF # IJq8FxEB7yR4G+j4iwsYBNlQAQgzU+QlconjWqXGYisdekGw5XuyjsJIzBCCpHMU # ft9nQzLcwraSFA4KysZo8fhpveIx4nqITh1LoZd7t4ZQGH79kgP/Ok9VDQIgUIN1 # rvcbAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQUS3DZG32dHBgf7ud+oHuTJ9Oi+Vgw # HwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmg # R4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWlj # VGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEF # BQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1T # dGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggr # BgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAOd8oA1qL0K4fH7pYjV1tAlAU83wO # EpeIfiDxIeZTXa4Qxcuk+DAPY7qdc85RZKWK1HNLE30AgDpwI5rpz4J5mkuW0n9l # R/DIN+FNqoDyyJzAJBmgbPwc2myeuWCntT+SCmTe1o9m0XwitNxEvJEu4OmEB+u4 # sTAkAiw63lgyiWLDbNHITaSTgM8iXhn8kVHvk1FGxcI7Av9fCpmDg1YKUUmGcdFu # 46xqpSVRHobsKUiLBjmAgTJyQzXSpz/tdwoOvHFbQjV+pCXb1BR9GYrjzJQWA+xq # wj6gEZUp/r8X3zIr7tgzCSS5HssMUnw+drA1fjQX+SJ4rihXBPctJvZtozCCBnEw # ggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoX # DTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEi # MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRr # dFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmx # MEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKE # HnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBi # sV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpO # BpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMB # AAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPND # e3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQD # AgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb # 186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29t # L3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoG # CCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1Ud # IAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2j # oSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJE # Evu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5 # SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJK # J/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yj # ojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0 # v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgi # CGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iC # tHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO # 2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyX # UHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWz # fjUeCLraNtvTX4/edIhJEqGCAtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg # T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 # NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2WiIwoBATAHBgUrDgMCGgMVAKnJK3Ma59ELIabqM46fpfg0nzS/oIGDMIGA # pH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQAC # BQDlEO+0MCIYDzIwMjExMDEzMDk0MDA0WhgPMjAyMTEwMTQwOTQwMDRaMHcwPQYK # KwYBBAGEWQoEATEvMC0wCgIFAOUQ77QCAQAwCgIBAAICG+YCAf8wBwIBAAICEQIw # CgIFAOUSQTQCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgC # AQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAZjxys4a5dNjRM # vYw8KUoRQFUwpoEYIhnlwYIu/Z+8/GX2oezaMWMuOLV495LRJmr+3hzrS33EEvR6 # UiTmRs9JG0P/kO7W+HXLMYInxoOlSxQnEWKVHN96/w+M/uoBW4tQEutd3LpelGuH # Fmz+PIxnKiUffaUhVK4qcn2Tk1F/iTGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABWHBaIve+luYDAAAAAAFYMA0GCWCGSAFl # AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN # AQkEMSIEIKPMP9lrBdiXMLDBmMdoRUU7nnqolEHJhKoOexPx/rBCMIH6BgsqhkiG # 9w0BCRACLzGB6jCB5zCB5DCBvQQg8kozjWyGNZdsyk+G2uLAiOFpAQurCH0fbklT # Vcdw0wcwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # AVhwWiL3vpbmAwAAAAABWDAiBCAXMBbp+bF8/MKENa6CbF4fbiS0YhIaOL7BCJ+e # 3DFgCTANBgkqhkiG9w0BAQsFAASCAQAZKiBU/fJhyT4tu1GOmtTkoTGB2UdQwaOk # 2AN19KCJ+ZyIRJu1BlyqVwKyc056Dlsh1lGmvKCHBReHKrvvkcwx5M2yNM0tk9Te # EvB2cV3Y08CJMJFljKfgYpwgB5V+4qhAQ/LczRMKpaxwCt40JY3+0waSZZpOS75R # EHgXfEgyPErm+/LOSUCwvFD5xPpUp+oXUu9wDgzNwAlfSfZJJE8PnzuUso9f0Yfs # z17op4PGlfVU+l8yjHlX7evFl43WE1ROhMwJ65YvAlIXLW29rtc7/W0LL7Ize4du # tLahiyymM35Yj+KOqQpQWUgB2innOW0VHcFJzReHEeCOTJPs1+DA # SIG # End signature block |