CredentialSpec.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

# Helper function to obtain credential spec directory
# Order: Docker Root Directory, Docker Graph Directory, Program Data
$Script:CredSpecRoot = $null
function GetCredSpecRoot {
    # Check if root already computed
    if ($Script:CredSpecRoot) {
        return $Script:CredSpecRoot
    }

    # Default location is Docker's Program Data folder
    $root = "$env:ProgramData\Docker"

    # First, try to query the Docker root directory
    try {
        $DockerRootDir = docker info --format "{{.DockerRootDir}}" | Convert-Path
        if (Test-Path $DockerRootDir) {
            $root = $DockerRootDir
        }
        else {
            $TryGraph = $true
        }
    }
    catch {
        $TryGraph = $true
    }

    # If the Docker root directory doesn't exist or couldn't be found, try the graph directory
    $DaemonFile = "$env:ProgramData\Docker\config\daemon.json"
    if ($TryGraph -and (Test-Path "$env:ProgramData\Docker\config\daemon.json")) {
        $config = Get-Content $DaemonFile | ConvertFrom-Json
        
        if ($config.graph -and (Test-Path $config.graph)) {
            $root = $config.graph | Convert-Path
        }
    }

    # Finally, build full path and check for credential spec folder
    $CredSpecRoot = Join-Path $root "\CredentialSpecs"
    
    if (Test-Path $CredSpecRoot) {
        $Script:CredSpecRoot = $CredSpecRoot
        return $CredSpecRoot
    }
    else {
        throw "Credential spec directory does not exist: $CredSpecRoot"
    }
}

function TestgMSAExistence($AccountName, $Domain) {
    $gMSA = Get-ADServiceAccount -Identity $AccountName -Server $Domain -ErrorAction SilentlyContinue
    if (-not $gMSA) {
        Write-Error "The group managed service account `"$AccountName`" could not be found in the $Domain domain.`nIf the account belongs to a different domain, specify the correct domain using the -Domain parameter (or hashtable key for additional accounts) and ensure a trust has been established between this computer's domain and the gMSA's domain."
        return $false
    }
    elseif (-not (Test-ADServiceAccount -Identity $gMSA.DistinguishedName)) {
        Write-Warning "This computer is not authorized to use the group managed service account `"$AccountName`"`nRun `"Get-ADServiceAccount $AccountName -Properties PrincipalsAllowedToRetrieveManagedPassword`" and verify this computer object, or a security group to which the computer belongs, is allowed to retrieve the gMSA password.`nNote: if you recently added this computer account to a security group, you may need to restart the computer for the group membership to take effect."
    }

    return $true
}

function New-CredentialSpec {

    <#
    .SYNOPSIS
    Creates and stores a credential spec file for Windows Containers.
 
    .DESCRIPTION
    Windows containers are able to run with an Active Directory identity. This enables applications running in the contianer to use Windows authentication instead of stored username/password combinations.
     
    Containers cannot join domains, but instead use group managed service accounts (gMSA) to act as the computer account on the network.
    This allows integrated Windows authentication to perform as it would on a traditional, domain-joined machine.
    The credential spec file defines which gMSA should be used when starting up a container.
    The gMSA account must already be provisioned in Active Directory and installed on the machine running the container before the container can use the identity.
 
    Each credential spec file contains:
    - A default account that will be mapped to Local System and Network Service in the container.
    - (Optional) Additional group managed service accounts that may be used inside the container.
 
    .PARAMETER AccountName
    The name of the group managed service account that should be used when the container communicates over the network.
 
    .PARAMETER Path
    The full path (*.json) to the file where the credential spec will be stored.
 
    .PARAMETER FileName
    The name of the file where the credential spec will be stored. If a value is not specified, the file name will default to DOMAIN_ACCOUNTNAME.json.
    The file will be located in the configured credential spec directory in Docker.
    To store the file in an arbitrary location on the filesystem (not in the Docker root directory), use the -Path parameter instead.
 
    .PARAMETER Domain
    The DNS name of the Active Directory domain where the gMSA account exists.
    If a domain name is not provided, the domain to which the current computer is joined will be used.
 
    .PARAMETER AdditionalAccounts
    The name of additional group managed service accounts that should be made available for use in the container.
    This parameter accepts a list of samAccountNames and hashtables with AccountName and Domain information for each additional gMSA.
 
    .PARAMETER NoClobber
    Determines whether New-CredentialSpec will overwrite existing credential specs in the event of a name collision.
    The default behavior is to overwrite existing files.
 
    .EXAMPLE
    New-CredentialSpec "FrontEndWeb01"
 
    Creates a credential spec using the default file name for a gMSA named "FrontEndWeb01".
 
    .EXAMPLE
    New-CredentialSpec "FrontEndWeb01" -FileName "credspec_for_webapp"
 
    Creates a credential spec for a gMSA named "FrontEndWeb01" and saves that file to "credspec_for_webapp.json"
    Note that .json is automatically appended to any file name if it is not already present.
 
    .EXAMPLE
    New-CredentialSpec -AccountName "BackEndWeb02" -Domain "dev.contoso.com"
 
    Create a credential spec for a gMSA named "BackEndWeb02" that belongs to the "dev.contoso.com" domain.
 
    .EXAMPLE
    New-CredentialSpec -AccountName "FrontEndWeb01" -AdditionalAccounts "LogAccount01", @{ AccountName = "gMSA3"; Domain = "dev.contoso.com" }
 
    Creates a credential spec for a gMSA named "FrontEndWeb01" and includes 2 additional gMSAs: LogAccount01 and gMSA3.
    gMSA3 comes from the dev.contoso.com domain, which may be different from the current computer's domain and that of the other gMSAs.
 
    .EXAMPLE
    New-CredentialSpec -AccountName "FrontEndWeb01" -Path "C:\src\myapp\webapp01.json"
     
    Creates a credential spec for a gMSA named "FrontEndWeb01" and stores the file at "C:\src\myapp\webapp01.json"
    #>


    [CmdletBinding(DefaultParameterSetName = "DefaultPath")]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [String]
        $AccountName,

        [Parameter(Mandatory = $true, ParameterSetName = "CustomPath")]
        [String]
        $Path,

        [Parameter(Mandatory = $false, ParameterSetName = "DefaultPath")]
        [Alias("Name")]
        [String]
        $FileName,

        [Parameter(Mandatory = $false)]
        [string]
        $Domain,

        [Parameter(Mandatory = $false)]
        [object[]]
        $AdditionalAccounts,

        [Parameter(Mandatory = $false)]
        [switch]
        $NoClobber = $false
    )

    # Check if computer is domain joined
    # TODO: Add support for AAD joined machines when an explicit domain name is specified
    $cs = Get-CimInstance -ClassName Win32_ComputerSystem -Property PartOfDomain
    if (-not $cs.PartOfDomain) {
        Write-Error "This computer is not joined to an Active Directory domain.`nNew-CredentialSpec is only supported on domain joined machines."
        return
    }

    # Import the AD PS module (required dependency to create new cred specs)
    try {
        Import-Module ActiveDirectory -Force -ErrorAction Stop
    }
    catch {
        # Generate instructions on how to obtain RSAT
        $os = Get-CimInstance -ClassName Win32_OperatingSYstem -Property OperatingSystemSKU, Version
        $installStep = "Check https://aka.ms/RSAT for more information on how to install the Active Directory Remote Server Administration Tools."
        
        # Check if Server SKU and provide Server Manager installation instructions
        if ($os.OperatingSystemSKU -in 7, 8, 12, 13, 64) {
            $installStep = "You can install the Active Directory PowerShell Module by running `"Install-WindowsFeature RSAT-AD-PowerShell`" in an elevated PowerShell window or by adding the feature using Server Manager or Windows Admin Center."
        }
        # Check if Win 10 1809 or greater and provide RSAT FOD installation instructions
        elseif ($os.Version -ge [System.Version]"10.0.17763") {
            $installStep = "You can install the Active Directory PowerShell module by adding the `"RSAT: Active Directory Domain Service and Lightweight Directory Services Tools`" optional feature in the Settings app or by running `"Install-WindowsCapability -Online 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0'`" in an elevated PowerShell window."
        }

        Write-Error "The Active Directory PowerShell module is required to create a credential spec file.`n`n$installStep"
        return
    }

    # Ensure AD drive is present
    if (-not (Get-PSDrive -PSProvider ActiveDirectory)) {
        Write-Error "The Active Directory PowerShell Module cannot reach a domain controller.`nCheck your network configuration and try again."
        return
    }

    # Validate domain information
    if ($Domain) {
        $ADDomain = Get-ADDomain -Server $Domain -ErrorAction Continue

        if (-not $ADDomain) {
            Write-Error "The specified Active Directory domain ($Domain) could not be found.`nCheck your network connectivity and domain trust settings to ensure the current user can authenticate to a domain controller in that domain."
            return
        }
    }
    else {
        # Use the logged on user's domain if an explicit domain name is not provided
        $ADDomain = Get-ADDomain -Current LocalComputer -ErrorAction Continue

        if (-not $ADDomain) {
            Write-Error "An error ocurred while loading information for the computer account's domain.`nCheck your network connectivity to ensure the computer can authenticate to a domain controller in this domain."
            return
        }

        $Domain = $ADDomain.DNSRoot
    }

    # Clean up account names and validate formatting
    $AccountName = $AccountName.TrimEnd('$')

    if ($AdditionalAccounts) {
        $AdditionalAccounts = $AdditionalAccounts | ForEach-Object {
            if ($_ -is [hashtable]) {
                # Check for AccountName and Domain keys
                if (-not $_.AccountName -or -not $_.Domain) {
                    Write-Error "Invalid additional account specified: $_`nExpected a samAccountName or a hashtable containing AccountName and Domain keys."
                    return
                }
                else {

                    @{
                        AccountName = $_.AccountName.TrimEnd('$')
                        Domain = $_.Domain
                    }
                }
            }
            elseif ($_ -is [string]) {
                @{
                    AccountName = $_.TrimEnd('$')
                    Domain = $Domain
                }
            }
            else {
                Write-Error "Invalid additional account specified: $_`nExpected a samAccountName or a hashtable containing AccountName and Domain keys."
                return
            }
        }
    }

    # Validate the accounts
    if (-not (TestgMSAExistence -AccountName $AccountName -Domain $Domain)) {
        return
    }
    if ($AdditionalAccounts) {
        foreach ($account in $AdditionalAccounts) {
            if (-not (TestgMSAExistence -AccountName $account.AccountName -Domain $account.Domain)) {
                return
            }
        }
    }

    # Get the location to store the cred spec file either from input params or helper function
    if ($Path) {
        $CredSpecRoot = Split-Path $Path -Parent
        $FileName = Split-Path $Path -Leaf
    } else {
        $CredSpecRoot = GetCredSpecRoot
    }

    if (-not $FileName) {
        $FileName = "{0}_{1}" -f $ADDomain.NetBIOSName.ToLower(), $AccountName.ToLower()
    }

    $FullPath = Join-Path $CredSpecRoot "$($FileName.TrimEnd(".json")).json"
    if ((Test-Path $FullPath) -and $NoClobber) {
        Write-Error "A credential spec already exists with the name `"$FileName`".`nRemove the -NoClobber switch to overwrite this file or select a different name using the -FileName parameter."
        return
    }

    # Start hash table for output
    $output = @{}

    # Create ActiveDirectoryConfig Object
    $output.ActiveDirectoryConfig = @{}
    $output.ActiveDirectoryConfig.GroupManagedServiceAccounts = @( @{"Name" = $AccountName; "Scope" = $ADDomain.DNSRoot } )
    $output.ActiveDirectoryConfig.GroupManagedServiceAccounts += @{"Name" = $AccountName; "Scope" = $ADDomain.NetBIOSName }
    if ($AdditionalAccounts) {
        $AdditionalAccounts | ForEach-Object {
            $output.ActiveDirectoryConfig.GroupManagedServiceAccounts += @{"Name" = $_.AccountName; "Scope" = $_.Domain }
        }
    }
    
    # Create CmsPlugins Object
    $output.CmsPlugins = @("ActiveDirectory")

    # Create DomainJoinConfig Object
    $output.DomainJoinConfig = @{}
    $output.DomainJoinConfig.DnsName = $ADDomain.DNSRoot
    $output.DomainJoinConfig.Guid = $ADDomain.ObjectGUID
    $output.DomainJoinConfig.DnsTreeName = $ADDomain.Forest
    $output.DomainJoinConfig.NetBiosName = $ADDomain.NetBIOSName
    $output.DomainJoinConfig.Sid = $ADDomain.DomainSID.Value
    $output.DomainJoinConfig.MachineAccountName = $AccountName

    $output | ConvertTo-Json -Depth 5 | Out-File -FilePath $FullPath -Encoding ascii -NoClobber:$NoClobber

    Get-Item $FullPath | Select-Object @{
        Name       = 'Name'
        Expression = { $_.Name }
    },
    @{
        Name       = 'Path'
        Expression = { $_.FullName }
    }
}

function Get-CredentialSpec {
    <#
    .SYNOPSIS
    Gets all file-based credential specs on current system
 
    .DESCRIPTION
    Windows containers are able to run with an Active Directory identity. This enables applications running
    in the container to use Windows authentication instead of stored username/password combinations.
    #>


    $CredSpecRoot = GetCredSpecRoot

    Get-ChildItem $CredSpecRoot | Select-Object @{
        Name       = 'Name'
        Expression = { $_.Name }
    },
    @{
        Name       = 'Path'
        Expression = { $_.FullName }
    }
}

# SIG # Begin signature block
# MIIjnwYJKoZIhvcNAQcCoIIjkDCCI4wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDcOCdpR4xpTE8t
# vS/H28G25DGhguAU3DIFTj4JH3lnrKCCDYEwggX/MIID56ADAgECAhMzAAABUZ6N
# j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1
# oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os
# +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU
# 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg
# B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ
# IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A
# rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS
# se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf
# NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL
# crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs
# 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF
# sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD
# 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK
# YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j
# 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu
# Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY
# HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIVdDCCFXACAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN
# BglghkgBZQMEAgEFAKCByDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgl4WKxyEE
# 0CHe3diiAB0Bfjn0t/S61ZBLlfc29hD7DKowXAYKKwYBBAGCNwIBDDFOMEygLIAq
# AEMAcgBlAGQAZQBuAHQAaQBhAGwAUwBwAGUAYwAgAFAAUgAxADEAOQA4oRyAGmh0
# dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20gMA0GCSqGSIb3DQEBAQUABIIBAFJrMZ85
# tV+m2w39iFCyMRarlfcfwjTVUDT2D2DatDp2jAvNAbaYPvKN/1Yfd/nk+GEy7kOq
# CpZKpcjd+8TbwxApmmAmNuouH9cm3zy5DQOioEY246gBDM/DGNLJ6vZZ0jXEyq66
# Q4u6Q8RDAWCkybmWxFLX3pfhCjDBuu4HZXtszRjY7PvtDfUy+UHMVvdsyPt7QMZj
# lEF4URT5Wv5h7MJk/jSRqHixdTlg/Dm18kXnqi5FVfySSllofTKZl2EVjMpUyUWR
# I99NPUW15I0cJjMgs9D/l6gazteS7orYnceyFM8rFjFV+rcvOFw/gaSJlSxnU9+Z
# ZA9S9NiS3vB1TBGhghLkMIIS4AYKKwYBBAGCNwMDATGCEtAwghLMBgkqhkiG9w0B
# BwKgghK9MIISuQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUAYLKoZIhvcNAQkQAQSg
# ggE/BIIBOzCCATcCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQg5agG
# ha147hC1Dh/OsQXqLcGIirXsegzWmN1T3tLwAncCBl1doiIl+RgSMjAxOTA5MDQx
# NTUyNTYuOTNaMASAAgH0oIHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRp
# b25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpBRTJDLUUzMkItMUFGQzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDjwwggTxMIID2aAD
# AgECAhMzAAAA7edvcr1PVbjSAAAAAADtMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE4MTAyNDIxMTQxNFoXDTIwMDExMDIx
# MTQxNFowgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAj
# BgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRo
# YWxlcyBUU1MgRVNOOkFFMkMtRTMyQi0xQUZDMSUwIwYDVQQDExxNaWNyb3NvZnQg
# VGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEA9sRKWW2f1Jky74hDs6oeHwlnH7LZk3HDCvhxEUNKQAGIN3gjVk20mY7Y/06v
# BG2rsUZ29LlNqFGk7OjYQcCdBiShhiX9knED319jN49U4cVI+WznFLBtK9hO8joS
# wheyHVpRL4zkfblSUgkPhNyBZhDp7vq4ADXB/1nMmXJjY4cmGUmC84Int4DE0aQ3
# x/6cVjgjNAAmw2QbbL3ntuY7chSa7K8xqOqieQxIQvmVwWRDyKdQoA2s6bq6E2CF
# 0dYzMjmuFNSwiAd8TePbXI9iYZYY6EHXD2BeAeGFfgDDkd9yUrYSzIsutdMlBnyr
# ZEJxIi7v3AS8OCQuvM/wbku62wIDAQABo4IBGzCCARcwHQYDVR0OBBYEFML0O53H
# 0udV4QjxEp8I3j+eVX7rMB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1V
# MFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kv
# Y3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEF
# BQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
# a2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQC
# MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAF+QPOss
# 3PR+cm8+teN/BNcqxBegBNrdTTPxKVKp23Pa/aQnC3pV0liHNL1nE5Fti6LS0XJM
# DvnoYA2TPJUsQNveDPlrqov6i8FOrmd1ZVP1Z54JDq/K0tMSor1tAGdDOfI6iPK5
# cQ2aTQf1dowKRAY3fsFwC0rqP6BV2p7f6pdjLGti/tYXL0opNXouvy/ytmYikdz0
# mDpx3U8elofh7JN3sGsP8fr35mvDvnHncxpI5R4Ln8g1C7Hdmy7Ty+vjVWY54TzZ
# 7Em57u3H+zKlsXPTJu3fjOLEgUk7kxMUKLBsMejwuE/2Ga1xQIuFPvUla9VU9znz
# O/dvoEVudXi9b6wwggZxMIIEWaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEB
# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD
# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe
# Fw0xMDA3MDEyMTM2NTVaFw0yNTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0N
# vHcRijog7PwTl/X6f2mUa3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycE
# MR9BGxqVHc4JE458YTBZsTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1
# R4HNvyRgMlhgRvJYR4YyhB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxG
# wScdJGcSchohiq9LZIlQYrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/Q
# S/1u5ZrKsajyeioKMfDaTgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38
# vMDJRF1eFpwBBU8iTQIDAQABo4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYD
# VR0OBBYEFNVjOlyKMZDzQ3t8RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1
# AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA
# FNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8y
# MDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3J0MIGgBgNVHSABAf8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9Bggr
# BgEFBQcCARYxaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9k
# ZWZhdWx0Lmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABp
# AGMAeQBfAFMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEA
# B+aIUQ3ixuCYP4FxAz2do6Ehb7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9
# x6ieJeP5vO1rVFcIK1GCRBL7uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9C
# EMivv3/Gf/I3fVo/HPKZeUqRUgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP
# 7QOllo9ZKby2/QThcJ8ySif9Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoL
# kSbiOewZSnFjnXshbcOco6I8+n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4E
# QoO4tYCbIjggtSXlZOz39L9+Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQ
# zafQ732D8OE7cQnfXXSYIghh2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5h
# a293qYHLpwmsObvsxsvYgrRyzR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3q
# jeAzLhIp9cAvVCch98isTtoouLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99j
# e/WZii8bxyGvWbWu3EQ8l1Bx16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8
# z1gFLu8NoFA12u8JJxzVs341Hgi62jbb01+P3nSISRKhggLOMIICNwIBATCB+KGB
# 0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMG
# A1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046QUUyQy1FMzJCLTFBRkMxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAGYniRBsTRX3mYLQgEBC
# 5/P11TjCoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJ
# KoZIhvcNAQEFBQACBQDhGey4MCIYDzIwMTkwOTA0MTU1NjQwWhgPMjAxOTA5MDUx
# NTU2NDBaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOEZ7LgCAQAwCgIBAAICI7kC
# Af8wBwIBAAICEgwwCgIFAOEbPjgCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYB
# BAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOB
# gQArtcjV1Dd5tbu1v+uywPM7oU71SIY2pJLjGdSSbbLXz4Qs+P8ttfvxTUxSql0r
# mzSA8rQCFybmAK7yw9iGGgLS+Cw455lc2nUU6sOr3Hmb4FLmfr26X6c9ZfVtX5JD
# U07vE3mDNVWcuDtf3smc9o3styVUj78SzG3jckN5eiycVTGCAw0wggMJAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAA7edvcr1PVbjSAAAA
# AADtMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEILC2X8AVMVyOMm/5/Qz1jWN8gq3csBRRzcu5dqlg
# jfVyMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQg0cHo+XKd0/KM3RzNcgIZ
# MrB29AObGV0MmiR3UZ4wpL0wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAO3nb3K9T1W40gAAAAAA7TAiBCBHybXzEpltjOlG03oCQB0p
# OvZbTgqxLY+uWHv0lsc3BzANBgkqhkiG9w0BAQsFAASCAQDuKUxz/YlsrvscLm/0
# uo4Ea419yxMDKAoaTZF7S42d7n4MyyOu6J/VO+JwC2lt1wCtLE0FdpO78t4gc3iS
# CMqHBioBqQa8cduM7rcC7CN74x7f0XbhSwWxqO2T+12NSfyjuEYU30oelkjzqH4O
# C6sKnaWE8NbRRLOtaUwQ3qQAIKFxTFicK+IRQTTYd29naPtGdaaKuxiFb/E630q9
# B7yKH7PPgE0Ky/B9pN+VlxeuQw6+PmUz96lkL6JP96U/pnO8bK97CqzgbnaDc0H3
# 2C60kMLCwAYWB2o/gRO7ZZusNjo9Wyb6r9VoRaSu+Qon7Dzo/OY6gw2NjOGBPg+T
# +1PS
# SIG # End signature block