The ability to invoke Lamda functions via REST based web API is incredibly powerful. As the API gateway is relatively new functionality, the documentation surrounding REST requests and PowerShell is sparse with official documentation sometimes confusing and appearing to be contradictory.
The attached PowerShell routine can be used to make signed GET and POST requests to an API advertised Lambda script.
The script accepts a JSON payload as a text string. With a PUT, properties can be directly referenced within Lambda using directly using a call such as: “event['name']”
Currently there are quirks with using a JSON mapping template and GET requests to Lambda – a workaround can be read at: https://forums.aws.amazon.com/thread.jspa?messageID=663593&tstart=0#663593
The script also demonstrates the Signature version 4 signing process for AWS API Gateway usage.
########################
# SUPPORTING FUNCTIONS
<#
.Synopsis
Retrieves the current Universal time in required format for AWS
#>
function UniversalTime{
$curdate = Get-Date
$UniversalTime = ($curdate.ToUniversalTime().Year).ToString("0000") `
+ ($curdate.ToUniversalTime().Month).ToString("00") `
+ ($curdate.ToUniversalTime().Day).ToString("00") + "T" `
+ ($curdate.ToUniversalTime().Hour).ToString("00") `
+ ($curdate.ToUniversalTime().Minute).ToString("00") `
+ ($curdate.ToUniversalTime().Second).ToString("00") + "Z"
return $UniversalTime
}
<#
.Synopsis
Retrieves the current Universal time in short-date format used by AWS
#>
function ShortDate{
$curdate = Get-Date
$Shortdate = ($curdate.ToUniversalTime().Year).ToString("0000") `
+ ($curdate.ToUniversalTime().Month).ToString("00") `
+ ($curdate.ToUniversalTime().Day).ToString("00")
return $Shortdate
}
<#
.Synopsis
Retrieves an SHA hash of a string as required by AWS Signature 4
#>
function [string]sha($message) {
$sha256 = new-object -TypeName System.Security.Cryptography.SHA256Managed
$utf8 = new-object -TypeName System.Text.UTF8Encoding
$hash = [System.BitConverter]::ToString($sha256.ComputeHash($utf8.GetBytes($message)))
return $hash.replace('-','').toLower()
}
<#
.Synopsis
HMACSHA256 signing function used in the construction of a "Signature 4 " request
#>
function [byte[]]hmacSHA256([byte[]]$key, [string]$message) {
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = $key
return $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($message))
}
<#
.Synopsis
The AWS Signature version 4 creation routine
#>
function GetSignatureKey([String]$AWSAccessKey,[String]$shortdate,[String]$AWSRegion,[String]$AWSService){
$kSecret = [System.Text.Encoding]::UTF8.GetBytes("AWS4"+$AWSAccessKey)
$kDate = hmacSHA256 $kSecret $shortdate
$kRegion = hmacSHA256 $kDate $AWSRegion
$kService = hmacSHA256 $kRegion $AWSService
$kSigningKey = hmacSHA256 $kService "aws4_request"
return $kSigningKey
}
###########
# Main
# Used for constructing a POST request for the AWS API Gateway
<#
.Synopsis
Submits a request to the AWS API Gateway and retrieves the results
.Description
This function demonstrates using PowerShell to submit REST based requests to
Amazon's AWS API gateway using Signature 4 signing.
There are quirks with using GET requests, Mapping Templates and Lambda functions
through the API Gateway. A workaround can be read:
https://forums.aws.amazon.com/thread.jspa?messageID=663593&tstart=0#663593
POST requests bypass the need for mappins templates.
The example correctly shows the use of the AWS signature version 4.
.Example
Get-WebResponse -Method POST `
-EndpointURI "https://sacvo76l6b.execute-api.ap-northeast-1.amazonaws.com/prod/ExamplePostFunction" `
-AWSAccessID "MyAccessID" `
-AWSAccessKey "MySecRetAccEssKey" `
-Payload $ExamplePayload
.Example
Get-WebResponse -Method "GET" `
-EndpointURI "https://sacvo76l6b.execute-api.ap-northeast-1.amazonaws.com/prod/ExampleGetFunction?name=Bernie&address=Washington" `
-AWSAccessID "MyAccessID" `
-AWSAccessKey "MySecRetAccEssKey"
.Parameter EndpointURI
The Fully Qualified endpoint URL shown attached to a Lambda function
.Parameter AWSAccessID
The Access Key ID created for a user account under IAM / Security Credentials
.Parameter AWSAccessKey
The secret Access Key ID created for a user account under IAM / Security Credentials
.Parameter Payload
A text string representing the body or payload of the request - typically JSON
.Inputs
[string]
.OutPuts
[string]
.Notes
NAME: Get-WebResponse
AUTHOR: Laurie Rhodes
LASTEDIT: 13/02/2016
KEYWORDS:
.Link
Http://www.laurierhodes.info
#Requires -Version 4.5
#>
function Get-WebResponse{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,
Position = 0,
HelpMessage="The method of web request such as POST, GET")]
[string]
[ValidateNotNullOrEmpty()]
$Method,
[Parameter(Mandatory = $true,
Position = 1,
HelpMessage="the AWS endpoint URI to query")]
[string]
[ValidateNotNullOrEmpty()]
$EndpointURI,
[Parameter(Mandatory = $false,
Position = 2,
HelpMessage="The access ID of a User authorised to use the web service")]
[string]
[ValidateNotNullOrEmpty()]
$AWSAccessID,
[Parameter(Mandatory = $false,
Position = 3,
HelpMessage="The Secret AWS Access Key")]
[string]
[ValidateNotNullOrEmpty()]
$AWSAccessKey,
[Parameter(Mandatory = $false,
Position = 4,
HelpMessage="The Payload / Body of a POST request")]
[string]
#[ValidateNotNullOrEmpty()]
$Payload
) #end param
#eg endpoint https://sacvo76l6b.execute-api.ap-northeast-1.amazonaws.com/prod/ExampleFunction
$EndpointURI = $EndpointURI.replace("https://","")
# The AWSHost is the DNS name from the request which contains
# the personal Domain ID, AWS Region and AWS Service details
$AWShost = $EndpointURI.Substring(0,($EndpointURI.IndexOf("/")))
$hostarray = $AWShost.Split(".")
$AssignedDomainID = $hostarray[0]
$AWSService = $hostarray[1]
$AWSRegion = $hostarray[2]
#post requests have an empty string as a payload
if($Method -eq "GET"){
$Payload =""
}
#The canonical Request & any Request Query are determined from the remainder of the URI
if ($EndpointURI.Contains("?")){
#The URI contains a URI and Query
$URIParams = $EndpointURI.Substring(($EndpointURI.IndexOf("/")),($EndpointURI.Length -$EndpointURI.IndexOf("/") ))
$CanonicalURI = $URIParams.Substring(0,($URIParams.IndexOf("?") ))
$CanonicalQuery = ($URIParams.Substring(($URIParams.IndexOf("?")),($URIParams.Length -$URIParams.IndexOf("?") ))).Replace("?","")
}
else
{
#The URI does not contain a query string
$CanonicalURI = $EndpointURI.Substring(($EndpointURI.IndexOf("/")),($EndpointURI.Length -$EndpointURI.IndexOf("/") ))
}
$shortdate = Shortdate
$universaltime = UniversalTime
$fullHostname = "$($AssignedDomainID).$($AWSService).$($AWSRegion).amazonaws.com"
$URI = "https://$($AssignedDomainID).$($AWSService).$($AWSRegion).amazonaws.com$($CanonicalURI)"
$PayloadBytes = ([System.Text.Encoding]::UTF8.GetBytes($Payload)).Length
write-host "AWSAccessID = $($AWSAccessID)"
write-host "AWSAccessKey = $($AWSAccessKey)"
write-host "AssignedDomainID = $($AssignedDomainID)"
write-host "AWSService = $($AWSService)"
write-host "AWSRegion = $($AWSRegion)"
write-host "CanonicalURI = $($CanonicalURI)"
write-host "CanonicalQuery = $($CanonicalQuery)"
write-host "RequestMethod = $($Method)"
#Query String URI Safe
#http utility doesnt produce the results expected by AWS
$CanonicalQuery = $CanonicalQuery.Replace("=","%3D")
$CanonicalQuery = $CanonicalQuery.Replace("&","%26")
############################################
# Create the Canonical Request
#
#
$CanonicalRequest = ""
$CanonicalRequest = $Method +"`n"
$CanonicalRequest = $CanonicalRequest + $CanonicalURI +"`n"
if(!$CanonicalQuery){ #no query string
$CanonicalRequest = $CanonicalRequest + $CanonicalQuery +"`n"
}
else #query string exists
{
$CanonicalRequest = $CanonicalRequest + $CanonicalQuery +"=" +"`n"
}
#### Canonical Headers and values
#Different AWS Services have different Header requirements
# all have some common headers
$CanonicalHeaderHashTable = @{}
$CanonicalHeaderHashTable.Add("host",$fullHostname)
$CanonicalHeaderHashTable.Add("x-amz-date",$universaltime)
# Add additional headers as required
# API Gateway Service
if($AWSService -eq "execute-api"){
if($Method -eq "POST"){
write-host "POST Request received"
$CanonicalHeaderHashTable.Add("content-length",$($PayloadBytes))
$CanonicalHeaderHashTable.Add("content-type","application/json")
}
}
$cHeaderList = $CanonicalHeaderHashTable.GetEnumerator() | Sort Name| % {
[string]$newheader = ""
$newheader = ($_.Key) +":" + $_.Value
$newheader = $newheader.Trim()
$newheader = $newheader.Replace("`r","")
$CanonicalRequest = $CanonicalRequest + $newheader +"`n"
}
$CanonicalRequest = $CanonicalRequest +"`n"
#Each key from the Canonical Header HashTable needs to be listed
[System.Collections.ArrayList]$SignedHeadersArray = @()
$CanonicalHeaderHashTable.GetEnumerator() | Sort Name| % {
$null = $SignedHeadersArray.Add($_.Key)
}
$SignedHeadersList = ($SignedHeadersArray -join ";")
$CanonicalRequest = $CanonicalRequest + $SignedHeadersList +"`n"
$CanonicalRequest = $CanonicalRequest + $(sha $Payload)
##### Create the Signed String
# Add Algorithm
$Signedstring = "AWS4-HMAC-SHA256" +"`n"
# Add UTC Request Date
$Signedstring = $Signedstring +$universaltime + "`n"
# Add CredentialScope
$Signedstring = $Signedstring + "$($shortdate)/$($AWSRegion)/$($AWSService)/aws4_request" + "`n"
# Add Canonical Request Hash
$Signedstring = $Signedstring + $(sha $CanonicalRequest)# +"`n"
############################
# Sign the Created 'Signed String'
$kSigningKey = GetSignatureKey $AWSAccessKey $shortdate $AWSRegion $AWSService
# HexEncoded version is signature
$encSignedString = hmacSHA256 $kSigningKey $Signedstring
[string]$hexstring = [System.BitConverter]::ToString($encSignedString )
$signature = $hexstring.replace("-","")
$signature = $signature.ToLower()
############################
# Add Headers for dispatching web request
#
#
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Clear()
$CanonicalHeaderHashTable.GetEnumerator() | Sort Name| % {
if($_.Key -eq "host"){
# Host Key is derived from the domain name - it can't be set separately
}
else{
$headers.Add(($_.Key),($_.value))
}
}
# API Gateway Service
# The authorization key can't be signed but must still be added to theheader
if($AWSService -eq "execute-api"){
$headers.Add("authorization", "AWS4-HMAC-SHA256 Credential=$($AWSAccessID)/$($shortdate)/$($AWSRegion)/$($AWSService)/aws4_request,SignedHeaders=$($SignedHeadersList),Signature=$($signature)")
}
if($Method -eq "POST"){
$Result = Invoke-RestMethod -Uri $URI -Body $Payload -Method $Method -Headers $Headers
}
if($Method -eq "GET"){
write-host $CanonicalQuery
write-host "$($URI)?$($CanonicalQuery)"
$Result = Invoke-RestMethod -Uri ("$($URI)?$($CanonicalQuery)") -Method $Method -Headers $Headers
}
write-host ""
return $Result
} #end Get-Webresponse
- Log in to post comments