helpers/discovery/DiscoveryProvider-Fortinet.ps1

<#
.SYNOPSIS
    Fortinet FortiGate discovery provider for infrastructure monitoring.
 
.DESCRIPTION
    Registers a Fortinet discovery provider that queries FortiOS REST API
    to discover system status, resources, interfaces, VPN tunnels, and
    SD-WAN health, then builds a monitor plan.
    Works standalone or with WUG integration.
 
    Active Monitors (up/down):
      - System status via /api/v2/monitor/system/status
      - HA peer health via /api/v2/monitor/system/ha-peer
      - License validity via /api/v2/monitor/license/status
      - VPN tunnel status via /api/v2/monitor/vpn/ipsec
 
    Performance Monitors (stats over time):
      - CPU utilization via /api/v2/monitor/system/resource/usage
      - Memory utilization via resource/usage
      - Active session count via resource/usage
      - Session setup rate via resource/usage
      - Disk utilization via resource/usage
 
    Authentication:
      FortiGate uses API tokens (bearer tokens). The token is stored
      securely in the DPAPI vault for live discovery and in the WUG
      credential store for ongoing monitoring. Monitor definitions use
      the WUG context variable %Credential.Password% so the plaintext
      token is never embedded in monitor configurations.
 
    Prerequisites:
      1. FortiGate device exists in WUG (or will be created)
      2. FortiGate admin with REST API token generated
      3. Device attribute 'DiscoveryHelper.Fortinet' = 'true'
      4. REST API credential assigned to device (token as password)
 
.NOTES
    Author: Jason Alberino (jason@wug.ninja)
    Requires: DiscoveryHelpers.ps1 loaded first
    Encoding: UTF-8 with BOM
 
    FortiGate API Token Security:
    - The API token is stored in the DPAPI vault (encrypted) for discovery.
    - In WUG, the token is stored in the WUG credential store (not in
      device attributes or monitor params). Monitor definitions reference
      it via %Credential.Password% — the plaintext token is never exposed.
    - The token should be scoped to read-only API access on the FortiGate.
#>


# Ensure DiscoveryHelpers is available
if (-not (Get-Command -Name 'Register-DiscoveryProvider' -ErrorAction SilentlyContinue)) {
    $discoveryPath = Join-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) 'DiscoveryHelpers.ps1'
    if (Test-Path $discoveryPath) {
        . $discoveryPath
    }
    else {
        throw "DiscoveryHelpers.ps1 not found. Load it before this provider."
    }
}

# ============================================================================
# Fortinet Discovery Provider
# ============================================================================

