From d0197bfdcef0ccbc6bdf6d5f8669065e97b3a1ec Mon Sep 17 00:00:00 2001 From: jlanson Date: Fri, 20 Sep 2024 16:23:50 -0400 Subject: [PATCH] feat: allow use of unknown arches through commandline --- hipcheck/build.rs | 10 +++ hipcheck/src/cli.rs | 11 ++- hipcheck/src/engine.rs | 9 +- hipcheck/src/main.rs | 2 +- .../src/plugin/{supported_arch.rs => arch.rs} | 87 ++++++++++++------- hipcheck/src/plugin/download_manifest.rs | 19 ++-- hipcheck/src/plugin/mod.rs | 4 +- hipcheck/src/plugin/plugin_manifest.rs | 38 ++++---- 8 files changed, 112 insertions(+), 68 deletions(-) rename hipcheck/src/plugin/{supported_arch.rs => arch.rs} (55%) diff --git a/hipcheck/build.rs b/hipcheck/build.rs index 0391c0e8..38e64f3f 100644 --- a/hipcheck/build.rs +++ b/hipcheck/build.rs @@ -5,8 +5,18 @@ use pathbuf::pathbuf; use tonic_build::compile_protos; fn main() -> Result<()> { + // Compile the Hipcheck gRPC protocol spec to an .rs file let root = env!("CARGO_MANIFEST_DIR"); let path = pathbuf![root, "proto", "hipcheck", "v1", "hipcheck.proto"]; compile_protos(path)?; + + // Make the target available as a compile-time env var for plugin arch + // resolution + println!( + "cargo:rustc-env=TARGET={}", + std::env::var("TARGET").unwrap() + ); + println!("cargo:rerun-if-changed-env=TARGET"); + Ok(()) } diff --git a/hipcheck/src/cli.rs b/hipcheck/src/cli.rs index d95a7eab..f7d51ed5 100644 --- a/hipcheck/src/cli.rs +++ b/hipcheck/src/cli.rs @@ -7,7 +7,7 @@ use crate::{ error::Context, error::Result, hc_error, - plugin::SupportedArch, + plugin::Arch, session::pm, shell::{color_choice::ColorChoice, verbosity::Verbosity}, source, @@ -19,7 +19,10 @@ use crate::{ use clap::{Parser as _, ValueEnum}; use hipcheck_macros as hc; use pathbuf::pathbuf; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; use url::Url; /// Automatated supply chain risk assessment of software packages. @@ -417,8 +420,8 @@ pub struct CheckArgs { #[clap(subcommand)] command: Option, - #[arg(long = "arch")] - pub arch: Option, + #[arg(long = "arch", value_parser = Arch::from_str)] + pub arch: Option, #[arg(short = 't', long = "target")] pub target_type: Option, diff --git a/hipcheck/src/engine.rs b/hipcheck/src/engine.rs index 661f1183..8a730381 100644 --- a/hipcheck/src/engine.rs +++ b/hipcheck/src/engine.rs @@ -5,8 +5,8 @@ use crate::{ cache::plugin::HcPluginCache, hc_error, plugin::{ - get_plugin_key, retrieve_plugins, try_get_current_arch, Plugin, PluginManifest, - PluginResponse, QueryResult, + get_current_arch, get_plugin_key, retrieve_plugins, Plugin, PluginManifest, PluginResponse, + QueryResult, }, policy::PolicyFile, util::fs::{find_file_by_name, read_string}, @@ -230,7 +230,8 @@ pub fn start_plugins( /* jitter_percent */ 10, )?; - let current_arch = try_get_current_arch()?; + let current_arch = get_current_arch(); + println!("CURRENT ARCH: {}", current_arch); // retrieve, verify and extract all required plugins let required_plugin_names = retrieve_plugins(&policy_file.plugins.0, plugin_cache)?; @@ -248,7 +249,7 @@ pub fn start_plugins( let contents = read_string(&plugin_kdl)?; let plugin_manifest = PluginManifest::from_str(contents.as_str())?; let entrypoint = plugin_manifest - .get_entrypoint(current_arch) + .get_entrypoint(¤t_arch) .ok_or_else(|| { hc_error!( "Could not find {} entrypoint for {}/{} {}", diff --git a/hipcheck/src/main.rs b/hipcheck/src/main.rs index ea8f98d3..2fdece46 100644 --- a/hipcheck/src/main.rs +++ b/hipcheck/src/main.rs @@ -119,7 +119,7 @@ fn main() -> ExitCode { /// Run the `check` command. fn cmd_check(args: &CheckArgs, config: &CliConfig) -> ExitCode { // Before we do any analysis, set the user-provided arch - if let Some(arch) = args.arch { + if let Some(arch) = &args.arch { if let Err(e) = try_set_arch(arch) { Shell::print_error(&e, Format::Human); return ExitCode::FAILURE; diff --git a/hipcheck/src/plugin/supported_arch.rs b/hipcheck/src/plugin/arch.rs similarity index 55% rename from hipcheck/src/plugin/supported_arch.rs rename to hipcheck/src/plugin/arch.rs index 058b503d..a86d85d7 100644 --- a/hipcheck/src/plugin/supported_arch.rs +++ b/hipcheck/src/plugin/arch.rs @@ -9,7 +9,7 @@ use std::{fmt::Display, result::Result as StdResult, str::FromStr, sync::OnceLoc /// Officially supported target triples, as of RFD #0004 /// /// NOTE: these architectures correspond to the offically supported Rust platforms -pub enum SupportedArch { +pub enum KnownArch { /// Used for macOS running on "Apple Silicon" running on a 64-bit ARM Instruction Set Architecture (ISA) Aarch64AppleDarwin, /// Used for macOS running on the Intel 64-bit ISA @@ -18,22 +18,34 @@ pub enum SupportedArch { X86_64PcWindowsMsvc, /// Used for Linux operating systems running on the Intel 64-bit ISA with a GNU toolchain for compilation X86_64UnknownLinuxGnu, + /// Used for Linux operating systems running on a 64-bit ARM ISA + Aarch64UnknownLinuxGnu, } -pub const DETECTED_ARCH: Option = { +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Arch { + Known(KnownArch), + Unknown(String), +} + +pub const DETECTED_ARCH_STR: &str = env!("TARGET"); + +pub const DETECTED_ARCH: Option = { if cfg!(target_arch = "x86_64") { if cfg!(target_os = "macos") { - Some(SupportedArch::X86_64AppleDarwin) + Some(KnownArch::X86_64AppleDarwin) } else if cfg!(target_os = "linux") { - Some(SupportedArch::X86_64UnknownLinuxGnu) + Some(KnownArch::X86_64UnknownLinuxGnu) } else if cfg!(target_os = "windows") { - Some(SupportedArch::X86_64PcWindowsMsvc) + Some(KnownArch::X86_64PcWindowsMsvc) } else { None } } else if cfg!(target_arch = "aarch64") { if cfg!(target_os = "macos") { - Some(SupportedArch::Aarch64AppleDarwin) + Some(KnownArch::Aarch64AppleDarwin) + } else if cfg!(target_os = "linux") { + Some(KnownArch::Aarch64UnknownLinuxGnu) } else { None } @@ -42,34 +54,24 @@ pub const DETECTED_ARCH: Option = { } }; -pub static USER_PROVIDED_ARCH: OnceLock = OnceLock::new(); +pub static USER_PROVIDED_ARCH: OnceLock = OnceLock::new(); /// Get the target architecture for plugins. If the user provided a target, /// return that. Otherwise, if the `hc` binary was compiled for a supported /// architecture, return that. Otherwise return None. -pub fn get_current_arch() -> Option { +pub fn get_current_arch() -> Arch { if let Some(arch) = USER_PROVIDED_ARCH.get() { - Some(*arch) - } else if DETECTED_ARCH.is_some() { - DETECTED_ARCH + arch.clone() + } else if let Some(known_arch) = DETECTED_ARCH { + Arch::Known(known_arch) } else { - None + Arch::Unknown(DETECTED_ARCH_STR.to_owned()) } } -/// Like `get_current_arch()`, but returns an error message suggesting the -/// user specifies a target on the CLI -pub fn try_get_current_arch() -> Result { - if let Some(arch) = get_current_arch() { - Ok(arch) - } else { - Err(hc_error!("Could not resolve the current machine to one of the Hipcheck supported architectures. Please specify --arch on the commandline.")) - } -} - -pub fn try_set_arch(arch: SupportedArch) -> Result<()> { - let set_arch = USER_PROVIDED_ARCH.get_or_init(|| arch); - if *set_arch == arch { +pub fn try_set_arch(arch: &Arch) -> Result<()> { + let set_arch = USER_PROVIDED_ARCH.get_or_init(|| arch.clone()); + if set_arch == arch { Ok(()) } else { Err(hc_error!( @@ -80,12 +82,13 @@ pub fn try_set_arch(arch: SupportedArch) -> Result<()> { } } -impl FromStr for SupportedArch { +impl FromStr for KnownArch { type Err = crate::Error; fn from_str(s: &str) -> StdResult { match s { "aarch64-apple-darwin" => Ok(Self::Aarch64AppleDarwin), + "aarch64-unknown-linux-gnu" => Ok(Self::Aarch64UnknownLinuxGnu), "x86_64-apple-darwin" => Ok(Self::X86_64AppleDarwin), "x86_64-pc-windows-msvc" => Ok(Self::X86_64PcWindowsMsvc), "x86_64-unknown-linux-gnu" => Ok(Self::X86_64UnknownLinuxGnu), @@ -94,14 +97,36 @@ impl FromStr for SupportedArch { } } -impl Display for SupportedArch { +impl Display for KnownArch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let target_triple = match self { - SupportedArch::Aarch64AppleDarwin => "aarch64-apple-darwin", - SupportedArch::X86_64AppleDarwin => "x86_64-apple-darwin", - SupportedArch::X86_64PcWindowsMsvc => "x86_64-pc-windows-msvc", - SupportedArch::X86_64UnknownLinuxGnu => "x86_64-unknown-linux-gnu", + KnownArch::Aarch64AppleDarwin => "aarch64-apple-darwin", + KnownArch::Aarch64UnknownLinuxGnu => "aarch64-unknown-linux-gnu", + KnownArch::X86_64AppleDarwin => "x86_64-apple-darwin", + KnownArch::X86_64PcWindowsMsvc => "x86_64-pc-windows-msvc", + KnownArch::X86_64UnknownLinuxGnu => "x86_64-unknown-linux-gnu", }; write!(f, "{}", target_triple) } } + +impl FromStr for Arch { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> StdResult { + if let Ok(known_arch) = FromStr::from_str(s) { + Ok(Arch::Known(known_arch)) + } else { + Ok(Arch::Unknown(s.to_owned())) + } + } +} + +impl Display for Arch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Arch::Known(known_arch) => known_arch.fmt(f), + Arch::Unknown(arch_str) => arch_str.fmt(f), + } + } +} diff --git a/hipcheck/src/plugin/download_manifest.rs b/hipcheck/src/plugin/download_manifest.rs index 82c0f55c..f5d210c9 100644 --- a/hipcheck/src/plugin/download_manifest.rs +++ b/hipcheck/src/plugin/download_manifest.rs @@ -6,9 +6,9 @@ use crate::{ error::Error, hc_error, plugin::{ + arch::Arch, + get_current_arch, retrieval::{download_plugin, extract_plugin}, - supported_arch::SupportedArch, - try_get_current_arch, }, util::kdl::{extract_data, ParseKdlNode}, util::{ @@ -21,6 +21,9 @@ use kdl::{KdlDocument, KdlNode, KdlValue}; use std::{collections::HashSet, fmt::Display, io::Read, str::FromStr}; use url::Url; +#[cfg(test)] +use crate::plugin::arch::KnownArch; + // NOTE: the implementation in this crate was largely derived from RFD #0004 impl ParseKdlNode for url::Url { @@ -225,7 +228,7 @@ pub struct DownloadManifestEntry { /// but only a specific concrete version pub version: PluginVersion, /// The target architecture for a plugin - pub arch: SupportedArch, + pub arch: Arch, /// The URL of the archive file to download containing the plugin executable artifact and /// plugin manifest. pub url: url::Url, @@ -251,7 +254,7 @@ impl ParseKdlNode for DownloadManifestEntry { // Per RFD #0004, version is of type String let version = PluginVersion(node.get("version")?.value().as_string()?.to_string()); // Per RFD #0004, arch is of type String - let arch = SupportedArch::from_str(node.get("arch")?.value().as_string()?).ok()?; + let arch = Arch::from_str(node.get("arch")?.value().as_string()?).ok()?; // there should be one child for each plugin and it should contain the url, hash, compress // and size information @@ -288,7 +291,7 @@ impl DownloadManifestEntry { version: &PluginVersion, downloaded_plugins: &'a mut HashSet, ) -> Result<&'a HashSet, Error> { - let current_arch = try_get_current_arch()?; + let current_arch = get_current_arch(); let plugin_id = PluginId::new(publisher.clone(), name.clone(), version.clone()); @@ -564,7 +567,7 @@ mod test { let expected_entry = DownloadManifestEntry { version: PluginVersion(version.to_string()), - arch: SupportedArch::from_str(arch).unwrap(), + arch: Arch::Known(KnownArch::from_str(arch).unwrap()), url: Url::parse(url).unwrap(), hash: HashWithDigest::new( HashAlgorithm::try_from(hash_alg).unwrap(), @@ -605,7 +608,7 @@ plugin version="0.1.0" arch="x86_64-apple-darwin" { assert_eq!( &DownloadManifestEntry { version: PluginVersion("0.1.0".to_owned()), - arch: SupportedArch::Aarch64AppleDarwin, + arch: Arch::Known(KnownArch::Aarch64AppleDarwin), url: Url::parse("https://github.com/mitre/hipcheck/releases/download/hipcheck-v3.4.0/hipcheck-aarch64-apple-darwin.tar.xz").unwrap(), hash: HashWithDigest::new(HashAlgorithm::Sha256, "b8e111e7817c4a1eb40ed50712d04e15b369546c4748be1aa8893b553f4e756b".to_owned()), compress: Compress::new(ArchiveFormat::TarXz), @@ -618,7 +621,7 @@ plugin version="0.1.0" arch="x86_64-apple-darwin" { assert_eq!( &DownloadManifestEntry { version: PluginVersion("0.1.0".to_owned()), - arch: SupportedArch::X86_64AppleDarwin, + arch: Arch::Known(KnownArch::X86_64AppleDarwin), url: Url::parse("https://github.com/mitre/hipcheck/releases/download/hipcheck-v3.4.0/hipcheck-x86_64-apple-darwin.tar.xz").unwrap(), hash: HashWithDigest::new(HashAlgorithm::Sha256, "ddb8c6d26dd9a91e11c99b3bd7ee2b9585aedac6e6df614190f1ba2bfe86dc19".to_owned()), compress: Compress::new(ArchiveFormat::TarXz), diff --git a/hipcheck/src/plugin/mod.rs b/hipcheck/src/plugin/mod.rs index 87573286..c17e20b0 100644 --- a/hipcheck/src/plugin/mod.rs +++ b/hipcheck/src/plugin/mod.rs @@ -1,21 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 +mod arch; mod download_manifest; mod manager; mod plugin_id; mod plugin_manifest; mod retrieval; -mod supported_arch; mod types; use crate::error::Result; pub use crate::plugin::{get_plugin_key, manager::*, plugin_id::PluginId, types::*}; +pub use arch::{get_current_arch, try_set_arch, Arch}; pub use download_manifest::{ArchiveFormat, DownloadManifest, HashAlgorithm, HashWithDigest}; pub use plugin_manifest::{PluginManifest, PluginName, PluginPublisher, PluginVersion}; pub use retrieval::retrieve_plugins; use serde_json::Value; use std::collections::HashMap; -pub use supported_arch::{try_get_current_arch, try_set_arch, SupportedArch}; use tokio::sync::Mutex; pub async fn initialize_plugins( diff --git a/hipcheck/src/plugin/plugin_manifest.rs b/hipcheck/src/plugin/plugin_manifest.rs index 7b81c344..8d37ba10 100644 --- a/hipcheck/src/plugin/plugin_manifest.rs +++ b/hipcheck/src/plugin/plugin_manifest.rs @@ -3,13 +3,16 @@ use crate::{ error::Error, hc_error, - plugin::supported_arch::SupportedArch, + plugin::Arch, string_newtype_parse_kdl_node, util::kdl::{extract_data, ParseKdlNode}, }; use kdl::{KdlDocument, KdlNode}; use std::{collections::HashMap, str::FromStr}; +#[cfg(test)] +use crate::plugin::arch::KnownArch; + // NOTE: the implementation in this crate was largely derived from RFD #4 #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -29,15 +32,15 @@ pub struct License(pub String); string_newtype_parse_kdl_node!(License, "license"); #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Entrypoints(pub HashMap); +pub struct Entrypoints(pub HashMap); impl Entrypoints { pub fn new() -> Self { Self(HashMap::new()) } - pub fn insert(&mut self, arch: SupportedArch, entrypoint: String) -> Result<(), Error> { - match self.0.insert(arch, entrypoint) { + pub fn insert(&mut self, arch: Arch, entrypoint: String) -> Result<(), Error> { + match self.0.insert(arch.clone(), entrypoint) { Some(_duplicate_key) => Err(hc_error!("Multiple entrypoints specified for {}", arch)), None => Ok(()), } @@ -56,8 +59,7 @@ impl ParseKdlNode for Entrypoints { let mut entrypoints = Entrypoints::new(); for entrypoint_spec in node.children()?.nodes() { // per RFD #0004, the value for "arch" is of type String - let arch = - SupportedArch::from_str(entrypoint_spec.get("arch")?.value().as_string()?).ok()?; + let arch = Arch::from_str(entrypoint_spec.get("arch")?.value().as_string()?).ok()?; // per RFD #0004, the actual entrypoint is the first positional arg after "arch" and is // of type String let entrypoint = entrypoint_spec @@ -66,7 +68,7 @@ impl ParseKdlNode for Entrypoints { .value() .as_string()? .to_string(); - if let Err(_e) = entrypoints.insert(arch, entrypoint) { + if let Err(_e) = entrypoints.insert(arch.clone(), entrypoint) { log::error!("Duplicate entrypoint detected for [{}]", arch); return None; } @@ -185,8 +187,8 @@ pub struct PluginManifest { } impl PluginManifest { - pub fn get_entrypoint(&self, arch: SupportedArch) -> Option { - self.entrypoints.0.get(&arch).cloned() + pub fn get_entrypoint(&self, arch: &Arch) -> Option { + self.entrypoints.0.get(arch).cloned() } } @@ -278,7 +280,7 @@ mod test { let mut expected = Entrypoints::new(); expected .insert( - SupportedArch::Aarch64AppleDarwin, + Arch::Known(KnownArch::Aarch64AppleDarwin), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); @@ -308,25 +310,25 @@ mod test { let mut expected = Entrypoints::new(); expected .insert( - SupportedArch::Aarch64AppleDarwin, + Arch::Known(KnownArch::Aarch64AppleDarwin), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); expected .insert( - SupportedArch::X86_64AppleDarwin, + Arch::Known(KnownArch::X86_64AppleDarwin), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); expected .insert( - SupportedArch::X86_64UnknownLinuxGnu, + Arch::Known(KnownArch::X86_64UnknownLinuxGnu), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); expected .insert( - SupportedArch::X86_64PcWindowsMsvc, + Arch::Known(KnownArch::X86_64PcWindowsMsvc), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); @@ -408,25 +410,25 @@ dependencies { let mut entrypoints = Entrypoints::new(); entrypoints .insert( - SupportedArch::Aarch64AppleDarwin, + Arch::Known(KnownArch::Aarch64AppleDarwin), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); entrypoints .insert( - SupportedArch::X86_64AppleDarwin, + Arch::Known(KnownArch::X86_64AppleDarwin), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); entrypoints .insert( - SupportedArch::X86_64UnknownLinuxGnu, + Arch::Known(KnownArch::X86_64UnknownLinuxGnu), "./hc-mitre-affiliation".to_owned(), ) .unwrap(); entrypoints .insert( - SupportedArch::X86_64PcWindowsMsvc, + Arch::Known(KnownArch::X86_64PcWindowsMsvc), "./hc-mitre-affiliation".to_owned(), ) .unwrap();