Skip to content

Commit cbc3c34

Browse files
authored
Merge pull request #63 from tls-attacker/feature/add-scanjobdescription-to-scan-result
Add scanJobDescription ID and timestamp to ScanResult with retrieval method
2 parents 78fd284 + 6c988bf commit cbc3c34

File tree

6 files changed

+199
-9
lines changed

6 files changed

+199
-9
lines changed

src/main/java/de/rub/nds/crawler/data/ScanConfig.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import de.rub.nds.crawler.core.BulkScanWorker;
1212
import de.rub.nds.scanner.core.config.ScannerDetail;
1313
import de.rub.nds.scanner.core.probe.ProbeType;
14-
1514
import java.io.Serializable;
1615
import java.util.List;
1716

@@ -30,15 +29,14 @@ public abstract class ScanConfig implements Serializable {
3029
private List<ProbeType> excludedProbes;
3130

3231
@SuppressWarnings("unused")
33-
private ScanConfig() {
34-
}
32+
private ScanConfig() {}
3533

3634
/**
3735
* Creates a new scan configuration with the specified parameters.
3836
*
3937
* @param scannerDetail The level of detail for the scan
40-
* @param reexecutions The number of times to retry failed scans
41-
* @param timeout The timeout for each scan in seconds
38+
* @param reexecutions The number of times to retry failed scans
39+
* @param timeout The timeout for each scan in seconds
4240
*/
4341
protected ScanConfig(ScannerDetail scannerDetail, int reexecutions, int timeout) {
4442
this(scannerDetail, reexecutions, timeout, null);
@@ -121,9 +119,9 @@ public void setExcludedProbes(List<ProbeType> excludedProbes) {
121119
* Creates a worker for this scan configuration. Each implementation must provide a factory
122120
* method to create the appropriate worker type.
123121
*
124-
* @param bulkScanID The ID of the bulk scan this worker is for
122+
* @param bulkScanID The ID of the bulk scan this worker is for
125123
* @param parallelConnectionThreads The number of parallel connection threads to use
126-
* @param parallelScanThreads The number of parallel scan threads to use
124+
* @param parallelScanThreads The number of parallel scan threads to use
127125
* @return A worker for this scan configuration
128126
*/
129127
public abstract BulkScanWorker<? extends ScanConfig> createWorker(

src/main/java/de/rub/nds/crawler/data/ScanResult.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.annotation.JsonProperty;
1313
import de.rub.nds.crawler.constant.JobStatus;
1414
import java.io.Serializable;
15+
import java.time.Instant;
1516
import java.util.UUID;
1617
import org.bson.Document;
1718

@@ -27,25 +28,35 @@ public class ScanResult implements Serializable {
2728

2829
private final Document result;
2930

31+
private final String scanJobDescriptionId;
32+
33+
private final Instant timestamp;
34+
3035
@JsonCreator
3136
private ScanResult(
37+
@JsonProperty("scanJobDescription") String scanJobDescriptionId,
3238
@JsonProperty("bulkScan") String bulkScan,
3339
@JsonProperty("scanTarget") ScanTarget scanTarget,
3440
@JsonProperty("resultStatus") JobStatus jobStatus,
35-
@JsonProperty("result") Document result) {
41+
@JsonProperty("result") Document result,
42+
@JsonProperty("timestamp") Instant timestamp) {
3643
this.id = UUID.randomUUID().toString();
44+
this.scanJobDescriptionId = scanJobDescriptionId;
3745
this.bulkScan = bulkScan;
3846
this.scanTarget = scanTarget;
3947
this.jobStatus = jobStatus;
4048
this.result = result;
49+
this.timestamp = timestamp != null ? timestamp : Instant.now();
4150
}
4251

4352
public ScanResult(ScanJobDescription scanJobDescription, Document result) {
4453
this(
54+
scanJobDescription.getId().toString(),
4555
scanJobDescription.getBulkScanInfo().getBulkScanId(),
4656
scanJobDescription.getScanTarget(),
4757
scanJobDescription.getStatus(),
48-
result);
58+
result,
59+
Instant.now());
4960
if (scanJobDescription.getStatus() == JobStatus.TO_BE_EXECUTED) {
5061
throw new IllegalArgumentException(
5162
"ScanJobDescription must not be in TO_BE_EXECUTED state");
@@ -86,4 +97,14 @@ public Document getResult() {
8697
public JobStatus getResultStatus() {
8798
return jobStatus;
8899
}
100+
101+
@JsonProperty("scanJobDescription")
102+
public String getScanJobDescriptionId() {
103+
return scanJobDescriptionId;
104+
}
105+
106+
@JsonProperty("timestamp")
107+
public Instant getTimestamp() {
108+
return timestamp;
109+
}
89110
}

src/main/java/de/rub/nds/crawler/persistence/IPersistenceProvider.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,16 @@ public interface IPersistenceProvider {
6161
* @return The scan result, or null if not found.
6262
*/
6363
ScanResult getScanResultById(String dbName, String collectionName, String id);
64+
65+
/**
66+
* Retrieve the most recent scan result by its scan job description ID. If multiple results
67+
* exist for the same scan job description ID, returns the one with the latest timestamp.
68+
*
69+
* @param dbName The database name where the scan result is stored.
70+
* @param collectionName The collection name where the scan result is stored.
71+
* @param scanJobDescriptionId The scan job description ID to search for.
72+
* @return The most recent scan result, or null if not found.
73+
*/
74+
ScanResult getScanResultByScanJobDescriptionId(
75+
String dbName, String collectionName, String scanJobDescriptionId);
6476
}

src/main/java/de/rub/nds/crawler/persistence/MongoPersistenceProvider.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ private JacksonMongoCollection<ScanResult> initResultCollection(
205205
collection.createIndex(Indexes.ascending("scanTarget.hostname"));
206206
collection.createIndex(Indexes.ascending("scanTarget.trancoRank"));
207207
collection.createIndex(Indexes.ascending("scanTarget.resultStatus"));
208+
collection.createIndex(Indexes.ascending("scanJobDescription"));
209+
collection.createIndex(Indexes.descending("timestamp"));
208210
return collection;
209211
}
210212

@@ -343,4 +345,51 @@ public ScanResult getScanResultById(String dbName, String collectionName, String
343345
throw new RuntimeException("Failed to retrieve scan result with ID: " + id, e);
344346
}
345347
}
348+
349+
@Override
350+
public ScanResult getScanResultByScanJobDescriptionId(
351+
String dbName, String collectionName, String scanJobDescriptionId) {
352+
LOGGER.info(
353+
"Retrieving most recent scan result for scanJobDescriptionId {} from collection: {}.{}",
354+
scanJobDescriptionId,
355+
dbName,
356+
collectionName);
357+
358+
try {
359+
var collection = resultCollectionCache.getUnchecked(Pair.of(dbName, collectionName));
360+
361+
var query = new org.bson.Document("scanJobDescription", scanJobDescriptionId);
362+
363+
var iterable = collection.find(query).sort(new org.bson.Document("timestamp", -1));
364+
365+
var iterator = iterable.iterator();
366+
ScanResult result = null;
367+
if (iterator.hasNext()) {
368+
result = iterator.next();
369+
}
370+
371+
if (result == null) {
372+
LOGGER.warn(
373+
"No scan result found for scanJobDescriptionId: {} in collection: {}.{}",
374+
scanJobDescriptionId,
375+
dbName,
376+
collectionName);
377+
} else {
378+
LOGGER.info(
379+
"Retrieved most recent scan result for scanJobDescriptionId: {} from collection: {}.{} (timestamp: {})",
380+
scanJobDescriptionId,
381+
dbName,
382+
collectionName,
383+
result.getTimestamp());
384+
}
385+
386+
return result;
387+
} catch (Exception e) {
388+
LOGGER.error("Exception while retrieving scan result from MongoDB: ", e);
389+
throw new RuntimeException(
390+
"Failed to retrieve scan result for scanJobDescriptionId: "
391+
+ scanJobDescriptionId,
392+
e);
393+
}
394+
}
346395
}

src/test/java/de/rub/nds/crawler/dummy/DummyPersistenceProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,13 @@ public ScanResult getScanResultById(String dbName, String collectionName, String
4646
.findFirst()
4747
.orElse(null);
4848
}
49+
50+
@Override
51+
public ScanResult getScanResultByScanJobDescriptionId(
52+
String dbName, String collectionName, String id) {
53+
return results.stream()
54+
.filter(result -> result.getScanJobDescriptionId().equals(id))
55+
.max((r1, r2) -> r1.getTimestamp().compareTo(r2.getTimestamp()))
56+
.orElse(null);
57+
}
4958
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* TLS-Crawler - A TLS scanning tool to perform large scale scans with the TLS-Scanner
3+
*
4+
* Copyright 2018-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
5+
*
6+
* Licensed under Apache License, Version 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0.txt
8+
*/
9+
package de.rub.nds.crawler.dummy;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
import de.rub.nds.crawler.constant.JobStatus;
14+
import de.rub.nds.crawler.core.BulkScanWorker;
15+
import de.rub.nds.crawler.data.BulkScan;
16+
import de.rub.nds.crawler.data.ScanConfig;
17+
import de.rub.nds.crawler.data.ScanJobDescription;
18+
import de.rub.nds.crawler.data.ScanResult;
19+
import de.rub.nds.crawler.data.ScanTarget;
20+
import de.rub.nds.scanner.core.config.ScannerDetail;
21+
import org.bson.Document;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
25+
class DummyPersistenceProviderTest {
26+
27+
private DummyPersistenceProvider provider;
28+
private BulkScan testBulkScan;
29+
30+
@BeforeEach
31+
void setUp() {
32+
provider = new DummyPersistenceProvider();
33+
34+
// Create a test BulkScan
35+
ScanConfig scanConfig =
36+
new ScanConfig(ScannerDetail.NORMAL, 1, 5000) {
37+
@Override
38+
public BulkScanWorker<? extends ScanConfig> createWorker(
39+
String bulkScanID,
40+
int parallelConnectionThreads,
41+
int parallelScanThreads) {
42+
return null;
43+
}
44+
};
45+
46+
testBulkScan =
47+
new BulkScan(
48+
this.getClass(),
49+
this.getClass(),
50+
"test-scan",
51+
scanConfig,
52+
System.currentTimeMillis(),
53+
false,
54+
null);
55+
testBulkScan.set_id("test-bulk-scan-id");
56+
}
57+
58+
@Test
59+
void testGetScanResultByScanJobDescriptionId_ReturnsMostRecent() throws InterruptedException {
60+
ScanTarget target = new ScanTarget();
61+
target.setHostname("example.com");
62+
target.setIp("93.184.216.34");
63+
target.setPort(443);
64+
65+
ScanJobDescription jobDescription =
66+
new ScanJobDescription(target, testBulkScan, JobStatus.SUCCESS);
67+
String scanJobDescriptionId = jobDescription.getId().toString();
68+
69+
Document resultDoc1 = new Document();
70+
resultDoc1.put("attempt", 1);
71+
ScanResult scanResult1 = new ScanResult(jobDescription, resultDoc1);
72+
provider.insertScanResult(scanResult1, jobDescription);
73+
74+
Thread.sleep(10);
75+
76+
Document resultDoc2 = new Document();
77+
resultDoc2.put("attempt", 2);
78+
ScanResult scanResult2 = new ScanResult(jobDescription, resultDoc2);
79+
provider.insertScanResult(scanResult2, jobDescription);
80+
81+
Thread.sleep(10);
82+
83+
Document resultDoc3 = new Document();
84+
resultDoc3.put("attempt", 3);
85+
ScanResult scanResult3 = new ScanResult(jobDescription, resultDoc3);
86+
provider.insertScanResult(scanResult3, jobDescription);
87+
88+
ScanResult retrieved =
89+
provider.getScanResultByScanJobDescriptionId(
90+
"test-db", "test-collection", scanJobDescriptionId);
91+
92+
assertNotNull(retrieved);
93+
assertEquals(scanJobDescriptionId, retrieved.getScanJobDescriptionId());
94+
95+
assertTrue(retrieved.getTimestamp().compareTo(scanResult1.getTimestamp()) >= 0);
96+
assertTrue(retrieved.getTimestamp().compareTo(scanResult2.getTimestamp()) >= 0);
97+
assertTrue(retrieved.getTimestamp().compareTo(scanResult3.getTimestamp()) >= 0);
98+
99+
assertEquals(scanResult3.getTimestamp(), retrieved.getTimestamp());
100+
}
101+
}

0 commit comments

Comments
 (0)