From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 4FF6973858; Fri, 28 May 2021 16:30:30 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4A6AFB059; Fri, 28 May 2021 16:30:23 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id DB03FAC23; Fri, 28 May 2021 16:30:08 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id AA91A466D4; Fri, 28 May 2021 16:30:08 +0200 (CEST) From: Fabian Ebner To: pve-devel@lists.proxmox.com, pbs-devel@lists.proxmox.com Date: Fri, 28 May 2021 16:29:43 +0200 Message-Id: <20210528143002.16190-5-f.ebner@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210528143002.16190-1-f.ebner@proxmox.com> References: <20210528143002.16190-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.003 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mod.rs, check.rs, repositories.rs, types.rs] Subject: [pve-devel] [PATCH v5 proxmox-apt 04/23] add check_repositories function X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 28 May 2021 14:30:30 -0000 which checks for bad suites and official URIs. Signed-off-by: Fabian Ebner --- Changes from v4: * switch from a suites_is_variant (returning bool) to a suite_variant helper (returning the base part and variant part), which can be re-used for the replace_suite function src/repositories/check.rs | 151 +++++++++++++++++++++- src/repositories/mod.rs | 19 ++- src/types.rs | 19 +++ tests/repositories.rs | 97 +++++++++++++- tests/sources.list.d.expected/bad.sources | 30 +++++ tests/sources.list.d/bad.sources | 29 +++++ 6 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 tests/sources.list.d.expected/bad.sources create mode 100644 tests/sources.list.d/bad.sources diff --git a/src/repositories/check.rs b/src/repositories/check.rs index a682b69..d03d5f7 100644 --- a/src/repositories/check.rs +++ b/src/repositories/check.rs @@ -1,6 +1,22 @@ use anyhow::{bail, Error}; -use crate::types::{APTRepository, APTRepositoryFileType, APTRepositoryPackageType}; +use crate::types::{ + APTRepository, APTRepositoryFile, APTRepositoryFileType, APTRepositoryInfo, + APTRepositoryPackageType, +}; + +/// Splits the suite into its base part and variant. +fn suite_variant(suite: &str) -> (&str, &str) { + let variants = ["-backports-sloppy", "-backports", "-updates", "/updates"]; + + for variant in variants.iter() { + if let Some(base) = suite.strip_suffix(variant) { + return (base, variant); + } + } + + (suite, "") +} impl APTRepository { /// Makes sure that all basic properties of a repository are present and @@ -102,4 +118,137 @@ impl APTRepository { false } } + + /// Checks if old or unstable suites are configured and also that the + /// `stable` keyword is not used. + fn check_suites(&self, add_info: &mut dyn FnMut(String, String)) { + let old_suites = [ + "lenny", + "squeeze", + "wheezy", + "jessie", + "stretch", + "oldoldstable", + "oldstable", + ]; + + let next_suite = "bullseye"; + + let new_suites = ["testing", "unstable", "sid", "experimental"]; + + if self + .types + .iter() + .any(|package_type| *package_type == APTRepositoryPackageType::Deb) + { + for suite in self.suites.iter() { + if old_suites + .iter() + .any(|base_suite| suite_variant(suite).0 == *base_suite) + { + add_info( + "warning".to_string(), + format!("old suite '{}' configured!", suite), + ); + } + + if suite_variant(suite).0 == next_suite { + add_info( + "ignore-pre-upgrade-warning".to_string(), + format!("suite '{}' should not be used in production!", suite), + ); + } + + if new_suites + .iter() + .any(|base_suite| suite_variant(suite).0 == *base_suite) + { + add_info( + "warning".to_string(), + format!("suite '{}' should not be used in production!", suite), + ); + } + + if suite_variant(suite).0 == "stable" { + add_info( + "warning".to_string(), + "use the name of the stable distribution instead of 'stable'!".to_string(), + ); + } + } + } + } + + /// Checks if an official host is configured in the repository. + fn check_uris(&self) -> Option<(String, String)> { + let official_host = |domains: &Vec<&str>| { + match domains.len() { + 3 => match domains[0] { + "ftp" | "deb" | "security" => domains[1] == "debian" && domains[2] == "org", + "download" | "enterprise" => domains[1] == "proxmox" && domains[2] == "com", + _ => false, + }, + // ftp.*.debian.org + 4 => domains[0] == "ftp" && domains[2] == "debian" && domains[3] == "org", + _ => false, + } + }; + + for uri in self.uris.iter() { + if let Some(begin) = uri.find("://") { + let mut host = uri.split_at(begin + 3).1; + if let Some(end) = host.find('/') { + host = host.split_at(end).0; + } // otherwise assume everything belongs is the host + + let domains = host.split('.').collect(); + + if official_host(&domains) { + return Some(("badge".to_string(), "official host name".to_string())); + } + } + } + + None + } +} + +impl APTRepositoryFile { + /// Checks if old or unstable suites are configured and also that the + /// `stable` keyword is not used. + pub fn check_suites(&self) -> Vec { + let mut infos = vec![]; + + for (n, repo) in self.repositories.iter().enumerate() { + let mut add_info = |kind, message| { + infos.push(APTRepositoryInfo { + path: self.path.clone(), + number: n + 1, + kind, + message, + }) + }; + repo.check_suites(&mut add_info); + } + + infos + } + + /// Checks for official URIs. + pub fn check_uris(&self) -> Vec { + let mut infos = vec![]; + + for (n, repo) in self.repositories.iter().enumerate() { + if let Some((kind, message)) = repo.check_uris() { + infos.push(APTRepositoryInfo { + path: self.path.clone(), + number: n + 1, + kind, + message, + }); + } + } + + infos + } } diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs index b7919a9..c2bbc06 100644 --- a/src/repositories/mod.rs +++ b/src/repositories/mod.rs @@ -4,7 +4,7 @@ use anyhow::{bail, format_err, Error}; use crate::types::{ APTRepository, APTRepositoryFile, APTRepositoryFileError, APTRepositoryFileType, - APTRepositoryOption, + APTRepositoryInfo, APTRepositoryOption, }; mod list_parser; @@ -148,6 +148,23 @@ impl APTRepositoryFile { } } +/// Provides additional information about the repositories. +/// +/// The kind of information can be: +/// `warnings` for bad suites. +/// `ignore-pre-upgrade-warning` when the next stable suite is configured. +/// `badge` for official URIs. +pub fn check_repositories(files: &[APTRepositoryFile]) -> Vec { + let mut infos = vec![]; + + for file in files.iter() { + infos.append(&mut file.check_suites()); + infos.append(&mut file.check_uris()); + } + + infos +} + /// Checks if the enterprise repository for the specified Proxmox product is /// configured and enabled. pub fn enterprise_repository_enabled(files: &[APTRepositoryFile], product: &str) -> bool { diff --git a/src/types.rs b/src/types.rs index bbd8e7e..057fffa 100644 --- a/src/types.rs +++ b/src/types.rs @@ -244,3 +244,22 @@ impl std::error::Error for APTRepositoryFileError { None } } + +#[api] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +/// Additional information for a repository. +pub struct APTRepositoryInfo { + /// Path to the defining file. + #[serde(skip_serializing_if = "String::is_empty")] + pub path: String, + + /// Number of the associated respository within the file. + pub number: usize, + + /// Info kind (e.g. "warning") + pub kind: String, + + /// Info message + pub message: String, +} diff --git a/tests/repositories.rs b/tests/repositories.rs index ffb1888..9b0cd56 100644 --- a/tests/repositories.rs +++ b/tests/repositories.rs @@ -3,9 +3,10 @@ use std::path::PathBuf; use anyhow::{bail, format_err, Error}; use proxmox_apt::repositories::{ - enterprise_repository_enabled, no_subscription_repository_enabled, write_repositories, + check_repositories, enterprise_repository_enabled, no_subscription_repository_enabled, + write_repositories, }; -use proxmox_apt::types::APTRepositoryFile; +use proxmox_apt::types::{APTRepositoryFile, APTRepositoryInfo}; #[test] fn test_parse_write() -> Result<(), Error> { @@ -159,3 +160,95 @@ fn test_proxmox_repositories() -> Result<(), Error> { Ok(()) } + +#[test] +fn test_check_repositories() -> Result<(), Error> { + let test_dir = std::env::current_dir()?.join("tests"); + let read_dir = test_dir.join("sources.list.d"); + + let absolute_suite_list = read_dir.join("absolute_suite.list"); + let mut file = APTRepositoryFile::new(&absolute_suite_list)?.unwrap(); + file.parse()?; + + let infos = check_repositories(&vec![file]); + + assert_eq!(infos.is_empty(), true); + let pve_list = read_dir.join("pve.list"); + let mut file = APTRepositoryFile::new(&pve_list)?.unwrap(); + file.parse()?; + + let path_string = pve_list.into_os_string().into_string().unwrap(); + + let mut expected_infos = vec![]; + for n in 1..=5 { + expected_infos.push(APTRepositoryInfo { + path: path_string.clone(), + number: n, + kind: "badge".to_string(), + message: "official host name".to_string(), + }); + } + + let mut infos = check_repositories(&vec![file]); + + assert_eq!(infos.sort(), expected_infos.sort()); + + let bad_sources = read_dir.join("bad.sources"); + let mut file = APTRepositoryFile::new(&bad_sources)?.unwrap(); + file.parse()?; + + let path_string = bad_sources.into_os_string().into_string().unwrap(); + + let mut expected_infos = vec![ + APTRepositoryInfo { + path: path_string.clone(), + number: 1, + kind: "warning".to_string(), + message: "suite 'sid' should not be used in production!".to_string(), + }, + APTRepositoryInfo { + path: path_string.clone(), + number: 2, + kind: "warning".to_string(), + message: "old suite 'lenny-backports' configured!".to_string(), + }, + APTRepositoryInfo { + path: path_string.clone(), + number: 3, + kind: "warning".to_string(), + message: "old suite 'stretch/updates' configured!".to_string(), + }, + APTRepositoryInfo { + path: path_string.clone(), + number: 4, + kind: "warning".to_string(), + message: "use the name of the stable distribution instead of 'stable'!".to_string(), + }, + APTRepositoryInfo { + path: path_string.clone(), + number: 5, + kind: "ignore-pre-upgrade-warning".to_string(), + message: "suite 'bullseye' should not be used in production!".to_string(), + }, + APTRepositoryInfo { + path: path_string.clone(), + number: 6, + kind: "warning".to_string(), + message: "suite 'testing' should not be used in production!".to_string(), + }, + ]; + for n in 1..=6 { + expected_infos.push(APTRepositoryInfo { + path: path_string.clone(), + number: n, + kind: "badge".to_string(), + message: "official URI".to_string(), + }); + } + + let mut infos = check_repositories(&vec![file]); + + assert_eq!(infos.sort(), expected_infos.sort()); + + Ok(()) +} diff --git a/tests/sources.list.d.expected/bad.sources b/tests/sources.list.d.expected/bad.sources new file mode 100644 index 0000000..36ff7a0 --- /dev/null +++ b/tests/sources.list.d.expected/bad.sources @@ -0,0 +1,30 @@ +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: sid +Components: main contrib + +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: lenny-backports +Components: contrib + +Types: deb +URIs: http://security.debian.org +Suites: stretch/updates +Components: main contrib + +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: stable +Components: main + +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: bullseye +Components: main + +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: testing +Components: main + diff --git a/tests/sources.list.d/bad.sources b/tests/sources.list.d/bad.sources new file mode 100644 index 0000000..6f2524a --- /dev/null +++ b/tests/sources.list.d/bad.sources @@ -0,0 +1,29 @@ +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: sid +Components: main contrib + +Types: deb +URIs: http://ftp.at.debian.org/debian +Suites: lenny-backports +Components: contrib + +Types: deb +URIs: http://security.debian.org +Suites: stretch/updates +Components: main contrib + +Suites: stable +URIs: http://ftp.at.debian.org/debian +Components: main +Types: deb + +Suites: bullseye +URIs: http://ftp.at.debian.org/debian +Components: main +Types: deb + +Suites: testing +URIs: http://ftp.at.debian.org/debian +Components: main +Types: deb -- 2.20.1