Pular para o conteúdo principal

Implementação C#: IP Filtering + APIM/API Gateway em Azure

· 5 min para ler
Alfredo Fernandez
Arquiteto de solução

Este guia mostra código completo em C# para implementar IP Filtering robusto no ASP.NET Core, com suporte a CIDR (ranges), leitura de IP real via Forwarded Headers, e integração com listas dinâmicas (ex.: Azure App Configuration / Key Vault).

Nota: não entramos em detalhes do cliente Azure SDK aqui — assume-se que você carregue a lista confiável em IConfiguration ou usando um serviço que você implemente para buscar do App Configuration/Key Vault.


1) Helper: Verificar se um IP pertence a um CIDR (IPv4/IPv6)

using System.Net;

public static class IpUtils
{
public static bool IsInSubnet(IPAddress address, string cidrOrAddress)
{
if (string.IsNullOrWhiteSpace(cidrOrAddress))
return false;

// Single IP (no slash)
if (!cidrOrAddress.Contains("/"))
{
if (IPAddress.TryParse(cidrOrAddress, out var single))
return single.Equals(address);
return false;
}

var parts = cidrOrAddress.Split('/');
if (parts.Length != 2) return false;

if (!IPAddress.TryParse(parts[0], out var baseAddress)) return false;
if (!int.TryParse(parts[1], out var prefixLength)) return false;

var addrBytes = address.GetAddressBytes();
var baseBytes = baseAddress.GetAddressBytes();

// IPv4 vs IPv6 mismatch
if (addrBytes.Length != baseBytes.Length) return false;

var byteCount = addrBytes.Length;
var mask = new byte[byteCount];
var remainingBits = prefixLength;
for (int i = 0; i < byteCount; i++)
{
if (remainingBits >= 8)
{
mask[i] = 0xFF;
remainingBits -= 8;
}
else if (remainingBits > 0)
{
mask[i] = (byte)(~(0xFF >> remainingBits));
remainingBits = 0;
}
else
{
mask[i] = 0x00;
}
}

for (int i = 0; i < byteCount; i++)
{
if ((addrBytes[i] & mask[i]) != (baseBytes[i] & mask[i]))
return false;
}

return true;
}
}

2) Serviço para prover lista dinâmica de IPs/Range (pode buscar de App Configuration / Key Vault)

using Microsoft.Extensions.Options;

public interface IAllowedIpProvider
{
Task<IEnumerable<string>> GetAllowedCidrsAsync(CancellationToken ct = default);
}

public class ConfigurationAllowedIpProvider : IAllowedIpProvider
{
private readonly IConfiguration _config;
private readonly string _configKey = "AllowedIpCidrs"; // ex: comma-separated ou JSON array

public ConfigurationAllowedIpProvider(IConfiguration config)
{
_config = config;
}

public Task<IEnumerable<string>> GetAllowedCidrsAsync(CancellationToken ct = default)
{
var raw = _config[_configKey] ?? "";
// Suporta CSV ou JSON array; aqui simplificamos assumindo CSV
var items = raw.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.ToArray();
return Task.FromResult((IEnumerable<string>)items);
}
}

Em produção, substitua por um provedor que use o Azure App Configuration ou Key Vault. Você pode armazenar um JSON com ranges e usar um refresh token do App Configuration para atualizar dinamicamente.


3) Middleware que verifica IP usando CIDR + Forwarded Headers

using System.Net;
using Microsoft.AspNetCore.Http;

public class IpFilterMiddleware : IMiddleware
{
private readonly IAllowedIpProvider _allowedIpProvider;

public IpFilterMiddleware(IAllowedIpProvider allowedIpProvider)
{
_allowedIpProvider = allowedIpProvider;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// Obter IP real: priorize X-Forwarded-For (caso APIM / Gateway esteja na frente)
var ipString = GetRemoteIpString(context);
if (!IPAddress.TryParse(ipString, out var remoteIp))
{
// se não conseguir resolver, seja conservador: negar ou permitir? aqui negamos.
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsync("Forbidden: Unable to determine client IP.");
return;
}

var allowedCidrs = await _allowedIpProvider.GetAllowedCidrsAsync();
var allowed = allowedCidrs.Any(cidr => IpUtils.IsInSubnet(remoteIp, cidr));

if (!allowed)
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsync("Forbidden: Access denied due to IP filtering.");
return;
}

await next(context);
}

