AWS Lambda for PowerShell Encrypted Environment Variables

Standard

Recently I was developing a AWS Lambda for PowerShell function to read a table from a Microsoft SQL on RDS Instance within a VPC. Using the .NET Core SQL client I need to provide credentials to access the database. For the password it should be stored encrypted at rest, never in plain text, and when passed to the function should be done so as a secure string. We can configure the function to use KMS for encryption at rest. This presents us with two challenges. How do I encrypt a AWS Lambda environment variable and how will my AWS Lambda for PowerShell function securely get the decrypted value in memory as a secure string when the Lambda function is invoked.

Encrypting an AWS Lambda Environment Variable

Utilizing the AWS Lambda console when configuring the function, under environment variable, select the option to “Enable helpers for encryption in transit”. You’ll need to provide what KMS key you’ll want to encrypt the value with. When we enter a value and select encrypt the console encrypts the value with KMS and stores the value as an encrypted base 64 text string.

 

Enter the unencrypted environment variable and click Encrypt


 

Save and refresh the console to see the value as a encrypted base 64 text string

 

Securely Decrypting Variables with AWS Lambda for PowerShell

So far this is easy however if we want to access the variable we have a problem. The console provides a “code” link for the Lambda function but our PowerShell function is executed in a .NET Core environment and the console displays the helper code as C# which isn’t going to work in my PowerShell function. Thankfully since PowerShell is just a layer of abstraction from C# I was able to create a PowerShell function to convert the encrypted variable to a secure string utilizing the .NET Core KMS client.

function ConvertFrom-KmsEncryptedString
{
<#
    .EXAMPLE
    $SecureString = ConvertFrom-KmsEncryptedString -EncryptedBase64String $env:password
    .PARAMETER EncryptedBase64String
    Encrypted base 64 string to be decrypted.
    .PARAMETER ToPlainText
    Default output is a secure string object, us this to output as a plain text string.
#>
param(
    [Parameter(Mandatory=$true)][string]$EncryptedBase64String,
    [switch]$ToPlainText = $false
)
begin {
    #KMS Client Object
    $KmsClient = New-Object Amazon.KeyManagementService.AmazonKeyManagementServiceClient
    #Convert encrypted base 64 text string to and encrypted bytes array. 
    $EncryptedBytes = [System.Convert]::FromBase64String($EncryptedBase64String)
    $utf8 = [System.Text.UTF8Encoding]::UTF8
    $SecureString = [System.Security.SecureString]::new()
}
process {
    foreach ($byte in $KmsClient.Decrypt($EncryptedBytes,$null)) {
        $SecureString.AppendChar($utf8.GetString($byte))
    }
    #Disposing the KMS client to release its resources.
    $kmsClient.Dispose()
    #Makes the secure string value immutable.
    $SecureString.MakeReadOnly()   
}
end {
    if ($ToPlainText.IsPresent -eq $true) {
        #Returns the plain text string of the encrypted environment variable when -PlainText switch is used.
        Return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecurestringToBstr($SecureString))
    }
    else {
        #Returns a secure string of the decrypted environment variable.
        Return $SecureString
    }
}
}

The function input will be the $env:password environment variable key containing the encrypted base 64 text string. The default output will be a secure string object unless you use the flag for output as plain text. Here’s an example of the function being used to pass a secure string to my other function which connects to the Microsoft SQL database.

$SecureString = ConvertFrom-KmsEncryptedString -EncryptedBase64String $env:password
$Datatable = Get-SqlToDataTable -Instance $env:instance -Database $env:database -Table $env:table -UserName $env:username -Password $SecureString

Conclusion

Utilizing this method our secure value is never stored as plain text. It’s passed to the Lambda execution environment as an encrypted base 64 text value. The ConvertFrom-KmsEncryptedString function uses the KMS client over HTTPS and loads the decrypted bytes into a secure string so it’s protected in memory. I experimented with lots of methods to do this as securely and easily as possible. One consideration is that if I used the KMS client async decrypt the value is returned as a unencrypted memory stream object in the response.I looked at using crypto streams but was overly complicated and had more overhead. Using the KMS client synchronous decrypt call and appending the bytes to the secure string gave me the desired results with the least amount of complexity.

Let me know if you have any recommendations or suggestions. Thank you!

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.