What's in the bag? Behind the scenes at vBrownBag.com
- What’s in the bag? Behind the scenes at vBrownBag.com
- Part 2 of “What’s in the bag?” Behind the scenes at vBrownBag.com
- Part 3 of “What’s in the bag?” Behind the scenes at vBrownBag.com
- Part 4 of “What’s in the bag?” Behind the scenes at vBrownBag.com
- Part 5 of “What’s in the bag?” Behind the scenes at vBrownBag.com
- Part 6 of “What’s in the bag?” Behind the scenes at vBrownBag.com
- Automating the vBrownBag with AWS Serverless
Part 4 of this series covered orchestration of the AWS Lambda function using AWS Step Functions & EventBridge. In this post, I’d like to cover Google OAuth with PSAuthClient, decrypting AWS Lambda environment variables, and more.
Google OAuth with PSAuthClient
Some quick introductory info: there are two ways of authenticating with the YouTube data API. The first is through an API key created through https://console.cloud.google.com. It’s an alpha-numeric string that can be appended to REST API calls against endpoints such as https://www.googleapis.com/youtube/v3/videos. However, the API key doesn’t allow privileged access to an account, and for that you need to use OAuth.
There’s a ton of helpful documentation on Google OAuth, but my goal is not to repeat all of that here, nor talk about creating an app in Google’s Cloud console. Rather, I’d like to talk about how I used PSAuthClient to meet my OAuth needs using PowerShell. The two most important pieces of information needed to get started are the client_id and client_secret. The first uniquely identifies the Google app, and the second is a client secret string.
After installing PSAuthClient using Install-Module, I used Invoke-OAuth2AuthorizationEndpoint to make the initial OAuth request in order to begin building my $authorization object. The customParameters hashtable is important as I need to be able to refresh the OAuth token without requiring human interaction every time, since this is running in a Lambda function. Google OAuth also needs the client_secret in the $authorization object, so I add it to the object. Finally, I use the Invoke-OAuth2TokenEndpoint cmdlet to get the $token object by splatting the $authorization object (passing a collection of parameter values to a command as a unit).
$authorization = Invoke-OAuth2AuthorizationEndpoint -uri $uri -client_id $client_id -redirect_uri $redirect_uri -response_type "code" -scope $scope -customParameters @{ access_type = "offline"; include_granted_scopes = "true" }
$authorization.Add("client_secret", $client_secret)
$token = Invoke-OAuth2TokenEndpoint -uri $uri @authorization$token is a PSCustom object that contains access_token and refresh_token strings. The access_token expires in about an hour, which means that I need to get a new one whenever I’m making an OAuth request. This is accomplished through Invoke-OAuth2TokenEndpoint.
$token = Invoke-OAuth2TokenEndpoint -uri $uri -refresh_token $refresh_token -client_id $client_id -client_secret $client_secretNow I can begin building a new $youtubeService object for uploading new videos, assigning them to playlists, searching, etc.
$googleCredential = [Google.Apis.Auth.OAuth2.GoogleCredential]::FromAccessToken($token.access_token).CreateScoped($token.scope)
# Create a new YouTube service
$youtubeService = New-Object Google.Apis.YouTube.v3.YouTubeService(
New-Object Google.Apis.Services.BaseClientService+Initializer -Property @{
HttpClientInitializer = $googleCredential
ApplicationName = "YouTube Uploader"
}
)I’d like to note for the purposes of clarity, that I had to use Invoke-OAuth2AuthorizationEndpoint from my desktop, as it uses WebView2 to handle web interaction/approving the credentials. PSAuthClient is a PowerShell Core & Desk module which throws errors when I require the entire module in my Lambda script. However, Invoke-OAuth2TokenEndpoint is a PSCore cmdlet, so I’m able to include the .ps1 file directly in my Lambda zip file.
Encrypted environment variables in Lambda

As you can see above, I’m storing a lot of sensitive information in variables. I decided during the early stages of working on Meatgrinder to use Lambda environment variables instead of storing those values directly in the script, or a secrets file, etc. The variables & their values are defined in the AWS Lambda configuration panel. When the Lambda function runs, those variables are available to the script with the $env prefix, so the value of oauth_client_id is available as $env:oauth_client_id, and so on. For the sensitive variables, I chose to create an AWS KMS symmetric key to encrypt & decrypt my environment variables, and require the AWS.Tools.KeyManagementService module in my script, which includes the Invoke-KMSDecrypt cmdlet. Now you might be thinking, “oh, I can just use that cmdlet to directly decrypt my variable!” and you’d be wrong just like I was. There’s actually a few extra steps required.
First, the encryption context needs to be defined, and in this case, it’s the environment variable AWS_LAMBDA_FUNCTION_NAME. (The encryption context is defined in the execution role policy that allows the function the kms:decrypt action using the resource arn of the KMS key.) Then the ciphertext value stored in the encrypted environment variable needs to be converted from a base64-encoded string to a byte array. The context and the encrypted byte array are passed to the Invoke-KMSDecrypt cmdlet with the -Select Plaintext parameter which asks for the plaintext value instead of the whole service response (which is the default), and a decrypted byte array is returned. That byte array is converted to a string which is the decrypted plaintext value. That’s a lot, right? If you were setting up the encryption configuration for environment variables chose the “enable helpers” checkbox, AWS would present you with popup containing a .NET 8 snippet that does all of this, but it doesn’t show it in PowerShell. So, here’s the PowerShell equivalent:
<#
.SYNOPSIS
Helper function to decrypt a ciphertext using the AWS Key Management Service (KMS).
.PARAMETER ciphertext
The ciphertext to decrypt.
.OUTPUTS
Returns the plain text
.EXAMPLE
decrypt -ciphertext "ciphertext"
#>
function decrypt {
param (
[Parameter(Mandatory = $true)]
[string]$ciphertext
)
$encryptionContext = @{"LambdaFunctionName" = $env:AWS_LAMBDA_FUNCTION_NAME }
$cipherbytes = [System.Convert]::FromBase64String($ciphertext)
$response = Invoke-KMSDecrypt -CiphertextBlob $cipherbytes -EncryptionContext $encryptionContext -Select Plaintext
return [System.Text.Encoding]::UTF8.GetString($response.ToArray())
}Well, if you read this far, congratulations! Or, hi Mom, whichever the case may be. 🙂 In the next post, I’d like to cover WordPress REST API and RSS XML updates. Cheers!