This repository has been archived by the owner on Nov 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
bin | ||
obj | ||
csx | ||
.vs | ||
edge | ||
Publish | ||
|
||
*.user | ||
*.suo | ||
*.cscfg | ||
*.Cache | ||
project.lock.json | ||
|
||
/packages | ||
/TestResults | ||
|
||
/tools/NuGet.exe | ||
/App_Data | ||
/secrets | ||
/data | ||
.secrets | ||
appsettings.json | ||
local.settings.json | ||
|
||
node_modules | ||
dist | ||
|
||
# Local python packages | ||
.python_packages/ | ||
|
||
# Python Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Webhooks Endpoint Example | ||
|
||
This example endpoint takes any incoming [OneFuzz webhook](../../docs/webhooks.md) and submits it to Microsoft Teams. | ||
|
||
Check out [Webhook Events Details](../../docs/webhook_events.md) for the schema of all supported events. | ||
|
||
## Creating an Azure Function | ||
|
||
1. Edit `local.settings.json` and add the following to the `Values` dictionary: | ||
* Create a [Microsoft Teams incoming webhook URL](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#setting-up-a-custom-incoming-webhook), and set this the value for `TEAMS_URL`. | ||
* Create a random string that you generate, and set this value for `HMAC_TOKEN`. This will be used to [help secure your webhook](https://github.com/microsoft/onefuzz/blob/main/docs/webhooks.md#securing-your-webhook) | ||
2. [Create Azure Resources for an Azure Function](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python?tabs=azure-cli%2Cbash%2Cbrowser#5-create-supporting-azure-resources-for-your-function). | ||
3. Ensure your function is HTTPS only: | ||
```bash | ||
az functionapp update --resource-group <RESOURCE_GROUP> --name <FUNCTION_APP_NAME> --set httpsOnly=true | ||
``` | ||
4. Deploy your function | ||
```bash | ||
func azure functionapp publish <FUNCTION_APP_NAME> --publish-local-settings | ||
``` | ||
5. From the previous command, write down the URL for your webhook. It should look something like this: | ||
``` | ||
webhook - [httpTrigger] | ||
Invoke url: https://<FUNCTION_APP_NAME>.azurewebsites.net/api/webhook?code=<BASE64_ENCODED_STRING> | ||
``` | ||
6. Register this new URL webhook to OneFuzz. In this example, we're registering a webhook to tell our service any time a job is created and stopped: | ||
```bash | ||
onefuzz webhooks create my-webhook "https://<FUNCTION_APP_NAME>.azurewebsites.net/api/webhook?code=<BASE64_ENCODED_STRING>" job_created job_stopped --secret_token <HMAC_TOKEN> | ||
``` | ||
> NOTE: Make sure `HMAC_TOKEN` is the value we added to `local.settings.json` earlier. | ||
This will respond with something akin to: | ||
```json | ||
{ | ||
"event_types": [ | ||
"job_created", | ||
"job_stopped" | ||
], | ||
"name": "my-webhook", | ||
"webhook_id": "9db7a8bb-0680-42a9-b336-655d3654fd6c" | ||
} | ||
``` | ||
7. Using the `webhook_id` we got in response, we can test our webhook using: | ||
```bash | ||
onefuzz webhooks ping 9db7a8bb-0680-42a9-b336-655d3654fd6c | ||
``` | ||
8. Using `webhook_id` we got in response, we can test if OneFuzz was able to send our service webhooks: | ||
``` | ||
onefuzz webhooks logs 9db7a8bb-0680-42a9-b336-655d3654fd6c | ||
``` | ||
If our webhook is successful, we'll see something akin to: | ||
```json | ||
[ | ||
{ | ||
"event": { | ||
"ping_id": "0770679d-67a0-4a6e-a5d7-751c7f80ebab" | ||
}, | ||
"event_id": "0c12ca77-bff8-4f8b-ae0d-f38f64cf0247", | ||
"event_type": "ping", | ||
"instance_id": "833bd437-775c-4b80-be62-599a9907f0f9", | ||
"instance_name": "YOUR-ONEFUZZ-INSTANCE-NAME", | ||
"state": "succeeded", | ||
"try_count": 1, | ||
"webhook_id": "9db7a8bb-0680-42a9-b336-655d3654fd6c" | ||
} | ||
] | ||
``` | ||
|
||
OneFuzz will attempt to send each event up to 5 times before giving up. Instead of `succeeded` as above, you might see `retrying` if OneFuzz is still working to send the event, or `failed` if OneFuzz has given up sending the event. | ||
9. Check your Teams channel. If all is successful, we should see something like the following: | ||
![Teams message Screenshot](example-message.png) | ||
|
||
## Troubleshooting | ||
|
||
* If your function isn't working as expected, check out the logs for your Azure Functions via: | ||
``` | ||
func azure functionapp logstream <FUNCTION_APP_NAME> --browser | ||
``` | ||
* If you see exceptions that say `missing HMAC_TOKEN` or `missing TEAMS_URL`, you forgot to add those settings above. | ||
* If you see exceptions saying `missing X-Onefuzz-Digest`, you forgot to set the `--secret_token` when you during `onefuzz webhooks create` above. This can be addressed via: | ||
``` | ||
onefuzz webhooks update --secret_token <HMAC_TOKEN> <webhook_id> | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"version": "2.0", | ||
"logging": { | ||
"applicationInsights": { | ||
"samplingSettings": { | ||
"isEnabled": true, | ||
"excludedTypes": "Request" | ||
} | ||
} | ||
}, | ||
"extensionBundle": { | ||
"id": "Microsoft.Azure.Functions.ExtensionBundle", | ||
"version": "[1.*, 2.0.0)" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"IsEncrypted": false, | ||
"Values": { | ||
"FUNCTIONS_WORKER_RUNTIME": "python" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[mypy] | ||
disallow_untyped_defs = True | ||
follow_imports = silent | ||
check_untyped_defs = True | ||
disallow_any_generics = True | ||
no_implicit_reexport = True | ||
strict_optional = True | ||
warn_redundant_casts = True | ||
warn_return_any = True | ||
warn_unused_configs = True | ||
warn_unused_ignores = True | ||
|
||
[mypy-azure.*] | ||
ignore_missing_imports = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Do not include azure-functions-worker as it may conflict with the Azure Functions platform | ||
|
||
azure-functions | ||
aiohttp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#!/usr/bin/env python | ||
# | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
# | ||
|
||
|
||
import hmac | ||
import json | ||
import logging | ||
import os | ||
from hashlib import sha512 | ||
from typing import Any, Dict | ||
|
||
import aiohttp | ||
import azure.functions as func | ||
|
||
|
||
def code_block(data: str) -> str: | ||
data = data.replace("`", "``") | ||
return "\n```\n%s\n```\n" % data | ||
|
||
|
||
async def send_message(req: func.HttpRequest) -> bool: | ||
data = req.get_json() | ||
teams_url = os.environ.get("TEAMS_URL") | ||
if teams_url is None: | ||
raise Exception("missing TEAMS_URL") | ||
|
||
message: Dict[str, Any] = { | ||
"@type": "MessageCard", | ||
"@context": "https://schema.org/extensions", | ||
"summary": data["instance_name"], | ||
"sections": [ | ||
{ | ||
"facts": [ | ||
{"name": "instance", "value": data["instance_name"]}, | ||
{"name": "event type", "value": data["event_type"]}, | ||
] | ||
}, | ||
{"text": code_block(json.dumps(data["event"], sort_keys=True))}, | ||
], | ||
} | ||
async with aiohttp.ClientSession() as client: | ||
async with client.post(teams_url, json=message) as response: | ||
return response.ok | ||
|
||
|
||
def verify(req: func.HttpRequest) -> bool: | ||
request_hmac = req.headers.get("X-Onefuzz-Digest") | ||
if request_hmac is None: | ||
raise Exception("missing X-Onefuzz-Digest") | ||
|
||
hmac_token = os.environ.get("HMAC_TOKEN") | ||
if hmac_token is None: | ||
raise Exception("missing HMAC_TOKEN") | ||
|
||
digest = hmac.new( | ||
hmac_token.encode(), msg=req.get_body(), digestmod=sha512 | ||
).hexdigest() | ||
if digest != request_hmac: | ||
logging.error("invalid hmac") | ||
return False | ||
|
||
return True | ||
|
||
|
||
async def main(req: func.HttpRequest) -> func.HttpResponse: | ||
if not verify(req): | ||
return func.HttpResponse("no thanks") | ||
|
||
if await send_message(req): | ||
return func.HttpResponse("unable to send message") | ||
|
||
return func.HttpResponse("thanks") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"scriptFile": "__init__.py", | ||
"bindings": [ | ||
{ | ||
"authLevel": "function", | ||
"type": "httpTrigger", | ||
"direction": "in", | ||
"name": "req", | ||
"methods": [ | ||
"post" | ||
] | ||
}, | ||
{ | ||
"type": "http", | ||
"direction": "out", | ||
"name": "$return" | ||
} | ||
] | ||
} |