PAM/Rotation.ps1
|
#requires -Version 5.1 function script:getPamRotationScheduleRows { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Plugins.PAM.IPamPlugin] $Plugin, [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [KeeperSecurity.Authentication.IAuthentication] $Auth, [bool] $VerboseOutput ) $schedulesResponse = [KeeperSecurity.Plugins.PAM.RouterUtils]::GetRotationSchedulesAsync($Auth).GetAwaiter().GetResult() $schedules = @() if ($null -ne $schedulesResponse -and $null -ne $schedulesResponse.Schedules) { $schedules = @($schedulesResponse.Schedules) } $configs = [KeeperSecurity.Plugins.PAM.PamVaultHelpers]::GetConfigurationRecords($Vault) $rows = New-Object 'System.Collections.Generic.List[object]' $prepared = New-Object 'System.Collections.Generic.List[object]' foreach ($schedule in $schedules) { $recordUid = encodePamByteString $schedule.RecordUid if ([string]::IsNullOrEmpty($recordUid)) { continue } $record = $null if (-not [KeeperSecurity.Plugins.PAM.PamVaultHelpers]::TryGetUserRecord($Vault, $recordUid, [ref]$record)) { continue } $sortTitle = if ($record.Title) { $record.Title } else { $recordUid } $prepared.Add(@{ Schedule = $schedule Record = $record RecordUid = $recordUid SortTitle = $sortTitle }) } foreach ($item in ($prepared | Sort-Object { $_.SortTitle })) { $schedule = $item.Schedule $record = $item.Record $recordUid = $item.RecordUid $controllerUid = encodePamByteString $schedule.ControllerUid $configUid = encodePamByteString $schedule.ConfigurationUid $configRecord = $null if (-not [string]::IsNullOrEmpty($configUid) -and $configs.ContainsKey($configUid)) { $configRecord = $configs[$configUid] } $scheduleText = [KeeperSecurity.Plugins.PAM.RotationUtils]::FormatScheduleDisplay($schedule.ScheduleData, $schedule.NoSchedule) $gatewayName = resolvePamRotationGatewayName -Plugin $Plugin -GatewayUid $controllerUid -FallbackName $null $configText = if ($configRecord) { "$($configRecord.Title) ($($configRecord.TypeName))" } else { '[No config found]' } if ($VerboseOutput) { $rows.Add(@{ RecordUid = $recordUid RecordTitle = $record.Title RecordType = $record.TypeName Schedule = $scheduleText Gateway = $gatewayName GatewayUid = $controllerUid PamConfiguration = $configText PamConfigurationUid = $configUid }) } else { $rows.Add(@{ RecordUid = $recordUid RecordTitle = $record.Title RecordType = $record.TypeName Schedule = $scheduleText Gateway = $gatewayName PamConfiguration = $configText }) } } return $rows.ToArray() } function script:writePamRotationListTable { Param ( $Rows, [bool] $VerboseOutput = $false ) $rowArray = @($Rows) if ($rowArray.Count -eq 0) { return } if ($VerboseOutput) { $table = $rowArray | Select-Object ` @{ Name = 'Record UID'; Expression = { $_.RecordUid } }, ` @{ Name = 'Record Title'; Expression = { $_.RecordTitle } }, ` @{ Name = 'Record Type'; Expression = { $_.RecordType } }, ` @{ Name = 'Schedule'; Expression = { $_.Schedule } }, ` @{ Name = 'Gateway'; Expression = { $_.Gateway } }, ` @{ Name = 'Gateway UID'; Expression = { $_.GatewayUid } }, ` @{ Name = 'PAM Configuration (Type)'; Expression = { $_.PamConfiguration } }, ` @{ Name = 'PAM Configuration UID'; Expression = { $_.PamConfigurationUid } } | Format-Table -AutoSize | Out-String -Width 4096 } else { $table = $rowArray | Select-Object ` @{ Name = 'Record UID'; Expression = { $_.RecordUid } }, ` @{ Name = 'Record Title'; Expression = { $_.RecordTitle } }, ` @{ Name = 'Record Type'; Expression = { $_.RecordType } }, ` @{ Name = 'Schedule'; Expression = { $_.Schedule } }, ` @{ Name = 'Gateway'; Expression = { $_.Gateway } }, ` @{ Name = 'PAM Configuration (Type)'; Expression = { $_.PamConfiguration } } | Format-Table -AutoSize | Out-String -Width 4096 } if (-not [string]::IsNullOrWhiteSpace($table)) { Write-Output $table.TrimEnd() } } function script:resolvePamRotationGatewayName { Param ( [object] $Plugin, [string] $GatewayUid, [string] $FallbackName ) if (-not [string]::IsNullOrWhiteSpace($GatewayUid) -and $GatewayUid -ne '-' -and $Plugin) { $controller = $null try { $controller = $Plugin.Controllers.GetEntity($GatewayUid) } catch { $controller = $null } if (-not $controller) { foreach ($item in $Plugin.Controllers.GetAll()) { if ($item -and [string]::Equals($item.ControllerUid, $GatewayUid, [StringComparison]::Ordinal)) { $controller = $item break } } } if ($controller -and -not [string]::IsNullOrWhiteSpace($controller.ControllerName)) { return $controller.ControllerName } } if (-not [string]::IsNullOrWhiteSpace($FallbackName)) { return $FallbackName } if (-not [string]::IsNullOrWhiteSpace($GatewayUid) -and $GatewayUid -ne '-') { return $GatewayUid } return '-' } function script:convertPamRotationComplexityDetailToHashtable { Param ( [object] $Detail ) if ($null -eq $Detail) { return $null } if ($Detail -is [string]) { return $Detail } if ($Detail -isnot [System.Collections.IDictionary]) { return $Detail } $ht = [ordered]@{} foreach ($key in $Detail.Keys) { $ht[[string]$key] = $Detail[$key] } return $ht } function script:getPamRotationInfoModel { Param ( [Parameter(Mandatory = $true)] [object] $RotationInfo, [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record, [object] $Schedule, [object] $Plugin = $null, [KeeperSecurity.Vault.VaultOnline] $Vault = $null ) $statusName = [string]$RotationInfo.Status $isReady = ($RotationInfo.Status.ToString() -eq 'RrsOnline') $configUid = encodePamByteString $RotationInfo.ConfigurationUid $gatewayUid = encodePamByteString $RotationInfo.ControllerUid if ([string]::IsNullOrEmpty($gatewayUid)) { $gatewayUid = '-' } $gatewayName = resolvePamRotationGatewayName -Plugin $Plugin -GatewayUid $gatewayUid -FallbackName $RotationInfo.ControllerName $adminResourceUid = encodePamByteString $RotationInfo.ResourceUid if ([string]::IsNullOrEmpty($adminResourceUid)) { $adminResourceUid = $null } $pwdComplexityRaw = $null if (-not [string]::IsNullOrEmpty($RotationInfo.PwdComplexity)) { $pwdComplexityRaw = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlDecode($RotationInfo.PwdComplexity) } $pwdComplexityDetail = $null $pwdComplexityDecryptFailed = $false if ($pwdComplexityRaw -and $pwdComplexityRaw.Length -gt 0) { $pwdComplexityDecryptFailed = -not [KeeperSecurity.Plugins.PAM.RotationUtils]::TryDecryptPasswordComplexity( $pwdComplexityRaw, $Record.RecordKey, [ref]$pwdComplexityDetail) } $scheduleType = $null $scheduleData = $null $scheduleText = $null if ($Schedule) { $scheduleType = if ($Schedule.NoSchedule) { 'manual' } else { 'scheduled' } $scheduleData = $Schedule.ScheduleData $scheduleText = if ($Schedule.NoSchedule) { 'Manual Rotation' } else { [KeeperSecurity.Plugins.PAM.RotationUtils]::FormatScheduleDisplay($Schedule.ScheduleData, $false) } } $complexityDetailOut = $null $complexityDisplay = $null if ($pwdComplexityDecryptFailed) { $complexityDetailOut = '[decrypt failed]' $complexityDisplay = '[decrypt failed]' } elseif ($null -ne $pwdComplexityDetail) { $complexityDetailOut = convertPamRotationComplexityDetailToHashtable ( [KeeperSecurity.Plugins.PAM.RotationUtils]::PasswordComplexityToDetail($pwdComplexityDetail)) $complexityDisplay = formatPamPasswordComplexityInfoDisplay -Rules $pwdComplexityDetail } $hasComplexity = ($pwdComplexityRaw -and $pwdComplexityRaw.Length -gt 0) $scriptName = $null if (-not [string]::IsNullOrWhiteSpace($RotationInfo.ScriptName)) { $scriptName = [string]$RotationInfo.ScriptName } $useDefaultSchedule = $false if ($null -ne $Vault -and -not [string]::IsNullOrWhiteSpace($configUid)) { $useDefaultSchedule = testPamUsesDefaultRotationSchedule -Vault $Vault -RecordUid $Record.Uid -ConfigUid $configUid } return @{ StatusName = $statusName IsReady = $isReady ConfigUid = $configUid NodeId = $RotationInfo.NodeId GatewayName = $gatewayName GatewayUid = $gatewayUid AdminResourceUid = $adminResourceUid HasComplexity = $hasComplexity PasswordComplexity = if ($hasComplexity) { $RotationInfo.PwdComplexity } else { $null } ComplexityDetail = $complexityDetailOut ComplexityDisplay = $complexityDisplay ScheduleType = $scheduleType ScheduleData = $scheduleData ScheduleText = $scheduleText UseDefaultRotationSchedule = $useDefaultSchedule Disabled = $RotationInfo.Disabled ScriptName = $scriptName } } function script:writePamRotationInfoTable { Param ( [Parameter(Mandatory = $true)] [object] $Model ) if ($Model.IsReady) { Write-Output "Rotation Status: Ready to rotate ($($Model.StatusName))" Write-Output "PAM Config UID: $($Model.ConfigUid)" Write-Output "Node ID: $($Model.NodeId)" Write-Output "Gateway Name: $($Model.GatewayName)" Write-Output "Gateway UID: $($Model.GatewayUid)" if ($Model.AdminResourceUid) { Write-Output "Admin Resource Uid: $($Model.AdminResourceUid)" } if ($Model.HasComplexity) { Write-Output "Password Complexity: $($Model.PasswordComplexity)" if (-not [string]::IsNullOrEmpty($Model.ComplexityDisplay)) { Write-Output "Password Complexity Data: $($Model.ComplexityDisplay)" } } else { Write-Output 'Password Complexity: [not set]' } Write-Output "Is Rotation Disabled: $($Model.Disabled)" if (-not [string]::IsNullOrEmpty($Model.ScheduleText)) { Write-Output "Schedule: $($Model.ScheduleText)" } Write-Output '' Write-Output 'Manual rotation is not supported yet. Coming soon.' } else { Write-Output "Rotation Status: Not ready to rotate ($($Model.StatusName))" } } function script:writePamRotationInfoJson { Param ( [Parameter(Mandatory = $true)] [object] $Model ) $result = [ordered]@{ status = $Model.StatusName ready_to_rotate = $Model.IsReady pam_config_uid = $Model.ConfigUid node_id = $Model.NodeId gateway_name = $Model.GatewayName gateway_uid = $Model.GatewayUid admin_resource_uid = $Model.AdminResourceUid password_complexity = $Model.PasswordComplexity password_complexity_detail = $Model.ComplexityDetail schedule_type = $Model.ScheduleType schedule_data = $Model.ScheduleData use_default_rotation_schedule = [bool]$Model.UseDefaultRotationSchedule disabled = $Model.Disabled script_name = $Model.ScriptName } Write-Output ($result | ConvertTo-Json -Depth 8) } function script:escapePamCsvField { Param ([object] $Value) if ($null -eq $Value) { return '' } $text = $null if (($Value -is [System.Collections.IDictionary]) -or (($Value -is [System.Collections.IEnumerable]) -and -not ($Value -is [string]))) { $text = ($Value | ConvertTo-Json -Compress -Depth 8) } else { $text = [string]$Value } if ($text -match '[,"\r\n]') { return '"' + ($text.Replace('"', '""')) + '"' } return $text } function script:writePamRotationInfoCsv { Param ( [Parameter(Mandatory = $true)] [object] $Model ) $headers = @( 'status', 'ready_to_rotate', 'pam_config_uid', 'node_id', 'gateway_name', 'gateway_uid', 'admin_resource_uid', 'password_complexity', 'password_complexity_detail', 'schedule_type', 'schedule_data', 'use_default_rotation_schedule', 'disabled', 'script_name' ) $values = @( $Model.StatusName $Model.IsReady $Model.ConfigUid $Model.NodeId $Model.GatewayName $Model.GatewayUid $Model.AdminResourceUid $Model.PasswordComplexity $Model.ComplexityDetail $Model.ScheduleType $Model.ScheduleData $Model.UseDefaultRotationSchedule $Model.Disabled $Model.ScriptName ) Write-Output ($headers -join ',') Write-Output (($values | ForEach-Object { escapePamCsvField $_ }) -join ',') } function script:testPamRotationScriptField { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]] $Field ) return ($Field.FieldName -eq 'script' -or $Field.FieldLabel -eq 'rotationScripts') } function script:getPamRotationScriptFields { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record ) $fields = New-Object 'System.Collections.Generic.List[KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]]' foreach ($field in $Record.Fields) { if ($field -is [KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]]) { $scriptField = [KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]]$field if (testPamRotationScriptField -Field $scriptField) { [void]$fields.Add($scriptField) } } } return , $fields } function script:getPamRotationScriptField { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record ) foreach ($field in $Record.Fields) { if ($field -is [KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]]) { $scriptField = [KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]]$field if (testPamRotationScriptField -Field $scriptField) { return $scriptField } } } return $null } function script:getOrCreatePamRotationScriptField { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record ) $scriptField = getPamRotationScriptField -Record $Record if ($scriptField) { return $scriptField } $scriptField = New-Object KeeperSecurity.Vault.TypedField[KeeperSecurity.Vault.FieldScript]('script', 'rotationScripts') $Record.Fields.Add($scriptField) return $scriptField } function script:getPamRotationFileRefUids { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record ) $facade = New-Object KeeperSecurity.Vault.TypedRecordFacade[KeeperSecurity.Vault.TypedRecordFileRef]($Record) $uids = New-Object 'System.Collections.Generic.HashSet[string]' if ($null -ne $facade.Fields.FileRef) { foreach ($uid in $facade.Fields.FileRef.Values) { if (-not [string]::IsNullOrEmpty($uid)) { [void]$uids.Add($uid) } } } return , $uids } function script:resolvePamRotationCredentialUids { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [string[]] $Credentials ) $refs = New-Object 'System.Collections.Generic.List[string]' if (-not $Credentials) { return [string[]]@() } foreach ($credential in $Credentials) { if ([string]::IsNullOrWhiteSpace($credential)) { continue } $keeperRecord = $null if ($Vault.TryGetKeeperRecord($credential.Trim(), [ref]$keeperRecord)) { [void]$refs.Add($keeperRecord.Uid) } } return (toPamStringArray -Collection $refs) } function script:toPamStringArray { Param ($Collection) if ($null -eq $Collection) { return [string[]]@() } return [string[]]@($Collection) } function script:getPamScriptLeafName { Param ([string] $Value) if ([string]::IsNullOrWhiteSpace($Value)) { return '' } $trimmed = $Value.Trim().Trim('"').Trim("'") $normalized = $trimmed.Replace('/', [System.IO.Path]::DirectorySeparatorChar).Replace('\', [System.IO.Path]::DirectorySeparatorChar) try { $leaf = [System.IO.Path]::GetFileName($normalized) if (-not [string]::IsNullOrWhiteSpace($leaf)) { return $leaf } } catch { } return $trimmed } function script:findPamRotationScriptValue { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record, [Parameter(Mandatory = $true)] [string] $ScriptName ) $scriptField = getPamRotationScriptField -Record $Record if (-not $scriptField) { return $null, $null } $input = $ScriptName.Trim().Trim('"').Trim("'") $leaf = getPamScriptLeafName -Value $input $needles = New-Object 'System.Collections.Generic.List[string]' [void]$needles.Add($input) if (-not [string]::IsNullOrWhiteSpace($leaf) -and -not [string]::Equals($leaf, $input, [System.StringComparison]::OrdinalIgnoreCase)) { [void]$needles.Add($leaf) } foreach ($scriptValue in $scriptField.Values) { if ($null -eq $scriptValue -or [string]::IsNullOrEmpty($scriptValue.FileRef)) { continue } foreach ($needle in $needles) { if ([string]::Equals($scriptValue.FileRef, $needle, [System.StringComparison]::OrdinalIgnoreCase)) { return $scriptValue, $scriptField } } $keeperRecord = $null if (-not $Vault.TryGetKeeperRecord($scriptValue.FileRef, [ref]$keeperRecord) -or $null -eq $keeperRecord) { continue } $names = New-Object 'System.Collections.Generic.List[string]' if (-not [string]::IsNullOrEmpty($keeperRecord.Uid)) { [void]$names.Add($keeperRecord.Uid) } if (-not [string]::IsNullOrEmpty($keeperRecord.Title)) { [void]$names.Add($keeperRecord.Title) } $fileRecord = $keeperRecord -as [KeeperSecurity.Vault.FileRecord] if ($fileRecord -and -not [string]::IsNullOrEmpty($fileRecord.Name)) { [void]$names.Add($fileRecord.Name) } foreach ($name in $names) { foreach ($needle in $needles) { if ([string]::Equals($name, $needle, [System.StringComparison]::OrdinalIgnoreCase)) { return $scriptValue, $scriptField } } } } return $null, $scriptField } function script:testPamRotationScriptRecord { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.TypedRecord] $Record ) $typeName = if ($Record.TypeName) { $Record.TypeName } else { '' } return [KeeperSecurity.Plugins.PAM.PamRecordTypes]::Script.Contains($typeName) } function script:testPamRotationRecordPattern { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.KeeperRecord] $Record, [string] $Pattern ) if ([string]::IsNullOrEmpty($Pattern)) { return $true } if ([string]::Equals($Record.Uid, $Pattern, [System.StringComparison]::OrdinalIgnoreCase)) { return $true } return ($null -ne $Record.Title -and $Record.Title.IndexOf($Pattern, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) } function script:getPamRotationRunCommand { Param ( [string] $RunCommand, [string] $ScriptCommand ) if (-not [string]::IsNullOrWhiteSpace($RunCommand)) { return $RunCommand.Trim() } if (-not [string]::IsNullOrWhiteSpace($ScriptCommand)) { $trimmed = $ScriptCommand.Trim() if (-not (testPamRotationScriptVerb -Value $trimmed)) { return $trimmed } } return '' } function script:resolvePamRotationScriptRecordId { Param ( [string] $Record, [string] $RecordUid ) if (-not [string]::IsNullOrWhiteSpace($Record)) { return $Record.Trim() } if (-not [string]::IsNullOrWhiteSpace($RecordUid)) { return $RecordUid.Trim() } return $null } function script:writePamRotationScriptTable { Param ( $Rows ) if ($null -eq $Rows) { return } if ($Rows -is [System.Array]) { $rowArray = $Rows } elseif ($Rows.PSObject.Methods['ToArray']) { $rowArray = $Rows.ToArray() } else { $rowArray = [object[]]::new(0) foreach ($row in $Rows) { $rowArray += $row } } if ($rowArray.Count -eq 0) { return } $table = $rowArray | Select-Object ` @{ Name = 'Record UID'; Expression = { $_.RecordUid } }, ` @{ Name = 'Title'; Expression = { $_.Title } }, ` @{ Name = 'Record Type'; Expression = { $_.RecordType } }, ` @{ Name = 'Script UID'; Expression = { $_.ScriptUid } }, ` @{ Name = 'Script Name'; Expression = { $_.ScriptName } }, ` @{ Name = 'Records'; Expression = { $_.Records } }, ` @{ Name = 'Command'; Expression = { $_.Command } } | Format-Table -AutoSize | Out-String -Width 4096 if (-not [string]::IsNullOrWhiteSpace($table)) { Write-Output $table.TrimEnd() } } function script:getPamRotationScriptListRows { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [string] $Pattern ) $rows = New-Object 'System.Collections.Generic.List[object]' foreach ($record in @($Vault.KeeperRecords)) { $typed = $record -as [KeeperSecurity.Vault.TypedRecord] if (-not $typed -or -not (testPamRotationScriptRecord -Record $typed)) { continue } if (-not (testPamRotationRecordPattern -Record $typed -Pattern $Pattern)) { continue } foreach ($scriptField in (getPamRotationScriptFields -Record $typed)) { foreach ($script in $scriptField.Values) { if ([string]::IsNullOrEmpty($script.FileRef)) { continue } $fileRecord = $null $vault.TryGetKeeperRecord($script.FileRef, [ref]$fileRecord) | Out-Null $recordRefs = if ($script.RecordRef) { ($script.RecordRef -join ', ') } else { '' } $scriptName = if ($fileRecord) { $fileRecord.Title } else { '[inaccessible]' } $rows.Add(@{ RecordUid = $typed.Uid Title = $typed.Title RecordType = $typed.TypeName ScriptUid = $script.FileRef ScriptName = $scriptName Records = $recordRefs Command = if ($script.Command) { $script.Command } else { '' } }) } } } return $rows.ToArray() } function script:resolvePamRotationScriptRecord { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [string] $RecordId ) return resolvePamRotationRecord -Vault $Vault -Identifier $RecordId -AllowedTypes ([KeeperSecurity.Plugins.PAM.PamRecordTypes]::Script) } function Get-KeeperPamRotationList { <# .Synopsis List PAM record rotation schedules. .Description Shows pamUser rotation schedules with record, schedule, gateway, and PAM configuration details. .Parameter VerboseOutput Include Gateway UID and PAM Configuration UID columns. .Example Get-KeeperPamRotationList .Example Get-KeeperPamRotationList -VerboseOutput pam-rot-list -v #> [CmdletBinding()] Param ( [Alias('v')] [switch] $VerboseOutput ) $plugin = ensurePamPlugin if (-not $plugin) { Write-Error -Message 'PAM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $vault = getPamRotationVault $auth = getPamEnterpriseAuth $rows = @(getPamRotationScheduleRows -Plugin $plugin -Vault $vault -Auth $auth -VerboseOutput $VerboseOutput.IsPresent) if ($rows.Count -eq 0) { Write-Output 'No pamUser rotation schedules found.' return } writePamRotationListTable -Rows $rows -VerboseOutput $VerboseOutput.IsPresent Write-Output '' Write-Output 'Manual rotation is not supported yet. Coming soon.' } function Get-KeeperPamRotationInfo { <# .Synopsis Show rotation status for a PAM record. .Description Displays readiness, gateway, PAM config, schedule, password complexity, and disabled state. .Parameter Record Record UID, name, or title. .Parameter RecordUid Record UID alias for -Record. .Parameter Format Output format: table (default), csv, or json. .Example Get-KeeperPamRotationInfo -Record "<uid>" .Example Get-KeeperPamRotationInfo -r "My PAM User" -Format json pam-rot-info -r "<uid>" pam-rot-info -RecordUid "<uid>" -Format csv #> [CmdletBinding()] Param ( [Alias('r')] [string] $Record, [Alias('record-uid')] [string] $RecordUid, [ValidateSet('table', 'csv', 'json')] [string] $Format = 'table' ) $recordId = if (-not [string]::IsNullOrWhiteSpace($Record)) { $Record } else { $RecordUid } if ([string]::IsNullOrWhiteSpace($recordId)) { Write-Output '--record or --record-uid is required' return } $vault = getVault if (-not $vault) { Write-Error -Message 'Vault is not available.' -ErrorAction Stop } $plugin = ensurePamPlugin -SyncIfNeeded $false $auth = getPamEnterpriseAuth try { $resolved = resolvePamRotationRecord -Vault $vault -Identifier $recordId -AllowedTypes ([KeeperSecurity.Plugins.PAM.PamRecordTypes]::Rotation) } catch [System.InvalidOperationException] { return } if (-not $resolved) { Write-Output "Record '$recordId' not found" return } $rotationInfo = [KeeperSecurity.Plugins.PAM.RotationUtils]::GetRotationInfoAsync($auth, $resolved.Uid).GetAwaiter().GetResult() $schedulesResponse = [KeeperSecurity.Plugins.PAM.RouterUtils]::GetRotationSchedulesAsync($auth).GetAwaiter().GetResult() $schedule = $null if ($null -ne $schedulesResponse -and $null -ne $schedulesResponse.Schedules) { foreach ($item in $schedulesResponse.Schedules) { if ((encodePamByteString $item.RecordUid) -eq $resolved.Uid) { $schedule = $item break } } } $model = getPamRotationInfoModel -RotationInfo $rotationInfo -Record $resolved -Schedule $schedule -Plugin $plugin -Vault $vault switch ($Format) { 'json' { writePamRotationInfoJson -Model $model } 'csv' { writePamRotationInfoCsv -Model $model } default { writePamRotationInfoTable -Model $model } } } function Set-KeeperPamRotation { <# .Synopsis Create or update PAM record rotation configuration. .Description Create or update rotation settings for a PAM record. Supports single record (-Record) or bulk folder setup (-Folder). .Parameter Record Record UID, name, or pattern to configure for rotation. .Parameter Folder Folder UID or name for bulk rotation setup. .Parameter Force Skip confirmation prompts. .Parameter Config UID or title of the PAM configuration record. .Parameter IamAadConfig UID of a PAM configuration for IAM or Azure AD users (instead of -Resource). .Parameter Resource UID or title of the admin resource record. .Parameter ScheduleJson Rotation schedule as JSON. Example: '{"type": "WEEKLY", "weekday": "SATURDAY", "time": "22:00", "tz": "America/New_York"}' .Parameter ScheduleCron CRON schedule string (6-field rotation format). .Parameter OnDemand Configure manual on-demand rotation. .Parameter ScheduleConfig Inherit schedule from the PAM configuration record. .Parameter ScheduleOnly Update only the rotation schedule without changing other settings. .Parameter Complexity Password complexity CSV: length,upper,lower,digits,symbols[,special-chars]. Example: 20,1,4,2,2,.=+- .Parameter ComplexityJson Password complexity rules as JSON (alternative to -Complexity). .Parameter AdminUser PAM user record UID to set as admin credential on the resource. .Parameter Enable Enable rotation for the record. .Parameter Disable Disable rotation for the record. .Parameter RotationProfile Optional rotation profile: general, iam_user, scripts_only, saas. .Parameter SaasConfigUid SaaS configuration UID when using the saas rotation profile. .Example Set-KeeperPamRotation -Record "My PAM User" -Config "AWS Config" -Resource "My Server" -OnDemand .Example Set-KeeperPamRotation -r "<uid>" -sj '{"type": "MONTHLY_BY_DAY", "monthDay": 1, "time": "04:00", "tz": "America/Chicago"}' -c "<config-uid>" -rs "<resource-uid>" .Example Set-KeeperPamRotation -r "<uid>" -x "20,1,4,2,2,.=+-" -c "<config-uid>" -rs "<resource-uid>" -Force .Example Set-KeeperPamRotation -Folder "<folder-uid>" -c "<config-uid>" -od -so -Force #> [CmdletBinding(PositionalBinding = $false)] Param ( [Alias('r')] [string] $Record, [Alias('fd')] [string] $Folder, [Alias('c')] [string] $Config, [Alias('iac')] [string] $IamAadConfig, [Alias('rp')] [string] $RotationProfile, [string] $SaasConfigUid, [Alias('rs')] [string] $Resource, [Alias('sj')] [string] $ScheduleJson, [Alias('sc')] [string] $ScheduleCron, [Alias('od')] [switch] $OnDemand, [Alias('sf')] [switch] $ScheduleConfig, [Alias('so')] [switch] $ScheduleOnly, [Alias('x')] [string] $Complexity, [string] $ComplexityJson, [Alias('a')] [string] $AdminUser, [Alias('e')] [switch] $Enable, [Alias('d')] [switch] $Disable, [Alias('f')] [switch] $Force ) if ([string]::IsNullOrWhiteSpace($Record) -and [string]::IsNullOrWhiteSpace($Folder)) { Write-Output '--record or --folder is required' return } if ($Enable.IsPresent -and $Disable.IsPresent) { Write-Output 'Cannot use both --enable and --disable at the same time.' return } if (-not [string]::IsNullOrWhiteSpace($Record) -and -not [string]::IsNullOrWhiteSpace($Folder)) { Write-Output 'Cannot use both --record and --folder at the same time.' return } if (-not [string]::IsNullOrWhiteSpace($Resource) -and -not [string]::IsNullOrWhiteSpace($IamAadConfig)) { Write-Output 'Cannot use both --resource and --iam-aad-config at once. --resource configures users on a resource; --iam-aad-config configures IAM/Azure AD users.' return } $plugin = ensurePamPlugin if (-not $plugin) { Write-Error -Message 'PAM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $vault = getPamRotationVault $auth = getPamEnterpriseAuth invokeKeeperPamRotationEdit -Auth $auth -Vault $vault -Options @{ Record = $Record Folder = $Folder Config = $Config IamAadConfig = $IamAadConfig RotationProfile = $RotationProfile SaasConfigUid = $SaasConfigUid Resource = $Resource ScheduleJson = $ScheduleJson ScheduleCron = $ScheduleCron OnDemand = $OnDemand.IsPresent ScheduleConfig = $ScheduleConfig.IsPresent ScheduleOnly = $ScheduleOnly.IsPresent Complexity = $Complexity ComplexityJson = $ComplexityJson AdminUser = $AdminUser Enable = $Enable.IsPresent Disable = $Disable.IsPresent Force = $Force.IsPresent } } function Get-KeeperPamRotationScript { <# .Synopsis List post-rotation scripts on PAM records. .Description Lists post-rotation scripts attached to PAM records. .Parameter Pattern Record UID or title filter. .Parameter Record Alias for filtering by record UID or title (same as -Pattern). .Example Get-KeeperPamRotationScript Get-KeeperPamRotationScript -Pattern "My PAM User" #> [CmdletBinding()] Param ( [string] $Pattern, [Alias('r')] [string] $Record ) $vault = getVault if (-not $vault) { Write-Error -Message 'Vault is not available.' -ErrorAction Stop } $filter = if (-not [string]::IsNullOrWhiteSpace($Pattern)) { $Pattern.Trim() } else { $Record } $rows = @(getPamRotationScriptListRows -Vault $vault -Pattern $filter) if ($rows.Count -eq 0) { Write-Output 'No post-rotation scripts found.' return } writePamRotationScriptTable -Rows $rows } function Add-KeeperPamRotationScript { <# .Synopsis Upload and attach a post-rotation script to a PAM record. .Description Uploads a local script file and attaches it as a post-rotation script. .Parameter Record Record UID or title (pamUser / pamDirectory). .Parameter Script Local file path to upload. .Parameter RunCommand Command line to run the script after rotation. .Parameter ScriptCommand Alias for -RunCommand (unless value is a script subcommand name). .Parameter AddCredential Record UID(s) with rotation credentials to link. .Example Add-KeeperPamRotationScript -Record "<uid>" -Script "C:\scripts\rotate.ps1" -RunCommand "powershell -File rotate.ps1" Add-KeeperPamRotationScript -Record "<uid>" -Script "/tmp/rotate.sh" -RunCommand "bash rotate.sh" Add-KeeperPamRotationScript fajHNL7_T3qsuzt2MDGEiw -Script "C:\scripts\rotate.ps1" #> [CmdletBinding()] Param ( [Parameter(Position = 0)] [Alias('r')] [string] $Record, [Alias('record-uid')] [string] $RecordUid, [Parameter(Position = 1)] [string] $Script, [Alias('script-command')] [string] $ScriptCommand, [Alias('run-command')] [string] $RunCommand, [Alias('add-credential', 'ac')] [string[]] $AddCredential ) $recordId = resolvePamRotationScriptRecordId -Record $Record -RecordUid $RecordUid if ([string]::IsNullOrWhiteSpace($recordId)) { Write-Output '--record is required (or provide record UID as a positional argument)' return } if ([string]::IsNullOrWhiteSpace($Script)) { Write-Output '--script is required' return } $vault = getVault if (-not $vault) { Write-Error -Message 'Vault is not available.' -ErrorAction Stop } try { $resolved = resolvePamRotationScriptRecord -Vault $vault -RecordId $recordId } catch [System.InvalidOperationException] { return } if (-not $resolved) { Write-Output "Record '$recordId' not found" return } $filePath = [Environment]::ExpandEnvironmentVariables($Script.Trim()) if (-not (Test-Path -LiteralPath $filePath)) { Write-Output "File `"$Script`" not found." return } $runCommand = getPamRotationRunCommand -RunCommand $RunCommand -ScriptCommand $ScriptCommand $scriptField = getOrCreatePamRotationScriptField -Record $resolved $preRefs = getPamRotationFileRefUids -Record $resolved if ($null -eq $preRefs) { $preRefs = New-Object 'System.Collections.Generic.HashSet[string]' } $uploadTask = New-Object KeeperSecurity.Vault.FileAttachmentUploadTask($filePath, $null, $true) try { $vault.UploadAttachment($resolved, $uploadTask).GetAwaiter().GetResult() | Out-Null } catch { Write-Output $_.Exception.Message return } finally { if ($uploadTask) { $uploadTask.Dispose() } } $postRefs = getPamRotationFileRefUids -Record $resolved if ($null -eq $postRefs) { $postRefs = New-Object 'System.Collections.Generic.HashSet[string]' } $newUids = @( $postRefs | Where-Object { -not [string]::IsNullOrEmpty($_) -and -not $preRefs.Contains($_) } ) if ($newUids.Count -ne 1) { Write-Output 'Failed to determine uploaded script file UID. Only the record owner can attach post-rotation scripts.' return } $facade = New-Object KeeperSecurity.Vault.TypedRecordFacade[KeeperSecurity.Vault.TypedRecordFileRef]($resolved) if ($null -ne $facade.Fields.FileRef) { [void]$facade.Fields.FileRef.Values.Remove($newUids[0]) } $scriptValue = New-Object KeeperSecurity.Vault.FieldScript $scriptValue.FileRef = $newUids[0] $scriptValue.RecordRef = resolvePamRotationCredentialUids -Vault $vault -Credentials $AddCredential $scriptValue.Command = $runCommand $scriptField.Values.Add($scriptValue) if (-not (updatePamRotationScriptRecord -Vault $vault -Record $resolved -Action add)) { return } Write-Output "Script added to record '$($resolved.Title)' ($($resolved.Uid))." } function Set-KeeperPamRotationScript { <# .Synopsis Update a post-rotation script on a PAM record. .Description Updates an existing post-rotation script on a PAM record. .Parameter Record Record UID or title. .Parameter Script Script file UID or name to update. .Parameter RunCommand New command line to run the script. .Parameter AddCredential Credential record UID(s) to link. .Parameter RemoveCredential Credential record UID(s) to unlink. .Example Set-KeeperPamRotationScript -Record "<uid>" -Script "rotate.ps1" -RunCommand "powershell -File rotate.ps1" -ac @("<cred-uid>") Set-KeeperPamRotationScript fajHNL7_T3qsuzt2MDGEiw -Script E5HGAlv7lHIkGa5PzPXgbQ -RunCommand "powershell -File rotate_password_v2.ps1" #> [CmdletBinding()] Param ( [Parameter(Position = 0)] [Alias('r')] [string] $Record, [Alias('record-uid')] [string] $RecordUid, [Parameter(Position = 1)] [string] $Script, [Alias('script-command')] [string] $ScriptCommand, [Alias('run-command')] [string] $RunCommand, [Alias('add-credential', 'ac')] [string[]] $AddCredential, [Alias('remove-credential', 'rc')] [string[]] $RemoveCredential ) $recordId = resolvePamRotationScriptRecordId -Record $Record -RecordUid $RecordUid if ([string]::IsNullOrWhiteSpace($recordId)) { Write-Output '--record is required (or provide record UID as a positional argument)' return } if ([string]::IsNullOrWhiteSpace($Script)) { Write-Output '--script is required' return } $vault = getVault if (-not $vault) { Write-Error -Message 'Vault is not available.' -ErrorAction Stop } try { $resolved = resolvePamRotationScriptRecord -Vault $vault -RecordId $recordId } catch [System.InvalidOperationException] { return } if (-not $resolved) { Write-Output "Record '$recordId' not found" return } $scriptValue, $scriptField = findPamRotationScriptValue -Vault $vault -Record $resolved -ScriptName $Script.Trim() if (-not $scriptField) { Write-Output "Record '$($resolved.Title)' has no rotation scripts." return } if (-not $scriptValue) { Write-Output "Record '$($resolved.Title)' does not have script '$Script'. Use Script UID or name from Get-KeeperPamRotationScript (file path basename is also accepted)." return } $modified = $false $refs = New-Object 'System.Collections.Generic.HashSet[string]' if ($scriptValue.RecordRef) { foreach ($item in $scriptValue.RecordRef) { if (-not [string]::IsNullOrEmpty($item)) { [void]$refs.Add($item) } } } if ($RemoveCredential) { foreach ($cred in (resolvePamRotationCredentialUids -Vault $vault -Credentials $RemoveCredential)) { if ($refs.Remove($cred)) { $modified = $true } } } if ($AddCredential) { foreach ($cred in (resolvePamRotationCredentialUids -Vault $vault -Credentials $AddCredential)) { if ($refs.Add($cred)) { $modified = $true } } } if ($modified) { $scriptValue.RecordRef = toPamStringArray -Collection $refs } $runCommand = getPamRotationRunCommand -RunCommand $RunCommand -ScriptCommand $ScriptCommand if (-not [string]::IsNullOrWhiteSpace($runCommand)) { $scriptValue.Command = $runCommand $modified = $true } if (-not $modified) { Write-Output 'Nothing to do' return } if (-not (updatePamRotationScriptRecord -Vault $vault -Record $resolved -Action edit)) { return } Write-Output "Script updated on record '$($resolved.Title)' ($($resolved.Uid))." } function Remove-KeeperPamRotationScript { <# .Synopsis Remove a post-rotation script from a PAM record. .Description Removes a post-rotation script from a PAM record. .Parameter Record Record UID or title. .Parameter Script Script file UID or name to remove. .Example Remove-KeeperPamRotationScript -Record "<uid>" -Script "rotate.ps1" Remove-KeeperPamRotationScript fajHNL7_T3qsuzt2MDGEiw -Script CTUH9gBqQK_iJoph_APtNQ #> [CmdletBinding()] Param ( [Parameter(Position = 0)] [Alias('r')] [string] $Record, [Alias('record-uid')] [string] $RecordUid, [Parameter(Position = 1)] [string] $Script ) $recordId = resolvePamRotationScriptRecordId -Record $Record -RecordUid $RecordUid if ([string]::IsNullOrWhiteSpace($recordId)) { Write-Output '--record is required (or provide record UID as a positional argument)' return } if ([string]::IsNullOrWhiteSpace($Script)) { Write-Output '--script is required' return } $vault = getVault if (-not $vault) { Write-Error -Message 'Vault is not available.' -ErrorAction Stop } try { $resolved = resolvePamRotationScriptRecord -Vault $vault -RecordId $recordId } catch [System.InvalidOperationException] { return } if (-not $resolved) { Write-Output "Record '$recordId' not found" return } $scriptValue, $scriptField = findPamRotationScriptValue -Vault $vault -Record $resolved -ScriptName $Script.Trim() if (-not $scriptField) { Write-Output "Record '$($resolved.Title)' has no rotation scripts." return } if (-not $scriptValue) { Write-Output "Record '$($resolved.Title)' does not have script '$Script'." return } [void]$scriptField.Values.Remove($scriptValue) if (-not (updatePamRotationScriptRecord -Vault $vault -Record $resolved -Action remove)) { return } Write-Output "Script removed from record '$($resolved.Title)' ($($resolved.Uid))." } New-Alias -Name pam-rotation-list -Value Get-KeeperPamRotationList -ErrorAction SilentlyContinue New-Alias -Name pam-rot-list -Value Get-KeeperPamRotationList -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-info -Value Get-KeeperPamRotationInfo -ErrorAction SilentlyContinue New-Alias -Name pam-rot-info -Value Get-KeeperPamRotationInfo -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-edit -Value Set-KeeperPamRotation -ErrorAction SilentlyContinue New-Alias -Name pam-rot-edit -Value Set-KeeperPamRotation -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-new -Value Set-KeeperPamRotation -ErrorAction SilentlyContinue New-Alias -Name pam-rot-new -Value Set-KeeperPamRotation -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-script-list -Value Get-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rot-script-list -Value Get-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-script-add -Value Add-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rot-script-add -Value Add-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-script-edit -Value Set-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rot-script-edit -Value Set-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rotation-script-delete -Value Remove-KeeperPamRotationScript -ErrorAction SilentlyContinue New-Alias -Name pam-rot-script-delete -Value Remove-KeeperPamRotationScript -ErrorAction SilentlyContinue # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBBv/3vAFdnT4ns # fYY3Ps9WO3UN+dfN+O7HSwy+cscKa6CCITswggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQg # MjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0Cit # eLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS # 1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZM # swEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBC # Mo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3 # /hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26j # q22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5 # OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo # 7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzU # tw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwm # KwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NP # TLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkq # hkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDK # r2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvda # qpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+ # lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7a # brs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNS # y0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeK # iwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYb # KyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8Q # xAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVm # zGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCn # HwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4w # gga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1 # c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0Zo # dLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi # 6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNg # xVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiF # cMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJ # m/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvS # GmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1 # ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9 # MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7 # Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bG # RinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6 # X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAd # BgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJx # XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy # bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL # BQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxj # aaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0 # hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0 # F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnT # mpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKf # ZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzE # wlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbh # OhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOX # gpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EO # LLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wG # WqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWg # AwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3 # zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8Tch # TySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWj # FDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2Uo # yrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjP # KHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KS # uNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7w # JNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vW # doUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOg # rY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K # 096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCf # gPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zy # Me39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZ # D9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ # ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu # +WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4o # bEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2h # ECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasn # M9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol # /DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgY # xQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3oc # CVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcB # ZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCB0kwggUx # oAMCAQICEAHdzU+FVN9jCMv0HhHagNUwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MTAeFw0yNjA2MDUwMDAwMDBaFw0yNzA2MDQyMzU5NTlaMIHRMRMwEQYLKwYBBAGC # NzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMR0wGwYDVQQPDBRQ # cml2YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQwNzk4NTELMAkGA1UEBhMC # VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK # ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5 # IEluYy4wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCb4DRTV0sNQsa1 # 0YRh+bliabmLOVYr6S0+BSVvRJAN3SHP6x52i1Dkpki5xVDIH06ZnnsToVrgvTv+ # QxGwsn9SAPHEZ/PIJRFxbMR4ShDaptYyL4f0u4k/3HwRzIleWE4mTUonYH8BdgLw # /F53B7wa7VTDHtxXltYTibEOwJxYCOi4Zr2FYQhjw14/CHcqS3FSMs6YYU2T56+g # w819hQM3K0YlwTNOFoIm1v7/ZZZiJGH8uGDsvy1makh1Xyyo/wN8EbQ1nbslmePT # roPm9w7WqiP/yiq+CZHiuTk9JK5bEgkWG3ns+v25cI251WidJx3SU7IZnX0OTd6/ # ZdKhprD5Gcfy5GBbJdcYw2WycQRW0PT5BEt55xRE0heufkpDaTUN6RdOuJdXbkl0 # hV91IZIuhueEMCk3h5mDTlU5gImxqj0R/TbAxjSSGTKCeuYFkQIRqytSabdrZZ48 # kW5hOIZMVDY1f4kpPJa8UeEvDZXT3vrtj36aSJrwez2uh4FMNlkCAwEAAaOCAgIw # ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBT1 # SmCYU/7Yrz1fX66Ur5nSzlSYOzA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQBcavcUHNFEg872HDRq2+hRlnvaghCXv7X/6h9HSzjAQP3rt95BZty3ASqi # 2MYyGQLGdDl4DToe/WhajtEOBOYa83agW6tBvrfcKRrDrwJOMPTbwNYvn+GuiL4T # CKzXaytWiJJbrc5odc7Ecat2ZvJylpPmNainr4Q0LzzH23Gea/Mm/hIJTN4IGgrH # hrXiTIIW/ZUzrY6g8b3RZB4BA497n43wNdSqP+C3ntFw6NiGB4Z25SW4YntIxYPv # Kf37OVhF0xqxLC1sK/XxgK0EGQ6iaj8Ncpr2C5vSNZqfW2MndxOA1W67pgDpg83k # UWG+/YJeGhqOTF82/0kIzQXeI/lIqbnL/IJAJqSm/ROSpsGUKVbzk03cpTD55ZQX # WjM0fLirypBqY05T8gnh1L0fSwxr/SwJZ8OddivgyK1YOMn02nnsEG5kxBt9cMX4 # JCYABhypmAVDRvyYifEVdoFWv2gAXXW+PPRvlNa6E4aMCZrVcoKHiyeMAXOi1IC9 # mHvC2+foTSMFueq3AdnYfeKnZnAiKXKRhXcdHbQYcR2A7AIzIcqahPYr4FNEgb/E # /y/kypAkf0rMHlYl1kNqLs2Nv1UnMEHYT5YmDVLO63+1Trcw4zTZ70zuqIqeID/d # nbOlgtyG6DSRCL7f0E7kP18f4RoX5i1PkfeO4VJHsAuCeNG1qjGCBdkwggXVAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQAd3NT4VU32MIy/QeEdqA1TANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCCgDoVgYUaWa139rEqCW5PUFppt+6ltzd53UJNOOMQauzANBgkqhkiG9w0B # AQEFAASCAYBw/MzcIcp9yG4EJXFWtFBJKCyu+/aomFPPsK7Wg9u85u308O5/1ilF # XjsJC2i3GucDRjExshRxYYIVWcXzS60cDTIsx7uikYMivcxJVZsqh07U9DlcIaL5 # 6kwFjQrcqGfbn9tnd5fwaLRLwL+fYkDtc+eCXXGkhSTM3W0CUNNTPfcxkjCxnQTD # iHq3s/H831GsgAY0A8brZbIo2LKXT0J/bslV0Xfh9TxMfEIUh7JEXgOlFR5x38TW # aavJOnEUGB+58Shzdg//A75MnfMGVSUdLsdHXSSRg0dqb1zsmJ+8RaMvj9Zh+bYe # I4goNqOROicifLdwImjpOVsHcN++5q9w+CXoL8J0anD/W86pR8WAJs9h8E90gvvv # Rw+u3K1/+fNadyTQCGlNYeso8d/ogwEkX+XkQn0b1LvvkofHfk0mwSTSHXXUsNUR # aV/BG+p8IxO9nEv06i05oXf17cxdC6OURU3LJddmCW2GaRtMCc5WN5u3/K3oLLLt # EglHkZOVf2ShggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwODE0MjExOTIyWjAv # BgkqhkiG9w0BCQQxIgQgCD1P3hcZpOrfUeVf2PESoYYl79w8j30JbxY4fF3zc4Uw # DQYJKoZIhvcNAQEBBQAEggIAFVOzWiu8fj6ay3DpqKMn6+tR0vscIX4ifPlvCHW7 # a9OgW58PJOGmutj/IX3Q/doE/Ry4bO6FpSU3qDDq+5ur61pPgXbe24HnpRWA++YH # zYKPv7LcBEPZRW5bDVzWt8ZusM1o2pXlKhKYp9Iql10rmvGXMp9HNgDPYhmtmCir # OiGkeWXQNuyvz1u+feqfWZLz/zDKgwXVTbO6LJB4lUDZowLo8DC7RgwWzCzSHT64 # ij2owULtekUJnY1LW7ABcHHsVY1M9iGeWjwcn5f/snPbMa7dp5tSivn+PCVWrNTA # BnEZfzptYBQQGgQcfaaeETXpsTDm4Rg+4Tb407GbsMV6f8JBzTS0bIo653PHaMjF # OKN6LgC6gq3Yl0k2goyNDWAyLBG2n8uzhLCF6RU/Tc6Bi2P4zUsaqy7k0CM0b/tz # vYlS7ZqjHyCaDTXpk6yZ/L6l8ohCRlteA7VBMvS6O1nE+EHr31Qz1e85sXsU65jj # rqU4bAoXa1LSdNDfyF+Z2YxXxLWJ/K1DXX8wr0JMbXmkTGw8MX9iuoG8xSu8sian # v2EHigaWZXU2yPBNpvKbLDyJhSOMFOZOA9XaaJr0eSrWjRkFWKngA1SDD7y2gm7A # FxEr9B7zXeXsQOTTWvXr+f7dRcw4wkE0FT9vsI5Wv/SWD3p10/epoCIP4Ff1Q6Yy # yBE= # SIG # End signature block |