Search code examples
.netasp.net-coresecuritynginx

How to prevent certain endpoints go public?


Beginner here, don't get offended. Imagine you are writing a web site for some organization. There will be Frontend #1 for public web site, and there will be Frontend #2 for admin panel (through which they will update contents of that web site).

CASE 1

Have 2 separate backends in one solution:

  1. PublicApi (API)
  2. AdminApi (API)
  3. PublicServices
  4. AdminServices
  5. Data (this one is shared among both APIs, to make sure both have updates on entity structure)

Then you would deploy apps separately, restricting requests to AdminApi only from certain network or static IP.

CASE 2

Have just one API, just put [Authorize] to certain routes and get over it. The question is is this secure enough? Because theoretically you would have the admin panel's routes exposed globally (with just some jwt auth on them). How can I make those endpoints not visible globally in one API? And more importantly should I bother?

How would you do? And why? The goal is to make the app safe.

P.S.

My setup is: .Net 8, Nginx


Solution

  • Could you be please more specific on the middleware part? Is there any source I can read from, or can you show me what you meant, how would it look like, and what would you achieve with that?

    Sure, there is multiple way to achieve this, the middelware is used for all request come into this application.

    The example codes like below:

    Inside the appsettings.json, you could set AdminSafeList as below:

    {
      "AdminSafeList": "127.0.0.1;192.168.1.5;::1",
      "Logging": {
    

    Then inside the middleware, you could refer to below codes:

    public class AdminSafeListMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<AdminSafeListMiddleware> _logger;
        private readonly byte[][] _safelist;
    
        public AdminSafeListMiddleware(
            RequestDelegate next,
            ILogger<AdminSafeListMiddleware> logger,
            string safelist)
        {
            var ips = safelist.Split(';');
            _safelist = new byte[ips.Length][];
            for (var i = 0; i < ips.Length; i++)
            {
                _safelist[i] = IPAddress.Parse(ips[i]).GetAddressBytes();
            }
    
            _next = next;
            _logger = logger;
        }
    
        public async Task Invoke(HttpContext context)
        {
            // here you could also check the request path by using the reuqest.path 
            if (context.Request.Method != HttpMethod.Get.Method)
            {
                var remoteIp = context.Connection.RemoteIpAddress;
                _logger.LogDebug("Request from Remote IP address: {RemoteIp}", remoteIp);
    
                var bytes = remoteIp.GetAddressBytes();
                var badIp = true;
                foreach (var address in _safelist)
                {
                    if (address.SequenceEqual(bytes))
                    {
                        badIp = false;
                        break;
                    }
                }
    
                if (badIp)
                {
                    _logger.LogWarning(
                        "Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
                    context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
                    return;
                }
            }
    
            await _next.Invoke(context);
        }
    }
    

    If you just want to let it work for specific MVC controller, you could use action filter.

    Like this :

    public class ClientIpCheckActionFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;
        private readonly string _safelist;
    
        public ClientIpCheckActionFilter(string safelist, ILogger logger)
        {
            _safelist = safelist;
            _logger = logger;
        }
    
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var remoteIp = context.HttpContext.Connection.RemoteIpAddress;
            _logger.LogDebug("Remote IpAddress: {RemoteIp}", remoteIp);
            var ip = _safelist.Split(';');
            var badIp = true;
            
            if (remoteIp.IsIPv4MappedToIPv6)
            {
                remoteIp = remoteIp.MapToIPv4();
            }
            
            foreach (var address in ip)
            {
                var testIp = IPAddress.Parse(address);
                
                if (testIp.Equals(remoteIp))
                {
                    badIp = false;
                    break;
                }
            }
    
            if (badIp)
            {
                _logger.LogWarning("Forbidden Request from IP: {RemoteIp}", remoteIp);
                context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
                return;
            }
    
            base.OnActionExecuting(context);
        }
    }
    

    Register it inside the program.cs:

    services.AddScoped<ClientIpCheckActionFilter>(container =>
    {
        var loggerFactory = container.GetRequiredService<ILoggerFactory>();
        var logger = loggerFactory.CreateLogger<ClientIpCheckActionFilter>();
    
        return new ClientIpCheckActionFilter(
            Configuration["AdminSafeList"], logger);
    });
    

    Usage:

    [ServiceFilter(typeof(ClientIpCheckActionFilter))]
    [HttpGet]
    public IEnumerable<string> Get()
    

    More details, you could refer to this MSFT article.