Skip to content

Instantly share code, notes, and snippets.

@davepcallan
Last active January 20, 2025 15:40
Show Gist options
  • Save davepcallan/807bdc3c51f4cf8912631549701d6483 to your computer and use it in GitHub Desktop.
Save davepcallan/807bdc3c51f4cf8912631549701d6483 to your computer and use it in GitHub Desktop.
Example Azure Function code for handling a Github webhook for a repo push event
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
namespace AZFunctionsGHWebhooksDemo;
public class HandleRepoPush(ILogger<HandleRepoPush> logger)
{
private const string GitHubSignatureHeader = "X-Hub-Signature-256";
private const string GitHubWebhookSecret = "123456"; // This comes from the vault etc. in a real app
[Function(nameof(HandleRepoPush))]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
// Step 1: Get the GitHub Signature Header
if (!req.Headers.TryGetValue(GitHubSignatureHeader, out var signatureHeader))
{
logger.LogWarning($"Missing {GitHubSignatureHeader} header.");
return new UnauthorizedResult();
}
// Step 2: Read the request body
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
// Step 3: Compute the HMAC hash
string computedHash = ComputeHash(requestBody, GitHubWebhookSecret);
// Step 4: Compare the hashes
if (!CheckHashes(signatureHeader, computedHash))
{
logger.LogWarning("Invalid HMAC signature.");
return new UnauthorizedResult();
}
GitHubPushPayload? payload;
try
{
payload = JsonSerializer.Deserialize<GitHubPushPayload>(requestBody);
}
catch (JsonException ex)
{
logger.LogError(ex, "Failed to deserialize payload.");
return new BadRequestObjectResult("Invalid JSON payload.");
}
logger.LogInformation(new string('-', 50));
logger.LogInformation($"Received push event for repository: {payload.repository.full_name}");
foreach (var commit in payload.commits)
{
logger.LogInformation($"Commit ID: {commit.id}");
logger.LogInformation($"Commit Message: {commit.message}");
logger.LogInformation($"Commit URL: {commit.url}");
logger.LogInformation($"Commit Timestamp: {commit.timestamp}");
logger.LogInformation($"Committer Username: {commit.author.username}");
logger.LogInformation($"Modified Files: {string.Join(", ", commit.modified)}");
}
return new OkObjectResult("Webhook received and processed.");
}
private static string ComputeHash(string payload, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return "sha256=" + BitConverter.ToString(hash).Replace("-", "").ToLower();
}
private static bool CheckHashes(string signatureHeader, string computedHash)
{
// Compare hashes in a time-safe manner to prevent timing attacks
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signatureHeader),
Encoding.UTF8.GetBytes(computedHash)
);
}
}
public record GitHubPushPayload(Repository repository, Commit[] commits);
public record Repository(string full_name);
public record Commit(
string id,
string message,
string url,
DateTime timestamp,
CommitAuthor author,
string[] modified
);
public record CommitAuthor(string username);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment