Security: controllers and stack traces in logs
All checks were successful
Build Docker Images / test (map[name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m32s
Build Docker Images / test (map[name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m29s
Build Docker Images / build-and-push (map[image_suffix:morska name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m47s
Build Docker Images / build-and-push (map[image_suffix:pedrollopl name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m42s
All checks were successful
Build Docker Images / test (map[name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m32s
Build Docker Images / test (map[name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m29s
Build Docker Images / build-and-push (map[image_suffix:morska name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m47s
Build Docker Images / build-and-push (map[image_suffix:pedrollopl name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m42s
This commit is contained in:
63
DiunaBI.API/Attributes/ApiKeyAuthAttribute.cs
Normal file
63
DiunaBI.API/Attributes/ApiKeyAuthAttribute.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace DiunaBI.API.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Authorization attribute that validates API key from X-API-Key header.
|
||||
/// Uses constant-time comparison to prevent timing attacks.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class ApiKeyAuthAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
private const string ApiKeyHeaderName = "X-API-Key";
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiKeyAuthAttribute>>();
|
||||
|
||||
// Get expected API key from configuration
|
||||
var expectedApiKey = configuration["apiKey"];
|
||||
if (string.IsNullOrEmpty(expectedApiKey))
|
||||
{
|
||||
logger.LogError("API key not configured in appsettings");
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status500InternalServerError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get API key from header
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))
|
||||
{
|
||||
logger.LogWarning("API key missing from request header");
|
||||
context.Result = new UnauthorizedObjectResult(new { error = "API key is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
if (!IsApiKeyValid(extractedApiKey!, expectedApiKey))
|
||||
{
|
||||
logger.LogWarning("Invalid API key provided from {RemoteIp}", context.HttpContext.Connection.RemoteIpAddress);
|
||||
context.Result = new UnauthorizedObjectResult(new { error = "Invalid API key" });
|
||||
return;
|
||||
}
|
||||
|
||||
// API key is valid - allow the request to proceed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constant-time string comparison to prevent timing attacks.
|
||||
/// </summary>
|
||||
private static bool IsApiKeyValid(string providedKey, string expectedKey)
|
||||
{
|
||||
if (providedKey == null || expectedKey == null)
|
||||
return false;
|
||||
|
||||
var providedBytes = Encoding.UTF8.GetBytes(providedKey);
|
||||
var expectedBytes = Encoding.UTF8.GetBytes(expectedKey);
|
||||
|
||||
return CryptographicOperations.FixedTimeEquals(providedBytes, expectedBytes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user