Forked from davefunkel/Script-Template-WithCreds.ps1
Last active
May 10, 2019 08:43
-
-
Save naamancampbell/b33af1f2e566b014e2ba8deff898b939 to your computer and use it in GitHub Desktop.
Generates and distributes encrypted passwords for use with PowerShell scripts used in CloudFormation templates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Generates and distributes encrypted passwords for use with PowerShell scripts used in CloudFormation templates. | |
.DESCRIPTION | |
The CloudFormation-PowerShell-Creds script is designed to be run prior to running a CloudFormation stack | |
then called again from within the CloudFormation template. The purpose is to avoid the use of cleartext | |
passwords in CloudFormation templates/PowerShell scripts. | |
There are three main components of this script: | |
1. Prepare Credentials | |
This phase stores encrypted passwords in a restricted folder before uploading to AWS S3. | |
Re-running this phase will generate a new unique credential folder. | |
2. Download Credentials | |
This phase is recreates the restricted folder structure on the target system before downloading from AWS S3. | |
3. Access Credentials | |
This phase is used to access credentials (as PSCredential objects) from the local credential store. | |
Author: Naaman Campbell ([email protected]) | |
Change Log: | |
07/10/2017 0.1 NC Initial Release | |
Forked from: Script-Template-WithCreds.ps1 | |
Author: David Lee ([email protected]) | |
https://gist.github.com/davefunkel/415a4a09165b8a6027a297085bf812c5 | |
.PARAMETER PrepareCredentials | |
Records and prepares credentials for use within AWS CloudFormation templates (and other scripts) | |
.PARAMETER DownloadCredentials | |
Downloads credentials from AWS S3 for use on target system | |
.PARAMETER AccessCredential | |
Retrieves the specified credential from the local credential store | |
.PARAMETER CredentialPath | |
Path to credential store folder | |
.PARAMETER S3Bucket | |
Name of S3 Bucket to stage credentials | |
.PARAMETER AWSAccessKey | |
AWS Access Key for user with S3 permissions for -S3Bucket | |
.PARAMETER AWSSecretKey | |
AWS Secret Key for -AWSAccessKey | |
.PARAMETER AWSRegion | |
AWS Region for -S3Bucket | |
.EXAMPLE | |
Prompts for credentials to save to the secure files in C:\Admin\Keys before copying to the specified AWS S3 bucket: | |
.\CloudFormation-PowerShell-Creds.ps1 -PrepareCredentials -CredentialPath "C:\Admin\Keys" -S3Bucket "cf-templates-1234deadbeef1-ap-southeast-2" -AWSAccessKey "XXXX" -AWSSecretKey "XXXX" -AWSRegion "ap-southeast-2" | |
.EXAMPLE | |
Downloads the secure files from the specified AWS S3 bucket to the matching local folder path (eg. C:\Admin\Keys\CF-Creds-20171018T0954207027): | |
.\CloudFormation-PowerShell-Creds.ps1 -DownloadCredentials -CredentialPath "C:\Admin\Keys\CF-Creds-20171018T0954207027" -S3Bucket "cf-templates-1234deadbeef1-ap-southeast-2" | |
.EXAMPLE | |
Retrieves "ADFSSVC" user credential (as $ADFSSVC-Cred) from local secure credential store | |
$ADFSSVC-Cred = .\CloudFormation-PowerShell-Creds.ps1 -AccessCredential "ADFSSVC" -CredentialPath "C:\Admin\Keys\CF-Creds-20171018T0954207027" | |
#> | |
[CmdletBinding( | |
HelpUri = 'https://github.com/naamancampbell/CloudFormationPowerShellCreds/')] | |
Param( | |
# Prepare credentials for use within CloudFormation templates | |
[Parameter(Position = 0, | |
Mandatory = $true, | |
ParameterSetName = "Prepare")] | |
[switch] | |
$PrepareCredentials, | |
# Download credentials from within CloudFormation template | |
[Parameter(Position = 0, | |
Mandatory = $true, | |
ParameterSetName = "Download")] | |
[switch] | |
$DownloadCredentials, | |
# Access credentials from within CloudFormation template | |
[Parameter(Position = 0, | |
Mandatory = $true, | |
ParameterSetName = "Access")] | |
[String] | |
$AccessCredential, | |
# Path to store credentials | |
# | |
# - with -PrepareCredentials: | |
# eg. C:\Admin\Keys | |
# | |
# - with -DownloadCredentials & -AccessCredential (includes Credential dir): | |
# eg. C:\Admin\Keys\CF-Creds-20171018T0305398200 | |
# | |
[Parameter(Mandatory = $true, | |
ParameterSetName = "Prepare")] | |
[Parameter(Mandatory = $true, | |
ParameterSetName = "Download")] | |
[Parameter(Mandatory = $true, | |
ParameterSetName = "Access")] | |
[String] | |
$CredentialPath, | |
# S3 Bucket to stage credentials | |
[Parameter(Mandatory = $true, | |
ParameterSetName = "Prepare")] | |
[Parameter(Mandatory = $true, | |
ParameterSetName = "Download")] | |
[String] | |
$S3Bucket, | |
# AWS Access Key to upload to S3 | |
[Parameter(ParameterSetName = "Prepare")] | |
[String] | |
$AWSAccessKey, | |
# AWS Secret Key to upload to S3 | |
[Parameter(ParameterSetName = "Prepare")] | |
[String] | |
$AWSSecretKey, | |
# AWS Region to upload to S3 | |
[Parameter(ParameterSetName = "Prepare")] | |
[String] | |
$AWSRegion, | |
) | |
<##==================================================================================== | |
GLOBAL CONFIGURATION | |
##===================================================================================#> | |
$erroractionpreference = "stop" | |
$debugFlag = $PSBoundParameters.Debug.IsPresent | |
$verboseFlag = $PSBoundParameters.Verbose.IsPresent | |
# Use this to control whether to break on debugs or not | |
if($debugFlag -eq $true) { | |
# If you want a break prompt on each Write-Debug entry, use "inquire" | |
# Otherwise use "continue" to simply output debug log (recommended) | |
$debugPreference = "continue" | |
} | |
# Output Log File Name: DescriptiveName_yyyy-MM-ddTHHmm.log | |
$outputLogFileName = "$scriptName_" + (get-date -f yyyy-MM-ddTHHmm) + ".log" | |
# Change Log File Directory as necessary | |
$rootDir = Split-Path -Path $CredentialPath -Parent | |
$outputLogDir = "$rootDir\CF-Creds-Logs" | |
# Full Path of Log File | |
$outputLogPath = "$outputLogDir\$outputLogFileName" | |
# Log Age Limit (in days). Log files older than this will be auto deleted | |
$logAgeLimit = 30 | |
# This is where I put global variables and the like for easy access an updates | |
<##==================================================================================== | |
FUNCTIONS | |
##===================================================================================#> | |
<# | |
.SYNOPSIS | |
Initializes the output log file. | |
.DESCRIPTION | |
Execute this function at the beginning of your main code to ensure the log file path | |
exists, and if it doesn't, create the folder paths to ensure future Write-Log calls | |
will run without issue | |
This function can also be modified to create log file headers | |
.PARAMETER overwrite | |
Set this flag to overwrite any existing log files. Otherwise default is to append | |
to the existing log file | |
.PARAMETER headerText | |
Optional ability to define some text at the start of eveyr initialization of the log file | |
Useful if you are not overwriting the log file each time script is run] | |
#> | |
function Start-LogFile([switch]$overwrite, $headerText) { | |
# Check if log dir exists, if not, create it | |
if(!(Test-Path $outputLogDir)) { | |
New-Item -type Directory $outputLogDir | out-null | |
} | |
Write-Output $headerText | |
try { | |
if($overwrite -eq $true) { | |
set-content $outputLogPath $headerText | |
} else { | |
add-content $outputLogPath $headerText | |
} | |
} catch { | |
Write-Warning "Could not initialize the log file: $outputLogPath" | |
} | |
} | |
<# | |
.SYNOPSIS | |
Writes to an entry into the output log file, and to the console if -Verbose is used | |
.DESCRIPTION | |
This is a useful function for log file outputs. Change the structure of this output | |
as necessary for your scripts. | |
If the -Verbose flag is set, then the log file message will also be written to the console | |
using the Write-Verbose command | |
.PARAMETER type | |
Optional, but can use to tag the log entry type. Recommend to use one of INFO,WARNING,ERROR | |
Will default to INFO if non specified. | |
NOTE: If you wish to write a DEBUG log entry, use the Write-DebugLog function | |
.PARAMETER message | |
The text to write to your ouput file | |
#> | |
function Write-Log($message, $type) { | |
if($type -eq $null -or $type -eq "") { | |
$type = "INFO" | |
} | |
try { | |
# Log Entry Structure: [Date] [TYPE] [MESSAGE] | |
$logEntry = (Get-Date -format u) + "`t" + $type.ToUpper() + "`t" + $message | |
if ($type -eq "WARNING") { | |
Write-Host -foregroundcolor Yellow $logEntry | |
} elseif ($type -eq "ERROR") { | |
Write-Host -foregroundcolor Red $logEntry | |
} else { | |
Write-Host $logEntry | |
} | |
Add-Content $outputLogPath $logEntry | |
} catch { | |
Write-Warning "Could not write entry to output log file: $outputLogPath `nLog Entry:$message" | |
} | |
} | |
<# | |
.SYNOPSIS | |
Writes to an entry ino the output log file only if the -debug parameter was set in the script | |
.DESCRIPTION | |
This is a useful function for performing debug logging. It will use both the in built Write-Debug | |
function as well as creating an entry to the log file with a DEBUG type | |
.PARAMETER message | |
The debug text to write to your ouput file | |
#> | |
function Write-DebugLog($message) { | |
Write-Debug $message | |
try { | |
# Only write to the log file if the -Debug parameter has been set | |
if($script:debugFlag -eq $true) { | |
# Log Entry Structure: [Date] [TYPE] [MESSAGE] | |
$logEntry = (Get-Date -format u) + "`t" + "DEBUG" + "`t" + $message | |
Add-Content $outputLogPath $logEntry | |
} | |
} catch { | |
Write-Warning "Could not write entry to output log file: $outputLogPath `nLog Entry:$message" | |
} | |
} | |
<# | |
.SYNOPSIS | |
Cleans up log files | |
.DESCRIPTION | |
This is a useful function when scripts are run regularly and thus create lots of log files. | |
Based on a log age date, it will remove all log files older than that period | |
Uses a Global variable for the log age date | |
.PARAMETER logPath | |
Folder location for log files to remove | |
.PARAMETER fileExtension | |
Type of files to delete. Use a wildcard format like "*.log" | |
#> | |
function Cleanup-LogFiles($logPath, $fileExtension) { | |
# Determine the date of which files older than specific period will be deleted | |
$dateToDelete = (Get-Date).AddDays(-$logAgeLimit) | |
$filesToDelete = Get-ChildItem $logPath -Include $fileExtension -Recurse | where { $_.LastWriteTime -le $dateToDelete } | |
foreach ($file in $filesToDelete) { | |
$filePath = $file.FullName | |
try { | |
Write-DebugLog "Deleting $file..." | |
Remove-Item $filePath -force | out-null | |
} catch { | |
Write-Log "Failed to delete old log file ($filePath)" -type WARNING | |
} | |
} | |
} | |
<# | |
.SYNOPSIS | |
Uploads files to S3 Bucket | |
.DESCRIPTION | |
This function uses the EC2 Instance-attached IAM Role to upload files to AWS S3 storage. | |
.PARAMETER uploadPath | |
Directory/file location to upload | |
#> | |
function Upload-FilesToS3($uploadPath) { | |
# Determine if AWS Tools for PowerShell are installed | |
if (!(Get-AWSPowerShellVersion)) { | |
Write-Log "AWS Tools for PowerShell not installed. Installing from PowerShell Gallery for current user.." -type INFO | |
# test if AWS Access Key, AWS Secret Key and AWS Region were provided | |
if (!($AWSAccessKey -and $AWSSecretKey -and $AWSRegion)) { | |
Write-Log "Failed to Install AWS Tools for PowerShell. Re-run with -AWSAccessKey, -AWSSecretKey and -AWSRegion set." -type ERROR | |
Write-Host -foreground red "Failed to Install AWS Tools for PowerShell. Re-run with -AWSAccessKey, -AWSSecretKey and -AWSRegion set." | |
Exit -1 | |
} | |
try { | |
Set-PSRepository -InstallationPolicy Trusted -Name "PSGallery" | |
Install-Package -Name AWSPowerShell -Scope CurrentUser | |
Set-AWSCredentials -StoreAs CF-Creds -AccessKey $AWSAccessKey -SecretKey $AWSSecretKey | |
Initialize-AWSDefaults -ProfileName 'CF-Creds' -Region $AWSRegion | |
} catch { | |
$errText = $error[0] | |
Write-Log "Failed to Install AWS Tools for PowerShell. Error Message was: $errText" -type ERROR | |
Write-Host -foreground red "Failed to Install AWS Tools for PowerShell. Please check Log File." | |
Exit -1 | |
} | |
} | |
try { | |
# Upload credential directory to AWS S3 Bucket and encrypt using AWS managed keys | |
$folderName = Split-Path -Path $uploadPath -Leaf | |
Write-S3Object -BucketName $S3Bucket -Folder $uploadPath -KeyPrefix $folderName -Recurse -CannedACLName private -ServerSideEncryption AES256 | |
} catch { | |
$errText = $error[0] | |
Write-Log "Failed to Upload Credentials to AWS S3 Bucket. Error Message was: $errText" -type ERROR | |
Write-Host -foreground red "Failed to Upload to AWS S3. Please check Log File." | |
Exit -1 | |
} | |
Write-Log "Credentials successfully uploaded to AWS S3 Bucket." -Type INFO | |
Write-Host -foreground Green "Credentials uploaded to AWS S3." | |
} | |
<# | |
.SYNOPSIS | |
Downloads files from S3 Bucket | |
.DESCRIPTION | |
This function uses the EC2 Instance-attached IAM Role to download files from AWS S3 storage. | |
#> | |
function Download-FilesFromS3() { | |
# Determine if AWS Tools for PowerShell are installed | |
if (!(Get-AWSPowerShellVersion)) { | |
Write-Log "AWS Tools for PowerShell not installed. Installing from PowerShell Gallery for current user.." -type INFO | |
try { | |
Set-PSRepository -InstallationPolicy Trusted -Name "PSGallery" | |
Install-Package -Name AWSPowerShell -Scope CurrentUser | |
} catch { | |
$errText = $error[0] | |
Write-Log "Failed to Install AWS Tools for PowerShell. Error Message was: $errText" -type ERROR | |
Write-Host -foreground red "Failed to Install AWS Tools for PowerShell. Please check Log File." | |
Exit -1 | |
} | |
} | |
try { | |
# Download credential directory to AWS S3 Bucket and encrypt using AWS managed keys | |
$folderName = Split-Path -Path $CredentialPath -Leaf | |
Read-S3Object -BucketName $S3Bucket -KeyPrefix $folderName -Folder $CredentialPath | |
} catch { | |
$errText = $error[0] | |
Write-Log "Failed to Download Credentials from AWS S3 Bucket. Error Message was: $errText" -type ERROR | |
Write-Host -foreground red "Failed to Download from AWS S3. Please check Log File." | |
Exit -1 | |
} | |
Write-Log "Credentials successfully downloaded from AWS S3 Bucket." -Type INFO | |
Write-Host -foreground Green "Credentials downloaded from AWS S3." | |
} | |
<##==================================================================================== | |
MAIN CODE | |
##===================================================================================#> | |
if ($PrepareCredentials -eq $true) { | |
#### | |
# Generates the secure files and uploads to AWS S3. | |
#### | |
try { | |
$headerText = (Get-Date -format u) + "`t" + "INIT" + "`t" + "****$scriptName log initialised in PrepareCredentials mode.****" | |
Start-LogFile -headerText $headerText # Initialize the log file with a header | |
# This will be used in file paths below, so avoid using spaces and special characters | |
$ScriptPath = "$PSCommandPath" | |
# Datestamp | |
$DateStamp = Get-Date -Format FileDateTime | |
# Initialise Credential Store | |
$CredentialDir = "$CredentialPath\CF-Creds-$DateStamp" | |
$CredentialKey = "$CredentialDir\AES.key" | |
$CredentialStore = "$CredentialDir\SecureStore.txt" | |
mkdir -p $CredentialDir | |
New-Item -Path $CredentialKey -ItemType "file" | |
New-Item -Path $CredentialStore -ItemType "file" | |
Copy-Item -Path $ScriptPath -Destination $CredentialDir | |
# Secure Credential Store | |
# - restrict access to current user (chmod 700 dir) | |
$ScriptUser = "$env:userdomain\$env:username" | |
Invoke-Expression -Command:"icacls $CredentialDir /grant:r ${ScriptUser}:F /T" | |
Invoke-Expression -Command:"icacls $CredentialDir /inheritance:r /T" | |
# Generate a random AES Encryption Key. | |
$AESKey = New-Object Byte[] 32 | |
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey) | |
Add-Content $CredentialKey $AESKey | |
Write-Log "Collecting Credentials to create a secure credential file..." -Type INFO | |
# Display credential prompt until closed by user | |
do { | |
$creds = $Host.UI.PromptForCredential('CloudFormation PowerShell Credentials', | |
'Close window to finish collecting credentials.', | |
'', '') | |
# exit credentials loop if prompt is closed | |
if (!$creds) { | |
break | |
} | |
# skip blank credentials | |
if ((!$creds.UserName) -or (!$creds.Password)) { | |
Write-Log "Blank Credentials Entered. Skipping.." -type WARNING | |
Write-Host -foreground yellow "Skipping Missing Credentials.." | |
continue | |
} | |
# Store the details in a hashed format | |
$userName = $creds.UserName | |
$passwordSecureString = $creds.Password | |
$password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey | |
Add-Content $CredentialStore "${userName}:${password}" | |
} while ($creds -ne $null) | |
Write-Log "Credentials collected and stored." -Type INFO | |
Write-Host -foreground Green "Credentials collected and stored." | |
Write-Log "Upload Credentials to AWS S3 Bucket" -Type INFO | |
Write-Host -foreground Yellow "Uploading Credentials to AWS S3" | |
Upload-FilesToS3 $CredentialDir | |
} catch { | |
$errText = $error[0] | |
Write-Log "Failed to Prepare Credentials. Error Message was: $errText" -type ERROR | |
Write-Host -foreground red "Failed to Prepare Credentials. Please check Log File." | |
Exit -1 | |
} | |
} elseif ($DownloadCredentials -eq $true) { | |
#### | |
# Downloads secure files from AWS S3 into secured folder | |
#### | |
$headerText = (Get-Date -format u) + "`t" + "INIT" + "`t" + "****$scriptName log initialised in AccessCredentials mode****" | |
Start-LogFile -headerText $headerText # Initialize the log file with a header | |
# Initialise/Secure Credential Directory | |
$CredentialDir = $CredentialPath | |
mkdir -p $CredentialDir | |
$ScriptUser = "$env:userdomain\$env:username" | |
Invoke-Expression -Command:"icacls $CredentialDir /grant:r ${ScriptUser}:F /T" | |
Invoke-Expression -Command:"icacls $CredentialDir /inheritance:r /T" | |
# Download secure files from AWS S3 | |
Download-FilesFromS3 | |
# Secure downloaded files in credential directory | |
Invoke-Expression -Command:"icacls $CredentialDir /grant:r ${ScriptUser}:F /T" | |
Invoke-Expression -Command:"icacls $CredentialDir /inheritance:r /T" | |
} elseif ($AccessCredential -ne $null) { | |
#### | |
# Retrieves password from secured folder as a SecureString | |
#### | |
# Check to ensure we have a secure credential directory (i.e. -DownloadCredentials has been run) and that the contents are valid | |
if(!(Test-Path $CredentialPath)) { | |
Write-Log "Could not find a secure credential directory at $CredentialPath. Exiting." -Type ERROR | |
Write-Host -foreground red "[ERROR] Could not find a secure credential directory at $CredentialPath. Ensure that you have run the -DownloadCredentials parameter at least once for this script." | |
exit -1 | |
} | |
$CredentialFilePath = "$CredentialPath\SecureStore.txt" | |
$AESKeyFilePath = "$CredentialPath\AES.key" | |
try { | |
Write-DebugLog "Reading secure credential file at $CredentialFilePath." | |
$AESKey = Get-Content $AESKeyFilePath | |
$CredLine = Select-String -Path $CredentialFilePath -Pattern $AccessCredential | %{ $_.ToString() } | |
$UserName = $CredLine.Split(':')[3] | |
$Password = $CredLine.Split(':')[4] | ConvertTo-SecureString -Key $AESKey | |
Write-DebugLog "Creating credential object..." | |
$CredObject = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $Password | |
Write-DebugLog "Credential store successfully read for username: $UserName" | |
return $CredObject | |
} catch { | |
$errText = $error[0] | |
Write-Log "Could not execute in AccessCredential mode. Error Message was: $errText" -type ERROR | |
exit -1 | |
} | |
} | |
<##==================================================================================== | |
END OF CODE | |
##===================================================================================#> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment