Skip to content

About

TEC.Http: pipeline HTTP endurecido para APIs ASP.NET Core (.NET 10)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Tudo em Código

🏠 Tudo em Código › 📦 TEC.Http

🛡️ TEC.Http

Borda HTTP fechada por padrão para APIs ASP.NET Core: proxy confiável explícito, hosts permitidos, HSTS e headers de segurança, limite de requisições com resposta 429 padronizada e CORS por allowlist.

CI .NET Native AOT Versão NuGet Licença: Apache 2.0

📑 Sumário


✨ Por que usar

Sem TEC.Http Com TEC.Http
CORS, limite e HSTS reescritos em cada serviço, cada um de um jeito Uma linha no Program.cs e uma seção Http no appsettings
AllowAnyOrigin com credenciais, AllowedHosts: "*" em produção A aplicação não sobe com essas configurações
X-Forwarded-For aceito de qualquer cliente: o IP é forjável e o limite por IP é burlado O header só vale se vier de um proxy listado; senão é ignorado
429 com corpo e formato próprios (ou 503 padrão do limiter) 429 no ApiResponse do TEC.Core, com Retry-After e traceId

O componente usa só o ASP.NET Core (e o TEC.Core para o envelope de erro): nenhuma dependência de terceiros.

📥 Instalação

dotnet nuget add source https://nuget.pkg.github.com/tudoemcodigo/index.json -n tec-interno -u <usuario> -p <PAT>
dotnet add package TEC.Http

🚀 Início rápido

using TEC.Http;

builder.Services.AddTecHttp(builder.Configuration);

var app = builder.Build();

app.UseTecObservability();        // TEC.Observability: correlation id primeiro
app.UseTecExceptionHandler();     // TEC.Cqrs.AspNetCore
app.UseTecHttp();                 // proxy, HSTS, headers, CORS, limite por IP: ANTES da autenticação
app.UseAuthentication();
app.UseTecRateLimitingPerIdentity();   // DEPOIS da autenticação
app.UseAuthorization();

Important

A ordem importa. O limite por IP precisa vir antes de UseAuthentication: é ele que contém tentativas de credencial (API key e token inválidos). O limite por identidade só existe depois da autenticação.

⚙️ Configuração

Seção Http. Os valores abaixo são os padrões (a seção inteira é opcional).