private string GetRemoteIpString(HttpContext context)
{
// Se houver X-Forwarded-For, pegue primeiro IP (mais à esquerda é o cliente original)
if (context.Request.Headers.TryGetValue("X-Forwarded-For", out var xff))
{
var first = xff.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim();
if (!string.IsNullOrEmpty(first)) return first;
}

// Fallback para conexão direta
return context.Connection.RemoteIpAddress?.ToString() ?? "";
}
}

4) Extensão para registrar o Middleware

public static class IpFilterExtensions
{
public static IApplicationBuilder UseIpFilterMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<IpFilterMiddleware>();
}
}

5) Registro no Program.cs (exemplo .NET 7+ minimal API)

var builder = WebApplication.CreateBuilder(args);

// Configuração: carregue AllowedIpCidrs via App Configuration ou settings locais
// Exemplo local appsettings.json:
// "AllowedIpCidrs": "203.0.113.0/24,198.51.100.5,10.0.0.0/24"
builder.Services.AddSingleton<IAllowedIpProvider, ConfigurationAllowedIpProvider>();
builder.Services.AddTransient<IpFilterMiddleware>();

var app = builder.Build();

// Muito importante: habilite Forwarded Headers se estiver por trás de Proxy/Gateway
using Microsoft.AspNetCore.HttpOverrides;
var forwardedOptions = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
// Opcional: limite de IPs conhecidos que podem enviar forwarded headers
// forwardedOptions.KnownProxies.Add(IPAddress.Parse("IP_DO_SEU_GATEWAY"));
app.UseForwardedHeaders(forwardedOptions);

// Registre antes de middlewares que protegem endpoints sensíveis
app.UseIpFilterMiddleware();

app.MapGet("/", () => "Hello world");

app.Run();

Importante: se você usa Azure API Management ou Front Door, configure KnownProxies ou KnownNetworks com os IPs do gateway para evitar spoofing do cabeçalho X-Forwarded-For. Para APIM, consulte a documentação para obter ranges que o serviço usa (ou integre via Managed Identity para buscar a lista oficial).


6) Atualização dinâmica (opcional): HostedService que recarrega a lista

Para ambientes onde os ranges mudam, use um IHostedService que busca a lista em intervalos e atualiza um repositório em memória:

using Microsoft.Extensions.Caching.Memory;

public class AllowedIpRefreshService : BackgroundService, IAllowedIpProvider
{
private readonly IConfiguration _configuration;
private readonly IMemoryCache _cache;
private readonly TimeSpan _refreshInterval = TimeSpan.FromMinutes(5);

public AllowedIpRefreshService(IConfiguration configuration, IMemoryCache cache)
{
_configuration = configuration;
_cache = cache;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var raw = _configuration["AllowedIpCidrs"] ?? "";
var items = raw.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
_cache.Set("allowedCidrs", items, TimeSpan.FromMinutes(10));
}
catch
{
// log error
}

await Task.Delay(_refreshInterval, stoppingToken);
}
}

public Task<IEnumerable<string>> GetAllowedCidrsAsync(CancellationToken ct = default)
{
_cache.TryGetValue("allowedCidrs", out string[] arr);
return Task.FromResult((IEnumerable<string>)(arr ?? Array.Empty<string>()));
}
}

Registre no Program.cs:

builder.Services.AddMemoryCache();
builder.Services.AddHostedService<AllowedIpRefreshService>();
// como o AllowedIpRefreshService implementa IAllowedIpProvider, registre-o assim:
builder.Services.AddSingleton<IAllowedIpProvider>(sp => sp.GetRequiredService<AllowedIpRefreshService>());

7) Observações de segurança finais

  • Evite spoofing: configure ForwardedHeadersOptions.KnownProxies/KnownNetworks para que apenas proxies confiáveis possam enviar X-Forwarded-For.
  • Teste em staging: valide ranges e CIDRs antes de aplicar em produção — um erro pode bloquear tráfego legítimo.
  • Logs e métricas: envie eventos de requisições bloqueadas para Azure Monitor / App Insights.
  • Integre com APIM: use políticas de APIM como primeira linha e deixe o middleware como reforço no backend.

Conclusão

Este MDX fornece a implementação prática em C# para IP Filtering com suporte a ranges e atualizações dinâmicas. Combine com APIM/WAF para máxima proteção em ambientes Azure.