Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"entity",
"migration",
"modules/analysis",
"modules/exploit-intelligence",
"modules/fundamental",
"modules/importer",
"modules/ingestor",
Expand Down Expand Up @@ -48,6 +49,7 @@ aws-config = { version = "1.8.14", features = ["behavior-version-latest"] }
aws-sdk-s3 = { version = "1.124.0", default-features = false, features = ["behavior-version-latest", "default-https-client", "http-1x", "rt-tokio", "sigv4a"] }
aws-smithy-http-client = { version = "1.2.0", features = ["rustls-aws-lc"] }
aws-smithy-types = { version = "1.4.5" }
backon = "1.6"
base16ct = "1"
base64 = "0.22"
build-info = "0.0.44"
Expand Down Expand Up @@ -167,6 +169,7 @@ trustify-entity = { path = "entity" }
trustify-infrastructure = { path = "common/infrastructure" }
trustify-migration = { path = "migration" }
trustify-module-analysis = { path = "modules/analysis" }
trustify-module-exploit-intelligence = { path = "modules/exploit-intelligence" }
trustify-module-fundamental = { path = "modules/fundamental" }
trustify-module-importer = { path = "modules/importer" }
trustify-module-ingestor = { path = "modules/ingestor" }
Expand Down
2 changes: 2 additions & 0 deletions common/auth/src/authenticator/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub const DEFAULT_SCOPE_MAPPINGS: &[(&str, &[&str])] = &[
"create:document",
&[
"create.advisory",
"create.exploitIntelligence",
"create.importer",
"create.metadata",
"create.sbom",
Expand All @@ -24,6 +25,7 @@ pub const DEFAULT_SCOPE_MAPPINGS: &[(&str, &[&str])] = &[
&[
"ai",
"read.advisory",
"read.exploitIntelligence",
"read.importer",
"read.metadata",
"read.sbom",
Expand Down
5 changes: 5 additions & 0 deletions common/auth/src/permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ permission! {

#[strum(serialize = "ai")]
Ai,

#[strum(serialize = "create.exploitIntelligence")]
CreateExploitIntelligence,
#[strum(serialize = "read.exploitIntelligence")]
ReadExploitIntelligence,
}
}

Expand Down
14 changes: 14 additions & 0 deletions docs/env-vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@
| `UI_ISSUER_URL` | Issuer URL used by the UI | `http://localhost:8090/realms/trustify` |
| `UI_LOAD_USER` | Whether to load user info | `true` |
| `UI_SCOPE` | Scopes to request | `openid` |
| `EXPLOIT_INTELLIGENCE_URL` | Base URL of the Exploit Intelligence client service | |
| `EXPLOIT_INTELLIGENCE_UI_URL` | Base URL of the EI web UI for deep-linking to reports | |
| `EXPLOIT_INTELLIGENCE_POLL_INTERVAL`| Polling interval for EI analysis completion (humantime) | `30s` |
| `EXPLOIT_INTELLIGENCE_MAX_POLL_DURATION`| Maximum duration before EI polling is considered timed out (humantime) | `30m` |
| `EXPLOIT_INTELLIGENCE_UPLOAD_MAX_RETRIES`| Maximum number of upload retry attempts | `3` |
| `EXPLOIT_INTELLIGENCE_UPLOAD_RETRY_DELAY`| Initial delay between upload retries (humantime, exponential backoff) | `1s` |
| `EXPLOIT_INTELLIGENCE_MAX_CONSECUTIVE_POLL_FAILURES`| Maximum consecutive poll failures before giving up | `5` |
| `EXPLOIT_INTELLIGENCE_WORKER_POLL_INTERVAL`| How often the background worker checks for new EI jobs (humantime) | `5s` |
| `EXPLOIT_INTELLIGENCE_AUTH_TOKEN` | Static authentication token for the Exploit Intelligence service (used when OIDC is not configured) | |
| `EXPLOIT_INTELLIGENCE_OIDC_ISSUER_URL` | OIDC issuer URL for EI service authentication (client credentials flow with discovery) | |
| `EXPLOIT_INTELLIGENCE_OIDC_CLIENT_ID` | OIDC client ID for EI service authentication | |
| `EXPLOIT_INTELLIGENCE_OIDC_CLIENT_SECRET`| OIDC client secret for EI service authentication | |
| `EXPLOIT_INTELLIGENCE_OIDC_REFRESH_BEFORE`| Duration an EI access token must still be valid before requesting a new one | `30s` |
| `EXPLOIT_INTELLIGENCE_OIDC_TLS_INSECURE`| Allow insecure TLS connections with the EI OIDC issuer | `false` |

## Data Migration

Expand Down
120 changes: 120 additions & 0 deletions entity/src/exploit_intelligence_job.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/// Entity model for tracking Exploit Intelligence analysis jobs.
///
/// Each row represents a single analysis request submitted to the Exploit Intelligence
/// service, tracking its lifecycle from submission through completion. Per-component
/// results (scan IDs, findings, advisories) live on the related
/// [`exploit_intelligence_job_component`](super::exploit_intelligence_job_component) rows —
/// even for single-component CycloneDX jobs, which have exactly one component record.
use sea_orm::entity::prelude::*;
use time::OffsetDateTime;

/// Lifecycle status of an Exploit Intelligence analysis job.
#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "exploit_intelligence_job_status"
)]
pub enum ExploitIntelligenceJobStatus {
/// Job has been created but not yet started.
#[sea_orm(string_value = "pending")]
Pending,
/// Analysis is in progress.
#[sea_orm(string_value = "running")]
Running,
/// Analysis completed successfully.
#[sea_orm(string_value = "completed")]
Completed,
/// Analysis failed with an error.
#[sea_orm(string_value = "failed")]
Failed,
}

/// Analysis finding from the Exploit Intelligence service.
///
/// Represents the outcome of a vulnerability analysis for a given component/CVE pair.
#[derive(Copy, Clone, Debug, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "ei_finding")]
pub enum Finding {
/// The analysed component is vulnerable to the CVE.
#[sea_orm(string_value = "vulnerable")]
Vulnerable,
/// The analysed component is not vulnerable to the CVE.
#[sea_orm(string_value = "not_vulnerable")]
NotVulnerable,
/// The analysis was inconclusive.
#[sea_orm(string_value = "uncertain")]
Uncertain,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "exploit_intelligence_job")]
pub struct Model {
/// The database internal ID.
#[sea_orm(primary_key)]
pub id: Uuid,

/// FK to the SBOM being analysed.
pub sbom_id: Uuid,

/// CVE identifier (e.g., "CVE-2024-9680").
pub vulnerability_id: String,

/// Current lifecycle status of the job.
pub status: ExploitIntelligenceJobStatus,

/// Error details for failed analyses.
pub error_message: Option<String>,

/// EI product ID for SPDX multi-component flow (`None` for single-component).
pub product_id: Option<String>,

/// Expected number of components in the product.
pub total_components: Option<i32>,

/// Visibility timeout for the job queue pattern. When set, the job is
/// invisible to other workers until this timestamp is reached.
pub visible_at: Option<OffsetDateTime>,

/// Number of times this job has been dequeued for processing.
pub retry_count: i32,

/// Timestamp when the job was created.
pub created: OffsetDateTime,

/// Timestamp when the job was last updated.
pub updated: OffsetDateTime,
}