{
  "AllowedHosts": "api.exemplo.com",
  "Http": {
    "Forwarding":      { "Enabled": false, "KnownProxies": [], "KnownNetworks": [], "ForwardLimit": 1 },
    "Hsts":            { "Enabled": true, "MaxAgeDays": 365, "IncludeSubDomains": false, "Preload": false },
    "SecurityHeaders": { "Enabled": true },
    "RateLimiting": {
      "Enabled": true,
      "PerIp":       { "PermitLimit": 100, "WindowSeconds": 60 },
      "PerIdentity": { "PermitLimit": 600, "WindowSeconds": 60 }
    },
    "Cors": {
      "Origins": [], "Methods": [], "Headers": [], "ExposedHeaders": [],
      "AllowCredentials": false, "MaxAgeSeconds": 600
    }
  }
}
Item Comportamento
CORS Sem Origins, nenhuma origem é liberada (é o estado de partida: servidores não usam CORS). Formato https://host[:porta], sem caminho, barra final nem curinga. * só em Development e sem credenciais. http só para localhost em Development. Methods vazio: GET, POST, PUT, PATCH, DELETE. Headers vazio: Content-Type, Authorization, X-Api-Key, Idempotency-Key, If-Match. Cada item de Methods, Headers e ExposedHeaders precisa ser um token HTTP (sem espaço, vírgula ou quebra de linha, até 128 caracteres). * vale só como item único e nunca com AllowCredentials. Para o navegador ler o Retry-After do 429, inclua Retry-After em ExposedHeaders.
Listas Origins, Methods, Headers, ExposedHeaders, KnownProxies e KnownNetworks são lidas por índice (Http:Cors:Origins:0; em variável de ambiente, Http__Cors__Origins__0). Um valor único na própria chave (ex.: Http__Cors__Origins=https://a,https://b) derruba a subida com a chave esperada na mensagem, em vez de deixar a lista vazia em silêncio.
HSTS Só fora de Development e em HTTPS. IncludeSubDomains e Preload vêm desligados: sem configuração explícita o header é só max-age. Em produção, ligue Http:Hsts:IncludeSubDomains depois de inventariar os subdomínios (todos, inclusive os internos, precisam servir HTTPS). Http:Hsts:Preload é um passo deliberado de release (praticamente irreversível; a submissão em hstspreload.org é manual) e exige IncludeSubDomains = true e MaxAgeDays >= 365; senão a subida falha com a chave na mensagem. A validação vale também em Development e com Enabled: false, embora o header não saia ali. Valor que não seja true ou false também derruba a subida.
Headers X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer. Não sobrescrevem header já definido pela aplicação.
AllowedHosts Fora de Development, vazio ou * derruba a subida. Liste também o host que o orquestrador usa nas sondas de saúde (muitos chamam pelo IP do pod, e o filtro responde 400).

🚦 Limite de requisições

  • Janela deslizante em memória, por instância: com N instâncias o limite efetivo é N vezes o configurado. Se precisar de limite global, use o gateway ou um limiter distribuído. Não há ponto de extensão público hoje (YAGNI): a criação do limiter e as chaves de partição ficam isoladas em RateLimitMiddleware e ClientPartition, onde um limiter distribuído entraria sem mudar o contrato de configuração.
  • Estágio por IP (toda requisição): IPv6 é agrupado por prefixo /64 (um cliente controla o /64 inteiro; sem isso, rotacionar o sufixo burlaria o limite). Sem IP remoto (por exemplo, conexão por Unix domain socket) o limite vale por conexão: clientes diferentes não dividem um balde só, e cada conexão continua limitada. Um aviso é registrado uma vez (EventId 6002). Se nem o id da conexão existir, a requisição cai em um balde compartilhado (falha fechada, nunca sem limite).
  • Preflight CORS: com Cors:Origins configurado, o OPTIONS de preflight é respondido pelo CORS antes do limite e não conta no balde do IP. Sem CORS configurado não há preflight a responder: OPTIONS é uma requisição comum e conta.
  • Estágio por identidade (só autenticadas): chave tec_uid (identidade normalizada do TEC.Security), sub ou NameIdentifier. Autenticado sem identificador usa um balde próprio. Chaves longas viram hash (memória limitada).
  • Resposta 429: ApiResponse com errors[0].code = "http.rate_limit_exceeded", traceId, Cache-Control: no-store e Retry-After em segundos (nunca 0). Para origem liberada, o 429 sai com os headers CORS, então o navegador consegue lê-lo.
  • Desligar (RateLimiting:Enabled: false) é decisão explícita, para quando há gateway à frente.

🌐 Proxy reverso

Os headers X-Forwarded-* são escolhidos pelo cliente. O componente não detecta se há proxy: você declara.

  • Sem proxy (padrão): X-Forwarded-For é ignorado; o IP é o da conexão. Forjar o header não burla o limite.
  • Atrás de proxy: ligue Forwarding e liste KnownProxies e/ou KnownNetworks (CIDR). Loopback não é confiável por padrão; liste-o se o proxy roda na mesma máquina. Sem isso, todo cliente aparece com o IP do proxy e divide o mesmo balde.
  • X-Forwarded-Proto também é aplicado, então Request.IsHttps e o HSTS funcionam atrás de quem termina o TLS.
  • Header X-Forwarded-For recebido com Forwarding desligado: a requisição segue, o contador tec.http.forwarded_headers.ignored sobe e um aviso é registrado uma vez. É o sinal de proxy sem configuração.

🧱 O que derruba a subida

A configuração é validada na subida (UseTecHttp e ValidateOnStart); erro derruba a aplicação, não a primeira requisição.

  • AllowedHosts ausente ou * fora de Development.
  • Forwarding ligado sem proxy nem rede; IP ou CIDR inválido; prefixo /0; ForwardLimit fora de 1 a 5.
  • Origem CORS fora do formato; * fora de Development ou com AllowCredentials; http que não seja localhost em Development.
  • Item de Methods, Headers ou ExposedHeaders que não seja token HTTP; * misturado com outros itens ou com AllowCredentials.
  • Lista configurada com valor único em vez de itens por índice.
  • Limite fora de 1 a 1.000.000 ou janela fora de 1 a 86.400 s; MaxAgeSeconds fora de 0 a 86.400.
  • Valor presente e ilegível (ex.: "abc" num número): nunca é trocado pelo padrão em silêncio.

📈 Observabilidade

Medidor TEC.Http (registre-o no OpenTelemetry com AddMeter("TEC.Http")):

Métrica Quando
tec.http.ratelimit.rejected (tag stage: ip ou identity) Requisição recusada com 429
tec.http.forwarded_headers.ignored X-Forwarded-For recebido com Forwarding desligado

🧩 Diagrama de arquitetura

Componentes e ligações do TEC.Http, gerados pelo GitDiagram a partir da main em 02/11/2026. O diagrama é gerado por IA a partir de uma amostra do código: use-o como mapa de leitura, não como contrato.

flowchart TD

subgraph group_configuration["Configuration and validation"]
  node_options["HTTP options<br/>[TecHttpOptions.cs]"]
  node_reader["Configuration reader"]
  node_validator["Startup validation"]
end

subgraph group_edge["HTTP edge protections"]
  node_registration["Service registration"]
  node_proxy["Trusted proxy forwarding"]
  node_hosts["Allowed hosts"]
  node_hsts["HSTS"]
  node_headers["Security headers"]
  node_cors["CORS allowlist"]
end

subgraph group_limiting["Rate limiting"]
  node_iplimit["Per-IP limiter"]
  node_identity["Per-identity limiter"]
end

subgraph group_internal["Shared internals"]
  node_partition["Partition keys<br/>[ClientPartition.cs]"]
  node_response["429 response"]
  node_metrics["HTTP metrics<br/>[TecHttpMetrics.cs]"]
  node_logging["HTTP logging<br/>[TecHttpLog.cs]"]
end

node_app(("API application"))
node_request(("Incoming requests"))
node_auth["Authentication middleware"]

node_app -->|"registers services"| node_registration
node_registration -->|"reads configuration"| node_reader
node_reader -->|"populates"| node_options
node_registration -->|"registers validation"| node_validator
node_validator -->|"validates"| node_options
node_app -->|"adds middleware"| node_proxy
node_request -->|"passes through"| node_proxy
node_request -->|"host checked"| node_hosts
node_request -->|"passes through"| node_hsts
node_request -->|"passes through"| node_headers
node_request -->|"passes through"| node_cors
node_request -->|"passes through"| node_iplimit
node_auth -->|"precedes"| node_identity
node_request -->|"passes through"| node_identity
node_iplimit -->|"gets IP key"| node_partition
node_identity -->|"gets identity key"| node_partition
node_iplimit -->|"writes rejection"| node_response
node_identity -->|"writes rejection"| node_response
node_iplimit -->|"records rejections"| node_metrics
node_identity -->|"records rejections"| node_metrics
node_proxy -->|"records ignored headers"| node_metrics
node_proxy -->|"logs ignored headers"| node_logging


classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_options,node_reader,node_validator toneBlue
class node_registration,node_proxy,node_hosts,node_hsts,node_headers,node_cors toneAmber
class node_iplimit,node_identity,node_app,node_request toneMint
class node_partition,node_response,node_metrics,node_logging toneRose
class node_auth toneIndigo
Loading

⚡ Compatibilidade

.NET 10, compatível com Native AOT e trimming (configuração lida sem reflexão).

🛡️ Limites conhecidos

  • Limite por instância (ver acima).
  • Preflights não contam no limite (o CORS responde 204 antes dele, inclusive para origem não liberada). É uma resposta barata, sem rota nem autenticação; contenção de volume contra OPTIONS em massa fica com o gateway.
  • Atrás de proxy por Unix domain socket, o ASP.NET não consegue atestar o proxy (não há IP), então Forwarding não se aplica e o limite vale por conexão do proxy: se ele abre uma conexão por requisição, o limite por IP perde efeito. Limite também no proxy, ou use TCP com Forwarding.
  • Sem CSP: a API não serve HTML; se servir (ex.: documentação), defina a política na aplicação.
  • O componente não faz limite de tamanho de corpo nem de tempo de requisição: use Kestrel (MaxRequestBodySize, RequestHeadersTimeout) e o gateway.
  • Atrás de proxy, o IP só é confiável se o proxy sobrescreve (não repassa) o X-Forwarded-For recebido do cliente.

About

TEC.Http: pipeline HTTP endurecido para APIs ASP.NET Core (.NET 10)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages