First full commit
This commit is contained in:
parent
ac2a7b7ef4
commit
c34cead304
183
Modules/.build/Clear-BadPesterUserAccounts.ps1
Normal file
183
Modules/.build/Clear-BadPesterUserAccounts.ps1
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
Function Clear-BadPesterUserAccounts {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Cleans up all incorrectly created user accounts on the system from Pester.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Clear-BadPesterUserAccounts
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
Returns the list of folder names (from C:\Users) that are "invalid".
|
||||||
|
If the return list is empty then there were none to remove.
|
||||||
|
This is useful for post-facto additional resource cleanup. In the case of a non-WhatIf, this will be an ephemeral response, so catch it while you can.
|
||||||
|
|
||||||
|
.PARAMETER AllPossibleDomainsToSearch
|
||||||
|
What domains do we want to search? Defaults to [fh, corp]
|
||||||
|
|
||||||
|
.PARAMETER WhatIf
|
||||||
|
Used to test before removing for validation
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
$AllPossibleDomainsToSearch = @("fh","corp"),
|
||||||
|
[switch]$WhatIf
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
# This means we are on Windows
|
||||||
|
# At the very least, we can't get the CIM Instances to remove users if they exist
|
||||||
|
if ($null -eq (Get-Command Get-CimInstance -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "$logLead : Can't CimInstance on this host, nothing to do"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$logLead : Cleaning bad pester user accounts [Dry run? $WhatIf]"
|
||||||
|
|
||||||
|
# Get local user accounts
|
||||||
|
# Get IIS user accounts or IIS app pool names (it defaults the identity to the name if no identity specified)
|
||||||
|
# Get all domain GMSA accounts
|
||||||
|
# Get all domain user accounts
|
||||||
|
#
|
||||||
|
# Get all c:\users folders
|
||||||
|
# Diff the names
|
||||||
|
# Remove the users
|
||||||
|
|
||||||
|
# Annoying bug...
|
||||||
|
if ($null -ne (Get-Module Carbon)) {
|
||||||
|
# Carbon stomps all over our Get-ADDomainController amongst other things
|
||||||
|
Remove-Module Carbon -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAccountsToExclude = @()
|
||||||
|
$foldersToKeep = @()
|
||||||
|
$foldersToRemove = @()
|
||||||
|
|
||||||
|
# The system can't determine about some of these, they are special folders
|
||||||
|
# This is where any such folders go
|
||||||
|
# Try to make them dynamic before adding them here.
|
||||||
|
# Public is a very special child unrelated to anything else.
|
||||||
|
$userAccountsToExclude += "Public"
|
||||||
|
|
||||||
|
if ($null -ne (Get-Module -ListAvailable -Name WebAdministration)) {
|
||||||
|
Get-Module WebAdministration | Remove-Module -Force
|
||||||
|
Import-Module WebAdministration
|
||||||
|
$appPools = @(Get-ChildItem IIS:\AppPools)
|
||||||
|
foreach($appPool in $appPools) {
|
||||||
|
if (![string]::IsNullOrWhiteSpace($appPool.processModel.userName)) {
|
||||||
|
$userAccountsToExclude += $appPool.processModel.userName
|
||||||
|
} else {
|
||||||
|
$userAccountsToExclude += $appPool.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$localUsers = @(Get-LocalUser)
|
||||||
|
foreach($localUser in $localUsers) {
|
||||||
|
$userAccountsToExclude += $localUser.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq (Get-Module -ListAvailable -Name ActiveDirectory)) {
|
||||||
|
# This is truly an impossible condition to hit. So if we hit it, hit it hard.
|
||||||
|
throw 'how in the name of sanity is this server not setup to query the domain???'
|
||||||
|
}
|
||||||
|
|
||||||
|
Import-Module ActiveDirectory
|
||||||
|
foreach($domainName in $AllPossibleDomainsToSearch) {
|
||||||
|
$dc = Get-ADDomainController -DomainName $domainName -Discover -NextClosestSite
|
||||||
|
|
||||||
|
$dcHostname = $dc.Hostname[0]
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($dcHostname)) {
|
||||||
|
Write-Warning "$logLead : Could not find a hostname for $domainName - Can you access that domain from here?"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainServiceAccounts = @(Get-ADServiceAccount -Filter "*" -Server $dcHostname)
|
||||||
|
foreach($domainServiceAccount in $domainServiceAccounts) {
|
||||||
|
$userAccountsToExclude += $domainServiceAccount.SamAccountName
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainUserAccounts = @(Get-ADUser -Filter "*" -Server $dcHostname)
|
||||||
|
foreach($domainUserAccount in $domainUserAccounts) {
|
||||||
|
$userAccountsToExclude += $domainUserAccount.SamAccountName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$userFolders = @(Get-ChildItem -Directory -Path C:\Users)
|
||||||
|
foreach($folder in $userFolders) {
|
||||||
|
if ($userAccountsToExclude -contains $folder.Name) {
|
||||||
|
$foldersToKeep += $folder.Name
|
||||||
|
} else {
|
||||||
|
$foldersToRemove += $folder.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allCimAccounts = @(Get-CimInstance -ClassName Win32_UserProfile)
|
||||||
|
|
||||||
|
$teamCity = (Test-IsTeamCityProcess)
|
||||||
|
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockOpened name='Display accounts for followup']"
|
||||||
|
} else {
|
||||||
|
Write-Host "================================="
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-IsCollectionNullOrEmpty $foldersToKeep)) {
|
||||||
|
$tcBlurb = "Keep these accounts"
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockOpened name='$tcBlurb']"
|
||||||
|
} else {
|
||||||
|
Write-Host "$logLead : Found these folders to KEEP"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($folder in $foldersToKeep) {
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "$logLead : KEEP C:\Users\$folder"
|
||||||
|
} else {
|
||||||
|
Write-Verbose "$logLead : KEEP C:\Users\$folder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockClosed name='$tcBlurb']"
|
||||||
|
} else {
|
||||||
|
Write-Host "================================="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-IsCollectionNullOrEmpty $foldersToRemove)) {
|
||||||
|
$tcBlurb = "Remove these accounts"
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockOpened name='$tcBlurb']"
|
||||||
|
} else {
|
||||||
|
Write-Host "$logLead : Found these folders to REMOVE"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($folder in $foldersToRemove) {
|
||||||
|
Write-Host "$logLead : REMOVE C:\Users\$folder"
|
||||||
|
$matchingCimInstances = @($allCimAccounts.Where({$_.LocalPath -eq "C:\Users\$folder"}))
|
||||||
|
|
||||||
|
foreach($instance in $matchingCimInstances) {
|
||||||
|
if ($instance.Sid.StartsWith("S-1-5-82")) {
|
||||||
|
Write-Host "$logLead : $folder is an IIS-AppPool SID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$matchingCimInstances | Remove-CimInstance -WhatIf:$WhatIf
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockClosed name='$tcBlurb']"
|
||||||
|
} else {
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($teamCity) {
|
||||||
|
Write-Host "##teamcity[blockClosed name='Display accounts for followup']"
|
||||||
|
} else {
|
||||||
|
Write-Host "================================="
|
||||||
|
}
|
||||||
|
|
||||||
|
return $foldersToRemove
|
||||||
|
}
|
42
Modules/.build/Get-Aliases.ps1
Normal file
42
Modules/.build/Get-Aliases.ps1
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Function Get-Aliases {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Collects all alias names from files in a folder. Assumes Test-FunctionNames properly ran and validated target function names.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-Aliases .\Alkami.PowerShell.IIS\Public
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$aliases = @()
|
||||||
|
$verbs = Get-Verb | Select-Object -ExpandProperty Verb
|
||||||
|
|
||||||
|
$files = (Get-ChildItem -Path $FolderPath *.ps1)
|
||||||
|
foreach($file in $files) {
|
||||||
|
if ($file.BaseName.ToLower().EndsWith(".tests") -or $file.BaseName.ToLower().EndsWith(".test")) {
|
||||||
|
Write-Verbose "skipping function names of test $file"
|
||||||
|
} else {
|
||||||
|
$lines = (Get-Content $file.FullName)
|
||||||
|
foreach($line in $lines) {
|
||||||
|
if (($line.Trim().ToLower().StartsWith("set-alias")) -or ($line.Trim().ToLower().StartsWith("new-alias"))) {
|
||||||
|
$candidateLine = $line -replace ';','' -replace '-name','' -replace '-value','' -replace '-force','' -replace '-scope:global','' -replace '-Scope Global',''
|
||||||
|
$splits = ($candidateLine.Trim() -split '\s+')
|
||||||
|
if ($splits.length -ne 3) {
|
||||||
|
Write-Warning "Could not parse line [$line] for Set-Alias, found [$splits]"
|
||||||
|
} else {
|
||||||
|
$aliases += $splits[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aliases
|
||||||
|
}
|
||||||
|
}
|
32
Modules/.build/Get-BuildConfigs.ps1
Normal file
32
Modules/.build/Get-BuildConfigs.ps1
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
Function Get-BuildConfigs {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Get the build configs from a single csproj
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
$csprojPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
if (!(Test-Path $csprojPath)) {
|
||||||
|
Write-Warning "can't test on an empty path [$csprojPath]"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnValues = @()
|
||||||
|
|
||||||
|
$xml = [Xml](Get-Content $csprojPath)
|
||||||
|
|
||||||
|
$propertyGroups = @($xml.Project.PropertyGroup)
|
||||||
|
foreach($propertyGroup in $propertyGroups) {
|
||||||
|
if ($null -ne $propertyGroup.Condition) {
|
||||||
|
$value = $propertyGroup.Condition.Replace("'",'').Split('==')[-1].Split('|')[0].Trim()
|
||||||
|
if (!([string]::IsNullOrWhiteSpace($value))) {
|
||||||
|
$returnValues += $value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValues
|
||||||
|
}
|
||||||
|
}
|
58
Modules/.build/Get-ContentFromFilesInPath.ps1
Normal file
58
Modules/.build/Get-ContentFromFilesInPath.ps1
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
Function Get-ContentFromFilesInPath {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Collects all content from valid files for inclusion into the PSM1
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Collects all content from valid files for inclusion into the PSM1
|
||||||
|
* Skips test files
|
||||||
|
* Adds a header line saying where the file came from during compile
|
||||||
|
The reason for these files to match their names is about process, not a functional concern.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ContentFromFilesInPath .\Alkami.PowerShell.IIS\
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$functionLines = @()
|
||||||
|
|
||||||
|
$prefixPath = (Split-Path $FolderPath -leaf)
|
||||||
|
|
||||||
|
$files = (Get-ChildItem (Join-Path $FolderPath "*.ps1"))
|
||||||
|
|
||||||
|
foreach($file in $files) {
|
||||||
|
if ($file.BaseName.ToLower().EndsWith(".tests") -or $file.BaseName.ToLower().EndsWith(".test")) {
|
||||||
|
Write-Verbose "skipping function names of test $file"
|
||||||
|
}
|
||||||
|
elseif ($file.BaseName.ToLower() -eq "variabledeclarations") {
|
||||||
|
|
||||||
|
$content = Get-Content $file.FullName
|
||||||
|
$functionLines += @("## Function from $file")
|
||||||
|
|
||||||
|
if ($null -ne $content -and $content[0] -match "SuppressMessageAttribute" -and $content[1] -match "param\(\)") {
|
||||||
|
|
||||||
|
Write-Host "Skipping PSScriptAnalyzer and Param Declaration from $($file.FullName)" -ForegroundColor Green
|
||||||
|
$functionLines += ($content | Select-Object -Skip 2)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$functionLines += $content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$functionLines += @("## Function from $file")
|
||||||
|
|
||||||
|
$functionLines += (Get-Content $file.FullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
$functionLines += ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return $functionLines
|
||||||
|
}
|
||||||
|
}
|
57
Modules/.build/Get-ContentFromFormatFilesInPath.ps1
Normal file
57
Modules/.build/Get-ContentFromFormatFilesInPath.ps1
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
Function Get-ContentFromFormatFilesInPath {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Collects all content from valid files for inclusion into the PSM1
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Collects all content from valid files for inclusion into the PSM1
|
||||||
|
* Skips test files
|
||||||
|
* Adds a header line saying where the file came from during compile
|
||||||
|
The reason for these files to match their names is about process, not a functional concern.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ContentFromFilesInPath .\Alkami.PowerShell.IIS\
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$functionLines = @()
|
||||||
|
|
||||||
|
$prefixPath = (Split-Path $FolderPath -leaf)
|
||||||
|
|
||||||
|
$files = (Get-ChildItem (Join-Path $FolderPath "*.ps1xml"))
|
||||||
|
|
||||||
|
[Xml]$defaultXml = [Xml]@"
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Configuration>
|
||||||
|
</Configuration>
|
||||||
|
"@
|
||||||
|
$defaultXml.DocumentElement.AppendChild($defaultXml.CreateElement("SelectionSets")) | Out-Null
|
||||||
|
$defaultXml.DocumentElement.AppendChild($defaultXml.CreateElement("ViewDefinitions")) | Out-Null
|
||||||
|
$defaultXml.DocumentElement.AppendChild($defaultXml.CreateElement("Controls")) | Out-Null
|
||||||
|
$defaultXml.DocumentElement.AppendChild($defaultXml.CreateElement("DefaultSettings")) | Out-Null
|
||||||
|
|
||||||
|
foreach($file in $files) {
|
||||||
|
[Xml]$xml = [Xml](Get-Content $file.FullName)
|
||||||
|
foreach($node in $xml.DocumentElement.SelectionSets.ChildNodes) {
|
||||||
|
$defaultXml.DocumentElement.SelectSingleNode("SelectionSets").AppendChild($defaultXml.ImportNode($node,$true)) | Out-Null
|
||||||
|
}
|
||||||
|
foreach($node in $xml.DocumentElement.Controls.ChildNodes) {
|
||||||
|
$defaultXml.DocumentElement.SelectSingleNode("Controls").AppendChild($defaultXml.ImportNode($node,$true)) | Out-Null
|
||||||
|
}
|
||||||
|
foreach($node in $xml.DocumentElement.ViewDefinitions.ChildNodes) {
|
||||||
|
$defaultXml.DocumentElement.SelectSingleNode("ViewDefinitions").AppendChild($defaultXml.ImportNode($node,$true)) | Out-Null
|
||||||
|
}
|
||||||
|
foreach($node in $xml.DocumentElement.DefaultSettings.ChildNodes) {
|
||||||
|
$defaultXml.DocumentElement.SelectSingleNode("DefaultSettings").AppendChild($defaultXml.ImportNode($node,$true)) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $defaultXml
|
||||||
|
}
|
||||||
|
}
|
31
Modules/.build/Get-FunctionNames.ps1
Normal file
31
Modules/.build/Get-FunctionNames.ps1
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Function Get-FunctionNames {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Collects all function names from filenames. Assumes Test-FunctionNames properly ran.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-FunctionNames .\Alkami.PowerShell.IIS\Public
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$functionNames = @()
|
||||||
|
$verbs = Get-Verb | Select-Object -ExpandProperty Verb
|
||||||
|
|
||||||
|
$files = (Get-ChildItem -Path $FolderPath *.ps1)
|
||||||
|
foreach($file in $files) {
|
||||||
|
if ($file.BaseName.ToLower().EndsWith(".tests") -or $file.BaseName.ToLower().EndsWith(".test")) {
|
||||||
|
Write-Verbose "skipping function names of test $file"
|
||||||
|
} else {
|
||||||
|
$functionNames += $file.BaseName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $functionNames
|
||||||
|
}
|
||||||
|
}
|
41
Modules/.build/Get-MSBuildPath.ps1
Normal file
41
Modules/.build/Get-MSBuildPath.ps1
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Function Get-MSBuildPath {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Get the local machine MSBuild path in a way that it can be easily consumed
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-MSBuildPath
|
||||||
|
|
||||||
|
C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param()
|
||||||
|
process {
|
||||||
|
if ($script:isSet_GetMSBuildPath) {
|
||||||
|
return $script:value_GetMSBuildPath
|
||||||
|
}
|
||||||
|
$msBuildPath = ""
|
||||||
|
if (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe") {
|
||||||
|
$msBuildPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
|
||||||
|
} elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe") {
|
||||||
|
$msBuildPath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe"
|
||||||
|
} elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe") {
|
||||||
|
$msBuildPath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe"
|
||||||
|
} elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe") {
|
||||||
|
$msBuildPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe"
|
||||||
|
} elseif (Test-Path "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe") {
|
||||||
|
$msBuildPath = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe"
|
||||||
|
} else {
|
||||||
|
$buildVersion = (Get-ChildItem HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions -Name | Measure -Maximum).Maximum
|
||||||
|
$MSBuildVersionRegEntry = (Get-ItemProperty -LiteralPath "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\$($buildVersion).0\" -Name "msbuild.exe")
|
||||||
|
if ($null -ne $MSBuildVersionRegEntry) {
|
||||||
|
$msBuildPath = $MSBuildVersionRegEntry."msbuild.exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!([string]::IsNullOrEmpty($msBuildPath))) {
|
||||||
|
$script:isSet_GetMSBuildPath = $true
|
||||||
|
$script:value_GetMSBuildPath = $msBuildPath
|
||||||
|
}
|
||||||
|
return $msBuildPath
|
||||||
|
}
|
||||||
|
}
|
29
Modules/.build/Join-PS1XMLFromFiles.ps1
Normal file
29
Modules/.build/Join-PS1XMLFromFiles.ps1
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Function Join-PS1XMLFromFiles {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Uses Get-ContentFromFormatFilesInPath to join the file contents appropriately
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Collects all content from valid files for inclusion into the PS1XML file
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Join-PS1XMLFromFiles .\Alkami.PowerShell.IIS\Public\ .\Alkami.PowerShell.IIS\Alkami.PowerShell.IIS.ps1xml
|
||||||
|
|
||||||
|
.PARAMETER PublicPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
|
||||||
|
.PARAMETER Ps1xmlFilePath
|
||||||
|
The file to overwrite with the contents of the build process.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$PublicPath,
|
||||||
|
$Ps1xmlFilePath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
# Get all of the xml nodes from the format files
|
||||||
|
$nodes = @(Get-ContentFromFormatFilesInPath $PublicPath)
|
||||||
|
|
||||||
|
Set-Content -Path $Ps1xmlFilePath -Value $nodes.OuterXml -Force
|
||||||
|
}
|
||||||
|
}
|
41
Modules/.build/Join-PSM1FromFiles.ps1
Normal file
41
Modules/.build/Join-PSM1FromFiles.ps1
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Function Join-PSM1FromFiles {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Uses Get-ContentFromFilesInPath to join the file contents appropriately
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Collects all content from valid files for inclusion into the PSM1
|
||||||
|
* Skips test files
|
||||||
|
* Adds a header line saying where the file came from during compile
|
||||||
|
The reason for these files to match their names is about process, not a functional concern.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Join-PSM1FromFiles .\Alkami.PowerShell.IIS\Public\ .\Alkami.PowerShell.IIS\Private\ .\Alkami.PowerShell.IIS\Alkami.PowerShell.IIS.psm1
|
||||||
|
|
||||||
|
.PARAMETER PublicPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
|
||||||
|
.PARAMETER PrivatePath
|
||||||
|
The name of the folder to examine all files under. Tested for existence, may not be valid.
|
||||||
|
|
||||||
|
.PARAMETER Psm1FilePath
|
||||||
|
The file to overwrite with the contents of the build process.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$PublicPath,
|
||||||
|
[String]$PrivatePath,
|
||||||
|
$Psm1FilePath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
## Put all of the content from the public files in a long array with a file-header
|
||||||
|
$functionLines = @(Get-ContentFromFilesInPath $PublicPath)
|
||||||
|
|
||||||
|
if (Test-Path $PrivatePath) {
|
||||||
|
## Put all of the content from the private files in the array with a file-header, after the public functions
|
||||||
|
$functionLines += @(Get-ContentFromFilesInPath $PrivatePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -Path $Psm1FilePath -Value $functionLines -Force
|
||||||
|
}
|
||||||
|
}
|
24
Modules/.build/Load-Includes.ps1
Normal file
24
Modules/.build/Load-Includes.ps1
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
## This file tries to load functions if they haven't been loaded into scope yet.
|
||||||
|
## We know if things are in the scope if the magic function Get-ContentFromFilesInPath has been loaded into the current session scope
|
||||||
|
## The way that this is loaded is from this file or manually
|
||||||
|
## This file was chosen because it doesn't live in any module for building the powershell modules
|
||||||
|
|
||||||
|
## The use of this file is as such:
|
||||||
|
## . $PSScriptRoot\.build\Load-Includes.ps1
|
||||||
|
## The use-case of this file and the unusual style is that this is just to include things for execution in the build system
|
||||||
|
## This is not a typical style of how to load things. This is a micro-op.
|
||||||
|
$script:buildFilesLoaded = $script:buildFilesLoaded -or ($null -ne ${function:Get-ContentFromFilesInPath})
|
||||||
|
|
||||||
|
if (!($script:buildFilesLoaded)) {
|
||||||
|
$buildScriptsFolder = $PSScriptRoot
|
||||||
|
|
||||||
|
if (Test-Path $buildScriptsFolder) {
|
||||||
|
$buildScripts = (Get-ChildItem *.ps1 -Path $buildScriptsFolder -Exclude "Load-Includes.ps1" -Recurse)
|
||||||
|
foreach($script in $buildScripts) {
|
||||||
|
Write-Verbose "[Clean-Project] - Including the build files: $script"
|
||||||
|
. $script.FullName
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:buildFilesLoaded = $true
|
||||||
|
}
|
||||||
|
}
|
86
Modules/.build/Test-FunctionNames.ps1
Normal file
86
Modules/.build/Test-FunctionNames.ps1
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
Function Test-FunctionNames {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Test that the name of the function/filter/workflow in the file match the name of the file and for verb match
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Test that the name of the function/filter/workflow in the file match the name of the file and for verb match
|
||||||
|
* This will look in public/private folders
|
||||||
|
* This will examine for verb match to Get-Verb as part of the testing
|
||||||
|
* This ignores any names that end with .tests.ps1 or .test.ps1
|
||||||
|
* This will check .tests?.ps1 files that they start with a reference to Load-PesterModules
|
||||||
|
* If they don't, it will emit a warning per file
|
||||||
|
The reason for these files to match their names is about process, not a functional concern.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Test-FunctionNames .\Alkami.PowerShell.IIS\
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$verbs = Get-Verb | Select-Object -ExpandProperty Verb
|
||||||
|
|
||||||
|
$badMatch = @()
|
||||||
|
$files = (Get-ChildItem (Join-Path $FolderPath "*.ps1"))
|
||||||
|
foreach($file in $files) {
|
||||||
|
if ($file.FullName -match '(scratch|bak)') {
|
||||||
|
Write-Verbose "Skipping $($file.FullName) for (scratch|bak)"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$content = (Get-Content $file)
|
||||||
|
if ($file -match '\.Tests\.ps1' -or $file -match '\.Test\.ps1') {
|
||||||
|
$firstLine = $content[0]
|
||||||
|
if (([string]::IsNullOrWhitespace($firstLine) -or $firstLine -notmatch 'PesterModules') -and $firstline -notmatch "param") {
|
||||||
|
Write-Warning "[$file] does not start with load-pestermodules or parameter declarations. You're gonna have a bad time."
|
||||||
|
} elseif ($firstLine -match "param" -and ($null -eq ($content | Where-Object {$_ -match "PesterModules"}))) {
|
||||||
|
Write-Warning "[$file] has a parameter declaration but does not include load-pestermodules. You're gonna have a bad time."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "skipping testing of test $file"
|
||||||
|
} else {
|
||||||
|
$functionCandidate = $file.BaseName
|
||||||
|
$keywordStartsLine = 0
|
||||||
|
|
||||||
|
$passing = $false
|
||||||
|
foreach($line in $content) {
|
||||||
|
## Make sure the line doesn't start with whitespace and that people didn't put a bunch of spaces between 'keyword' and 'function-name'
|
||||||
|
$line = ($line -replace '\s+',' ' -replace '^function','' -replace '^filter','' -replace '^workflow','').Trim()
|
||||||
|
|
||||||
|
if ($line.StartsWith($functionCandidate, [StringComparison]::InvariantCultureIgnoreCase)) {
|
||||||
|
$passing = $true
|
||||||
|
## we found the line we want
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
$keywordStartsLine += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure that the function name uses a good verb-naming pattern
|
||||||
|
$verb = $functionCandidate.Split("-")[0]
|
||||||
|
|
||||||
|
if($verbs -notcontains $verb) {
|
||||||
|
$passing = $false
|
||||||
|
Write-Warning "Function $functionCandidate (current verb: [$verb]) does not use a valid PowerShell verb. See: Get-Verb"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($keywordStartsLine -gt 1) {
|
||||||
|
Write-Verbose "The file [$file] doesn't start with the keyword and function-name"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$passing) {
|
||||||
|
$badMatch += $functionCandidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($badMatch.Length -gt 0) {
|
||||||
|
$badMatch
|
||||||
|
throw "The previous files failed their test constraints"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
Modules/.build/Test-IsTeamCityProcess.ps1
Normal file
23
Modules/.build/Test-IsTeamCityProcess.ps1
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Function Test-IsTeamCityProcess {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Determines if the Current Process is a TeamCity Agent Process
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Will not work except when running in a direct agent process. Checks for TeamCity agent environment
|
||||||
|
variables to exist.
|
||||||
|
#>
|
||||||
|
## Duplicates some of Alkami.DevOps.Common\Public\Test-IsTeamCityProcess.ps1
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([System.Boolean])]
|
||||||
|
Param()
|
||||||
|
|
||||||
|
$teamCityJREVariable = $ENV:TEAMCITY_JRE
|
||||||
|
|
||||||
|
if ([String]::IsNullOrEmpty($teamCityJREVariable)) {
|
||||||
|
Write-Verbose "[Test-IsTeamCityProcess] TEAMCITY_JRE User Environment Variable Not Found"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
return $true
|
||||||
|
}
|
473
Modules/.build/Test-ModuleInclusion.ps1
Normal file
473
Modules/.build/Test-ModuleInclusion.ps1
Normal file
File diff suppressed because one or more lines are too long
65
Modules/.build/Update-AliasesInPSD1.ps1
Normal file
65
Modules/.build/Update-AliasesInPSD1.ps1
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
Function Update-AliasesInPSD1 {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Concatenates all the aliases from Get-Aliases for insertion into the PSD1 file referenced.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Update-AliasesInPSD1 .\Alkami.PowerShell.IIS\Public .\Alkami.PowerShell.IIS\Alkami.PowerShell.IIS.psd1
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
|
||||||
|
.PARAMETER Psd1FilePath
|
||||||
|
The file to be rewritten. (This is called out in case more than one .psd1 file exists in a folder.)
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath,
|
||||||
|
[String]$Psd1FilePath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$aliasNames = @(Get-Aliases $FolderPath) | Sort-Object
|
||||||
|
|
||||||
|
if ($aliasNames.Count -eq 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$toBeJoinedAliases = @()
|
||||||
|
|
||||||
|
foreach($name in $aliasNames) {
|
||||||
|
$toBeJoinedAliases += "'$name'"
|
||||||
|
}
|
||||||
|
|
||||||
|
$joinedAliases = $toBeJoinedAliases -Join ','
|
||||||
|
|
||||||
|
$contents = (Get-Content $Psd1FilePath)
|
||||||
|
$newContents = @()
|
||||||
|
|
||||||
|
$foundAliasesLine = $false
|
||||||
|
foreach($line in $contents) {
|
||||||
|
if ($line.Trim().StartsWith("AliasesToExport")) {
|
||||||
|
$splits = $line -split '='
|
||||||
|
$line = $splits[0].TrimEnd() + " = $joinedAliases"
|
||||||
|
$foundAliasesLine = $true
|
||||||
|
}
|
||||||
|
$newContents += $line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$foundAliasesLine) {
|
||||||
|
$newContents = @()
|
||||||
|
## We couldn't find an existing aliases line in that PSD1, so we need to add one.
|
||||||
|
## We are gonna add it after FunctionsToExport
|
||||||
|
foreach($line in $contents) {
|
||||||
|
$newContents += $line
|
||||||
|
if ($line.Trim().StartsWith("FunctionsToExport")) {
|
||||||
|
$splits = $line -split '='
|
||||||
|
$newLine = $splits[0].TrimEnd().Replace('FunctionsToExport','AliasesToExport') + " = $joinedAliases"
|
||||||
|
|
||||||
|
$newContents += $newLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -Path $Psd1FilePath -Value $newContents
|
||||||
|
}
|
||||||
|
}
|
61
Modules/.build/Update-FormatsToProcessInPSD1.ps1
Normal file
61
Modules/.build/Update-FormatsToProcessInPSD1.ps1
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
Function Update-FormatsToProcessInPSD1 {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Updates the PSD1 FormatsToProcess entry
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Update-FormatsToProcessInPSD1 .\Alkami.PowerShell.IIS\Public .\Alkami.PowerShell.IIS\Alkami.PowerShell.IIS.psd1
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
|
||||||
|
.PARAMETER Psd1FilePath
|
||||||
|
The file to be rewritten. (This is called out in case more than one .psd1 file exists in a folder.)
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath,
|
||||||
|
[String]$Psd1FilePath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
# In the future, may want to ship all the format files separately
|
||||||
|
# Currently there is Get-ContentFromFormatFilesInPath and Join-PS1XMLFromFiles that smacks these two together
|
||||||
|
# So for now, we just want to write out the name of the built ps1xml to the psd1
|
||||||
|
# The names are the same, so that's easy.
|
||||||
|
|
||||||
|
$contents = (Get-Content $Psd1FilePath)
|
||||||
|
$newContents = @()
|
||||||
|
|
||||||
|
$projectName = [System.IO.Path]::GetFileNameWithoutExtension($psd1Filepath)
|
||||||
|
$magicWord = 'FormatsToProcess'
|
||||||
|
$magicConcat = " = `"$projectName.ps1xml`""
|
||||||
|
|
||||||
|
$foundFormatsLine = $false
|
||||||
|
foreach($line in $contents) {
|
||||||
|
if ($line.Trim().StartsWith($magicWord)) {
|
||||||
|
$splits = $line -split '='
|
||||||
|
$line = $splits[0].TrimEnd() + $magicConcat
|
||||||
|
$foundFormatsLine = $true
|
||||||
|
}
|
||||||
|
$newContents += $line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$foundFormatsLine) {
|
||||||
|
$newContents = @()
|
||||||
|
## We couldn't find an existing formats line in that PSD1, so we need to add one.
|
||||||
|
## We are gonna add it after FunctionsToExport
|
||||||
|
$duplicatableTag = 'FunctionsToExport'
|
||||||
|
foreach($line in $contents) {
|
||||||
|
$newContents += $line
|
||||||
|
if ($line.Trim().StartsWith($duplicatableTag)) {
|
||||||
|
$splits = $line -split '='
|
||||||
|
$newLine = $splits[0].TrimEnd().Replace($duplicatableTag,$magicWord) + $magicConcat
|
||||||
|
|
||||||
|
$newContents += $newLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -Path $Psd1FilePath -Value $newContents
|
||||||
|
}
|
||||||
|
}
|
43
Modules/.build/Update-FunctionNamesInPSD1.ps1
Normal file
43
Modules/.build/Update-FunctionNamesInPSD1.ps1
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
Function Update-FunctionNamesInPSD1 {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Concatenates all the function names from Get-FunctionNames for insertion into the PSD1 file referenced.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Update-FunctionNamesInPSD1 .\Alkami.PowerShell.IIS\Public .\Alkami.PowerShell.IIS\Alkami.PowerShell.IIS.psd1
|
||||||
|
|
||||||
|
.PARAMETER FolderPath
|
||||||
|
The name of the folder to examine all files under.
|
||||||
|
|
||||||
|
.PARAMETER Psd1FilePath
|
||||||
|
The file to be rewritten. (This is called out in case more than one .psd1 file exists in a folder.)
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[String]$FolderPath,
|
||||||
|
[String]$Psd1FilePath
|
||||||
|
)
|
||||||
|
process {
|
||||||
|
$functionNames = @(Get-FunctionNames $FolderPath) | Sort-Object
|
||||||
|
|
||||||
|
$toBeJoinedFunctionNames = @()
|
||||||
|
|
||||||
|
foreach($name in $functionNames) {
|
||||||
|
$toBeJoinedFunctionNames += "'$name'"
|
||||||
|
}
|
||||||
|
|
||||||
|
$joinedFunctionNames = $toBeJoinedFunctionNames -Join ','
|
||||||
|
|
||||||
|
$contents = (Get-Content $Psd1FilePath)
|
||||||
|
$newContents = @()
|
||||||
|
|
||||||
|
foreach($line in $contents) {
|
||||||
|
if ($line.Trim().StartsWith("FunctionsToExport")) {
|
||||||
|
$splits = $line -split '='
|
||||||
|
$line = $splits[0].TrimEnd() + " = $joinedFunctionNames"
|
||||||
|
}
|
||||||
|
$newContents += $line
|
||||||
|
}
|
||||||
|
Set-Content -Path $Psd1FilePath -Value $newContents
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-16"?>
|
||||||
|
<package>
|
||||||
|
<metadata>
|
||||||
|
<id>Alkami.DevOps.Certificates</id>
|
||||||
|
<version>$version$</version>
|
||||||
|
<title>Alkami Platform Modules - DevOps - Certificates</title>
|
||||||
|
<authors>Alkami Technologies</authors>
|
||||||
|
<owners>Alkami Technologies</owners>
|
||||||
|
<projectUrl>https://extranet.alkamitech.com/display/ORB/Alkami.DevOps.Certificates</projectUrl>
|
||||||
|
<iconUrl>https://www.alkami.com/files/alkamilogo75x75.png</iconUrl>
|
||||||
|
<licenseUrl>http://alkami.com/files/orblicense.html</licenseUrl>
|
||||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
<description>Installs the DevOps Certificates module for use with PowerShell.</description>
|
||||||
|
<releaseNotes />
|
||||||
|
<tags>PowerShell</tags>
|
||||||
|
<copyright>Copyright (c) 2018 Alkami Technologies</copyright>
|
||||||
|
<dependencies>
|
||||||
|
<dependency id="Alkami.PowerShell.Common" version="3.2.6" />
|
||||||
|
<dependency id="Alkami.PowerShell.Services" version="3.1.7" />
|
||||||
|
<dependency id="Alkami.Ops.Common" version="3.0.3" />
|
||||||
|
<dependency id="Alkami.PowerShell.IIS" version="3.4.0" />
|
||||||
|
<dependency id="Alkami.Ops.SecretServer" version="3.0.2" />
|
||||||
|
</dependencies>
|
||||||
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<file src="AlkamiManifest.xml" target="AlkamiManifest.xml" />
|
||||||
|
<file src="tools\ChocolateyInstall.ps1" target="tools\ChocolateyInstall.ps1" />
|
||||||
|
<file src="tools\ChocolateyUninstall.ps1" target="tools\ChocolateyUninstall.ps1" />
|
||||||
|
<!-- <file src="public\*.ps1" target="module\public" /> -->
|
||||||
|
<!-- <file src="private\*.ps1" target="module\private" /> -->
|
||||||
|
<file src="*.psd1" target="module\" />
|
||||||
|
<file src="*.psm1" target="module\" />
|
||||||
|
</files>
|
||||||
|
</package>
|
@ -0,0 +1,21 @@
|
|||||||
|
@{
|
||||||
|
RootModule = 'Alkami.DevOps.Certificates.psm1'
|
||||||
|
ModuleVersion = '3.20.11'
|
||||||
|
GUID = 'bf9b5927-92e8-4eff-b8f3-c19cc554e4c2'
|
||||||
|
Author = 'SRE'
|
||||||
|
CompanyName = 'Alkami Technologies, Inc.'
|
||||||
|
Copyright = '(c) 2018 Alkami Technologies, Inc. All rights reserved.'
|
||||||
|
Description = 'A set of functions used to deploy the ORB application'
|
||||||
|
PowerShellVersion = '5.0'
|
||||||
|
RequiredModules = 'Alkami.PowerShell.Common','Alkami.PowerShell.Services','Alkami.Ops.Common','Alkami.PowerShell.IIS','Alkami.Ops.SecretServer'
|
||||||
|
FunctionsToExport = 'Compress-Certificates','Export-Certificates','Export-CertificatesToFileSystem','Get-ExpiringCertificates','Get-PrivateKeyPermissions','Get-SecretServerConnection','Import-Certificates','Import-PfxCertificateWithPermissions','Import-PodFromSecretServer','Publish-PodToSecretServer','Read-AppTierCertificates','Read-Certificates','Read-WebTierCertificates','Remove-Certificate','Save-CertificatesToDisk','Update-CertBindings'
|
||||||
|
AliasesToExport = 'Load-AppTierCertificates','Load-Certificates','Load-WebTierCertificates'
|
||||||
|
PrivateData = @{
|
||||||
|
PSData = @{
|
||||||
|
Tags = @('powershell', 'module', 'deploy', 'deployment')
|
||||||
|
ProjectUri = 'Https://extranet.alkamitech.com/display/SRE/Alkami.DevOps.Certificate+Module'
|
||||||
|
IconUri = 'https://www.alkami.com/files/alkamilogo75x75.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpInfoURI = 'Https://extranet.alkamitech.com/display/SRE/Alkami.DevOps.Certificate+Module'
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{90fa52ce-26c5-44f0-a0c5-0ac3355e6fdc}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>MyApplication</RootNamespace>
|
||||||
|
<AssemblyName>MyApplication</AssemblyName>
|
||||||
|
<Name>Alkami.DevOps.Certificates</Name>
|
||||||
|
<PreBuildScript>..\build-project.ps1 (Join-Path $(SolutionDir) "Alkami.DevOps.Certificates")</PreBuildScript>
|
||||||
|
<PostBuildScript>Invoke-Pester;</PostBuildScript>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Private\" />
|
||||||
|
<Folder Include="Public\" />
|
||||||
|
<Folder Include="tools\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Alkami.DevOps.Certificates.psd1" />
|
||||||
|
<Compile Include="Private\Export-CertChain.ps1" />
|
||||||
|
<Compile Include="Private\Export-CertificateToFileSystem.ps1" />
|
||||||
|
<Compile Include="Private\Get-Cert.ps1" />
|
||||||
|
<Compile Include="Private\Get-CertificateChain.ps1" />
|
||||||
|
<Compile Include="Private\Get-CertificateExportInfo.ps1" />
|
||||||
|
<Compile Include="Private\Get-CertificateExportName.ps1" />
|
||||||
|
<Compile Include="Private\Get-CertificateStoreName.ps1" />
|
||||||
|
<Compile Include="Private\SecretServerConnection.ps1" />
|
||||||
|
<Compile Include="Private\SecretServerConnection.tests.ps1" />
|
||||||
|
<Compile Include="Public\Export-Certificates.tests.ps1" />
|
||||||
|
<Compile Include="Public\Export-CertificatesToFileSystem.ps1" />
|
||||||
|
<Compile Include="Public\Get-PrivateKeyPermissions.ps1" />
|
||||||
|
<Compile Include="Public\Get-SecretServerConnection.ps1" />
|
||||||
|
<Compile Include="Public\Import-Certificates.tests.ps1" />
|
||||||
|
<Compile Include="Public\Import-PodFromSecretServer.ps1" />
|
||||||
|
<Compile Include="Public\Read-AppTierCertificates.ps1" />
|
||||||
|
<Compile Include="Public\Read-Certificates.ps1" />
|
||||||
|
<Compile Include="Public\Read-WebTierCertificates.ps1" />
|
||||||
|
<Compile Include="Public\Publish-PodToSecretServer.ps1" />
|
||||||
|
<Compile Include="Public\Save-CertificatesToDisk.ps1" />
|
||||||
|
<Compile Include="Public\Update-CertBindings.tests.ps1" />
|
||||||
|
<Compile Include="tools\chocolateyInstall.ps1" />
|
||||||
|
<Compile Include="tools\chocolateyUninstall.ps1" />
|
||||||
|
<Compile Include="Private\Confirm-Cert.ps1" />
|
||||||
|
<Compile Include="Private\Export-Cert.ps1" />
|
||||||
|
<Compile Include="Public\Compress-Certificates.ps1" />
|
||||||
|
<Compile Include="Public\Export-Certificates.ps1" />
|
||||||
|
<Compile Include="Public\Get-ExpiringCertificates.ps1" />
|
||||||
|
<Compile Include="Private\Import-Cert.ps1" />
|
||||||
|
<Compile Include="Public\Import-Certificates.ps1" />
|
||||||
|
<Compile Include="Private\Set-CertPermissions.ps1" />
|
||||||
|
<Compile Include="Public\Update-CertBindings.ps1" />
|
||||||
|
<Compile Include="Private\VariableDeclarations.ps1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Alkami.DevOps.Certificates.nuspec" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Target Name="Build" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\PowerShell Tools for Visual Studio\PowerShellTools.targets" Condition="Exists('$(MSBuildExtensionsPath)\PowerShell Tools for Visual Studio\PowerShellTools.targets')" />
|
||||||
|
</Project>
|
12
Modules/Alkami.DevOps.Certificates/AlkamiManifest.xml
Normal file
12
Modules/Alkami.DevOps.Certificates/AlkamiManifest.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<packageManifest>
|
||||||
|
<version>1.0</version>
|
||||||
|
<general>
|
||||||
|
<creatorCode>Alkami</creatorCode>
|
||||||
|
<element>Alkami.DevOps.Certificates</element>
|
||||||
|
<componentType>SREModule</componentType>
|
||||||
|
</general>
|
||||||
|
<moduleManifest>
|
||||||
|
<status>Production</status>
|
||||||
|
</moduleManifest>
|
||||||
|
</packageManifest>
|
46
Modules/Alkami.DevOps.Certificates/Private/Confirm-Cert.ps1
Normal file
46
Modules/Alkami.DevOps.Certificates/Private/Confirm-Cert.ps1
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
function Confirm-Cert {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Validates that Certificate is provided.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$certName,
|
||||||
|
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]$storeName
|
||||||
|
)
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$OriginalErrorActionPreference = $ErrorActionPreference;
|
||||||
|
$ErrorActionPreference = "Continue";
|
||||||
|
|
||||||
|
Write-Output ("Validating certificate $certName");
|
||||||
|
|
||||||
|
[Alkami.Ops.Common.Cryptography.CertificateHelper]::ValidateCertificate(
|
||||||
|
$certName,
|
||||||
|
$storeName,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine);
|
||||||
|
|
||||||
|
Write-Output ("Certificate $certName Passed Validation");
|
||||||
|
}
|
||||||
|
catch [Alkami.Ops.Common.Exceptions.InvalidCertificateException]
|
||||||
|
{
|
||||||
|
Write-Warning ("Certificate validation failed");
|
||||||
|
Write-Host (" Error: " + $_.Exception.Message);
|
||||||
|
Write-Host (" Name: " + $_.Exception.CertificateName);
|
||||||
|
Write-Host (" Thumbprint: " + $_.Exception.CertificateThumbPrint);
|
||||||
|
Write-Host (" Effective Date: " + $_.Exception.EffectiveDateTime);
|
||||||
|
Write-Host (" Expiration Date: " + $_.Exception.ExpirationDateTime + "`n");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
$ErrorActionPreference = $OriginalErrorActionPreference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
37
Modules/Alkami.DevOps.Certificates/Private/Export-Cert.ps1
Normal file
37
Modules/Alkami.DevOps.Certificates/Private/Export-Cert.ps1
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
function Export-Cert {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports a Certificate.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([System.Object])]
|
||||||
|
Param(
|
||||||
|
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[string]$exportPath,
|
||||||
|
|
||||||
|
[parameter(Mandatory=$false)]
|
||||||
|
[string]$exportPassword,
|
||||||
|
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]$storeName
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($exportPassword)
|
||||||
|
{
|
||||||
|
return ,[Alkami.Ops.Common.Cryptography.CertificateHelper]::ExportAllCertificates(
|
||||||
|
$storeName,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine,
|
||||||
|
$exportPath,
|
||||||
|
$exportPassword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ,[Alkami.Ops.Common.Cryptography.CertificateHelper]::ExportAllCertificates(
|
||||||
|
$storeName,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine,
|
||||||
|
$exportPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
|||||||
|
function Export-CertChain {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports a Certificate's Chain.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[ValidateNotNull()]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$ExportStorePath,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$ExportCertPath,
|
||||||
|
$ADGroups
|
||||||
|
)
|
||||||
|
$certName = $exportCertPath.Split("\") | Select-Object -Last 1
|
||||||
|
$chain = Get-CertificateChain $cert $exportStorePath
|
||||||
|
$chainInfo = [System.Collections.ArrayList]::new()
|
||||||
|
foreach ($chainCert in $chain) {
|
||||||
|
|
||||||
|
$chainCertStore = Get-CertificateStoreName $chainCert
|
||||||
|
if (!$chainCertStore) {
|
||||||
|
Write-Warning "Chain is broken for cert $certName and thumbprint $($chainCert.thumbprint)"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
$exportChainPath = $exportCertPath, "ChainedCertificates", $chainCertStore -join "\"
|
||||||
|
|
||||||
|
$exportInfo = Export-CertificateToFileSystem $chainCert $exportChainPath -IsChainExport $true -ADGroups $ADGroups
|
||||||
|
if ($null -eq $exportInfo) {break}
|
||||||
|
|
||||||
|
[void]$chainInfo.Add($exportInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chainInfo
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
function Export-CertificateToFileSystem {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports a Certificate to a Store.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[ValidateNotNull()]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert,
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[ValidateNotNull()]
|
||||||
|
[string]$ExportStorePath,
|
||||||
|
[bool]$IsChainExport = $false,
|
||||||
|
[string[]]$ADGroups
|
||||||
|
)
|
||||||
|
$certName = Get-CertificateExportName $cert
|
||||||
|
$exportCertPath = if ($IsChainExport) {$exportStorePath}else {Join-Path $exportStorePath $certName}
|
||||||
|
|
||||||
|
$exportInfo = Get-CertificateExportInfo $cert $exportCertPath
|
||||||
|
if ($exportInfo.certExportType -eq [System.Security.Cryptography.X509Certificates.X509ContentType]::Unknown) {return $null}
|
||||||
|
|
||||||
|
if (-Not (Test-Path $exportCertPath -PathType Container)) {
|
||||||
|
New-Item $exportCertPath -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$exportInfo.certName = $certName
|
||||||
|
$exportInfo.certPassword = ([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | Sort-Object {Get-Random})[0..128] -join ''
|
||||||
|
$exportInfo.ADGroups = $ADGroups
|
||||||
|
|
||||||
|
if ($exportInfo.certExportType -eq [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx) {
|
||||||
|
$password = $exportInfo.certPassword | ConvertTo-SecureString -AsPlainText -Force
|
||||||
|
try {
|
||||||
|
Export-PfxCertificate -Cert $cert -ProtectTo $ADGroups -FilePath $exportInfo.exportCertFile -Password $password
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Certificate $certName with thumbprint $($cert.Thumbprint) but could not be exported
|
||||||
|
$($_.Exception.Message)"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$certBytes = $cert.Export($exportInfo.certExportType)
|
||||||
|
[void][io.file]::WriteAllBytes($exportInfo.exportCertFile, $certBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exportInfo
|
||||||
|
}
|
36
Modules/Alkami.DevOps.Certificates/Private/Get-Cert.ps1
Normal file
36
Modules/Alkami.DevOps.Certificates/Private/Get-Cert.ps1
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
function Get-Cert {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetches a Certificate.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Thumbprint,
|
||||||
|
[string]$StoreName,
|
||||||
|
[string]$FriendlyName
|
||||||
|
)
|
||||||
|
|
||||||
|
$certStore = @{ }
|
||||||
|
$certStores = Get-ChildItem Cert:\LocalMachine\ | ForEach-Object { "Cert:\LocalMachine\$($_.Name)" }
|
||||||
|
if ($StoreName) {
|
||||||
|
$certStores = $certStores | Where-Object { $_ -Match "\\$StoreName" }
|
||||||
|
}
|
||||||
|
foreach ($store in $certStores) {
|
||||||
|
Get-ChildItem $store | Where-Object { $_.NotAfter -gt (Get-Date) } | ForEach-Object {
|
||||||
|
if ($certStore.ContainsKey($_.Thumbprint)) {
|
||||||
|
$certStore[$_.Thumbprint].Add($_)
|
||||||
|
} else {
|
||||||
|
$list = [System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate]]::new()
|
||||||
|
$list.Add($_)
|
||||||
|
$certStore.Add($_.Thumbprint, $list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Thumbprint) {
|
||||||
|
return $certStore[$Thumbprint]
|
||||||
|
} elseif ($FriendlyName) {
|
||||||
|
return $certStore.Values | Where-Object { $_.FriendlyName -match $FriendlyName }
|
||||||
|
}
|
||||||
|
return $certStore.Values
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
function Get-CertificateChain {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetches a Certificate's Chain.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter()]
|
||||||
|
$Cert
|
||||||
|
)
|
||||||
|
$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
|
||||||
|
[void]$chain.Build($cert)
|
||||||
|
return $chain.ChainElements.Certificate | Select-Object -Skip 1
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
function Get-CertificateExportInfo {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetches a Certificate's Export Information.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ExportCertPath)
|
||||||
|
|
||||||
|
$exportInfo = [PSCustomObject]@{
|
||||||
|
CertExportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx
|
||||||
|
ExportCertFile = Join-Path $exportCertPath "$certName.pfx"
|
||||||
|
ExportCertPath = $exportCertPath
|
||||||
|
CertPassword = ""
|
||||||
|
ADGroups = ""
|
||||||
|
CertName = ""
|
||||||
|
ExpirationDate = $cert.NotAfter
|
||||||
|
Thumbprint = $cert.Thumbprint
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cert.HasPrivateKey) {
|
||||||
|
if (!$cert.PrivateKey.CspKeyContainerInfo.Exportable) {
|
||||||
|
Write-Warning "Certificate $certName with thumbprint $($cert.Thumbprint) has a private key but is marked as unexportable.
|
||||||
|
This certificate will not be exported"
|
||||||
|
$exportInfo.certExportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$exportInfo.certExportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
|
||||||
|
$exportInfo.exportCertFile = Join-Path $exportCertPath "$certName.cer"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exportInfo
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
function Get-CertificateExportName {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetches a Certificate's Export Name.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
$Cert
|
||||||
|
)
|
||||||
|
|
||||||
|
$canonicalName = ($cert.Subject.Trim().Split(",") | Where-Object {$_ -match "CN="} | Select-Object -First 1 ) -replace "CN=", ""
|
||||||
|
$invalidFileNameChars = [IO.Path]::GetInvalidFileNameChars() -join ''
|
||||||
|
$validFileNameCN = ($canonicalName -replace ("[{0}]" -f [RegEx]::Escape($invalidFileNameChars)))
|
||||||
|
|
||||||
|
$certName = if ($validFileNameCN) { $validFileNameCN } else { $cert.Thumbprint }
|
||||||
|
|
||||||
|
return $certName.Trim()
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
function Get-CertificateStoreName {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetches a Certificate's Store Name.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter()]
|
||||||
|
$Cert
|
||||||
|
)
|
||||||
|
$psParent = Get-Cert -Thumbprint $cert.Thumbprint | Select-Object -ExpandProperty PSParentPath
|
||||||
|
if (!$psParent) {return}
|
||||||
|
$storeName = $psParent.Split("\") | Select-Object -Last 1
|
||||||
|
return $storeName
|
||||||
|
}
|
33
Modules/Alkami.DevOps.Certificates/Private/Import-Cert.ps1
Normal file
33
Modules/Alkami.DevOps.Certificates/Private/Import-Cert.ps1
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
function Import-Cert {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Imports a Certificate into a Store.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$certFullName,
|
||||||
|
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]$storeName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$importPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($importPassword)
|
||||||
|
{
|
||||||
|
return [Alkami.Ops.Common.Cryptography.CertificateHelper]::LoadCertificateToStore(
|
||||||
|
$certFullName,
|
||||||
|
$storeName,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine,
|
||||||
|
$importPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Alkami.Ops.Common.Cryptography.CertificateHelper]::LoadCertificateToStore(
|
||||||
|
$certFullName,
|
||||||
|
$storeName,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine);
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
# Ignore Measure-HelpSynopsis warnings in the PSScript Analyzer
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('Alkami.PowerShell.PSScriptAnalyzerRules\Measure-HelpSynopsis', '', Scope = 'Class')]
|
||||||
|
# Ignore Measure-HelpSynopsis warnings in the PSScript Analyzer - PS Classes can't do CmdletBinding, afaict
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('Alkami.PowerShell.PSScriptAnalyzerRules\Measure-CmdletBinding', '', Scope = 'Class')]
|
||||||
|
|
||||||
|
Class SecretServerConnection {
|
||||||
|
[string]$api
|
||||||
|
[string]$site
|
||||||
|
[string]$userName
|
||||||
|
[string]$domain
|
||||||
|
[string]$password
|
||||||
|
[string]$tokenRoute
|
||||||
|
[ScriptBlock]$commonFilters = { "?filter.includeRestricted=true&filter.searchtext=$searchString&filter.folderId=$folderId&filter.includeSubFolders=true" }
|
||||||
|
[ScriptBlock]$updateEndpoint
|
||||||
|
[System.Collections.Generic.Dictionary[[String], [String]]]$commonHeader
|
||||||
|
$token
|
||||||
|
|
||||||
|
SecretServerConnection() { }
|
||||||
|
|
||||||
|
SecretServerConnection([string]$site, [string]$userName, [string]$password) {
|
||||||
|
$this.site = $site
|
||||||
|
$this.tokenRoute = "$site/oauth2/token"
|
||||||
|
$this.api = $site, "api/v1" -join "/"
|
||||||
|
$this.updateEndpoint = { "/secrets/$secretId/fields/$fieldToUpdate" }
|
||||||
|
$this.userName = $userName
|
||||||
|
$this.password = $password
|
||||||
|
$this.commonHeader = [System.Collections.Generic.Dictionary[[String], [String]]]::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
[void]Authenticate() {
|
||||||
|
$this.Authenticate($False)
|
||||||
|
}
|
||||||
|
[void]Authenticate([bool]$UseTwoFactor) {
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$creds = @{
|
||||||
|
username = $this.username
|
||||||
|
password = $this.password
|
||||||
|
grant_type = "password"
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = $null
|
||||||
|
If ($UseTwoFactor) {
|
||||||
|
$headers = @{
|
||||||
|
"OTP" = (Read-Host -Prompt "Enter your OTP for 2FA: ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod $this.tokenRoute -Method Post -Body $creds -Headers $headers
|
||||||
|
$this.token = $response.access_token;
|
||||||
|
if ($this.commonHeader.Count -gt 0) { $this.commonHeader.Clear() }
|
||||||
|
$this.commonHeader.Add("Authorization", "Bearer $($this.token)")
|
||||||
|
} catch {
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[object]GetSecretByName([string]$secretName, [string]$folderId) {
|
||||||
|
# $searchString is used in $commonFilters above. This "not used" warning is a lie.
|
||||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'False Positive')]
|
||||||
|
$searchString = $secretName
|
||||||
|
$filters = $this.commonFilters.Invoke()
|
||||||
|
Write-Debug "$($this.api)/secrets/lookup$filters"
|
||||||
|
$result = Invoke-RestMethod "$($this.api)/secrets$filters" -Headers $this.commonHeader
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
[object]GetSecretById([string]$secretId) {
|
||||||
|
|
||||||
|
Write-Debug "$($this.api)/secrets/$secretId"
|
||||||
|
$response = Invoke-RestMethod "$($this.api)/secrets/$secretId" -Headers $this.commonHeader
|
||||||
|
|
||||||
|
return $response
|
||||||
|
}
|
||||||
|
[object]GetSecretByFolderId([string]$folderId) {
|
||||||
|
$parameters = "?filter.folderId=$folderId"
|
||||||
|
|
||||||
|
$response = Invoke-RestMethod "$($this.api)/secrets/$parameters" -Headers $this.commonHeader
|
||||||
|
|
||||||
|
return $response.records
|
||||||
|
}
|
||||||
|
|
||||||
|
[object]GetSecretTemplateById([int]$templateId, [int]$folderId) {
|
||||||
|
$secret = Invoke-RestMethod "$($this.api)/secrets/stub?filter.secrettemplateid=$templateId&filter.folderId=$folderId" -Headers $this.commonHeader
|
||||||
|
|
||||||
|
return $secret
|
||||||
|
}
|
||||||
|
|
||||||
|
[int]GetSecretTemplateIdByName([string]$templateName) {
|
||||||
|
$searchString = "?filter.searchText=$templateName"
|
||||||
|
|
||||||
|
$secret = Invoke-RestMethod -Method Get "$($this.api)/secret-templates$searchString" -Headers $this.commonHeader
|
||||||
|
|
||||||
|
if ($secret.records) {
|
||||||
|
$record = $secret.records | Where-Object { $_.name -eq $templateName }
|
||||||
|
return $record.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
[int]CreateSecret([object]$secret, [int]$folderId, [string]$secretName) {
|
||||||
|
|
||||||
|
$secret.name = $secretName
|
||||||
|
$secret.siteId = 1
|
||||||
|
$secret.folderId = $folderId
|
||||||
|
|
||||||
|
# Get Secret Template first, set up the template with various items and their values and then pass it to create secret
|
||||||
|
$requestCreateSecretParams = $secret | ConvertTo-Json
|
||||||
|
$secret = Invoke-RestMethod "$($this.api)/secrets/" -Method Post -Body $requestCreateSecretParams -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
|
||||||
|
return $secret.id
|
||||||
|
}
|
||||||
|
[object]UpdateField([string]$secretId, [string]$fieldToUpdate, [string]$newValue) {
|
||||||
|
$body = @{ value = $newValue } | ConvertTo-Json
|
||||||
|
$response = Invoke-RestMethod -Method Put -Uri "$($this.api)$($this.updateEndpoint.Invoke())" -Headers $this.commonHeader -ContentType "application/json" -Body $body
|
||||||
|
return $response
|
||||||
|
}
|
||||||
|
|
||||||
|
[object]GetField([string]$secretId, [string]$field) {
|
||||||
|
$response = Invoke-RestMethod -Method Get -Uri "$($this.api)$($this.updateEndpoint.Invoke())" -Headers $this.commonHeader
|
||||||
|
return $response.Records
|
||||||
|
}
|
||||||
|
|
||||||
|
[void]UploadFile([int]$secretId, [string]$fieldToUpdate, [string]$filePath) {
|
||||||
|
|
||||||
|
$fileName = Get-ChildItem $filePath | Select-Object -ExpandProperty Name
|
||||||
|
|
||||||
|
$requestUploadFileParams = @{
|
||||||
|
fileName = $fileName;
|
||||||
|
fileAttachment = [IO.File]::ReadAllBytes($filePath)
|
||||||
|
} | ConvertTo-Json
|
||||||
|
|
||||||
|
Invoke-RestMethod -Method Put -Uri "$($this.api)$($this.updateEndpoint.Invoke())" -Headers $this.commonHeader -Body $requestUploadFileParams -ContentType "application/json"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[bool]DownloadFile([int]$secretId, [string]$fieldToupdate, [string]$filePath) {
|
||||||
|
# invokeing $this.updateEndpoint.Invoke() sets variables hidden above in SecretServerConnection
|
||||||
|
Write-Debug "$($this.api)$($this.updateEndpoint.Invoke())"
|
||||||
|
try {
|
||||||
|
Invoke-RestMethod -Method Get -Uri "$($this.api)$($this.updateEndpoint.Invoke())" -Headers $this.commonHeader -OutFile $filePath | Out-Null
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
Write-Warning "An error occurred while downloading a file."
|
||||||
|
Write-Warning -Message "StatusCode: $($_.Exception.Response.StatusCode.value__)"
|
||||||
|
Write-Warning -Message "StatusDescription: $($_.Exception.Response.StatusDescription)"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[int]AddFolder([int]$parentFolderId, [string]$folderName, [bool]$inheritPermissions, [bool]$inheritSecretPolicy) {
|
||||||
|
|
||||||
|
$folderStub = Invoke-RestMethod "$($this.api)/folders/stub" -Method GET -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
|
||||||
|
$folderStub.folderName = $folderName
|
||||||
|
$folderStub.folderTypeId = 1
|
||||||
|
$folderStub.inheritPermissions = $inheritPermissions
|
||||||
|
$folderStub.inheritSecretPolicy = $inheritSecretPolicy
|
||||||
|
$folderStub.parentFolderId = $parentFolderId
|
||||||
|
|
||||||
|
$folderArgs = $folderStub | ConvertTo-Json
|
||||||
|
|
||||||
|
$folderAddResult = Invoke-RestMethod "$($this.api)/folders" -Method POST -Body $folderArgs -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
|
||||||
|
return $folderAddResult.id
|
||||||
|
}
|
||||||
|
[int]AddFolder([int]$parentFolderId, [string]$folderName) {
|
||||||
|
return $this.AddFolder($parentFolderId, $folderName, $true, $true)
|
||||||
|
}
|
||||||
|
[object]GetFolderById([int]$folderId) {
|
||||||
|
$folderGetResult = Invoke-RestMethod "$($this.api)/folders/$folderId" -Method GET -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
return $folderGetResult.records
|
||||||
|
}
|
||||||
|
[object]GetChildFolders([int]$parentId) {
|
||||||
|
$parameters = "?filter.parentFolderId=$parentId"
|
||||||
|
$folderGetResult = Invoke-RestMethod "$($this.api)/folders/$parameters" -Method GET -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
return $folderGetResult.records
|
||||||
|
}
|
||||||
|
[object]GetFolderIdByName([string]$folderName) {
|
||||||
|
$searchFilter = "?filter.searchText=$folderName"
|
||||||
|
|
||||||
|
$searchResults = Invoke-RestMethod "$($this.api)/folders$searchFilter" -Method GET -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
|
||||||
|
return $searchResults.Records.Id
|
||||||
|
}
|
||||||
|
[object]GetFolderIdByName([string]$folderName, [int]$parentFolderId) {
|
||||||
|
$searchString = $folderName
|
||||||
|
$folderId = $parentFolderId
|
||||||
|
$filters = "?filter.includeRestricted=true&filter.parentFolderId=$folderId&filter.searchtext=%$searchString"
|
||||||
|
Write-Debug $filters
|
||||||
|
$searchResults = Invoke-RestMethod "$($this.api)/folders$filters" -Method GET -Headers $this.commonHeader -ContentType "application/json"
|
||||||
|
|
||||||
|
return $searchResults.Records.Id
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$global:functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
#Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
InModuleScope -ModuleName Alkami.DevOps.Certificates -ScriptBlock {
|
||||||
|
Write-Host "InModuleScope - Overriding SUT: $($global:functionPath)"
|
||||||
|
Import-Module $global:functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
$inScopeModuleForAssert = "Alkami.DevOps.Certificates"
|
||||||
|
|
||||||
|
Describe "SecretServerConnection" {
|
||||||
|
Context "When Calling GetSecretByName" {
|
||||||
|
|
||||||
|
Mock Invoke-RestMethod {
|
||||||
|
#Write-Warning "Mocked Invoke-RestMethod"
|
||||||
|
if ($uri -like "*CertName*123*") {
|
||||||
|
return New-Object psobject -Property @{
|
||||||
|
Name = "I'm a fake Secret"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Returns Secrets Which Match The Supplied Name And FolderId" {
|
||||||
|
|
||||||
|
$connection = [SecretServerConnection]::new()
|
||||||
|
$result = $connection.GetSecretByName("CertName", 123)
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Invoke-RestMethod
|
||||||
|
|
||||||
|
$result.Name | should -BeLike "I'm a fake Secret"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Return Secrets Which Do Not Match The Supplied Name" {
|
||||||
|
|
||||||
|
$connection = [SecretServerConnection]::new()
|
||||||
|
$result = $connection.GetSecretByName("BadName", 123)
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Invoke-RestMethod
|
||||||
|
|
||||||
|
$result | should -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
It "Does Not Return Secrets Which Do Match The Supplied Name But Not the Folder Id" {
|
||||||
|
|
||||||
|
$connection = [SecretServerConnection]::new()
|
||||||
|
$result = $connection.GetSecretByName("CertName", 456)
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Invoke-RestMethod
|
||||||
|
|
||||||
|
$result | should -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
function Set-CertPermissions {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Assigns Certificate Permissions for a user.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$certThumprint,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$user
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
|
||||||
|
$certObj = Get-ChildItem "Cert:\LocalMachine\my\$certThumprint"
|
||||||
|
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($CertObj)
|
||||||
|
|
||||||
|
if ($rsaCert.key -and $rsaCert.key.UniqueName) {
|
||||||
|
$fileName = $rsaCert.key.UniqueName
|
||||||
|
$directoryRsaMachineKeys = Join-Path "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" $fileName
|
||||||
|
$directoryCryptoKeys = Join-Path "C:\ProgramData\Microsoft\Crypto\Keys\" $fileName
|
||||||
|
|
||||||
|
if (Test-Path $directoryRsaMachineKeys) {
|
||||||
|
$path = $directoryRsaMachineKeys
|
||||||
|
} elseif (Test-Path $directoryCryptoKeys) {
|
||||||
|
$path = $directoryCryptoKeys
|
||||||
|
} else {
|
||||||
|
Write-Error "$logLead : Did not find an associated ACL File for $certThumbprint."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Error "$logLead : Unable to determine Unique Key Name for $certThumprint"
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissions = Get-Acl -Path $path
|
||||||
|
$rule = New-Object Security.AccessControl.FileSystemAccessRule $user, "FullControl", Allow
|
||||||
|
$permissions.AddAccessRule($rule)
|
||||||
|
Set-Acl -Path $path -AclObject $permissions
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
function Compress-Certificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Combine Certificates into a .zip File.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
$Certificates,
|
||||||
|
$TempFolder
|
||||||
|
)
|
||||||
|
#Prepare certificate folders by zipping them.
|
||||||
|
foreach ($certificate in $Certificates) {
|
||||||
|
$zipFileName = $certificate.Name.Trim() + ".zip"
|
||||||
|
$CompressedDir = $TempFolder, $zipFileName -join "\"
|
||||||
|
Remove-Item $CompressedDir -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item (Join-Path $certificate.Folder $zipFileName) -Force -ErrorAction SilentlyContinue
|
||||||
|
[System.IO.Compression.ZipFile]::CreateFromDirectory($certificate.Folder,
|
||||||
|
$CompressedDir, [System.IO.Compression.CompressionLevel]::Optimal, $false)
|
||||||
|
Move-Item $CompressedDir $certificate.Folder -Force
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
function Export-Certificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports certificates from a machine.
|
||||||
|
|
||||||
|
.PARAMETER exportPassword
|
||||||
|
|
||||||
|
The password used to secure the certificate with
|
||||||
|
|
||||||
|
.PARAMETER exportPath
|
||||||
|
|
||||||
|
The path the certificates are exported to. If no path is defined, the current working directory is used
|
||||||
|
|
||||||
|
.PARAMETER skipRootCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the exporting of certificates in the 'Root' store
|
||||||
|
|
||||||
|
.PARAMETER skipPersonalCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the exporting of certificates in the 'My' store
|
||||||
|
|
||||||
|
.PARAMETER skipTrustedCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the exporting of certificates in the 'Trusted' store
|
||||||
|
|
||||||
|
.PARAMETER skipIACerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the exporting of certificates in the 'CertificateAuthority' store
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[parameter(Mandatory=$false)]
|
||||||
|
[string]$exportPassword,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$exportPath = $PWD,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$skipRootCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$skipPersonalCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$skipTrustedCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$skipIACerts
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!$skipPersonalCerts.IsPresent -and !$exportPassword)
|
||||||
|
{
|
||||||
|
throw "Export Password cannot be null"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($skipRootCerts.IsPresent -and $skipPersonalCerts.IsPresent -and $skipTrustedCerts.IsPresent -and $skipIACerts.IsPresent)
|
||||||
|
{
|
||||||
|
throw "All Skip Switches cannot be set"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path $exportPath))
|
||||||
|
{
|
||||||
|
[System.IO.Directory]::CreateDirectory($exportPath) | Out-Null
|
||||||
|
}
|
||||||
|
## Removing because of issues mocking. This shouldn't be an issue.
|
||||||
|
# Clear-Host
|
||||||
|
|
||||||
|
[System.Reflection.Assembly]::LoadWithPartialName("System.Security.Cryptography") | Out-Null
|
||||||
|
## TODO: Don't just blindly set the $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
[Collections.Generic.List[Alkami.Ops.Common.Exceptions.CertificateExportException]]$exportErrors = @()
|
||||||
|
|
||||||
|
if (!($skipPersonalCerts.IsPresent))
|
||||||
|
{
|
||||||
|
Write-Host "Exporting Personal Certs"
|
||||||
|
|
||||||
|
$pfxExportPath = (Join-Path $exportPath "Personal")
|
||||||
|
|
||||||
|
if (!(Test-Path $pfxExportPath))
|
||||||
|
{
|
||||||
|
Write-Host "Creating directory at $pfxExportPath"
|
||||||
|
|
||||||
|
[System.IO.Directory]::CreateDirectory($pfxExportPath) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = Export-Cert -exportPath $pfxExportPath $exportPassword -storeName ([System.Security.Cryptography.X509Certificates.StoreName]::My)
|
||||||
|
|
||||||
|
$exportErrors.AddRange($errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($skipIACerts.IsPresent))
|
||||||
|
{
|
||||||
|
Write-Host "Exporting IA Certs"
|
||||||
|
|
||||||
|
$iaExportPath = (Join-Path $exportPath "IA")
|
||||||
|
|
||||||
|
if (!(Test-Path $iaExportPath))
|
||||||
|
{
|
||||||
|
[System.IO.Directory]::CreateDirectory($iaExportPath) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = Export-Cert -exportPath $iaExportPath -storeName ([System.Security.Cryptography.X509Certificates.StoreName]::CertificateAuthority)
|
||||||
|
|
||||||
|
$exportErrors.AddRange($errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($skipRootCerts.IsPresent))
|
||||||
|
{
|
||||||
|
Write-Host "Exporting Root Certs"
|
||||||
|
|
||||||
|
$rootExportPath = (Join-Path $exportPath "Root")
|
||||||
|
|
||||||
|
if (!(Test-Path $rootExportPath))
|
||||||
|
{
|
||||||
|
[System.IO.Directory]::CreateDirectory($rootExportPath) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = Export-Cert -exportPath $rootExportPath -storeName ([System.Security.Cryptography.X509Certificates.StoreName]::Root)
|
||||||
|
|
||||||
|
$exportErrors.AddRange($errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($skipTrustedCerts.IsPresent))
|
||||||
|
{
|
||||||
|
Write-Host "Exporting Trusted Certs"
|
||||||
|
|
||||||
|
$trustedPeopleExportPath = (Join-Path $exportPath "TrustedPeople")
|
||||||
|
|
||||||
|
if (!(Test-Path $trustedPeopleExportPath))
|
||||||
|
{
|
||||||
|
[System.IO.Directory]::CreateDirectory($trustedPeopleExportPath) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors = Export-Cert -exportPath $trustedPeopleExportPath -storeName ([System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople)
|
||||||
|
|
||||||
|
$exportErrors.AddRange($errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($exportError in $exportErrors)
|
||||||
|
{
|
||||||
|
[Alkami.Ops.Common.Exceptions.CertificateExportException]$strongError = $exportError
|
||||||
|
Write-Warning ("{0}" -f $strongError.Message)
|
||||||
|
Write-Warning ("`tError: {0}" -f $strongError.BaseExceptionMessage.TrimEnd())
|
||||||
|
Write-Warning ("`tName: {0}" -f $strongError.CertificateName)
|
||||||
|
Write-Warning ("`tThumbprint: {0}" -f $strongError.CertificateThumbPrint)
|
||||||
|
Write-Warning ("`tSubject: {0}" -f $strongError.Subject.Trim())
|
||||||
|
Write-Output `n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
# Yes. I hate this at least as much as you do. If you can find a way around it, please show me.
|
||||||
|
$global:functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
$compiledModuleForMock = "Alkami.DevOps.Certificates"
|
||||||
|
$exportPassword = "Test"
|
||||||
|
$exportPath = "c:\temp\CertificateTest"
|
||||||
|
|
||||||
|
Remove-FileSystemItem -Path $exportPath -Force -Recurse -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
New-Item -ItemType Directory $exportPath -Force | Out-Null
|
||||||
|
|
||||||
|
InModuleScope -ModuleName Alkami.DevOps.Certificates -ScriptBlock {
|
||||||
|
Write-Host "InModuleScope - Overriding SUT: $($global:functionPath)"
|
||||||
|
Import-Module $global:functionPath -Force
|
||||||
|
$inScopeModuleForAssert = "Alkami.DevOps.Certificates"
|
||||||
|
$exportPassword = "Test"
|
||||||
|
$exportPath = "c:\temp\CertificateTest"
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Mock -CommandName Export-Cert {
|
||||||
|
[Collections.Generic.List[Alkami.Ops.Common.Exceptions.CertificateExportException]] $emptyArr = @()
|
||||||
|
return ,$emptyArr
|
||||||
|
} -ModuleName $moduleForMock
|
||||||
|
#prevents the mehtod under test from clearing the screen during testing.
|
||||||
|
Mock -ModuleName $moduleForMock -CommandName Clear-Host {}
|
||||||
|
Mock -CommandName Write-Host -MockWith {} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
Describe "Export-Certificates" {
|
||||||
|
|
||||||
|
Context "When there are bad inputs when calling Export-Certificates" {
|
||||||
|
|
||||||
|
It "Throws Exception if all skip flags set" {
|
||||||
|
|
||||||
|
{ Export-Certificates $exportPassword -skipPersonalCert -skipRootCerts -skipTrustedCert -skipIACert } | Should Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When the parameters are valid" {
|
||||||
|
|
||||||
|
It "Doesnt Require Password if not exporting Personal Certificates" {
|
||||||
|
{ Export-Certificates -skipPersonalCerts } | Should -Not -Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates Path if it doesn't exist" {
|
||||||
|
|
||||||
|
$path = 'c:\temp\badPath'
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $path
|
||||||
|
|
||||||
|
$path | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates Personal Directory" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath
|
||||||
|
|
||||||
|
Join-Path $exportPath "Personal" | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates IA Directory" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath
|
||||||
|
|
||||||
|
Join-Path $exportPath "IA" | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates Root Directory" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath
|
||||||
|
|
||||||
|
Join-Path $exportPath "Root" | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates TrustedPeople Directory" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath
|
||||||
|
|
||||||
|
Join-Path $exportPath "TrustedPeople" | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Export-Certs 4 times when no filters are used" {
|
||||||
|
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath
|
||||||
|
|
||||||
|
#$result | Should be {}
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Export-Cert -Times 4 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Export-Certs 3 times when skipRootCerts filter used" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath -skipRootCerts
|
||||||
|
|
||||||
|
#$result | Should be {}
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Export-Cert -Times 3 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Export-Certs 3 times when skipPersonalCerts filter used" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath -skipPersonalCerts
|
||||||
|
|
||||||
|
#$result | Should be {}
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Export-Cert -Times 3 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Export-Certs 3 times when skipTrustedCerts filter used" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath -skipTrustedCerts
|
||||||
|
|
||||||
|
#$result | Should be {}
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Export-Cert -Times 3 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Export-Certs 3 times when skipIACerts filter used" {
|
||||||
|
|
||||||
|
Export-Certificates $exportPassword -exportPath $exportPath -skipIACerts
|
||||||
|
|
||||||
|
#$result | Should be {}
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Export-Cert -Times 3 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
function Export-CertificatesToFileSystem {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports certificates from desired stores (My, CertificateAuthority, Root, and TrustedPeople by default)
|
||||||
|
onto the file system.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script will export all public certificates and private keys in a given store to the file system. The
|
||||||
|
certificate chain is also exported in an individualized folder along with the certificate so that you
|
||||||
|
can delineate what certificates are needed for the one exported.
|
||||||
|
|
||||||
|
A password is generated for private keys, if ADGroups are given they are assigned to the private keys as well.
|
||||||
|
This script also returns a model of the data exported. With this model the file system data is more
|
||||||
|
easily manipulated and was originally intended to be used by a module for uploading these certificates
|
||||||
|
and their chains to individual secrets in secret server.
|
||||||
|
|
||||||
|
.PARAMETER PodName
|
||||||
|
String
|
||||||
|
Used to create a leaf folder with in which the certificates will be exported and set as a property
|
||||||
|
on the model returned as a result of this cmdlet.
|
||||||
|
|
||||||
|
.PARAMETER CertRoot
|
||||||
|
String
|
||||||
|
Location that this cmdlet will be working out of. This is set to LocalMachine but could be set to Personal
|
||||||
|
or other certificate providers.
|
||||||
|
|
||||||
|
.PARAMETER StoresToExport
|
||||||
|
StoreName[]
|
||||||
|
All certificates in these stores will be exported along with their certificate chains and private keys.
|
||||||
|
|
||||||
|
.PARAMETER ADGroups
|
||||||
|
String[]
|
||||||
|
Any ADGroups or UserAccounts that are given will be assigned to private keys exported. These users
|
||||||
|
will be able to import the private keys without a password, although one is generated and set
|
||||||
|
by this cmdlet regardless
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Export-CertificatesToFileSystem -PodName "Pod 1.1.1"
|
||||||
|
|
||||||
|
Base usage, will export all certificates in the default stores and their private keys to the Default Export Root
|
||||||
|
Folder under leaf folder 'Pod 1.1.1'. A password will be generated for the private keys and returned to the
|
||||||
|
caller in the model object.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Export-CertificatesToFileSystem -PodName "Pod 7.0.5" -ADGroups "corp\JSmith","corp\CCarter" -RootExportFolder "C:\Exports"
|
||||||
|
|
||||||
|
Will export all certificates in the default stores and their private keys to the C:\Exports
|
||||||
|
Folder under leaf folder 'Pod 7.0.5'. Users JSmith and CCarter will be able to import the private keys
|
||||||
|
without the password, however passwords generated for these certificates will be returned with the
|
||||||
|
model object that this cmdlet produces.
|
||||||
|
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $True)]
|
||||||
|
[string]$PodName,
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
[string]$CertRoot = "Cert:\LocalMachine\",
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
[string]$RootExportFolder = "c:\temp\CertExports",
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
$StoresToExport = @([System.Security.Cryptography.X509Certificates.StoreName]::My,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]::CertificateAuthority,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]::Root,
|
||||||
|
[System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople),
|
||||||
|
[Parameter(Mandatory = $False)]
|
||||||
|
[string[]]$ADGroups = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
begin {
|
||||||
|
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
|
||||||
|
$Pod = [PSObject]@{
|
||||||
|
PodName = $PodName
|
||||||
|
ExportFolder = (Join-Path $RootExportFolder $PodName)
|
||||||
|
StoresToExport = $StoresToExport
|
||||||
|
CertRoot = $CertRoot
|
||||||
|
Stores = @{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -Path $Pod.ExportFolder) {
|
||||||
|
Write-Host "$logLead : Removing items from $($Pod.ExportFolder) before beginning"
|
||||||
|
Remove-FileSystemItem -Path $Pod.ExportFolder -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -PathType Container -Path $Pod.ExportFolder)) {
|
||||||
|
New-Item $Pod.ExportFolder -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process {
|
||||||
|
foreach ($storeName in $Pod.StoresToExport) {
|
||||||
|
$store = [PSObject]@{
|
||||||
|
Name = $storeName
|
||||||
|
ExportStorePath = Join-Path $Pod.ExportFolder $storeName
|
||||||
|
StorePath = Join-Path $Pod.CertRoot $storeName
|
||||||
|
Certificates = @{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Powershell's Paths don't match the X509 enum. So here we do a poor man's lookup. If we ever need more stores than just the CA, this needs to be expanded to something more robust and clean.
|
||||||
|
if ($store.Name -eq "CertificateAuthority") {
|
||||||
|
$store.StorePath = "Cert:\LocalMachine\CA"
|
||||||
|
}
|
||||||
|
|
||||||
|
$Pod.Stores.Add($storeName, $store)
|
||||||
|
$Pod.Stores | Add-Member -MemberType NoteProperty -Name $storeName -Value $store
|
||||||
|
|
||||||
|
New-Item $store.ExportStorePath -ItemType Directory | Out-Null
|
||||||
|
|
||||||
|
#foreach cert in store
|
||||||
|
try {
|
||||||
|
$certificates = Get-ChildItem $store.StorePath;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Cannot find Certificates in $storeName"
|
||||||
|
}
|
||||||
|
if ($certificates.Count -gt 0) {
|
||||||
|
foreach ($cert in $certificates) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$exportInfo = Export-CertificateToFileSystem -cert $cert -exportStorePath $store.ExportStorePath -ADGroups $ADGroups
|
||||||
|
if ($null -eq $exportInfo) { continue }
|
||||||
|
|
||||||
|
$chainInfo = Export-CertChain -cert $cert -exportStorePath $store.ExportStorePath -exportCertPath $exportInfo.exportCertPath -ADGroups $ADGroups
|
||||||
|
|
||||||
|
$CertificateChain = [PSObject]@{
|
||||||
|
Folder = Join-Path $exportInfo.ExportCertPath "ChainedCertificates"
|
||||||
|
Certificates = @{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($chainedCert in $chainInfo) {
|
||||||
|
$CertificateChain.Certificates.Add($chainedCert.certName, $chainedCert)
|
||||||
|
$CertificateChain.Certificates | Add-Member -MemberType NoteProperty -Name $chainedCert.certName -Value $chainedCert
|
||||||
|
}
|
||||||
|
|
||||||
|
$certificate = [PSObject]@{
|
||||||
|
Name = $exportInfo.certName
|
||||||
|
FilePath = $exportInfo.exportCertFile
|
||||||
|
Folder = $exportInfo.exportCertPath
|
||||||
|
Password = $exportInfo.certPassword
|
||||||
|
CertificateChain = $CertificateChain
|
||||||
|
ADGroups = $exportInfo.ADGroups
|
||||||
|
ExpirationDate = $exportInfo.ExpirationDate
|
||||||
|
Thumbprint = $exportInfo.Thumbprint
|
||||||
|
}
|
||||||
|
|
||||||
|
$store.Certificates.Add($certificate.Name, $certificate)
|
||||||
|
|
||||||
|
$store.Certificates | Add-Member -MemberType NoteProperty -Name $certificate.Name -Value $certificate
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
Write-Error "Certificate has failed to export
|
||||||
|
Name: $($certificate.Name), FriendlyName: $($cert.FriendlyName), Thumprint: $($cert.Thumbprint), Store: $storeName
|
||||||
|
$($_.Exception) $($_.Message) $($_.ScriptStackTrace)"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end {
|
||||||
|
return $Pod
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
function Get-ExpiringCertificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets certificates that will expire soon.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Takes a list of machines and connects to their certificate stores, compares the expiration date
|
||||||
|
to a configureable threshold date. If the expiration date is less than the threshold date the
|
||||||
|
certificate is returned in a list.
|
||||||
|
|
||||||
|
.PARAMETER ComputerName
|
||||||
|
[string[]]One or more computers on which to get expired certificates from.
|
||||||
|
|
||||||
|
.PARAMETER ExpirationThreshold
|
||||||
|
[int] An amount of days you wish to set the threshold.
|
||||||
|
Note* Can be negative. Defaults to 30
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ExpiringCertificates "Server1","Server2"
|
||||||
|
Will connect to these servers in parallel, and retrieve certificates that are due to expire within 30 days or less from now.
|
||||||
|
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ExpiringCertificates "Server1","Server2" -Threshold 90
|
||||||
|
Will connect to these servers in parallel, and retrieve certificates that are due to expire within 90 days or less from now.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[Alias("Servers","Machines")]
|
||||||
|
[string[]]$ComputerName,
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[int]$ExpirationThreshold = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
begin{
|
||||||
|
#Ensure there are machines to connect to
|
||||||
|
$sessions = New-PSSession $ComputerName -ErrorAction SilentlyContinue;
|
||||||
|
$Unreachable = $ComputerName | Where-Object {$sessions.ComputerName -notcontains $_}
|
||||||
|
if($Unreachable){Write-Host "Could not connect to $Unreachable";}
|
||||||
|
if(!$sessions){throw "Could not connect to any machines";}
|
||||||
|
}
|
||||||
|
process{
|
||||||
|
$ScriptBlock = {
|
||||||
|
param($ExpirationThreshold);
|
||||||
|
|
||||||
|
$personalStore = [System.Security.Cryptography.X509Certificates.StoreName]::My;
|
||||||
|
$machineStore = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine;
|
||||||
|
|
||||||
|
$certificates = [Alkami.Ops.Common.Cryptography.CertificateHelper]::GetAllCertificates($personalStore, $machineStore, $env:COMPUTERNAME);
|
||||||
|
|
||||||
|
$expirationThresholdDate = (Get-Date).AddDays($ExpirationThreshold);
|
||||||
|
|
||||||
|
#Filter certificates by threshold date
|
||||||
|
$expiredCertificates = $certificates | Where-Object {$_.notAfter -lt $expirationThresholdDate} | `
|
||||||
|
Select-Object @{N="Machine";E={$env:COMPUTERNAME}},@{N="ExpirationDate";E={$_.NotAfter}},`
|
||||||
|
@{N="DaysRemaining";E={(New-TimeSpan -start (get-date) -end $_.notAfter | Select-Object -ExpandProperty days)}},Thumbprint,FriendlyName,Subject;
|
||||||
|
|
||||||
|
if($expiredCertificates){Write-Output $expiredCertificates;}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Connect to machines and execute
|
||||||
|
$expiredCertificates = Invoke-Command -Session $sessions -ScriptBlock $ScriptBlock -ArgumentList $ExpirationThreshold;
|
||||||
|
|
||||||
|
Remove-PSSession $sessions;
|
||||||
|
|
||||||
|
return $expiredCertificates;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
function Get-PrivateKeyPermissions {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Fetch Private Key Permissions.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$certificate
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Exception Occurred While Reading Private Key Details for Certificate with Thumbprint $($certificate.Thumbprint): $($_.Exception.Message.Trim())"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$rsaKeyFileName = $rsaKey.Key.UniqueName
|
||||||
|
$rsaKeyPath = "${env:ALLUSERSPROFILE}\Microsoft\Crypto\RSA\MachineKeys\$rsaKeyFileName"
|
||||||
|
|
||||||
|
if (Test-Path $rsaKeyPath) {
|
||||||
|
|
||||||
|
return (Get-Acl -Path $rsaKeyPath).Access
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Unable to Find Private Key for Certificate with Thumbprint $($certificate.Thumbprint) at $rsaKeyPath"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
function Get-SecretServerConnection {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exports a [SecretServerConnection] which can be used from Powershell's commandline. Largely for testing purposes.
|
||||||
|
|
||||||
|
.PARAMETER Site
|
||||||
|
The uri for the Secret Server to connect to.
|
||||||
|
|
||||||
|
.PARAMETER userName
|
||||||
|
Secret Server username.
|
||||||
|
|
||||||
|
.PARAMETER password
|
||||||
|
Secret Server password.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Site,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$UserName,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Password
|
||||||
|
)
|
||||||
|
|
||||||
|
return [SecretServerConnection]::new([string]$Site, [string]$UserName, [string]$Password);
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
function Import-Certificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Imports certificates onto a machine.
|
||||||
|
|
||||||
|
.PARAMETER importPassword
|
||||||
|
|
||||||
|
The password used to import the certificate with. Only used when Personal store certificates are imported.
|
||||||
|
|
||||||
|
.PARAMETER importPath
|
||||||
|
The path the certificates are imported from. If no path is defined, the current working directory is used.
|
||||||
|
|
||||||
|
.PARAMETER usersWhoNeedRights
|
||||||
|
Optional list of users to grant rights for certificates which have private keys. If not supplied, defaults to IIS_IUSRS + $defaultUsersWhoNeedRights array.
|
||||||
|
|
||||||
|
.PARAMETER securityGroup
|
||||||
|
The Security Group for each GMSA account (pod6, pod8). When not supplied, defaults to the value in the machine.config on the current server.
|
||||||
|
If not found in machine.config, throws.
|
||||||
|
|
||||||
|
.PARAMETER skipRootCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the importing of certificates in the 'Root' store.
|
||||||
|
|
||||||
|
.PARAMETER skipPersonalCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the importing of certificates in the 'My' store.
|
||||||
|
|
||||||
|
.PARAMETER skipTrustedCerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the importing of certificates in the 'Trusted' store.
|
||||||
|
|
||||||
|
.PARAMETER skipIACerts
|
||||||
|
|
||||||
|
When this flag is supplied it will skip the importing of certificates in the 'CertificateAuthority' store.
|
||||||
|
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[parameter(Mandatory = $false)]
|
||||||
|
[string]$importPassword,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$importPath = $PWD,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string[]]$usersWhoNeedRights = @("IIS_IUSRS"),
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$securityGroup,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[hashtable]$certPasswordList = @{ },
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$skipRootCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$skipPersonalCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$skipTrustedCerts,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$skipIACerts
|
||||||
|
)
|
||||||
|
|
||||||
|
$unsignedCertFileIncludeFilter = @("*.cer", "*.crt")
|
||||||
|
|
||||||
|
# Modify this to add/remove accounts as defaults when usersWhoNeedRights is not supplied by the user
|
||||||
|
# Will be formatted as FH\<securityGroup>.<account>
|
||||||
|
$defaultUsersWhoNeedRights = @("radium$", "nag$", "dbms$", "micro$")
|
||||||
|
|
||||||
|
if (!$skipPersonalCerts.IsPresent -and !$importPassword) {
|
||||||
|
throw "Import Password cannot be null";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($skipRootCerts.IsPresent -and $skipPersonalCerts.IsPresent -and $skipTrustedCerts.IsPresent -and $skipIACerts.IsPresent) {
|
||||||
|
throw "All Skip Switches cannot be set";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path $importPath)) {
|
||||||
|
throw "Import Path not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !($PSBoundParameters.ContainsKey('securityGroup')) ) {
|
||||||
|
Write-Host "securityGroup was not supplied by the user. Attempting to pull securityGroup from Environment.UserPrefix in machine.config."
|
||||||
|
|
||||||
|
if ($securityGroup = Get-AppSetting Environment.UserPrefix) {
|
||||||
|
Write-Host "securityGroup read from machine.config as: $securityGroup"
|
||||||
|
} else {
|
||||||
|
throw("securityGroup was not supplied by the user and could not be found in the machine config. Please specify the security group (i.e. pod6, pod8) and rerun this function.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !($PSBoundParameters.ContainsKey('usersWhoNeedRights')) ) {
|
||||||
|
Write-Host "usersWhoNeedRights was not supplied by the user. Using default accounts."
|
||||||
|
|
||||||
|
$defaultUsersWhoNeedRights = foreach ($defaultUserWhoNeedsRights in $defaultUsersWhoNeedRights) {
|
||||||
|
"fh\$securityGroup.$defaultUserWhoNeedsRights"
|
||||||
|
}
|
||||||
|
|
||||||
|
$usersWhoNeedRights += $defaultUsersWhoNeedRights
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootImportPath = (Join-Path $importPath "Root");
|
||||||
|
|
||||||
|
if (!($skipRootCerts.IsPresent) -and (Test-Path $rootImportPath)) {
|
||||||
|
Write-Host "Importing Root Certs";
|
||||||
|
|
||||||
|
$certs = Get-ChildItem $rootImportPath -Recurse -include $unsignedCertFileIncludeFilter -Exclude "WMSVC*";
|
||||||
|
|
||||||
|
foreach ($cert in $certs) {
|
||||||
|
Import-Cert $cert.FullName ([System.Security.Cryptography.X509Certificates.StoreName]::Root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$iaImportPath = (Join-Path $importPath "IA");
|
||||||
|
|
||||||
|
if (!($skipIACerts.IsPresent) -and (Test-Path $iaImportPath)) {
|
||||||
|
Write-Host "Importing IA Certs";
|
||||||
|
|
||||||
|
$certs = Get-ChildItem $iaImportPath -Recurse -include $unsignedCertFileIncludeFilter -Exclude "WMSVC*";
|
||||||
|
|
||||||
|
foreach ($cert in $certs) {
|
||||||
|
Import-Cert $cert.FullName ([System.Security.Cryptography.X509Certificates.StoreName]::CertificateAuthority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$trustedImportPath = (Join-Path $importPath "TrustedPeople");
|
||||||
|
|
||||||
|
if (!($skipTrustedCerts.IsPresent) -and (Test-Path $trustedImportPath)) {
|
||||||
|
Write-Host "Importing Trusted Certs";
|
||||||
|
|
||||||
|
$certs = Get-ChildItem $trustedImportPath -Recurse -include $unsignedCertFileIncludeFilter -Exclude "WMSVC*";
|
||||||
|
|
||||||
|
foreach ($cert in $certs) {
|
||||||
|
Import-Cert $cert.FullName ([System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pfxImportPath = (Join-Path $importPath "Personal");
|
||||||
|
|
||||||
|
if (!($skipPersonalCerts.IsPresent) -and (Test-Path $pfxImportPath)) {
|
||||||
|
Write-Host "Importing Personal Certs";
|
||||||
|
|
||||||
|
$certs = Get-ChildItem $pfxImportPath -Recurse -include @("*.pfx", "*.cer") -Exclude "WMSVC*";
|
||||||
|
|
||||||
|
# Add any additional Service Users to $usersWhoNeedRights if they're assigned to a Windows Service
|
||||||
|
# and are running under the environment's gMSA Security Group
|
||||||
|
$ServiceUserAccounts = Get-CIMInstance Win32_Service | Where-Object { $_.StartName -match $securityGroup }
|
||||||
|
if ($ServiceUserAccounts) {
|
||||||
|
$usersWhoNeedRights += $ServiceUserAccounts.StartName
|
||||||
|
}
|
||||||
|
|
||||||
|
$usersWhoNeedRights = $usersWhoNeedRights | Sort-Object | Get-Unique
|
||||||
|
|
||||||
|
foreach ($cert in $certs) {
|
||||||
|
if ($cert.Extension -eq ".pfx") {
|
||||||
|
$currentCertPassword = $importPassword;
|
||||||
|
|
||||||
|
if ($certPasswordList.ContainsKey($cert.Name)) {
|
||||||
|
$currentCertPassword = certPasswordList.Get_Item($cert.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Import-Cert $cert.FullName ([System.Security.Cryptography.X509Certificates.StoreName]::My) $currentCertPassword;
|
||||||
|
|
||||||
|
Confirm-Cert $cert.Name ([System.Security.Cryptography.X509Certificates.StoreName]::My);
|
||||||
|
|
||||||
|
$x509cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($cert.FullName, $currentCertPassword);
|
||||||
|
|
||||||
|
$usersWhoNeedRights | ForEach-Object {
|
||||||
|
|
||||||
|
$user = $_;
|
||||||
|
|
||||||
|
Write-Host ("Granting {0} rights to PK for certificate {1}" -f $user, $targetCert.Name);
|
||||||
|
|
||||||
|
Set-CertPermissions $x509cert.Thumbprint $user;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$currentCertPassword = $importPassword;
|
||||||
|
|
||||||
|
if ($certPasswordList.ContainsKey($cert.Name)) {
|
||||||
|
$currentCertPassword = certPasswordList.Get_Item($cert.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Import-Cert $cert.FullName ([System.Security.Cryptography.X509Certificates.StoreName]::My);
|
||||||
|
|
||||||
|
Confirm-Cert $cert.Name ([System.Security.Cryptography.X509Certificates.StoreName]::My);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$global:functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
$exportPassword = "Test"
|
||||||
|
$exportPath = "c:\temp\CertificateTest"
|
||||||
|
$usersWhoNeedRights = @("testuser1", "testuser2")
|
||||||
|
|
||||||
|
Remove-FileSystemItem -Path $exportPath -Force -Recurse -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
New-Item -ItemType Directory $exportPath -Force | Out-Null
|
||||||
|
|
||||||
|
InModuleScope -ModuleName Alkami.DevOps.Certificates -ScriptBlock {
|
||||||
|
Write-Host "InModuleScope - Overriding SUT: $($global:functionPath)"
|
||||||
|
Import-Module $global:functionPath -Force
|
||||||
|
$inScopeModuleForAssert = "Alkami.DevOps.Certificates"
|
||||||
|
$moduleForMock = ""
|
||||||
|
$exportPassword = "Test"
|
||||||
|
$exportPath = "c:\temp\CertificateTest"
|
||||||
|
$usersWhoNeedRights = @("testuser1", "testuser2")
|
||||||
|
|
||||||
|
|
||||||
|
Describe "Import-Certificates" {
|
||||||
|
|
||||||
|
Mock -CommandName Write-Host -ModuleName $moduleForMock -MockWith {}
|
||||||
|
Mock -CommandName Write-Warning -ModuleName $moduleForMock -MockWith {}
|
||||||
|
Mock -CommandName Get-AppSetting -ModuleName $moduleForMock -MockWith { return $null }
|
||||||
|
|
||||||
|
Context "When there are bad inputs when calling Import-Certificates" {
|
||||||
|
|
||||||
|
It "Throws Exception if all skip flags set" {
|
||||||
|
|
||||||
|
{ Import-Certificates $exportPassword -skipPersonalCert -skipRootCerts -skipTrustedCert -skipIACert -securityGroup "pod1" } | Should -Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Throws Exception if path doesn't exist" {
|
||||||
|
|
||||||
|
{ Import-Certificates $exportPassword -importPath 'C:\BadPath' -securityGroup "pod1" } | Should -Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Throws Exception if securityGroup is not supplied and it is not found in machine.config" {
|
||||||
|
|
||||||
|
{ Import-Certificates $exportPassword } | Should -Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When Inputs are correct" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Join-Path { return "C:\temp\testpath" }
|
||||||
|
Mock -ModuleName $moduleForMock Test-Path { return $true }
|
||||||
|
Mock -ModuleName $moduleForMock -CommandName Import-Cert { }
|
||||||
|
Mock -ModuleName $moduleForMock Confirm-Cert { } -Verifiable
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return @{ FullName = "c:\temp\testpath\Test.pfx"; Name = "Test.pfx"; Extension = ".pfx"} }
|
||||||
|
Mock -ModuleName $moduleForMock Get-AlkamiServices { @{ Name = "Alkami.Radium"} }
|
||||||
|
Mock -ModuleName $moduleForMock Get-CIMInstance { @{ StartName = "podtest.user"} }
|
||||||
|
Mock -ModuleName $moduleForMock Set-CertPermissions {}
|
||||||
|
Mock -ModuleName $moduleForMock New-Object { @{ Thumbprint = "ABCDEFG"} }
|
||||||
|
|
||||||
|
It "Doesnt Require Password if not exporting Personal Certificates" {
|
||||||
|
|
||||||
|
{ Import-Certificates -skipPersonalCerts -securityGroup "pod1" } | Should -Not -Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Import-Cert when importing personal certs" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Import-Cert -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Confirm-Cert when importing personal certs" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Confirm-Cert -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Set-CertPermissions for default users + test WMI user when usersWhoNeedRights is not supplied" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Set-CertPermissions -Times 5 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Set-CertPermissions for supplied users + test WMI user when usersWhoNeedRights is supplied" {
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -usersWhoNeedRights $usersWhoNeedRights -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Set-CertPermissions -Times 2 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Set-CertPermissions for default users + test WMI user when usersWhoNeedRights is not supplied and additional users found in services" {
|
||||||
|
Mock -ModuleName $moduleForMock Get-CIMInstance { @( @{StartName = "pod1.user"}, @{StartName = "podtest.user"} ) }
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Set-CertPermissions -Times 6 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Set-CertPermissions for supplied users + test WMI user when usersWhoNeedRights is supplied and additional users found in services" {
|
||||||
|
Mock -ModuleName $moduleForMock Get-CIMInstance { @( @{StartName = "pod1.user"}, @{StartName = "podtest.user"} ) }
|
||||||
|
Import-Certificates $exportPassword -skipRootCerts -skipTrustedCerts -skipIACerts -usersWhoNeedRights $usersWhoNeedRights -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Set-CertPermissions -Times 3 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Import-Cert when importing root certs" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipPersonalCerts -skipTrustedCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Import-Cert -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Import-Cert when importing trusted certs" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipPersonalCerts -skipRootCerts -skipIACerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Import-Cert -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Import-Cert when importing IA certs" {
|
||||||
|
|
||||||
|
Import-Certificates $exportPassword -skipPersonalCerts -skipRootCerts -skipTrustedCerts -securityGroup "pod1"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $inScopeModuleForAssert -CommandName Import-Cert -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
function Import-PfxCertificateWithPermissions {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Import a PFX certificate with appropriate user permissions without needing the folder structure as specified in Import-Certificates.
|
||||||
|
|
||||||
|
.PARAMETER ImportPassword
|
||||||
|
The password used to import the certificate.
|
||||||
|
|
||||||
|
.PARAMETER PathToPfxCertificate
|
||||||
|
The path to a specified PFX file.
|
||||||
|
|
||||||
|
.PARAMETER UsersWhoNeedRights
|
||||||
|
Optional list of users to grant rights for certificates which have private keys. If not supplied, then the resulting Import-Certificates call will assign the default users
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword 'PASSWORD_GOES_HERE' -PathToPfxCertificate "\\10.0.16.67\c`$\temp\mccu.com.pfx"
|
||||||
|
[Import-PfxCertificateWithPermissions] : Copied cert to C:\Users\ccoane\AppData\Local\Temp\2\904bf9e6-4be5-4b8e-805b-03817b6dd198\Personal
|
||||||
|
Importing Personal Certs
|
||||||
|
Validating certificate mccu.com.pfx
|
||||||
|
Certificate mccu.com.pfx Passed Validation
|
||||||
|
Granting fh\dev.dbms$ rights to PK for certificate
|
||||||
|
Granting fh\dev.micro$ rights to PK for certificate
|
||||||
|
Granting FH\dev.micro$ rights to PK for certificate
|
||||||
|
Granting fh\dev.nag$ rights to PK for certificate
|
||||||
|
Granting fh\dev.radium$ rights to PK for certificate
|
||||||
|
Granting iis_iusrs rights to PK for certificate
|
||||||
|
[Import-PfxCertificateWithPermissions] : Removed temporary directory C:\Users\ccoane\AppData\Local\Temp\2\904bf9e6-4be5-4b8e-805b-03817b6dd198
|
||||||
|
|
||||||
|
|
||||||
|
// If you need to make a modification to the default user list used by Import-Certificates e.g. PFX also requires access granted to fh\xxxx.bank$
|
||||||
|
$SecurityGroup = Get-AppSetting -appSettingKey "Environment.UserPrefix"
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword 'PASSWORD_GOES_HERE' -PathToPfxCertificate "\\10.0.16.67\c`$\temp\mccu.com.pfx" -UsersWhoNeedRights @("iis_iusrs", "fh\$SecurityGroup.radium$", "fh\$SecurityGroup.nag$", "fh\$SecurityGroup.dbms$", "fh\$SecurityGroup.micro$", "fh\$SecurityGroup.bank$")
|
||||||
|
[Import-PfxCertificateWithPermissions] : Copied cert to C:\Users\ccoane\AppData\Local\Temp\2\a7ad2893-884b-4604-bed2-2c2d6bd597da\Personal
|
||||||
|
Importing Personal Certs
|
||||||
|
Validating certificate mccu.com.pfx
|
||||||
|
Certificate mccu.com.pfx Passed Validation
|
||||||
|
Granting fh\dev.bank$ rights to PK for certificate
|
||||||
|
Granting fh\dev.dbms$ rights to PK for certificate
|
||||||
|
Granting FH\dev.micro$ rights to PK for certificate
|
||||||
|
Granting fh\dev.micro$ rights to PK for certificate
|
||||||
|
Granting fh\dev.nag$ rights to PK for certificate
|
||||||
|
Granting fh\dev.radium$ rights to PK for certificate
|
||||||
|
Granting iis_iusrs rights to PK for certificate
|
||||||
|
[Import-PfxCertificateWithPermissions] : Removed temporary directory C:\Users\ccoane\AppData\Local\Temp\2\a7ad2893-884b-4604-bed2-2c2d6bd597da
|
||||||
|
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[string]$ImportPassword,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$PathToPfxCertificate,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string[]]$UsersWhoNeedRights
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
# Verify Pfx is valid and exists/accessible
|
||||||
|
if (!($PathToPfxCertificate -Like "*.pfx")) {
|
||||||
|
Write-Error "$logLead : This function is expecting a certificate with a .pfx extension as the value for `$PathToPfxCertificate. Provided value is: $PathToPfxCertificate"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path $PathToPfxCertificate)) {
|
||||||
|
Write-Error "$logLead : Unable to reach the specified file from this server. Verify the path is correct and accessible to this server. Provided value is: $PathToPfxCertificate"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$randomPath = Join-Path $Env:Temp $(New-Guid)
|
||||||
|
|
||||||
|
# Copy PFX to a randomly created folder in appropriate Import-Certificates folder structure
|
||||||
|
$tempFolderPersonalPath = New-Item -Path $randomPath -ItemType Directory -Name "Personal" -Force
|
||||||
|
Copy-Item -Path $PathToPfxCertificate -Destination $tempFolderPersonalPath
|
||||||
|
Write-Host "$logLead : Copied cert to $tempFolderPersonalPath"
|
||||||
|
|
||||||
|
# If user provided an argument for $UsersWhoNeedRights
|
||||||
|
if ($UsersWhoNeedRights) {
|
||||||
|
Import-Certificates -importPassword $ImportPassword -importPath $randomPath -usersWhoNeedRights $UsersWhoNeedRights
|
||||||
|
} else {
|
||||||
|
Import-Certificates -importPassword $ImportPassword -importPath $randomPath
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Error "$logLead : $_"
|
||||||
|
} finally {
|
||||||
|
# Delete the randomly created folder if it exists
|
||||||
|
if (Test-Path -Path $randomPath) {
|
||||||
|
Remove-Item -Path $randomPath -Recurse -Force
|
||||||
|
Write-Host "$logLead : Removed temporary directory $randomPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $randomPath
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
|
||||||
|
$importPasswordTest = "PASSWORD_GOES_HERE"
|
||||||
|
$pathToCertificateIsADirectory = "C:\Temp\Personal"
|
||||||
|
$pathToCertificateIsNotAPfxFile = "C:\Temp\Personal\Certificate.crt"
|
||||||
|
$pathToCertificateIsNotAccessible = "C:\Temp\Personal\CertificateThatShouldNotBeAccessibleUnlessYouMakeThisFileToSpiteMe.pfx"
|
||||||
|
$pathToCertificatePfxFile = "C:\Temp\Personal\Certificate.pfx"
|
||||||
|
|
||||||
|
$WriteErrorIncorrectPfxArgument = "This function is expecting a certificate with a .pfx extension as the value for"
|
||||||
|
$WriteErrorInaccessiblePfxFile = "Unable to reach the specified file from this server"
|
||||||
|
|
||||||
|
Describe "Import-PfxCertificateWithPermissions" {
|
||||||
|
|
||||||
|
Mock -CommandName Copy-Item -ModuleName Alkami.DevOps.Certificates -MockWith { }
|
||||||
|
Mock -CommandName Import-Certificates -ModuleName Alkami.DevOps.Certificates -MockWith { }
|
||||||
|
Mock -CommandName Write-Error -ModuleName Alkami.DevOps.Certificates -MockWith { }
|
||||||
|
|
||||||
|
Context "When there are bad inputs when calling Import-PfxCertificateWithPermissions" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-Path -ModuleName Alkami.DevOps.Certificates -MockWith { return $false }
|
||||||
|
|
||||||
|
It "Writes Error if path argument has a folder path and does not point to a PFX file" {
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword $importPasswordTest -PathToPfxCertificate $pathToCertificateIsADirectory
|
||||||
|
Assert-MockCalled -CommandName Write-Error -Times 1 -Exactly -Scope It -ModuleName Alkami.DevOps.Certificates -ParameterFilter { $Message -match $WriteErrorIncorrectPfxArgument }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes Error if path argument has a file path but does not point to a PFX file" {
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword $importPasswordTest -PathToPfxCertificate $pathToCertificateIsNotAPfxFile
|
||||||
|
Assert-MockCalled -CommandName Write-Error -Times 1 -Exactly -Scope It -ModuleName Alkami.DevOps.Certificates -ParameterFilter { $Message -match $WriteErrorIncorrectPfxArgument }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes Error if path argument does not point to an accessible PFX file" {
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword $importPasswordTest -PathToPfxCertificate $pathToCertificateIsNotAccessible
|
||||||
|
Assert-MockCalled -CommandName Write-Error -Times 1 -Exactly -Scope It -ModuleName Alkami.DevOps.Certificates -ParameterFilter { $Message -match $WriteErrorInaccessiblePfxFile }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When Inputs are correct" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-Path -ModuleName Alkami.DevOps.Certificates -MockWith { return $true }
|
||||||
|
|
||||||
|
It "Assert reaches Import-Certificates " {
|
||||||
|
Import-PfxCertificateWithPermissions -ImportPassword $importPasswordTest -PathToPfxCertificate $pathToCertificatePfxFile
|
||||||
|
Assert-MockCalled -ModuleName Alkami.DevOps.Certificates Import-Certificates -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Assert randomly created folder doesn't exist after function completion " {
|
||||||
|
$folder = Import-PfxCertificateWithPermissions -ImportPassword $importPasswordTest -PathToPfxCertificate $pathToCertificatePfxFile
|
||||||
|
$folder | Should -Not -Exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
function Import-PodFromSecretServer {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Import Pod Certificates from Secret Server.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
$PodName,
|
||||||
|
[Parameter(Mandatory = $false, ParameterSetName = "credentials")]
|
||||||
|
[string]$SecretServerUrl ="https://alkami.secretservercloud.com",
|
||||||
|
[Parameter(Mandatory = $false, ParameterSetName = "credentials")]
|
||||||
|
$SecretServerUserName,
|
||||||
|
[Parameter(Mandatory = $false, ParameterSetName = "credentials")]
|
||||||
|
$SecretServerPassword,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("Web", "App")]
|
||||||
|
$ServerType,
|
||||||
|
[Parameter(Mandatory = $false, HelpMessage = "ADGroups that need permission to these certificates")]
|
||||||
|
[string[]]$ADGroups,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
$TempDirectory = "c:\temp\importTempDir",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$CertRoot = "Cert:\LocalMachine\",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$UsePassword,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("Production", "Staging")]
|
||||||
|
[string]$EnvironmentType
|
||||||
|
)
|
||||||
|
begin {
|
||||||
|
Import-AWSModule # SSM
|
||||||
|
|
||||||
|
New-Item -Path $TempDirectory -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
|
||||||
|
switch ($EnvironmentType) {
|
||||||
|
"Production" {$RootFolderName = "Production-CertApi"}
|
||||||
|
"Staging" {$RootFolderName = "Staging-CertApi"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all parmeters which weren't supplied.
|
||||||
|
if(!$EnvironmentType)
|
||||||
|
{
|
||||||
|
$EnvironmentType = Get-AppSetting Environment.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$PodName)
|
||||||
|
{
|
||||||
|
$PodName = Get-AppSetting Environment.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$ServerType)
|
||||||
|
{
|
||||||
|
$ServerType = Get-AppSetting Environment.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
if((!$SecretServerUserName -or !$SecretServerPassword))
|
||||||
|
{
|
||||||
|
if(Test-IsAws)
|
||||||
|
{
|
||||||
|
if(!$SecretServerUserName) {
|
||||||
|
$SecretServerUserName = (Get-SSMParameter -Name "secret_server_api_username" -WithDecryption $true).Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$SecretServerPassword) {
|
||||||
|
$SecretServerPassword = (Get-SSMParameter -Name "secret_server_api_password" -WithDecryption $true).Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Secret credentials must be manually provided if this is run on a non-AWS machine. They cannot be retrieved from SSM."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$secretServer = [SecretServerConnection]::new($SecretServerUrl, $SecretServerUserName, $SecretServerPassword)
|
||||||
|
$UseTwoFactor = $false
|
||||||
|
$secretServer.Authenticate($UseTwoFactor)
|
||||||
|
|
||||||
|
$NameOfCertificateField = "pfx-file"
|
||||||
|
$NameOfPasswordField = "Import Password"
|
||||||
|
Add-Type -Assembly System.IO.Compression.FileSystem
|
||||||
|
}
|
||||||
|
process {
|
||||||
|
$startTime = Get-Date
|
||||||
|
Write-Debug "Getting folder info"
|
||||||
|
$environmentFolderId = $secretServer.GetFolderIdByName($RootFolderName)
|
||||||
|
$podFolderId = $secretServer.GetFolderIdByName($PodName, $environmentFolderId)
|
||||||
|
$subFolderId = $secretServer.GetFolderIdByName($ServerType, $podFolderId) # Should be at an App/Mic/Web folder by this point. These folder names aren't unique, hence getting the environment folder id above.
|
||||||
|
|
||||||
|
$storeFolders = $secretServer.GetChildFolders($subFolderId)
|
||||||
|
|
||||||
|
$timeElapsed = New-TimeSpan -Start $startTime -End (Get-Date)
|
||||||
|
Write-Debug "Folder info retreived - $timeElapsed"
|
||||||
|
|
||||||
|
foreach ($storeFolder in $storeFolders) {
|
||||||
|
if ($storeFolder.FolderName -eq "CertificateAuthority") {$sanitizedStore = "CA"} else {$sanitizedStore = $storeFolder.FolderName} # Powershell's Paths don't match the X509 enum. So here we do a poor man's lookup. If we ever need more stores than just the CA, this needs to be expanded to something more robust and clean.
|
||||||
|
|
||||||
|
$TempStoreDirectory = Join-Path $TempDirectory $storeFolder.FolderName
|
||||||
|
|
||||||
|
$installedCerts = (Get-Cert -StoreName $sanitizedStore).thumbprint # Get certs in the current store.
|
||||||
|
|
||||||
|
Write-Debug "Getting Secrets and downloading certificate chain"
|
||||||
|
$secretInfoList = $secretServer.GetSecretByFolderId($storeFolder.id)
|
||||||
|
$secretInfoList | Where-Object {$installedCerts -notcontains $_.name.split("-")[-1]}
|
||||||
|
foreach ($secretInfo in $secretInfoList) {
|
||||||
|
$secret = $secretServer.GetSecretById($secretInfo.id)
|
||||||
|
|
||||||
|
# Don't import expired certs
|
||||||
|
$certExpirationDate = $secret.items | Where-Object {$_.fieldName -eq "ExperationDate"}
|
||||||
|
if ((Get-Date $certExpirationDate.itemValue) -gt (Get-Date)) {
|
||||||
|
if ($installedCerts) { #Don't die if there are no certs
|
||||||
|
$secretThumbprint = $secret.items | Where-Object {$_.fieldName -eq "Thumbprint"} | Select-Object -ExpandProperty itemValue
|
||||||
|
if ($installedCerts.Contains($secretThumbprint)) {
|
||||||
|
Write-Verbose "Certificate with thumbprint $secretThumbprint already installed"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($UsePassword) {
|
||||||
|
$TempPassword = $secret.items | Where-Object {$_.fieldName -eq $NameOfPasswordField} | Select-Object -ExpandProperty itemValue
|
||||||
|
$Password = ConvertTo-SecureString $TempPassword -AsPlainText -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$folder = Join-Path $TempStoreDirectory $secret.Name
|
||||||
|
Remove-Item $folder -Force -ea SilentlyContinue -Recurse
|
||||||
|
New-Item $folder -ItemType Directory -Force -ea SilentlyContinue | Out-Null
|
||||||
|
|
||||||
|
$zipFolder = $folder, "$($secret.Name).zip" -join "\"
|
||||||
|
if ($secretServer.DownloadFile($secret.Id, $NameOfCertificateField, $zipFolder)) {
|
||||||
|
$timeElapsed = New-TimeSpan -Start $startTime -End (Get-Date)
|
||||||
|
Write-Debug "downloaded... - $timeElapsed"
|
||||||
|
|
||||||
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFolder, $folder)
|
||||||
|
Write-Debug "unzipping complete - $timeElapsed"
|
||||||
|
|
||||||
|
$certificateFiles = Get-ChildItem $folder -Include "*.cer", "*.pfx" -Recurse
|
||||||
|
$certList = [System.Collections.Arraylist]::new()
|
||||||
|
foreach ($file in $certificateFiles) {
|
||||||
|
$certStore = if ($file.Directory.Name -Match $file.BaseName) {$sanitizedStore}else {$file.Directory.Name}
|
||||||
|
|
||||||
|
[void]$certList.Add(([PSObject]@{
|
||||||
|
file = $file
|
||||||
|
certStore = Join-path $CertRoot $certStore
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($cert in $certList) {
|
||||||
|
if ($cert.file.extension -eq ".cer") {
|
||||||
|
try {
|
||||||
|
Import-Certificate -FilePath $cert.file.FullName -CertStoreLocation $cert.certStore | Out-null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Failed to import Cert with filename $($cert.file.FullName)"
|
||||||
|
Write-Warning -Message "Error: $($_.Exception.ErrorRecord)"
|
||||||
|
Write-Warning -Message "Stack Trace: $($_.Exception.StackTrace)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
$installedCert = Import-PfxCertificate -FilePath $cert.file.FullName -CertStoreLocation $cert.certStore -Exportable -Password $Password
|
||||||
|
foreach ($ADGroup in $ADGroups.Split("{,}")) {
|
||||||
|
Write-Host "Setting Permissions"
|
||||||
|
Set-CertPermissions -certThumprint $installedCert.Thumbprint -user $ADGroup | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch [System.ComponentModel.Win32Exception] {
|
||||||
|
Write-Warning "Failed to import Cert with filename $($cert.file.FullName)"
|
||||||
|
Write-Warning -Message "Error: $($_.Exception.Message)"
|
||||||
|
Write-Warning -Message "Stack Trace: $($_.Exception.StackTrace)"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Failed to import Cert with filename $($cert.file.FullName)"
|
||||||
|
Write-Warning -Message "Error: $($_.Exception.ErrorRecord)"
|
||||||
|
Write-Warning -Message "Stack Trace: $($_.Exception.StackTrace)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning -Message "File could not be downloaded for certificate with thumbprint $($secretThumbprint)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
function Publish-PodToSecretServer {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Publish Pod's Certificates to Secret Server.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string[]]$ADGroups,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[PSObject]$Pod,
|
||||||
|
[Parameter (Mandatory = $false)]
|
||||||
|
[string]$PodName,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$SecretServerUserName,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$SecretServerPassword,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$SecretServerUrl ="https://alkami.secretservercloud.com",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$TempFolder = "C:\Temp",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("Production", "Staging")]
|
||||||
|
[string]$EnvironmentType,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$SubFolder
|
||||||
|
)
|
||||||
|
begin {
|
||||||
|
Import-AWSModule # SSM
|
||||||
|
|
||||||
|
switch ($EnvironmentType) {
|
||||||
|
"Production" {$RootFolderName = "Production-CertApi"}
|
||||||
|
"Staging" {$RootFolderName = "Staging-CertApi"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all parmeters which weren't supplied.
|
||||||
|
if(!$EnvironmentType)
|
||||||
|
{
|
||||||
|
$EnvironmentType = Get-AppSetting Environment.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$PodName)
|
||||||
|
{
|
||||||
|
$PodName = Get-AppSetting Environment.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$SubFolder)
|
||||||
|
{
|
||||||
|
$SubFolder = Get-AppSetting Environment.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
if((!$SecretServerUserName -or !$SecretServerPassword))
|
||||||
|
{
|
||||||
|
if(Test-IsAws)
|
||||||
|
{
|
||||||
|
if(!$SecretServerUserName) {
|
||||||
|
$SecretServerUserName = (Get-SSMParameter -Name "secret_server_api_username" -WithDecryption $true).Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$SecretServerPassword) {
|
||||||
|
$SecretServerPassword = (Get-SSMParameter -Name "secret_server_api_password" -WithDecryption $true).Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Secret credentials must be manually provided if this is run on a non-AWS machine. They cannot be retrieved from SSM."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$Pod) {
|
||||||
|
$PodName = "$PodName-CertApi" # Tack on something to make the folder name distinct from manually created folders.
|
||||||
|
$Pod = Export-CertificatesToFileSystem -PodName $PodName -ADGroups $ADGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
$secretServer = [SecretServerConnection]::new($SecretServerUrl, $SecretServerUserName, $SecretServerPassword)
|
||||||
|
$UseTwoFactor = $false
|
||||||
|
$secretServer.Authenticate($UseTwoFactor)
|
||||||
|
|
||||||
|
Add-Type -Assembly System.IO.Compression.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
process {
|
||||||
|
|
||||||
|
#Zip certficates and their chain
|
||||||
|
$certificatesFromAllStores = $Pod.Stores.Values.Certificates.Values
|
||||||
|
Compress-Certificates $certificatesFromAllStores $TempFolder
|
||||||
|
|
||||||
|
#Create folder for this pod
|
||||||
|
$rootFolderId = $SecretServer.GetFolderIdByName($RootFolderName)
|
||||||
|
|
||||||
|
$podFolderId = $secretServer.GetFolderIdByName($Pod.PodName, $rootFolderId)
|
||||||
|
if (!$podFolderId) {
|
||||||
|
$podFolderId = $secretServer.AddFolder($rootFolderId, $Pod.PodName)
|
||||||
|
}
|
||||||
|
|
||||||
|
$subFolderId = $secretServer.GetFolderIdByName($Subfolder, $podFolderId)
|
||||||
|
if (!$subFolderId) {
|
||||||
|
$subFolderId = $secretServer.AddFolder($podFolderId, $Subfolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($store in $Pod.Stores.Keys) {
|
||||||
|
$storeFolderId = $secretServer.GetFolderIdByName($store, $subFolderId)
|
||||||
|
if (!$storeFolderId) {
|
||||||
|
$storeFolderId = $secretServer.AddFolder($subFolderId, $store)
|
||||||
|
}
|
||||||
|
$certificates = $Pod.Stores.$store.Certificates.Values
|
||||||
|
|
||||||
|
foreach ($certificate in $certificates) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
#Process
|
||||||
|
#Get a new secret template
|
||||||
|
$templateId = $secretServer.GetSecretTemplateIdByName("CertificateStore")
|
||||||
|
$secret = $SecretServer.GetSecretTemplateById($templateId, $storeFolderId)
|
||||||
|
|
||||||
|
# Configure the secret
|
||||||
|
# Each of these two line blocks creates a *reference* to a specific item in the secret.items list. That reference is then set to the correct
|
||||||
|
# certificate field value, which updates the $secret.items[x].itemValue value. Clear as mud?
|
||||||
|
$certName = $secret.items | Where-Object {$_.fieldName -eq "Certificate Name"}
|
||||||
|
$certName.itemValue = $certificate.Name
|
||||||
|
|
||||||
|
$certPassword = $secret.items | Where-Object {$_.fieldName -eq "Import Password"}
|
||||||
|
$certPassword.itemValue = $certificate.Password
|
||||||
|
|
||||||
|
$certNotes = $secret.items | Where-Object {$_.fieldName -eq "Notes"}
|
||||||
|
$certNotes.itemValue = "ADGroups with access to this certificate: $($certificate.ADGroups)"
|
||||||
|
|
||||||
|
$certThumbprint = $secret.items | Where-Object {$_.fieldName -eq "Thumbprint"}
|
||||||
|
$certThumbprint.itemValue = $certificate.Thumbprint
|
||||||
|
|
||||||
|
$certExpirationDate = $secret.items | Where-Object {$_.fieldName -eq "ExperationDate"} # Yes, Expiration is mispelled, because Thycotic.
|
||||||
|
$certExpirationDate.itemValue = $certificate.ExpirationDate.ToShortDateString()
|
||||||
|
|
||||||
|
$secretName = $certificate.Name, $certificate.thumbprint, $store.ToString()[0] -join "-"
|
||||||
|
$secretId = $secretServer.CreateSecret($secret, $storeFolderId, $secretName)
|
||||||
|
|
||||||
|
#Upload certificate zip folder
|
||||||
|
Write-Output "Publishing certificate $($certificate.Name)"
|
||||||
|
$zipFilePath = $certificate.Folder, ($certificate.Name.Trim() + ".zip") -join "\"
|
||||||
|
$secretServer.UploadFile($secretId, "pfx-File", $zipFilePath)
|
||||||
|
}
|
||||||
|
catch [InvalidOperationException] {
|
||||||
|
Write-Warning "There was a problem publishing a certificate."
|
||||||
|
Write-Warning $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
function Read-AppTierCertificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Reads App Tier Certificates.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$baseFolder,
|
||||||
|
[Hashtable[]]$certificatesTable
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
|
||||||
|
[string[]]$usersWhoNeedRights = $null
|
||||||
|
$usersWhoNeedRights += "IIS_IUSRS"
|
||||||
|
|
||||||
|
foreach ($service in (Get-AppTierServices) | Where-Object {$_.User -notmatch "SYSTEM|REPLACEME"}) {
|
||||||
|
$usersWhoNeedRights += $service.User
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose ("$logLead : Users who need rights read as {0}" -f ($usersWhoNeedRights -join ","))
|
||||||
|
Read-Certificates $baseFolder $certificatesTable $usersWhoNeedRights
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Alias -name Load-AppTierCertificates -value Read-AppTierCertificates;
|
100
Modules/Alkami.DevOps.Certificates/Public/Read-Certificates.ps1
Normal file
100
Modules/Alkami.DevOps.Certificates/Public/Read-Certificates.ps1
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
function Read-Certificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Reads Certificates.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$baseFolder,
|
||||||
|
[Hashtable[]]$certificatesTable,
|
||||||
|
[string[]]$usersWhoNeedRights
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
|
||||||
|
$localMachineStoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
|
||||||
|
$trustedPeopleStore = [System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople
|
||||||
|
$personalStore = [System.Security.Cryptography.X509Certificates.StoreName]::My
|
||||||
|
$rootStore = [System.Security.Cryptography.X509Certificates.StoreName]::Root
|
||||||
|
|
||||||
|
$folderInfo = @(
|
||||||
|
|
||||||
|
@{ Name = "Root"; Store = $rootStore; Path = (Join-Path $baseFolder "ROOT"); ImportPrivateKey = $false; },
|
||||||
|
@{ Name = "Personal"; Store = $personalStore; Path = (Join-Path $baseFolder "Personal"); ImportPrivateKey = $true; },
|
||||||
|
@{ Name = "TrustedPeople"; Store = $trustedPeopleStore; Path = (Join-Path $baseFolder "TrustedPeople"); ImportPrivateKey = $false; }
|
||||||
|
)
|
||||||
|
|
||||||
|
# Loop through each folder
|
||||||
|
foreach ($folder in $folderInfo) {
|
||||||
|
Write-Output ("$logLead : Loading Certificates from {0}" -f $folder.Path)
|
||||||
|
$certificates = $certificatesTable | Where-Object {$_.FileName.ToUpperInvariant().StartsWith($folder.Path.ToUpperInvariant())}
|
||||||
|
|
||||||
|
# Loop through each certificate found
|
||||||
|
foreach ($certificate in $certificates) {
|
||||||
|
if (!(Test-Path $certificate.FileName)) {
|
||||||
|
Write-Output ("$logLead : Could not locate certificate on disk at {0}" -f $certificate.FileName)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($certificate.FileName.EndsWith(".pfx")) {
|
||||||
|
# Create an X509Certificate2 Object
|
||||||
|
$x509Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
|
||||||
|
$certificate.FileName,
|
||||||
|
$certificate.Password
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($folder.ImportPrivateKey) {
|
||||||
|
Write-Output ("$logLead : Loading certificate {0} (with private key) to store {1}" -f $certificate.FileName, $folder.Store.ToString())
|
||||||
|
|
||||||
|
# Load the Certificate and Private Key from File
|
||||||
|
[Alkami.Ops.Common.Cryptography.CertificateHelper]::LoadCertificateToStore(
|
||||||
|
$certificate.FileName,
|
||||||
|
$folder.Store,
|
||||||
|
$localMachineStoreLocation,
|
||||||
|
$certificate.Password
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grant rights to the private key
|
||||||
|
$usersWhoNeedRights | ForEach-Object {
|
||||||
|
|
||||||
|
$serviceAccount = $_
|
||||||
|
|
||||||
|
Write-Output ("$logLead : Granting rights to private key for user {0}" -f $serviceAccount)
|
||||||
|
|
||||||
|
[Alkami.Ops.Common.Cryptography.CertificateHelper]::GrantRightsToPrivateKeys(
|
||||||
|
$x509Cert.Thumbprint,
|
||||||
|
$folder.Store,
|
||||||
|
$localMachineStoreLocation,
|
||||||
|
$serviceAccount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output ("$logLead : Loading certificate {0} (without private key) to store {1}" -f $certificate.FileName, $folder.Store.ToString())
|
||||||
|
|
||||||
|
# Load only the Certificate from File
|
||||||
|
[Alkami.Ops.Common.Cryptography.CertificateHelper]::LoadCertificateFromPFXToStore(
|
||||||
|
$certificate.FileName,
|
||||||
|
$folder.Store,
|
||||||
|
$localMachineStoreLocation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($certificate.FileName.EndsWith(".cer")) {
|
||||||
|
Write-Output ("$logLead : Loading certificate {0} to store {1}" -f $certificate.FileName, $folder.Store.ToString())
|
||||||
|
|
||||||
|
# Load the Certificate from File
|
||||||
|
[Alkami.Ops.Common.Cryptography.CertificateHelper]::LoadCertificateToStore(
|
||||||
|
$certificate.FileName,
|
||||||
|
$folder.Store,
|
||||||
|
$localMachineStoreLocation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output ("$logLead : Filetype for {0} does not have any logic associated with it. No action taken" -f $certificate.FileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Alias -name Load-Certificates -value Read-Certificates;
|
@ -0,0 +1,23 @@
|
|||||||
|
function Read-WebTierCertificates {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Reads Web Tier Certificates.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$baseFolder,
|
||||||
|
[Hashtable[]]$certificatesTable,
|
||||||
|
[string[]]$usersWhoNeedRights
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
|
||||||
|
[string[]]$usersWhoNeedRights = $null
|
||||||
|
$usersWhoNeedRights += "IIS_IUSRS"
|
||||||
|
|
||||||
|
Write-Verbose ("$logLead : Users who need rights read as {0}" -f ($usersWhoNeedRights -join ","))
|
||||||
|
Read-Certificates $baseFolder $certificatesTable $usersWhoNeedRights
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Alias -name Load-WebTierCertificates -value Read-WebTierCertificates;
|
157
Modules/Alkami.DevOps.Certificates/Public/Remove-Certificate.ps1
Normal file
157
Modules/Alkami.DevOps.Certificates/Public/Remove-Certificate.ps1
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
function Remove-Certificate {
|
||||||
|
<#
|
||||||
|
|
||||||
|
.SYNOPSIS
|
||||||
|
Deletes a specified Certificate
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This function helps locate a specific Certificate to delete based on where it is located and by a distinguishing characteristic.
|
||||||
|
Otherwise a Certificate object can be inputted into the function to delete as well.
|
||||||
|
For the function to work as intended, one of the three must be provided...
|
||||||
|
|
||||||
|
FriendlyName [string]
|
||||||
|
Thumbprint [string]
|
||||||
|
Certificate [object]
|
||||||
|
|
||||||
|
.PARAMETER <StoreLocation>
|
||||||
|
[string] Specifying the CurrentUser or LocalComputer when navigating Certificate locations
|
||||||
|
|
||||||
|
.PARAMETER <Store>
|
||||||
|
[string] Specifying the folder in which to search for a specific Certificate
|
||||||
|
|
||||||
|
.PARAMETER <FriendlyName>
|
||||||
|
[string] A descriptory text to distinguish different Certificates. Not always provided by the Certificate
|
||||||
|
|
||||||
|
.PARAMETER <Thumbprint>
|
||||||
|
[string] A specific code that singles out a Certificate
|
||||||
|
|
||||||
|
.PARAMETER <Certificate>
|
||||||
|
[object] This is an item (certificate) that has already been selected to run this function against
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Remove-Certificate -Store TrustedPublisher -FriendlyName 'Alkami Issued Token'
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-Item 'Cert:\LocalMachine\TrustedPublisher\E7EAC1F158CB26C5D2B061B9085D65F7CCC4ADAC' | Remove-Certificate
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding(DefaultParameterSetName = 'Certificate', SupportsShouldProcess = $true)]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory, ParameterSetName = "Certificate", ValueFromPipeline)]
|
||||||
|
[object]$Certificate,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName = "Thumbprint")]
|
||||||
|
[string]$Thumbprint,
|
||||||
|
|
||||||
|
[Parameter(ParameterSetName = "FriendlyName")]
|
||||||
|
[string]$FriendlyName,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[ValidateSet('CurrentUser','LocalMachine')]
|
||||||
|
[string]$StoreLocation = 'LocalMachine',
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[ArgumentCompleter({
|
||||||
|
$possibleValues = @('My','TrustedPeople','CA','UserDS','Root','TrustedPublisher', 'AddressBook','Remote Desktop')
|
||||||
|
return $possibleValues | ForEach-Object { $_ } # Runs through the foreach loop while the user is tabbing through the different fields
|
||||||
|
})][string]$Store,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[switch]$Force
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
|
||||||
|
# If the certificate is populated, and is not a string or is explicitly a certificate that data is used for input
|
||||||
|
if ($null -ne $Certificate ) {
|
||||||
|
$certType = $Certificate.GetType().Name
|
||||||
|
|
||||||
|
if ($certType -eq "String") {
|
||||||
|
Write-Error "$logLead : Certificate parameter was passed as an unrecognizable or unacceptable type: [$certType]. Exiting."
|
||||||
|
throw New-Object System.ArgumentException "Certificate parameter is expected to be a certificate object."
|
||||||
|
}
|
||||||
|
if ($certType -ne "X509Certificate2") {
|
||||||
|
Write-Warning "$logLead : Certificate is of type [$certType]. This is not explicitly an X509Certificate2, but it's not a string either. Proceeding."
|
||||||
|
}
|
||||||
|
|
||||||
|
$CertificatePSPath = $Certificate.PSPath
|
||||||
|
if (![string]::IsNullOrWhiteSpace($CertificatePSPath)) {
|
||||||
|
# Splitting the path to get the $Store and $StoreLocation
|
||||||
|
$pathSplits = $Certificate.PSParentPath -split '::'
|
||||||
|
if ($pathSplits[0] -eq "Microsoft.PowerShell.Security\Certificate") {
|
||||||
|
$CertLocationAndStore = $pathSplits[1] -split "\\"
|
||||||
|
# Overwriting user input with Certificate input for Store and StoreLocation
|
||||||
|
$CertStoreLocation = $CertLocationAndStore[0]
|
||||||
|
$CertStore = $CertLocationAndStore[1]
|
||||||
|
if (![string]::IsNullOrWhiteSpace($StoreLocation)) {
|
||||||
|
if ($CertStoreLocation -ne $StoreLocation) {
|
||||||
|
Write-Warning "$logLead : The Certificate's Store Location [$CertStoreLocation] does not match the inputted Store Location [$StoreLocation]"
|
||||||
|
Write-Warning "$logLead : Using the Certificate provided Store Location [$CertStoreLocation]"
|
||||||
|
$StoreLocation = $CertStoreLocation
|
||||||
|
}
|
||||||
|
if (![string]::IsNullOrWhiteSpace($Store)) {
|
||||||
|
if ($CertStore -ne $Store) {
|
||||||
|
Write-Warning "$logLead : The Certificate's Store [$CertStore] does not match the inputted Store [$Store]"
|
||||||
|
Write-Warning "$logLead : Using the Certificate provided Store [$CertStore]"
|
||||||
|
$Store = $CertStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$Thumbprint = $Certificate.Thumbprint
|
||||||
|
}
|
||||||
|
|
||||||
|
$certBasePath = (Join-Path (Join-Path "cert:\" $StoreLocation) $Store)
|
||||||
|
# Testing to see if the $Store provided is an actual directory
|
||||||
|
if (!(Test-Path $certBasePath)) {
|
||||||
|
Write-Warning "$logLead : The specified store [$Store] does not exist on the store location [$StoreLocation]"
|
||||||
|
Write-Warning "$logLead : No work to do, stopping"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$certificates = @()
|
||||||
|
|
||||||
|
if (![string]::IsNullOrWhiteSpace($Thumbprint)) {
|
||||||
|
# Finding cert based on given Thumbprint
|
||||||
|
$certificates = @(Get-ChildItem $certBasePath -Recurse | Where-Object { $_.Thumbprint -eq $Thumbprint})
|
||||||
|
if (Test-IsCollectionNullOrEmpty $certificates) {
|
||||||
|
Write-Warning "$logLead : No certificate could be found with the thumbprint [$Thumbprint] in the specified store and location [$StoreLocation\$Store]"
|
||||||
|
Write-Warning "$logLead : No work to do, stopping"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} elseif(Test-StringIsNullOrWhiteSpace -value $FriendlyName) {
|
||||||
|
$foundCertificates = @(Get-ChildItem $certBasePath -Recurse | Where-Object { $_.FriendlyName -eq $FriendlyName })
|
||||||
|
Write-Warning "$logLead : Received no thumbprint, and an empty string for FriendlyName. This has resulted in inadvertent mass certificate removal in the past."
|
||||||
|
Write-Warning "$logLead : The following is the list of certificates which would be removed, along with their thumbprints."
|
||||||
|
Write-Warning "$logLead : If you truly intended to remove these certificates, call this function with each thumbprint explicitly:"
|
||||||
|
$foundCertificates
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
# Finding cert based on given FriendlyName
|
||||||
|
$certificates = @(Get-ChildItem $certBasePath -Recurse | Where-Object { $_.FriendlyName -eq $FriendlyName })
|
||||||
|
if (Test-IsCollectionNullOrEmpty $certificates) {
|
||||||
|
Write-Warning "$logLead : No certificate could be found with the friendly name [$FriendlyName] in the specified store and location [$StoreLocation\$Store]"
|
||||||
|
Write-Warning "$logLead : No work to do, stopping"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Force -or $PSCmdlet.ShouldProcess("Do you want to delete [$($certificates.Count)] certificate(s) with thumbprint(s) [$($certificates.Thumbprint)] in store(s) [$($certificates.PSPath)]")) {
|
||||||
|
foreach ($cert in $certificates) {
|
||||||
|
# Checking to see if the Certificate has a Private Key
|
||||||
|
if ($cert.HasPrivateKey -eq $true) {
|
||||||
|
Write-Host "$logLead : Private Key Found"
|
||||||
|
# Removal of Certification and Private Key
|
||||||
|
Write-Host "$logLead : Removed $($Cert.PSPath) [$($cert.FriendlyName)] and corresponding Private Key"
|
||||||
|
Remove-Item -Path $Cert.PSPath -DeleteKey -Force
|
||||||
|
} else {
|
||||||
|
# If there is no private key, continue to delete the Certificate
|
||||||
|
Write-Host "$logLead : Private Key Not Found"
|
||||||
|
Write-Host "$logLead : Removing $($Cert.PSPath) $($cert.FriendlyName)"
|
||||||
|
Remove-Item -Path $Cert.PSPath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
function Save-CertificatesToDisk {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Saves Certificates to Disk.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[Alkami.Ops.SecretServer.Model.Certificate]$cert,
|
||||||
|
[ref]$savedCertificates,
|
||||||
|
[string]$downloadFolder
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
|
||||||
|
$rootCertFolder = Join-Path $downloadFolder "ROOT"
|
||||||
|
$personalCertFolder = Join-Path $downloadFolder "Personal"
|
||||||
|
$trustedPeopleFolder = Join-Path $downloadFolder "TrustedPeople"
|
||||||
|
|
||||||
|
if (!([System.IO.Directory]::Exists($rootCertFolder))) {
|
||||||
|
Write-Verbose ("$logLead : Creating root cert folder {0}" -f $rootCertFolder)
|
||||||
|
New-Item $rootCertFolder -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!([System.IO.Directory]::Exists($personalCertFolder))) {
|
||||||
|
Write-Verbose ("$logLead : Creating personal cert folder {0}" -f $personalCertFolder)
|
||||||
|
New-Item $personalCertFolder -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!([System.IO.Directory]::Exists($trustedPeopleFolder))) {
|
||||||
|
Write-Verbose ("$logLead : Creating trusted people folder {0}" -f $trustedPeopleFolder)
|
||||||
|
New-Item $trustedPeopleFolder -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cert.Name -like "*entrust*" -or $cert.Name -like "*identityguard*") {
|
||||||
|
# Entrust must go in to Trusted People and Root
|
||||||
|
Write-Verbose ("$logLead : Downloading Entrust certificate to {0}" -f $rootCertFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($rootCertFolder)); Password = ""; }
|
||||||
|
Write-Verbose ("$logLead : Downloading Entrust certificate to {0}" -f $trustedPeopleFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($trustedPeopleFolder)); Password = ""; }
|
||||||
|
}
|
||||||
|
elseif ($cert.Name -like "*root*") {
|
||||||
|
# If the certificate name contains "root" we will assume it's a root certificate
|
||||||
|
Write-Verbose ("$logLead : Downloading Root certificate to {0}" -f $rootCertFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($rootCertFolder)); Password = ""; }
|
||||||
|
}
|
||||||
|
elseif ($cert.FileName -match "Alkami.+(Issued|Mutual|RPSTS)") {
|
||||||
|
# Certs for Web <-> App Communication go in TrustedPeople and Personal
|
||||||
|
Write-Verbose ("$logLead : Downloading Alkami certificate {0} to {1}" -f $cert.FileName, $trustedPeopleFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($trustedPeopleFolder)); Password = $cert.Password; }
|
||||||
|
Write-Verbose ("$logLead : Downloading Alkami certificate {0} to {1}" -f $cert.FileName, $personalCertFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($personalCertFolder)); Password = $cert.Password; }
|
||||||
|
}
|
||||||
|
elseif ($cert.FileName.EndsWith(".zip")) {
|
||||||
|
# Client Certs are saved in Secret as ZIP files
|
||||||
|
# We need to unzip to Personal
|
||||||
|
Write-Verbose ("$logLead : Downloading certificate ZIP file {0} to {1}" -f $cert.FileName, $downloadFolder)
|
||||||
|
$downloadedZIP = $cert.SaveFileToDisk($downloadFolder)
|
||||||
|
|
||||||
|
$randomFolderName = [System.IO.Path]::GetRandomFileName().Split('.') | Select-Object -First 1
|
||||||
|
$unzipFolder = Join-Path $personalCertFolder $randomFolderName
|
||||||
|
|
||||||
|
if (!([System.IO.Directory]::Exists($unzipFolder))) {
|
||||||
|
Write-Verbose ("$logLead : Creating temporary unzip folder {0}" -f $unzipFolder)
|
||||||
|
New-Item $unzipFolder -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose ("$logLead : Unzipping ZIP file contents to {0}" -f $unzipFolder)
|
||||||
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($downloadedZIP, $unzipFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = (Get-ChildItem $unzipFolder -Recurse -Include *.PFX | Sort-Object -Property LastWriteTimeUtc -Descending | Select-Object -First 1 -ExpandProperty FullName); Password = $cert.Password; }
|
||||||
|
}
|
||||||
|
elseif ($cert.FileName -like "*trusted*") {
|
||||||
|
# If the filename contains "trusted" we will assume it's a trusted people certificate
|
||||||
|
Write-Verbose ("$logLead : Downloading certificate {0} to {1}" -f $cert.FileName, $trustedPeopleFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($trustedPeopleFolder)); Password = ""; }
|
||||||
|
}
|
||||||
|
elseif ($cert.FileName.EndsWith(".cer")) {
|
||||||
|
# Any other .CER files will be saved to ROOT
|
||||||
|
Write-Verbose ("$logLead : Downloading certificate {0} to {1}" -f $cert.FileName, $rootCertFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($rootCertFolder)); Password = ""; }
|
||||||
|
}
|
||||||
|
elseif ($cert.FileName.EndsWith(".pfx")) {
|
||||||
|
# All .PFX files will be saved to Personal
|
||||||
|
Write-Verbose ("$logLead : Downloading certificate with private key {0} to {1}" -f $cert.FileName, $personalCertFolder)
|
||||||
|
$savedCertificates.Value += @{FileName = ($cert.SaveFileToDisk($personalCertFolder)); Password = $cert.Password; }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output ("$logLead : Unable to determine what to do with certificate {0} with SecretID {1}" -f $cert.FileName, $cert.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
|||||||
|
function Update-CertBindings {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Updates all sites in IIS using a certificate to a new certificate if the existing certificate's thumbprint matches the value passed in.
|
||||||
|
|
||||||
|
.PARAMETER existingCertThumbprint
|
||||||
|
|
||||||
|
The existing Cert Thumprint. This must be passed in with the spaces "10 11 14 be"
|
||||||
|
|
||||||
|
.PARAMETER replacementCertThumbprint
|
||||||
|
|
||||||
|
The replacement Cert Thumprint. This must be passed in with the spaces "10 11 14 be"
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[ValidateNotNullorEmpty()]
|
||||||
|
[string]$existingCertThumbprint,
|
||||||
|
|
||||||
|
[parameter(Mandatory=$true)]
|
||||||
|
[ValidateNotNullorEmpty()]
|
||||||
|
[string]$replacementCertThumbprint
|
||||||
|
)
|
||||||
|
$existingCertByteArray = $existingCertThumbprint.Split(" ") | ForEach-Object { [CONVERT]::toint16($_,16)}
|
||||||
|
$existingCertThumbprint = $existingCertThumbprint -replace " "
|
||||||
|
$existingCert = Get-ChildItem -PATH "CERT:\\LocalMachine\My\$existingCertThumbprint" -Recurse
|
||||||
|
|
||||||
|
if (!$existingCert)
|
||||||
|
{
|
||||||
|
throw "Unable to find existing cert in the store with the thumbprint $existingCertThumbprint"
|
||||||
|
}
|
||||||
|
|
||||||
|
$replacementCertByteArray = $replacementCertThumbprint.Split(" ") | ForEach-Object { [CONVERT]::toint16($_,16)}
|
||||||
|
$replacementCertThumbprint = $replacementCertThumbprint -replace " "
|
||||||
|
$replacementCert = Get-ChildItem -PATH "CERT:\\LocalMachine\My\$replacementCertThumbprint" -Recurse
|
||||||
|
|
||||||
|
if (!$replacementCert)
|
||||||
|
{
|
||||||
|
throw "Unable to find replacement cert in the store with the thumbprint $replacementCertThumbprint"
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverManager = New-Object Microsoft.Web.Administration.ServerManager
|
||||||
|
|
||||||
|
foreach ($site in $serverManager.sites) {
|
||||||
|
|
||||||
|
$applicableBindings = $site.Bindings | Where-Object {$null -ne $_.CertificateHash}
|
||||||
|
|
||||||
|
if ($applicableBindings.Count -eq 0)
|
||||||
|
{
|
||||||
|
Write-Host ("Site {0} does not have any existing certificate bindings." -f $site.Name)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach ($binding in $applicableBindings)
|
||||||
|
{
|
||||||
|
$hash = $binding.CertificateHash
|
||||||
|
|
||||||
|
#Write-Host ("Certificate Hash for site {0} is $hash" -f $binding.CertificateHash)
|
||||||
|
|
||||||
|
if (@(Compare-Object $hash $existingCertByteArray -sync 0).Length -eq 0)
|
||||||
|
{
|
||||||
|
Write-Host ("Updating binding for site {0}" -f $site.Name)
|
||||||
|
$existingBinding = $binding
|
||||||
|
$existingBinding.CertificateHash = $replacementCertByteArray
|
||||||
|
|
||||||
|
Save-IISServerManagerChanges $serverManager
|
||||||
|
}
|
||||||
|
elseif (@(Compare-Object $hash $replacementCertByteArray -sync 0).Length -eq 0)
|
||||||
|
{
|
||||||
|
Write-Host ("The binding for site {0} is already using the new certificate" -f $site.Name)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write-Host ("The binding cert hash did not match the old or new certificate for site {0}." -f $site.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
$exportPassword = "Test"
|
||||||
|
$exportPath = "c:\temp\CertificateTest"
|
||||||
|
|
||||||
|
Remove-Item $exportPath -Force -Recurse -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
New-Item -ItemType Directory $exportPath -Force | Out-Null
|
||||||
|
|
||||||
|
Describe "Update-CertBindings" {
|
||||||
|
|
||||||
|
BeforeEach {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return @{ PsParentPath="Microsoft.PowerShell.Security\Certificate::LocalMachine\My" }} -ParameterFilter { $Path -and $Path -eq "CERT:\\LocalMachine\My\0102030405" }
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return @{ PsParentPath="Microsoft.PowerShell.Security\Certificate::LocalMachine\My" }} -ParameterFilter { $Path -and $Path -eq "CERT:\\LocalMachine\My\1011121314" }
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return $null} -ParameterFilter { $PsParentPath -and !$PsParentPath -eq "CERT:\\0102030405" }
|
||||||
|
Mock -ModuleName $moduleForMock Save-IISServerManagerChanges {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When there are bad inputs when calling Update-CertBindings" {
|
||||||
|
|
||||||
|
It "Throws Exception if all skip flags set" {
|
||||||
|
{ Update-CertBindings '' "thumbprint" } | Should Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Throws Exception if path doesn't exist" {
|
||||||
|
{ Update-CertBindings "thumbprint" '' } | Should Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When the inputs are valid and the certificates are missing" {
|
||||||
|
|
||||||
|
It "Throws Exception when existing cert not found" {
|
||||||
|
{ Update-CertBindings "99 99 99 99 99" '01 02 03 04 05' } | Should Throw "9999999999"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Throws Exception when replacement cert not found" {
|
||||||
|
{ Update-CertBindings "01 02 03 04 05" '99 99 99 99 99' } | Should Throw "9999999999"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When the inputs are valid and the certificates exist" {
|
||||||
|
|
||||||
|
It "Updates Certificate Hash with new certificate hash when the site is valid and matches existing cert" {
|
||||||
|
Mock -ModuleName $moduleForMock New-Object {
|
||||||
|
@{
|
||||||
|
|
||||||
|
Sites = @{
|
||||||
|
Name = "Test Site"
|
||||||
|
Bindings = @{
|
||||||
|
CertificateHash = "01 02 03 04 05".Split(" ") | ForEach-Object { [CONVERT]::toint16($_,16)}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} -ParameterFilter { $TypeName -and $TypeName -eq "Microsoft.Web.Administration.ServerManager"}
|
||||||
|
|
||||||
|
Update-CertBindings "01 02 03 04 05" "10 11 12 13 14"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $moduleForMock Save-IISServerManagerChanges -Times 1 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does not Update Certificate Hash when there are no sites" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock New-Object { } -ParameterFilter { $TypeName -and $TypeName -eq "Microsoft.Web.Administration.ServerManager"}
|
||||||
|
|
||||||
|
Update-CertBindings "01 02 03 04 05" "10 11 12 13 14"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $moduleForMock Save-IISServerManagerChanges -Times 0 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does not Update Certificate Hash when no sites have a certificate binding" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock New-Object {
|
||||||
|
@{
|
||||||
|
|
||||||
|
Sites = @{
|
||||||
|
Name = "Test Site"
|
||||||
|
Bindings = $null
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} -ParameterFilter { $TypeName -and $TypeName -eq "Microsoft.Web.Administration.ServerManager"}
|
||||||
|
|
||||||
|
Update-CertBindings "01 02 03 04 05" "10 11 12 13 14"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $moduleForMock Save-IISServerManagerChanges -Times 0 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does not update hash when sites hash matches new cert hash" {
|
||||||
|
Mock -ModuleName $moduleForMock New-Object {
|
||||||
|
@{
|
||||||
|
|
||||||
|
Sites = @{
|
||||||
|
Name = "Test Site"
|
||||||
|
Bindings = @{
|
||||||
|
CertificateHash = "10 11 12 13 14".Split(" ") | ForEach-Object { [CONVERT]::toint16($_,16)}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} -ParameterFilter { $TypeName -and $TypeName -eq "Microsoft.Web.Administration.ServerManager"}
|
||||||
|
|
||||||
|
Update-CertBindings "01 02 03 04 05" "10 11 12 13 14"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $moduleForMock Save-IISServerManagerChanges -Times 0 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does not update hash when sites hash does not match existing certificate" {
|
||||||
|
Mock -ModuleName $moduleForMock New-Object {
|
||||||
|
@{
|
||||||
|
|
||||||
|
Sites = @{
|
||||||
|
Name = "Test Site"
|
||||||
|
Bindings = @{
|
||||||
|
CertificateHash = "03 04 02 01".Split(" ") | ForEach-Object { [CONVERT]::toint16($_,16)}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} -ParameterFilter { $TypeName -and $TypeName -eq "Microsoft.Web.Administration.ServerManager"}
|
||||||
|
|
||||||
|
Update-CertBindings "01 02 03 04 05" "10 11 12 13 14"
|
||||||
|
|
||||||
|
Assert-MockCalled -ModuleName $moduleForMock Save-IISServerManagerChanges -Times 0 -Exactly -Scope It
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
Param()
|
||||||
|
process {
|
||||||
|
$myCurrentPath = $PSScriptRoot;
|
||||||
|
Write-Verbose "Installing the Module from $myCurrentPath";
|
||||||
|
|
||||||
|
$parentPath = (Split-Path $myCurrentPath);
|
||||||
|
$systemModulePath = "C:\Program Files\WindowsPowerShell\Modules\";
|
||||||
|
$myModulePath = (Join-Path $parentPath "module");
|
||||||
|
|
||||||
|
$metadata = ([Xml](Get-Content (Join-Path $parentPath "*.nuspec"))).package.metadata;
|
||||||
|
|
||||||
|
$id = $metadata.id;
|
||||||
|
|
||||||
|
$version = $metadata.version -replace '-pre.+','';
|
||||||
|
|
||||||
|
$targetModulePath = (Join-Path $systemModulePath $id);
|
||||||
|
$targetModuleVersionPath = (Join-Path $targetModulePath $version);
|
||||||
|
|
||||||
|
if (Test-Path $targetModulePath) {
|
||||||
|
## If the target folder already existed, remove it, because we are re-installing this package, obviously
|
||||||
|
if (Test-Path $targetModuleVersionPath) {
|
||||||
|
Write-Warning "Found an already existing module at [$targetModuleVersionPath]!!"
|
||||||
|
Remove-Item $targetModuleVersionPath -Recurse -Force;
|
||||||
|
}
|
||||||
|
|
||||||
|
## Clear previous children for name conflicts
|
||||||
|
(Get-ChildItem $targetModulePath) | ForEach-Object {
|
||||||
|
Write-Information "Removing module located at [$_]";
|
||||||
|
Remove-Item $_.FullName -Recurse -Force;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Copying module $id to [$targetModuleVersionPath]";
|
||||||
|
Copy-Item $myModulePath -Destination $targetModuleVersionPath -Recurse -Force;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
Param()
|
||||||
|
process {
|
||||||
|
$myCurrentPath = $PSScriptRoot;
|
||||||
|
Write-Verbose "Uninstalling the Module from $myCurrentPath";
|
||||||
|
|
||||||
|
$parentPath = (Split-Path $myCurrentPath);
|
||||||
|
$systemModulePath = "C:\Program Files\WindowsPowerShell\Modules\";
|
||||||
|
$myModulePath = (Join-Path $parentPath "module");
|
||||||
|
|
||||||
|
$metadata = ([Xml](Get-Content (Join-Path $parentPath "*.nuspec"))).package.metadata;
|
||||||
|
|
||||||
|
$id = $metadata.id;
|
||||||
|
|
||||||
|
$version = $metadata.version -replace '-pre.+','';
|
||||||
|
|
||||||
|
$targetModulePath = (Join-Path $systemModulePath $id);
|
||||||
|
$targetModuleVersionPath = (Join-Path $targetModulePath $version);
|
||||||
|
|
||||||
|
if (Test-Path $targetModuleVersionPath) {
|
||||||
|
Write-Information "Removing module at [$targetModuleVersionPath]!!"
|
||||||
|
Remove-Item $targetModuleVersionPath -Recurse -Force;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
33
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.nuspec
Normal file
33
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.nuspec
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-16"?>
|
||||||
|
<package>
|
||||||
|
<metadata>
|
||||||
|
<id>Alkami.DevOps.Common</id>
|
||||||
|
<version>$version$</version>
|
||||||
|
<title>Alkami Platform Modules - DevOps - Common</title>
|
||||||
|
<authors>Alkami Technologies</authors>
|
||||||
|
<owners>Alkami Technologies</owners>
|
||||||
|
<projectUrl>https://extranet.alkamitech.com/display/ORB/Alkami.DevOps.Common</projectUrl>
|
||||||
|
<iconUrl>https://www.alkami.com/files/alkamilogo75x75.png</iconUrl>
|
||||||
|
<licenseUrl>http://alkami.com/files/orblicense.html</licenseUrl>
|
||||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
<description>Installs the DevOps Common module for use with PowerShell.</description>
|
||||||
|
<releaseNotes />
|
||||||
|
<tags>PowerShell</tags>
|
||||||
|
<copyright>Copyright (c) 2018 Alkami Technologies</copyright>
|
||||||
|
<dependencies>
|
||||||
|
<dependency id="Alkami.PowerShell.Common" version="3.2.2" />
|
||||||
|
<dependency id="Alkami.PowerShell.Configuration" version="3.2.6" />
|
||||||
|
<dependency id="Alkami.Ops.SecretServer" version="3.0.2" />
|
||||||
|
</dependencies>
|
||||||
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<file src="AlkamiManifest.xml" target="AlkamiManifest.xml" />
|
||||||
|
<file src="tools\ChocolateyInstall.ps1" target="tools\ChocolateyInstall.ps1" />
|
||||||
|
<file src="tools\ChocolateyUninstall.ps1" target="tools\ChocolateyUninstall.ps1" />
|
||||||
|
<file src="Resources\**" target="Resources\" />
|
||||||
|
<!-- <file src="public\*.ps1" target="module\public" /> -->
|
||||||
|
<!-- <file src="private\*.ps1" target="module\private" /> -->
|
||||||
|
<file src="*.psd1" target="module\" />
|
||||||
|
<file src="*.psm1" target="module\" />
|
||||||
|
</files>
|
||||||
|
</package>
|
22
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.psd1
Normal file
22
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.psd1
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@{
|
||||||
|
RootModule = 'Alkami.DevOps.Common.psm1'
|
||||||
|
ModuleVersion = '4.2.1'
|
||||||
|
GUID = '5151a169-c763-489e-8213-b437b7a294b1'
|
||||||
|
Author = 'SRE,dsage,cbrand'
|
||||||
|
CompanyName = 'Alkami Technologies, Inc.'
|
||||||
|
Copyright = '(c) 2018 Alkami Technologies, Inc.. All rights reserved.'
|
||||||
|
Description = 'A set of common functions and filters not typically used directly'
|
||||||
|
FileList = @('Resources\ObjectAsTableTemplate.html')
|
||||||
|
FormatsToProcess = @('Resources\Formatters\*.ps1xml')
|
||||||
|
RequiredModules = 'Alkami.PowerShell.Common','Alkami.PowerShell.Configuration','Alkami.Ops.SecretServer','Alkami.DevOps.Certificates'
|
||||||
|
FunctionsToExport = 'Get-AlkamiAwsProfileList','Get-AlkamiServiceFabricHostnamesByTag','Get-ArmorList','Get-AutomoxAgentPath','Get-AwsCredentialConfiguration','Get-AwsStandardDynamicParameters','Get-CatalogsFromMaster','Get-DesignationTagNameByEnvironment','Get-DynamicAwsProfilesParameter','Get-DynamicAwsRegionParameter','Get-EntrustAdminUrlFromClient','Get-Environment','Get-FileContentHash','Get-HostnamesByEnvironmentName','Get-InstanceHostname','Get-InstancesByTag','Get-IPSTSUrlFromClient','Get-LocalNlbIp','Get-LogDiskUtilization','Get-PackageForInstallation','Get-PodName','Get-SecretsForPod','Get-SecurityGroupPrefix','Get-ServerStatusReport','Get-SlackAction','Get-SlackAttachment','Get-SlackAttachmentFields','Get-SlackMessage','Get-SlackMessageColor','Get-UserCredentialsFromSecretServer','Get-UTF8ContentHash','Invoke-GetAllDesignationsByEnvironmentType','Invoke-GetAllHostsByDesignation','Invoke-GetCurrentStatusByDesignation','Invoke-GetCurrentStatusByHostnames','Invoke-GetDesignationExclusionsByEnvironmentType','Invoke-MaximizeDesignation','Invoke-MinimizeDesignation','Invoke-RemoveShutdownPendingTag','Invoke-StartDesignation','Invoke-StartEnvironment','Invoke-StartServers','Invoke-StopDesignation','Invoke-StopEnvironment','Invoke-StopServers','New-AlkamiServiceFabricClusterByTag','New-WebTierMachineConfigAppSettings','Publish-MessageToSlack','Reset-ASInstanceHealth','Select-AvailableWinRmHosts','Send-ObjectAsHtmlTable','Test-IsTeamCityProcess'
|
||||||
|
AliasesToExport = 'Create-WebTierMachineConfigAppSettings'
|
||||||
|
PrivateData = @{
|
||||||
|
PSData = @{
|
||||||
|
Tags = @('powershell', 'module', 'common')
|
||||||
|
ProjectUri = 'Https://extranet.alkamitech.com/display/SRE/Alkami.DevOps.Common+Module'
|
||||||
|
IconUri = 'https://www.alkami.com/files/alkamilogo75x75.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpInfoURI = 'https://extranet.alkamitech.com/display/SRE/Alkami.DevOps.Common+Module'
|
||||||
|
}
|
114
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.pssproj
Normal file
114
Modules/Alkami.DevOps.Common/Alkami.DevOps.Common.pssproj
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{1bd8fc22-5882-4d5c-8128-81f1d61f8d77}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>MyApplication</RootNamespace>
|
||||||
|
<AssemblyName>MyApplication</AssemblyName>
|
||||||
|
<Name>Alkami.DevOps.Common</Name>
|
||||||
|
<Author>SRE</Author>
|
||||||
|
<CompanyName>Alkami Technology</CompanyName>
|
||||||
|
<Copyright>(c) 2017 Alkami Technology. All rights reserved.</Copyright>
|
||||||
|
<Description>A set of common functions and filters not typically used directly</Description>
|
||||||
|
<Guid>5151a169-c763-489e-8213-b437b7a294b1</Guid>
|
||||||
|
<Version>0.0.0.1</Version>
|
||||||
|
<PostBuildScript>Invoke-Pester;</PostBuildScript>
|
||||||
|
<PreBuildScript>..\build-project.ps1 (Join-Path $(SolutionDir) "Alkami.DevOps.Common")</PreBuildScript>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Alkami.Ops.Common\Alkami.Ops.Common.csproj">
|
||||||
|
<Name>Alkami.Ops.Common</Name>
|
||||||
|
<Project>{fa9745dd-68ac-4194-9c33-acf19411d357}</Project>
|
||||||
|
<Private>True</Private>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Public\Compare-StringToLocalMachineIdentifiers.ps1" />
|
||||||
|
<Compile Include="Public\Compare-StringToLocalMachineIdentifiers.tests.ps1" />
|
||||||
|
<Compile Include="Public\ConvertTo-SafeTeamCityMessage.ps1" />
|
||||||
|
<Compile Include="Public\ConvertTo-SafeTeamCityMessage.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-AlkamiServiceFabricHostnamesByTag.ps1" />
|
||||||
|
<Compile Include="Public\Get-ArmorList.ps1" />
|
||||||
|
<Compile Include="Public\Get-AutomoxAgentPath.ps1" />
|
||||||
|
<Compile Include="Public\Get-AutomoxAgentPath.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-DesignationTagNameByEnvironment.ps1" />
|
||||||
|
<Compile Include="Public\Get-DesignationTagNameByEnvironment.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-Environment.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-FileContentHash.ps1" />
|
||||||
|
<Compile Include="Public\Get-HostnamesByDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Get-InstancesByTag.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-LocalNlbIp.tests.ps1" />
|
||||||
|
<Compile Include="Public\Get-UserCredentialsFromSecretServer.ps1" />
|
||||||
|
<Compile Include="Public\Get-UTF8ContentHash.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-GetAllDesignationsByEnvironmentType.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-GetAllHostsByDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-GetCurrentStatusByDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-GetCurrentStatusByHostnames.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-MaximizeDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-MinimizeDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StartDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StartEnvironment.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StartServers.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StopDesignation.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StopEnvironment.ps1" />
|
||||||
|
<Compile Include="Public\Invoke-StopServers.ps1" />
|
||||||
|
<Compile Include="Public\New-AlkamiServiceFabricClusterByTag.ps1" />
|
||||||
|
<Compile Include="Public\New-WebTierMachineConfigAppSettings.ps1" />
|
||||||
|
<Compile Include="Public\Get-HostnamesByPod.ps1" />
|
||||||
|
<Compile Include="Public\Get-InstancesByTag.ps1" />
|
||||||
|
<Compile Include="Public\Get-LocalNlbIp.ps1" />
|
||||||
|
<Compile Include="Public\Get-PackageForInstallation.ps1" />
|
||||||
|
<Compile Include="Public\Optimize-FilebeatsConfig.ps1" />
|
||||||
|
<Compile Include="Public\Optimize-FilebeatsConfig.tests.ps1" />
|
||||||
|
<Compile Include="Public\Publish-MessageToSlack.ps1" />
|
||||||
|
<Compile Include="Public\Publish-MessageToSlack.tests.ps1" />
|
||||||
|
<Compile Include="Public\Reset-ASInstanceHealth.ps1" />
|
||||||
|
<Compile Include="Public\Select-AvailableWinRmHosts.ps1" />
|
||||||
|
<Compile Include="Public\Test-IsStringIPAddress.ps1" />
|
||||||
|
<Compile Include="Public\Test-IsStringIPAddress.tests.ps1" />
|
||||||
|
<Compile Include="Public\Test-IsTeamCityProcess.ps1" />
|
||||||
|
<Compile Include="Public\Test-WinRmHostsAvailable.ps1" />
|
||||||
|
<Compile Include="tools\chocolateyInstall.ps1" />
|
||||||
|
<Compile Include="tools\chocolateyUninstall.ps1" />
|
||||||
|
<Compile Include="Private\VariableDeclarations.ps1" />
|
||||||
|
<Compile Include="Public\Get-CatalogsFromMaster.ps1" />
|
||||||
|
<Compile Include="Public\Get-EntrustAdminUrlFromClient.ps1" />
|
||||||
|
<Compile Include="Public\Get-Environment.ps1" />
|
||||||
|
<Compile Include="Public\Get-IPSTSUrlFromClient.ps1" />
|
||||||
|
<Compile Include="Public\Get-SecretsForPod.ps1" />
|
||||||
|
<Compile Include="Alkami.DevOps.Common.psd1" />
|
||||||
|
<Compile Include="Public\Send-ObjectAsHtmlTable.ps1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Private\" />
|
||||||
|
<Folder Include="Public\" />
|
||||||
|
<Folder Include="Resources\" />
|
||||||
|
<Folder Include="tools\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Alkami.DevOps.Common.nuspec" />
|
||||||
|
<Content Include="Resources\ObjectAsTableTemplate.html" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Target Name="Build" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\PowerShell Tools for Visual Studio\PowerShellTools.targets" Condition="Exists('$(MSBuildExtensionsPath)\PowerShell Tools for Visual Studio\PowerShellTools.targets')" />
|
||||||
|
</Project>
|
12
Modules/Alkami.DevOps.Common/AlkamiManifest.xml
Normal file
12
Modules/Alkami.DevOps.Common/AlkamiManifest.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<packageManifest>
|
||||||
|
<version>1.0</version>
|
||||||
|
<general>
|
||||||
|
<creatorCode>Alkami</creatorCode>
|
||||||
|
<element>Alkami.DevOps.Common</element>
|
||||||
|
<componentType>SREModule</componentType>
|
||||||
|
</general>
|
||||||
|
<moduleManifest>
|
||||||
|
<status>Production</status>
|
||||||
|
</moduleManifest>
|
||||||
|
</packageManifest>
|
@ -0,0 +1,46 @@
|
|||||||
|
function ConvertTo-AwsCredentialEntry {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Used to convert AWS credentials parsed from file to a standard / friendly format
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Used to convert AWS credentials parsed from file to a standard / friendly format
|
||||||
|
|
||||||
|
.PARAMETER CredentialData
|
||||||
|
Credential data read from file by Get-AwsCredentialConfiguration
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
Get-AwsCredentialConfiguration
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
|
||||||
|
[System.Collections.ArrayList]$CredentialData
|
||||||
|
)
|
||||||
|
begin {
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
|
||||||
|
$defaultTypeName = 'AwsCredentialEntry'
|
||||||
|
$defaultKeys = @('Name')
|
||||||
|
$defaultDisplaySet = @('Name', 'role_arn', 'mfa_serial', 'region', 'source_profile')
|
||||||
|
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
|
||||||
|
$defaultKeyPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultKeyPropertySet', [string[]]$defaultKeys)
|
||||||
|
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet, $defaultKeyPropertySet)
|
||||||
|
}
|
||||||
|
process {
|
||||||
|
$entryArray = @()
|
||||||
|
|
||||||
|
foreach ($entry in $CredentialData) {
|
||||||
|
try {
|
||||||
|
$entry.PSObject.TypeNames.Insert(0, $defaultTypeName)
|
||||||
|
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers -InputObject $entry
|
||||||
|
$entryArray += $entry
|
||||||
|
} catch {
|
||||||
|
Write-Warning "$logLead : Could not convert item to AwsCredentialEntry object. Exception: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entryArray
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
# Define Tag Magic Strings
|
||||||
|
$Global:AlkamiTagKeyEnvironment = "alk:env"
|
||||||
|
$Global:AlkamiTagKeyService = "alk:service"
|
||||||
|
$Global:AlkamiTagKeyProject = "alk:project"
|
||||||
|
$Global:AlkamiTagKeyRole = "alk:role"
|
||||||
|
$Global:AlkamiTagKeyHostName = "alk:hostname"
|
||||||
|
$Global:AlkamiTagKeyBootstrap = "alk:bootstrap-phase"
|
||||||
|
$Global:AlkamiTagKeyInstanceId = "alk:instanceid"
|
||||||
|
|
||||||
|
$Global:AlkamiTagValueEnvironmentDev = "dev"
|
||||||
|
$Global:AlkamiTagValueEnvironmentQa = "qa"
|
||||||
|
$Global:AlkamiTagValueEnvironmentSandbox = "sandbox"
|
||||||
|
$Global:AlkamiTagValueEnvironmentStaging = "staging"
|
||||||
|
$Global:AlkamiTagValueEnvironmentProd = "prod"
|
||||||
|
$Global:AlkamiTagValueEnvironmentDR = "dr"
|
||||||
|
$Global:AlkamiTagValueEnvironmentLTM = "ltm"
|
||||||
|
$Global:AlkamiTagValueEnvironmentLoadtest = "loadtest"
|
||||||
|
|
||||||
|
$Global:AlkamiDesignationEnvironmentDev = "designation"
|
||||||
|
$Global:AlkamiDesignationEnvironmentQA = "designation"
|
||||||
|
$Global:AlkamiDesignationEnvironmentSandbox = "designation"
|
||||||
|
$Global:AlkamiDesignationEnvironmentStaging = "lane"
|
||||||
|
$Global:AlkamiDesignationEnvironmentProd = "pod"
|
||||||
|
$Global:AlkamiDesignationEnvironmentDR = "pod"
|
||||||
|
$Global:AlkamiDesignationEnvironmentLTM = "designation"
|
||||||
|
$Global:AlkamiDesignationEnvironmentLoadtest = "designation"
|
||||||
|
|
||||||
|
$Global:AlkamiTagValueRoleApp = "app:app"
|
||||||
|
$Global:AlkamiTagValueRoleWeb = "web"
|
||||||
|
$Global:AlkamiTagValueRoleFab = "app:fab"
|
||||||
|
$Global:AlkamiTagValueRoleMicroservice = "app:microservice"
|
||||||
|
$Global:AlkamiTagValueRoleEntrust = "app:entrust"
|
120
Modules/Alkami.DevOps.Common/Public/Get-AlkamiAwsProfileList.ps1
Normal file
120
Modules/Alkami.DevOps.Common/Public/Get-AlkamiAwsProfileList.ps1
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
function Get-AlkamiAwsProfileList {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Retrieves a list of all Alkami AWS profile names.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Retrieves a list of all Alkami AWS profile names. Will prepend 'temp-' to the profile names if not running
|
||||||
|
through a TeamCity process; this logic can be bypassed using the '-RawOutput' argument.
|
||||||
|
|
||||||
|
.PARAMETER RawOutput
|
||||||
|
[switch] Flag indicating that the profiles should not have 'temp-' prepended to them.
|
||||||
|
|
||||||
|
.PARAMETER IncludeSubsidiaries
|
||||||
|
[switch] Flag indicating that the profile names for Alkami subsidiaries should also be included.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-AlkamiAwsProfileList
|
||||||
|
|
||||||
|
temp-corp
|
||||||
|
temp-dev
|
||||||
|
temp-loadtest
|
||||||
|
temp-mgmt
|
||||||
|
temp-mp
|
||||||
|
temp-prod
|
||||||
|
temp-qa
|
||||||
|
temp-sandbox
|
||||||
|
temp-security
|
||||||
|
temp-transit
|
||||||
|
temp-transitnp
|
||||||
|
temp-workspaces
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-AlkamiAwsProfileList -RawOutput
|
||||||
|
|
||||||
|
Corp
|
||||||
|
Dev
|
||||||
|
Loadtest
|
||||||
|
Mgmt
|
||||||
|
Mp
|
||||||
|
Prod
|
||||||
|
Qa
|
||||||
|
Sandbox
|
||||||
|
Security
|
||||||
|
Transit
|
||||||
|
Transitnp
|
||||||
|
Workspaces
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-AlkamiAwsProfileList -IncludeSubsidiaries
|
||||||
|
|
||||||
|
temp-achdev
|
||||||
|
temp-corp
|
||||||
|
temp-dev
|
||||||
|
temp-loadtest
|
||||||
|
temp-mgmt
|
||||||
|
temp-mp
|
||||||
|
temp-prod
|
||||||
|
temp-qa
|
||||||
|
temp-sandbox
|
||||||
|
temp-security
|
||||||
|
temp-transit
|
||||||
|
temp-transitnp
|
||||||
|
temp-workspaces
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[OutputType([string[]])]
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch] $RawOutput,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[Alias("All")]
|
||||||
|
[switch] $IncludeSubsidiaries
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the standard list of AWS profiles for Alkami.
|
||||||
|
$awsProfileList = @(
|
||||||
|
"Corp",
|
||||||
|
"Dev",
|
||||||
|
"Loadtest",
|
||||||
|
"Mgmt",
|
||||||
|
"Mp",
|
||||||
|
"Prod",
|
||||||
|
"Qa",
|
||||||
|
"Sandbox",
|
||||||
|
"Security",
|
||||||
|
"Transit",
|
||||||
|
"Transitnp",
|
||||||
|
"Workspaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the standard list of AWS profiles for Alkami subsidiaries.
|
||||||
|
$awsProfileListSubsidiaries = @(
|
||||||
|
'AchDev'
|
||||||
|
'IgniteDev',
|
||||||
|
'IgniteMerchantsProd',
|
||||||
|
'IgniteIam',
|
||||||
|
'IgniteBanksProd',
|
||||||
|
'IgniteBanksTest',
|
||||||
|
'IgniteBanksQa'
|
||||||
|
)
|
||||||
|
|
||||||
|
$result = $awsProfileList
|
||||||
|
|
||||||
|
if ( $IncludeSubsidiaries ) {
|
||||||
|
$result += $awsProfileListSubsidiaries
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( $false -eq ( Test-IsTeamCityProcess ) ) -and ( $false -eq $RawOutput.IsPresent ) ) {
|
||||||
|
|
||||||
|
# Prepend 'temp-' to the profile name if we aren't on TeamCity and the user didn't specify
|
||||||
|
# that they wanted raw output.
|
||||||
|
$result = $result.ForEach( { "temp-" + $_.ToLower() } )
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( $result | Sort-Object )
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
|
||||||
|
Describe "Get-AlkamiAwsProfileList" {
|
||||||
|
|
||||||
|
Context "Result Respects TeamCity Process" {
|
||||||
|
|
||||||
|
It "Does Not Prepend 'temp-'" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsTeamCityProcess -ModuleName Alkami.DevOps.Common -MockWith { return $true }
|
||||||
|
|
||||||
|
(Get-AlkamiAwsProfileList) | Should -Contain "Prod"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Calls Test-IsTeamCityProcess" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsTeamCityProcess -ModuleName Alkami.DevOps.Common -MockWith { return $true }
|
||||||
|
|
||||||
|
Get-AlkamiAwsProfileList | Out-Null
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Test-IsTeamCityProcess -Times 1 -Exactly -Scope It -ModuleName Alkami.DevOps.Common
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects 'RawOutput' Flag For Non-TeamCity Processes" {
|
||||||
|
|
||||||
|
It "Prepends 'temp-' By Default" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsTeamCityProcess -ModuleName Alkami.DevOps.Common -MockWith { return $false }
|
||||||
|
|
||||||
|
( Get-AlkamiAwsProfileList ) | Should -Contain 'temp-prod'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Prepend 'temp-' When RawOutput is Specified" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsTeamCityProcess -ModuleName Alkami.DevOps.Common -MockWith { return $false }
|
||||||
|
|
||||||
|
( Get-AlkamiAwsProfileList -RawOutput ) | Should -Contain 'Prod'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects 'IncludeSubsidiaries' Flag" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsTeamCityProcess -ModuleName Alkami.DevOps.Common -MockWith { return $false }
|
||||||
|
|
||||||
|
It "Does Not Include AchDev By Default" {
|
||||||
|
|
||||||
|
( Get-AlkamiAwsProfileList ) | Should -Not -Contain 'temp-achdev'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Includes AchDev When IncludeSubsidiaries is Specified" {
|
||||||
|
|
||||||
|
( Get-AlkamiAwsProfileList -IncludeSubsidiaries ) | Should -Contain 'temp-achdev'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Includes AchDev When All is Specified" {
|
||||||
|
|
||||||
|
( Get-AlkamiAwsProfileList -All ) | Should -Contain 'temp-achdev'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
function Get-AlkamiServiceFabricHostnamesByTag {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns service fabric servers in a pod that are at the minimum bootstrap phase.
|
||||||
|
Writes an error and returns $null if the requested minimum number of servers cannot be found.
|
||||||
|
|
||||||
|
.PARAMETER designation
|
||||||
|
The pod tag designation for the cluster.
|
||||||
|
|
||||||
|
.PARAMETER minservers
|
||||||
|
The minimum number of servers to wait for in the group, before creating the cluster.
|
||||||
|
|
||||||
|
.PARAMETER role
|
||||||
|
The alk:role tag of the instance to query for, to discover peer servers.
|
||||||
|
|
||||||
|
.PARAMETER minBootstrapPhase
|
||||||
|
The minimum bootstrap phase of servers that are allowed to join the cluster. 95 (at time of writing) is the ServiceFabric bootstrap phase.
|
||||||
|
|
||||||
|
.PARAMETER timeout
|
||||||
|
The amount of time (in minutes) this command will poll for valid servers to join into the cluster.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$designation,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[int]$minServers = 5,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$role = "app:fab",
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[int]$minBootstrapPhase = 95,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[int]$timeout = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
$loglead = Get-LogLeadName;
|
||||||
|
|
||||||
|
if (($minServers -le 0) -or ($minServers -eq 2)) {
|
||||||
|
Write-Error "$loglead : Minimum number of servers in SF cluster must be 1, or 3+"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$interval = 15; # Seconds
|
||||||
|
# $timeout is in minutes.
|
||||||
|
$numRetries = [Math]::Floor(($timeout * 60) / $interval);
|
||||||
|
$retries = 0;
|
||||||
|
|
||||||
|
$designationTagName = Get-DesignationTagNameByEnvironment
|
||||||
|
Write-Host "$logLead : Designation tag for current environment is $designationTagName"
|
||||||
|
|
||||||
|
# Query for ready-to-join peer FAB servers in this pod.;
|
||||||
|
Write-Host "$loglead : Querying for FAB servers in the pod.";
|
||||||
|
Write-Host "$logLead : Query Criteria: `n alk:$designationTagName = $designation`n $Global:AlkamiTagKeyRole = $role`n State = Running"
|
||||||
|
|
||||||
|
while ($retries -lt $numRetries) {
|
||||||
|
|
||||||
|
# Look for servers from the pod at the service-fabric-joining state of initialization.
|
||||||
|
$servers = Get-InstancesByTag -tags @{ "alk:$designationTagName" = $designation; $Global:AlkamiTagKeyRole = $role }
|
||||||
|
|
||||||
|
# Filter by servers that are ready to be joined into the cluster, or are already in the cluster.
|
||||||
|
# 95 is the userdata bootscrap script phase that joins nodes into the cluster.
|
||||||
|
Write-Host "$logLead : Looking for instances with a bootstrap phase greater than $minBootstrapPhase"
|
||||||
|
$servers = $servers | Where-Object { $null -ne ($_.Tag | Where-Object { ($_.Key -eq $Global:AlkamiTagKeyBootstrap) -and ($_.Value -ge $minBootstrapPhase) }) };
|
||||||
|
|
||||||
|
# Sort them by IP so every server that gets to this step will have the same list in the same order.
|
||||||
|
$servers = $servers | Sort-Object -Property PrivateIpAddress;
|
||||||
|
|
||||||
|
# Filter out servers that aren't running.
|
||||||
|
$servers = $servers | Where-Object { $_.State.Name -eq "Running"; }
|
||||||
|
|
||||||
|
# We need at least 3 instances to join into a cluster.
|
||||||
|
if ($servers.count -ge $minServers) {
|
||||||
|
# We found the instances we needed.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "$loglead : Found $($servers.count) ready instances. Need at least $minServers servers to join into a Service Fabric cluster. Retrying in 15s";
|
||||||
|
Start-Sleep -s $interval;
|
||||||
|
$retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($retries -eq $numRetries) {
|
||||||
|
Write-Error "$loglead : Could not discover at least $minServers ready instances to join into a Service Fabric cluster.";
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$loglead : Found $minServers servers at bootstrap phase $minBootstrapPhase to create a ServiceFabric cluster with.";
|
||||||
|
|
||||||
|
# Build the fqdn's from the private IP's of the servers in the list.
|
||||||
|
$serverIps = $servers | select-object -ExpandProperty PrivateIpAddress;
|
||||||
|
$serverNames = @();
|
||||||
|
foreach ($ip in $serverIps) {
|
||||||
|
$hostname = "fab{0}.fh.local" -f $ip.Replace(".", "").Substring(2);
|
||||||
|
$serverNames += $hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$loglead : Discovered Fabric Server Peers:";
|
||||||
|
$serverNames | ForEach-Object { Write-Host $_; };
|
||||||
|
|
||||||
|
return $serverNames;
|
||||||
|
}
|
145
Modules/Alkami.DevOps.Common/Public/Get-ArmorList.ps1
Normal file
145
Modules/Alkami.DevOps.Common/Public/Get-ArmorList.ps1
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
function Get-ArmorList {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Retrieve hostnames for an environment in a usable format.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
A script that allows the user to retrieve all or some of the hosts in an environment, properly formatted for addition to an armor file, or for use in other applications.
|
||||||
|
|
||||||
|
.PARAMETER EnvironmentName
|
||||||
|
Required Parameter. The moniker associated with the environment (e.g. '12.4', 'Smith')
|
||||||
|
|
||||||
|
.PARAMETER EnvironmentType
|
||||||
|
Optional Parameter. The type of environment (e.g. 'prod', 'dr'). Defaults to the environment of the server the command is run from.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod'
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
APP16115197.fh.local,APP167765.fh.local,APP1697110.fh.local,MIC1676159.fh.local,MIC169629.fh.local,WEB16118134.fh.local,WEB1671254.fh.local,WEB1698191.fh.local
|
||||||
|
|
||||||
|
.PARAMETER Tier
|
||||||
|
Optional Parameter. Filter to a specific tier: App, Web, Mic, Fab. Defaults to include all tiers if not provided.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod' -Tier 'Web'
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
WEB16118134.fh.local,WEB1671254.fh.local,WEB1698191.fh.local
|
||||||
|
|
||||||
|
.PARAMETER Quote
|
||||||
|
Optional Parameter. Wraps each hostname in doublequotes.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod' -Tier 'App' -Quote
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
"APP16115197.fh.local","APP167765.fh.local","APP1697110.fh.local"
|
||||||
|
|
||||||
|
.PARAMETER NoDomain
|
||||||
|
Optional Parameter. Omits the domain name from the hostnames.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod' -Tier 'App' -NoDomain
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
APP16115197,APP167765,APP1697110
|
||||||
|
|
||||||
|
.PARAMETER List
|
||||||
|
Optional Parameter. Returns an array of hostnames instead of a comma-delimited string.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod' -Tier 'App' -List
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
APP16115197.fh.local
|
||||||
|
APP167765.fh.local
|
||||||
|
APP1697110.fh.local
|
||||||
|
|
||||||
|
.PARAMETER IncludeOffline
|
||||||
|
Optional Parameter. Returns both offline and online hosts.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 12.4 -EnvironmentType 'Prod' -Tier 'App' -IncludeOffline
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
APP16111223.fh.local,APP16115197.fh.local,APP16122230.fh.local,APP167765.fh.local,APP1697110.fh.local
|
||||||
|
|
||||||
|
.PARAMETER ProfileName
|
||||||
|
Optional Parameter. Specify the AWS profile to use.
|
||||||
|
|
||||||
|
.PARAMETER Region
|
||||||
|
Optional Parameter. Specify the AWS region to use.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-ArmorList -EnvironmentName 17 -EnvironmentType 'Prod' -Tier 'App' -ProfileName 'temp-prod' -Region 'us-west-2'
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment Prod
|
||||||
|
APP3210599.fh.local,APP3272118.fh.local,APP327852.fh.local
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[Alias("Pod")]
|
||||||
|
[string]$EnvironmentName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$EnvironmentType,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet("web", "app", "mic", "fab")]
|
||||||
|
[string]$Tier,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[Switch]$Quote,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[Switch]$NoDomain,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[Switch]$List,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[Switch]$IncludeOffline,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ProfileName = $null,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$Region = $null
|
||||||
|
)
|
||||||
|
|
||||||
|
[string[]] $servers = Get-HostnamesByEnvironmentName -EnvironmentName $EnvironmentName -EnvironmentType $EnvironmentType `
|
||||||
|
-ProfileName $ProfileName -Region $Region -IncludeOffline:$IncludeOffline
|
||||||
|
|
||||||
|
if ( ! $NoDomain ) {
|
||||||
|
|
||||||
|
$servers = foreach ( $server in $servers ) {
|
||||||
|
|
||||||
|
"$server.fh.local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $PSBoundParameters.ContainsKey('Tier') ) {
|
||||||
|
|
||||||
|
[string[]] $servers = Get-ServerByType -Server $servers -Type $Tier
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $Quote ) {
|
||||||
|
|
||||||
|
$servers = foreach ( $server in $servers ) {
|
||||||
|
|
||||||
|
"`"$server`""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $List ) {
|
||||||
|
|
||||||
|
$servers = $servers -join ','
|
||||||
|
}
|
||||||
|
|
||||||
|
return $servers
|
||||||
|
}
|
137
Modules/Alkami.DevOps.Common/Public/Get-ArmorList.tests.ps1
Normal file
137
Modules/Alkami.DevOps.Common/Public/Get-ArmorList.tests.ps1
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-ArmorList" {
|
||||||
|
|
||||||
|
Context "Parameter Validation" {
|
||||||
|
|
||||||
|
It "Null Environment Name Should Throw" {
|
||||||
|
|
||||||
|
{ Get-ArmorList -EnvironmentName $null } | Should Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Empty Environment Name Should Throw" {
|
||||||
|
|
||||||
|
{ Get-ArmorList -EnvironmentName '' } | Should Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Invalid Tier Should Throw" {
|
||||||
|
|
||||||
|
{ Get-ArmorList -EnvironmentName 'Test' -Tier 'Test' } | Should Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects NoDomain Parameter" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Appends Domain By Default" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' ) | Should -BeExactly 'A.fh.local,B.fh.local'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Append Domain When Switch Is Present" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain ) | Should -BeExactly 'A,B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects List Parameter" {
|
||||||
|
|
||||||
|
It "Returns a Comma-Delimited String By Default" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain ) | Should -BeExactly 'A,B'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns a String Type By Default" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain ) -is [string] | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns An Array When Switch Is Present" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List ) | Should -BeExactly @( 'A', 'B' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns An Array Type When Switch Is Present" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List ) -is [array] | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns a String Type By Default When There Is A Single Hostname" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain ) -is [string] | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns An Array Type When Switch Is Present When There Is A Single Hostname" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
# This test feels worthless, but Powershell is coercing a single element array to a string on return.
|
||||||
|
# I verified through debug prints that the variable is of type [string[]] all the way up to the return
|
||||||
|
# statement. Have fun reading https://superuser.com/a/414666
|
||||||
|
[string[]] $result = Get-ArmorList -EnvironmentName 'Test' -NoDomain -List
|
||||||
|
$result -is [array] | Should -BeTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects Quote Parameter" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'A', 'B' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Returns Unquoted Values By Default" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain ) | Should -BeExactly 'A,B'
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Quoted Values When Switch Is Present" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -Quote ) | Should -BeExactly '"A","B"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Result Respects Tier Parameter" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-HostnamesByEnvironmentName -MockWith { return @( 'WEB123', 'APP123', 'MIC123', 'FAB123' ) } -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Returns All Tiers By Default" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List ) | Should -BeExactly @( 'WEB123', 'APP123', 'MIC123', 'FAB123' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Only Web Servers When Web Tier Filtering" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List -Tier 'Web') | Should -BeExactly @( 'WEB123' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Only App Servers When App Tier Filtering" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List -Tier 'App') | Should -BeExactly @( 'APP123' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Only Mic Servers When Mic Tier Filtering" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List -Tier 'Mic') | Should -BeExactly @( 'MIC123' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Only Fab Servers When Fab Tier Filtering" {
|
||||||
|
|
||||||
|
( Get-ArmorList -EnvironmentName 'Test' -NoDomain -List -Tier 'Fab') | Should -BeExactly @( 'FAB123' )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Modules/Alkami.DevOps.Common/Public/Get-AutomoxAgentPath.ps1
Normal file
25
Modules/Alkami.DevOps.Common/Public/Get-AutomoxAgentPath.ps1
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
function Get-AutomoxAgentPath {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns the full path and filename of the Automox Agent executable
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Searches Program Files and Program Files x86 for amagent.exe. Returns the full path and filename
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
$automoxAgentDirectory = Get-ChildItem -File -Recurse -Depth 2 -Path @(${env:ProgramFiles(x86)}, $ENV:ProgramFiles) -Filter amagent.exe | Select-Object -First 1
|
||||||
|
|
||||||
|
if ($null -eq $automoxAgentDirectory) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Unable to Locate the Automox Agent Executable"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return $automoxAgentDirectory.FullName
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-AutomoxAgentPath" {
|
||||||
|
|
||||||
|
Context "Validation" {
|
||||||
|
|
||||||
|
It "Writes a Warning if No Executable Found" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return $null }
|
||||||
|
( Get-AutomoxAgentPath 3>&1 ) -match "Unable to Locate the Automox Agent Executable" | Should -Be $true
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns the FullName Property of the File When Found" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-ChildItem { return New-Object PSObject -Property @{ Name="Foo"; FullName="C:\Temp\FooBar\amagent.exe"; } }
|
||||||
|
Get-AutomoxAgentPath | Should -Be "C:\Temp\FooBar\amagent.exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
function Get-AwsCredentialConfiguration {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Used to read/parse the AWS credentials file entries to a queryable object array
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Used to read/parse the AWS credentials file entries to a queryable object array. Runs parsed values through ConvertTo-AwsCredentialEntry
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-AwsCredentialConfiguration
|
||||||
|
|
||||||
|
Name : GrandPooBah
|
||||||
|
role_arn : arn:aws:iam::123430414321:role/CLI-God-Mode-On
|
||||||
|
mfa_serial : arn:aws:iam::123410044321:mfa/poobah-cli
|
||||||
|
region : canada-west-99
|
||||||
|
source_profile : default
|
||||||
|
|
||||||
|
Name : Gibberish
|
||||||
|
role_arn : arn:aws:iam::123495924321:role/CLI-NoGood-DoNothing
|
||||||
|
mfa_serial : arn:aws:iam::123410044321:mfa/gwhiting-cli
|
||||||
|
region : mexico-north-1
|
||||||
|
source_profile : default
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
ConvertTo-AwsCredentialEntry
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([object[]])]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
|
||||||
|
$userProfileDirectory = Get-EnvironmentVariable -StoreName Process -Name USERPROFILE
|
||||||
|
|
||||||
|
$configurationSources = @()
|
||||||
|
$configurationSources += Join-Path -Path $userProfileDirectory -ChildPath ".aws/config"
|
||||||
|
$configurationSources += Join-Path -Path $userProfileDirectory -ChildPath ".aws/credentials"
|
||||||
|
# ToDo - Add the AWS SDK Credentials Source
|
||||||
|
|
||||||
|
[System.Collections.ArrayList]$objects = @()
|
||||||
|
$awsProfile = @{}
|
||||||
|
$lines = @()
|
||||||
|
$propertiesToIgnore = @("aws_secret_access_key", "aws_session_token")
|
||||||
|
|
||||||
|
foreach ($source in $configurationSources) {
|
||||||
|
|
||||||
|
if (-NOT (Test-Path -Path $source)) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Credential file does not exist at [$source]"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileContent = Get-Content -Path $source
|
||||||
|
|
||||||
|
if (Test-IsCollectionNullOrEmpty -Collection $fileContent) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Credential file at [$source] exists but is empty. Skipping."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines += $fileContent
|
||||||
|
$lines += "EOF"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-IsCollectionNullOrEmpty -Collection $lines) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Unable to locate any configured AWS credentials sources. Have you set up your local profiles?"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($line in $lines) {
|
||||||
|
|
||||||
|
if ($line.StartsWith('#') -or (Test-StringIsNullOrWhitespace -Value $line)) {
|
||||||
|
|
||||||
|
# This is a comment or empty line, ignore it
|
||||||
|
continue
|
||||||
|
|
||||||
|
} elseif ($line -eq "EOF" -and (-NOT (Test-StringIsNullOrWhitespace -Value $awsProfile.Name))) {
|
||||||
|
|
||||||
|
# Save the final profile or in between file swaps
|
||||||
|
$objects += $awsProfile
|
||||||
|
|
||||||
|
} elseif ($line.StartsWith('[')) {
|
||||||
|
|
||||||
|
# This is a new profile, push any old profile into the objects array and start fresh
|
||||||
|
if ($null -ne $awsProfile.Name -and ($null -eq ($objects | Where-Object { $_.Name -eq $awsProfile.Name }))) {
|
||||||
|
|
||||||
|
# $awsProfile object is populated, save it
|
||||||
|
$objects += $awsProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
$sanitizedProfileName = $line -replace "\[|\]|(profile\s+)", ""
|
||||||
|
Write-Verbose "$logLead : Looking for existing parsed profiles named [$sanitizedProfileName]"
|
||||||
|
$existingProfile = $objects | Where-Object { $_.Name -eq $sanitizedProfileName }
|
||||||
|
|
||||||
|
if ($null -ne $existingProfile) {
|
||||||
|
|
||||||
|
# Load the existing profile to attach properties, where unique
|
||||||
|
Write-Verbose "$logLead : Duplicate profile name [$sanitizedProfileName] found. First-in properties win in this scenario."
|
||||||
|
|
||||||
|
$awsProfile = $existingProfile
|
||||||
|
$objects.Remove($existingProfile)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
# This is a new profile
|
||||||
|
$awsProfile = New-Object PSObject -Property @{ Name = $sanitizedProfileName }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($null -eq $awsProfile.Name) {
|
||||||
|
|
||||||
|
# We skip this step unless we're anchored to a profile
|
||||||
|
# to avoid picking up trash or duplicate properties
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# The only things left are valid properties to attach to an object
|
||||||
|
# Unless we're specifically ignoring them
|
||||||
|
$splits = $line -split '='
|
||||||
|
$propertyName = $splits[0].Trim()
|
||||||
|
|
||||||
|
if ($propertiesToIgnore -contains $propertyName) {
|
||||||
|
|
||||||
|
# This is a sensitive property that will never be recorded
|
||||||
|
Write-Verbose "$logLead : Skipping excluded property [$propertyName] for profile [$($awsProfile.Name)]"
|
||||||
|
continue
|
||||||
|
|
||||||
|
} elseif ($null -ne $awsProfile.$propertyName) {
|
||||||
|
|
||||||
|
# This property has been recorded already. It is a duplicate, and first read wins
|
||||||
|
Write-Verbose ("$logLead : Skipping duplicate property [$propertyName] for profile [$($awsProfile.Name)] - existing value: [$($awsProfile.$propertyName)]")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$propertyValue = $splits[1].Trim()
|
||||||
|
Add-Member -InputObject $awsProfile -NotePropertyName $propertyName -NotePropertyValue $propertyValue -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($objects | ConvertTo-AwsCredentialEntry | Sort-Object -Property Name)
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-AwsCredentialConfiguration" {
|
||||||
|
|
||||||
|
# This is a private function, so Pester has a fit
|
||||||
|
function ConvertTo-AwsCredentialEntry { param([System.Collections.ArrayList]$CredentialData) }
|
||||||
|
|
||||||
|
Context "Logic" {
|
||||||
|
|
||||||
|
It "Reads from Both Text-Based Credentials Files" {
|
||||||
|
|
||||||
|
$expectedFileReads = @("credentials", "config")
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith {}
|
||||||
|
|
||||||
|
Get-AwsCredentialConfiguration | Should -BeNullOrEmpty
|
||||||
|
|
||||||
|
foreach ($fileRead in $expectedFileReads) {
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Get-Content -Scope It -Times 1 -Exactly -ParameterFilter { $Path -match "$fileRead$"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes a Warning and Exits Early if No Credentials Files Found" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith {}
|
||||||
|
Mock -CommandName Write-Warning -ModuleName $moduleForMock -MockWith { }
|
||||||
|
Mock -CommandName Write-Verbose -ModuleName $moduleForMock -MockWith { }
|
||||||
|
Mock -CommandName Test-Path -ModuleName $moduleForMock -MockWith { return $false }
|
||||||
|
|
||||||
|
Get-AwsCredentialConfiguration -Verbose
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Scope It -Times 1 -Exactly -ParameterFilter { $Message -match "Unable to locate any configured AWS credentials sources" }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes a Warning and Exits Early if Credentials Files Found but Empty" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @() }
|
||||||
|
Mock -CommandName Write-Warning -ModuleName $moduleForMock -MockWith { }
|
||||||
|
Mock -CommandName Write-Verbose -ModuleName $moduleForMock -MockWith { }
|
||||||
|
Mock -CommandName Test-Path -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
|
||||||
|
Get-AwsCredentialConfiguration -Verbose
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Scope It -Times 1 -Exactly -ParameterFilter { $Message -match "Unable to locate any configured AWS credentials sources" }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Parses the File Contents Appropriately" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @( "[ErMerGerd]","role_arn=some_value","source_profile=another_value","region=some_region","mfa_serial=12345") } `
|
||||||
|
-ParameterFilter { $Path -Match "credentials" }
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @( "[AllInAll]","role_arn=Its","source_profile=Just","region=Another","mfa_serial=BrickInTheWall") } `
|
||||||
|
-ParameterFilter { $Path -Match "config" }
|
||||||
|
|
||||||
|
Mock -CommandName Test-Path -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName ConvertTo-AwsCredentialEntry -ModuleName $moduleForMock -MockWith { return $_ }
|
||||||
|
|
||||||
|
$result = Get-AwsCredentialConfiguration
|
||||||
|
$result | Should -HaveCount 2
|
||||||
|
$firstResult = $result | Select -First 1
|
||||||
|
$lastResult = $result | Select -Last 1
|
||||||
|
|
||||||
|
$firstResult.Name | Should -BeExactly "AllInAll"
|
||||||
|
$firstResult.role_arn | Should -BeExactly "Its"
|
||||||
|
$firstResult.source_profile | Should -BeExactly "Just"
|
||||||
|
$firstResult.region | Should -BeExactly "Another"
|
||||||
|
$firstResult.mfa_serial | Should -BeExactly "BrickInTheWall"
|
||||||
|
|
||||||
|
$lastResult.Name | Should -BeExactly "ErMerGerd"
|
||||||
|
$lastResult.role_arn | Should -BeExactly "some_value"
|
||||||
|
$lastResult.source_profile | Should -BeExactly "another_value"
|
||||||
|
$lastResult.region | Should -BeExactly "some_region"
|
||||||
|
$lastResult.mfa_serial | Should -BeExactly "12345"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Merges Profile Properties Preferring First In When Overlap Occurs" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @( "[Shmoo]","role_arn=first_value","source_profile=first_value") } `
|
||||||
|
-ParameterFilter { $Path -Match "config" }
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @( "[Shmoo]","role_arn=second_value","region=a_value_not_in_object_one" ) } `
|
||||||
|
-ParameterFilter { $Path -Match "credentials" }
|
||||||
|
|
||||||
|
Mock -CommandName Test-Path -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName ConvertTo-AwsCredentialEntry -ModuleName $moduleForMock -MockWith { return $_ }
|
||||||
|
|
||||||
|
$result = Get-AwsCredentialConfiguration
|
||||||
|
$result | Should -HaveCount 1
|
||||||
|
$result.role_arn | Should -Be "first_value"
|
||||||
|
$result.source_profile | Should -BeExactly "first_value"
|
||||||
|
$result.region | Should -BeExactly "a_value_not_in_object_one"
|
||||||
|
$result.mfa_serial | Should -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Refuses to Return Properties Defined as Sensitive" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-Path -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName ConvertTo-AwsCredentialEntry -ModuleName $moduleForMock -MockWith { return $_ }
|
||||||
|
|
||||||
|
$propertiesToIgnore = @("aws_secret_access_key", "aws_session_token")
|
||||||
|
foreach ($property in $propertiesToIgnore) {
|
||||||
|
|
||||||
|
Mock -CommandName Get-Content -ModuleName $moduleForMock -MockWith { return @( "[OhSay]","role_arn=Can","source_profile=You","$property=See") } `
|
||||||
|
-ParameterFilter { $Path -Match "config" }
|
||||||
|
|
||||||
|
$result = Get-AwsCredentialConfiguration
|
||||||
|
$result[0].$property | Should -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
function Get-AwsStandardDynamicParameters {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all locally configured AWS Profiles and Alkami-supported Regions
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all locally configured AWS Profiles and Alkami-supported Regions as DynamicParameters in functions.
|
||||||
|
|
||||||
|
.PARAMETER RegionParameterName
|
||||||
|
The parameter name to set on the Region parameter. Defaults to Region
|
||||||
|
|
||||||
|
.PARAMETER RegionParameterSetName
|
||||||
|
The parameter set to associate the dynamic Region parameter with. Defaults to all parameter sets.
|
||||||
|
|
||||||
|
.PARAMETER RegionParameterRequired
|
||||||
|
Whether or not the Region parameter is mandatory. Defaults to false.
|
||||||
|
|
||||||
|
.PARAMETER ProfileParameterName
|
||||||
|
The parameter name to set on the Profile Name parameter. Defaults to ProfileName
|
||||||
|
|
||||||
|
.PARAMETER ProfileParameterSetName
|
||||||
|
The parameter set to associate the dynamic ProfileName parameter with. Defaults to all parameter sets.
|
||||||
|
|
||||||
|
.PARAMETER ProfileParameterRequired
|
||||||
|
Whether or not the ProfileName parameter is mandatory. Defaults to false.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-AwsStandardDynamicParameters
|
||||||
|
|
||||||
|
Key Value
|
||||||
|
--- -----
|
||||||
|
Region System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
Profile System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
Get-DynamicAwsProfilesParameter
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
Get-DynamicAwsRegionParameter
|
||||||
|
#>
|
||||||
|
|
||||||
|
[Cmdletbinding()]
|
||||||
|
[OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])]
|
||||||
|
param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$RegionParameterName = "Region",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$RegionParameterSetName = "__AllParameterSets",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$RegionParameterRequired,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$ProfileParameterName = "ProfileName",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$ProfileParameterSetName = "__AllParameterSets",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]$ProfileParameterRequired
|
||||||
|
)
|
||||||
|
|
||||||
|
$regionDynamicParams = @{
|
||||||
|
|
||||||
|
"DynamicParameterName" = $RegionParameterName;
|
||||||
|
"ParameterSetName" = $RegionParameterSetName;
|
||||||
|
"IsMandatoryParameter" = $RegionParameterRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
$regionRuntimeParameter = Get-DynamicAwsRegionParameter @regionDynamicParams
|
||||||
|
|
||||||
|
$profileDynamicParams = @{
|
||||||
|
|
||||||
|
"DynamicParameterName" = $ProfileParameterName;
|
||||||
|
"ParameterSetName" = $ProfileParameterSetName;
|
||||||
|
"IsMandatoryParameter" = $ProfileParameterRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileRuntimeParameter = Get-DynamicAwsProfilesParameter @profileDynamicParams
|
||||||
|
|
||||||
|
# The Dynamic params functions return dictionaries so they can be used independently if needed
|
||||||
|
# We will add both returned values to a new Dictionary and return it for use by the calling function
|
||||||
|
$runtimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
|
||||||
|
$runtimeParameterDictionary.Add($RegionParameterName, $($regionRuntimeParameter.Values[0]))
|
||||||
|
$runtimeParameterDictionary.Add($ProfileParameterName, $($profileRuntimeParameter.Values[0]))
|
||||||
|
|
||||||
|
return $runtimeParameterDictionary
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-AwsStandardDynamicParameters" {
|
||||||
|
|
||||||
|
Context "Logic" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-AwsCredentialConfiguration -ModuleName $moduleForMock -MockWith { return @(
|
||||||
|
(New-Object PSObject -Property @{ "Name"="FogoDeChao"; }),
|
||||||
|
(New-Object PSObject -Property @{ "Name"="12Cuts"; })
|
||||||
|
)}
|
||||||
|
|
||||||
|
Mock -CommandName Get-SupportedAwsRegions -ModuleName $moduleForMock -MockWith { return @(
|
||||||
|
"mexico-south-55",
|
||||||
|
"australia-west-99"
|
||||||
|
)}
|
||||||
|
|
||||||
|
It "Profile Uses the Specified Parameter Name" {
|
||||||
|
|
||||||
|
$testParamName = "OnlyYouCanMakeAllThisWorldSeemRight"
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -ProfileParameterName $testParamName
|
||||||
|
$profileParam = $params.Item($testParamName)
|
||||||
|
$profileParam | Should -Not -BeNullOrEmpty
|
||||||
|
$profileParam.Attributes.HelpMessage -like "*Profile*" | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Region Uses the Specified Parameter Name" {
|
||||||
|
|
||||||
|
$testParamName = "OnlyYouCanMakeTheDarknessBright"
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -RegionParameterName $testParamName
|
||||||
|
$regionParam = $params.Item($testParamName)
|
||||||
|
$regionParam | Should -Not -BeNullOrEmpty
|
||||||
|
$regionParam.Attributes.HelpMessage -like "*Region*" | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Profile Uses the Specified ParameterSet Name" {
|
||||||
|
|
||||||
|
$testParamSetName = "OnlyYouAndYouAloneCanThrillMeLikeYouDo"
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -ProfileParameterSetName $testParamSetName
|
||||||
|
$profileParam = $params.Item("ProfileName")
|
||||||
|
$profileParam | Should -Not -BeNullOrEmpty
|
||||||
|
$profileParam.Attributes.ParameterSetName | Should -BeExactly $testParamSetName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Region Uses the Specified ParameterSet Name" {
|
||||||
|
|
||||||
|
$testParamSetName = "AndFillMyHeartWithLoveForOnlyYou"
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -RegionParameterSetName $testParamSetName
|
||||||
|
$regionParam = $params.Item("Region")
|
||||||
|
$regionParam | Should -Not -BeNullOrEmpty
|
||||||
|
$regionParam.Attributes.ParameterSetName | Should -BeExactly $testParamSetName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Profile Is a Mandatory Parameter if Specified" {
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -ProfileParameterRequired
|
||||||
|
$profileParam = $params.Item("ProfileName")
|
||||||
|
$profileParam | Should -Not -BeNullOrEmpty
|
||||||
|
$profileParam.Attributes.Mandatory | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Region Is a Mandatory Parameter if Specified" {
|
||||||
|
|
||||||
|
$params = Get-AwsStandardDynamicParameters -RegionParameterRequired
|
||||||
|
$regionParam = $params.Item("Region")
|
||||||
|
$regionParam | Should -Not -BeNullOrEmpty
|
||||||
|
$regionParam.Attributes.Mandatory | Should -BeTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
function Get-CatalogsFromMaster {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a collection of tenants from the master database as a hashtable
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Position = 0, Mandatory = $false)]
|
||||||
|
[string]$ConnectionString
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([String]::IsNullOrEmpty($ConnectionString)) {
|
||||||
|
$masterConnectionString = Get-MasterConnectionString
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$masterConnectionString = $ConnectionString
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn = New-Object System.Data.SqlClient.SqlConnection
|
||||||
|
$conStrBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder($masterConnectionString)
|
||||||
|
|
||||||
|
$conn.ConnectionString = $conStrBuilder.ToString()
|
||||||
|
|
||||||
|
[hashtable[]]$databases = @()
|
||||||
|
|
||||||
|
try {
|
||||||
|
$conn.Open()
|
||||||
|
$query = New-Object System.Data.SqlClient.SqlCommand("SELECT Name, BankUrlSignatures, BankAdminUrlSignatures, DataSource, Catalog FROM dbo.Tenant", $conn)
|
||||||
|
$results = $query.ExecuteReader()
|
||||||
|
|
||||||
|
if (!$results.HasRows) {
|
||||||
|
Write-Warning (" No rows were returned from the tenant table.`nServer: {0}`nDatabase: {1}" -f $conStrBuilder.DataSource, $conStrBuilder.InitialCatalog)
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($results.Read()) {
|
||||||
|
$tenantConStringBuilder = $conStrBuilder
|
||||||
|
$tenantConStringBuilder.'Data Source' = $results.Item(3)
|
||||||
|
$tenantConStringBuilder.'Initial Catalog' = $results.Item(4)
|
||||||
|
$databases += @{Name = $results.Item(0); Signature = $results.Item(1); AdminSignature = $results.Item(2); DataSource = $results.Item(3); Catalog = $results.Item(4); ConnectionString = $tenantConStringBuilder.ToString()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $databases
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "An exception occurred while trying to pull tenants from the master database"
|
||||||
|
Write-Warning $_ | Format-List -Force
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ($conn.State -ne [System.Data.ConnectionState]::Closed) {
|
||||||
|
$conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
|||||||
|
function Get-DesignationTagNameByEnvironment {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Looks up the appropriate designation string for a given environment
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Looks up the appropriate designation string such as Pod, Lane, Box, or Designation for a given environment
|
||||||
|
Will use the current server's environment for lookup if the machine is an EC2 and no environment parameter has
|
||||||
|
been provided
|
||||||
|
|
||||||
|
.PARAMETER EnvironmentType
|
||||||
|
[String] An optional parameter to look up the string for a particular environment type. When supplied will override
|
||||||
|
the current instance value if the instance is in AWS. Is required for execution on non-EC2 machines
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment prod
|
||||||
|
pod
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "prodshared"
|
||||||
|
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment prodshared
|
||||||
|
pod
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[ValidateSet("dev", "qa", "staging", "prod", "dr", "sandbox", "devshared", "qashared", "stagingshared", "prodshared", "drshared", "sandboxshared","ltm","ltmshared")]
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$environmentType
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
$checkCurrentInstanceTags = Test-IsAws
|
||||||
|
$parameterProvided = !([String]::IsNullOrEmpty($environmentType))
|
||||||
|
|
||||||
|
if (!$checkCurrentInstanceTags -and !$parameterProvided) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Current server is not in AWS and no environment type name is provided. Execution cannot continue"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($checkCurrentInstanceTags -and !$parameterProvided) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Current host is in AWS and no Environment Type provided. Getting the current instance's $Global:AlkamiTagKeyEnvironment tag"
|
||||||
|
$environmentTagValue = Get-CurrentInstanceTags $Global:AlkamiTagKeyEnvironment -ValueOnly -ErrorAction Continue
|
||||||
|
|
||||||
|
if ([String]::IsNullOrEmpty($environmentTagValue)) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Current server is not configured with the $Global:AlkamiTagKeyEnvironment tag or it could not be retrieved. Execution cannot continue"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Using tag value $environmentTagValue for lookup"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Using parameter value $environmentType for lookup"
|
||||||
|
$environmentTagValue = $environmentType
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$logLead : Checking designation value for environment $environmentTagValue"
|
||||||
|
$lookupValue = switch ($environmentTagValue) {
|
||||||
|
|
||||||
|
"$Global:AlkamiTagValueEnvironmentProd" { $Global:AlkamiDesignationEnvironmentProd }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentProd" + "shared") { $Global:AlkamiDesignationEnvironmentProd }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentDR" { $Global:AlkamiDesignationEnvironmentDR }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentDR" + "shared") { $Global:AlkamiDesignationEnvironmentDR }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentStaging" { $Global:AlkamiDesignationEnvironmentStaging }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentStaging" + "shared") { $Global:AlkamiDesignationEnvironmentStaging }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentSandbox" { $Global:AlkamiDesignationEnvironmentSandbox }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentSandbox" + "shared") { $Global:AlkamiDesignationEnvironmentSandbox }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentDev" { $Global:AlkamiDesignationEnvironmentDev }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentDev" + "shared") { $Global:AlkamiDesignationEnvironmentDev }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentQa" { $Global:AlkamiDesignationEnvironmentQA }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentQa" + "shared") { $Global:AlkamiDesignationEnvironmentQA }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentLTM" { $Global:AlkamiDesignationEnvironmentLTM }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentLTM" + "shared") { $Global:AlkamiDesignationEnvironmentLTM }
|
||||||
|
"$Global:AlkamiTagValueEnvironmentLoadtest" { $Global:AlkamiDesignationEnvironmentLoadtest }
|
||||||
|
("$Global:AlkamiTagValueEnvironmentLoadtest" + "shared") { $Global:AlkamiDesignationEnvironmentLoadtest }
|
||||||
|
default { "Unknown" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lookupValue
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-DesignationTagNameByEnvironment" {
|
||||||
|
|
||||||
|
Context "Error and Parameter Handling" {
|
||||||
|
|
||||||
|
Mock -CommandName Write-Warning -MockWith {} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Does Not Execute if No Parameter Provided and Not in AWS" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $false }
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -BeNullOrEmpty
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "Current server is not in AWS" }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Lookup Instance Tags if a Parameter is Provided" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { }
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "prod" | Out-Null
|
||||||
|
Assert-MockCalled -CommandName Get-CurrentInstanceTags -Times 0 -Exactly -Scope It -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Pulls Current Instance Tags if in AWS And No Parameter is Supplied" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { }
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment | Out-Null
|
||||||
|
Assert-MockCalled -CommandName Get-CurrentInstanceTags -Times 1 -Exactly -Scope It -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Unknown if the Tags Have an Unexpected Value for Environment" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return @{
|
||||||
|
Key=$Global:AlkamiTagKeyEnvironment;Value="FoobarDoopityDo";
|
||||||
|
}}
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be "Unknown"
|
||||||
|
Assert-MockCalled -CommandName Get-CurrentInstanceTags -Times 1 -Exactly -Scope It -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Throws if an Unknown Value is Passed as a Parameter" {
|
||||||
|
|
||||||
|
{ Get-DesignationTagNameByEnvironment "IfSomeoneAddsThisToTheValidateSetIllFirethem" } | Should -Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Happy Path" {
|
||||||
|
|
||||||
|
It "Returns the Expected Values When Parameters are Provided" {
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "prod" | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
Get-DesignationTagNameByEnvironment "prodshared" | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "dr" | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
Get-DesignationTagNameByEnvironment "drshared" | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "dev" | Should -Be $Global:AlkamiDesignationEnvironmentDev
|
||||||
|
Get-DesignationTagNameByEnvironment "devshared" | Should -Be $Global:AlkamiDesignationEnvironmentDev
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "qa" | Should -Be $Global:AlkamiDesignationEnvironmentQA
|
||||||
|
Get-DesignationTagNameByEnvironment "qashared" | Should -Be $Global:AlkamiDesignationEnvironmentQA
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "sandbox" | Should -Be $Global:AlkamiDesignationEnvironmentSandbox
|
||||||
|
Get-DesignationTagNameByEnvironment "sandboxshared" | Should -Be $Global:AlkamiDesignationEnvironmentSandbox
|
||||||
|
|
||||||
|
Get-DesignationTagNameByEnvironment "staging" | Should -Be $Global:AlkamiDesignationEnvironmentStaging
|
||||||
|
Get-DesignationTagNameByEnvironment "stagingshared" | Should -Be $Global:AlkamiDesignationEnvironmentStaging
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns the Expected Values When Tags are Queried" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return $Global:MockedEnvironmentValue }
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "prod"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "prodshared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "dr"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "drshared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentProd
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "dev"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentDev
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "devshared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentDev
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "qa"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentQA
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "qashared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentQA
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "sandbox"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentSandbox
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "sandboxshared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentSandbox
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "staging"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentStaging
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "stagingshared"
|
||||||
|
Get-DesignationTagNameByEnvironment | Should -Be $Global:AlkamiDesignationEnvironmentStaging
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Get-CurrentInstanceTags -Times 1 -Scope It -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
function Get-DynamicAwsProfilesParameter {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all locally configured AWS Profiles
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all locally configured AWS Profiles for use as a DynamicParameter in functions. Does
|
||||||
|
not retrieve profiles for any AWSSDK configurations.
|
||||||
|
|
||||||
|
.PARAMETER DynamicParameterName
|
||||||
|
The parameter name to set on the return value. Defaults to ProfileName
|
||||||
|
|
||||||
|
.PARAMETER ParameterSetName
|
||||||
|
The parameter set to associate the dynamic parameter with. Defaults to all parameter sets.
|
||||||
|
|
||||||
|
.PARAMETER IsMandatoryParameter
|
||||||
|
Wheter or not the parameter is mandatory. Defaults to false.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-DynamicAwsProfilesParameter
|
||||||
|
|
||||||
|
Key Value
|
||||||
|
--- -----
|
||||||
|
ProfileName System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-DynamicAwsProfilesParameter -DynamicParameterName "HooDoggie"
|
||||||
|
|
||||||
|
Key Value
|
||||||
|
--- -----
|
||||||
|
HooDoggie System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
Get-AwsCredentialConfiguration
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])]
|
||||||
|
param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$DynamicParameterName = "ProfileName",
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ParameterSetName = "__AllParameterSets",
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$IsMandatoryParameter
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the Paramater Attributes
|
||||||
|
$runtimeParameterDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
|
||||||
|
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
|
||||||
|
|
||||||
|
$parameterAttribute = New-Object -Type System.Management.Automation.ParameterAttribute
|
||||||
|
$parameterAttribute.Mandatory = $IsMandatoryParameter
|
||||||
|
$parameterAttribute.ParameterSetName = $ParameterSetName
|
||||||
|
$parameterAttribute.HelpMessage = "The Local AWS Credential Profile Name to Use for Requests"
|
||||||
|
$attributeCollection.Add($parameterAttribute)
|
||||||
|
|
||||||
|
# Generate and add the ValidateSet
|
||||||
|
# Do not print warnings because this may be loaded/evaluated on servers without any profiles
|
||||||
|
$profileNames = Get-AwsCredentialConfiguration -WarningAction SilentlyContinue | Select-Object -ExpandProperty Name
|
||||||
|
|
||||||
|
# If no profiles are found, set the only available value to NoLocalProfilesFound
|
||||||
|
if (Test-IsCollectionNullOrEmpty -Collection $profileNames) {
|
||||||
|
|
||||||
|
$profileNames = @( "NoLocalProfilesFound" )
|
||||||
|
}
|
||||||
|
|
||||||
|
$validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($profileNames)
|
||||||
|
$attributeCollection.Add($validateSetAttribute)
|
||||||
|
|
||||||
|
# Create the dynamic parameter
|
||||||
|
$runtimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($DynamicParameterName, [string], $attributeCollection)
|
||||||
|
$runtimeParameterDictionary.Add($DynamicParameterName, $RuntimeParameter)
|
||||||
|
return $runtimeParameterDictionary
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-DynamicAwsProfilesParameter" {
|
||||||
|
|
||||||
|
Context "Logic" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-AwsCredentialConfiguration -ModuleName $moduleForMock -MockWith { return @(
|
||||||
|
(New-Object PSObject -Property @{ "Name"="FogoDeChao"; }),
|
||||||
|
(New-Object PSObject -Property @{ "Name"="12Cuts"; })
|
||||||
|
)}
|
||||||
|
|
||||||
|
It "Uses the Specified Parameter Name" {
|
||||||
|
|
||||||
|
$testParamName = "SupaFly"
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsProfilesParameter -DynamicParameterName $testParamName
|
||||||
|
$param.Keys | Should -BeExactly $testParamName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Uses the Specified ParameterSet Name" {
|
||||||
|
|
||||||
|
$testParamSetName = "MisterP"
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsProfilesParameter -ParameterSetName $testParamSetName
|
||||||
|
$param.Values.Attributes.ParameterSetName | Should -BeExactly $testParamSetName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Is a Mandatory Parameter if Specified" {
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsProfilesParameter -IsMandatoryParameter
|
||||||
|
$param.Values.Attributes.Mandatory | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates a Validate Set with Valid AWS Profile Names" {
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsProfilesParameter
|
||||||
|
$param.Values.Attributes.ValidValues | Should -Be @( "FogoDeChao", "12Cuts")
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns a Single Invalid Parameter if None Can be Read from Local Configuration" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-AwsCredentialConfiguration -ModuleName $moduleForMock -MockWith { return $null }
|
||||||
|
$param = Get-DynamicAwsProfilesParameter
|
||||||
|
$param.Values.Attributes.ValidValues | Should -Be @( "NoLocalProfilesFound" )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
function Get-DynamicAwsRegionParameter {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all Alkami-permitted AWS Regions
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns a RuntimeDefinedParameterDictionary with all Alkami-permitted AWS Regions for use as a DynamicParameter in functions.
|
||||||
|
|
||||||
|
.PARAMETER DynamicParameterName
|
||||||
|
The parameter name to set on the return value. Defaults to Region
|
||||||
|
|
||||||
|
.PARAMETER ParameterSetName
|
||||||
|
The parameter set to associate the dynamic parameter with. Defaults to all parameter sets.
|
||||||
|
|
||||||
|
.PARAMETER IsMandatoryParameter
|
||||||
|
Wheter or not the parameter is mandatory. Defaults to false.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-DynamicAwsRegionParameter
|
||||||
|
|
||||||
|
Key Value
|
||||||
|
--- -----
|
||||||
|
Region System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-DynamicAwsRegionParameter -DynamicParameterName "OhNoHeDidnt"
|
||||||
|
|
||||||
|
Key Value
|
||||||
|
--- -----
|
||||||
|
OhNoHeDidnt System.Management.Automation.RuntimeDefinedParameter
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
Get-SupportedAwsRegions
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])]
|
||||||
|
param(
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$DynamicParameterName = "Region",
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ParameterSetName = "__AllParameterSets",
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$IsMandatoryParameter
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define the Paramater Attributes
|
||||||
|
$runtimeParameterDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
|
||||||
|
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
|
||||||
|
|
||||||
|
$parameterAttribute = New-Object -Type System.Management.Automation.ParameterAttribute
|
||||||
|
$parameterAttribute.Mandatory = $IsMandatoryParameter
|
||||||
|
$parameterAttribute.ParameterSetName = $ParameterSetName
|
||||||
|
$parameterAttribute.HelpMessage = "The AWS Region to Use for Requests"
|
||||||
|
$attributeCollection.Add($parameterAttribute)
|
||||||
|
|
||||||
|
# Generate and add the ValidateSet
|
||||||
|
$supportedRegions = Get-SupportedAwsRegions
|
||||||
|
$validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($supportedRegions)
|
||||||
|
$attributeCollection.Add($validateSetAttribute)
|
||||||
|
|
||||||
|
# Create the dynamic parameter
|
||||||
|
$runtimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($DynamicParameterName, [string], $attributeCollection)
|
||||||
|
$runtimeParameterDictionary.Add($DynamicParameterName, $RuntimeParameter)
|
||||||
|
return $runtimeParameterDictionary
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-DynamicAwsRegionParameter" {
|
||||||
|
|
||||||
|
Context "Logic" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-SupportedAwsRegions -ModuleName $moduleForMock -MockWith { return @(
|
||||||
|
"mexico-south-55",
|
||||||
|
"australia-west-99"
|
||||||
|
)}
|
||||||
|
|
||||||
|
It "Uses the Specified Parameter Name" {
|
||||||
|
|
||||||
|
$testParamName = "SupaFly"
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsRegionParameter -DynamicParameterName $testParamName
|
||||||
|
$param.Keys | Should -BeExactly $testParamName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Uses the Specified ParameterSet Name" {
|
||||||
|
|
||||||
|
$testParamSetName = "wOne"
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsRegionParameter -ParameterSetName $testParamSetName
|
||||||
|
$param.Values.Attributes.ParameterSetName | Should -BeExactly $testParamSetName
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Is a Mandatory Parameter if Specified" {
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsRegionParameter -IsMandatoryParameter
|
||||||
|
$param.Values.Attributes.Mandatory | Should -BeTrue
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Creates a Validate Set with Valid AWS Profile Names" {
|
||||||
|
|
||||||
|
$param = Get-DynamicAwsRegionParameter
|
||||||
|
$param.Values.Attributes.ValidValues | Should -Be @( "mexico-south-55", "australia-west-99")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
function Get-EntrustAdminUrlFromClient {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns the Entrust Admin URL based on a Client Database object
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Position = 0, Mandatory = $true)]
|
||||||
|
[PSObject]$Client
|
||||||
|
)
|
||||||
|
$queryString = "SELECT s.Value FROM core.ItemSetting s JOIN core.Item i on i.ID = s.ItemID JOIN core.Provider p on p.ID = i.ParentId WHERE REPLACE(p.AssemblyInfo, ' ', '') = " +
|
||||||
|
"'Alkami.Security.Provider.User.Entrust.Provider,Alkami.Security.Provider.User.Entrust' AND s.Name = 'EntrustAdminUrl'"
|
||||||
|
|
||||||
|
$url = Invoke-QueryOnClientDatabase $client $queryString
|
||||||
|
return ($url -ireplace "Service/services/AdminServiceV\d", "/do")
|
||||||
|
}
|
||||||
|
|
84
Modules/Alkami.DevOps.Common/Public/Get-Environment.ps1
Normal file
84
Modules/Alkami.DevOps.Common/Public/Get-Environment.ps1
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
function Get-Environment {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tries to determine the Environment type (AWS, QA, Prod, Staging, etc.) using environment details
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([System.String])]
|
||||||
|
Param()
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName);
|
||||||
|
$unknown = "UNKNOWN"
|
||||||
|
|
||||||
|
# Get Environment From Tags if Possible
|
||||||
|
if (Test-IsAws) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Hosting provider is AWS -- checking tags to determine environment"
|
||||||
|
|
||||||
|
$serverEnvironment = Get-CurrentInstanceTags ($Global:AlkamiTagKeyEnvironment) -ValueOnly
|
||||||
|
|
||||||
|
if ([String]::IsNullOrEmpty($serverEnvironment)) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Could not determine environment from the $Global:AlkamiTagKeyEnvironment tag. Value returned: <$serverEnvironment>"
|
||||||
|
return $unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
$validTagValues = @(
|
||||||
|
$Global:AlkamiTagValueEnvironmentProd,
|
||||||
|
$Global:AlkamiTagValueEnvironmentStaging,
|
||||||
|
$Global:AlkamiTagValueEnvironmentQa,
|
||||||
|
$Global:AlkamiTagValueEnvironmentDr,
|
||||||
|
$Global:AlkamiTagValueEnvironmentSandbox,
|
||||||
|
$Global:AlkamiTagValueEnvironmentDev,
|
||||||
|
$Global:AlkamiTagValueEnvironmentLTM,
|
||||||
|
$Global:AlkamiTagValueEnvironmentLoadtest
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($serverEnvironment -notin $validTagValues) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Unknown tag value '$serverEnvironment' identified. What is going on with your tags?"
|
||||||
|
return $unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$logLead : Environment determined to be $serverEnvironment based on the $Global:AlkamiTagKeyEnvironment tag value"
|
||||||
|
return $serverEnvironment
|
||||||
|
}
|
||||||
|
|
||||||
|
$environmentType = Get-AppSetting "Environment.Type" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Get Environment from Features / Beacon Values if Tags Not Available. So long as it's an ORB server it should never really get past this stage
|
||||||
|
if (!([String]::IsNullOrEmpty($environmentType))) {
|
||||||
|
Write-Host "$logLead : Checking Environment.Type Value $environmentType from Machine Config Against Known List"
|
||||||
|
|
||||||
|
$environment = switch ($environmentType)
|
||||||
|
{
|
||||||
|
"Production" { $Global:AlkamiTagValueEnvironmentProd }
|
||||||
|
"Staging" { $Global:AlkamiTagValueEnvironmentStaging }
|
||||||
|
"QA" { $Global:AlkamiTagValueEnvironmentQa }
|
||||||
|
"TeamQA" { $Global:AlkamiTagValueEnvironmentQa }
|
||||||
|
"Integration" { $Global:AlkamiTagValueEnvironmentQa }
|
||||||
|
"Development" { $Global:AlkamiTagValueEnvironmentDev }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "$logLead : Environment determined to be $environment based on machine.config"
|
||||||
|
return $environment
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get Environment from Computer Name if We Absolutely Have To. Some misconfigured CORP dev/qa environments might hit this
|
||||||
|
$compName = $env:ComputerName
|
||||||
|
if ($compName -like "ALK-*") {
|
||||||
|
if ($compName -like "*QA*") {
|
||||||
|
Write-Host ("$logLead : Environment determined to be {0} based on hostname {1}" -f $Global:AlkamiTagValueEnvironmentQa, $compName)
|
||||||
|
return $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host ("$logLead : Environment determined to be {0} based on hostname {1}" -f $Global:AlkamiTagValueEnvironmentDev, $compName)
|
||||||
|
return $Global:AlkamiTagValueEnvironmentDev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Give up
|
||||||
|
Write-Warning ("$logLead : Unable to determine environment automatically")
|
||||||
|
return $unknown
|
||||||
|
}
|
||||||
|
|
113
Modules/Alkami.DevOps.Common/Public/Get-Environment.tests.ps1
Normal file
113
Modules/Alkami.DevOps.Common/Public/Get-Environment.tests.ps1
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-Environment" {
|
||||||
|
|
||||||
|
Context "Happy Path" {
|
||||||
|
|
||||||
|
It "Returns the Expected Environment Based on AWS Tags" {
|
||||||
|
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return $Global:MockedEnvironmentValue; }
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentDev
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentDev
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentSandbox
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentSandbox
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentStaging
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentStaging
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentProd
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentProd
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentDR
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentDR
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentLTM
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentLTM
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = $Global:AlkamiTagValueEnvironmentLoadtest
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentLoadtest
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns the Expected Environment Based on Machine Config Environment.Type Setting" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $false }
|
||||||
|
Mock -CommandName Get-AppSetting -ModuleName $moduleForMock -MockWith { return $Global:MockAppSettingValue; }
|
||||||
|
|
||||||
|
$Global:MockAppSettingValue = "Development"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentDev
|
||||||
|
|
||||||
|
$Global:MockAppSettingValue = "TeamQA"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
|
||||||
|
$Global:MockAppSettingValue = "QA"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
|
||||||
|
$Global:MockAppSettingValue = "Staging"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentStaging
|
||||||
|
|
||||||
|
$Global:MockAppSettingValue = "Production"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentProd
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns the Expected Environment Based on (Shudder) Naming Convention" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $false }
|
||||||
|
Mock -CommandName Get-AppSetting -ModuleName $moduleForMock -MockWith { return $null; }
|
||||||
|
|
||||||
|
$env:ComputerName = "ALK-PLA1-QA999999999999"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentQa
|
||||||
|
|
||||||
|
$env:ComputerName = "ALK-WIDGETSZ-WEB1"
|
||||||
|
Get-Environment | Should -Be $Global:AlkamiTagValueEnvironmentDev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Errors and Unknowns" {
|
||||||
|
|
||||||
|
It "Returns Unknown if the AWS Tag Value is Unexpected" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return $Global:MockedEnvironmentValue; }
|
||||||
|
|
||||||
|
$Global:MockedEnvironmentValue = "You fool! You thought you could defeat me? With my power levels at OVER 50000?"
|
||||||
|
Get-Environment | Should -Be "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Unknown if All Efforts At Finding a Value to Key Off of Fail" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $false }
|
||||||
|
Mock -CommandName Get-AppSetting -ModuleName $moduleForMock -MockWith { return $null; }
|
||||||
|
$env:COMPUTERNAME = "WEB1234567"
|
||||||
|
|
||||||
|
Get-Environment | Should -Be "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Unknown if The $Global:AlkamiTagKeyEnvironment Tag Doesn't Exist" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return $null; }
|
||||||
|
|
||||||
|
Get-Environment | Should -Be "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Unknown if The $Global:AlkamiTagKeyEnvironment Tag Is Empty" {
|
||||||
|
|
||||||
|
Mock -CommandName Test-IsAws -ModuleName $moduleForMock -MockWith { return $true }
|
||||||
|
Mock -CommandName Get-CurrentInstanceTags -ModuleName $moduleForMock -MockWith { return ""; }
|
||||||
|
|
||||||
|
Get-Environment | Should -Be "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
Modules/Alkami.DevOps.Common/Public/Get-FileContentHash.ps1
Normal file
29
Modules/Alkami.DevOps.Common/Public/Get-FileContentHash.ps1
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
function Get-FileContentHash {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Retrieves a hash generated from a file's contents.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Use this command to generate a hash for use in determining if a file's text contents have changed.
|
||||||
|
|
||||||
|
.PARAMETER FilePath
|
||||||
|
[string] The full path (including file name) to the file to be hashed. Required.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-FileContentHash "C:\Temp\File.txt"
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-FileContentHash -FilePath "C:\Temp\File.txt"
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[Alias("FilePath")]
|
||||||
|
[string]$file
|
||||||
|
)
|
||||||
|
|
||||||
|
$fileContent = [System.IO.File]::ReadAllBytes($file)
|
||||||
|
$convertedContent = [System.Text.Encoding]::GetEncoding(1252).GetString($fileContent);
|
||||||
|
|
||||||
|
return Get-UTF8ContentHash $convertedContent
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
function Get-HostnamesByEnvironmentName {
|
||||||
|
|
||||||
|
<#
|
||||||
|
|
||||||
|
.SYNOPSIS
|
||||||
|
Retrieves the hostnames for a specified environment.
|
||||||
|
|
||||||
|
.PARAMETER EnvironmentName
|
||||||
|
[string] The moniker associated with the environment (e.g. 'Smith', '16.1')
|
||||||
|
|
||||||
|
.PARAMETER EnvironmentType
|
||||||
|
[string] The type associated with the environment (e.g. 'prod', 'dr').
|
||||||
|
|
||||||
|
.PARAMETER IncludeOffline
|
||||||
|
[switch] Flag indicating whether or not to retrieve hostnames of offline instances.
|
||||||
|
|
||||||
|
.PARAMETER ProfileName
|
||||||
|
[string] AWS profile name to use in the query.
|
||||||
|
|
||||||
|
.PARAMETER Region
|
||||||
|
[string] AWS region to use in the query.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-HostnamesByEnvironmentName -EnvironmentName '16' -EnvironmentType 'DR' -Region 'us-west-2' -IncludeOffline -Verbose
|
||||||
|
|
||||||
|
VERBOSE: [Get-HostnamesByEnvironmentName] : Using environment value 'DR'
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment DR
|
||||||
|
VERBOSE: [Get-HostnamesByEnvironmentName] : Designation value resolved to 'pod'
|
||||||
|
APP32101109
|
||||||
|
APP326418
|
||||||
|
MIC3210483
|
||||||
|
MIC327179
|
||||||
|
WEB3229114
|
||||||
|
WEB323468
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-HostnamesByEnvironmentName -EnvironmentName 16 -Verbose
|
||||||
|
|
||||||
|
VERBOSE: [Get-HostnamesByEnvironmentName] : Environment type not specified; attempting to determine value.
|
||||||
|
[Get-Environment] : Environment determined to be prod based on the alk:env tag value
|
||||||
|
VERBOSE: [Get-HostnamesByEnvironmentName] : Using environment value 'prod'
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment prod
|
||||||
|
VERBOSE: [Get-HostnamesByEnvironmentName] : Designation value resolved to 'pod'
|
||||||
|
APP16107240
|
||||||
|
APP1611088
|
||||||
|
APP1612031
|
||||||
|
APP16121149
|
||||||
|
APP16122255
|
||||||
|
MIC16106199
|
||||||
|
MIC16108100
|
||||||
|
MIC16117162
|
||||||
|
MIC1612736
|
||||||
|
MIC1665169
|
||||||
|
WEB162253
|
||||||
|
WEB1623247
|
||||||
|
WEB163753
|
||||||
|
WEB1647241
|
||||||
|
WEB165798
|
||||||
|
WEB166072
|
||||||
|
#>
|
||||||
|
|
||||||
|
[OutputType([string[]])]
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter (Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$EnvironmentName,
|
||||||
|
|
||||||
|
[Parameter (Mandatory = $false)]
|
||||||
|
[string]$EnvironmentType = $null,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$IncludeOffline,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ProfileName = $null,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$Region = $null
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
if ( [string]::IsNullOrEmpty( $EnvironmentType ) ) {
|
||||||
|
Write-Verbose "$logLead : Environment type not specified; attempting to determine value."
|
||||||
|
[string] $EnvironmentType = ( Get-Environment )
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Using environment value '$EnvironmentType'"
|
||||||
|
|
||||||
|
[string] $designation = ( Get-DesignationTagNameByEnvironment $environmentType )
|
||||||
|
Write-Verbose "$logLead : Designation value resolved to '$designation'"
|
||||||
|
|
||||||
|
$designationTag = ( "alk:{0}" -f $designation )
|
||||||
|
|
||||||
|
$searchTags = @{
|
||||||
|
$designationTag = $EnvironmentName
|
||||||
|
$Global:AlkamiTagKeyEnvironment = $EnvironmentType.ToLowerInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
[Amazon.EC2.Model.Instance[]] $Instances = Get-InstancesByTag -tags $searchTags -IncludeOffline:$IncludeOffline -ProfileName $ProfileName -Region $Region
|
||||||
|
|
||||||
|
[string[]] $hostNames = @()
|
||||||
|
foreach ( $instance in $instances ) {
|
||||||
|
|
||||||
|
$hostname = Get-InstanceHostname $instance
|
||||||
|
|
||||||
|
if ( ! [string]::IsNullOrEmpty( $hostname ) ) {
|
||||||
|
$hostNames += $hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Array]::Sort($hostNames)
|
||||||
|
return $hostNames
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-HostnamesByEnvironmentName" {
|
||||||
|
|
||||||
|
# Load up AWSPowerShell for Mocking if Available
|
||||||
|
$awsPowerShellLoaded = $false
|
||||||
|
if ($null -ne (Get-Module -ListAvailable AWSPowerShell)) {
|
||||||
|
|
||||||
|
Import-AWSModule # EC2
|
||||||
|
$awsPowerShellLoaded = $true
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Write-Warning "AWSPowerShell Module *NOT* installed. Some tests will not execute."
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Parameter Validation" {
|
||||||
|
|
||||||
|
It "Null Environment Name Should Throw" {
|
||||||
|
|
||||||
|
{ Get-HostnamesByEnvironmentName -EnvironmentName $null } | Should Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Empty Environment Name Should Throw" {
|
||||||
|
|
||||||
|
{ Get-HostnamesByEnvironmentName -EnvironmentName '' } | Should Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Get-Environment Is Called When EnvironmentType Parameter Is Null" {
|
||||||
|
Mock -CommandName Get-Environment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-DesignationTagNameByEnvironment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith { return @() }
|
||||||
|
|
||||||
|
$result = ( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType $null )
|
||||||
|
|
||||||
|
It "Get-Environment Is Called One Time" {
|
||||||
|
|
||||||
|
Assert-MockCalled Get-Environment -Times 1 -Exactly -Scope Context -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Get-Environment Is Called When EnvironmentType Parameter Is Empty" {
|
||||||
|
Mock -CommandName Get-Environment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-DesignationTagNameByEnvironment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith { return @() }
|
||||||
|
|
||||||
|
$result = ( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType '' )
|
||||||
|
|
||||||
|
It "Get-Environment Is Called One Time" {
|
||||||
|
|
||||||
|
Assert-MockCalled Get-Environment -Times 1 -Exactly -Scope Context -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Get-Environment Is Not Called When EnvironmentType Parameter Is Provided" {
|
||||||
|
Mock -CommandName Get-Environment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-DesignationTagNameByEnvironment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith { return @() }
|
||||||
|
|
||||||
|
$result = ( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType 'Prod' )
|
||||||
|
|
||||||
|
It "Get-Environment Is Not Called" {
|
||||||
|
|
||||||
|
Assert-MockCalled Get-Environment -Times 0 -Exactly -Scope Context -ModuleName $moduleForMock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Returns Empty Array When No Instances Are Found" {
|
||||||
|
Mock -CommandName Get-Environment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-DesignationTagNameByEnvironment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith { return @() }
|
||||||
|
|
||||||
|
It "Is Empty" {
|
||||||
|
|
||||||
|
( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType 'Prod' ) | Should -BeExactly @()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Sorted Result Does Not Include Null Or Empty Entries" {
|
||||||
|
Mock -CommandName Get-Environment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
Mock -CommandName Get-DesignationTagNameByEnvironment -ModuleName $moduleForMock -MockWith { return 'Prod' }
|
||||||
|
|
||||||
|
It "Does Not Include Null" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith {
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
$nullInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$nullInstance.InstanceId = $null
|
||||||
|
|
||||||
|
return @( $testInstance, $nullInstance )
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstanceHostname -ModuleName $moduleForMock -MockWith {
|
||||||
|
return $Instance.InstanceId.ToUpperInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType 'Prod' ) | Should -BeExactly @( 'TEST' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Include Empty String" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith {
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
$emptyInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$emptyInstance.InstanceId = ''
|
||||||
|
|
||||||
|
return @( $testInstance, $emptyInstance )
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstanceHostname -ModuleName $moduleForMock -MockWith {
|
||||||
|
return $Instance.InstanceId.ToUpperInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType 'Prod' ) | Should -BeExactly @( 'TEST' )
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Is Sorted" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstancesByTag -ModuleName $moduleForMock -MockWith {
|
||||||
|
$bInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$bInstance.InstanceId = 'B'
|
||||||
|
|
||||||
|
$aInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$aInstance.InstanceId = 'A'
|
||||||
|
|
||||||
|
return @( $bInstance, $aInstance )
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Get-InstanceHostname -ModuleName $moduleForMock -MockWith {
|
||||||
|
return $Instance.InstanceId.ToUpperInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
( Get-HostnamesByEnvironmentName -EnvironmentName 'Test' -EnvironmentType 'Prod' ) | Should -BeExactly @( 'A', 'B' )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
function Get-IPSTSUrlFromClient {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns the IPSTS URL based on a Client Database object
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Position = 0, Mandatory = $true)]
|
||||||
|
[PSObject]$Client
|
||||||
|
)
|
||||||
|
$queryString = "SELECT s.Value FROM core.ItemSetting s JOIN core.Item i on i.ID = s.ItemID JOIN core.Provider p on p.ID = i.ParentId WHERE REPLACE(p.AssemblyInfo, ' ', '') = " +
|
||||||
|
"'Alkami.Security.Provider.TokenTranslator.Entrust,Alkami.Security.Provider.TokenTranslator.Entrust.Provider' AND s.Name = 'Issuer'"
|
||||||
|
|
||||||
|
return (Invoke-QueryOnClientDatabase $client $queryString)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
39
Modules/Alkami.DevOps.Common/Public/Get-InstanceHostname.ps1
Normal file
39
Modules/Alkami.DevOps.Common/Public/Get-InstanceHostname.ps1
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
function Get-InstanceHostname {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets the hostname for an EC2 instance.
|
||||||
|
|
||||||
|
.PARAMETER Instance
|
||||||
|
[Amazon.EC2.Model.Instance] The instance under inspection.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-InstanceHostname -Instance (Get-CurrentInstance)
|
||||||
|
|
||||||
|
APP1611088
|
||||||
|
#>
|
||||||
|
|
||||||
|
[OutputType([string])]
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNull()]
|
||||||
|
[Amazon.EC2.Model.Instance]$Instance
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
[string] $hostname = ($instance.Tags | Where-Object { $_.Key -eq $Global:AlkamiTagKeyHostName } | Select-Object -First 1).Value
|
||||||
|
|
||||||
|
if ( [string]::IsNullOrEmpty( $hostname ) ) {
|
||||||
|
|
||||||
|
Write-Warning ( "{0} : No hostname found for {1}" -f $logLead, $instance.InstanceId )
|
||||||
|
$hostname = $null
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$hostname = $hostname.ToUpperInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hostname
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-InstanceHostname" {
|
||||||
|
|
||||||
|
# Load up AWSPowerShell for mocking if available
|
||||||
|
$awsPowerShellLoaded = $false
|
||||||
|
if ($null -ne (Get-Module -ListAvailable AWSPowerShell)) {
|
||||||
|
|
||||||
|
Import-AWSModule # EC2
|
||||||
|
$awsPowerShellLoaded = $true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Write-Warning "AWSPowerShell Module *NOT* installed. Some tests will not execute."
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Parameter Validation" {
|
||||||
|
|
||||||
|
It "Null Instance Should Throw" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ Get-InstanceHostname -Instance $null } | Should Throw
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Invalid Parameter Type Should Throw" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ Get-InstanceHostname -Instance 'test' } | Should Throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Hostname Determined By Instance Tag" {
|
||||||
|
|
||||||
|
Mock -CommandName Write-Warning -MockWith {} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
It "Writes Warning When Hostname Tag Does Not Exist" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
Get-InstanceHostname -Instance $testInstance | Out-Null
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "No hostname found" }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Null When Hostname Tag Does Not Exist" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
( Get-InstanceHostname -Instance $testInstance ) | Should -BeNull
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes Warning When Hostname Tag Is Null" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, $null))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
Get-InstanceHostname -Instance $testInstance | Out-Null
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "No hostname found" }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Null When Hostname Tag Is Null" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, $null))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
( Get-InstanceHostname -Instance $testInstance ) | Should -BeNull
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes Warning When Hostname Tag Is Null" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, ''))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
Get-InstanceHostname -Instance $testInstance | Out-Null
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "No hostname found" }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Null When Hostname Tag Is Null" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, ''))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
( Get-InstanceHostname -Instance $testInstance ) | Should -BeNull
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Does Not Write Warning When Hostname Tag Is Valid" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, 'Test'))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
Get-InstanceHostname -Instance $testInstance | Out-Null
|
||||||
|
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 0 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns Uppercased Hostname Tag Value When Hostname Tag Is Valid" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testTag = (New-Object Amazon.EC2.Model.Tag($Global:AlkamiTagKeyHostName, 'Test'))
|
||||||
|
|
||||||
|
$testInstance = (New-Object Amazon.EC2.Model.Instance)
|
||||||
|
$testInstance.Tags.Add($testTag)
|
||||||
|
$testInstance.InstanceId = 'Test'
|
||||||
|
|
||||||
|
( Get-InstanceHostname -Instance $testInstance ) | Should -BeExactly 'TEST'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Modules/Alkami.DevOps.Common/Public/Get-InstancesByTag.ps1
Normal file
68
Modules/Alkami.DevOps.Common/Public/Get-InstancesByTag.ps1
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
function Get-InstancesByTag {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets EC2 Instances By Tag Filter
|
||||||
|
|
||||||
|
.PARAMETER Tags
|
||||||
|
[hashtable] Mandatory. Keys are tag names, values are the value.
|
||||||
|
|
||||||
|
.PARAMETER IncludeOffline
|
||||||
|
[switch] Include Instances not in a "running" state
|
||||||
|
|
||||||
|
.PARAMETER ProfileName
|
||||||
|
[string] Specific AWS CLI Profile to use in AWS API calls
|
||||||
|
|
||||||
|
.PARAMETER Region
|
||||||
|
[string] Specific AWS CLI Region to use in AWS API calls
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter (Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[hashtable]$Tags,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$IncludeOffline,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ProfileName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$Region
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
Import-AWSModule # EC2
|
||||||
|
|
||||||
|
$splatParams = @{}
|
||||||
|
|
||||||
|
$filters = @()
|
||||||
|
foreach ($key in $tags.Keys) {
|
||||||
|
$filter = (New-Object Amazon.EC2.Model.Filter);
|
||||||
|
$filter.Name = "tag:$key";
|
||||||
|
$filter.Value = $tags.$key;
|
||||||
|
$filters += $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($includeOffline.IsPresent)) {
|
||||||
|
$onlineFilter = (New-Object Amazon.EC2.Model.Filter)
|
||||||
|
$onlineFilter.Name = "instance-state-name"
|
||||||
|
$onlineFilter.Value = "running"
|
||||||
|
$filters += $onlineFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!([string]::IsNullOrEmpty($ProfileName))) {
|
||||||
|
$splatParams["ProfileName"] = "$ProfileName"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!([string]::IsNullOrEmpty($Region))) {
|
||||||
|
$splatParams["Region"] = "$Region"
|
||||||
|
}
|
||||||
|
|
||||||
|
$instances = Get-EC2Instance -Filter $filters @splatParams
|
||||||
|
|
||||||
|
Write-Verbose ("$logLead : Found {0} Instances with Filter" -f $instances.Count)
|
||||||
|
|
||||||
|
return $instances.RunningInstance
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
Describe "Get-InstancesByTag" {
|
||||||
|
|
||||||
|
# Load up AWSPowerShell for Mocking if Available
|
||||||
|
$awsPowerShellLoaded = $false
|
||||||
|
if ($null -ne (Get-Module -ListAvailable AWSPowerShell)) {
|
||||||
|
|
||||||
|
Import-AWSModule # EC2
|
||||||
|
$awsPowerShellLoaded = $true
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Write-Warning "AWSPowerShell Module *NOT* installed. Some tests will not execute."
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Parameter and Environment Validation" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2Instance { return @("I'm not Null!") }
|
||||||
|
|
||||||
|
It "Excludes Offline Instances by Default" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-InstancesByTag -tags @{"alk:pod" = "fakepod" }
|
||||||
|
Assert-MockCalled Get-EC2Instance -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { ($null -ne ($Filter | Where-Object { $_.Name -eq "instance-state-name" })) }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Includes Offline Instances When Specified" {
|
||||||
|
|
||||||
|
if (!($awsPowerShellLoaded)) {
|
||||||
|
|
||||||
|
Set-ItResult -Inconclusive -Because "AWSPowerShell Not Installed"
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# This test is less than ideal, since all we can realistically test is that the if statement which controls adding this filter or not
|
||||||
|
# is hit
|
||||||
|
Get-InstancesByTag -tags @{"alk:pod" = "fakepod" } -IncludeOffline
|
||||||
|
Assert-MockCalled Get-EC2Instance -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { ($null -eq ($Filter | Where-Object { $_.Name -eq "instance-state-name" })) }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Correctly splats Region" {
|
||||||
|
Get-InstancesByTag -Tags @{"alk:pod" = "fakepod"} -Region "us-fake-1"
|
||||||
|
Assert-MockCalled Get-EC2Instance -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter {$Region -eq "us-fake-1"}
|
||||||
|
|
||||||
|
}
|
||||||
|
It "Correctly splats Profile" {
|
||||||
|
Get-InstancesByTag -Tags @{"alk:pod" = "fakepod"} -ProfileName "fake-profile"
|
||||||
|
Assert-MockCalled Get-EC2Instance -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter {$ProfileName -eq "fake-profile"}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
Modules/Alkami.DevOps.Common/Public/Get-LocalNlbIp.ps1
Normal file
91
Modules/Alkami.DevOps.Common/Public/Get-LocalNlbIp.ps1
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
function Get-LocalNlbIp {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets the Ip for the NLB NIC which is in the same AZ as the server from which it's run.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Gets the Ip for the NLB NIC which is in the same AZ as the server from which it's run. Uses the current availability zone, ENI description, and interfacetype to determine the appropriate IP
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Get-LocalNlbIp -verbose
|
||||||
|
|
||||||
|
VERBOSE: [Get-LocalNlbIp] : Current Instance AZ Read as us-east-1b
|
||||||
|
VERBOSE: [Get-LocalNlbIp] : Environment Read as qa
|
||||||
|
[Get-DesignationTagNameByEnvironment] : Checking designation value for environment qa
|
||||||
|
VERBOSE: [Get-LocalNlbIp] : Read designation tag value Smith
|
||||||
|
VERBOSE: [Get-LocalNlbIp] : Using Expected NLB Name ELB net/Smith-qa-nlb for Filtering
|
||||||
|
VERBOSE: Invoking Amazon Elastic Compute Cloud operation 'DescribeNetworkInterfaces' in region 'us-east-1'
|
||||||
|
Returning IP Address for ENI with Description: ELB net/smith-qa-nlb/93947386b64a5aac, Id: eni-0718dc98cdcec5e18
|
||||||
|
10.26.91.212
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
Import-AWSModule # EC2
|
||||||
|
|
||||||
|
if (!(Test-IsAws))
|
||||||
|
{
|
||||||
|
Write-Warning "$logLead : This function can only be executed on an AWS server"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the current instance and AZ
|
||||||
|
$currentInstance = Get-CurrentInstance;
|
||||||
|
$currentAz = $currentInstance.Placement.AvailabilityZone;
|
||||||
|
Write-Verbose "$logLead : Current Instance AZ Read as $currentAz"
|
||||||
|
|
||||||
|
# Check the current server's role
|
||||||
|
$serverRole = $currentInstance.Tag | Where-Object {$_.Key -eq $Global:AlkamiTagKeyRole}
|
||||||
|
if ($serverRole.Value -eq 'app:app')
|
||||||
|
{
|
||||||
|
# App servers should use 127.0.0.1
|
||||||
|
Write-Warning "This is currently running on an app server. The IP returned shouldn't be used in the host file."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the expected designation tag name
|
||||||
|
$environment = $currentInstance.Tag | Where-Object { $_.Key -eq $Global:AlkamiTagKeyEnvironment; };
|
||||||
|
Write-Verbose "$logLead : Environment Read as $($environment.Value)"
|
||||||
|
|
||||||
|
$targetTag = Get-DesignationTagNameByEnvironment $environment.Value
|
||||||
|
|
||||||
|
if ($null -ne $targetTag) {
|
||||||
|
|
||||||
|
# Pull the Designation Tag Value
|
||||||
|
$environmentTagValue = $currentInstance.Tag | Where-Object {$_.Key -eq "alk:$targetTag" }
|
||||||
|
Write-Verbose "$logLead : Read designation tag value $($environmentTagValue.Value)"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : Unable to pull $Global:AlkamiTagKeyEnvironment for the current instance. Execution cannot continue."
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cleanedName = $environmentTagValue.Value.replace('.','-');
|
||||||
|
$nlbName = "ELB net/" + $cleanedName + '-' + $environment.Value + '-nlb';
|
||||||
|
Write-Verbose "$logLead : Using Expected NLB Name $nlbName for Filtering"
|
||||||
|
|
||||||
|
$nlbNics = Get-EC2NetworkInterface -Filter @( @{name='availability-zone';values=$currentAz} );
|
||||||
|
[array]$filteredNics = $nlbNics | Where-Object { $_.InterfaceType -eq 'network_load_balancer' -and $_.Description -match $nlbName}
|
||||||
|
$matchCount = $filteredNics.Count
|
||||||
|
Write-Verbose "$logLead : Found $matchCount Matching ENIs with InterfaceType: network_load_balancer, Description: $nlbName, Availability Zone $currentAz"
|
||||||
|
|
||||||
|
if ($null -ne $filteredNics -and $filteredNics.Count -eq 1) {
|
||||||
|
|
||||||
|
$nic = $filteredNics | Select-Object -First 1
|
||||||
|
Write-Host ("Returning IP Address for ENI with Description: {0}, Id: {1}" -f $nic.Description, $nic.NetworkInterfaceId)
|
||||||
|
return (($nic | Select-Object -First 1).PrivateIpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -eq $filteredNics) {
|
||||||
|
|
||||||
|
Write-Warning "$logLead : No ENIs found with Description $nlbName for AZ $currentAz"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning ("$logLead : {0} ENIs found with Description $nlbName for AZ $currentAz. Execution cannot continue." -f $filteredNics.Count)
|
||||||
|
return $null
|
||||||
|
}
|
329
Modules/Alkami.DevOps.Common/Public/Get-LocalNlbIp.tests.ps1
Normal file
329
Modules/Alkami.DevOps.Common/Public/Get-LocalNlbIp.tests.ps1
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
. $PSScriptRoot\..\..\Load-PesterModules.ps1
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.tests\.', '.'
|
||||||
|
$functionPath = Join-Path -Path $here -ChildPath $sut
|
||||||
|
Write-Host "Overriding SUT: $functionPath"
|
||||||
|
Import-Module $functionPath -Force
|
||||||
|
$moduleForMock = ""
|
||||||
|
|
||||||
|
#region Get-LocalNlbIp
|
||||||
|
|
||||||
|
# Handle the AWS Native Functions When AWSPowerShell Not Available
|
||||||
|
if ($null -eq (Get-Command "Get-EC2NetworkInterface" -ErrorAction SilentlyContinue))
|
||||||
|
{
|
||||||
|
function Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
throw "This Should Never Be Actually Called"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Get-LocalNlbIp" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Test-IsAws { return $true }
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface { return $null }
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = $Global:AlkamiTagValueEnvironmentProd},
|
||||||
|
@{Key = "alk:pod"; Value = "6"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When Environment Names Overlap" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
# args[1] is an ec2 filter based on the AvailabilityZone from the current instance
|
||||||
|
# https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TFilter.html
|
||||||
|
$ec2Filter = $args[1]
|
||||||
|
|
||||||
|
Write-Warning "Using EC2 Filter Value: $($ec2Filter.Values)"
|
||||||
|
|
||||||
|
$possibleReturns = @(
|
||||||
|
|
||||||
|
@{ "Description"="ELB net/12.6-prod-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="8.8.8.8"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/6-prod-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="100.100.100.100"; InterfaceType="network_load_balancer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return $possibleReturns | Where-Object {$_.AvailabilityZone -like $ec2Filter.Values}
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Returns the Correct IP When Searching for POD 6 and 12.6 is Available" {
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -Be "100.100.100.100"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "When the Environment is an App Server" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleApp},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = $Global:AlkamiTagValueEnvironmentProd},
|
||||||
|
@{Key = "alk:pod"; Value = "6"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes a Warning" {
|
||||||
|
|
||||||
|
( Get-LocalNlbIp 3>&1 ) -match "This is currently running on an app server." | Should -Be $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Environment Tag Switch" {
|
||||||
|
|
||||||
|
$validEnvironments = @("Prod", "DR")
|
||||||
|
|
||||||
|
foreach ($Global:podBasedEnvironment in $validEnvironments) {
|
||||||
|
|
||||||
|
It "Uses the alk:pod tag when the environment is $podBasedEnvironment" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = "$podBasedEnvironment"},
|
||||||
|
@{Key = "alk:pod"; Value = "Pass"},
|
||||||
|
@{Key = "alk:lane"; Value = "Fail"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
# args[1] is an ec2 filter based on the NLB name constructed from the current instance
|
||||||
|
# https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TFilter.html
|
||||||
|
$ec2Filter = $args[1]
|
||||||
|
|
||||||
|
Write-Warning "Using EC2 Filter Value: $($ec2Filter.Values)"
|
||||||
|
|
||||||
|
$possibleReturns = @(
|
||||||
|
|
||||||
|
@{ "Description"="ELB net/Pass-$podBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="100.100.100.100"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/Fail-$podBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="9.9.9.9"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/Fail-staging-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="8.8.8.8"; InterfaceType="network_load_balancer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return $possibleReturns | Where-Object {$_.AvailabilityZone -like $ec2Filter.Values}
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -Be 100.100.100.100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Uses the alk:lane tag when the environment is Staging" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = $Global:AlkamiTagValueEnvironmentStaging},
|
||||||
|
@{Key = "alk:pod"; Value = "Fail"},
|
||||||
|
@{Key = "alk:lane"; Value = "Pass"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
# args[1] is an ec2 filter based on the NLB name constructed from the current instance
|
||||||
|
# https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TFilter.html
|
||||||
|
$ec2Filter = $args[1]
|
||||||
|
|
||||||
|
Write-Warning "Using EC2 Filter Value: $($ec2Filter.Values)"
|
||||||
|
|
||||||
|
$possibleReturns = @(
|
||||||
|
|
||||||
|
@{ "Description"="ELB net/Pass-staging-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="100.100.100.100"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/Fail-staging-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="9.9.9.9"; InterfaceType="network_load_balancer"}
|
||||||
|
@{ "Description"="ELB net/Fail-prod-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="8.8.8.8"; InterfaceType="network_load_balancer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return $possibleReturns | Where-Object {$_.AvailabilityZone -like $ec2Filter.Values}
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -Be 100.100.100.100
|
||||||
|
}
|
||||||
|
|
||||||
|
$validEnvironments = @("Dev", "QA")
|
||||||
|
|
||||||
|
foreach ($Global:designationBasedEnvironment in $validEnvironments) {
|
||||||
|
It "Uses the alk:designation tag when the environment is $designationBasedEnvironment" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = "$designationBasedEnvironment"},
|
||||||
|
@{Key = "alk:pod"; Value = "Fail"},
|
||||||
|
@{Key = "alk:lane"; Value = "Fail"},
|
||||||
|
@{Key = "alk:designation"; Value = "Pass"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
# args[1] is an ec2 filter based on the NLB name constructed from the current instance
|
||||||
|
# https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TFilter.html
|
||||||
|
$ec2Filter = $args[1]
|
||||||
|
|
||||||
|
Write-Warning "Using EC2 Filter Value: $($ec2Filter.Values)"
|
||||||
|
|
||||||
|
$possibleReturns = @(
|
||||||
|
@{ "Description"="ELB net/Pass-$designationBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="100.100.100.100"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/Fail-$designationBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="9.9.9.9"; InterfaceType="network_load_balancer"}
|
||||||
|
@{ "Description"="ELB net/Fail-prod-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="8.8.8.8"; InterfaceType="network_load_balancer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return $possibleReturns | Where-Object {$_.AvailabilityZone -like $ec2Filter.Values}
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -Be 100.100.100.100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Error Scenarios" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-EC2NetworkInterface {
|
||||||
|
|
||||||
|
# args[1] is an ec2 filter based on the NLB name constructed from the current instance
|
||||||
|
# https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TFilter.html
|
||||||
|
$ec2Filter = $args[1]
|
||||||
|
|
||||||
|
Write-Warning "Using EC2 Filter Value: $($ec2Filter.Values)"
|
||||||
|
|
||||||
|
$possibleReturns = @(
|
||||||
|
|
||||||
|
@{ "Description"="ELB net/Pass-$podBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="100.100.100.100"; InterfaceType="network_load_balancer"},
|
||||||
|
@{ "Description"="ELB net/Pass-$podBasedEnvironment-nlb-something"; Id="Test"; "AvailabilityZone"="us-east-1a"; "PrivateIpAddress"="111.111.111.111"; InterfaceType="network_load_balancer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return $possibleReturns | Where-Object {$_.AvailabilityZone -like $ec2Filter.Values}
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes a Warning and Returns Null When No Match Found" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-999";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = "$podBasedEnvironment"},
|
||||||
|
@{Key = "alk:pod"; Value = "Pass"},
|
||||||
|
@{Key = "alk:lane"; Value = "Fail"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Write-Warning -MockWith {} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -BeNullOrEmpty
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "No ENIs found with Description" }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Writes a Warning and Returns Null When More Than One Match Found" {
|
||||||
|
|
||||||
|
Mock -ModuleName $moduleForMock Get-CurrentInstance {
|
||||||
|
|
||||||
|
$placementMock = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
"AvailabilityZone" = "us-east-1a";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pod6Instance = New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Placement = $placementMock;
|
||||||
|
|
||||||
|
Tag = @(
|
||||||
|
@{Key = $Global:AlkamiTagKeyRole; Value = $Global:AlkamiTagValueRoleWeb},
|
||||||
|
@{Key = $Global:AlkamiTagKeyEnvironment; Value = "$podBasedEnvironment"},
|
||||||
|
@{Key = "alk:pod"; Value = "Pass"},
|
||||||
|
@{Key = "alk:lane"; Value = "Fail"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pod6Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
Mock -CommandName Write-Warning -MockWith {} -ModuleName $moduleForMock
|
||||||
|
|
||||||
|
Get-LocalNlbIp | Should -BeNullOrEmpty
|
||||||
|
Assert-MockCalled -CommandName Write-Warning -Times 1 -Exactly -Scope It `
|
||||||
|
-ModuleName $moduleForMock -ParameterFilter { $Message -match "2 ENIs found with Description" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Get-LocalNlbIp
|
109
Modules/Alkami.DevOps.Common/Public/Get-LogDiskUtilization.ps1
Normal file
109
Modules/Alkami.DevOps.Common/Public/Get-LogDiskUtilization.ps1
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
function Get-LogDiskUtilization {
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets disk utilization from ORB Logs
|
||||||
|
|
||||||
|
.PARAMETER ComputerName
|
||||||
|
The single remote computer name to execute against. If not supplied, runs on the local system
|
||||||
|
#>
|
||||||
|
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ComputerName = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = Get-LogLeadName
|
||||||
|
$isLocalExecution = $false
|
||||||
|
|
||||||
|
if (( [String]::IsNullOrEmpty($ComputerName) ) -or (Compare-StringToLocalMachineIdentifiers -stringToCheck $ComputerName )) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Setting local execution to true"
|
||||||
|
$ComputerName = $ENV:ComputerName
|
||||||
|
$isLocalExecution = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$logPath = Get-ORBLogsPath
|
||||||
|
if ($isLocalExecution) {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Searching for log files locally"
|
||||||
|
$logFiles = Get-ChildItem -Path $logPath -Filter *.log* -File
|
||||||
|
} else {
|
||||||
|
|
||||||
|
Write-Verbose "$logLead : Searching for log files on $ComputerName"
|
||||||
|
$logFiles = Invoke-Command $ComputerName { Get-ChildItem -Path $using:logPath -Filter *.log* -File }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-IsCollectionNullOrEmpty $logFiles) {
|
||||||
|
|
||||||
|
Write-Host "$logLead : No log files found under $logPath on computer $ComputerName"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$logDetails = @()
|
||||||
|
foreach ($logFile in $logFiles) {
|
||||||
|
$logDetails += New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
Name = $logFile.BaseName -replace ".log$", "";
|
||||||
|
Size = $logFile.Length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = $logDetails | Group-Object -Property Name
|
||||||
|
$totalDiskUtilization = 0
|
||||||
|
|
||||||
|
$analysisResults = @()
|
||||||
|
foreach ($logGroup in ($groups | Sort-Object -Property Count -Descending)) {
|
||||||
|
|
||||||
|
[array]$sizeDetails = $logGroup.Group | Select-Object -ExpandProperty Size
|
||||||
|
if ($sizeDetails.Count -eq 0) {
|
||||||
|
$cumulativeSize = $sizeDetails;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
[int]$cumulativeSize = 0
|
||||||
|
$sizeDetails | ForEach-Object {
|
||||||
|
$cumulativeSize += [int]$_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalDiskUtilization += $cumulativeSize
|
||||||
|
$analysisResults += New-Object PSObject -Property @{
|
||||||
|
|
||||||
|
LogFile = $logGroup.Name;
|
||||||
|
LogCount = $logGroup.Count;
|
||||||
|
Size = $cumulativeSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$topTenLogSizes = $analysisResults | Sort-Object -Property Size -Descending | Select-Object -First 10
|
||||||
|
|
||||||
|
# Begin unnecessarily complicated table print that I threw together during a meeting because I was bored
|
||||||
|
$maxLogNameLength = ($topTenLogSizes | Select-Object -ExpandProperty LogFile | Sort-Object -Property Length -Descending | Select-Object -First 1).Length
|
||||||
|
$maxLogNamePadding = [math]::Max($maxLogNameLength, 8)
|
||||||
|
|
||||||
|
$maxLogSizeLength = ($topTenLogSizes | Select-Object -ExpandProperty Size | ForEach-Object { $_.ToString() } | Sort-Object -Property Length -Descending | Select-Object -First 1).Length
|
||||||
|
$logSizePadding = [math]::Max($maxLogSizeLength , 4)
|
||||||
|
|
||||||
|
$headerRow = (" | " + ("Log File".PadRight($maxLogNameLength), "Size".PadRight($maxLogSizeLength), "Log Count" -Join " | ") + " | ")
|
||||||
|
$headerRowLength = $headerRow.Length - 4
|
||||||
|
|
||||||
|
$totalUtilizationLine = " | $ComputerName Log Utilization: $([math]::Round(($totalDiskUtilization/1024/1024),2))mb".PadRight($headerRowLength + 2) + "|"
|
||||||
|
|
||||||
|
$border = " %" + ("=" * $headerRowLength) + "%"
|
||||||
|
|
||||||
|
Write-Host $border
|
||||||
|
Write-Host $totalUtilizationLine
|
||||||
|
Write-Host $border
|
||||||
|
Write-Host $headerRow
|
||||||
|
Write-Host $border
|
||||||
|
|
||||||
|
foreach ($topLog in $topTenLogSizes) {
|
||||||
|
|
||||||
|
$outputLine = " | " + ($topLog.LogFile.PadRight($maxLogNamePadding), $topLog.Size.ToString().PadRight($logSizePadding), $topLog.LogCount.ToString().PadRight(9) -Join " | ") + " | "
|
||||||
|
Write-Host $outputLine
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host $border
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
function Get-PackageForInstallation {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Downloads a File to the Specified Outputfolder
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$loglead,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$outputFolder,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$outputfileName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$downloadUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!(Test-Path $outputFolder)) {
|
||||||
|
Write-Verbose ("$logLead : Creating Package Download Folder {0}" -f $outputFolder)
|
||||||
|
New-Item $outputFolder -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadFile = (Join-Path $outputFolder $outputfileName)
|
||||||
|
|
||||||
|
if (Test-Path $downloadFile) {
|
||||||
|
# If a job is rerun, this should prevent downloading again, unless it spans a day
|
||||||
|
Write-Host ("$logLead : Using Existing Package from {0}" -f $downloadFile)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host ("$logLead : Downloading File from {0} to {1}" -f $downloadUrl, $downloadFile)
|
||||||
|
Invoke-WebRequest -Uri $downloadUrl -OutFile $downloadFile
|
||||||
|
}
|
||||||
|
|
||||||
|
return $downloadFile
|
||||||
|
}
|
39
Modules/Alkami.DevOps.Common/Public/Get-PodName.ps1
Normal file
39
Modules/Alkami.DevOps.Common/Public/Get-PodName.ps1
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
function Get-PodName {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Easily return human-readable name for the pod, lane, designation in which a host belongs.
|
||||||
|
.DESCRIPTION
|
||||||
|
Query the host's Machine.config to determine the pod, lane, designation, etc, and return in a format people can use.
|
||||||
|
|
||||||
|
.PARAMETER ComputerName
|
||||||
|
Optional Parameter. If specified, gets the name for that server's pod, lane, designation, etc.
|
||||||
|
|
||||||
|
.PARAMETER Full
|
||||||
|
Optional Parameter. If specified, returns the full name of the environment. Otherwise, returns the short name.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-PodName -ComputerName APP169671
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$ComputerName = "$env:computerName",
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[switch]$Full
|
||||||
|
)
|
||||||
|
|
||||||
|
# get the FQDN no matter what for simplicity
|
||||||
|
$ComputerName=[System.Net.Dns]::GetHostByName($ComputerName).HostName
|
||||||
|
|
||||||
|
# get the key value
|
||||||
|
$keyValue = Get-AppSetting -Key Environment.Name -ComputerName $ComputerName
|
||||||
|
|
||||||
|
if ( $Full ) {
|
||||||
|
$keyValue
|
||||||
|
} else {
|
||||||
|
$podName = ($keyValue -split " ")[-1]
|
||||||
|
$podName
|
||||||
|
}
|
||||||
|
}
|
50
Modules/Alkami.DevOps.Common/Public/Get-SecretsForPod.ps1
Normal file
50
Modules/Alkami.DevOps.Common/Public/Get-SecretsForPod.ps1
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
function Get-SecretsForPod {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Gets Secrets for Pod.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$secretUserName,
|
||||||
|
[string]$secretPassword,
|
||||||
|
[string]$secretDomain,
|
||||||
|
[string]$secretFolderNames
|
||||||
|
)
|
||||||
|
|
||||||
|
$client = New-Object Alkami.Ops.SecretServer.Client
|
||||||
|
$authResult = $client.AuthenticateAsync($secretUserName, $secretPassword, $secretDomain).GetAwaiter().GetResult()
|
||||||
|
|
||||||
|
if ($authResult.Status -ne [Alkami.Ops.SecretServer.Enum.ResultStatus]::Success) {
|
||||||
|
Write-Warning ("Unable to authenticate with SecretServer: {0}" -f $authResult.Message)
|
||||||
|
|
||||||
|
if ($authResult.Errors.Count -gt 0) {
|
||||||
|
$errors = ("Error(s) from server - " + ($authResult.Errors | Select-Object -ExpandProperty "ErrorMessage") -join ", ")
|
||||||
|
Write-Warning -Message $errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
[HashTable]$secrets = $null
|
||||||
|
|
||||||
|
foreach ($secretFolder in $secretFolderNames.Split(',')) {
|
||||||
|
$result = $client.GetFolderSecretsAsync($secretFolder.Trim()).GetAwaiter().GetResult()
|
||||||
|
|
||||||
|
if ($result.Status -ne [Alkami.Ops.SecretServer.Enum.ResultStatus]::Success) {
|
||||||
|
Write-Warning ("Error pulling secrets for {0} from SecretServer: {1}" -f $secretFolder, $secretResult.Message)
|
||||||
|
|
||||||
|
if ($secretResult.Errors.Count -gt 0) {
|
||||||
|
$errors = ("Error(s) from server - " + ($secretResult.Errors | Select-Object -ExpandProperty "ErrorMessage") -join ", ")
|
||||||
|
Write-Warning -Message $errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$secrets += $result.Secrets
|
||||||
|
}
|
||||||
|
|
||||||
|
return $secrets
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
|||||||
|
function Get-SecurityGroupPrefix {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Easily return human-readable name for the Security Group in which a host belongs.
|
||||||
|
.DESCRIPTION
|
||||||
|
Query the host's Machine.config to determine the Security Group for a host and return in a format people can use.
|
||||||
|
|
||||||
|
.PARAMETER ComputerName
|
||||||
|
Optional Parameter. If specified, gets the Security Group for that server's pod, lane, designation, etc. Defaults to local computer.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
|
||||||
|
Get-SecurityGroup -ComputerName APP169671
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$ComputerName="$env:computerName"
|
||||||
|
)
|
||||||
|
|
||||||
|
# get the FQDN no matter what for simplicity
|
||||||
|
$ComputerName=[System.Net.Dns]::GetHostByName($ComputerName).HostName
|
||||||
|
|
||||||
|
$securityGroup=Get-AppSetting -Key Environment.UserPrefix -ComputerName $ComputerName
|
||||||
|
|
||||||
|
return $securityGroup
|
||||||
|
}
|
166
Modules/Alkami.DevOps.Common/Public/Get-ServerStatusReport.ps1
Normal file
166
Modules/Alkami.DevOps.Common/Public/Get-ServerStatusReport.ps1
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
function Get-ServerStatusReport {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Start the Server Status check, typically run after a Scale-Up event.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Performs several sanity tests to ensure the servers are fully operational after a Scale-Up event.
|
||||||
|
Main entrypoint that calls this is in teamcity.sre.code/ScaleEnvironments/Invoke-ReportServerStatus.ps1
|
||||||
|
|
||||||
|
.PARAMETER Servers
|
||||||
|
[string[]]Array of servers to run tests against. Usually this is a list of servers by Designation (LC3, Morph, etc.)
|
||||||
|
|
||||||
|
.PARAMETER ProfileName
|
||||||
|
[string] Specific AWS CLI Profile to use in AWS API calls.
|
||||||
|
|
||||||
|
.PARAMETER Region
|
||||||
|
[string] Specific AWS CLI Region to use in AWS API calls.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Outputs a table with the results of the test by host.
|
||||||
|
Returns custom object with the following properties:
|
||||||
|
"Hostname"
|
||||||
|
"Designation"
|
||||||
|
"InLoadBalancer"
|
||||||
|
"IsNagRunning"
|
||||||
|
"IsSubServiceRunning"
|
||||||
|
"AllAlkServicesRunning"
|
||||||
|
"TagsCorrect"
|
||||||
|
"IsAppServer"
|
||||||
|
"IsWebServer"
|
||||||
|
"IsMicServer"
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([Object])]
|
||||||
|
Param (
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string[]] $Servers,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $ProfileName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string] $Region
|
||||||
|
)
|
||||||
|
|
||||||
|
$logLead = (Get-LogLeadName)
|
||||||
|
|
||||||
|
Write-Host "$logLead : Running for servers:`n$Servers"
|
||||||
|
|
||||||
|
# Script Block to do the tests.
|
||||||
|
$testsScriptBlock = {
|
||||||
|
param ($hostname, $arguments)
|
||||||
|
|
||||||
|
Write-Host "##teamcity[blockOpened name='Running tests for hostname $hostname']"
|
||||||
|
|
||||||
|
$testResults = Invoke-Command -ComputerName $hostname -ScriptBlock {
|
||||||
|
$computername = (Get-FullyQualifiedServerName)
|
||||||
|
$isAppServer = Test-IsAppServer -ComputerName $computername
|
||||||
|
$isWebServer = Test-IsWebServer -ComputerName $computername
|
||||||
|
$isMicServer = Test-IsMicroServer -ComputerName $computername
|
||||||
|
|
||||||
|
# Make an object to hold the results of the tests
|
||||||
|
$testResult = New-Object PSObject -Property @{
|
||||||
|
"Hostname" = $computername
|
||||||
|
"Designation" = "Unknown"
|
||||||
|
"InLoadBalancer" = $null
|
||||||
|
"IsNagRunning" = $null
|
||||||
|
"IsSubServiceRunning" = $null
|
||||||
|
"AllAlkServicesRunning" = $null
|
||||||
|
"TagsCorrect" = $null
|
||||||
|
"IsAppServer" = $isAppServer
|
||||||
|
"IsWebServer" = $isWebServer
|
||||||
|
"IsMicServer" = $isMicServer
|
||||||
|
}
|
||||||
|
|
||||||
|
# NAG test
|
||||||
|
try {
|
||||||
|
if($IsAppServer) {
|
||||||
|
Write-Host "Running NAG test"
|
||||||
|
$nagStatus = Test-IsNagRunning -Server $computername 2>$null
|
||||||
|
$testResult.IsNagRunning = $nagStatus
|
||||||
|
} else {
|
||||||
|
$testResult.IsNagRunning = "N/A"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Resolve-Error -ErrorRecord $_
|
||||||
|
$testResult.IsNagRunning = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Subscription Service test
|
||||||
|
try {
|
||||||
|
if($isMicServer -or $isWebServer -or $isAppServer) {
|
||||||
|
Write-Host "Running Subscription Service test"
|
||||||
|
$chocoServices = Get-ChocolateyServices 2>$null
|
||||||
|
$subscriptionService = ($chocoServices | Where-Object {$_.Name -eq "Alkami.Services.Subscriptions.Host"})
|
||||||
|
if($null -ne $subscriptionService) {
|
||||||
|
$testResult.IsSubServiceRunning = ($subscriptionService.State -eq "Running")
|
||||||
|
} else {
|
||||||
|
$testResult.IsSubServiceRunning = $false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$testResult.IsSubServiceRunning = "N/A"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Resolve-Error -ErrorRecord $_
|
||||||
|
$testResult.IsSubServiceRunning = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# All ALK Services running test?
|
||||||
|
Write-Host "Running All Alk Services test"
|
||||||
|
$alkServices = ($chocoServices | Where-Object {$_.Name -like "Alkami.*"})
|
||||||
|
$stoppedAlkServiceCount = ($alkServices | Where-Object {$_.State -eq "Stopped"}).count
|
||||||
|
$testResult.AllAlkServicesRunning = !($stoppedAlkServiceCount -gt 0)
|
||||||
|
|
||||||
|
|
||||||
|
# Tags test
|
||||||
|
try {
|
||||||
|
Write-Host "Running Tags test"
|
||||||
|
$tags = Get-CurrentInstanceTags
|
||||||
|
$autoShutdownTagKey = "alk:autoshutdown"
|
||||||
|
$autoShutdownTag = ($tags | Where-Object { ($_.Key -eq $autoShutdownTagKey -and $_.Value -eq "true") })
|
||||||
|
$isAutoShutdown = ($null -ne $autoShutdownTag)
|
||||||
|
$testResult.TagsCorrect = (!($isAutoShutdown))
|
||||||
|
} catch {
|
||||||
|
Resolve-Error -ErrorRecord $_
|
||||||
|
$testResult.TagsCorrect = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $testResult
|
||||||
|
}
|
||||||
|
|
||||||
|
# LoadBalancer test
|
||||||
|
# This test is done outside of the above script block as it needs to be run off-host.
|
||||||
|
# Running this on host will fail due to the IAM creds not having permissions for some
|
||||||
|
# resources (Only for NGinx lookups).
|
||||||
|
try {
|
||||||
|
if($testResults.IsAppServer -or $testResults.IsWebServer) {
|
||||||
|
Write-Host "Running LoadBalancer test"
|
||||||
|
$lbState = Get-LoadBalancerState -Server $hostname -AwsProfileName $arguments.ProfileName -AwsRegion $arguments.Region
|
||||||
|
$testResults.InLoadBalancer = ($lbState -eq "Active")
|
||||||
|
} else {
|
||||||
|
$testResults.InLoadBalancer = "N/A"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Resolve-Error -ErrorRecord $_
|
||||||
|
$testResults.InLoadBalancer = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "##teamcity[blockClosed name='Running tests for hostname $hostname']"
|
||||||
|
|
||||||
|
# Return the test
|
||||||
|
return $testResults
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel over the servers to test.
|
||||||
|
try {
|
||||||
|
$results = (Invoke-Parallel2 -Objects $Servers -Arguments @{ProfileName = $ProfileName; Region = $Region;} -Script $testsScriptBlock).Result
|
||||||
|
} catch {
|
||||||
|
Resolve-Error -ErrorRecord $_
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send results to caller.
|
||||||
|
return $results
|
||||||
|
}
|
34
Modules/Alkami.DevOps.Common/Public/Get-SlackAction.ps1
Normal file
34
Modules/Alkami.DevOps.Common/Public/Get-SlackAction.ps1
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
function Get-SlackAction {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Used to create interactive elements in messages
|
||||||
|
|
||||||
|
.PARAMETER Text
|
||||||
|
The text to display
|
||||||
|
|
||||||
|
.PARAMETER Url
|
||||||
|
The URL the user will interact with
|
||||||
|
|
||||||
|
.PARAMETER Type
|
||||||
|
Defaults to button
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
[OutputType([object])]
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Text,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Url,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateSet('button')] # We only usually offer one option type. Feel free to update when you've got a new use-case
|
||||||
|
[string]$Type = 'button'
|
||||||
|
)
|
||||||
|
|
||||||
|
return @{
|
||||||
|
text = $Text
|
||||||
|
url = $Url
|
||||||
|
type = $Type
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user