From df112de4c41e8452d5e2e8030af83ead8c04ddab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kelvin=20Tegelaar=20=F0=9F=92=BB=20Lime=20Networks?= Date: Tue, 19 May 2020 11:52:51 +0200 Subject: [PATCH] First commit. --- Azglue.ps1 | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 ++++++++++ 2 files changed, 81 insertions(+) create mode 100644 Azglue.ps1 create mode 100644 README.md diff --git a/Azglue.ps1 b/Azglue.ps1 new file mode 100644 index 0000000..876620a --- /dev/null +++ b/Azglue.ps1 @@ -0,0 +1,69 @@ +using namespace System.Net +param($Request, $TriggerMetadata) +#Check if AZapiKey is correct +if ($request.Headers.'x-api-key' -eq $ENV:AzAPIKey) { + #Comparing the client IP to the Organization list, and checking if it exists. + $ClientIP = ($request.headers.'X-Forwarded-For' -split ':')[0] + $CompareList = import-csv "AzGlueForwarder\OrgList.csv" -delimiter "," + $AllowedOrgs = $comparelist | where-object { $_.ip -eq $ClientIP } + if (!$AllowedOrgs) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + headers = @{'content-type' = 'application\json' } + StatusCode = [httpstatuscode]::OK + Body = @{"Error" = "401 - No match found in allowed list" } | convertto-json + }) + exit 1 + } + + #Sending request to ITGlue + #$resource = $request.params.path -replace "AzGlueForwarder/", "" + $resource = $request.url -replace "https://$($ENV:WEBSITE_HOSTNAME)/API", "" + #Replace x-api-key with actual key + $ITGHeaders = @{ + "x-api-key" = $ENV:ITGlueAPIKey + } + $Method = $($Request.method) + $ITGBody = $($Request.body) + #write-host ($AllowedOrgs | out-string) + $SuccessfullQuery = $false + $attempt = 3 + while ($attempt -gt 0 -and -not $SuccessfullQuery) { + try { + $ITGlueRequest = Invoke-RestMethod -Method $Method -ContentType "application/vnd.api+json" -Uri "$($ENV:ITGlueURI)/$resource" -Body $ITGBody -Headers $ITGHeaders + $SuccessfullQuery = $true + } + catch { + $ITGlueRequest = @{'Errorcode' = $_.Exception.Response.StatusCode.value__ } + $rand = get-random -Minimum 0 -Maximum 10 + start-sleep $rand + $attempt-- + if ($attempt -eq 0) { $ITGlueRequest = @{'Errorcode' = "Error code $($_.Exception.Response.StatusCode.value__) - Made 3 attempts and upload failed. $($_.Exception.Message) " } } + } + } + + #Checking if we can strip the data that does not belong to this client. + #Important so passwords/items can only be retrieved belonging to this organisation. + #Can't do it for all requests, such as get-organisation, but for senstive data it works perfectly. :) + + if ($($ITGlueRequest.data.attributes.'organization-id')) { + write-host ($AllowedOrgs.ITGlueOrgID) + $ITGlueRequest.data = $ITGlueRequest.data | where-object { $_.attributes.'organization-id' -in $($AllowedOrgs.ITGlueOrgID) } + } + + #Sending the final object back to the client. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + headers = @{'content-type' = 'application\json' } + StatusCode = [httpstatuscode]::OK + Body = $ITGlueRequest + }) + + +} +else { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + headers = @{'content-type' = 'application\json' } + StatusCode = [httpstatuscode]::OK + Body = @{"Error" = "401 - No API Key entered or API key incorrect." } | convertto-json + }) + +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b524da --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Azglue - A forwarder hosted in Azure to Secure the IT-Glue API. +See https://www.cyberdrain.com/documenting-with-powershell-handling-it-glue-api-security-and-rate-limiting/ for more information. + +After my previous blogs the comment I’ve received most was worries about the API key. If they key gets stolen you’re giving away the keys to the castle. The API has no limitations and with a leaked key all your documentation could be download. I’ve been discussing this issue with IT-Glue for some time but haven’t gotten a real solution yet. This has forced me to look for a solution myself. I gave myself some requirements for the solution. + +- The solution needed to be simple and accessible for everyone. +- The solution needed to have multiple levels of authentication; an API key, IP whitelisting, and organization whitelisting. +- The solution needed to block requests for all passwords/files/etc for all organisations. +- The solution needed to allow some form of handling of the API rate limiting, e.g. repeating a request if it was rate limited. +- The solution needed to be able to used, without adapting any scripts (except URLs and API codes.) +- So after some research I decided to use an Azure Function for this. I’ve blogged about Azure Functions before, but the main reason is that running this function in the consumption model will cost us nothing (or next to nothing if you are an extremely heavy user.) +