Last active
April 24, 2024 08:46
-
-
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
This file contains 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
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