Introduction
Last post I talked about some simple whitelisting of IPs for Microsoft Sentinel Playbooks - today weāre looking into similar topics when working with webhook triggers in Azure DevOps pipelines.
Like last time, weāre the general idea of IP whitelisting is that in order to access a resource X, you need to fullfill conditions Y (in this case, the IP youāre trying to work form needs to be added to an allowlist).
graph LR
R[Resource X]
C[Conditions Y]
U[User] -->|Fullfills| C --> |Access| R
This time we are following the same idea, but using some different parameters than IP for our allowlist.
Pipelines triggered by webhooks
Using webhooks we can trigger pipelines from a range of different applications, scripts and flows (like Power Automate). One big downside to using this is that thereās no way to add a list of allowed IPs and the only supported āsecurityā feature is the secret field in the screenshot below, which, by looking quickly at Github issues on the topic isnāt very well documented.
So how can we secure our pipelines? Well, first, let look at how we configure it for usage in a pipeline.
Implementing a webhook trigger in a Azure DevOps pipeline
Iāve created a simple hello world
-pipeline over in my github repo to demonstrate how to add the webhook resource. The relevant portions are the resources
-block:
resources:
webhooks:
- webhook: infernux_webhook ### Webhook alias
connection: infernux_webhook ### Incoming webhook service connection
filters:
- path: allowlistvalue ### JSON path in the payload
value: SuperSecureJK1 ### Expected value in the path provided
So, letās look at the different parameters here:
- First of all the
- webhook:
is an alias you can set. This can be set to whatever you want, it has no bearing other than further references (usually you never use it again in my experience). connection:
refers to the service connection name (from the above screenshot)filters:
allows us to provide values from the body of the incoming webhookpath
refers to the json path in the payloadvalue
tells us what value we should expect and can work as a sudo security feature
Triggering our pipeline via webhook
So letās try to trigger our pipeline using a webhook - I will use PowerShell for this, but you can also just use Postman or other similar tools.
The URI for triggering pipelines is https://dev.azure.com/<org>/_apis/public/distributedtask/webhooks/<WebhookName>/?api-version=6.0-preview
.
Please note that this is the Webhook Name from the configuration of the webhook trigger, NOT the alias or the service connection name.
This topic from the developercommunity letās us know some other common errors. The most important thing however is that you will need to manually trigger the pipeline once before the webhook trigger will work.
First, letās try to trigger it without providing the expected JSON from above:
$Org = "secretOrg"
$Webhook = "infernux_webhook"
$Url = "https://dev.azure.com/$Org/_apis/public/distributedtask/webhooks/$Webhook/?api-version=6.0-preview"
Invoke-WebRequest -Uri $Url -Method Post -ContentType 'application/json'
Remove the filters and this would have worked - we get a 200 but the pipeline doesnāt trigger:
Letās try again, this time Iāll provide the expected JSON in the body:
$Body = '{"allowlistvalue":"SuperSecureJK1"}'
Invoke-WebRequest -Uri $Url -Method Post -ContentType 'application/json'
This time it works as expected and the pipeline triggers:
Adding the secret value
As mentioned above, the usage of the secret parameter isnāt very well documented (yet) but if we go looking we can actually find out what we need to make it work. In the topic mentioned above from the dev community this comment leads us to this gist that shows an example PowerShell-code for triggering using the secret variable. The original author is Igor Abade, big ups. Letās test it!
Updating our webhook trigger with a secret value
First lets add a secret to our trigger. We can do this by editing the service connection, adding our secret (WeLikeIce
). We also add the Http Header
parameter as X-Hub-Signature
before saving.
Post-Message-to-AzDO-Webhook.ps1
Param
(
# URL to the Azure DevOps incoming webhook endpoint. Expected format is
# https://dev.azure.com/<org>/_apis/public/distributedtask/webhooks/<svc-trig>/?api-version=6.0-preview.
[uri]
$Url,
# Shared secret used to sign the JSON payload. Must be the same value supplied during
# the creation of the Incoming Webhook service connection
[string]
$Secret,
# HTTP header name to send the hash signature. When omitted, defaults to "X-Hub-Signature"
[string]
$HeaderName = 'X-Hub-Signature',
# JSON payload to send to the Azure DevOps web hook
[string]
$Body
)
$hmacSha = New-Object System.Security.Cryptography.HMACSHA1 -Property @{
Key = [Text.Encoding]::ASCII.GetBytes($secret)
}
$hashBytes = $hmacSha.ComputeHash([Text.Encoding]::UTF8.GetBytes($body))
$signature = ''
$hashBytes | ForEach-Object { $signature += $_.ToString('x2')}
$headers = @{
$headerName = "sha1=$signature"
}
Invoke-WebRequest -Uri $Url -Body $Body -Method Post -ContentType 'application/json' -Headers $headers
Running the script gives us the following output:
Woop, it works.
Summary
First of all, for anyone trying to trigger my pipeline - Iāve already deleted it ;)
Hopefully this allows more people to use both the secret value and add filters for working with pipelines.
Filters are not really a security mechanism, but can be used for triggering different pipelines from one location without having to use multiple webhooks.
The usage of the secret-value and the Http Header
field still isnāt very well documented, so hopefully someone from Microsoft picks that up and fixes it.