Pular para o conteúdo principal
ImmutableLog logo
Voltar
Minimal APIC#

Integração com Minimal API

A Minimal API usa o mesmo pipeline HTTP do ASP.NET Core, então reutiliza exatamente o mesmo `ImmutableLogMiddleware`. A diferença está no registro funcional no `Program.cs` e em como você define evento e trilha por endpoint via `HttpContext.Items`.

Reaproveite o middleware

O código do middleware é idêntico ao da página ASP.NET Core — copie o `ImmutableLogMiddleware.cs` de lá. Aqui mostramos apenas o registro e o uso em endpoints Minimal API.

Registro no Program.cs

Registre as opções, o `IHttpClientFactory` e o middleware. A ordem importa: chame `UseMiddleware` antes de mapear os endpoints.

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("immutablelog", c => c.Timeout = TimeSpan.FromSeconds(5));
builder.Services.AddSingleton(new ImmutableLogOptions
{
    ApiKey = Environment.GetEnvironmentVariable("IMTBL_API_KEY") ?? "",
    Service = "my-minimal-api",
    Env = builder.Environment.EnvironmentName,
    SkipPaths = new() { "/health", "/metrics" },
});
builder.Services.AddSingleton<ImmutableLogMiddleware>();

var app = builder.Build();

// Registre antes de mapear os endpoints.
app.UseMiddleware<ImmutableLogMiddleware>();

app.MapGet("/health", () => Results.Ok(new { ok = true }));
app.MapGet("/users", () => Results.Ok(new[] { "alice", "bob" }));

app.Run();

Nunca coloque o token no código. Carregue IMTBL_API_KEY de variáveis de ambiente ou do user-secrets em desenvolvimento.

Evento e trilha por endpoint

Receba o `HttpContext` no delegate do endpoint e defina `Items["imtbl.eventName"]` e `Items["imtbl.trail"]`. O middleware lê esses valores ao emitir o evento.

csharp
// Receba o HttpContext para definir evento/trilha por endpoint.
app.MapPost("/payments", (HttpContext ctx, PaymentDto dto) =>
{
    ctx.Items["imtbl.eventName"] = "payment.created";
    ctx.Items["imtbl.trail"] = "order-7782";

    // ... business logic ...
    return Results.Ok(new { ok = true, payment_id = "pay_123" });
});

// Trilha por fluxo de negócio:
app.MapPost("/flows/{id}/run", (HttpContext ctx, string id) =>
{
    ctx.Items["imtbl.trail"] = $"flow-{id}";
    return Results.Ok(new { status = "ok" });
});

Padrão automático: http.METHOD.path.

Trilha imutável (immutable_trail)

A trilha agrupa eventos relacionados em uma mesma linha do tempo auditável. Defina `ctx.Items["imtbl.trail"]` no delegate do endpoint, ou propague entre serviços com o header `X-Imtbl-Trail`. O valor é normalizado pelo middleware antes do envio.

A trilha não pode ser vazia, exceder 256 caracteres ou conter `:` — violações retornam `400 invalid_immutable_trail`. O `SanitizeTrail` do middleware corrige o valor antes do envio.

Resposta e códigos de status

A API de ingestão é assíncrona. Sucesso retorna 202 (novo) ou 200 (duplicata idempotente). Como o middleware é fire-and-forget, esses códigos importam principalmente se você enviar eventos manualmente.

StatusSignificado
202Evento aceito e enfileirado (novo)
200Idempotency-Key já existente — duplicate: true
400Idempotency-Key ausente/vazia ou invalid_immutable_trail
403Assinatura inativa/expirada, escopo ou retenção
413payload_too_large (limite do servidor: 16KB)
429monthly_limit_exceeded — não fazer retry
503mempool_full — transitório, pode dar retry

Campos de meta

Além do payload, o objeto meta carrega os campos que o SIEM usa para normalizar (ECS) e enriquecer o evento. Todos os valores de meta são strings.

Campo em metaO que faz no SIEM
event_nameO rótulo do evento (ex.: http.GET.auth-me-user, Pagamento erro) — o identificador que aparece na listagem. Vira event.action e deriva event.category (ex.: nome com loginauthentication).
client_ipVira source.ip com precedência máxima e proveniência client_asserted. Ver a seção abaixo.
immutable_trailVira imtbl.immutable_trail — agrupa eventos relacionados para investigação (ex.: um pedido, um usuário).
typeSeveridade do evento: error / warning / info / success. Colore o badge na listagem. (event_type é aceito como sinônimo.)
service, env, request_idMetadados: indexados e consultáveis no painel (busca/filtros).

client_ip — IP do usuário final

O problema que resolve

Entre o navegador e o core há proxies/ALB. O IP que o core observa na conexão é o do salto anterior (o backend do cliente ou um proxy da AWS), não o do usuário. Sem ação, source.ip seria o IP do proxy — inútil para geo, threat e detecção por IP. (Exemplo real: o XFF chegava 44.192.13.3 (AWS) enquanto o usuário era 179.110.4.205.)

A solução

O backend do cliente é o único que enxerga o IP real do navegador — então ele encaminha esse IP em meta.client_ip ao chamar POST /v1/events.

Como o SIEM trata

  • Valida que é um IP (v4/v6). Um valor não-IP é ignorado — não derruba o evento.
  • Escreve em source.ip com precedência máxima (ver ordem abaixo).
  • Carimba imtbl.source_ip_origin = "client_asserted" — a UI mostra o selo “IP do usuário”.
  • Alimenta GeoIP, threat intel e regras de detecção por IP.
Precedência de source.ip:client_ipX-Forwarded-For (1º hop)X-Real-IPIP de conexão

Proveniência e confiança

client_ip é asserido pelo tenant — o backend do cliente afirma o IP, e é spoofável por quem controla aquele backend. Porém é auto-contido ao próprio tenant (não cruza a fronteira de outro tenant). Um hit de threat/geo sobre um client_asserted é uma alegação, não uma observação de borda.

Como obter o IP no seu backend

Pegue o primeiro IP do X-Forwarded-For (o mais próximo do usuário); no fallback, use o IP de conexão.

csharp
static string? ClientIp(HttpContext ctx)
{
    var xff = ctx.Request.Headers["X-Forwarded-For"].ToString();
    if (!string.IsNullOrEmpty(xff))
        return xff.Split(',')[0].Trim(); // primeiro hop = IP do usuario
    return ctx.Connection.RemoteIpAddress?.ToString();
}

var meta = new Dictionary<string, string>
{
    ["event_name"] = "user.login",
    ["client_ip"] = ClientIp(ctx) ?? "",
};

Exemplo de payload

json
{
  "payload": "{\"user\":\"bob\",\"action\":\"login\"}",
  "meta": {
    "event_name": "user.login",
    "client_ip": "179.110.4.205",
    "immutable_trail": "user-bob"
  }
}

Nota de PII: client_ip é PII e vai selado no bloco imutável (permanente). Decida conscientemente entre evidência forense e “direito ao esquecimento” antes de enviá-lo.

Esta documentação reflete o comportamento atual da integração. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.