Skip to content

Commit

Permalink
[backend/frontend] If Caldera executor is enabled and Caldera is down…
Browse files Browse the repository at this point in the history
…, the platform should fail to start (#1034)

Co-authored-by: Gael Leblan <[email protected]>
  • Loading branch information
RomuDeuxfois and Dimfacion authored Jul 19, 2024
1 parent 2048007 commit a407bc6
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import io.openbas.executors.caldera.service.CalderaExecutorService;
import io.openbas.integrations.ExecutorService;
import io.openbas.integrations.InjectorService;
import io.openbas.service.PlatformSettingsService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;

Expand All @@ -26,10 +26,14 @@ public class CalderaExecutor {
private final CalderaExecutorContextService calderaExecutorContextService;
private final ExecutorService executorService;
private final InjectorService injectorService;
private final PlatformSettingsService platformSettingsService;

@PostConstruct
public void init() {
CalderaExecutorService service = new CalderaExecutorService(this.executorService, this.client, this.config, this.calderaExecutorContextService, this.endpointService, this.injectorService);
CalderaExecutorService service = new CalderaExecutorService(
this.executorService, this.client, this.config, this.calderaExecutorContextService,
this.endpointService, this.injectorService, this.platformSettingsService
);
if (this.config.isEnable()) {
this.taskScheduler.scheduleAtFixedRate(service, Duration.ofSeconds(60));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import io.openbas.database.model.Executor;
import io.openbas.database.model.Injector;
import io.openbas.executors.caldera.client.CalderaExecutorClient;
import io.openbas.executors.caldera.client.model.Ability;
import io.openbas.executors.caldera.config.CalderaExecutorConfig;
import io.openbas.executors.caldera.model.Agent;
import io.openbas.integrations.ExecutorService;
import io.openbas.integrations.InjectorService;
import io.openbas.service.PlatformSettingsService;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.java.Log;
Expand All @@ -35,6 +35,7 @@
@Log
@Service
public class CalderaExecutorService implements Runnable {

private static final int CLEAR_TTL = 1800000; // 30 minutes
private static final int DELETE_TTL = 86400000; // 24 hours
private static final String CALDERA_EXECUTOR_TYPE = "openbas_caldera";
Expand All @@ -47,6 +48,7 @@ public class CalderaExecutorService implements Runnable {
private final CalderaExecutorContextService calderaExecutorContextService;

private final InjectorService injectorService;
private final PlatformSettingsService platformSettingsService;

private Executor executor = null;

Expand Down Expand Up @@ -74,12 +76,13 @@ public CalderaExecutorService(
CalderaExecutorConfig config,
CalderaExecutorContextService calderaExecutorContextService,
EndpointService endpointService,
InjectorService injectorService
) {
InjectorService injectorService,
PlatformSettingsService platformSettingsService) {
this.client = client;
this.endpointService = endpointService;
this.calderaExecutorContextService = calderaExecutorContextService;
this.injectorService = injectorService;
this.platformSettingsService = platformSettingsService;
try {
if (config.isEnable()) {
this.executor = executorService.register(config.getId(), CALDERA_EXECUTOR_TYPE, CALDERA_EXECUTOR_NAME, getClass().getResourceAsStream("/img/icon-caldera.png"), new String[]{Endpoint.PLATFORM_TYPE.Windows.name(), Endpoint.PLATFORM_TYPE.Linux.name(), Endpoint.PLATFORM_TYPE.MacOS.name()});
Expand All @@ -94,38 +97,43 @@ public CalderaExecutorService(

@Override
public void run() {
log.info("Running Caldera executor endpoints gathering...");
// The executor only retrieve "main" agents (without the keyword "executor")
// This is NOT a standard behaviour, this is because we are using Caldera as an executor and we should not
// Will be replaced by the XTM agent
List<Agent> agents = this.client.agents().stream().filter(agent -> !agent.getExe_name().contains("implant")).toList();
List<Endpoint> endpoints = toEndpoint(agents).stream().filter(Asset::getActive).toList();
log.info("Caldera executor provisioning based on " + endpoints.size() + " assets");
endpoints.forEach(endpoint -> {
List<Endpoint> existingEndpoints = this.endpointService.findAssetsForInjectionByHostname(endpoint.getHostname()).stream().filter(endpoint1 -> Arrays.stream(endpoint1.getIps()).anyMatch(s -> Arrays.stream(endpoint.getIps()).toList().contains(s))).toList();
if (existingEndpoints.isEmpty()) {
Optional<Endpoint> endpointByExternalReference = endpointService.findByExternalReference(endpoint.getExternalReference());
if (endpointByExternalReference.isPresent()) {
this.updateEndpoint(endpoint, List.of(endpointByExternalReference.get()));
try {
log.info("Running Caldera executor endpoints gathering...");
// The executor only retrieve "main" agents (without the keyword "executor")
// This is NOT a standard behaviour, this is because we are using Caldera as an executor and we should not
// Will be replaced by the XTM agent
List<Agent> agents = this.client.agents().stream().filter(agent -> !agent.getExe_name().contains("implant")).toList();
List<Endpoint> endpoints = toEndpoint(agents).stream().filter(Asset::getActive).toList();
log.info("Caldera executor provisioning based on " + endpoints.size() + " assets");
endpoints.forEach(endpoint -> {
List<Endpoint> existingEndpoints = this.endpointService.findAssetsForInjectionByHostname(endpoint.getHostname()).stream().filter(endpoint1 -> Arrays.stream(endpoint1.getIps()).anyMatch(s -> Arrays.stream(endpoint.getIps()).toList().contains(s))).toList();
if (existingEndpoints.isEmpty()) {
Optional<Endpoint> endpointByExternalReference = endpointService.findByExternalReference(endpoint.getExternalReference());
if (endpointByExternalReference.isPresent()) {
this.updateEndpoint(endpoint, List.of(endpointByExternalReference.get()));
} else {
this.endpointService.createEndpoint(endpoint);
}
} else {
this.endpointService.createEndpoint(endpoint);
this.updateEndpoint(endpoint, existingEndpoints);
}
} else {
this.updateEndpoint(endpoint, existingEndpoints);
}
});
List<Endpoint> inactiveEndpoints = toEndpoint(agents).stream().filter(endpoint -> !endpoint.getActive()).toList();
inactiveEndpoints.forEach(endpoint -> {
Optional<Endpoint> optionalExistingEndpoint = this.endpointService.findByExternalReference(endpoint.getExternalReference());
if (optionalExistingEndpoint.isPresent()) {
Endpoint existingEndpoint = optionalExistingEndpoint.get();
if ((now().toEpochMilli() - existingEndpoint.getClearedAt().toEpochMilli()) > DELETE_TTL) {
log.info("Found stale agent " + existingEndpoint.getName() + ", deleting it...");
this.client.deleteAgent(existingEndpoint);
this.endpointService.deleteEndpoint(existingEndpoint.getId());
});
List<Endpoint> inactiveEndpoints = toEndpoint(agents).stream().filter(endpoint -> !endpoint.getActive()).toList();
inactiveEndpoints.forEach(endpoint -> {
Optional<Endpoint> optionalExistingEndpoint = this.endpointService.findByExternalReference(endpoint.getExternalReference());
if (optionalExistingEndpoint.isPresent()) {
Endpoint existingEndpoint = optionalExistingEndpoint.get();
if ((now().toEpochMilli() - existingEndpoint.getClearedAt().toEpochMilli()) > DELETE_TTL) {
log.info("Found stale agent " + existingEndpoint.getName() + ", deleting it...");
this.client.deleteAgent(existingEndpoint);
this.endpointService.deleteEndpoint(existingEndpoint.getId());
}
}
}
});
});
this.platformSettingsService.cleanMessage();
} catch (Exception e) {
this.platformSettingsService.errorMessage("Executor Caldera is not responding, your exercises may be impacted.");
}
}

// -- PRIVATE --
Expand Down Expand Up @@ -184,8 +192,4 @@ private Instant toInstant(@NotNull final String lastSeen) {
ZonedDateTime zonedDateTime = localDateTime.atZone(UTC);
return zonedDateTime.toInstant();
}

private List<Ability> abilities() {
return this.client.abilities();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package io.openbas.injectors.caldera.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.openbas.database.model.Endpoint;
import io.openbas.database.model.Injector;
import io.openbas.injectors.caldera.client.model.Ability;
import io.openbas.injectors.caldera.client.model.Agent;
import io.openbas.injectors.caldera.client.model.Result;
Expand All @@ -13,8 +11,8 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPatch;
Expand All @@ -27,13 +25,13 @@
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
@Service
@Log
public class CalderaInjectorClient {

private static final String KEY_HEADER = "KEY";
Expand Down Expand Up @@ -86,6 +84,7 @@ public List<Agent> agents() {
return this.objectMapper.readValue(jsonResponse, new TypeReference<>() {
});
} catch (IOException e) {
log.severe("Cannot retrieve agent list");
throw new RuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import io.openbas.rest.settings.form.PolicyInput;
import io.openbas.rest.settings.form.ThemeInput;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

import static java.util.Optional.ofNullable;
import static lombok.AccessLevel.NONE;

@Setter
@Getter
@NoArgsConstructor
Expand Down Expand Up @@ -112,4 +112,18 @@ public class PlatformSettings {
@JsonProperty("disabled_dev_features")
private List<String> disabledDevFeatures = new ArrayList<>();

// PLATFORM MESSAGE
@JsonProperty("platform_banner_level")
@Getter(NONE)
private String platformBannerLevel;

public String getPlatformBannerLevel() {
return ofNullable(this.platformBannerLevel)
.map(String::toLowerCase)
.orElse(null);
}

@JsonProperty("platform_banner_text")
private String platformBannerText;

}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ private String getValueFromMapOfSettings(@NotBlank Map<String, Setting> dbSettin

private Setting resolveFromMap(Map<String, Setting> dbSettings, String themeKey, String value) {
Optional<Setting> optionalSetting = ofNullable(dbSettings.get(themeKey));
return resolve(optionalSetting, themeKey, value);
}

private Setting resolve(Optional<Setting> optionalSetting, String themeKey, String value) {
if (optionalSetting.isPresent()) {
Setting updateSetting = optionalSetting.get();
updateSetting.setValue(value);
Expand Down Expand Up @@ -174,10 +178,12 @@ public PlatformSettings findSettings() {
OpenBASPrincipal user = currentUser();
if (user != null) {
platformSettings.setPlatformEnterpriseEdition(
ofNullable(dbSettings.get(PLATFORM_ENTERPRISE_EDITION.key())).map(Setting::getValue).orElse(PLATFORM_ENTERPRISE_EDITION.defaultValue())
ofNullable(dbSettings.get(PLATFORM_ENTERPRISE_EDITION.key())).map(Setting::getValue)
.orElse(PLATFORM_ENTERPRISE_EDITION.defaultValue())
);
platformSettings.setPlatformWhitemark(
ofNullable(dbSettings.get(PLATFORM_WHITEMARK.key())).map(Setting::getValue).orElse(PLATFORM_WHITEMARK.defaultValue())
ofNullable(dbSettings.get(PLATFORM_WHITEMARK.key())).map(Setting::getValue)
.orElse(PLATFORM_WHITEMARK.defaultValue())
);
platformSettings.setMapTileServerLight(openBASConfig.getMapTileServerLight());
platformSettings.setMapTileServerDark(openBASConfig.getMapTileServerDark());
Expand Down Expand Up @@ -219,10 +225,16 @@ public PlatformSettings findSettings() {
platformSettings.setPolicies(policies);

// FEATURE FLAG
if(!openBASConfig.getDisabledDevFeatures().isEmpty()) {
platformSettings.setDisabledDevFeatures(Arrays.stream(openBASConfig.getDisabledDevFeatures().split(",")).toList());
if (!openBASConfig.getDisabledDevFeatures().isEmpty()) {
platformSettings.setDisabledDevFeatures(
Arrays.stream(openBASConfig.getDisabledDevFeatures().split(",")).toList()
);
}

// PLATFORM MESSAGE
platformSettings.setPlatformBannerLevel(getValueFromMapOfSettings(dbSettings, PLATFORM_BANNER_LEVEL.key()));
platformSettings.setPlatformBannerText(getValueFromMapOfSettings(dbSettings, PLATFORM_BANNER_TEXT.key()));

return platformSettings;
}

Expand Down Expand Up @@ -315,4 +327,21 @@ private PlatformSettings updateTheme(ThemeInput input, String themeType) {
return findSettings();
}

// -- PLATFORM MESSAGE --

public void cleanMessage() {
settingRepository.deleteByKeyIn(List.of(PLATFORM_BANNER_LEVEL.key(), PLATFORM_BANNER_TEXT.key()));
}

public void errorMessage(@NotBlank final String message) {
List<Setting> settingsToSave = new ArrayList<>();
Optional<Setting> bannerLevelOpt = this.settingRepository.findByKey(PLATFORM_BANNER_LEVEL.key());
Setting bannerLevel = resolve(bannerLevelOpt, PLATFORM_BANNER_LEVEL.key(), BannerLevel.ERROR.name());
settingsToSave.add(bannerLevel);
Optional<Setting> bannerTextOpt = this.settingRepository.findByKey(PLATFORM_BANNER_TEXT.key());
Setting bannerText = resolve(bannerTextOpt, PLATFORM_BANNER_TEXT.key(), message);
settingsToSave.add(bannerText);
settingRepository.saveAll(settingsToSave);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import io.openbas.executors.caldera.client.model.Ability;
import io.openbas.executors.caldera.config.CalderaExecutorConfig;
import io.openbas.executors.caldera.model.Agent;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -42,16 +41,15 @@ public class CalderaExecutorClient {
// -- AGENTS --

private final static String AGENT_URI = "/agents";
private final HttpSession httpSession;

public List<Agent> agents() {
try {
String jsonResponse = this.get(AGENT_URI);
return this.objectMapper.readValue(jsonResponse, new TypeReference<>() {
});
} catch (IOException e) {
log.severe("Cannot retrive agent list");
return new ArrayList<>();
log.severe("Cannot retrieve agent list");
throw new RuntimeException(e);
}
}

Expand Down
10 changes: 9 additions & 1 deletion openbas-front/src/admin/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { LoggedHelper } from '../actions/helper';
import Loader from '../components/Loader';
import NotFound from '../components/NotFound';
import InjectIndex from './components/simulations/simulation/injects/InjectIndex';
import SystemBanners, { computeBannerSettings } from '../public/components/systembanners/SystemBanners';

const Dashboard = lazy(() => import('./components/Dashboard'));
const IndexProfile = lazy(() => import('./components/profile/Index'));
Expand Down Expand Up @@ -42,7 +43,9 @@ const Index = () => {
const theme = useTheme<Theme>();
const classes = useStyles();
const navigate = useNavigate();
const logged = useHelper((helper: LoggedHelper) => helper.logged());
const { logged, settings } = useHelper((helper: LoggedHelper) => {
return { logged: helper.logged(), settings: helper.getPlatformSettings() };
});
if (logged.isOnlyPlayer) {
navigate('/private');
}
Expand All @@ -54,14 +57,19 @@ const Index = () => {
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
overflowY: 'hidden',
};
useDataLoader();
const { bannerHeight } = computeBannerSettings(settings);
return (
<>
<SystemBanners settings={settings} />
<Box
sx={{
display: 'flex',
minWidth: 1400,
marginTop: bannerHeight,
marginBottom: bannerHeight,
}}
>
<TopBar />
Expand Down
Loading

0 comments on commit a407bc6

Please sign in to comment.