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
|
## Get It
|
||||||
|
|
||||||
The project has its own maven repository. It can be added to the `pom.xml`:
|
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.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
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;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -25,6 +27,12 @@ public class CfClientTest {
|
|||||||
|
|
||||||
private final CfDnsClient client = new CfDnsClient(API_EMAIL, API_KEY);
|
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
|
@Test
|
||||||
void testZoneListAnlFailedSldList() throws Exception {
|
void testZoneListAnlFailedSldList() throws Exception {
|
||||||
List<ZoneEntity> zList = client.zoneListAll();
|
List<ZoneEntity> zList = client.zoneListAll();
|
||||||
@@ -62,8 +70,8 @@ public class CfClientTest {
|
|||||||
String domain = randomSld + "." + ZONE_STR;
|
String domain = randomSld + "." + ZONE_STR;
|
||||||
|
|
||||||
RecordEntity r;
|
RecordEntity r;
|
||||||
RecordEntity createdRe1 = null;
|
RecordEntity createdRe1;
|
||||||
RecordEntity createdRe2 = null;
|
RecordEntity createdRe2;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ensure clean state
|
// ensure clean state
|
||||||
|
|||||||
Reference in New Issue
Block a user