diff --git a/PwshAwsConfig.psd1 b/PwshAwsConfig.psd1 new file mode 100644 index 0000000..1d8313c --- /dev/null +++ b/PwshAwsConfig.psd1 @@ -0,0 +1,140 @@ +# +# Module manifest for module 'PwshAwsConfig' +# +# Generated by: Telstra Purple +# +# Generated on: 8/06/2021 +# + +@{ + +# Script module or binary module file associated with this manifest. +# RootModule = '' + +# Version number of this module. +ModuleVersion = '0.0.1' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '9f32767c-5342-4baa-9023-7ba040a71b7f' + +# Author of this module +Author = 'Curtis Lusmore' + +# Company or vendor of this module +CompanyName = 'Telstra Purple' + +# Copyright statement for this module +Copyright = '(c) Telstra Purple. All rights reserved.' + +# Description of the functionality provided by this module +# Description = 'Simplify the process of managing AWS CLI configuration and credentials' + +# Minimum version of the PowerShell engine required by this module +# PowerShellVersion = '' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = @( + 'PwshAwsConfig.psm1' +) + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @( + 'New-AwsAccount', + 'Edit-AwsAccount', + 'New-AwsRole', + 'Set-AwsProfile', + 'New-AwsSession' +) + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/PwshAwsConfig.psm1 b/PwshAwsConfig.psm1 new file mode 100644 index 0000000..800d5c4 --- /dev/null +++ b/PwshAwsConfig.psm1 @@ -0,0 +1,420 @@ +<# +.SYNOPSIS +A collection of functions for managing AWS CLI config and credentials. + +.DESCRIPTION +A collection of functions for managing AWS CLI config and credentials. +#> + +<# +.SYNOPSIS +Create IAM credentials for a new AWS account. + +.DESCRIPTION +Create IAM credentials for a new AWS account. This will create a record in your +~/.aws/credentials file to represent an IAM user in the given AWS account, by +reading the AWS Access Key ID and AWS Secret Access Key from a console prompt +or CSV file. This will silently ignore if the account already exists. To edit +an existing account, use Edit-AwsAccount. + +.PARAMETER AccountName +The name of the AWS account. This is purely for your own convenience to +distinguish between multiple AWS accounts + +.PARAMETER AccessKeyCsvPath +An optional path to a .csv file containing the AWS Access Key ID and AWS Secret +Access Key to use + +.EXAMPLE +The example below creates a new record in your ~/.aws/credentials file for +work:iam with the supplied AWS Access Key ID and AWS Secret Access Key. + +PS C:> New-AwsAccount -AccountName work +AWS Access Key ID: ******************** +AWS Secret Access Key: **************************************** + +.EXAMPLE +The example below creates a new record in your ~/.aws/credentials file for +work:iam by reading the AWS Access Key ID and AWS Secret Access Key from the +supplied CSV file. + +PS C:> New-AwsAccount -AccountName work -AccessKeyCsvPath my.user_accessKeys.csv +#> +Function New-AwsAccount +{ + Param( + [Parameter(Mandatory)] [string] $AccountName, + [string] $AccessKeyCsvPath + ) + + $credentialName = "$AccountName`:iam" + + If (Test-AwsProfile -Profile $credentialName) + { + Write-Information "Account already exists" + Return + } + + NewOrEdit-AwsAccount -AccountName $AccountName -AccessKeyCsvPath $AccessKeyCsvPath +} + +<# +.SYNOPSIS +Edit IAM credentials for an existing AWS account. + +.DESCRIPTION +Edit IAM credentials for an existing AWS account. This will edit a record in +your ~/.aws/credentials file which represents an IAM user in the given AWS +account, by reading the AWS Access Key ID and AWS Secret Access Key from a +console prompt or CSV file. This will fail if the account does not exist. To +create a new account, use New-AwsAccount. + +.PARAMETER AccountName +The name of the AWS account. This is purely for your own convenience to +distinguish between multiple AWS accounts + +.PARAMETER AccessKeyCsvPath +An optional path to a .csv file containing the AWS Access Key ID and AWS Secret +Access Key to use + +.EXAMPLE +The example below edits the record in your ~/.aws/credentials file for work:iam +with the supplied AWS Access Key ID and AWS Secret Access Key. + +PS C:> Edit-AwsAccount -AccountName work +AWS Access Key ID: ******************** +AWS Secret Access Key: **************************************** + +.EXAMPLE +The example below edits the record in your ~/.aws/credentials file for work:iam +by reading the AWS Access Key ID and AWS Secret Access Key from the supplied +CSV file. + +PS C:> Edit-AwsAccount -AccountName work -AccessKeyCsvPath my.user_accessKeys.csv +#> +Function Edit-AwsAccount +{ + Param( + [Parameter(Mandatory)] [string] $AccountName, + [string] $AccessKeyCsvPath + ) + + $credentialName = "$AccountName`:iam" + + If (-not (Test-AwsProfile -Profile $credentialName)) + { + Throw "No existing account $AccountName exists. Use New-AwsAccount to create a new account" + } + + NewOrEdit-AwsAccount -AccountName $AccountName -AccessKeyCsvPath $AccessKeyCsvPath +} + +<# +.SYNOPSIS +Create a new role for an existing AWS account. + +.DESCRIPTION +Create a new role an an existing AWS account. This will create a new record in +your ~/.aws/config file which represents a role in the given AWS account with +the supplied role ARN. The role can be backed by either an IAM credential or an +MFA credential. This will fail if the associated credential does not exist. + +.PARAMETER AccountName +The name of the AWS account that the role is for. Must be an account that you +previously created via New-AwsAccount + +.PARAMETER RoleName +The name of the role to create. This is purely for your own convenience to +distinguish between multiple roles + +.PARAMETER RoleArn +The ARN of the role to create + +.PARAMETER UseIam +Use this flag to specify that the role uses your IAM credentials. Defaults to +using your MFA credentials + +.EXAMPLE +The example below creates a new record in your ~/.aws/config file for a +ReadOnly role for the work account which authenticates with IAM credentials. + +PS C:> New-AwsRole ` + -AccountName work ` + -RoleName readonly ` + -RoleArn arn:aws:iam::000000000000:role/ReadOnly ` + -UseIam + +.EXAMPLE +The example below creates a new record in your ~/.aws/config file for a +PowerUsers role for the work account which authenticates with MFA credentials. + +PS C:> New-AwsRole ` + -AccountName work ` + -RoleName poweruser ` + -RoleArn arn:aws:iam::000000000000:role/PowerUsers +#> +Function New-AwsRole +{ + Param( + [Parameter(Mandatory)] [string] $AccountName, + [Parameter(Mandatory)] [string] $RoleName, + [Parameter(Mandatory)] [string] $RoleArn, + [switch] $UseIam + ) + + If ($UseIam) + { + $sourceProfile = "$AccountName`:iam" + If (-not (Test-AwsProfile -Profile $sourceProfile)) + { + throw "No existing account $AccountName exists. Use New-AwsAccount to create a new account" + } + } + Else + { + $sourceProfile = "$AccountName`:mfa" + If (-not (Test-AwsProfile -Profile $sourceProfile)) + { + throw "No existing account $AccountName exists. Use New-AwsAccount to create a new account" + } + } + + $profile = "$AccountName`:$RoleName" + + aws configure --profile $profile set role_arn $RoleArn + aws configure --profile $profile set source_profile $sourceProfile +} + +<# +.SYNOPSIS +Set the current AWS profile + +.DESCRIPTION +Set the current AWS profile by setting the AWS_PROFILE environment variable. If +no profile is supplied, displays a list of available profiles. + +.PARAMETER Profile +The name of the profile to use. If not supplied, displays a list of available +profiles + +.EXAMPLE +The example below sets the current AWS profile to the work:poweruser profile +previously created via New-AwsRole + +PS C:> Set-AwsProfile -Profile work:poweruser + +.EXAMPLE +The example below displays a list of available profiles, and then prompts the +user for their selection. + +PS C:> Set-AwsProfile +Set AWS Profile +Please select from your available AWS profiles +[0] work:iam [1] work:mfa [2] work:readonly [3] work:poweruser +[?] Help +#> +Function Set-AwsProfile +{ + Param( + [string] $Profile + ) + + If (-not $Profile) + { + $hotkeys = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + $index = 0 + $profiles = aws configure list-profiles | Sort-Object + $options = $profiles | % { + $hotkey = $hotkeys[$index++] + [System.Management.Automation.Host.ChoiceDescription]::new("&$hotkey`b$_", $_) + } + $selection = $Host.UI.PromptForChoice( + "Set AWS Profile", + "Please select from your available AWS profiles", + $options, -1 + ) + $Profile = $profiles[$selection] + } + + $env:AWS_PROFILE = $Profile +} + +<# +.SYNOPSIS +Create a new AWS session. + +.DESCRIPTION +Create a new AWS session for the given profile. This will create an MFA +profile if required, refresh the associated MFA credentials, and set the +current profile (by setting the AWS_PROFILE environment variable). + +.PARAMETER Profile +The name of the profile to use. Defaults to the current AWS profile (stored in +the AWS_PROFILE environment variable) if set, or displays a list of available +profiles if not set + +.PARAMETER Code +The value provided by the MFA device + +.PARAMETER Arn +The ARN of the MFA device + +.EXAMPLE +The example below starts a new session for the work PowerUser profile. In this +case, no MFA credentials were previously set up, so the user is prompted for +the MFA Device ARN and MFA code. + +PS C:> New-AwsSession -Profile work:poweruser +MFA Device ARN: arn:aws:iam::000000000000:mfa/my.user +MFA Code: ****** + +.EXAMPLE +The example below starts a new session for the currently selected profile +(stored in the AWS_PROFILE environment variable), which has an associated MFA +credential already configured. The user is prompted for a fresh MFA code. + +PS C:> New-AwsSession +MFA Code: ****** +#> +Function New-AwsSession +{ + Param( + [string] $Profile = $env:AWS_PROFILE, + [string] $Code, + [string] $Arn + ) + + If (-not $Profile) + { + Set-AwsProfile + $Profile = $env:AWS_PROFILE + } + + If ($Profile -match ":iam$") + { + Write-Information "IAM accounts do not need credentials refreshed" + Return + } + + $mfaCredentialName = If ($Profile -match ":mfa$") + { + $Profile + } + Else + { + aws configure --profile $Profile get source_profile + } + + Invoke-WithoutProfile { + Update-MfaCredentials ` + -MfaCredentialName $mfaCredentialName ` + -Code $Code ` + -Arn $Arn + } + + Set-AwsProfile -Profile $Profile +} + + +# Private functions + +Function NewOrEdit-AwsAccount +{ + Param( + [Parameter(Mandatory)] [string] $AccountName, + [string] $AccessKeyCsvPath + ) + + $credentialName = "$AccountName`:iam" + + If ($AccessKeyCsvPath) + { + If (-not (Test-Path $AccessKeyCsvPath)) + { + Throw "Unknown filepath: $AccessKeyCsvPath" + } + + $csv = Import-Csv -Path $AccessKeyCsvPath + $key = $csv."Access key ID" + $secret = $csv."Secret access key" + } + Else + { + $key = Read-Host -MaskInput -Prompt "AWS Access Key ID" + $secret = Read-Host -MaskInput -Prompt "AWS Secret Access Key" + } + + aws configure --profile $credentialName set aws_access_key_id $key + aws configure --profile $credentialName set aws_secret_access_key $secret +} + +Function Update-MfaCredentials +{ + Param( + [Parameter(Mandatory)] [string] $MfaCredentialName, + [string] $Code, + [string] $Arn + ) + + $iamCredentialName = $MfaCredentialName -replace ':mfa',':iam' + + If (Test-AwsProfile -Profile $MfaCredentialName) + { + $Arn = aws configure --profile $MfaCredentialName get mfa_device_arn + } + ElseIf (-not $Arn) + { + $Arn = Read-Host -Prompt "MFA Device ARN" + aws configure --profile $MfaCredentialName set mfa_device_arn $Arn + } + + If (-not $Code) + { + $Code = Read-Host -MaskInput -Prompt "MFA Code" + } + + $resp = aws sts get-session-token ` + --profile $iamCredentialName ` + --serial-number $Arn ` + --token-code $Code ` + --duration-seconds 86400 # 24hrs + + If (-not $?) + { + Throw "Failed to get session token: $resp" + } + + $credentials = $resp | ConvertFrom-Json | Select -ExpandProperty Credentials + + aws configure --profile $MfaCredentialName set aws_access_key_id $credentials.AccessKeyId + aws configure --profile $MfaCredentialName set aws_secret_access_key $credentials.SecretAccessKey + aws configure --profile $MfaCredentialName set aws_session_token $credentials.SessionToken +} + +Function Test-AwsProfile +{ + Param( + [Parameter(Mandatory)][string] $Profile + ) + + (aws configure list-profiles) -contains $Profile +} + +Function Invoke-WithoutProfile +{ + Param( + [Parameter(Mandatory)][ScriptBlock] $Block + ) + + $profile = $env:AWS_PROFILE + $env:AWS_PROFILE = $null + + Try + { + & $Block + } + Finally + { + $env:AWS_PROFILE = $profile + } +} \ No newline at end of file