/// Foreign-key relationships for the exploit intelligence job entity.
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
/// Link to the SBOM that was analysed.
#[sea_orm(
belongs_to = "super::sbom::Entity",
from = "Column::SbomId",
to = "super::sbom::Column::SbomId"
)]
Sbom,

/// Link to the per-component analysis results.
#[sea_orm(has_many = "super::exploit_intelligence_job_component::Entity")]
Components,
}

/// Navigate from an exploit intelligence job to its SBOM.
impl Related<super::sbom::Entity> for Entity {
fn to() -> RelationDef {
Relation::Sbom.def()
}
}

/// Navigate from an exploit intelligence job to its components.
impl Related<super::exploit_intelligence_job_component::Entity> for Entity {
fn to() -> RelationDef {
Relation::Components.def()
}
}

/// Default active-model behaviour (no custom hooks).
impl ActiveModelBehavior for ActiveModel {}
87 changes: 87 additions & 0 deletions entity/src/exploit_intelligence_job_component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/// Entity model for tracking per-component analysis results within a multi-component
/// Exploit Intelligence product job.
///
/// Each row represents one container image component within an SPDX product SBOM,
/// independently analysed by the Exploit Intelligence service. The parent
/// `exploit_intelligence_job` holds product-level metadata while this entity holds
/// the per-component scan results.
use super::exploit_intelligence_job::{ExploitIntelligenceJobStatus, Finding};
use sea_orm::entity::prelude::*;
use time::OffsetDateTime;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "exploit_intelligence_job_component")]
pub struct Model {
/// The database internal ID.
#[sea_orm(primary_key)]
pub id: Uuid,

/// FK to the parent exploit intelligence job.
pub job_id: Uuid,

/// Identifier of the component (purl or container image reference).
pub component_ref: String,

/// EI scan ID for this component's report (unique, nullable).
#[sea_orm(unique)]
pub scan_id: Option<String>,

/// Current lifecycle status of this component's analysis.
pub status: ExploitIntelligenceJobStatus,

/// Analysis finding for the component (set on completion).
pub finding: Option<Finding>,

/// FK to advisory — set when a VEX document is ingested from the result.
pub advisory_id: Option<Uuid>,

/// Whether this component was excluded by the EI service (e.g.,
/// unsupported ecosystem) rather than genuinely failing.
pub excluded: bool,

/// Error details for failed analyses.
pub error_message: Option<String>,

/// Timestamp when the component record was created.
pub created: OffsetDateTime,

/// Timestamp when the component record was last updated.
pub updated: OffsetDateTime,
}

