Skip to content

Commit dd3b3e3

Browse files
authored
Merge pull request #3766 from akto-api-security/feature/toggle-threat-archive-cron
add toggle for archive cron enable
2 parents 547f345 + 8ab7cbc commit dd3b3e3

File tree

10 files changed

+218
-19
lines changed

10 files changed

+218
-19
lines changed

apps/dashboard/src/main/java/com/akto/action/threat_detection/ThreatConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class ThreatConfiguration {
1111
private Actor actor;
1212
private RatelimitConfig ratelimitConfig;
1313
private Integer archivalDays;
14+
private Boolean archivalEnabled;
1415

1516
@lombok.Getter
1617
@lombok.Setter

apps/dashboard/src/main/java/com/akto/action/threat_detection/ThreatConfigurationAction.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
public class ThreatConfigurationAction extends AbstractThreatDetectionAction {
1919

2020
private ThreatConfiguration threatConfiguration;
21+
private Boolean enabled;
2122

2223
public ThreatConfiguration getThreatConfiguration() {
2324
return threatConfiguration;
@@ -26,6 +27,14 @@ public ThreatConfiguration getThreatConfiguration() {
2627
public void setThreatConfiguration(ThreatConfiguration threatConfiguration) {
2728
this.threatConfiguration = threatConfiguration;
2829
}
30+
31+
public Boolean getEnabled() {
32+
return enabled;
33+
}
34+
35+
public void setEnabled(Boolean enabled) {
36+
this.enabled = enabled;
37+
}
2938
// TODO: remove this, use API Executor.
3039
private final CloseableHttpClient httpClient;
3140

@@ -85,4 +94,30 @@ public String modifyThreatConfiguration() {
8594
return ERROR.toUpperCase();
8695
}
8796
}
97+
98+
public String toggleArchivalEnabled() {
99+
HttpPost post =
100+
new HttpPost(
101+
String.format("%s/api/dashboard/toggle_archival_enabled", this.getBackendUrl()));
102+
post.addHeader("Authorization", "Bearer " + this.getApiToken());
103+
post.addHeader("Content-Type", "application/json");
104+
try {
105+
String json = objectMapper.writeValueAsString(java.util.Collections.singletonMap("enabled", this.enabled));
106+
post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
107+
} catch (Exception e) {
108+
e.printStackTrace();
109+
loggerMaker.errorAndAddToDb("Error while toggling archival enabled" + e.getStackTrace());
110+
return ERROR.toUpperCase();
111+
}
112+
try (CloseableHttpResponse resp = this.httpClient.execute(post)) {
113+
String responseBody = EntityUtils.toString(resp.getEntity());
114+
java.util.Map<String, Object> response = objectMapper.readValue(responseBody, java.util.Map.class);
115+
this.enabled = (Boolean) response.get("enabled");
116+
return SUCCESS.toUpperCase();
117+
} catch (Exception e) {
118+
e.printStackTrace();
119+
loggerMaker.errorAndAddToDb("Error while toggling archival enabled" + e.getStackTrace());
120+
return ERROR.toUpperCase();
121+
}
122+
}
88123
}

apps/dashboard/src/main/resources/struts.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9677,6 +9677,39 @@
96779677
</result>
96789678
</action>
96799679

9680+
<action name="api/toggleArchivalEnabled"
9681+
class="com.akto.action.threat_detection.ThreatConfigurationAction"
9682+
method="toggleArchivalEnabled">
9683+
<interceptor-ref name="json" />
9684+
<interceptor-ref name="defaultStack" />
9685+
<interceptor-ref name="usageInterceptor">
9686+
<param name="featureLabel">THREAT_DETECTION</param>
9687+
</interceptor-ref>
9688+
<result name="UNAUTHORIZED" type="json">
9689+
<param name="statusCode">403</param>
9690+
<param name="ignoreHierarchy">false</param>
9691+
<param name="includeProperties">^actionErrors.*</param>
9692+
</result>
9693+
<interceptor-ref name="roleAccessInterceptor">
9694+
<param name="featureLabel">THREAT_PROTECTION</param>
9695+
<param name="accessType">READ_WRITE</param>
9696+
<param name="actionDescription">User toggled archival enabled</param>
9697+
</interceptor-ref>
9698+
<result name="FORBIDDEN" type="json">
9699+
<param name="statusCode">403</param>
9700+
<param name="ignoreHierarchy">false</param>
9701+
<param
9702+
name="includeProperties">^actionErrors.*</param>
9703+
</result>
9704+
<result name="SUCCESS" type="json"> </result>
9705+
<result name="ERROR" type="json">
9706+
<param name="statusCode">422</param>
9707+
<param name="ignoreHierarchy">false</param>
9708+
<param
9709+
name="includeProperties">^actionErrors.*</param>
9710+
</result>
9711+
</action>
9712+
96809713
<action name="api/getActorsCountPerCounty"
96819714
class="com.akto.action.threat_detection.ThreatActorAction"
96829715
method="getActorsCountPerCounty">

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/threat_configuration/ArchivalConfigComponent.jsx

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,50 @@
11
import { useState, useEffect } from "react";
22
import func from "@/util/func"
3-
import { LegacyCard, VerticalStack, Divider, Text, Button, Box } from "@shopify/polaris";
3+
import { LegacyCard, VerticalStack, Divider, Text, Button, Box, Checkbox } from "@shopify/polaris";
44
import api from "../../../pages/threat_detection/api.js";
55
import Dropdown from "../../../components/layouts/Dropdown.jsx";
66

77
const ArchivalConfigComponent = ({ title, description }) => {
88
const [archivalDays, setArchivalDays] = useState(60);
9+
const [archivalEnabled, setArchivalEnabled] = useState(false);
910
const [isSaveDisabled, setIsSaveDisabled] = useState(true);
11+
const [isToggleChanged, setIsToggleChanged] = useState(false);
12+
const [isDaysChanged, setIsDaysChanged] = useState(false);
1013

1114
const fetchData = async () => {
1215
const response = await api.fetchThreatConfiguration();
1316
const days = response?.threatConfiguration?.archivalDays;
1417
const value = days === 30 || days === 60 || days === 90 ? days : 60;
1518
setArchivalDays(value);
19+
20+
const enabled = response?.threatConfiguration?.archivalEnabled || false;
21+
setArchivalEnabled(enabled);
22+
1623
setIsSaveDisabled(true);
24+
setIsToggleChanged(false);
25+
setIsDaysChanged(false);
1726
};
1827

1928
const onSave = async () => {
20-
const payload = {
21-
archivalDays: archivalDays
22-
};
23-
await api.modifyThreatConfiguration(payload).then(() => {
24-
try {
25-
func.setToast(true, false, "Archival time saved successfully");
26-
fetchData()
27-
} catch (error) {
28-
func.setToast(true, true, "Error saving archival time");
29+
try {
30+
// Save archival days if changed
31+
if (isDaysChanged) {
32+
const payload = {
33+
archivalDays: archivalDays
34+
};
35+
await api.modifyThreatConfiguration(payload);
36+
}
37+
38+
// Toggle archival enabled if changed
39+
if (isToggleChanged) {
40+
await api.toggleArchivalEnabled(archivalEnabled);
2941
}
30-
});
42+
43+
func.setToast(true, false, "Archival configuration saved successfully");
44+
fetchData();
45+
} catch (error) {
46+
func.setToast(true, true, "Error saving archival configuration");
47+
}
3148
};
3249

3350
useEffect(() => {
@@ -53,6 +70,13 @@ const ArchivalConfigComponent = ({ title, description }) => {
5370

5471
const onChange = (val) => {
5572
setArchivalDays(val);
73+
setIsDaysChanged(true);
74+
setIsSaveDisabled(false);
75+
};
76+
77+
const onToggleEnabled = (val) => {
78+
setArchivalEnabled(val);
79+
setIsToggleChanged(true);
5680
setIsSaveDisabled(false);
5781
};
5882

@@ -68,6 +92,12 @@ const ArchivalConfigComponent = ({ title, description }) => {
6892
<Divider />
6993
<LegacyCard.Section>
7094
<VerticalStack gap="4">
95+
<Checkbox
96+
label="Enable archival cron"
97+
checked={archivalEnabled}
98+
onChange={onToggleEnabled}
99+
helpText="When enabled, malicious events older than the configured archival time will be automatically archived."
100+
/>
71101
<Box width="200px">
72102
<Dropdown
73103
menuItems={options}

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/api.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ const threatDetectionRequests = {
101101
data: { threatConfiguration: data}
102102
})
103103
},
104+
toggleArchivalEnabled(enabled) {
105+
return request({
106+
url: '/api/toggleArchivalEnabled',
107+
method: 'post',
108+
data: { enabled: enabled }
109+
})
110+
},
104111
fetchThreatCategoryCount(startTs, endTs) {
105112
return request({
106113
url: '/api/fetchThreatCategoryCount',

apps/threat-detection-backend/src/main/java/com/akto/threat/backend/Main.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ public static void main(String[] args) throws Exception {
8383

8484
new BackendVerticle(maliciousEventService, threatActorService, threatApiService, apiDistributionDataService).start();
8585

86-
// ArchiveOldMaliciousEventsCron cron = new ArchiveOldMaliciousEventsCron(threatProtectionMongo);
87-
// cron.runOnce();
88-
// cron.cron();
86+
ArchiveOldMaliciousEventsCron cron = new ArchiveOldMaliciousEventsCron(threatProtectionMongo);
87+
cron.cron();
8988
}
9089

9190
}

apps/threat-detection-backend/src/main/java/com/akto/threat/backend/cron/ArchiveOldMaliciousEventsCron.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ private boolean shouldSkipDatabase(String dbName) {
9797
private void archiveOldMaliciousEvents(String dbName, long nowSeconds) {
9898
String accountId = dbName;
9999

100+
// Check if archival is enabled for this account
101+
if (!isArchivalEnabled(accountId)) {
102+
logger.infoAndAddToDb("Archival is disabled for account " + accountId + ", skipping", LoggerMaker.LogDb.RUNTIME);
103+
return;
104+
}
105+
100106
long retentionDays = fetchRetentionDays(accountId);
101107
long threshold = nowSeconds - (retentionDays * 24 * 60 * 60);
102108

@@ -161,6 +167,20 @@ private void archiveOldMaliciousEvents(String dbName, long nowSeconds) {
161167
}
162168
}
163169

170+
private boolean isArchivalEnabled(String accountId) {
171+
try {
172+
Document doc = ThreatConfigurationDao.instance.getCollection(accountId).find().first();
173+
if (doc == null) return false; // disabled by default
174+
Object val = doc.get("archivalEnabled");
175+
if (val instanceof Boolean) {
176+
return (Boolean) val;
177+
}
178+
} catch (Exception e) {
179+
logger.errorAndAddToDb("Failed fetching archivalEnabled from threat_configuration for account " + accountId + ": " + e.getMessage(), LoggerMaker.LogDb.RUNTIME);
180+
}
181+
return false; // disabled by default
182+
}
183+
164184
private long fetchRetentionDays(String accountId) {
165185
try {
166186
Document doc = ThreatConfigurationDao.instance.getCollection(accountId).find().first();

apps/threat-detection-backend/src/main/java/com/akto/threat/backend/router/DashboardRouter.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.DeleteMaliciousEventsRequest;
2121
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.DeleteMaliciousEventsResponse;
2222
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.FetchTopNDataRequest;
23+
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ToggleArchivalEnabledRequest;
24+
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ToggleArchivalEnabledResponse;
2325
import com.akto.threat.backend.service.MaliciousEventService;
2426
import com.akto.threat.backend.service.ThreatActorService;
2527
import com.akto.threat.backend.service.ThreatApiService;
@@ -232,6 +234,30 @@ public Router setup(Vertx vertx) {
232234
)
233235
).ifPresent(s -> ctx.response().setStatusCode(200).end(s));
234236
});
237+
238+
router
239+
.post("/toggle_archival_enabled")
240+
.blockingHandler(ctx -> {
241+
RequestBody reqBody = ctx.body();
242+
ToggleArchivalEnabledRequest req = ProtoMessageUtils.<
243+
ToggleArchivalEnabledRequest
244+
>toProtoMessage(
245+
ToggleArchivalEnabledRequest.class,
246+
reqBody.asString()
247+
).orElse(null);
248+
249+
if (req == null) {
250+
ctx.response().setStatusCode(400).end("Invalid request");
251+
return;
252+
}
253+
ProtoMessageUtils.toString(
254+
threatActorService.toggleArchivalEnabled(
255+
ctx.get("accountId"),
256+
req
257+
)
258+
).ifPresent(s -> ctx.response().setStatusCode(200).end(s));
259+
});
260+
235261
router
236262
.get("/fetch_filters_for_threat_actors")
237263
.blockingHandler(ctx -> {

apps/threat-detection-backend/src/main/java/com/akto/threat/backend/service/ThreatActorService.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public ThreatConfiguration fetchThreatConfiguration(String accountId) {
9696
int archivalDays = ((Number) archivalDaysObj).intValue();
9797
builder.setArchivalDays(archivalDays);
9898
}
99+
100+
// Handle archivalEnabled
101+
Object archivalEnabledObj = doc.get("archivalEnabled");
102+
if (archivalEnabledObj instanceof Boolean) {
103+
boolean archivalEnabled = (Boolean) archivalEnabledObj;
104+
builder.setArchivalEnabled(archivalEnabled);
105+
}
99106
}
100107
return builder.build();
101108
}
@@ -105,7 +112,7 @@ public ThreatConfiguration modifyThreatConfiguration(String accountId, ThreatCon
105112
MongoCollection<Document> coll = this.threatConfigurationDao.getCollection(accountId);
106113

107114
Document newDoc = new Document();
108-
115+
109116
// Prepare a list of actorId documents
110117
if (updatedConfig.hasActor()) {
111118
List<Document> actorIdDocs = new ArrayList<>();
@@ -120,20 +127,23 @@ public ThreatConfiguration modifyThreatConfiguration(String accountId, ThreatCon
120127
}
121128
newDoc.append("actor", actorIdDocs);
122129
}
123-
130+
124131
// Prepare rate limit config documents using DTO
125132
if (updatedConfig.hasRatelimitConfig()) {
126133
Document ratelimitConfigDoc = RateLimitConfigDTO.toDocument(updatedConfig.getRatelimitConfig());
127134
if (ratelimitConfigDoc != null) {
128135
newDoc.append("ratelimitConfig", ratelimitConfigDoc.get("ratelimitConfig"));
129136
}
130137
}
131-
132-
// Handle archivalDays
138+
139+
// Handle archivalDays - only update if explicitly set (> 0)
133140
if (updatedConfig.getArchivalDays() > 0) {
134141
newDoc.append("archivalDays", updatedConfig.getArchivalDays());
135142
}
136-
143+
144+
// Note: archivalEnabled is now handled by separate endpoint /toggle_archival_enabled
145+
// This prevents other config updates from accidentally resetting it
146+
137147
Document existingDoc = coll.find().first();
138148

139149
if (existingDoc != null) {
@@ -158,10 +168,39 @@ public ThreatConfiguration modifyThreatConfiguration(String accountId, ThreatCon
158168
int archivalDays = ((Number) archivalDaysObj).intValue();
159169
builder.setArchivalDays(archivalDays);
160170
}
171+
// archivalEnabled is handled by separate endpoint, but include in response for frontend
172+
Object archivalEnabledObj = savedDoc.get("archivalEnabled");
173+
if (archivalEnabledObj instanceof Boolean) {
174+
boolean archivalEnabled = (Boolean) archivalEnabledObj;
175+
builder.setArchivalEnabled(archivalEnabled);
176+
}
161177
}
162178
return builder.build();
163179
}
164180

181+
public com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ToggleArchivalEnabledResponse toggleArchivalEnabled(
182+
String accountId,
183+
com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ToggleArchivalEnabledRequest request) {
184+
185+
MongoCollection<Document> coll = this.threatConfigurationDao.getCollection(accountId);
186+
boolean enabled = request.getEnabled();
187+
188+
Document existingDoc = coll.find().first();
189+
Document updateDoc = new Document("$set", new Document("archivalEnabled", enabled));
190+
191+
if (existingDoc != null) {
192+
coll.updateOne(new Document("_id", existingDoc.getObjectId("_id")), updateDoc);
193+
} else {
194+
// Create new document with just archivalEnabled
195+
Document newDoc = new Document("archivalEnabled", enabled);
196+
coll.insertOne(newDoc);
197+
}
198+
199+
return com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ToggleArchivalEnabledResponse.newBuilder()
200+
.setEnabled(enabled)
201+
.build();
202+
}
203+
165204
public void deleteAllMaliciousEvents(String accountId) {
166205
loggerMaker.infoAndAddToDb("Deleting all malicious events for accountId: " + accountId);
167206
maliciousEventDao.getCollection(accountId).drop();

protobuf/threat_detection/service/dashboard_service/v1/service.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,15 @@ message ThreatConfiguration {
343343
Actor actor = 1;
344344
RatelimitConfig ratelimit_config = 2;
345345
int32 archival_days = 3;
346+
bool archival_enabled = 4;
347+
}
348+
349+
message ToggleArchivalEnabledRequest {
350+
bool enabled = 1;
351+
}
352+
353+
message ToggleArchivalEnabledResponse {
354+
bool enabled = 1;
346355
}
347356

348357
message ApiDistributionDataRequestPayload {

0 commit comments

Comments
 (0)