Share AWS Vault session with Bash (WSL)

I'm on Windows and I use AWS Vault to connect to AWS using an MFA token. It works wonderfully, unless you need to execute some Bash scripts. I love using Bash on Windows, as WSL makes it really easy to write my scripts. But, alas, the AWS environment variables set by AWS Vault are not picked up by WSL / Bash. Let's see what we can do to fix that.

First of all, I could not find a way to share all of the current environment variables with WSL / Bash. If you have a way of sharing AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, AWS_REGION, AWS_SECRET_ACCESS_KEY, AWS_SECURITY_TOKEN, AWS_SESSION_EXPIRATION and AWS_SESSION_TOKEN, that were set by AWS Vault, with WSL, please let me know in the comments!

Ps: don't come knocking on my door with Git Bash!

Next best thing...

So, what can we do? Well... when I launch powershell.exe, it has all the environment variables we need. You can check this by doing:

aws-vault exec my_account -- powershell.exe -Command "Get-ChildItem -Path Env: | Where-Object { `$_.Name -like 'AWS_*' }"

🤔 We could convert those nice environment variables to a big string like this:

$awsVars = Get-ChildItem -Path Env: | Where-Object { $_.Name -like 'AWS_*' }
$awsVarsString = ($awsVars | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ' '

This will result in something like AWS_ACCESS_KEY_ID="..." AWS_DEFAULT_REGION="...".
🤔 We could feed that string to start Bash like this:

C:\Windows\Sysnative\bash.exe -c "$awsVarsString  ./my_script.sh"

And that will launch the script with the right AWS context.

Launcher script: aws-vault-bash.ps1

So what if we create a special PowerShell launcher script, which does the following:

  1. When launched, it determines which account and what Bash script should be executed.
  2. Now it calls AWS Vault with the right account, and feeds the script to a second Powershell script
  3. This PowerShell script reads the AWS environment variables and feeds them to Bash and launches the Bash script.

Should work right? Well... let's not use a second script, but reuse the first script.

If we take those rules, we'll get the following script called aws-vault-bash.ps1:

$ErrorActionPreference = "Stop"

$Action = $args[0]
$Account = $args[1]
$Script = $args[2]

if ($Action -eq 'start') {

    # Take all AWS vars and put then in one big string
    $awsVars = Get-ChildItem -Path Env: | Where-Object { $_.Name -like 'AWS_*' }
    $awsVarsString = ($awsVars | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ' '

    # The second $Account parameter contains our path to Bash now
    $bashExe = $Account
    Invoke-Expression "$bashExe -c ""$awsVarsString $Script"""

    exit
}

# Make bash understand a local file
if (Test-Path -Path $Script -PathType Leaf) {
    $Script = "./$Script"
}
$Script = $Script.replace("\", "/")

# Resolve the path to the Bash executable
$bashExe = (Get-Command bash).Source.replace("system32","Sysnative")

# Resolve the path to this script
$thisScriptLocation = $MyInvocation.MyCommand.Path

Invoke-Expression "aws-vault exec $Account -- powershell.exe ""$thisScriptLocation"" start ""$bashExe"" $Script"

We can now happily invoke Bash scripts with AWS Vault using our Powershell script:

# execute local file:
.\aws-vault-bash.ps1 exec my_account extract.sh

# execute sub dir file:
.\aws-vault-bash.ps1 exec my_account .\sub\extract.sh

Further improvements

Let's improve the script even further:

  • Let's make it possible for the $Script to capture the rest of the parameters.
  • Let's add some documentation.

Last, but not least, let's add some documentation to our aws-vault-bash.ps1, so we can invoke some help:

<#
.SYNOPSIS
This script uses AWS Vault to execute a Bash script with AWS environment variables.

.DESCRIPTION
More details on what the script does can be found here: https://keestalkstech.com/2023/02/share-aws-vault-session-with-bash-wsl/

.EXAMPLE
.\aws-vault-bash.ps1 exec my_aws_account my_script.sh
Executes the Bash script "my_script.sh" with the AWS account "my_aws_account".

.NOTES
Author: Kees C. Bakker (KeesTalksTech)

.LINK
Blog: https://keestalkstech.com/2023/02/share-aws-vault-session-with-bash-wsl/
#>
param (
    [Parameter(HelpMessage="Please use 'exec' to execute the script using AWS Vault. Start is used internally.")]
    [ValidateSet("exec", "start")]
    [string]$Action = "exec",
    [Parameter(Mandatory=$true, HelpMessage="The AWS account that is used to execute the script.")]
    [string]$Account,
    [Parameter(
        HelpMessage="The script that should be executed. May include parameters. Leave empty for just bash.",
        ValueFromRemainingArguments=$True
    )]
    [string[]]$Script = @("bash")
)

$ErrorActionPreference = "Stop"

if ($Action -eq 'start') {

    # Take all AWS vars and put then in one big string
    $awsVars = Get-ChildItem -Path Env: | Where-Object { $_.Name -like 'AWS_*' }
    $awsVarsString = ($awsVars | ForEach-Object { "$($_.Name)=$($_.Value)" }) -join ' '

    # The second $Account parameter contains our path to Bash now
    $bashExe = $Account
    $script = $Script -join " "

    Invoke-Expression "$bashExe -c ""$awsVarsString $script"""

    exit
}

# Make bash understand a local file
if (Test-Path -Path $Script[0] -PathType Leaf) {
    $Script[0] = "./" + $Script[0]
}
$Script[0] = $Script[0].replace("\", "/")
$script = $Script -join " "

# Resolve the path to the Bash executable
$bashExe = (Get-Command bash).Source.replace("system32","Sysnative")

# Resolve the path to this script
$thisScriptLocation = $MyInvocation.MyCommand.Path

Invoke-Expression "aws-vault exec $Account -- powershell.exe ""$thisScriptLocation"" start ""$bashExe"" $script"

Final thoughts

Let's just say: I'm happy that it works. Am I happy that I need to jump to these hoops? Not really... well, at least the PowerShell script gives me a feature I don't currently have and kind of lack: executing Bash script on Windows against AWS.

Changelog

  • 2023-02-20: Added support for remaining arguments for script.
expand_less