/// Foreign-key relationships for the exploit intelligence job component entity.
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
/// Link to the parent exploit intelligence job.
#[sea_orm(
belongs_to = "super::exploit_intelligence_job::Entity",
from = "Column::JobId",
to = "super::exploit_intelligence_job::Column::Id"
)]
Job,

/// Link to the advisory ingested from the analysis result.
#[sea_orm(
belongs_to = "super::advisory::Entity",
from = "Column::AdvisoryId",
to = "super::advisory::Column::Id"
)]
Advisory,
}

/// Navigate from a component to its parent job.
impl Related<super::exploit_intelligence_job::Entity> for Entity {
fn to() -> RelationDef {
Relation::Job.def()
}
}

/// Navigate from a component to its advisory.
impl Related<super::advisory::Entity> for Entity {
fn to() -> RelationDef {
Relation::Advisory.def()
}
}

/// Default active-model behaviour (no custom hooks).
impl ActiveModelBehavior for ActiveModel {}
2 changes: 2 additions & 0 deletions entity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod base_purl;
pub mod cpe;
pub mod cpe_status;
pub mod expanded_license;
pub mod exploit_intelligence_job;
pub mod exploit_intelligence_job_component;
pub mod importer;
pub mod importer_report;
pub mod labels;
Expand Down
2 changes: 2 additions & 0 deletions migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ mod m0002230_sle_license_id_index;
mod m0002240_product_version_sbom_index;
mod m0002250_create_cpe_status;
mod m0002260_cpe_part_vendor_product_index;
mod m0002270_create_exploit_intelligence_job;

pub trait MigratorExt: Send {
fn build_migrations() -> Migrations;
Expand Down Expand Up @@ -151,6 +152,7 @@ impl MigratorExt for Migrator {
.normal(m0002240_product_version_sbom_index::Migration)
.normal(m0002250_create_cpe_status::Migration)
.normal(m0002260_cpe_part_vendor_product_index::Migration)
.normal(m0002270_create_exploit_intelligence_job::Migration)
}
}

Expand Down
Loading
Loading