Using AWS CLI with Powershell's SecretStore Module to protect your Access keys
Tags: aws, powershell, security, cli
Published: 2023-02-20 04:31:52

Secrets stores are awesome! 🤩
They are those "Secrets Management Systems" where you can store the secret (like a DB password) as key/value pair with the key is a specific name, "POSTGRESQL_SERVER_PASSWORD" for example and its value is the actual password. Some secrets stores even encrypts those secrets at rest!
I think by now you are starting to see why they are super useful! As your app's source code won't contain your credentials any more. Just you make a call to the Secrets Store and if your app has enough permissions, then it will receive the secret it requested!
How cool is that! 😎

CSPs have also their version of **"managed"** Secrets Stores. On AWS it's "**Secrets Manager**" and on Azure it's "**Key Vault**". And they are extremely useful if you are developing a cloud native app so all you have to do is just tinkering with some permissions and your app is leaks-free (hopefully 😅)

Now after that we saw what are the secrets stores and why we should use them, most likely you are thinking "secrets stores are great! but what they have to do with the AWS CLI?".
My problem with AWS CLI that it is (by default) uses AWS Access keys that you generate from AWS IAM. This is a very dangerous credential specially if it's linked to a user with high privileges inside the AWS account! And not just that, it stores in plain text too! 😮
The Access key is stored as plain text in either the AWS CLI configuration file located at `~/.aws/credentials` or if you prefer to not use the config file (or the command `aws configure`) and use environment variables instead (*AWS_ACCESS_KEY_ID* and *AWS_SECRET_ACCESS_KEY*), again you will store them in plain text probably in your shell's "profile" file (for bash, it could be the `.bash_profile`or `.bashrc` file and for powershell it could be your profile `$PROFILE`). But I have a solution for that...

I work mainly on a Windows machine and I use Powershell a lot. Powershell can create a "SecretStore" for your local machine and you can use it via the "SecretManagent" module. So the idea is that I'll use the Powershell's SecretStore in a similar manner to the dedicated secrets stores described earlier to store the *AWS_ACCESS_KEY_ID* and *AWS_SECRET_ACCESS_KEY* credentials to be used by the AWS CLI. And as a bonus, we will encrypt them too! All that is done locally using only Powershell. Piece of cake 😁



![Process flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duo112ys4g2pxzcj1012.png)

## 1- Powershell modules installation:
The following instructions are executed on a Windows machine and Powershell v7.3.2. Some prior knowledge of Powershell will be very helpful.

