Skip to content

Instantly share code, notes, and snippets.

@gitfvb
Created April 13, 2018 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gitfvb/6f5c668545a2551c50ec62ced2b9566a to your computer and use it in GitHub Desktop.
Save gitfvb/6f5c668545a2551c50ec62ced2b9566a to your computer and use it in GitHub Desktop.
S3 on profitbricks via powershell (work in progress, but works): Download, Upload, ListBuckets and ListFilesInBucket
<#########################
LINKS
#########################>
<#
resource: https://devops.profitbricks.com/api/s3/
https://gist.github.com/chrismdp/6c6b6c825b07f680e710
https://gist.github.com/tabolario/93f24c6feefe353e14bd
https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
http://czak.pl/2015/09/15/s3-rest-api-with-curl.html
#>
# CHANGE THE OPERATION YOU WOULD LIKE TO DO!!!
$operation = "UPLOADFILE" # LISTBUCKETS, LISTFILES, DOWNLOADFILE, UPLOADFILE
<#########################
SETTINGS
already prepared for profitbricks
#########################>
$accessKey = "accesskey" # ENTER YOUR ACCESS KEY
$secretKey = "secretkey" # ENTER YOUR SECRET KEY
$region = "s3-de-central"
$service = "s3"
[System.Uri]$endpoint = "https://s3-de-central.profitbricks.com/"
<#########################
OPERATION SETTINGS
#########################>
# Default values
$verb = "GET"
$fileName = ""
$contentType = "text/plain"
$contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" #empty string
switch ($operation) {
"LISTBUCKETS" {
$bucket = ""
break
}
"LISTFILES" {
$bucket = "publish"
break
}
"DOWNLOADFILE" {
$bucket = "publish"
$contentType = "application/zip"
$fileName = "test.zip"
$targetFile = "C:\test.zip"
break
}
"UPLOADFILE" {
# TODO implement MD5 checksum
# TODO check server side encryption
# TODO multipart sometime...
<#
https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
To ensure that data is not corrupted traversing the network, use the Content-MD5 header. When you use this header,
Amazon S3 checks the object against the provided MD5 value and, if they do not match, returns an error. Additionally,
you can calculate the MD5 while putting an object to Amazon S3 and compare the returned ETag to the calculated MD5 value.
find out content type https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Function-to-6429566c
#>
$verb = "PUT"
$bucket = "publish"
$contentType = "application/zip"
$sourceFile = "test.zip"
$fileName = [System.IO.Path]::GetFileName($sourceFile)
$contentHash = (( Get-FileHash $sourceFile -Algorithm SHA256 ).Hash ).ToLower()
break
}
}
<#########################
FUNCTIONS
#########################>
function getSHA256($data) {
$hash = [System.Security.Cryptography.SHA256]::Create()
$array = $hash.ComputeHash( [System.Text.Encoding]::UTF8.GetBytes($data) )
return $array
}
# inspired by https://gallery.technet.microsoft.com/scriptcenter/Get-StringHash-aa843f71
function HmacSHA256($data, $key) {
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = $key
$sign = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($data))
return $sign
}
# transform bytes into hexadecimal string (e.g. for hash values)
function getStringFromByte($byteArray) {
$stringBuilder = ""
$byteArray | ForEach { $stringBuilder += $_.ToString("x2") }
return $stringBuilder
}
function getSignatureKey([String] $key, [String] $dateStamp, [String] $regionName, [String] $serviceName) {
$kSecret = [Text.Encoding]::UTF8.GetBytes("AWS4$($key)")
$kDate = HmacSHA256 -data $dateStamp -key $kSecret
$kRegion = HmacSHA256 -data $regionName -key $kDate
$kService = HmacSHA256 -data $serviceName -key $kRegion
$kSigning = HmacSHA256 -data "aws4_request" -key $kService
return $kSigning # return of signing key as byte array
}
<#########################
PREPARATION
#########################>
$currentDate = Get-Date
$date = $currentDate.ToUniversalTime().ToString("yyyyMMddTHHmmssZ")
$dateStamp = $currentDate.ToUniversalTime().ToString("yyyyMMdd")
$scope = "$( $dateStamp )/$( $region )/$( $service )/aws4_request"
# Allow only newer security protocols
# hints: https://www.frankysweb.de/powershell-es-konnte-kein-geschuetzter-ssltls-kanal-erstellt-werden/
$AllProtocols = [System.Net.SecurityProtocolType]'Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
# add bucket and dot if it is used
[System.Uri]$endpoint = "https://$( $bucket )$( if ($bucket -ne '') { '.' } )s3-de-central.profitbricks.com/$( $fileName )"
<#########################
CREATE THE CANONICAL REQUEST
#########################>
$canonicalRequestPlain = "$( $verb )`n/$( $fileName )`n`ncontent-type:$( $contentType )`nhost:$( $endpoint.Host )`nx-amz-content-sha256:$( $contentHash )`nx-amz-date:$( $date )`n`ncontent-type;host;x-amz-content-sha256;x-amz-date`n$( $contentHash )"
$canonicalRequestByte = getSHA256 -data $canonicalRequestPlain
$canonicalRequestHash = getStringFromByte -byteArray $canonicalRequestByte
<#########################
CREATE THE STRING TO SIGN
#########################>
$stringToSign = "AWS4-HMAC-SHA256`n$( $date )`n$( $scope )`n$( $canonicalRequestHash )"
<#########################
CREATE THE SIGNATURE KEY
#########################>
$sign = getSignatureKey -key $secretKey -dateStamp $dateStamp -regionName $region -serviceName $service
<#########################
CREATE THE SIGNATURE
Combines "String to sign" with the "Signature Key"
#########################>
$signatureByte = HmacSHA256 -data $stringToSign -key $sign
$signatureHash = getStringFromByte -byteArray $signatureByte
<#########################
GENERATE THE CALL
#########################>
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Host", $endpoint.Host)
$headers.Add("Content-Type", $contentType)
$headers.Add("x-amz-content-sha256", $contentHash)
$headers.Add("x-amz-date", $date)
$headers.Add("Authorization", "AWS4-HMAC-SHA256 Credential=$($accessKey)/$($scope),SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date,Signature=$( $signatureHash )")
switch ($operation) {
"DOWNLOADFILE" {
# 20 seconds for 20MB with invoke-restmethod
#$result = Invoke-RestMethod -uri $endpoint -Method $verb -Headers $headers -Verbose
# Done in 20 seconds too
$ProgressPreference = 'SilentlyContinue'
Measure-Command {
$wc = New-Object System.Net.WebClient
$headers.Keys | ForEach { $wc.Headers.Add($_, $headers.Item($_)) }
$wc.DownloadFile($endpoint, $targetFile)
} | select TotalSeconds
break
}
"UPLOADFILE" {
$ProgressPreference = 'SilentlyContinue'
Measure-Command {
Invoke-RestMethod -uri $endpoint -Method $verb -Headers $headers -InFile $sourceFile
} | select TotalSeconds
break
}
default {
$result = Invoke-RestMethod -uri $endpoint -Method $verb -Headers $headers -Verbose
break
}
}
@JohnCos247
Copy link

Awesome gist! Really appreciate the effort on this.

To anyone using this who is experiencing the error The format of value 'AWS4-HMAC-SHA256... is invalid.. You can do one of two things.

Either drop this line on the top of your script
$PSDefaultParameterValues['Invoke-RestMethod:SkipHeaderValidation'] = $true

Or add the following flag to all Invoke-RestMethod calls.

-RestMethod:SkipHeaderValidation

See this issue for more information.

@gitfvb
Copy link
Author

gitfvb commented Feb 12, 2021

Thanks @JohnCos247, really appreciate your input

@JohnCos247
Copy link

Sure thing @gitfvb. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment