Azure/.NET CORE 3.1 — проверка секрета Github Webhook в приложении-функции

Я пытаюсь проверить свой секретный ключ приложения-функции, который передается из Github Webhook, используя .NET CORE 3.1.

В моем веб-перехватчике Github я вставил ключ по умолчанию из функции Azure в поле «Секрет». Теперь я пытаюсь проверить это в своем коде. По какой-то причине мой зашифрованный секретный ключ отличается от того, что есть в вебхуке.

ПРИМЕЧАНИЕ. Секрет от Github Webhook зашифрован алгоритмом SHA1.

Код:

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    var secretKey = "my_key";
    StringValues outHeader;
    if (req.Headers.TryGetValue("x-hub-signature", out outHeader))
    {
        log.LogWarning("========= = ");
        log.LogWarning(outHeader);
        log.LogWarning(GetHash(secretKey));
        log.LogWarning("========= = ");
    }

    string responseMessage = "Everything went well!";
    return new OkObjectResult(responseMessage);
}

public static string GetHash(string input)
{
return "sha1 = " + string.Join("", 
    (new SHA1Managed()
        .ComputeHash(Encoding.UTF8.GetBytes(input)))
        .Select(x => x.ToString("x2"))
        .ToArray());
}

Выход:

2020-12-13T16:46:47.592 [Warning] ==========
2020-12-13T16:46:47.592 [Warning] sha1=f859bebbf5ec452a7ecd42efc69e0d86a4f25b16
2020-12-13T16:46:47.593 [Warning] sha1=fa1167715f137edff21d55d00adf63afb318b2a6
2020-12-13T16:46:47.593 [Warning] ==========

Официальная документация относится только к решению Node.js.

Как правильно проверить секрет Github Webhook Secret в .NET CORE 3.1? Спасибо за любую помощь.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
965
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь вы не передаете полезную нагрузку своему методу GetHash, а метод GetHash не принимает секрет. Это моя реализация:

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GitHubWebhooks
{
    public static class Security
    {
        private const string ShaPrefix = "sha256 = ";

        private const string keyVaultUrl = "<keyvault URL or replace with some other security>";
        private const string gitHubWebhookSecretSecretName = "GitHubWebHookSecret";

        private static KeyVaultSecret gitHubWebhookSecret;

        private static async Task FetchSecrets(CancellationToken cancellationToken)
        {
            var client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
            var gitHubWebHookSecretSecretResponse = await client.GetSecretAsync(gitHubWebhookSecretSecretName, cancellationToken: cancellationToken);
            gitHubWebhookSecret = gitHubWebHookSecretSecretResponse.Value;
        }

        // https://davidpine.net/blog/github-profanity-filter/
        // https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks
        public static async Task<bool> IsGithubPushAllowedAsync(HttpRequest request, CancellationToken cancellationToken)
        {
            if (gitHubWebhookSecret == null)
            {
                await FetchSecrets(cancellationToken);
            }

            request.Headers.TryGetValue("X-GitHub-Event", out StringValues eventName);
            request.Headers.TryGetValue("X-Hub-Signature-256", out StringValues signatureWithPrefix);
            request.Headers.TryGetValue("X-GitHub-Delivery", out StringValues delivery);

            if (string.IsNullOrWhiteSpace(eventName))
            {
                return false;
            }

            if (string.IsNullOrWhiteSpace(signatureWithPrefix))
            {
                return false;
            }

            if (string.IsNullOrWhiteSpace(delivery))
            {
                return false;
            }

            string payload;

            // https://justsimplycode.com/2020/08/02/reading-httpcontext-request-body-content-returning-empty-after-upgrading-to-net-core-3-from-2-0/
            // Request buffering needs to be enabled in app startup configuration.
            // The snippet is:
            // app.Use((context, next) =>
            // {
            //     context.Request.EnableBuffering();
            //     return next();
            // });

            request.Body.Position = 0;

            // We don't close the stream as we're not the one who's opened it.
            using (var reader = new StreamReader(request.Body, leaveOpen: true))
            {
                payload = await reader.ReadToEndAsync();
            }

            if (string.IsNullOrWhiteSpace(payload))
            {
                return false;
            }

            string signatureWithPrefixString = signatureWithPrefix;

            if (signatureWithPrefixString.StartsWith(ShaPrefix, StringComparison.OrdinalIgnoreCase))
            {
                var signature = signatureWithPrefixString.Substring(ShaPrefix.Length);
                var secret = Encoding.ASCII.GetBytes(gitHubWebhookSecret.Value);
                var payloadBytes = Encoding.UTF8.GetBytes(payload);

                using (var sha = new HMACSHA256(secret))
                {
                    var hash = sha.ComputeHash(payloadBytes);

                    var hashString = ToHexString(hash);

                    if (hashString.Equals(signature))
                    {
                        return true;
                    }
                }
            }

            return false;
        }


        public static string ToHexString(byte[] bytes)
        {
            var builder = new StringBuilder(bytes.Length * 2);
            foreach (byte b in bytes)
            {
                builder.AppendFormat("{0:x2}", b);
            }

            return builder.ToString();
        }
    }
}

Другие вопросы по теме