the json in the response has been replaced with html

This commit is contained in:
Vladislav Tsarev 2024-12-18 20:11:31 +03:00
parent 188fc631b6
commit 15680407e7
16 changed files with 619 additions and 467 deletions

View File

@ -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",

View File

@ -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

View File

@ -1,5 +1,5 @@
server:
port: 8000
port: 3000
host: 0.0.0.0
logger:
level: "info"

View File

@ -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>,
}

View File

@ -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")
};
}
}
}

View File

@ -1,7 +1,4 @@
use serde::{
Deserialize,
Serialize,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub(crate) struct Vulnerabilities {

View File

@ -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()
}
}

View File

@ -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");
}

View File

@ -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))
}

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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)]

View 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>

View 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>

View File

@ -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")
}
}

View File

@ -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")
}
}
}