Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for global IP allowlist (e.g. geo-fencing) #128

Open
wants to merge 4 commits into
base: AIK-4641
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ public abstract class ReportingApi {
*/
public abstract Optional<APIResponse> report(String token, APIEvent event, int timeoutInSec);

public record APIListsResponse(List<ListsResponseEntry> blockedIPAddresses, String blockedUserAgents) {}
public record APIListsResponse(
List<ListsResponseEntry> blockedIPAddresses,
List<ListsResponseEntry> allowedIPAddresses,
String blockedUserAgents
) {}
public record ListsResponseEntry(String source, String description, List<String> ips) {}
/**
* Fetch blocked lists using a separate API call, these can include :
* -> blocked IP Addresses (e.g. geo restrictions)
* -> allowed IP Addresses (e.g. geo restrictions)
* -> blocked User-Agents (e.g. bot blocking)
* @param token the authentication token
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ public class ThreadCacheObject {
private final Hostnames hostnames;
private final Routes routes;

// IP Blocking (e.g. Geo-IP Restrictions) :
public record BlockedIpEntry(IPList blocklist, String description) {}
private List<BlockedIpEntry> blockedIps = new ArrayList<>();
// IP restrictions (e.g. Geo-IP Restrictions) :
public record IPListEntry(IPList ipList, String description) {}
private List<IPListEntry> blockedIps = new ArrayList<>();
private List<IPListEntry> allowedIps = new ArrayList<>();
// User-Agent Blocking (e.g. bot blocking) :
private Pattern blockedUserAgentRegex;

Expand Down Expand Up @@ -72,8 +73,13 @@ public boolean isBypassedIP(String ip) {
* Check if the IP is blocked (e.g. Geo IP Restrictions)
*/
public BlockedResult isIpBlocked(String ip) {
for (BlockedIpEntry entry: blockedIps) {
if (entry.blocklist.matches(ip)) {
for (IPListEntry entry: allowedIps) {
if (!entry.ipList.matches(ip)) {
return new BlockedResult(true, entry.description);
}
}
for (IPListEntry entry: blockedIps) {
if (entry.ipList.matches(ip)) {
return new BlockedResult(true, entry.description);
}
}
Expand All @@ -87,7 +93,14 @@ public void updateBlockedLists(Optional<ReportingApi.APIListsResponse> blockedLi
if (res.blockedIPAddresses() != null) {
for (ReportingApi.ListsResponseEntry entry : res.blockedIPAddresses()) {
IPList ipList = createIPList(entry.ips());
blockedIps.add(new BlockedIpEntry(ipList, entry.description()));
blockedIps.add(new IPListEntry(ipList, entry.description()));
}
}
// Update allowed IP addresses (e.g. for geo restrictions) :
if (res.allowedIPAddresses() != null) {
for (ReportingApi.ListsResponseEntry entry: res.allowedIPAddresses()) {
IPList ipList = createIPList(entry.ips());
this.allowedIps.add(new IPListEntry(ipList, entry.description()));
}
}
// Update Blocked User-Agents regex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ void updateConfig_ShouldHandleAllUpdates_WhenApiResponseIsComplete() {
@Test
void testSetForBlockedIpRes() {
assertNull(serviceConfiguration.blockedListsRes);
serviceConfiguration.storeBlockedListsRes(Optional.of(new ReportingApi.APIListsResponse(null, null)));
serviceConfiguration.storeBlockedListsRes(Optional.of(new ReportingApi.APIListsResponse(null, null, null)));
assertNotNull(serviceConfiguration.blockedListsRes);
serviceConfiguration.storeBlockedListsRes(Optional.empty());

Expand Down
62 changes: 57 additions & 5 deletions agent_api/src/test/java/collectors/WebRequestCollectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ void testReport_noThreadCacheObject() {
void testReport_ipBlockedTwice() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.1.1"))
), "");
), null, "");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(new Endpoint(
"GET", "/api/resource", 100, 100,
Expand All @@ -126,6 +126,23 @@ void testReport_ipBlockedTwice() {
void testReport_ipBlockedUsingLists() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("bullshit.ip", "192.168.1.1"))
), null, "");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(), Set.of(), new Routes(), Optional.of(blockedListsRes));
ThreadCache.set(threadCacheObject);


WebRequestCollector.Res response = WebRequestCollector.report(contextObject);

assertNotNull(response);
assertEquals("Your IP address is not allowed to access this resource. (Your IP: 192.168.1.1)", response.msg());
assertEquals(403, response.status());
}
@SetEnvironmentVariable(key = "AIKIDO_TOKEN", value = "test-token")
@Test
void testReport_ipNotAllowedUsingLists() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(null, List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.2.1"))
), "");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(), Set.of(), new Routes(), Optional.of(blockedListsRes));
Expand All @@ -138,13 +155,28 @@ void testReport_ipBlockedUsingLists() {
assertEquals("Your IP address is not allowed to access this resource. (Your IP: 192.168.1.1)", response.msg());
assertEquals(403, response.status());
}
@SetEnvironmentVariable(key = "AIKIDO_TOKEN", value = "test-token")
@Test
void testReport_ipInAllowlist() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(null, List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.1.1", "10.0.0.0/24"))
), "");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(), Set.of(), new Routes(), Optional.of(blockedListsRes));
ThreadCache.set(threadCacheObject);


