Code Monkey home page Code Monkey logo

firewall's Introduction

Firewall Firewall

ASP.NET Core middleware for request filtering.

Firewall adds IP address-, geo-location and custom filtering capabilities to an ASP.NET Core web application which gives control over which connections are allowed to access the web server.

NuGet Info

Build History

Table of contents

About

Firewall is an ASP.NET Core middleware which enables IPv4 and IPv6 address-, geo-location and other request filtering features.

Request filtering can be added as an extra layer of security to a publicly exposed API or to force all API access through a certain set of proxy servers (e.g. Cloudflare).

How is it different to ASP.NET Core's IP safelist feature?

Simply ASP.NET Core's safelist feature doesn't support IPv4 and IPv6 address ranges specified through CIDR notations, which makes is somewhat less usable in the real world where a web application might need to "safelist" a CIDR notation as part of its security configuration.

Using with Cloudflare

Cloudflare is a popular internet service which provides enhanced performance and security features to any website. It is currently being used by more than 8 million websites world wide and requires no additional hardware or software as it operates at the DNS level.

The typical request flow for a website which is not protected by Cloudflare looks a little bit like this:

without-cloudflare

Image source: blog.christophetd.fr

When a website is protected by Cloudflare then Cloudflare essentially acts as a man in the middle, shielding a website from all sorts of malicious internet activity and giving a website administrator enhanced performance and security features such as HTTPS, Caching, CDNs, API rate limiting and more:

with-cloudflare

Image source: blog.christophetd.fr

The only problem with this configuration is that an attacker can still access the origin server by sending requests directly to its IP address and therefore bypassing all additional security and performance layers provided by Cloudflare.

In order to prevent anyone from talking directly to the origin server and forcing all access through Cloudflare one has to block all IP addresses which do not belong to Cloudflare.

Cloudflare maintains two public lists of all their IPv4 and IPv6 address ranges which can be used to configure an origin server's IP address filtering.

Firewall supports IP filtering for Cloudflare out of the box.

Getting Started

First install the Firewall NuGet package using PowerShell:

PM> Install-Package Firewall

...or via the dotnet command line:

dotnet add [PROJECT] package Firewall --package-directory [PACKAGE_CIRECTORY]

Then add the Firewall middleware to your ASP.NET Core Startup class:

using Firewall;

namespace BasicApp
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            var allowedIPs =
                new List<IPAddress>
                    {
                        IPAddress.Parse("10.20.30.40"),
                        IPAddress.Parse("1.2.3.4"),
                        IPAddress.Parse("5.6.7.8")
                    };

            var allowedCIDRs =
                new List<CIDRNotation>
                    {
                        CIDRNotation.Parse("110.40.88.12/28"),
                        CIDRNotation.Parse("88.77.99.11/8")
                    };

            app.UseFirewall(
                FirewallRulesEngine
                    .DenyAllAccess()
                    .ExceptFromIPAddressRanges(allowedCIDRs)
                    .ExceptFromIPAddresses(allowedIPs));

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

Documentation

Basics

Firewall uses a rules engine to configure request filtering. The FirewallRulesEngine helper class should be used to configure an object of IFirewallRule which then can be passed into the FirewallMiddleware:

var rules =
    FirewallRulesEngine
        .DenyAllAccess()
        .ExceptFromCloudflare()
        .ExceptFromLocalhost();

app.UseFirewall(rules);

Currently the following rules can be configures out of the box:

  • DenyAllAccess(): This is the base rule which should be used at the beginning of the rules configuration. It specifies that if no other rule can be met by an incoming HTTP request then access should be denied.
  • ExceptFromLocalhost(): This rule specifies that HTTP requests from the local host should be allowed. This might be useful when debugging the application.
  • ExceptFromIPAddresses(IList<IPAddress> ipAddresses): This rule enables access to a list of specific IP addresses.
  • ExceptFromIPAddressRanges(IList<CIDRNotation> cidrNotations): This rule enables access to a list of specific IP address ranges (CIDR notations).
  • ExceptFromCloudflare(string ipv4Url = null, string ipv6Url = null): This rule enables access to requests from Cloudflare servers.
  • ExceptFromCountry(IList<CountryCode> allowedCountries): This rule enables access to requests which originated from one of the specified countries.
  • ExceptWhen(Func<HttpContext, bool> filter): This rule enables a custom request filter to be applied (see Custom Filter Rules for more info).

A HTTP request only needs to satisfy a single rule in order to pass the Firewall access control layer. The reverse order of the rules specifies the order in which an incoming HTTP request gets validated. It is advisable to specify simple/quick rules at the end as they will get executed first.

Cloudflare Support

If an ASP.NET Core web application is going to sit behind Cloudflare then Firewall can be configured with the built-in ExceptFromCloudflare() Firewall rule:

using Firewall;

namespace BasicApp
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseFirewall(
                FirewallRulesEngine
                    .DenyAllAccess()
                    .ExceptFromCloudflare());

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

The ExceptFromCloudflare() configuration method will automatically pull the latest list of IPv4 and IPv6 address ranges from Cloudflare and register the FirewallMiddleware with those values.

Optionally one can specify custom URLs to load the correct IP address ranges from:

using Firewall;

namespace BasicApp
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseFirewall(
                FirewallRulesEngine
                    .DenyAllAccess()
                    .ExceptFromCloudflare(
                        ipv4ListUrl: "https://www.cloudflare.com/ips-v4",
                        ipv6ListUrl: "https://www.cloudflare.com/ips-v6"
                    ));

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        }
    }
}

Custom Rules

Custom Firewall rules can be added by creating a new class which implements IFirewallRule.

For example, if one would like to create a new Firewall rule which filters requests based on Cloudflare's CF-IPCountry HTTP header, then you'd start by implementing a new class which implements the IFirewallRule interface:

public class IPCountryRule : IFirewallRule
{
    private readonly IFirewallRule _nextRule;
    private readonly IList<string> _allowedCountryCodes;

    public IPCountryRule(
        IFirewallRule nextRule,
        IList<string> allowedCountryCodes)
    {
        _nextRule = nextRule;
        _allowedCountryCodes = allowedCountryCodes;
    }

    public bool IsAllowed(HttpContext context)
    {
        const string headerKey = "CF-IPCountry";

        if (!context.Request.Headers.ContainsKey(headerKey))
            return _nextRule.IsAllowed(context);

        var countryCode = context.Request.Headers[headerKey].ToString();
        var isAllowed = _allowedCountryCodes.Contains(countryCode);

        return isAllowed || _nextRule.IsAllowed(context);
    }
}

The constructor of the IPCountryRule class takes in a list of allowed country codes and the next rule in the pipeline. If a HTTP request originated from an allowed country then the custom rule will return true, otherwise it will invoke the next rule of the rules engine.

In order to chain this rule into the existing rules engine one can add an additional extension method:

public static class FirewallRulesEngineExtensions
{
    public static IFirewallRule ExceptFromCountryCodes(
        this IFirewallRule rule,
        IList<string> allowedCountryCodes)
    {
        return new IPCountryRule(rule, allowedCountryCodes);
    }
}

Afterwards the rule can be enabled by calling ExceptFromCountryCodes(allowedCountryCodes) during application setup:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseFirewall(
            FirewallRulesEngine
                .DenyAllAccess()
                .ExceptFromCountryCodes(new [] { "US", "GB", "JP" })
                .ExceptFromCloudflare());

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

Custom Filter Rules

Another slightly less flexible, but much easier and quicker way of applying a custom rule is by using the ExceptWhen(Func<HttpContext, bool> filter) configuration method. The ExceptWhen method can be used to set up simple rules by providing a Func<HttpContext, bool> predicate:

var adminIP = IPAddress.Parse("1.2.3.4");

