Skip to content

Commit a5f15c3

Browse files
committed
feat: added a new endpoint for creating ExploitIQ reports
Fixes: #1967 Cleaned up a bit of the download endpoint while I was in there.
1 parent 3818788 commit a5f15c3

File tree

4 files changed

+96
-23
lines changed

4 files changed

+96
-23
lines changed

modules/fundamental/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub enum Error {
1919
#[error(transparent)]
2020
Ingestor(#[from] trustify_module_ingestor::service::Error),
2121
#[error(transparent)]
22+
Json(#[from] serde_json::error::Error),
23+
#[error(transparent)]
2224
Purl(#[from] PurlErr),
2325
#[error("Bad request: {0}")]
2426
BadRequest(String),

modules/fundamental/src/sbom/endpoints/mod.rs

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ use crate::{
1616
sbom::{
1717
model::{
1818
LicenseRefMapping, SbomExternalPackageReference, SbomNodeReference, SbomPackage,
19-
SbomPackageRelation, SbomSummary, Which, details::SbomAdvisory,
19+
SbomPackageRelation, SbomSummary, Which,
20+
details::SbomAdvisory,
21+
exploitiq::{ExploitIqRequest, ReportRequest, ReportResult},
2022
},
2123
service::SbomService,
2224
},
2325
};
24-
use actix_web::{HttpResponse, Responder, delete, get, http::header, post, web};
26+
use actix_web::{HttpResponse, Responder, delete, get, http::header, post, web, web::BytesMut};
2527
use config::Config;
2628
use futures_util::TryStreamExt;
2729
use sea_orm::{TransactionTrait, prelude::Uuid};
@@ -424,6 +426,48 @@ pub async fn related(
424426
Ok(HttpResponse::Ok().json(result))
425427
}
426428

429+
/// Create ExploitIQ report
430+
#[utoipa::path(
431+
tag = "sbom",
432+
operation_id = "createExploitIQReport",
433+
params(
434+
("id" = Id, Path),
435+
),
436+
responses(
437+
(status = 201, description = "Create a report", body = ReportResult),
438+
(status = 400, description = "Unable to read advisory list"),
439+
(status = 404, description = "The SBOM could not be found"),
440+
)
441+
)]
442+
#[post("/v2/sbom/{id}/exploitiq")]
443+
pub async fn create_exploitiq_report(
444+
ingestor: web::Data<IngestorService>,
445+
db: web::Data<Database>,
446+
sbom: web::Data<SbomService>,
447+
id: web::Path<String>,
448+
web::Json(ReportRequest { vulnerabilities }): web::Json<ReportRequest>,
449+
_: Require<ReadSbom>,
450+
) -> Result<impl Responder, Error> {
451+
let id = Id::from_str(&id).map_err(Error::IdKey)?;
452+
match sbom
453+
.fetch_sbom_summary(id, db.as_ref())
454+
.await?
455+
.and_then(|sbom| sbom.source_document)
456+
{
457+
Some(doc) => Ok(match ingestor.storage().retrieve(doc.try_into()?).await? {
458+
Some(s) => {
459+
let buf = s.try_collect::<BytesMut>().await?;
460+
let sbom: serde_json::Value = serde_json::from_slice(buf.as_ref())?;
461+
let _req = ExploitIqRequest::new(sbom, vulnerabilities);
462+
// TODO: Invoke external service here
463+
HttpResponse::Ok().json(ReportResult::default())
464+
}
465+
None => HttpResponse::NotFound().finish(),
466+
}),
467+
None => Ok(HttpResponse::NotFound().finish()),
468+
}
469+
}
470+
427471
#[derive(Clone, Debug, serde::Deserialize, utoipa::IntoParams)]
428472
struct UploadQuery {
429473
/// Optional labels.
@@ -500,26 +544,18 @@ pub async fn download(
500544
_: Require<ReadSbom>,
501545
) -> Result<impl Responder, Error> {
502546
let id = Id::from_str(&key).map_err(Error::IdKey)?;
503-
504-
let Some(sbom) = sbom.fetch_sbom_summary(id, db.as_ref()).await? else {
505-
return Ok(HttpResponse::NotFound().finish());
506-
};
507-
508-
if let Some(doc) = &sbom.source_document {
509-
let storage_key = doc.try_into()?;
510-
511-
let stream = ingestor
512-
.storage()
513-
.retrieve(storage_key)
514-
.await
515-
.map_err(Error::Storage)?
516-
.map(|stream| stream.map_err(Error::Storage));
517-
518-
Ok(match stream {
519-
Some(s) => HttpResponse::Ok().streaming(s),
520-
None => HttpResponse::NotFound().finish(),
521-
})
522-
} else {
523-
Ok(HttpResponse::NotFound().finish())
547+
match sbom
548+
.fetch_sbom_summary(id, db.as_ref())
549+
.await?
550+
.and_then(|sbom| sbom.source_document)
551+
{
552+
Some(doc) => {
553+
let storage_key = doc.try_into()?;
554+
Ok(match ingestor.storage().retrieve(storage_key).await? {
555+
Some(s) => HttpResponse::Ok().streaming(s),
556+
None => HttpResponse::NotFound().finish(),
557+
})
558+
}
559+
None => Ok(HttpResponse::NotFound().finish()),
524560
}
525561
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use serde::{Deserialize, Serialize};
2+
use serde_json::{Value, json};
3+
use utoipa::ToSchema;
4+
5+
#[derive(Serialize, Deserialize, Debug, ToSchema)]
6+
pub struct ReportRequest {
7+
pub vulnerabilities: Vec<String>,
8+
}
9+
10+
#[derive(Serialize, Deserialize, Debug, ToSchema, Default)]
11+
#[serde(rename_all = "camelCase")]
12+
pub struct ReportResult {
13+
pub id: String,
14+
pub report_id: String,
15+
}
16+
17+
#[derive(Serialize, Deserialize, Debug)]
18+
pub struct ExploitIqRequest {
19+
pub vulnerabilities: Vec<String>,
20+
pub sbom: Value,
21+
pub sbom_info_type: String,
22+
pub metadata: Value,
23+
}
24+
25+
impl ExploitIqRequest {
26+
pub fn new(sbom: Value, vulnerabilities: Vec<String>) -> Self {
27+
ExploitIqRequest {
28+
vulnerabilities,
29+
sbom,
30+
sbom_info_type: "manual".into(),
31+
metadata: json!({}),
32+
}
33+
}
34+
}

modules/fundamental/src/sbom/model/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod details;
2+
pub mod exploitiq;
23
pub mod raw_sql;
34

45
use super::service::SbomService;

0 commit comments

Comments
 (0)