-
Notifications
You must be signed in to change notification settings - Fork 174
[frontend/backend] feat(scv): assign Security Domains to payloads (#4119) #4335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 33 commits
1981d8c
ebca9ae
153932b
33c4c8a
1fbe775
b0c60fe
261c4f1
742f88f
aac8773
4687356
43d6131
d044c58
ad1de12
ffe6945
671a39f
61cb866
221f827
dba2cbc
4f07645
884451d
31fe04b
5c289ea
b396179
e36a678
3781545
6d505bc
3f5ff5a
bff2e27
1e9921f
88bda40
a9f5cf8
fc9eaa0
08e46c3
f39abd1
77c3f9c
d466ff2
2646bbd
329fb35
e0443dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package io.openaev.migration; | ||
|
|
||
| import java.sql.Statement; | ||
| import org.flywaydb.core.api.migration.BaseJavaMigration; | ||
| import org.flywaydb.core.api.migration.Context; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class V4_52__Implement_Domains_notion extends BaseJavaMigration { | ||
| @Override | ||
| public void migrate(Context context) throws Exception { | ||
| try (Statement stmt = context.getConnection().createStatement()) { | ||
| stmt.execute( | ||
GaetanSantucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| CREATE TABLE domains ( | ||
| domain_id VARCHAR(255) NOT NULL CONSTRAINT domains_pkey PRIMARY KEY, | ||
| domain_name VARCHAR(255) NOT NULL UNIQUE, | ||
| domain_color VARCHAR(255) NOT NULL DEFAULT '#FFFFFF', | ||
| domain_created_at TIMESTAMPTZ DEFAULT now(), | ||
| domain_updated_at TIMESTAMPTZ DEFAULT now() | ||
| ); | ||
| """); | ||
|
|
||
| stmt.execute( | ||
| """ | ||
| CREATE INDEX idx_domains_domain_name | ||
| ON domains(domain_name); | ||
| """); | ||
|
|
||
| stmt.execute( | ||
| """ | ||
| CREATE TABLE payloads_domains ( | ||
| payload_id VARCHAR(255) NOT NULL, | ||
| domain_id VARCHAR(255) NOT NULL, | ||
| PRIMARY KEY (payload_id, domain_id), | ||
| CONSTRAINT fk_payloads_domains_domain FOREIGN KEY (domain_id) REFERENCES domains(domain_id) ON DELETE CASCADE, | ||
| CONSTRAINT fk_payloads_domains_payload FOREIGN KEY (payload_id) REFERENCES payloads(payload_id) ON DELETE CASCADE | ||
| ); | ||
| """); | ||
|
|
||
| stmt.execute("CREATE INDEX idx_payloads_domains_domain_id ON payloads_domains(domain_id);"); | ||
| stmt.execute("CREATE INDEX idx_payloads_domains_payload_id ON payloads_domains(payload_id);"); | ||
|
|
||
| stmt.execute( | ||
| """ | ||
| CREATE TABLE injectors_contracts_domains ( | ||
| injector_contract_id VARCHAR(255) NOT NULL, | ||
| domain_id VARCHAR(255) NOT NULL, | ||
| PRIMARY KEY (injector_contract_id, domain_id), | ||
|
|
||
| CONSTRAINT fk_icd_injector_contract | ||
| FOREIGN KEY (injector_contract_id) | ||
| REFERENCES injectors_contracts(injector_contract_id) | ||
| ON DELETE CASCADE, | ||
|
|
||
| CONSTRAINT fk_icd_domain | ||
| FOREIGN KEY (domain_id) | ||
| REFERENCES domains(domain_id) | ||
| ON DELETE CASCADE | ||
| ); | ||
| """); | ||
|
|
||
| stmt.execute( | ||
| "CREATE INDEX idx_icd_injector_contract_id ON injectors_contracts_domains(injector_contract_id);"); | ||
| stmt.execute("CREATE INDEX idx_icd_domain_id ON injectors_contracts_domains(domain_id);"); | ||
|
|
||
| stmt.execute( | ||
| "INSERT INTO domains (domain_id, domain_name, domain_color) VALUES " | ||
| + " (gen_random_uuid(), 'Endpoint', '#389CFF')," | ||
| + " (gen_random_uuid(), 'Network', '#009933')," | ||
| + " (gen_random_uuid(), 'Web App', '#FF9933')," | ||
| + " (gen_random_uuid(), 'E-mail Infiltration', '#FF6666')," | ||
| + " (gen_random_uuid(), 'Data Exfiltration', '#9933CC')," | ||
| + " (gen_random_uuid(), 'URL Filtering', '#66CCFF')," | ||
| + " (gen_random_uuid(), 'Cloud', '#9999CC')," | ||
| + " (gen_random_uuid(), 'Table-Top', '#FFCC33')," | ||
| + " (gen_random_uuid(), 'To classify', '#FFFFFF');"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Rollback script | ||
GaetanSantucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // DROP TABLE IF EXISTS domains; | ||
| // DROP INDEX IF EXISTS idx_payloads_domains_domain_id; | ||
| // DROP INDEX IF EXISTS idx_payloads_domains_payload_id; | ||
| // DROP TABLE IF EXISTS payloads_domains; | ||
| // DROP INDEX IF EXISTS idx_injectors_contracts_domains_domain_id; | ||
| // DROP INDEX IF EXISTS idx_injectors_contracts_domains_injector_contract_id; | ||
| // DROP TABLE IF EXISTS injectors_contracts_domains; | ||
GaetanSantucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package io.openaev.rest.domain; | ||
|
|
||
| import io.openaev.aop.LogExecutionTime; | ||
| import io.openaev.aop.RBAC; | ||
| import io.openaev.database.model.Action; | ||
| import io.openaev.database.model.Domain; | ||
| import io.openaev.database.model.ResourceType; | ||
| import io.openaev.rest.domain.form.DomainBaseInput; | ||
| import io.openaev.rest.helper.RestBehavior; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.transaction.Transactional; | ||
| import jakarta.validation.Valid; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @Tag(name = "Domain API", description = "Operations related to Domain") | ||
| public class DomainApi extends RestBehavior { | ||
|
|
||
| public static final String DOMAIN_URI = "/api/domains"; | ||
| private final DomainService domainService; | ||
|
|
||
| @LogExecutionTime | ||
| @Operation(summary = "Search Domains") | ||
| @GetMapping(DOMAIN_URI) | ||
| @RBAC(actionPerformed = Action.READ, resourceType = ResourceType.PAYLOAD) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not link the RBAC to the Payloads, we should create a new RessourceType DOMAIN. To do so : And finally on DomainApi.java, change the RBAC to use |
||
| public List<Domain> domains() { | ||
| return domainService.searchDomains(); | ||
| } | ||
|
|
||
| @Operation(summary = "Get a Domain by ID", description = "Fetches detailed Domain info by ID") | ||
| @GetMapping(DOMAIN_URI + "/{domainId}") | ||
| @RBAC( | ||
| resourceId = "#domainId", | ||
| actionPerformed = Action.READ, | ||
| resourceType = ResourceType.PAYLOAD) | ||
| public Domain getDomain(@PathVariable String domainId) { | ||
| return domainService.findById(domainId); | ||
| } | ||
|
|
||
| @PostMapping(DOMAIN_URI + "/{domainId}/upsert") | ||
| @RBAC(actionPerformed = Action.CREATE, resourceType = ResourceType.PAYLOAD) | ||
| @Transactional(rollbackOn = Exception.class) | ||
| @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The upserted domain")}) | ||
| @Operation(description = "Upsert a domain", summary = "Upsert domain") | ||
| public Domain upsertDomain(@Valid @RequestBody DomainBaseInput input) { | ||
| return domainService.upsertDomain(input); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package io.openaev.rest.domain; | ||
|
|
||
| import io.openaev.database.model.Domain; | ||
| import io.openaev.database.repository.DomainRepository; | ||
| import io.openaev.rest.domain.form.DomainBaseInput; | ||
| import io.openaev.rest.exception.ElementNotFoundException; | ||
| import java.time.Instant; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Random; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import static io.openaev.helper.StreamHelper.fromIterable; | ||
| import static io.openaev.utils.StringUtils.generateRandomColor; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class DomainService { | ||
|
|
||
| private static final String DOMAIN_ID_NOT_FOUND_MSG = "Domain not found with id"; | ||
|
|
||
| private final DomainRepository domainRepository; | ||
|
|
||
| public List<Domain> searchDomains() { | ||
| return fromIterable(domainRepository.findAll()); | ||
| } | ||
|
|
||
| private Optional<Domain> findByName(final String name) { | ||
GaetanSantucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return domainRepository.findByName(name); | ||
| } | ||
|
|
||
| public Domain findById(final String domainId) { | ||
| return domainRepository | ||
| .findById(domainId) | ||
| .orElseThrow(() -> new ElementNotFoundException((String.format("%s: %s", DOMAIN_ID_NOT_FOUND_MSG, domainId)))); | ||
| } | ||
|
|
||
| public Domain upsertDomain(final DomainBaseInput input) { | ||
| return this.upsert(input.getName(), input.getColor()); | ||
| } | ||
|
|
||
| public Domain upsert(final Domain domainToUpsert) { | ||
| return this.upsert(domainToUpsert.getName(), domainToUpsert.getColor()); | ||
| } | ||
|
|
||
| public Domain upsert(final String name, final String color) { | ||
| Optional<Domain> existingDomain = this.findByName(name); | ||
| return existingDomain.orElseGet( | ||
| () -> | ||
| domainRepository.save( | ||
| new Domain( | ||
| null, name, color != null ? color : generateRandomColor() , Instant.now(), null))); | ||
| } | ||
|
|
||
| public Set<Domain> upserts(final Set<Domain> domains) { | ||
| return domains.stream().map(this::upsert).collect(Collectors.toSet()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package io.openaev.rest.domain.form; | ||
|
|
||
| import static io.openaev.config.AppConfig.MANDATORY_MESSAGE; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @Setter | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| public class DomainBaseInput { | ||
| @NotBlank(message = MANDATORY_MESSAGE) | ||
| @JsonProperty("domain_name") | ||
| @Schema(description = "Name of the domain") | ||
| private String name; | ||
|
|
||
| @NotBlank(message = MANDATORY_MESSAGE) | ||
| @JsonProperty("domain_color") | ||
| @Schema(description = "Color of the domain") | ||
| private String color; | ||
GaetanSantucci marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,8 +4,7 @@ | |
| import static io.openaev.database.model.InjectorContract.*; | ||
| import static io.openaev.helper.DatabaseHelper.updateRelation; | ||
| import static io.openaev.helper.StreamHelper.fromIterable; | ||
| import static io.openaev.utils.JpaUtils.createJoinArrayAggOnId; | ||
| import static io.openaev.utils.JpaUtils.createLeftJoin; | ||
| import static io.openaev.utils.JpaUtils.*; | ||
| import static io.openaev.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder; | ||
|
|
||
| import com.fasterxml.jackson.databind.JsonNode; | ||
|
|
@@ -319,6 +318,12 @@ private void selectForInjectorContractFull( | |
| Expression<String[]> attackPatternIdsExpression = | ||
| createJoinArrayAggOnId(cb, injectorContractRoot, "attackPatterns"); | ||
|
|
||
| Expression<String[]> domainsIdsExpression = | ||
| createJoinArrayAggOnId(cb, injectorContractRoot, "domains"); | ||
|
|
||
| Expression<String[]> payloadDomainsIdsExpression = | ||
| createJoinArrayAggOnIdForJoin(cb, injectorContractPayloadJoin, "domains"); | ||
|
|
||
| // SELECT | ||
| cq.multiselect( | ||
| injectorContractRoot.get("id").alias("injector_contract_id"), | ||
|
|
@@ -331,6 +336,8 @@ private void selectForInjectorContractFull( | |
| injectorContractInjectorJoin.get("type").alias("injector_contract_injector_type"), | ||
| injectorContractInjectorJoin.get("name").alias("injector_contract_injector_name"), | ||
| attackPatternIdsExpression.alias("injector_contract_attack_patterns"), | ||
| payloadDomainsIdsExpression.alias("payload_domains"), | ||
| domainsIdsExpression.alias("injector_contract_domains"), | ||
| injectorContractRoot.get("updatedAt").alias("injector_contract_updated_at"), | ||
| injectorContractPayloadJoin.get("executionArch").alias("payload_execution_arch")) | ||
| .distinct(true); | ||
|
|
@@ -359,11 +366,23 @@ private List<InjectorContractFullOutput> execInjectorFullContract(TypedQuery<Tup | |
| tuple.get("collector_type", String.class), | ||
| tuple.get("injector_contract_injector_type", String.class), | ||
| tuple.get("injector_contract_attack_patterns", String[].class), | ||
| resolveEffectiveDomains( | ||
| tuple.get("injector_contract_domains", String[].class), | ||
| tuple.get("payload_domains", String[].class)), | ||
| tuple.get("injector_contract_updated_at", Instant.class), | ||
| tuple.get("payload_execution_arch", Payload.PAYLOAD_EXECUTION_ARCH.class))) | ||
| .toList(); | ||
| } | ||
|
|
||
| private List<String> resolveEffectiveDomains(String[] injectorDomains, String[] payloadDomains) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is a duplication of code from InjectorContractFullOutput, it should be moved to an utils class, with @component annotation |
||
| String[] effectiveDomains = | ||
| (payloadDomains != null && payloadDomains.length > 0) ? payloadDomains : injectorDomains; | ||
| if (effectiveDomains == null) { | ||
| return List.of(); | ||
| } | ||
| return Arrays.stream(effectiveDomains).filter(Objects::nonNull).distinct().toList(); | ||
| } | ||
|
|
||
| private void selectForInjectorContractBase( | ||
| @NotNull final CriteriaBuilder cb, | ||
| @NotNull final CriteriaQuery<Tuple> cq, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.