Add CfClientPenTest for penetration testing and enhance CfClientTest with environment checks
This commit is contained in:
@@ -25,12 +25,6 @@ This guide comes without any warranty. Use at your own risk. The author is not r
|
||||
|
||||
---
|
||||
|
||||
## State of the Project
|
||||
|
||||
BETA
|
||||
|
||||
---
|
||||
|
||||
## Get It
|
||||
|
||||
The project has its own maven repository. It can be added to the `pom.xml`:
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Penetration-style tests with malicious and invalid inputs to ensure CfDnsClient behaves safely
|
||||
* (throws appropriate exceptions, does not crash, and does not create unintended resources).
|
||||
* <p>
|
||||
* These tests will be skipped if API_EMAIL or API_KEY are not provided via environment variables.
|
||||
*/
|
||||
public class CfClientPenTest {
|
||||
|
||||
private static final String ZONE_STR = "mein-d-ns.de"; // existing baseline zone
|
||||
|
||||
private static final String API_EMAIL = System.getenv("API_EMAIL");
|
||||
private static final String API_KEY = System.getenv("API_KEY");
|
||||
|
||||
@BeforeAll
|
||||
static void checkEnv() {
|
||||
assumeTrue(API_EMAIL != null && !API_EMAIL.isBlank(), "API_EMAIL not set; skipping pen tests");
|
||||
assumeTrue(API_KEY != null && !API_KEY.isBlank(), "API_KEY not set; skipping pen tests");
|
||||
}
|
||||
|
||||
private CfDnsClient newClient() {
|
||||
return new CfDnsClient(API_EMAIL, API_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Invalid credentials should not authenticate and must throw CloudflareApiException")
|
||||
void testInvalidCredentialsShouldFail() {
|
||||
// Use syntactically valid but wrong credentials
|
||||
CfDnsClient badClient = new CfDnsClient("invalid@example.com", UUID.randomUUID().toString());
|
||||
assertThrows(CloudflareApiException.class, badClient::zoneListAll);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Malicious SLD inputs must not crash and should throw proper exception type")
|
||||
void testMaliciousSldPatternsDoNotSucceed() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
|
||||
List<String> syntacticallyInvalidSlds =
|
||||
List.of("; rm -rf /", "| cat /etc/passwd", "`shutdown -h now`",
|
||||
"<script>alert('x')</script>", "\"quoted\"");
|
||||
|
||||
List<String> syntacticallyValidOrNotAllowedFromCloudflare =
|
||||
List.of(".", "..", "../..", "..%2F..%2F", "a".repeat(300), "$(reboot)", "emoji-🥷-忍者",
|
||||
"doesnotexist", "abcdef12345", "unwahrscheinlich-" + System.currentTimeMillis());
|
||||
|
||||
for (String sld : syntacticallyInvalidSlds) {
|
||||
assertThrows(IllegalArgumentException.class, () -> client.sldListAll(zone, sld),
|
||||
"Should throw IllegalArgumentException for invalid SLD '" + sld + "'");
|
||||
}
|
||||
for (String sld : syntacticallyValidOrNotAllowedFromCloudflare) {
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.sldListAll(zone, sld),
|
||||
"Should throw CloudflareNotFoundException for valid but non-existing SLD '" + sld + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Invalid record content and TTL boundaries must be rejected by API")
|
||||
void testInvalidRecordCreateInputsRejected() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
|
||||
String sld = "pentest-" + System.currentTimeMillis();
|
||||
String fqdn = sld + "." + ZONE_STR;
|
||||
|
||||
// Ensure clean state and guarantee cleanup later
|
||||
try {
|
||||
client.recordDeleteTypeIfExists(zone, sld, RecordType.A, RecordType.AAAA, RecordType.CNAME);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
// A record with invalid IPv4
|
||||
assertThrows(CloudflareApiException.class,
|
||||
() -> client.recordCreate(zone, fqdn, 60, RecordType.A, "999.999.999.999"));
|
||||
|
||||
// AAAA record with non-IP content
|
||||
assertThrows(CloudflareApiException.class,
|
||||
() -> client.recordCreate(zone, fqdn, 60, RecordType.AAAA, "not-an-ipv6"));
|
||||
|
||||
// TTL boundary checks
|
||||
assertThrows(CloudflareApiException.class,
|
||||
() -> client.recordCreate(zone, fqdn, -1, RecordType.A, "130.0.0.3"));
|
||||
} finally {
|
||||
// Best-effort cleanup in case anything slipped through
|
||||
assertDoesNotThrow(
|
||||
() -> client.recordDeleteTypeIfExists(zone, sld, RecordType.A, RecordType.AAAA,
|
||||
RecordType.CNAME));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("recordDeleteTypeIfExists must be safe on non-existing SLD and types")
|
||||
void testDeleteTypeIfExistsOnNonExistingIsSafe() throws Exception {
|
||||
CfDnsClient client = newClient();
|
||||
ZoneEntity zone = client.zoneInfo(ZONE_STR);
|
||||
String randomSld = "nonexist-" + System.currentTimeMillis();
|
||||
// Should not throw even if nothing exists
|
||||
assertDoesNotThrow(
|
||||
() -> client.recordDeleteTypeIfExists(zone, randomSld, RecordType.A, RecordType.AAAA));
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Slf4j
|
||||
@@ -25,6 +27,12 @@ public class CfClientTest {
|
||||
|
||||
private final CfDnsClient client = new CfDnsClient(API_EMAIL, API_KEY);
|
||||
|
||||
@BeforeAll
|
||||
static void checkEnv() {
|
||||
assumeTrue(API_EMAIL != null && !API_EMAIL.isBlank(), "API_EMAIL not set; skipping pen tests");
|
||||
assumeTrue(API_KEY != null && !API_KEY.isBlank(), "API_KEY not set; skipping pen tests");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testZoneListAnlFailedSldList() throws Exception {
|
||||
List<ZoneEntity> zList = client.zoneListAll();
|
||||
@@ -62,8 +70,8 @@ public class CfClientTest {
|
||||
String domain = randomSld + "." + ZONE_STR;
|
||||
|
||||
RecordEntity r;
|
||||
RecordEntity createdRe1 = null;
|
||||
RecordEntity createdRe2 = null;
|
||||
RecordEntity createdRe1;
|
||||
RecordEntity createdRe2;
|
||||
|
||||
try {
|
||||
// ensure clean state
|
||||
|
||||
Reference in New Issue
Block a user