Files
git.stella-ops.org/src/Scheduler/__Tests/StellaOps.Scheduler.WebService.Tests/EventWebhookEndpointTests.cs

133 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
namespace StellaOps.Scheduler.WebService.Tests;
public sealed class EventWebhookEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
static EventWebhookEndpointTests()
{
Environment.SetEnvironmentVariable("Scheduler__Events__Webhooks__Conselier__HmacSecret", ConselierSecret);
Environment.SetEnvironmentVariable("Scheduler__Events__Webhooks__Conselier__Enabled", "true");
Environment.SetEnvironmentVariable("Scheduler__Events__Webhooks__Excitor__HmacSecret", ExcitorSecret);
Environment.SetEnvironmentVariable("Scheduler__Events__Webhooks__Excitor__Enabled", "true");
}
private const string ConselierSecret = "conselier-secret";
private const string ExcitorSecret = "excitor-secret";
private readonly WebApplicationFactory<Program> _factory;
public EventWebhookEndpointTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConselierWebhook_AcceptsValidSignature()
{
using var client = _factory.CreateClient();
var payload = new
{
exportId = "conselier-exp-1",
changedProductKeys = new[] { "pkg:rpm/openssl", "pkg:deb/nginx" },
kev = new[] { "CVE-2024-0001" },
window = new { from = DateTimeOffset.UtcNow.AddHours(-1), to = DateTimeOffset.UtcNow }
};
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions(JsonSerializerDefaults.Web));
using var request = new HttpRequestMessage(HttpMethod.Post, "/events/conselier-export")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
request.Headers.TryAddWithoutValidation("X-Scheduler-Signature", ComputeSignature(ConselierSecret, json));
var response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ConselierWebhook_RejectsInvalidSignature()
{
using var client = _factory.CreateClient();
var payload = new
{
exportId = "conselier-exp-2",
changedProductKeys = new[] { "pkg:nuget/log4net" }
};
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions(JsonSerializerDefaults.Web));
using var request = new HttpRequestMessage(HttpMethod.Post, "/events/conselier-export")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
request.Headers.TryAddWithoutValidation("X-Scheduler-Signature", "sha256=invalid");
var response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExcitorWebhook_HonoursRateLimit()
{
using var restrictedFactory = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, configuration) =>
{
configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["Scheduler:Events:Webhooks:Excitor:RateLimitRequests"] = "1",
["Scheduler:Events:Webhooks:Excitor:RateLimitWindowSeconds"] = "60"
});
});
});
using var client = restrictedFactory.CreateClient();
var payload = new
{
exportId = "excitor-exp-1",
changedClaims = new[]
{
new { productKey = "pkg:deb/openssl", vulnerabilityId = "CVE-2024-1234", status = "affected" }
}
};
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions(JsonSerializerDefaults.Web));
using var first = new HttpRequestMessage(HttpMethod.Post, "/events/excitor-export")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
first.Headers.TryAddWithoutValidation("X-Scheduler-Signature", ComputeSignature(ExcitorSecret, json));
var firstResponse = await client.SendAsync(first);
Assert.Equal(HttpStatusCode.Accepted, firstResponse.StatusCode);
using var second = new HttpRequestMessage(HttpMethod.Post, "/events/excitor-export")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
second.Headers.TryAddWithoutValidation("X-Scheduler-Signature", ComputeSignature(ExcitorSecret, json));
var secondResponse = await client.SendAsync(second);
Assert.Equal((HttpStatusCode)429, secondResponse.StatusCode);
Assert.True(secondResponse.Headers.Contains("Retry-After"));
}
private static string ComputeSignature(string secret, string payload)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
using StellaOps.TestKit;
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
}
}