Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active April 24, 2024 08:46
Show Gist options
  • Save PanosGreg/87a866f9701b27aaafb8d171f2ccc370 to your computer and use it in GitHub Desktop.
Save PanosGreg/87a866f9701b27aaafb8d171f2ccc370 to your computer and use it in GitHub Desktop.
Get AWS Credentials (access,secret,token) by assuming the IAM Role attached to the instance
function Get-AwsTempCredential {
<#
.SYNOPSIS
This function gets the AWS Access,Secret and Token by assuming the IAM Role that is attached to the current EC2 instance.
This should be used on EC2 instances only, not on any other VMs. (like Azure,GCP,Hyper-V or VMWare)
There should be an IAM Role attached to the instance, otherwise it won't work.
Once you get the temporary credentials, you can then login to AWS with "Set-AWSCredential"
or alternatively you can use the .LoginAWS() method from this object.
.EXAMPLE
(Get-AwsTempCredential -OutVariable aws).LoginAWS()
# it will login to AWS and save the object into a variable called $aws
.EXAMPLE
$aws = Get-AwsTempCredential
Set-AWSCredential -AccessKey $aws.AccessKey -SecretKey $aws.SecretKey -SessionToken $aws.TokenKey
Set-DefaultAWSRegion -Region $aws.Region
# get the temp credentials and then use them to login to AWS
.EXAMPLE
$aws = Get-AwsTempCredential
$aws.TokenExpiresIn()
$aws.TokenExpiresIn('Span')
$aws.TokenExpiresIn('String')
# use the custom method to check how much time is left until the current token expires
.EXAMPLE
Get-AwsTempCredential -SkipValidationCheck
# get the AWS credentials, without making the various internal checks.
# makes the function a bit faster, but assumes that all prerequisites are there.
.NOTES
Date: 15-Apr-2024
Author: Panos Grigoriadis
Version: 1.2.0
This function makes 9 network calls, which is a bit expensive.
I've added the -SkipValidationCheck switch, which reduces that to 6 network calls.
And also skips the WMI check which is the part that takes the most amount of time during runtime.
#>
[OutputType([pscustomobject])]
[CmdletBinding()]
param (
[switch]$SkipValidationCheck
)
#Requires -Version 5.1
# check if this is a windows box
if (-not $SkipValidationCheck -and $PSVersionTable.PSEdition -eq 'Core') {
$IsWindows = $PSVersionTable.Platform -eq 'Win32NT'
if (-not $IsWindows) {
Write-Warning 'This function was written only for Windows operating systems!'
return
}
}
# check if this is an AWS EC2 instance
if (-not $SkipValidationCheck) {
try {$Info = [wmi]"\root\cimv2:Win32_ComputerSystem.Name='$env:COMPUTERNAME'"}
catch {throw $_.Exception.Message}
$IsAwsInstance = $Info.Manufacturer -match 'Amazon'
if (-not $IsAwsInstance) {
Write-Warning 'This is not an EC2 Instance!'
Write-Warning 'Please make sure you run this function from an AWS EC2 Instance'
return
}
}
# don't use proxy
if ('Microsoft.PowerShell.Commands.WebRequestSession' -as [type]) {
$Proxy = [System.Net.WebProxy]::new() # <-- set null proxy
$WebSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
$WebSession.Proxy = $Proxy
$PSDefaultParameterValues['Invoke-RestMethod:WebSession'] = $WebSession
}
# otherwise skip the no-proxy config
# set some default parameters for Invoke-RestMethod
$PSDefaultParameterValues = @{
'Invoke-RestMethod:TimeoutSec' = 5
'Invoke-RestMethod:Verbose' = $false
'Invoke-RestMethod:ErrorAction' = 'Stop'
}
# also UseBasicParsing if PS version is 5
if ($PSVersionTable.PSVersion.Major -eq 5) {
$PSDefaultParameterValues['Invoke-RestMethod:UseBasicParsing'] = $true
}
# check if IMDS is turned on
if (-not $SkipValidationCheck) {
try {Invoke-RestMethod -Uri 'http://169.254.169.254/' | Out-Null}
catch {
$Status = $_.Exception.Response.StatusCode.ToString()
if ($Status -eq 'Forbidden') {
Write-Warning 'The Instance Metadata Service (IMDS) is not enabled!'
Write-Warning 'Please make sure the IMDS is turned on.'
return
}
elseif ($Status -eq 'Unauthorized') {} # <-- this is good, it means IMDSv2 is enabled
else {throw $_}
}
}
# get the metadata token in order to access the Metadata Service
$UriToken = 'http://169.254.169.254/latest/api/token'
$HeadTtl = @{'X-aws-ec2-metadata-token-ttl-seconds' = '21600'}
$Token = Invoke-RestMethod -Headers $HeadTtl -Method Put -Uri $UriToken
$HeadTok = @{'X-aws-ec2-metadata-token' = $Token}
# check for IAM Role
if (-not $SkipValidationCheck) {
$Root = 'http://169.254.169.254/latest/meta-data'
$Meta = Invoke-RestMethod -Headers $HeadTok -Method Get -Uri $Root
$HasIam = $Meta.Split("`n") -contains 'iam/'
if (-not $HasIam) {
Write-Warning 'This EC2 Instance may not have an IAM Role attached to it!'
Write-Warning 'Please make sure an IAM Role is attached to this instance'
return
}
}
# add two more default parameters
$PSDefaultParameterValues['Invoke-RestMethod:Method'] = 'Get'
$PSDefaultParameterValues['Invoke-RestMethod:Headers'] = $HeadTok
# get the IAM Role attached to the instance
$UriCreds = 'http://169.254.169.254/latest/meta-data/iam/security-credentials'
$IamRole = Invoke-RestMethod -Uri $UriCreds
# get the temporary AWS credentials when assuming this role
$UriRole = "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IamRole"
$AwsCreds = Invoke-RestMethod -Uri $UriRole
# get some info for this instance (id,region,account)
$UriInfo = 'http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info'
$UriInsID = 'http://169.254.169.254/latest/meta-data/instance-id'
$UriAZ = 'http://169.254.169.254/latest/meta-data/placement/availability-zone'
$Info = Invoke-RestMethod -Uri $UriInfo
$AvailZ = Invoke-RestMethod -Uri $UriAZ
$InsID = Invoke-RestMethod -Uri $UriInsID
# check if IMDSv1 is enabled
if (-not $SkipValidationCheck) {
$cmd = [PowerShell]::Create()
[void]$cmd.AddScript(@'
try {
$IMDSv1 = 'http://169.254.169.254/latest/dynamic/instance-identity/document'
(Invoke-RestMethod -Method Get -Uri $IMDSv1 -EA Stop) -as [bool]
}
catch {$false}
'@
)
$HasV1 = $cmd.Invoke() | foreach {$_}
$cmd.Dispose()
}
else {$HasV1 = 'Unknown (check skipped)'}
# Note: need to check in a different session, cause the $HeadTok is cached
# revert back the default params
$PSDefaultParameterValues.Clear()
# assemble the output object
$out = [PSCustomObject] @{
PSTypeName = 'Temporary.AWS.Credentials'
AccountID = $Info.AccountId
InstanceID = $InsID
Region = $AvailZ -replace '[a-h]$',$null
IAMRole = $IamRole
HasIMDSv1 = $HasV1
AccessKey = $AwsCreds.AccessKeyId
SecretKey = $AwsCreds.SecretAccessKey
TokenKey = $AwsCreds.Token
ExpiresAt = [DateTime]$AwsCreds.Expiration
}
# add a custom method that shows how much time is left until the token expires
$Method1 = {
param (
[ValidateSet('String','Span')] # <-- either [string] or [TimeSpan] output
$Format = 'String'
)
$Span = $this.ExpiresAt - (Get-Date)
if ($Format -eq 'String') {
$fmt = 'h\h\r\ m\m\i\n\ s\s\e\c'
$out = $Span.ToString($fmt)
}
elseif ($Format -eq 'Span') {$out = $Span}
Write-Output $out
}
$out | Add-Member -MemberType ScriptMethod -Name TokenExpiresIn -Value $Method1 -Force
# add a 2nd method to login to AWS using the properties of this object
# this requires the AWS module (either AWSPowerShell or AWS.Tools.Common)
$Method2 = {
Set-AWSCredential -AccessKey $this.AccessKey -SecretKey $this.SecretKey -SessionToken $this.TokenKey -ErrorAction Stop
Set-DefaultAWSRegion -Region $this.Region -ErrorAction Stop
}
$out | Add-Member -MemberType ScriptMethod -Name LoginAWS -Value $Method2 -Force
# finally show the output
Write-Output $out
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment