Skip to content

Commit

Permalink
#30: Add configuration property to opt in to subnets in reserved bloc…
Browse files Browse the repository at this point in the history
…ks getting banned
  • Loading branch information
Aldaviva committed Jun 30, 2023
1 parent 7a1c67a commit e75ccdc
Show file tree
Hide file tree
Showing 11 changed files with 51 additions and 20 deletions.
3 changes: 2 additions & 1 deletion Fail2Ban4Win/Config/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ public class Configuration: ICloneable {
public int? banRepeatedOffenseMax { get; set; }
public LogLevel? logLevel { get; set; }
public ICollection<IPNetwork>? neverBanSubnets { get; set; }
public bool neverBanReservedSubnets { get; set; } = true;
public ICollection<EventLogSelector> eventLogSelectors { get; set; } = null!;

public override string ToString() =>
$"{nameof(maxAllowedFailures)}: {maxAllowedFailures}, {nameof(failureWindow)}: {failureWindow}, {nameof(banPeriod)}: {banPeriod}, {nameof(banSubnetBits)}: {banSubnetBits}, {nameof(banRepeatedOffenseCoefficient)}: {banRepeatedOffenseCoefficient}, {nameof(banRepeatedOffenseMax)}: {banRepeatedOffenseMax}, {nameof(neverBanSubnets)}: [{{{string.Join("}, {", neverBanSubnets ?? new IPNetwork[0])}}}], {nameof(eventLogSelectors)}: [{{{string.Join("}, {", eventLogSelectors)}}}], {nameof(isDryRun)}: {isDryRun}, {nameof(logLevel)}: {logLevel}";
$"{nameof(maxAllowedFailures)}: {maxAllowedFailures}, {nameof(failureWindow)}: {failureWindow}, {nameof(banPeriod)}: {banPeriod}, {nameof(banSubnetBits)}: {banSubnetBits}, {nameof(banRepeatedOffenseCoefficient)}: {banRepeatedOffenseCoefficient}, {nameof(banRepeatedOffenseMax)}: {banRepeatedOffenseMax}, {nameof(neverBanSubnets)}: [{{{string.Join("}, {", neverBanSubnets ?? Array.Empty<IPNetwork>())}}}], {nameof(eventLogSelectors)}: [{{{string.Join("}, {", eventLogSelectors)}}}], {nameof(isDryRun)}: {isDryRun}, {nameof(logLevel)}: {logLevel}";

public object Clone() => new Configuration {
isDryRun = isDryRun,
Expand Down
2 changes: 1 addition & 1 deletion Fail2Ban4Win/Fail2Ban4Win.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
<Version>2.0.18.2</Version>
</PackageReference>
<PackageReference Include="IPNetwork2">
<Version>2.6.589</Version>
<Version>2.6.593</Version>
</PackageReference>
<PackageReference Include="LightInject">
<Version>6.6.4</Version>
Expand Down
6 changes: 3 additions & 3 deletions Fail2Ban4Win/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.1.2.0")]
[assembly: AssemblyFileVersion("1.1.2.0")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]

[assembly: InternalsVisibleTo("Tests")]
[assembly: InternalsVisibleTo("Tests")]
10 changes: 6 additions & 4 deletions Fail2Ban4Win/Services/BanManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ private void onFailure(object sender, IPAddress ipAddress) {

// this runs inside a lock on the SubnetFailureHistory
private bool shouldBan(IPNetwork subnet, SubnetFailureHistory clientFailureHistory) {
if (subnet.IsIANAReserved()) {
LOGGER.Debug("Not banning {0} because it is contained in an IANA-reserved block such as {1}", subnet, IPNetwork.IANA_CBLK_RESERVED1);
if (subnet.IsIANAReserved() && configuration.neverBanReservedSubnets) {
LOGGER.Debug("Not banning {0} because it is contained in an IANA-reserved block such as {1}. To ban anyway, set \"{2}\" to false.", subnet, IPNetwork.IANA_CBLK_RESERVED1,
nameof(configuration.neverBanReservedSubnets));
return false;
}

Expand All @@ -94,7 +95,8 @@ private bool shouldBan(IPNetwork subnet, SubnetFailureHistory clientFailureHisto

IPNetwork? neverBanSubnet = configuration.neverBanSubnets?.FirstOrDefault(neverBan => neverBan.Overlap(subnet));
if (neverBanSubnet is not null) {
LOGGER.Debug("Not banning {0} because it overlaps the {2} subnet in the \"neverBanSubnets\" values in {1}", subnet, ConfigurationModule.CONFIGURATION_FILENAME, neverBanSubnet);
LOGGER.Debug("Not banning {0} because it overlaps the {2} subnet in the \"{3}\" values in {1}", subnet, ConfigurationModule.CONFIGURATION_FILENAME, neverBanSubnet,
nameof(configuration.neverBanSubnets));
return false;
}

Expand All @@ -106,7 +108,7 @@ private bool shouldBan(IPNetwork subnet, SubnetFailureHistory clientFailureHisto
}

if (firewall.Rules.Any(isBanRule(subnet))) {
LOGGER.Debug("Not banning {0} because it is already banned. This is likely caused by receiving many failed requests before our first firewall rule took effect", subnet);
LOGGER.Debug("Not banning {0} because it is already banned. This is likely caused by receiving many failed requests before our first firewall rule took effect.", subnet);
return false;
}

Expand Down
1 change: 1 addition & 0 deletions Fail2Ban4Win/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"67.210.32.33/32",
"73.202.12.148/32"
],
"neverBanReservedSubnets": true,
"eventLogSelectors": [
{
"logName": "Security",
Expand Down
6 changes: 3 additions & 3 deletions Fail2Ban4Win/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
},
"IPNetwork2": {
"type": "Direct",
"requested": "[2.6.589, )",
"resolved": "2.6.589",
"contentHash": "uRYzoXBVoYJiyKa2b9Xeu+7DGlvIDSaQjBvocZBf9VRmsm8UqzxL/l4OrFArGF0/q0qe3dzWSFVGKjVI3XWOLA=="
"requested": "[2.6.593, )",
"resolved": "2.6.593",
"contentHash": "sBc/jnQnrtuIt/OGuCnA1zdFUZpzlxwoCFBfC49q2CHFOxflZsiw6J0bVuDErsS1QkS1dYriEMKeuD51iCY2SQ=="
},
"LightInject": {
"type": "Direct",
Expand Down
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Be aware that `isDryRun` defaults to `true` to avoid accidentally blocking traff
|`banRepeatedOffenseMax`|`4`|An optional limit on how many repeated offenses can be used to calculate ban duration. By default, the 5th offense and subsequent bans will be capped at the same duration as the 4th offense ban, which is 4 days.|
|`logLevel`|`Info`|Optionally adjust the logging verbosity of Fail2Ban4Win. Valid values are `Trace` (most verbose), `Debug`, `Info`, `Warn`, `Error`, and `Fatal` (least verbose). All messages at the given level will be logged, as well as all messages at less verbose levels, _i.e._ `Warn` will also log `Error` and `Fatal` messages. To see the log output, you must run `Fail2Ban4Win.exe` in a console like Command Prompt or PowerShell.|
|`neverBanSubnets`|`[]`|Optional whitelist of IP ranges that should never be banned, regardless of how many auth failures they generate. Each item can be a single IP address, like `67.210.32.33`, or a range, like `67.210.32.0/24`.|
|`neverBanReservedSubnets`|`true`|By default, IP addresses in the reserved blocks `10.0.0.0/8`, `172.16.0.0/12`, and `192.168.0.0/16` will not be banned, to avoid unintentionally blocking LAN access. To allow all three ranges to be banned, change this to `false`. To then selectively prevent some of those ranges from getting banned, you may add them to the `neverBanSubnets` list above. Regardless of this configuration, the loopback address will never be banned.|
|`eventLogSelectors`|`[]`|Required list of events to listen for in Event Log. Each object in the list can have the following properties.<ul><li>`logName`: required, which log in Event Viewer contains the events, _e.g._ `Application`, `Security`, `OpenSSH/Operational`.</li><li>`eventId`: required, numeric ID of event logged on auth failure, _e.g._ `4625` for RDP auth errors.</li><li>`source`: optional Source, AKA Provider Name, of events, _e.g._ `sshd` for Cygwin OpenSSH sshd. If omitted, events will not be filtered by Source.</li><li>`ipAddressEventDataName`: optional, the `Name` of the `Data` element in the event XML's `EventData` in which to search for the client IP address of the auth request, _e.g._ `IpAddress` for RDP. If omitted, the first `Data` element will be searched instead.</li><li>`ipAddressEventDataIndex`: optional, the 0-indexed offset of the `Data` element in the XML's `EventData` in which to search for the client IP address, _e.g._ `3` to search for IP addresses in the fourth `Data` element in `EventData`. Useful if `EventData` has multiple `Data` children, but none of them have a `Name` attribute to specify in `ipAddressEventDataName`, and the IP address doesn't appear in the first one. This offset is applied after any `Name` attribute filtering, and applies whether or not `ipAddressEventDataName` is specified. If omitted, defaults to `0`.</li><li>`ipAddressPattern`: optional, regular expression pattern string that matches the IP address in the `Data` element specified above. Useful if you want to filter out some events from the log with the desired ID and source but that don't describe an auth failure (_e.g._ sshd's disconnect events). If omitted, searches for all IPv4 addresses in the `Data` element's text content. To set [options like case-insensitivity](https://docs.microsoft.com/en-us/dotnet/standard/base-types/miscellaneous-constructs-in-regular-expressions), put `(?i)` at the start of the pattern. Patterns are not anchored to the entire input string unless you surround them with `^` and `$`. If you specify a pattern, ensure the desired IPv4 capture group in your pattern has the name `ipAddress`, _e.g._ <pre lang="regex">Failed: (?&lt;ipAddress&gt;(?:\d{1,3}\\.){3}\d{1,3})</pre></li></ul>See [Handling a new event](#handling-a-new-event) below for a tutorial on creating this object.|
1. After saving the configuration file, restart the Fail2Ban4Win service for your changes to take effect. Note that the service will clear existing bans when it starts.
Expand Down
4 changes: 3 additions & 1 deletion Tests/Config/ConfigurationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public ConfigurationTest(ITestOutputHelper testOutputHelper) {
"67.210.32.33",
"73.202.12.148"
],
"neverBanReservedSubnets": false,
"eventLogSelectors": [
{
"logName": "Security",
Expand All @@ -64,7 +65,7 @@ public ConfigurationTest(ITestOutputHelper testOutputHelper) {

[Fact]
public void parse() {
File.WriteAllText("configuration.json", JSON, Encoding.UTF8);
File.WriteAllText("configuration.json", JSON, new UTF8Encoding(false, true));
testOutputHelper.WriteLine("Wrote {0}", Path.GetFullPath("configuration.json"));

using ServiceContainer context = new();
Expand All @@ -85,6 +86,7 @@ public void parse() {
Assert.Contains(IPNetwork.Parse("192.168.1.0/24"), actual.neverBanSubnets!);
Assert.Contains(IPNetwork.Parse("67.210.32.33/32"), actual.neverBanSubnets!);
Assert.Contains(IPNetwork.Parse("73.202.12.148/32"), actual.neverBanSubnets!);
Assert.False(actual.neverBanReservedSubnets);
Assert.NotNull(actual.ToString());

EventLogSelector[] actualSelectors = actual.eventLogSelectors.ToArray();
Expand Down
4 changes: 2 additions & 2 deletions Tests/Logging/XunitTestOutputTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using NLog.Targets;
using Xunit.Abstractions;

namespace Tests.Logging;
namespace Tests.Logging;

[Target("xUnit")]
public class XunitTestOutputTarget: TargetWithLayout {
Expand All @@ -17,7 +17,7 @@ protected override void Write(LogEventInfo logEvent) {
}

public static void start(ITestOutputHelper testOutputHelper) {
SimpleConfigurator.ConfigureForTargetLogging(new XunitTestOutputTarget { testOutputHelper = testOutputHelper }, LogLevel.Trace);
LogManager.Setup().LoadConfiguration(builder => builder.ForLogger(LogLevel.Trace).WriteTo(new XunitTestOutputTarget { testOutputHelper = testOutputHelper }));
}

}
32 changes: 28 additions & 4 deletions Tests/Services/BanManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,43 @@ public void dontBanAfterInsufficientFailures() {
}

[Fact]
public void dontBanReservedAddress() {
public void dontBanReservedAddressByDefault() {
IPAddress reservedAddress = IPAddress.Parse("192.168.1.1");
for (int i = 0; i < MAX_ALLOWED_FAILURES; i++) {
for (int i = 0; i < MAX_ALLOWED_FAILURES + 1; i++) {
eventLogListener.failure += Raise.With(null, reservedAddress);
}

Assert.Empty(firewallRules);
}

[Fact]
public void dontBanReservedAddressWhenConfigured() {
configuration.neverBanReservedSubnets = true;

IPAddress reservedAddress = IPAddress.Parse("192.168.1.1");
for (int i = 0; i < MAX_ALLOWED_FAILURES + 1; i++) {
eventLogListener.failure += Raise.With(null, reservedAddress);
}

Assert.Empty(firewallRules);
}

[Fact]
public void banReservedAddressWhenConfigured() {
configuration.neverBanReservedSubnets = false;

IPAddress reservedAddress = IPAddress.Parse("192.168.1.1");
for (int i = 0; i < MAX_ALLOWED_FAILURES + 1; i++) {
eventLogListener.failure += Raise.With(null, reservedAddress);
}

Assert.NotEmpty(firewallRules);
}

[Fact]
public void dontBanLoopbackAddress() {
IPAddress reservedAddress = IPAddress.Parse("127.0.0.1");
for (int i = 0; i < MAX_ALLOWED_FAILURES; i++) {
for (int i = 0; i < MAX_ALLOWED_FAILURES + 1; i++) {
eventLogListener.failure += Raise.With(null, reservedAddress);
}

Expand All @@ -85,7 +109,7 @@ public void dontBanLoopbackAddress() {
[Fact]
public void dontBanWhitelistedAddress() {
IPAddress reservedAddress = IPAddress.Parse("73.202.12.148");
for (int i = 0; i < MAX_ALLOWED_FAILURES; i++) {
for (int i = 0; i < MAX_ALLOWED_FAILURES + 1; i++) {
eventLogListener.failure += Raise.With(null, reservedAddress);
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down

0 comments on commit e75ccdc

Please sign in to comment.