app.UseFirewall(
    FirewallRulesEngine
        .DenyAllAccess()
        .ExceptFromCloudflare()
        .ExceptWhen(ctx => ctx.Connection.RemoteIpAddress == adminIP));

Custom RequestDelegate for blocked requests

By default the Firewall middleware will return a 403 Forbidden plain text HTTP response for blocked requests. The (optional) accessDeniedDelegate parameter of the UseFirewall extension method can be used to override the default behaviour:

app.UseFirewall(
    FirewallRulesEngine
        .DenyAllAccess()
        .ExceptFromCloudflare(),
    accessDeniedDelegate:
        ctx =>
        {
            ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
            return ctx.Response.WriteAsync("Forbidden");
        });

Miscellaneous

IP Address and CIDR Notation Parsing

The easiest way to generate a custom list of IPAddress or CIDRNotation objects is by making use of the IPAddress.Parse("0.0.0.0") and CIDRNotation.Parse("0.0.0.0/32") helper methods.

X-Forwarded-For HTTP Header

If you have other proxies sitting between Cloudflare and the origin server (e.g. load balancer) then you'll have to enable the ForwardedHeader middleware, which will make sure that the correct IP address will be assigned to the RemoteIpAddress property of the HttpContext.Connection object:

public void Configure(IApplicationBuilder app)
{
    app.UseForwardedHeaders(
        new ForwardedHeadersOptions
        {
            ForwardedHeaders = ForwardedHeaders.XForwardedFor,
            ForwardLimit = 1
        }
    );

    // Register Firewall after error handling and forwarded headers,
    // but before other middleware:
    app.UseCloudflareFirewall();

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

Please be aware that the ForwardedHeaders middleware must be registered before the FirewallMiddleware and also that it is not recommended to set the ForwardedLimit to a value greater than 1 unless you also provide a list of trusted proxies.

Loading Rules from Configuration

Firewall doesn't prescribe a certain way of how to configure rules outside of the rules engine. It is up to an application author to decide how rules should be loaded from an external configuration provider. ASP.NET Core offers a wealth of default configuration providers which are recommended to use.

Example:

public class Startup
{
    private readonly IConfiguration _config;

    public void Configure(IApplicationBuilder app)
    {
        // Load custom config settings from whichever provider has been set up:
        var enableLocalhost = _config.GetValue("AllowRequestsFromLocalhost", false);
        var adminIPAddress = _config.GetValue<string>("AdminIPAddress", null);

        // Configure default Firewall rules:
        var firewallRules =
            FirewallRulesEngine.DenyAllAccess();

        // Add rules according to the config:
        if (enableLocalhost)
            firewallRules = firewallRules.ExceptFromLocalhost();

        if (adminIPAddress != null)
            firewallRules = firewallRules.ExceptFromIPAddresses(new [] { IPAddress.Parse(adminIPAddress) });

        // Enable Firewall with the configured rules:
        app.UseFirewall(firewallRules);

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

Diagnostics

Firewall logs all denied requests using ASP.NET Core's logging API with log level Warning.

If you're having troubles with Firewall and you want to get more insight into which requests are being blocked by the Firewall and for which reasons then you can turn up the log level to Debug and retrieve more detailed diagnostics:

// In this example Serilog is used to log to the console,
// but any .NET Core logger will work:
public class Program
{
    public static void Main(string[] args)
    {
        RunWebServer(args);
    }

    public static void RunWebServer(string[] args)
    {
        Log.Logger =
            new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .CreateLogger();
        WebHost
            .CreateDefaultBuilder(args)
            .UseSerilog()
            .UseStartup<Startup>()
            .Build()
            .Run();
    }
}

Sample console output when log level is set to Debug:

Hosting environment: Development
Content root path: /Redacted/Firewall/samples/BasicApp
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
[09:04:31 DBG] Connection id "0HLHNUJVHUQLD" started.
[09:04:31 DBG] Connection id "0HLHNUJVHUQLE" started.
[09:04:31 INF] Request starting HTTP/1.1 GET http://localhost:5000/
[09:04:31 DBG] Wildcard detected, all requests with hosts will be allowed.
[09:04:31 DBG] Firewall.CountryRule: Remote IP Address '::1' has been denied access, because it couldn't be verified against the current GeoIP2 database..
[09:04:31 DBG] Firewall.IPAddressRule: Remote IP Address '::1' has been denied access, because it didn't match any known IP address.
[09:04:31 DBG] Firewall.IPAddressRangeRule: Remote IP Address '::1' has been denied access, because it didn't belong to any known address range.
[09:04:31 DBG] Firewall.IPAddressRule: Remote IP Address '::1' has been denied access, because it didn't match any known IP address.
[09:04:31 DBG] Firewall.IPAddressRangeRule: Remote IP Address '::1' has been denied access, because it didn't belong to any known address range.
[09:04:31 DBG] Firewall.LocalhostRule: Remote IP Address '::1' has been granted access, because it originated on localhost.
[09:04:31 DBG] Connection id "0HLHNUJVHUQLD" completed keep alive response.
[09:04:31 INF] Request finished in 40.3263ms 200

Contributing

Feedback is welcome and pull requests get accepted!

Support

If you've got value from any of the content which I have created, but pull requests are not your thing, then I would also very much appreciate your support by buying me a coffee.

Buy Me A Coffee

License

Apache 2.0

Credits

Logo is made by Smashicons from Flaticon and is licensed under Creative Commons 3.0.

firewall's People

Contributors

dustinmoris avatar user135711 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

firewall's Issues

Only deny certain IP addresses

Hi,

I looked through the documentation and source codes, I could not find a built-in way to only block certain IP addresses.

Is it the best way to implement a custom rule by inheriting IFirewallRule and add the logic there?

Question

is it possible to protect a specific api's using filter ? also all the requests are coming from cloudflare too

Add support for async rules

I need to support dynamic rules loaded from a DB. Could we get an IAsyncFirewallRule interface where IsAllowed returns ValueTask<bool>?

Logging?

It would be great if we could log the request from a blocked IP. This is great for analytics, forensics and also to see if perhaps there are some other IPs we should consider allowing (through log analysis). Perhaps ILogger can be injected into the middleware constructor.

Perhaps pass an options via an Action<> param.... i.e.

app.UserFirewall(opts => opts.EnableLogging = true);

and/or

var opts = new FirewallOptions(); app.UserFirewall(opts);

X-Forwarded-For HTTP Header isn't used

I think that the value of X-Forwarded-For is not set in the RemoteIpAddress as stated in the doc. A custom rule is required for load balancers forwarding this header.

Possible to block specific methods/controllers?

Thanks for a nice nuget!

Is it possible to configure it so that it is not on an application leven but more detailed, like having specific IP requirements for a controller/method?

Thanks.

Except From Localhost not allowing requests from "::1"

I might be missing something but it seems when I hit my service from localhost the remote Ip address is set to "::1" which is not allowed (see screenshot below)

image

Is this not supposed to be allowed as part of the localhost rule?

ExceptFromCloudflare seems to have no effect

 app.UseFirewall(FirewallRulesEngine.DenyAllAccess()
                                            .ExceptFromCloudflare("https://www.cloudflare.com/ips-v4", "https://www.cloudflare.com/ips-v6")
                                            .ExceptFromLocalhost());

Following this example from the documentation in my experience resulted in all requests through Cloudflare being denied. What I am missing?

MVC sample

@dustinmoris,
Please can you upload a MVC sample to this repository?
If country or IP address is not allowed show blocked error page.
if allowed IP continue to home page ๐Ÿ‘

Block Specific Hosts

Would be great to allow an exclusion style lists as well. Say something like:

DontAllow(List<CIDRs>);
DontAllow(List<IPAddress>);
DontAllow(List<CountryCode>);

Right now, if I want to allow every country, say, except for Micronesia I have to list EVERY country but Micronesia. Same thing if I want to only block a few IPs... That said - great library!!! Thanks for your work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.