Skip to content

Commit

Permalink
[backend/frontend] Introduce inject readiness (#1041)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamuelHassine committed May 31, 2024
1 parent adc1b8f commit ac81b88
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.openbas.injectors.caldera;

import io.openbas.injector_contract.*;
import io.openbas.injector_contract.fields.ContractAsset;
import io.openbas.injector_contract.fields.ContractAssetGroup;
import io.openbas.injector_contract.fields.ContractExpectations;
import io.openbas.injector_contract.fields.ContractSelect;
import io.openbas.injector_contract.fields.*;
import io.openbas.database.model.Endpoint;
import io.openbas.helper.SupportedLanguage;
import io.openbas.injectors.caldera.client.model.Ability;
Expand All @@ -20,6 +17,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.openbas.executors.caldera.service.CalderaExecutorService.toPlatform;
Expand Down Expand Up @@ -119,6 +118,15 @@ private List<Contract> abilityContracts(@NotNull final ContractConfig contractCo
builder.optional(expectationsField);
List<String> platforms = new ArrayList<>();
ability.getExecutors().forEach(executor -> {
String command = executor.getCommand();
if (command != null && !command.isEmpty()) {
Matcher matcher = Pattern.compile("#\\{(.*?)\\}").matcher(command);
while (matcher.find()) {
if (!matcher.group(1).isEmpty()) {
builder.mandatory(ContractText.textField(matcher.group(1), matcher.group(1)));
}
}
}
if (!executor.getPlatform().equals("unknown")) {
String platform = toPlatform(executor.getPlatform()).name();
if (!platforms.contains(platform)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin
List<String> asyncIds = new ArrayList<>();
List<Expectation> expectations = new ArrayList<>();
// Execute inject for all assets
String contract = inject.getInjectorContract().getId();
if (assets.isEmpty()) {
execution.addTrace(traceError("Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)"));
}
String contract = inject.getInjectorContract().getId();
assets.forEach((asset, aBoolean) -> {
try {
Endpoint executionEndpoint = this.findAndRegisterAssetForExecution(injection.getInjection().getInject(), asset);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public class Executor {

private String name;
private String platform;
private String command;
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public class InjectResultDTO {
@JsonProperty("injects_documents")
private List<String> documents;

@JsonProperty("inject_ready")
private Boolean isReady;

@JsonProperty("inject_updated_at")
private Instant updatedAt = now();
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,24 @@ private void executeInternal(ExecutableInject executableInject) {
private void executeInject(ExecutableInject executableInject) {
// Depending on injector type (internal or external) execution must be done differently
Inject inject = executableInject.getInjection().getInject();
if( !inject.isReady() ) {
// Status
if( inject.getStatus().isEmpty() ) {
InjectStatus status = new InjectStatus();
status.getTraces().add(InjectStatusExecution.traceError("The inject is not ready to be executed (missing mandatory fields)"));
status.setName(ExecutionStatus.ERROR);
status.setTrackingSentDate(Instant.now());
status.setInject(inject);
injectStatusRepository.save(status);
} else {
InjectStatus status = inject.getStatus().get();
status.getTraces().add(InjectStatusExecution.traceError("The inject is not ready to be executed (missing mandatory fields)"));
status.setName(ExecutionStatus.ERROR);
status.setTrackingSentDate(Instant.now());
injectStatusRepository.save(status);
}
return;
}
Injector externalInjector = injectorRepository.findByType(inject.getInjectorContract().getInjector().getType()).orElseThrow();
LOGGER.log(Level.INFO, "Executing inject " + inject.getInject().getTitle());
// Executor logics
Expand Down
79 changes: 40 additions & 39 deletions openbas-api/src/main/java/io/openbas/utils/AtomicTestingMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,56 @@

public class AtomicTestingMapper {

public static InjectResultDTO toDtoWithTargetResults(Inject inject) {
List<InjectTargetWithResult> targets = AtomicTestingUtils.getTargetsWithResults(inject);
List<String> targetIds = targets.stream().map(InjectTargetWithResult::getId).toList();
public static InjectResultDTO toDtoWithTargetResults(Inject inject) {
List<InjectTargetWithResult> targets = AtomicTestingUtils.getTargetsWithResults(inject);
List<String> targetIds = targets.stream().map(InjectTargetWithResult::getId).toList();

return getAtomicTestingOutputBuilder(inject)
.targets(targets)
.expectationResultByTypes(AtomicTestingUtils.getExpectationResultByTypes(
getRefinedExpectations(inject, targetIds)
))
.build();
}
return getAtomicTestingOutputBuilder(inject)
.targets(targets)
.expectationResultByTypes(AtomicTestingUtils.getExpectationResultByTypes(
getRefinedExpectations(inject, targetIds)
))
.build();
}

public static InjectResultDTO toDto(Inject inject, List<InjectTargetWithResult> targets) {
List<String> targetIds = targets.stream().map(InjectTargetWithResult::getId).toList();
public static InjectResultDTO toDto(Inject inject, List<InjectTargetWithResult> targets) {
List<String> targetIds = targets.stream().map(InjectTargetWithResult::getId).toList();

return getAtomicTestingOutputBuilder(inject)
.targets(targets)
.expectationResultByTypes(AtomicTestingUtils.getExpectationResultByTypes(
getRefinedExpectations(inject, targetIds)
))
.build();
}
return getAtomicTestingOutputBuilder(inject)
.targets(targets)
.expectationResultByTypes(AtomicTestingUtils.getExpectationResultByTypes(
getRefinedExpectations(inject, targetIds)
))
.build();
}

private static InjectResultDTOBuilder getAtomicTestingOutputBuilder(Inject inject) {
return InjectResultDTO
.builder()
.id(inject.getId())
.title(inject.getTitle())
.description(inject.getDescription())
private static InjectResultDTOBuilder getAtomicTestingOutputBuilder(Inject inject) {
return InjectResultDTO
.builder()
.id(inject.getId())
.title(inject.getTitle())
.description(inject.getDescription())
.content(inject.getContent())
.expectations(inject.getExpectations())
.type(inject.getInjectorContract().getInjector().getType())
.tagIds(inject.getTags().stream().map(Tag::getId).toList())
.type(inject.getInjectorContract().getInjector().getType())
.tagIds(inject.getTags().stream().map(Tag::getId).toList())
.documents(inject.getDocuments().stream().map(InjectDocument::getDocument).map(Document::getId).toList())
.injectorContract(inject.getInjectorContract())
.status(inject.getStatus().orElse(draftInjectStatus()))
.killChainPhases(inject.getKillChainPhases())
.attackPatterns(inject.getAttackPatterns())
.updatedAt(inject.getUpdatedAt());
}
.injectorContract(inject.getInjectorContract())
.status(inject.getStatus().orElse(draftInjectStatus()))
.killChainPhases(inject.getKillChainPhases())
.attackPatterns(inject.getAttackPatterns())
.isReady(inject.isReady())
.updatedAt(inject.getUpdatedAt());
}

public record ExpectationResultsByType(@NotNull ExpectationType type,
@NotNull InjectExpectation.ExpectationStatus avgResult,
@NotNull List<ResultDistribution> distribution) {
public record ExpectationResultsByType(@NotNull ExpectationType type,
@NotNull InjectExpectation.ExpectationStatus avgResult,
@NotNull List<ResultDistribution> distribution) {

}
}

public record ResultDistribution(@NotNull String label, @NotNull Integer value) {
public record ResultDistribution(@NotNull String label, @NotNull Integer value) {

}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useState } from 'react';
import { Alert, Button, Dialog, DialogActions, DialogContent, DialogContentText, Tooltip, Typography } from '@mui/material';
import { PlayArrowOutlined } from '@mui/icons-material';
import { PlayArrowOutlined, SettingsOutlined } from '@mui/icons-material';
import { makeStyles } from '@mui/styles';
import { fetchInjectResultDto, tryAtomicTesting } from '../../../../actions/atomic_testings/atomic-testing-actions';
import AtomicTestingPopover from './AtomicTestingPopover';
Expand Down Expand Up @@ -62,17 +62,30 @@ const AtomicTestingHeader = () => {
</Typography>
</Tooltip>
<div className={classes.actions}>
<Button
style={{ marginRight: 10 }}
startIcon={<PlayArrowOutlined />}
variant="contained"
color="primary"
size="small"
onClick={() => setOpen(true)}
disabled={!availableLaunch}
>
{t('Launch')}
</Button>
{!injectResultDto.inject_ready || !injectResultDto.inject_targets || injectResultDto.inject_targets.length === 0 ? (
<Button
style={{ marginRight: 10 }}
startIcon={<SettingsOutlined />}
variant="contained"
color="warning"
size="small"
onClick={() => setOpenEdit(true)}
>
{t('Configure')}
</Button>
) : (
<Button
style={{ marginRight: 10 }}
startIcon={<PlayArrowOutlined />}
variant="contained"
color="primary"
size="small"
onClick={() => setOpen(true)}
disabled={!availableLaunch}
>
{t('Launch')}
</Button>
)}
<AtomicTestingPopover atomic={injectResultDto} setOpenEdit={setOpenEdit} openEdit={openEdit} />
</div>
<Dialog
Expand Down
10 changes: 3 additions & 7 deletions openbas-front/src/admin/components/common/injects/Injects.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ const Injects = (props) => {
let injectStatus = inject.inject_enabled
? t('Enabled')
: t('Disabled');
if (inject.inject_content === null) {
injectStatus = t('To fill');
if (!inject.inject_ready) {
injectStatus = t('Missing content');
}
return (
<ListItem
Expand Down Expand Up @@ -486,11 +486,7 @@ const Injects = (props) => {
style={inlineStyles.inject_enabled}
>
<ItemBoolean
status={
inject.inject_content === null
? false
: inject.inject_enabled
}
status={inject.inject_ready ? inject.inject_enabled : false}
label={injectStatus}
variant="inList"
/>
Expand Down
39 changes: 36 additions & 3 deletions openbas-front/src/utils/api-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,17 @@ export interface DryInject {

export interface DryInjectStatus {
status_id?: string;
status_name?: "DRAFT" | "INFO" | "QUEUING" | "EXECUTING" | "PENDING" | "PARTIAL" | "ERROR" | "SUCCESS";
status_name?:
| "DRAFT"
| "INFO"
| "QUEUING"
| "EXECUTING"
| "PENDING"
| "PARTIAL"
| "ERROR"
| "MAYBE_PARTIAL_PREVENTED"
| "MAYBE_PREVENTED"
| "SUCCESS";
status_traces?: InjectStatusExecution[];
/** @format date-time */
tracking_ack_date?: string;
Expand Down Expand Up @@ -879,6 +889,7 @@ export interface Inject {
inject_injector_contract?: InjectorContract;
inject_kill_chain_phases?: KillChainPhase[];
inject_payloads?: Asset[];
inject_ready?: boolean;
inject_scenario?: Scenario;
/** @format date-time */
inject_sent_at?: string;
Expand Down Expand Up @@ -1010,6 +1021,7 @@ export interface InjectResultDTO {
inject_injector_contract: InjectorContract;
/** Kill Chain Phases */
inject_kill_chain_phases: KillChainPhase[];
inject_ready?: boolean;
inject_status?: InjectStatus;
/**
* Specifies the categories of targetResults for atomic testing.
Expand All @@ -1027,7 +1039,17 @@ export interface InjectResultDTO {

export interface InjectStatus {
status_id?: string;
status_name?: "DRAFT" | "INFO" | "QUEUING" | "EXECUTING" | "PENDING" | "PARTIAL" | "ERROR" | "SUCCESS";
status_name?:
| "DRAFT"
| "INFO"
| "QUEUING"
| "EXECUTING"
| "PENDING"
| "PARTIAL"
| "ERROR"
| "MAYBE_PARTIAL_PREVENTED"
| "MAYBE_PREVENTED"
| "SUCCESS";
status_traces?: InjectStatusExecution[];
/** @format date-time */
tracking_ack_date?: string;
Expand All @@ -1052,7 +1074,17 @@ export interface InjectStatusExecution {
/** @format int32 */
execution_duration?: number;
execution_message?: string;
execution_status?: "DRAFT" | "INFO" | "QUEUING" | "EXECUTING" | "PENDING" | "PARTIAL" | "ERROR" | "SUCCESS";
execution_status?:
| "DRAFT"
| "INFO"
| "QUEUING"
| "EXECUTING"
| "PENDING"
| "PARTIAL"
| "ERROR"
| "MAYBE_PARTIAL_PREVENTED"
| "MAYBE_PREVENTED"
| "SUCCESS";
/** @format date-time */
execution_time?: string;
}
Expand Down Expand Up @@ -1115,6 +1147,7 @@ export interface InjectorConnection {
}

export interface InjectorContract {
convertedContent?: object;
injector_contract_atomic_testing?: boolean;
injector_contract_attack_patterns?: AttackPattern[];
injector_contract_content: string;
Expand Down
Loading

0 comments on commit ac81b88

Please sign in to comment.