### The SecretsStore module:
```Powershell
Install-Module -Name Microsoft.PowerShell.SecretStore
```
[Module page on PSGallery](https://www.powershellgallery.com/packages/Microsoft.PowerShell.SecretStore/1.0.6)

### The SecretManagement module:
```Powershell
Install-Module -Name Microsoft.PowerShell.SecretManagement
```
[Module page on PSGallery](https://www.powershellgallery.com/packages/Microsoft.PowerShell.SecretManagement/1.1.2)

## 2- Setup the SecretStore:
The first thing that we want to do after its installation is to configure it. The most important parameters in this setup are the `-Password` and `-PasswordTimeout`. Whenever you use the SecretStore, it will ask you for a password and the store will stay unlocked during your Powershell session for the timeout duration. So for my use case, I'll set the timeout to 1 sec so it will lock after that we receive the needed secret(s) and let the cmdlet read the password from the terminal as as SecureString. This way when you enter the password, it will appear as stars (******) and won't appear in your host history.
```Powershell
Set-SecretStoreConfiguration -PasswordTimeout 1
```

## 3- Register vaults:
Vaults are a logical containers for your secrets. You can have as many as you want. You can think of them as folders for your secrets for organizational purposes. In my case, I'll create 2 vaults. My main (default) vault ***MyVault*** and another one for AWS secrets ***AWSVault***.
```Powershell
Register-SecretVault -Name MyVault -ModuleName Microsoft.Powershell.SecretStore -DefaultVault
Register-SecretVault -Name AWSVault -ModuleName Microsoft.PowerShell.SecretStore

Get-SecretVault    # To list the vaults we have created.
```

![Get-SecretVault](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfegd8zky2gbvkjyfaib.png)

## 4- Generate a random encryption key and store it the default vault:

An encryption key is necessary as we will store the credentials encrypted. Powershell doesn't have a native way to do that so we will have to be creative! The encryption is done using `ConvertFrom-SecureString` cmdlet as we will see and the key must be either 128, 196 or 256 bits. We will use a 256 bits key generating an 16 characters long random string (16 * 2 * 8 = 256) (powershell treats characters as 2 byte long in this code snippet. That explains the 2 in the middle)
```powershell
$chars = ("a".."z") + ("A".."Z") + (0..9) + "@#%[]{}=+"    # CharArray for the permitted characters for the key.
$mykey = ""                                                # Initiate an empty key string.
(1..16) | ForEach-Object { $myKey += $chars | Get-Random } # Looping 16 times and each time appends a random character to the key.
```

## 5- Storing the encryption key in the default vault:
Now that we have the encryption key, we will store it in *MyVault* to be used later in encrypting and decrypting the credentials.
```powershell
Set-Secret -Vault MyVault -Name AWS_ENCRYPTION_KEY -Secret $myKey
```

## 6- Parsing the AWS credentials from a JSON file, encrypt them and store them in the AWS vault:
I don't want to write my credentials as plain text in the shell. It will appear in the shell's history and I'll have to delete it manually. That's a lot of work for me 😂.
One way to avoid that is to write my credential in a disposable JSON file and parse it in powershell then store the parsed credential in the *AWSVault*
```powershell
$myCredentials = Get-Content .\MyCredentials.json | ConvertFrom-Json -AsHashtable

$awsAccessKeyIDSecure = ConvertTo-SecureString -String ($myCredentials.AWS_ACCESS_KEY_ID) -AsPlainText
$awsSecretAccessKey = ConvertTo-SecureString -String ($myCredentials.AWS_SECRET_ACCESS_KEY) -AsPlainText
```

The reason that we are converting the credentials to *SecureString* is that this is the only input acceptable to the *ConvertFrom-SecureString* to encrypt them.
```powershell
$encryptionKey = Get-Secret -Vault MyVault -Name AWS_ENCRYPTION_KEY # secure string

$awsAccessKeyIDEncrypted = ConvertFrom-SecureString $awsAccessKeyIDSecure -SecureKey $encryptionKey   # Normal string but encrypted
$awsSecretAccessKeyEncrypted = ConvertFrom-SecureString $awsSecretAccessKey -SecureKey $encryptionKey
```
Now, storing them
```powershell
Set-Secret -Vault AWSVault -Name AWS_ACCESS_KEY_ID -Secret $awsAccessKeyIDEncrypted
Set-Secret -Vault AWSVault -Name AWS_SECRET_ACCESS_KEY -Secret $awsSecretAccessKeyEncrypted
```
## 7- Retrieving the AWS credentials from the vault:
As for retrieving the credentials, first we will have to get them from the *AWSVault* then decrypt them.
```powershell 
$encryptionKey = Get-Secret -Vault MyVault -Name AWS_ENCRYPTION_KEY # secure string

$awsAccessKeyIDVault = Get-Secret -Vault AWSVault -Name AWS_ACCESS_KEY_ID -AsPlainText    # We need them as plain text to be decrypted by ConverTo-SecureString.
$awsSecretAccessKeyVault = Get-Secret -Vault AWSVault -Name AWS_SECRET_ACCESS_KEY -AsPlainText

$awsAccessKeyIDSecure = ConvertTo-SecureString $awsAccessKeyIDVault -SecureKey $encryptionKey                   # Secure string but decrypted.
$awsAccessKeyIDDecrypted = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($awsAccessKeyIDSecure)  # Allocates the secure string to an unmanaged Binary string in memory.
$awsAccessKeyIDDecrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($awsAccessKeyIDDecrypted)  # Allocates the unmanged binary string to a managed string in memory.

$awsSecretAccessKeySecure = ConvertTo-SecureString $awsSecretAccessKeyVault -SecureKey $encryptionKey
$awsSecretAccessKeyDecrypted = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($awsSecretAccessKeySecure)
$awsSecretAccessKeyDecrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($awsSecretAccessKeyDecrypted)
# Sanity check
Write-Host "AWS_ACCESS_KEY_ID: $awsAccessKeyIDDecrypted`nAWS_SECRET_ACCESS_KEY: $awsSecretAccessKeyDecrypted"    # You should get the original credentials from the JSON file.
```

## 8- Tying it all up:
So in order to link what have done with the AWS CLI, we will create two functions in the *$PROFILE* file for powershell to retrieve the AWS key Id and the AWS secret key and store them in two environment variables (*AWS_ACCESS_KEY_ID* and *AWS_SECRET_ACCESS_KEY*) so that they will be retrieved from the *AWSVault* and won't be hardcoded in either the *$PROFILE* nor the aws cli credentials file. Also they will be available only for the current session and only if I've entered the SecretVault master password that we set in the beginning.
If you use VSCode you can type in `code $PROFILE` or you can just open it in any text editor.
```powershell
# $PROFILE
function Get-AWSAccessKeyID {
    $encryptionKey = Get-Secret -Vault MyVault -Name AWS_ENCRYPTION_KEY
    $awsAccessKeyIDVault = Get-Secret -Vault AWSVault -Name AWS_ACCESS_KEY_ID -AsPlainText

    $awsAccessKeyIDSecure = ConvertTo-SecureString $awsAccessKeyIDVault -SecureKey $encryptionKey
    $awsAccessKeyIDDecrypted = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($awsAccessKeyIDSecure)
    $awsAccessKeyIDDecrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($awsAccessKeyIDDecrypted)
    return $awsAccessKeyIDDecrypted
}

function Get-AWSSecretAccessKey {
    $encryptionKey = Get-Secret -Vault MyVault -Name AWS_ENCRYPTION_KEY
    $awsSecretAccessKeyVault = Get-Secret -Vault AWSVault -Name AWS_SECRET_ACCESS_KEY -AsPlainText

    $awsSecretAccessKeySecure = ConvertTo-SecureString $awsSecretAccessKeyVault -SecureKey $encryptionKey
    $awsSecretAccessKeyDecrypted = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($awsSecretAccessKeySecure)
    $awsSecretAccessKeyDecrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($awsSecretAccessKeyDecrypted)
    return $awsSecretAccessKeyDecrypted
}

$ENV:AWS_ACCESS_KEY_ID = Get-AWSAccessKeyID
$ENV:AWS_SECRET_ACCESS_KEY = Get-AWSSecretAccessKey
```

For me, I've used the following JSON file
```json
{
  "AWS_ACCESS_KEY_ID": "myAwsAccessKeyId",
  "AWS_SECRET_ACCESS_KEY": "myAwsSecretKey"
}
```
Don't forget to delete it after you parse it into powershell. If you don't, then you are left out with your credentials written in plain text which will defeat the purpose of this whole post 😉.

And here is how it looks like after you add the previous functions to the *$PROFILE* and query the configuration of the AWS CLI using `aws configure list` command. (don't forget to close your terminal and reopen it after you edit the *$PROFILE* file)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0v58lw08ja4i4ylx34d.gif)
The credentials are redacted as you can see but the last four letters matches the ones I've used in the JSON file and their type is *env*. So no credentials in the history, no hardcoded plain-text stored credentials anywhere on my drive and they are stored encrypted! Mission complete! 🥳🎉🎊✨

A little warning though, I use this in my local machine and remember you are storing the credentials in environment variables that could be easily retrieved if someone knows where to look. 😉
In my next post, I'll show a far superior and more secure method to use the AWS CLI locally via SSO (single sign on) like what Azure CLI does. 

I've have all the code used in this post available in this [repo](https://github.com/FadyGrAb/TechNuggets/tree/main/04-using-Microsoft-SecretStore)

That's all folks! Stay secure!