WebRequestCollector.Res response = WebRequestCollector.report(contextObject);

assertNull(response);
}

@SetEnvironmentVariable(key = "AIKIDO_TOKEN", value = "test-token")
@Test
void testReport_ipNotBlockedUsingListsNorUserAgent() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.1.2", "192.168.1.3"))
), "Unrelated|random");
), null, "Unrelated|random");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(), Set.of(), new Routes(), Optional.of(blockedListsRes));
ThreadCache.set(threadCacheObject);
Expand All @@ -160,7 +192,7 @@ void testReport_ipNotBlockedUsingListsNorUserAgent() {
void testReport_userAgentBlocked() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.1.2", "192.168.1.3"))
), "AI2Bot|hacker");
), null, "AI2Bot|hacker");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(), Set.of(), new Routes(), Optional.of(blockedListsRes));
ThreadCache.set(threadCacheObject);
Expand All @@ -178,7 +210,7 @@ void testReport_userAgentBlocked() {
void testReport_userAgentBlocked_Ip_Bypassed() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("192.168.1.2", "192.168.1.3"))
), "AI2Bot|hacker");
), null, "AI2Bot|hacker");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(),
/* bypassedIps : */ Set.of("192.168.1.1"), new Routes(), Optional.of(blockedListsRes));
Expand All @@ -196,7 +228,27 @@ void testReport_userAgentBlocked_Ip_Bypassed() {
void testReport_ipBlockedUsingLists_Ip_Bypassed() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("bullshit.ip", "192.168.1.1"))
), "");
), null, "");
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(),
/* bypassedIps : */ Set.of("192.168.1.1"), new Routes(), Optional.of(blockedListsRes));
ThreadCache.set(threadCacheObject);


WebRequestCollector.Res response = WebRequestCollector.report(contextObject);

assertNull(response);
assertNull(Context.get());
}

@SetEnvironmentVariable(key = "AIKIDO_TOKEN", value = "test-token")
@Test
void testReport_ipNotAllowedUsingLists_Ip_Bypassed() {
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(
null,
List.of(new ReportingApi.ListsResponseEntry("geoip", "geoip restrictions", List.of("1.2.3.4"))),
""
);
// Mock ThreadCache
threadCacheObject = new ThreadCacheObject(List.of(), Set.of(),
/* bypassedIps : */ Set.of("192.168.1.1"), new Routes(), Optional.of(blockedListsRes));
Expand Down
12 changes: 6 additions & 6 deletions agent_api/src/test/java/thread_cache/ThreadCacheObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void update() {
"fd00:3234:5678:9abc::1/64",
"5.6.7.8/32"
))
), "Test|One")));
), null, "Test|One")));

assertEquals(new ThreadCacheObject.BlockedResult(true, "description"), tCache.isIpBlocked("1.2.3.4"));
assertEquals(new ThreadCacheObject.BlockedResult(false, null), tCache.isIpBlocked("2.3.4.5"));
Expand Down Expand Up @@ -59,9 +59,9 @@ public void updateEmpty() {
"fd00:3234:5678:9abc::1/64",
"5.6.7.8/32"
))
), "Test|One")));
), null, "Test|One")));

tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, null)));
tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, null,null)));

assertEquals(new ThreadCacheObject.BlockedResult(true, "description"), tCache.isIpBlocked("1.2.3.4"));
assertEquals(new ThreadCacheObject.BlockedResult(false, null), tCache.isIpBlocked("2.3.4.5"));
Expand Down Expand Up @@ -96,16 +96,16 @@ public void updateRegexes() {
"fd00:3234:5678:9abc::1/64",
"5.6.7.8/32"
))
), "Test|One")));
), null, "Test|One")));

tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, "")));
tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, null, "")));

assertTrue(tCache.isBlockedUserAgent("This is my TEST user agent"));
assertTrue(tCache.isBlockedUserAgent("Test"));
assertTrue(tCache.isBlockedUserAgent("TEst and ONE"));
assertFalse(tCache.isBlockedUserAgent("Est|On"));
assertFalse(tCache.isBlockedUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"));
tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, "Mozilla")));
tCache.updateBlockedLists(Optional.of(new ReportingApi.APIListsResponse(null, null, "Mozilla")));

assertFalse(tCache.isBlockedUserAgent("This is my TEST user agent"));
assertFalse(tCache.isBlockedUserAgent("Test"));
Expand Down