Register-DiscoveryProvider -Name 'Fortinet' `
    -MatchAttribute 'DiscoveryHelper.Fortinet' `
    -AuthType 'BearerToken' `
    -DefaultPort 443 `
    -DefaultProtocol 'https' `
    -IgnoreCertErrors $true `
    -DiscoverScript {
        param($ctx)

        $items = @()
        $baseUri = $ctx.BaseUri
        $deviceId = $ctx.DeviceId
        $deviceName = $ctx.DeviceName
        $ignoreCert = if ($ctx.IgnoreCertErrors) { '1' } else { '0' }
        $attrValue = $ctx.AttributeValue

        # ================================================================
        # Auth: resolve token for live API calls (in-memory only)
        # ================================================================
        # Token is from $ctx.Credential.ApiToken (preferred) or legacy
        # $ctx.AttributeValue (backward compat). Never stored in monitors.
        $tokenValue = $null
        if ($ctx.Credential -and $ctx.Credential.ApiToken) {
            $tokenValue = $ctx.Credential.ApiToken
        }
        elseif ($attrValue -and $attrValue -ne 'true' -and $attrValue.Length -gt 10) {
            $tokenValue = $attrValue
        }

        # ================================================================
        # WUG Monitor Auth: use credential variable reference
        # ================================================================
        # Monitor definitions use %Credential.Password% so WUG resolves
        # the token from the credential store at poll time. The plaintext
        # token is NEVER embedded in monitor params.
        $credPwdVar    = '%Credential.Password%'
        $tplAuthHeader = "Authorization:Bearer ${credPwdVar}"
        $useAnonymous  = '1'  # Auth via custom header, not basic auth

        # ================================================================
        # 1. ACTIVE MONITOR — System Status (overall health check)
        # ================================================================
        $statusUrl = "${baseUri}/api/v2/monitor/system/status"

        $items += New-DiscoveredItem `
            -Name "FortiGate System Status [$deviceName]" `
            -ItemType 'ActiveMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                  = $statusUrl
                RestApiMethod               = 'GET'
                RestApiTimeoutMs            = 10000
                RestApiIgnoreCertErrors     = $ignoreCert
                RestApiUseAnonymous         = $useAnonymous
                RestApiCustomHeader         = $tplAuthHeader
                RestApiDownIfResponseCodeIsIn = '[400,401,403,404,500,502,503]'
                RestApiComparisonList       = '[]'
            } `
            -UniqueKey "Fortinet:${deviceId}:active:sysstatus" `
            -DeviceId $deviceId `
            -Attributes @{
                'Fortinet.MonitorType' = 'System Status'
                'DiscoveryHelper.Fortinet.LastRun' = (Get-Date).ToUniversalTime().ToString('o')
            } `
            -Tags @('fortinet', 'active', 'system')

        # ================================================================
        # 2. ACTIVE MONITOR — License Status
        # ================================================================
        $licenseUrl = "${baseUri}/api/v2/monitor/license/status"

        $items += New-DiscoveredItem `
            -Name "FortiGate License Status [$deviceName]" `
            -ItemType 'ActiveMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                  = $licenseUrl
                RestApiMethod               = 'GET'
                RestApiTimeoutMs            = 10000
                RestApiIgnoreCertErrors     = $ignoreCert
                RestApiUseAnonymous         = $useAnonymous
                RestApiCustomHeader         = $tplAuthHeader
                RestApiDownIfResponseCodeIsIn = '[400,401,403,404,500,502,503]'
                RestApiComparisonList       = '[]'
            } `
            -UniqueKey "Fortinet:${deviceId}:active:license" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'active', 'license')

        # ================================================================
        # 3. ACTIVE MONITOR — HA Peer Health
        # ================================================================
        $haUrl = "${baseUri}/api/v2/monitor/system/ha-peer"

        $items += New-DiscoveredItem `
            -Name "FortiGate HA Health [$deviceName]" `
            -ItemType 'ActiveMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                  = $haUrl
                RestApiMethod               = 'GET'
                RestApiTimeoutMs            = 10000
                RestApiIgnoreCertErrors     = $ignoreCert
                RestApiUseAnonymous         = $useAnonymous
                RestApiCustomHeader         = $tplAuthHeader
                RestApiDownIfResponseCodeIsIn = '[400,401,403,500,502,503]'
                RestApiComparisonList       = '[]'
            } `
            -UniqueKey "Fortinet:${deviceId}:active:ha" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'active', 'ha')

        # ================================================================
        # 4. ACTIVE MONITOR — VPN Tunnel Status
        # ================================================================
        $vpnUrl = "${baseUri}/api/v2/monitor/vpn/ipsec"

        $items += New-DiscoveredItem `
            -Name "FortiGate VPN Tunnels [$deviceName]" `
            -ItemType 'ActiveMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                  = $vpnUrl
                RestApiMethod               = 'GET'
                RestApiTimeoutMs            = 10000
                RestApiIgnoreCertErrors     = $ignoreCert
                RestApiUseAnonymous         = $useAnonymous
                RestApiCustomHeader         = $tplAuthHeader
                RestApiDownIfResponseCodeIsIn = '[400,401,403,500,502,503]'
                RestApiComparisonList       = '[]'
            } `
            -UniqueKey "Fortinet:${deviceId}:active:vpn" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'active', 'vpn')

        # ================================================================
        # 5. PERFORMANCE MONITOR — CPU Utilization
        # ================================================================
        $resourceUrl = "${baseUri}/api/v2/monitor/system/resource/usage?interval=1-min"

        $items += New-DiscoveredItem `
            -Name "FortiGate CPU [$deviceName]" `
            -ItemType 'PerformanceMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                = $resourceUrl
                RestApiJsonPath           = '$.results.cpu[0].current'
                RestApiHttpMethod         = 'GET'
                RestApiHttpTimeoutMs      = 10000
                RestApiIgnoreCertErrors   = $ignoreCert
                RestApiUseAnonymousAccess = $useAnonymous
                RestApiCustomHeader       = $tplAuthHeader
            } `
            -UniqueKey "Fortinet:${deviceId}:perf:cpu" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'performance', 'cpu')

        # ================================================================
        # 6. PERFORMANCE MONITOR — Memory Utilization
        # ================================================================
        $items += New-DiscoveredItem `
            -Name "FortiGate Memory [$deviceName]" `
            -ItemType 'PerformanceMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                = $resourceUrl
                RestApiJsonPath           = '$.results.mem[0].current'
                RestApiHttpMethod         = 'GET'
                RestApiHttpTimeoutMs      = 10000
                RestApiIgnoreCertErrors   = $ignoreCert
                RestApiUseAnonymousAccess = $useAnonymous
                RestApiCustomHeader       = $tplAuthHeader
            } `
            -UniqueKey "Fortinet:${deviceId}:perf:memory" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'performance', 'memory')

        # ================================================================
        # 7. PERFORMANCE MONITOR — Active Sessions
        # ================================================================
        $items += New-DiscoveredItem `
            -Name "FortiGate Sessions [$deviceName]" `
            -ItemType 'PerformanceMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                = $resourceUrl
                RestApiJsonPath           = '$.results.session[0].current'
                RestApiHttpMethod         = 'GET'
                RestApiHttpTimeoutMs      = 10000
                RestApiIgnoreCertErrors   = $ignoreCert
                RestApiUseAnonymousAccess = $useAnonymous
                RestApiCustomHeader       = $tplAuthHeader
            } `
            -UniqueKey "Fortinet:${deviceId}:perf:sessions" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'performance', 'sessions')

        # ================================================================
        # 8. PERFORMANCE MONITOR — Session Setup Rate
        # ================================================================
        $items += New-DiscoveredItem `
            -Name "FortiGate Setup Rate [$deviceName]" `
            -ItemType 'PerformanceMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                = $resourceUrl
                RestApiJsonPath           = '$.results.setuprate[0].current'
                RestApiHttpMethod         = 'GET'
                RestApiHttpTimeoutMs      = 10000
                RestApiIgnoreCertErrors   = $ignoreCert
                RestApiUseAnonymousAccess = $useAnonymous
                RestApiCustomHeader       = $tplAuthHeader
            } `
            -UniqueKey "Fortinet:${deviceId}:perf:setuprate" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'performance', 'setuprate')

        # ================================================================
        # 9. PERFORMANCE MONITOR — Disk Utilization
        # ================================================================
        $items += New-DiscoveredItem `
            -Name "FortiGate Disk [$deviceName]" `
            -ItemType 'PerformanceMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                = $resourceUrl
                RestApiJsonPath           = '$.results.disk[0].current'
                RestApiHttpMethod         = 'GET'
                RestApiHttpTimeoutMs      = 10000
                RestApiIgnoreCertErrors   = $ignoreCert
                RestApiUseAnonymousAccess = $useAnonymous
                RestApiCustomHeader       = $tplAuthHeader
            } `
            -UniqueKey "Fortinet:${deviceId}:perf:disk" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'performance', 'disk')

        # ================================================================
        # 10. ACTIVE MONITOR — SD-WAN Health Checks
        # ================================================================
        $sdwanUrl = "${baseUri}/api/v2/monitor/virtual-wan/health-check"

        $items += New-DiscoveredItem `
            -Name "FortiGate SD-WAN Health [$deviceName]" `
            -ItemType 'ActiveMonitor' `
            -MonitorType 'RestApi' `
            -MonitorParams @{
                RestApiUrl                  = $sdwanUrl
                RestApiMethod               = 'GET'
                RestApiTimeoutMs            = 10000
                RestApiIgnoreCertErrors     = $ignoreCert
                RestApiUseAnonymous         = $useAnonymous
                RestApiCustomHeader         = $tplAuthHeader
                RestApiDownIfResponseCodeIsIn = '[400,401,403,500,502,503]'
                RestApiComparisonList       = '[]'
            } `
            -UniqueKey "Fortinet:${deviceId}:active:sdwan" `
            -DeviceId $deviceId `
            -Tags @('fortinet', 'active', 'sdwan')

        $tokenValue = $null; $tplAuthHeader = $null
        return $items
    }

# ============================================================================
# Fortinet Discovery Dashboard Export
# ============================================================================
function Export-FortinetDiscoveryDashboardHtml {
    <#
    .SYNOPSIS
        Generates a Fortinet discovery dashboard HTML from plan data.
    .PARAMETER DashboardData
        Array of PSCustomObject rows (Device, IP, Monitor, Type, Status).
    .PARAMETER OutputPath
        File path for the generated HTML.
    .PARAMETER ReportTitle
        Dashboard title. Default: 'FortiGate Discovery Dashboard'.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]$DashboardData,
        [Parameter(Mandatory)][string]$OutputPath,
        [string]$ReportTitle = 'FortiGate Discovery Dashboard'
    )

    $dynDashPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'reports\Export-DynamicDashboardHtml.ps1'
    if (-not (Get-Command -Name 'Export-DynamicDashboardHtml' -ErrorAction SilentlyContinue)) {
        if (Test-Path $dynDashPath) { . $dynDashPath }
    }

    if (Get-Command -Name 'Export-DynamicDashboardHtml' -ErrorAction SilentlyContinue) {
        Export-DynamicDashboardHtml -Data $DashboardData `
            -OutputPath $OutputPath `
            -ReportTitle $ReportTitle `
            -CardField 'Device','Type' `
            -StatusField 'Status'
    }
    else {
        Write-Error "No dashboard generator available."
    }
}

# SIG # Begin signature block
# MIIVlwYJKoZIhvcNAQcCoIIViDCCFYQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDUqqfD/dE+SehW
# Lx1T59+S+uSv6+eVYi2DJzpDQavD7aCCEdMwggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M
# UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv
# ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5
# NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp
# BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI
# ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV
# DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3
# 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw
# mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm
# +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe
# dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4
# 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM
# dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY
# MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU
# pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV
# HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG
# A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1
# YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG
# AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl
# U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
# aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh
# w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd
# OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj
# cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc
# WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO
# hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs
# zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7
# 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J
# KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH
# j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2
# Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/
# L9Uo2bC5a4CH2RwwggY+MIIEpqADAgECAhAHnODk0RR/hc05c892LTfrMA0GCSqG
# SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw
# HhcNMjYwMjA5MDAwMDAwWhcNMjkwNDIxMjM1OTU5WjBVMQswCQYDVQQGEwJVUzEU
# MBIGA1UECAwLQ29ubmVjdGljdXQxFzAVBgNVBAoMDkphc29uIEFsYmVyaW5vMRcw
# FQYDVQQDDA5KYXNvbiBBbGJlcmlubzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAPN6aN4B1yYWkI5b5TBj3I0VV/peETrHb6EY4BHGxt8Ap+eT+WpEpJyE
# tRYPxEmNJL3A38Bkg7mwzPE3/1NK570ZBCuBjSAn4mSDIgIuXZnvyBO9W1OQs5d6
# 7MlJLUAEufl18tOr3ST1DeO9gSjQSAE5Nql0QDxPnm93OZBon+Fz3CmE+z3MwAe2
# h4KdtRAnCqwM+/V7iBdbw+JOxolpx+7RVjGyProTENIG3pe/hKvPb501lf8uBAAD
# LdjZr5ip8vIWbf857Yw1Bu10nVI7HW3eE8Cl5//d1ribHlzTzQLfttW+k+DaFsKZ
# BBL56l4YAlIVRsrOiE1kdHYYx6IGrEA809R7+TZA9DzGqyFiv9qmJAbL4fDwetDe
# yIq+Oztz1LvEdy8Rcd0JBY+J4S0eDEFIA3X0N8VcLeAwabKb9AjulKXwUeqCJLvN
# 79CJ90UTZb2+I+tamj0dn+IKMEsJ4v4Ggx72sxFr9+6XziodtTg5Luf2xd6+Phha
# mOxF2px9LObhBLLEMyRsCHZIzVZOFKu9BpHQH7ufGB+Sa80Tli0/6LEyn9+bMYWi
# 2ttn6lLOPThXMiQaooRUq6q2u3+F4SaPlxVFLI7OJVMhar6nW6joBvELTJPmANSM
# jDSRFDfHRCdGbZsL/keELJNy+jZctF6VvxQEjFM8/bazu6qYhrA7AgMBAAGjggGJ
# MIIBhTAfBgNVHSMEGDAWgBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQU
# 6YF0o0D5AVhKHbVocr8GaSIBibAwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQC
# MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIB
# AwIwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EM
# AQQBMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2Vj
# dGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBE
# BggrBgEFBQcwAoY4aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGlj
# Q29kZVNpZ25pbmdDQVIzNi5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNl
# Y3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4IBgQAEIsm4xnOd/tZMVrKwi3doAXvC
# wOA/RYQnFJD7R/bSQRu3wXEK4o9SIefye18B/q4fhBkhNAJuEvTQAGfqbbpxow03
# J5PrDTp1WPCWbXKX8Oz9vGWJFyJxRGftkdzZ57JE00synEMS8XCwLO9P32MyR9Z9
# URrpiLPJ9rQjfHMb1BUdvaNayomm7aWLAnD+X7jm6o8sNT5An1cwEAob7obWDM6s
# X93wphwJNBJAstH9Ozs6LwISOX6sKS7CKm9N3Kp8hOUue0ZHAtZdFl6o5u12wy+z
# zieGEI50fKnN77FfNKFOWKlS6OJwlArcbFegB5K89LcE5iNSmaM3VMB2ADV1FEcj
# GSHw4lTg1Wx+WMAMdl/7nbvfFxJ9uu5tNiT54B0s+lZO/HztwXYQUczdsFon3pjs
# Nrsk9ZlalBi5SHkIu+F6g7tWiEv3rtVApmJRnLkUr2Xq2a4nbslUCt4jKs5UX4V1
# nSX8OM++AXoyVGO+iTj7z+pl6XE9Gw/Td6WKKKsxggMaMIIDFgIBATBoMFQxCzAJ
# BgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNl
# Y3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYCEAec4OTRFH+FzTlzz3Yt
# N+swDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZ
# BgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYB
# BAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgYBo+EhqCPMgPyc6PyTgyrwhUYeRaJn5T
# qtb12mjUpewwDQYJKoZIhvcNAQEBBQAEggIAI+Mx6xCN7VBrjSQpbPhK8Ue164xH
# hyJaN7PjCqs1lyjriV9eoO4Qa4JfJhExgorCaj71e5aJvOTR9l5cgljUJqexAuq+
# XIH3MvMnGEdO4dN5x+OmKkOEPe7QEsBLOZJJIt9ojB9MJDZo7AzW5Fd/9H8CLnOe
# 5v9YTSnZTX4C0MLwvmyExfQ8zhM2iLFHURQYgtrd0TaTQcd/g+O0knsMk1NuG2FV
# 3jr9tUzEnUrRXzdDRPLmjMPmfcI1JNkT20DtL1N8Lg+0Ny6iTRRh0CSs5F68jj+I
# y+LS0p3kXjMtyYmrlyTNCjgGkE4TJajlngnF74FXi2g35KA+B1xMY1obCTw7C06R
# ZTgQN8zjBdi988zVq5r6Cx2cMMY1A71ktG6fDtwXqoyvNJfaXjFMSlIz/fUPutCv
# FV1rc19tY74Q2YeQ0Vk+yh40rDm6CGyl436WGZUysHomlD2L8eTvgVnuIUQ22EpE
# kz3l8YBC6Mc5RpdH0XDHxC+ZiCEtnVrPKPm7NnZ8sQymHPjVzgKwHqhyCe1i0ffp
# KPcD5J/fKzmScCLZP6qH6amiV9tXVe/hQaW59ezq8RuqbD9jVYlNNdX5Lz1Fl2uf
# p3Euh2805y7DEJzxZwz8jR5kk4yCM90mfCy/wmVb/owUeemjTgnqaKhYvLhjzXc1
# hGDK7JLPII3zzDw=
# SIG # End signature block