-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AbuseIPDB is an excellent service to add a caching layer in front. With the free tier you have 1,000 checks per day - checking the same IP over and over will quickly exhaust these. - #8 Add cached client utilizing a memory cache based on LazyCache. The cached client is added in it's own package. - #9 Add Readme.md to packages nuget.org supports [Readme's in nuget packages](https://devblogs.microsoft.com/nuget/add-a-readme-to-your-nuget-package/). Go ahead and bundle the readme with both packages. - #10 Extend unittest coverage Add more extensive unit tests.
- Loading branch information
Showing
17 changed files
with
969 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,113 @@ | ||
# kenc.abuseipdb | ||
Wrapper around abuseipdb.com API | ||
Wrapper around abuseipdb.com API for .net | ||
|
||
## Getting started | ||
|
||
```Powershell | ||
PM> Install-Package Kenc.AbuseIPDB | ||
``` | ||
|
||
Kenc.AbuseIPDb is built with dependency-injection as a first-class-citizen. | ||
As a result, there's a helper function to register the library including pointing to the configuration section, if IConfiguration is being utilized. | ||
|
||
```C# | ||
services.AddAbuseIPDBClient(Configuration.GetSection("AbuseIPDB")); | ||
``` | ||
|
||
with the configuration section "AbuseIPDB" having the following settings: | ||
```JSON | ||
"AbuseIPDB": { | ||
"APIKey": "<APIKey>", | ||
"APIEndpoint": "https://api.abuseipdb.com/api/v2/" | ||
} | ||
``` | ||
|
||
_Note_: Don't embed your API key with your source code, load it from [keyvault](https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-5.0) or another secure storage. | ||
|
||
### Using the client | ||
With the client registered with dependency injection, add IAbuseIPDBClient abuseIPDBClient to your constructor, eg: | ||
```C# | ||
private readonly ILogger<HomeController> _logger; | ||
private readonly IAbuseIPDBClient _abuseIPDBClient; | ||
private readonly IHttpContextAccessor _httpContextAccessor; | ||
|
||
public HomeController(ILogger<HomeController> logger, IAbuseIPDBClient abuseIPDBClient, IHttpContextAccessor httpContextAccessor) | ||
{ | ||
_logger = logger; | ||
_abuseIPDBClient = abuseIPDBClient ?? throw new ArgumentNullException(nameof(abuseIPDBClient)); | ||
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); | ||
} | ||
``` | ||
|
||
In an ASP.net MVC app, to check for an abusive IP: | ||
```C# | ||
[HttpPost] | ||
public async Task<IActionResult> PostComment(CommentModel comment) | ||
{ | ||
var ip = _httpContextAccessor.HttpContext.Features.Get<IHttpConnectionFeature>()?.RemoteIpAddress; | ||
try | ||
{ | ||
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); | ||
(Check abuseCheck, _) = await _abuseIPDBClient.CheckAsync(ip.ToString(), 90, false, cancellationTokenSource.Token); | ||
|
||
if (abuseCheck.AbuseConfidenceScore > 70) | ||
{ | ||
_logger.LogWarning($"{nameof(HomeController)} refused comment by {ip} due to high abuse confidence score."); | ||
var error = new ErrorModel | ||
{ | ||
Title = "Bad Request.", | ||
Detail = "User IP is blocked due to abuse.", | ||
Instance = $"/comment/{Activity.Current?.Id ?? httpContextAccessor.HttpContext.TraceIdentifier}", | ||
Status = 400, | ||
Type = "/comment/abusiveip" | ||
}; | ||
|
||
// return a 400 with the error information | ||
return Unauthorized(error); | ||
} | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
_logger.LogError($"{nameof(HomeController)}: Failed to check AbuseIPDb within configured timeout."); | ||
} | ||
catch (ApiException abuseIPException) | ||
{ | ||
_logger.LogError($"{nameof(HomeController)}: Caught error with AbuseIPDB: {abuseIPException.Message}"); | ||
} | ||
} | ||
``` | ||
|
||
## Cached client | ||
|
||
In case you have a website with a significant amount of traffic, or where users are expected to send multiple requests, consider using the cached IAbuseIPDBClient. This adds a memory cache in-front, so only a single lookup per IP/block is made. | ||
|
||
```Powershell | ||
PM> Install-Package Kenc.AbuseIPDB | ||
``` | ||
|
||
Register it with dependency injection using: | ||
|
||
```C# | ||
services.AddAbuseIPDBClientCache(Configuration.GetSection("AbuseIPDB"), Configuration.GetSection("AbuseIPDBCache")); | ||
``` | ||
|
||
The configuration section can be used to configure for how long values are cached. | ||
|
||
| Name | Default | | ||
| ----------------------- | -------- | | ||
| CheckCacheLifetime | 1 hour | | ||
| CheckBlockCacheLifetime | 1 hour | | ||
| BlackListCacheLifetime | 24 hours | | ||
|
||
Configuration values are deserialized into TimeSpan. By default these follow [ISO 8601 durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) | ||
|
||
The following sets the configurations to keep Check() responses for 1 minute, CheckBlock() responses for 2 hours and 30 minutes and lastly BlackList() checks for 36 hours. | ||
```json | ||
{ | ||
"AbuseIPDBCache": { | ||
"CheckCacheLifetime": "PT1M", | ||
"CheckBlockCacheLifetime": "PT2H30M", | ||
"BlackListCacheLifetime": "PT36H" | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
namespace Kenc.AbuseIPDB.Cache | ||
{ | ||
using System; | ||
|
||
/// <summary> | ||
/// Configuration of Abuse IP DB cache. | ||
/// </summary> | ||
public class CacheConfiguration | ||
{ | ||
/// <summary> | ||
/// Gets or sets how long to cache the results for Check operations. | ||
/// </summary> | ||
public TimeSpan CheckCacheLifetime { get; set; } = TimeSpan.FromMinutes(60); | ||
|
||
/// <summary> | ||
/// Gets or sets how long to cache the results for CheckBlock operations. | ||
/// </summary> | ||
public TimeSpan CheckBlockCacheLifetime { get; set; } = TimeSpan.FromMinutes(60); | ||
|
||
/// <summary> | ||
/// Gets or sets how long to cache the results for blacklists. | ||
/// </summary> | ||
public TimeSpan BlackListCacheLifetime { get; set; } = TimeSpan.FromHours(24); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
namespace Kenc.AbuseIPDB.Cache | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Kenc.AbuseIPDB.Entities; | ||
using Kenc.AbuseIPDB.Replies; | ||
using LazyCache; | ||
using Microsoft.Extensions.Options; | ||
|
||
/// <summary> | ||
/// Cache layer around <see cref="IAbuseIPDBClient"/>. | ||
/// </summary> | ||
/// <inheritdoc/> | ||
public class CachedAbuseIPDBClient : IAbuseIPDBClient | ||
{ | ||
private readonly IAppCache cache; | ||
private readonly IAbuseIPDBClient client; | ||
private readonly IOptions<CacheConfiguration> cacheConfiguration; | ||
private readonly ReaderWriterLockSlim rwl = new(); | ||
|
||
private RateLimit latestCheckRateLimit; | ||
private RateLimit latestBlackListRateLimit; | ||
private RateLimit latestCheckBlockRateLimit; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="CachedAbuseIPDBClient"/> class. | ||
/// </summary> | ||
/// <param name="client">Client to use for outgoing connections.</param> | ||
/// <param name="cacheConfiguration">Configuration of the cache.</param> | ||
/// <param name="cache">Instance of IAppCache.</param> | ||
internal CachedAbuseIPDBClient(IAbuseIPDBClient client, IOptions<CacheConfiguration> cacheConfiguration, IAppCache cache = null) | ||
{ | ||
this.client = client ?? throw new ArgumentNullException(nameof(client)); | ||
this.cache = cache ?? new CachingService(); | ||
|
||
if (cacheConfiguration.Value == null) | ||
{ | ||
throw new ArgumentNullException(nameof(cacheConfiguration)); | ||
} | ||
|
||
this.cacheConfiguration = cacheConfiguration; | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="CachedAbuseIPDBClient"/> class. | ||
/// </summary> | ||
/// <param name="httpClient">HTTPClient to use for connections.</param> | ||
/// <param name="clientConfiguration">Configuration of <see cref="AbuseIPDBClient"/>.</param> | ||
/// <param name="cacheConfiguration">Configuration of cache.</param> | ||
/// <param name="cache">Instance of IAppCache.</param> | ||
public CachedAbuseIPDBClient(HttpClient httpClient, IOptions<AbuseIPDBClientSettings> clientConfiguration, IOptions<CacheConfiguration> cacheConfiguration, IAppCache cache = null) | ||
{ | ||
this.cache = cache ?? new CachingService(); | ||
if (cacheConfiguration.Value == null) | ||
{ | ||
throw new ArgumentNullException(nameof(cacheConfiguration)); | ||
} | ||
|
||
this.cacheConfiguration = cacheConfiguration; | ||
client = new AbuseIPDBClient(httpClient, clientConfiguration); | ||
} | ||
|
||
public async Task<(IReadOnlyList<BlackListEntry> Data, BlackListMetadata Metadata, RateLimit RateLimit)> BlackListAsync(int confidenceMinimum = 100, int limit = 10000, CancellationToken cancellationToken = default) | ||
{ | ||
(IReadOnlyList<BlackListEntry> data, BlackListMetadata metadata) = await cache.GetOrAddAsync($"blacklist_{confidenceMinimum}_{limit}", async (item) => | ||
{ | ||
item.AbsoluteExpirationRelativeToNow = cacheConfiguration.Value.BlackListCacheLifetime; | ||
(IReadOnlyList<BlackListEntry> data, BlackListMetadata metadata, RateLimit rateLimit) = await client.BlackListAsync(confidenceMinimum, limit, cancellationToken); | ||
|
||
if (rwl.TryEnterWriteLock(1000)) | ||
{ | ||
try | ||
{ | ||
latestBlackListRateLimit = rateLimit; | ||
} | ||
finally | ||
{ | ||
rwl.ExitWriteLock(); | ||
} | ||
} | ||
|
||
return (data, metadata); | ||
}); | ||
|
||
|
||
if (rwl.TryEnterReadLock(1000)) | ||
{ | ||
try | ||
{ | ||
return (data, metadata, latestBlackListRateLimit); | ||
} | ||
finally | ||
{ | ||
rwl.ExitReadLock(); | ||
} | ||
} | ||
|
||
return (data, metadata, null); | ||
} | ||
|
||
public async Task<(Check Data, RateLimit RateLimit)> CheckAsync(string ipAddress, int maxAgeInDays, bool verbose, CancellationToken cancellationToken) | ||
{ | ||
Check data = await cache.GetOrAddAsync(ipAddress, async (item) => | ||
{ | ||
item.AbsoluteExpirationRelativeToNow = cacheConfiguration.Value.CheckCacheLifetime; | ||
(Check Data, RateLimit rateLimit) = await client.CheckAsync(ipAddress, maxAgeInDays, verbose, cancellationToken); | ||
|
||
if (rwl.TryEnterWriteLock(1000)) | ||
{ | ||
try | ||
{ | ||
latestCheckRateLimit = rateLimit; | ||
} | ||
finally | ||
{ | ||
rwl.ExitWriteLock(); | ||
} | ||
} | ||
|
||
return Data; | ||
}); | ||
|
||
|
||
if (rwl.TryEnterReadLock(1000)) | ||
{ | ||
try | ||
{ | ||
return (data, latestCheckRateLimit); | ||
} | ||
finally | ||
{ | ||
rwl.ExitReadLock(); | ||
} | ||
} | ||
|
||
return (data, null); | ||
} | ||
|
||
public async Task<(CheckBlockData data, RateLimit rateLimit)> CheckBlockAsync(string ipBlock, int maxAgeInDays = 30, CancellationToken cancellationToken = default) | ||
{ | ||
CheckBlockData data = await cache.GetOrAddAsync(ipBlock, async (item) => | ||
{ | ||
item.AbsoluteExpirationRelativeToNow = cacheConfiguration.Value.CheckBlockCacheLifetime; | ||
(CheckBlockData Data, RateLimit rateLimit) = await client.CheckBlockAsync(ipBlock, maxAgeInDays, cancellationToken); | ||
|
||
if (rwl.TryEnterWriteLock(1000)) | ||
{ | ||
try | ||
{ | ||
latestCheckBlockRateLimit = rateLimit; | ||
} | ||
finally | ||
{ | ||
rwl.ExitWriteLock(); | ||
} | ||
} | ||
return Data; | ||
}); | ||
|
||
|
||
if (rwl.TryEnterReadLock(1000)) | ||
{ | ||
try | ||
{ | ||
return (data, latestCheckBlockRateLimit); | ||
} | ||
finally | ||
{ | ||
rwl.ExitReadLock(); | ||
} | ||
} | ||
|
||
return (data, null); | ||
} | ||
|
||
public Task<(ReportUpdate Data, RateLimit rateLimit)> ReportAsync(string ip, string comment, Category[] categories, CancellationToken cancellationToken = default) | ||
{ | ||
return client.ReportAsync(ip, comment, categories, cancellationToken); | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/Kenc.AbuseIPDB.Cache/Extensions/DependencyInjection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
namespace Kenc.AbuseIPDB.Cache | ||
{ | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
public static class DependencyInjection | ||
{ | ||
/// <summary> | ||
/// Add <see cref="IAbuseIPDBClient"/> to dependency injection. | ||
/// </summary> | ||
/// <param name="serviceCollection">Service collection to add it to</param> | ||
/// <param name="clientConfiguration">Configuration to parse <see cref="AbuseIPDBClientSettings"/> from.</param> | ||
/// <param name="cacheConfiguration">Configuration to parse <see cref="CacheConfiguration"/> from.</param> | ||
/// <returns>The service collection.</returns> | ||
public static IServiceCollection AddAbuseIPDBClientCache(this IServiceCollection serviceCollection, IConfiguration clientConfiguration, IConfiguration cacheConfiguration) | ||
{ | ||
return serviceCollection.AddSingleton<IAbuseIPDBClient, CachedAbuseIPDBClient>() | ||
.Configure<AbuseIPDBClientSettings>(clientConfiguration) | ||
.Configure<CacheConfiguration>(cacheConfiguration); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||
</PropertyGroup> | ||
|
||
<!-- nuget package properties --> | ||
<PropertyGroup> | ||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> | ||
<PackageId>Kenc.AbuseIPDB.Cache</PackageId> | ||
<PackageDescription>Cache for Kenc.AbuseIPDB based on LazyCache.</PackageDescription> | ||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | ||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||
<PackageProjectUrl>https://github.com/Kencdk/kenc.abuseipdb</PackageProjectUrl> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
|
||
<PackageTags>AbuseIPDB</PackageTags> | ||
<Copyright>2021 Ken Christensen</Copyright> | ||
<Description>Cache for Kenc.AbuseIPDB based on LazyCache.</Description> | ||
|
||
<!-- source link properties --> | ||
<RepositoryType>Github</RepositoryType> | ||
<PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
<EmbedUntrackedSources>true</EmbedUntrackedSources> | ||
<RepositoryUrl>https://github.com/Kencdk/Kenc.abuseipdb/</RepositoryUrl> | ||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="LazyCache" Version="2.1.3" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Kenc.AbuseIPDB\Kenc.AbuseIPDB.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="..\..\README.md" Pack="true" PackagePath="\"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute"> | ||
<_Parameter1>Kenc.AbuseIPDB.Tests</_Parameter1> | ||
</AssemblyAttribute> | ||
</ItemGroup> | ||
</Project> |
Oops, something went wrong.