the json in the response has been replaced with html
This commit is contained in:
parent
188fc631b6
commit
15680407e7
@ -19,6 +19,7 @@ regex = "1.11.0"
|
||||
reqwest = { version = "0.12.8", features = ["json"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
tera = "1.20.0"
|
||||
tokio = { version = "1.40.0", features = [
|
||||
"macros",
|
||||
"process",
|
||||
|
@ -13,6 +13,12 @@ FROM rust:1.82.0-slim AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
|
||||
|
||||
COPY src/router/templates/ /app/templates/
|
||||
ENV TEMPLATE_DIR=/app/templates
|
||||
|
||||
COPY configuration.yaml /app/configuration.yaml
|
||||
COPY --from=builder /app/target/release/vuln-aggregator vuln-aggregator
|
||||
COPY --from=builder /app/zli zli
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
server:
|
||||
port: 8000
|
||||
port: 3000
|
||||
host: 0.0.0.0
|
||||
logger:
|
||||
level: "info"
|
||||
|
@ -1,128 +1,117 @@
|
||||
const API_ALT_IMAGES: &str = "https://registry.altlinux.org/v2";
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
|
||||
use crate::alt_registry_images::branch::Branch;
|
||||
|
||||
static VERSION_REGEX: Lazy<VersionRegex> =
|
||||
Lazy::new(|| VersionRegex::new().unwrap_or_else(|err| panic!("{}", err)));
|
||||
|
||||
struct VersionRegex(Regex);
|
||||
|
||||
impl VersionRegex {
|
||||
fn new() -> Result<Self, String> {
|
||||
Regex::new(r"^(v\d+\.\d+\.\d+(-\d+|-[\w]+)?|(\d+\.\d+(\.\d+)?)(-\d+)?|\d+\.\d+)$")
|
||||
.map(VersionRegex)
|
||||
.map_err(|err| format!("regex build error: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_branch_images(
|
||||
branch: &Branch,
|
||||
) -> Result<Vec<Image>, String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image_name in branch_images_names(branch).await? {
|
||||
for tag in image_tags(&image_name).await? {
|
||||
if !VERSION_REGEX.0.is_match(&tag) && tag != branch.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
images.push(Image {
|
||||
name: image_name.clone(),
|
||||
tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_all_images() -> Result<Vec<Image>, String> {
|
||||
let branches = [Branch::Sisyphus, Branch::P11, Branch::P10];
|
||||
|
||||
let mut images = Vec::new();
|
||||
|
||||
for branch in branches {
|
||||
images.extend(fetch_branch_images(&branch).await?)
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
async fn image_tags(image_name: &str) -> Result<Vec<String>, String> {
|
||||
let res =
|
||||
reqwest::get(format!("{}/{}/tags/list", API_ALT_IMAGES, image_name))
|
||||
.await
|
||||
.map_err(|err| format!("request execution error: {}", err))?
|
||||
.json::<TagsList>()
|
||||
.await
|
||||
.map_err(|err| format!("TagList parsing error: {}", err))?;
|
||||
|
||||
Ok(res.tags)
|
||||
}
|
||||
|
||||
async fn branch_images_names(branch: &Branch) -> Result<Vec<String>, String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image in images_names().await? {
|
||||
if image.contains(branch.as_str())
|
||||
|| branch_is_in_the_tags(&image, branch).await?
|
||||
{
|
||||
images.push(image.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
async fn images_names() -> Result<Vec<String>, String> {
|
||||
let res = reqwest::get(format!("{}/_catalog", API_ALT_IMAGES))
|
||||
.await
|
||||
.map_err(|err| format!("request execution error: {}", err))?
|
||||
.json::<Images>()
|
||||
.await
|
||||
.map_err(|err| format!("image parsing error: {}", err))?;
|
||||
|
||||
Ok(res.repositories)
|
||||
}
|
||||
|
||||
async fn branch_is_in_the_tags(
|
||||
image: &str,
|
||||
branch: &Branch,
|
||||
) -> Result<bool, String> {
|
||||
Ok(image_tags(image)
|
||||
.await?
|
||||
.contains(&branch.as_str().to_owned()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Images {
|
||||
repositories: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Image {
|
||||
name: String,
|
||||
tag: String,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub(crate) fn name_ref(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub(crate) fn tag_ref(&self) -> &str {
|
||||
&self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct TagsList {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
const API_ALT_IMAGES: &str = "https://registry.altlinux.org/v2";
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::alt_registry_images::branch::Branch;
|
||||
|
||||
static VERSION_REGEX: Lazy<VersionRegex> =
|
||||
Lazy::new(|| VersionRegex::new().unwrap_or_else(|err| panic!("{}", err)));
|
||||
|
||||
struct VersionRegex(Regex);
|
||||
|
||||
impl VersionRegex {
|
||||
fn new() -> Result<Self, String> {
|
||||
Regex::new(r"^(v\d+\.\d+\.\d+(-\d+|-[\w]+)?|(\d+\.\d+(\.\d+)?)(-\d+)?|\d+\.\d+)$")
|
||||
.map(VersionRegex)
|
||||
.map_err(|err| format!("regex build error: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_branch_images(branch: &Branch) -> Result<Vec<Image>, String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image_name in branch_images_names(branch).await? {
|
||||
for tag in image_tags(&image_name).await? {
|
||||
if !VERSION_REGEX.0.is_match(&tag) && tag != branch.as_str() {
|
||||
continue;
|
||||
}
|
||||
|
||||
images.push(Image {
|
||||
name: image_name.clone(),
|
||||
tag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_all_images() -> Result<Vec<Image>, String> {
|
||||
let branches = [Branch::Sisyphus, Branch::P11, Branch::P10];
|
||||
|
||||
let mut images = Vec::new();
|
||||
|
||||
for branch in branches {
|
||||
images.extend(fetch_branch_images(&branch).await?)
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
async fn image_tags(image_name: &str) -> Result<Vec<String>, String> {
|
||||
let res = reqwest::get(format!("{}/{}/tags/list", API_ALT_IMAGES, image_name))
|
||||
.await
|
||||
.map_err(|err| format!("request execution error: {}", err))?
|
||||
.json::<TagsList>()
|
||||
.await
|
||||
.map_err(|err| format!("TagList parsing error: {}", err))?;
|
||||
|
||||
Ok(res.tags)
|
||||
}
|
||||
|
||||
async fn branch_images_names(branch: &Branch) -> Result<Vec<String>, String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image in images_names().await? {
|
||||
if image.contains(branch.as_str()) || branch_is_in_the_tags(&image, branch).await? {
|
||||
images.push(image.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
async fn images_names() -> Result<Vec<String>, String> {
|
||||
let res = reqwest::get(format!("{}/_catalog", API_ALT_IMAGES))
|
||||
.await
|
||||
.map_err(|err| format!("request execution error: {}", err))?
|
||||
.json::<Images>()
|
||||
.await
|
||||
.map_err(|err| format!("image parsing error: {}", err))?;
|
||||
|
||||
Ok(res.repositories)
|
||||
}
|
||||
|
||||
async fn branch_is_in_the_tags(image: &str, branch: &Branch) -> Result<bool, String> {
|
||||
Ok(image_tags(image)
|
||||
.await?
|
||||
.contains(&branch.as_str().to_owned()))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Images {
|
||||
repositories: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Image {
|
||||
name: String,
|
||||
tag: String,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub(crate) fn name_ref(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub(crate) fn tag_ref(&self) -> &str {
|
||||
&self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct TagsList {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
use tokio::process::Command;
|
||||
|
||||
use super::image::{
|
||||
self,
|
||||
Image,
|
||||
use super::image::{self, Image};
|
||||
use crate::alt_registry_images::{
|
||||
degree_of_vulnerability::DegreeOfVulnerability, vulnerability::Vulnerabilities,
|
||||
};
|
||||
use crate::alt_registry_images::vulnerability::Vulnerabilities;
|
||||
|
||||
pub(crate) async fn vulnerable_images() -> Result<Vec<Image>, String> {
|
||||
let mut images = Vec::new();
|
||||
@ -46,3 +45,65 @@ pub(crate) async fn image_vulnerabilities(
|
||||
.map_err(|err| format!("json parsing error: {}", err))?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn images_that_vulnerabilities_are_eliminated_by_reassembly(
|
||||
) -> Result<Vec<Image>, String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image in vulnerable_images().await? {
|
||||
let vulnes = image_vulnerabilities(&image).await?.ok_or_else(|| {
|
||||
"the image is without vulnerabilities, but the opposite was expected".to_owned()
|
||||
})?;
|
||||
|
||||
if vulnes
|
||||
.cve_list()
|
||||
.iter()
|
||||
.filter(|cve| {
|
||||
cve.severity() == DegreeOfVulnerability::Critical.as_str()
|
||||
|| cve.severity() == DegreeOfVulnerability::High.as_str()
|
||||
|| cve.severity() == DegreeOfVulnerability::Medium.as_str()
|
||||
})
|
||||
.all(|cve| {
|
||||
cve.package_list_ref()
|
||||
.iter()
|
||||
.all(|package| package.name().contains("golang.org"))
|
||||
})
|
||||
{
|
||||
images.push(image.clone())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn images_that_vulnerabilities_are_eliminated_by_reassembly() {
|
||||
let images = super::images_that_vulnerabilities_are_eliminated_by_reassembly()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for image in images {
|
||||
let cves = super::image_vulnerabilities(&image).await.unwrap().unwrap();
|
||||
|
||||
if !cves
|
||||
.cve_list()
|
||||
.iter()
|
||||
.filter(|cve| {
|
||||
cve.severity() == "CRITICAL"
|
||||
|| cve.severity() == "HIGH"
|
||||
|| cve.severity() == "MEDIUM"
|
||||
})
|
||||
.all(|cve| {
|
||||
cve.package_list_ref()
|
||||
.iter()
|
||||
.all(|package| package.name().contains("golang.org"))
|
||||
})
|
||||
{
|
||||
panic!("not every vulnerability passes the test")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct Vulnerabilities {
|
||||
|
@ -1,42 +1,37 @@
|
||||
use std::env;
|
||||
|
||||
use config::{
|
||||
Config,
|
||||
ConfigError,
|
||||
File,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub static SETTINGS: Lazy<Settings> =
|
||||
Lazy::new(|| Settings::new().expect("failed to setup settings"));
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Settings {
|
||||
pub server: Server,
|
||||
pub logger: Logger,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Server {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Logger {
|
||||
pub level: String,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
fn new() -> Result<Self, ConfigError> {
|
||||
let mut builder =
|
||||
Config::builder().add_source(File::with_name("configuration"));
|
||||
|
||||
if let Ok(port) = env::var("PORT") {
|
||||
builder = builder.set_override("server.port", port)?;
|
||||
}
|
||||
|
||||
builder.build()?.try_deserialize()
|
||||
}
|
||||
}
|
||||
use std::env;
|
||||
|
||||
use config::{Config, ConfigError, File};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub static SETTINGS: Lazy<Settings> =
|
||||
Lazy::new(|| Settings::new().expect("failed to setup settings"));
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Settings {
|
||||
pub server: Server,
|
||||
pub logger: Logger,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Server {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Logger {
|
||||
pub level: String,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
fn new() -> Result<Self, ConfigError> {
|
||||
let mut builder = Config::builder().add_source(File::with_name("configuration.yaml"));
|
||||
|
||||
if let Ok(port) = env::var("PORT") {
|
||||
builder = builder.set_override("server.port", port)?;
|
||||
}
|
||||
|
||||
builder.build()?.try_deserialize()
|
||||
}
|
||||
}
|
||||
|
55
src/main.rs
55
src/main.rs
@ -1,29 +1,26 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use vuln_aggregator::configuration::SETTINGS;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_ansi(false)
|
||||
.with_target(false)
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or(EnvFilter::new(SETTINGS.logger.level.as_str())),
|
||||
)
|
||||
.pretty()
|
||||
.init();
|
||||
|
||||
let listener = TcpListener::bind(format!(
|
||||
"{}:{}",
|
||||
SETTINGS.server.host, SETTINGS.server.port
|
||||
))
|
||||
.await
|
||||
.expect("build listener error");
|
||||
|
||||
tracing::info!("Listening on {}", listener.local_addr().unwrap());
|
||||
|
||||
axum::serve(listener, vuln_aggregator::router::build::app().await)
|
||||
.await
|
||||
.expect("serve app error");
|
||||
}
|
||||
use tokio::net::TcpListener;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use vuln_aggregator::configuration::SETTINGS;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_ansi(false)
|
||||
.with_target(false)
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or(EnvFilter::new(SETTINGS.logger.level.as_str())),
|
||||
)
|
||||
.pretty()
|
||||
.init();
|
||||
|
||||
let listener = TcpListener::bind(format!("{}:{}", SETTINGS.server.host, SETTINGS.server.port))
|
||||
.await
|
||||
.expect("build listener error");
|
||||
|
||||
tracing::info!("Listening on {}", listener.local_addr().unwrap());
|
||||
|
||||
axum::serve(listener, vuln_aggregator::router::build::app().await)
|
||||
.await
|
||||
.expect("serve app error");
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::Request,
|
||||
routing,
|
||||
Router,
|
||||
};
|
||||
use std::env;
|
||||
|
||||
use axum::{body::Body, extract::Request, routing, Extension, Router};
|
||||
use tera::Tera;
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
use super::routing::routes;
|
||||
|
||||
pub async fn app() -> Router {
|
||||
let template_dir =
|
||||
env::var("TEMPLATE_DIR").unwrap_or_else(|_| "src/router/templates".to_string());
|
||||
|
||||
let tera = Tera::new(&format!("{}/**/*", template_dir)).expect("failed to load templates");
|
||||
|
||||
Router::new()
|
||||
.route(
|
||||
"/vulnerable_branch_images_considering_vulnerability/:branch",
|
||||
@ -16,7 +19,7 @@ pub async fn app() -> Router {
|
||||
)
|
||||
.route(
|
||||
"/images_that_vulnerabilities_are_eliminated_by_reassembly",
|
||||
routing::get(routes::images_that_vulnerabilities_are_eliminated_by_reassembly),
|
||||
routing::get(routes::images_that_vulnerabilities_are_eliminated_by_reassembly_handler),
|
||||
)
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(|request: &Request<Body>| {
|
||||
@ -32,4 +35,5 @@ pub async fn app() -> Router {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.layer(Extension(tera))
|
||||
}
|
||||
|
@ -1,38 +1,35 @@
|
||||
pub mod output_image;
|
||||
pub mod routes;
|
||||
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{
|
||||
IntoResponse,
|
||||
Response,
|
||||
},
|
||||
Json,
|
||||
};
|
||||
|
||||
pub(crate) enum AppError {
|
||||
NonSupportedParams(String),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, details) = match self {
|
||||
AppError::NonSupportedParams(err) => {
|
||||
tracing::debug!(err);
|
||||
|
||||
(StatusCode::BAD_REQUEST, err)
|
||||
}
|
||||
AppError::Other(err) => {
|
||||
tracing::error!(err);
|
||||
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"something went wrong".to_owned(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
(status, Json(serde_json::json!({"details": details}))).into_response()
|
||||
}
|
||||
}
|
||||
pub mod output_image;
|
||||
pub mod routes;
|
||||
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
|
||||
pub(crate) enum AppError {
|
||||
NonSupportedParams(String),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, details) = match self {
|
||||
AppError::NonSupportedParams(err) => {
|
||||
tracing::debug!(err);
|
||||
|
||||
(StatusCode::BAD_REQUEST, err)
|
||||
}
|
||||
AppError::Other(err) => {
|
||||
tracing::error!(err);
|
||||
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"something went wrong".to_owned(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
(status, Json(serde_json::json!({"details": details}))).into_response()
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,12 @@
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::AppError;
|
||||
use crate::alt_registry_images::{
|
||||
fetching_images::{
|
||||
image::Image,
|
||||
vulnerable_images,
|
||||
},
|
||||
fetching_images::{image::Image, vulnerable_images},
|
||||
vulnerability::Cve,
|
||||
};
|
||||
|
||||
pub(super) async fn output_images(
|
||||
images: Vec<Image>,
|
||||
) -> Result<OutputImages, AppError> {
|
||||
pub(super) async fn output_images(images: Vec<Image>) -> Result<OutputImages, AppError> {
|
||||
let mut output_images = Vec::new();
|
||||
|
||||
for image in images {
|
||||
|
@ -1,32 +1,22 @@
|
||||
use axum::{
|
||||
extract::{
|
||||
Path,
|
||||
Query,
|
||||
},
|
||||
response::IntoResponse,
|
||||
Json,
|
||||
extract::{Path, Query},
|
||||
response::{Html, IntoResponse},
|
||||
Extension, Json,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tera::Tera;
|
||||
|
||||
use super::{
|
||||
output_image,
|
||||
AppError,
|
||||
};
|
||||
use super::{output_image, AppError};
|
||||
use crate::alt_registry_images::{
|
||||
self,
|
||||
branch::Branch,
|
||||
degree_of_vulnerability::DegreeOfVulnerability,
|
||||
self, branch::Branch, degree_of_vulnerability::DegreeOfVulnerability,
|
||||
fetching_images::vulnerable_images,
|
||||
};
|
||||
|
||||
pub(crate) async fn vulnerable_branch_images_considering_vulnerability(
|
||||
Path(branch): Path<String>,
|
||||
Query(degree_of_vulnerability_template): Query<
|
||||
DegreeOfVulnerabilityTemplate,
|
||||
>,
|
||||
Query(degree_of_vulnerability_template): Query<DegreeOfVulnerabilityTemplate>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let branch = Branch::try_from(branch.as_str())
|
||||
.map_err(AppError::NonSupportedParams)?;
|
||||
let branch = Branch::try_from(branch.as_str()).map_err(AppError::NonSupportedParams)?;
|
||||
|
||||
let degree_of_vulnerability = DegreeOfVulnerability::try_from(
|
||||
degree_of_vulnerability_template
|
||||
@ -36,10 +26,7 @@ pub(crate) async fn vulnerable_branch_images_considering_vulnerability(
|
||||
.map_err(AppError::NonSupportedParams)?;
|
||||
|
||||
let mut images = Vec::new();
|
||||
for image in
|
||||
alt_registry_images::fetching_images::image::fetch_branch_images(
|
||||
&branch,
|
||||
)
|
||||
for image in alt_registry_images::fetching_images::image::fetch_branch_images(&branch)
|
||||
.await
|
||||
.map_err(AppError::Other)?
|
||||
{
|
||||
@ -71,43 +58,21 @@ pub(crate) async fn vulnerable_branch_images_considering_vulnerability(
|
||||
Ok(Json(output_image::output_images(images).await?))
|
||||
}
|
||||
|
||||
pub(crate) async fn images_that_vulnerabilities_are_eliminated_by_reassembly(
|
||||
pub(crate) async fn images_that_vulnerabilities_are_eliminated_by_reassembly_handler(
|
||||
Extension(tera): Extension<Tera>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for image in vulnerable_images::vulnerable_images()
|
||||
let images = vulnerable_images::images_that_vulnerabilities_are_eliminated_by_reassembly()
|
||||
.await
|
||||
.map_err(AppError::Other)?
|
||||
{
|
||||
let vulnes = vulnerable_images::image_vulnerabilities(&image)
|
||||
.await
|
||||
.map_err(AppError::Other)?
|
||||
.ok_or_else(|| {
|
||||
AppError::Other(
|
||||
"the image is without vulnerabilities, but the opposite was expected"
|
||||
.to_owned(),
|
||||
)
|
||||
})?;
|
||||
.map_err(AppError::Other)?;
|
||||
|
||||
if vulnes
|
||||
.cve_list()
|
||||
.iter()
|
||||
.filter(|cve| {
|
||||
cve.severity() == DegreeOfVulnerability::Critical.as_str()
|
||||
|| cve.severity() == DegreeOfVulnerability::High.as_str()
|
||||
|| cve.severity() == DegreeOfVulnerability::Medium.as_str()
|
||||
})
|
||||
.all(|cve| {
|
||||
cve.package_list_ref()
|
||||
.iter()
|
||||
.all(|package| package.name().contains("golang.org"))
|
||||
})
|
||||
{
|
||||
images.push(image.clone())
|
||||
}
|
||||
}
|
||||
let images = output_image::output_images(images).await?;
|
||||
|
||||
Ok(Json(output_image::output_images(images).await?))
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("images", &images.images());
|
||||
|
||||
Ok(Html(tera.render("index.html", &context).map_err(
|
||||
|err| AppError::Other(format!("failed render html: {}", err)),
|
||||
)?))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
59
src/router/templates/index.html
Normal file
59
src/router/templates/index.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
{% include "utils/header.html" %}
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Images that vulnerabilities are eliminated by reassembly</h1>
|
||||
{% for image in images %}
|
||||
<div class="block image">
|
||||
<input type="checkbox" id="image-{{ image.Image }}" />
|
||||
<label for="image-{{ image.Image }}" class="checkbox-label">
|
||||
<div class="title">{{ image.Image }}</div>
|
||||
</label>
|
||||
<div class="details vulnerabilities">
|
||||
{% for cve in image.CVEList %}
|
||||
<div class="block cve">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{ image.Image }}-{{ cve.Id }}"
|
||||
/>
|
||||
<label
|
||||
for="{{ image.Image }}-{{ cve.Id }}"
|
||||
class="checkbox-label"
|
||||
>
|
||||
<div class="title">
|
||||
{{cve.Id}} - {{cve.Severity}}
|
||||
</div>
|
||||
</label>
|
||||
<div class="details cve-info">
|
||||
{{ cve.Title }}<br />
|
||||
{% if cve.PackageList | length > 0 %}
|
||||
<div class="packages">
|
||||
<strong>Packages</strong>
|
||||
{% for package in cve.PackageList %}
|
||||
<div class="package">
|
||||
{{ package.Name }}<br />
|
||||
<div class="versions">
|
||||
<div class="version">
|
||||
<strong>Installed Version</strong>
|
||||
{{ package.InstalledVersion }}
|
||||
</div>
|
||||
<div class="version">
|
||||
<strong>Fixed Version</strong>
|
||||
{{ package.FixedVersion }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<strong>Description:</strong> {{ cve.Description }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
149
src/router/templates/utils/header.html
Normal file
149
src/router/templates/utils/header.html
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Images that vulnerabilities are eliminated by reassembly</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5eb;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
line-height: 1.8;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.block:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.image {
|
||||
background-color: #ede5d6;
|
||||
}
|
||||
|
||||
.image:hover {
|
||||
background: #e5dac4;
|
||||
}
|
||||
|
||||
.cve {
|
||||
background-color: #f5f2e4;
|
||||
}
|
||||
|
||||
.cve:hover {
|
||||
background: #fcfbf7;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.checkbox-label::before {
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-right: 3px solid #555;
|
||||
border-bottom: 3px solid #555;
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
transition: transform 0.3s ease;
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 55%;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked + .checkbox-label::before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
transition: all 0.3s ease;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked ~ .details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cve-info {
|
||||
font-size: 0.95em;
|
||||
color: #6e5532;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #6e5532;
|
||||
}
|
||||
|
||||
.packages {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.package {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
border: 2px solid #e2ddd6;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.package strong {
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
color: #7c6c57;
|
||||
}
|
||||
|
||||
.versions {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: wrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.version {
|
||||
flex-basis: 50%;
|
||||
-moz-box-flex: 0;
|
||||
flex-grow: 0;
|
||||
max-width: 50%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</html>
|
@ -1,51 +0,0 @@
|
||||
use axum::{
|
||||
body::{
|
||||
to_bytes,
|
||||
Body,
|
||||
},
|
||||
http::{
|
||||
Method,
|
||||
Request,
|
||||
},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use vuln_aggregator::router::routing::output_image::OutputImages;
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_only_those_images_that_can_be_fixed_by_reassembling() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/images_that_vulnerabilities_are_eliminated_by_reassembly")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
|
||||
let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
|
||||
|
||||
let output = serde_json::from_str::<OutputImages>(&body_str).unwrap();
|
||||
|
||||
if !output.images().iter().all(|image| {
|
||||
image
|
||||
.cve_list()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|cve| {
|
||||
cve.severity() == "CRITICAL"
|
||||
|| cve.severity() == "HIGH"
|
||||
|| cve.severity() == "MEDIUM"
|
||||
})
|
||||
.all(|cve| {
|
||||
cve.package_list_ref()
|
||||
.iter()
|
||||
.all(|package| package.name().contains("golang.org"))
|
||||
})
|
||||
}) {
|
||||
panic!("not every vulnerability passes the test")
|
||||
}
|
||||
}
|
@ -1,97 +1,88 @@
|
||||
use axum::{
|
||||
body::{
|
||||
to_bytes,
|
||||
Body,
|
||||
},
|
||||
http::{
|
||||
Method,
|
||||
Request,
|
||||
StatusCode,
|
||||
},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use vuln_aggregator::router::routing::output_image::OutputImages;
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_200_if_the_branch_and_degree_of_vulnerailities_is_supported()
|
||||
{
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=NONE")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_400_if_the_branch_is_not_supported() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri(
|
||||
"/vulnerable_branch_images_considering_vulnerability/not_supported?degree_vulnerability=NONE",
|
||||
)
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_400_if_the_degree_of_vulnerabilities_is_not_supported() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=not_supported")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn images_of_only_the_selected_branch_with_the_selected_vulnerability_are_returned(
|
||||
) {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=CRITICAL")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
|
||||
let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
|
||||
|
||||
let output = serde_json::from_str::<OutputImages>(&body_str).unwrap();
|
||||
|
||||
for image in output.images() {
|
||||
if !image.image().to_owned().contains("sisyphus") {
|
||||
panic!("unexpected branch")
|
||||
}
|
||||
|
||||
if !image
|
||||
.cve_list()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|cve| cve.severity() == "CRITICAL")
|
||||
{
|
||||
panic!("there is no selected vulnerability")
|
||||
}
|
||||
}
|
||||
}
|
||||
use axum::{
|
||||
body::{to_bytes, Body},
|
||||
http::{Method, Request, StatusCode},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use vuln_aggregator::router::routing::output_image::OutputImages;
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_200_if_the_branch_and_degree_of_vulnerailities_is_supported() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=NONE")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_400_if_the_branch_is_not_supported() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri(
|
||||
"/vulnerable_branch_images_considering_vulnerability/not_supported?degree_vulnerability=NONE",
|
||||
)
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returned_400_if_the_degree_of_vulnerabilities_is_not_supported() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=not_supported")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn images_of_only_the_selected_branch_with_the_selected_vulnerability_are_returned() {
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/vulnerable_branch_images_considering_vulnerability/sisyphus?degree_vulnerability=CRITICAL")
|
||||
.body(Body::empty())
|
||||
.expect("build request error");
|
||||
|
||||
let app = vuln_aggregator::router::build::app().await;
|
||||
|
||||
let response = app.oneshot(request).await.expect("request execution error");
|
||||
|
||||
let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
|
||||
let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
|
||||
|
||||
let output = serde_json::from_str::<OutputImages>(&body_str).unwrap();
|
||||
|
||||
for image in output.images() {
|
||||
if !image.image().to_owned().contains("sisyphus") {
|
||||
panic!("unexpected branch")
|
||||
}
|
||||
|
||||
if !image
|
||||
.cve_list()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|cve| cve.severity() == "CRITICAL")
|
||||
{
|
||||
panic!("there is no selected vulnerability")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user