From d29ad4cd3bafa73a54aa38a77a6ff00d7a27aaaa Mon Sep 17 00:00:00 2001 From: karthik2804 Date: Fri, 25 Oct 2024 15:28:23 +0200 Subject: [PATCH] add ability for plugins to be fetched from authenticated URLs Signed-off-by: karthik2804 --- crates/plugins/src/manager.rs | 45 +++++++++++++++++++++++++++++++---- src/commands/external.rs | 1 + src/commands/plugins.rs | 40 ++++++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/crates/plugins/src/manager.rs b/crates/plugins/src/manager.rs index 34a11364cf..cc7623accb 100644 --- a/crates/plugins/src/manager.rs +++ b/crates/plugins/src/manager.rs @@ -8,6 +8,7 @@ use crate::{ use anyhow::{anyhow, bail, Context, Result}; use path_absolutize::Absolutize; +use reqwest::{header::HeaderMap, Client}; use serde::Serialize; use spin_common::sha256; use std::{ @@ -87,6 +88,7 @@ impl PluginManager { plugin_manifest: &PluginManifest, plugin_package: &PluginPackage, source: &ManifestLocation, + auth_header_value: &Option, ) -> Result { let target = plugin_package.url.to_owned(); let target_url = Url::parse(&target)?; @@ -105,7 +107,15 @@ impl PluginManager { ); } } - _ => download_plugin(&plugin_manifest.name(), &temp_dir, &target).await?, + _ => { + download_plugin( + &plugin_manifest.name(), + &temp_dir, + &target, + auth_header_value, + ) + .await? + } }; verify_checksum(&plugin_tarball_path, &plugin_package.sha256)?; @@ -185,11 +195,16 @@ impl PluginManager { manifest_location: &ManifestLocation, skip_compatibility_check: bool, spin_version: &str, + auth_header_value: &Option, ) -> PluginLookupResult { let plugin_manifest = match manifest_location { ManifestLocation::Remote(url) => { tracing::info!("Pulling manifest for plugin from {url}"); - reqwest::get(url.as_ref()) + let client = Client::new(); + client + .get(url.as_ref()) + .headers(request_headers(auth_header_value)?) + .send() .await .map_err(|e| { Error::ConnectionFailed(ConnectionFailedError::new( @@ -334,9 +349,19 @@ pub fn get_package(plugin_manifest: &PluginManifest) -> Result<&PluginPackage> { }) } -async fn download_plugin(name: &str, temp_dir: &TempDir, target_url: &str) -> Result { +async fn download_plugin( + name: &str, + temp_dir: &TempDir, + target_url: &str, + auth_header_value: &Option, +) -> Result { tracing::trace!("Trying to get tar file for plugin '{name}' from {target_url}"); - let plugin_bin = reqwest::get(target_url).await?; + let client = Client::new(); + let plugin_bin = client + .get(target_url) + .headers(request_headers(auth_header_value)?) + .send() + .await?; if !plugin_bin.status().is_success() { match plugin_bin.status() { reqwest::StatusCode::NOT_FOUND => bail!("The download URL specified in the plugin manifest was not found ({target_url} returned HTTP error 404). Please contact the plugin author."), @@ -364,6 +389,17 @@ fn verify_checksum(plugin_file: &Path, expected_sha256: &str) -> Result<()> { } } +/// Get the request headers for a call to the plugin API +/// +/// If set, this will include the user provided authorization header. +fn request_headers(auth_header_value: &Option) -> Result { + let mut headers = HeaderMap::new(); + if let Some(auth_value) = auth_header_value { + headers.insert(reqwest::header::AUTHORIZATION, auth_value.parse()?); + } + Ok(headers) +} + #[cfg(test)] mod tests { use super::*; @@ -385,6 +421,7 @@ mod tests { &ManifestLocation::Local(PathBuf::from( "../tests/nonexistent-url/nonexistent-url.json", )), + &None, ) .await; diff --git a/src/commands/external.rs b/src/commands/external.rs index 6ad91cb718..15a5d068b5 100644 --- a/src/commands/external.rs +++ b/src/commands/external.rs @@ -209,6 +209,7 @@ fn installer_for(plugin_name: &str) -> Install { remote_manifest_src: None, override_compatibility_check: false, version: None, + auth_header_value: None, } } diff --git a/src/commands/plugins.rs b/src/commands/plugins.rs index fc2385ecf3..639fbacb71 100644 --- a/src/commands/plugins.rs +++ b/src/commands/plugins.rs @@ -98,6 +98,11 @@ pub struct Install { #[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)] pub override_compatibility_check: bool, + /// Provide the value for the authorization header to be able to install a plugin from a private repository. + /// (e.g) --auth-header-value "Bearer " + #[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)] + pub auth_header_value: Option, + /// Specific version of a plugin to be install from the centralized plugins /// repository. #[clap( @@ -126,6 +131,7 @@ impl Install { &manifest_location, self.override_compatibility_check, SPIN_VERSION, + &self.auth_header_value, ) .await?; try_install( @@ -135,6 +141,7 @@ impl Install { self.override_compatibility_check, downgrade, &manifest_location, + &self.auth_header_value, ) .await?; Ok(()) @@ -207,6 +214,11 @@ pub struct Upgrade { #[clap(short = 'y', long = "yes", takes_value = false)] pub yes_to_all: bool, + /// Provide the value for the authorization header to be able to install a plugin from a private repository. + /// (e.g) --auth-header-value "Bearer " + #[clap(long = "auth-header-value", requires = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT)] + pub auth_header_value: Option, + /// Overrides a failed compatibility check of the plugin with the current version of Spin. #[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)] pub override_compatibility_check: bool, @@ -288,7 +300,12 @@ impl Upgrade { // Attempt to get the manifest to check eligibility to upgrade if let Ok(manifest) = manager - .get_manifest(&manifest_location, false, SPIN_VERSION) + .get_manifest( + &manifest_location, + false, + SPIN_VERSION, + &self.auth_header_value, + ) .await { // Check if upgraded candidates have a newer version and if are compatible @@ -341,7 +358,16 @@ impl Upgrade { None, )); - try_install(&manifest, &manager, true, false, false, &manifest_location).await?; + try_install( + &manifest, + &manager, + true, + false, + false, + &manifest_location, + &self.auth_header_value, + ) + .await?; } Ok(()) @@ -365,6 +391,7 @@ impl Upgrade { &manifest_location, self.override_compatibility_check, SPIN_VERSION, + &self.auth_header_value, ) .await { @@ -382,6 +409,7 @@ impl Upgrade { self.override_compatibility_check, self.downgrade, &manifest_location, + &self.auth_header_value, ) .await?; } @@ -405,6 +433,7 @@ impl Upgrade { &manifest_location, self.override_compatibility_check, SPIN_VERSION, + &self.auth_header_value, ) .await?; try_install( @@ -414,6 +443,7 @@ impl Upgrade { self.override_compatibility_check, self.downgrade, &manifest_location, + &self.auth_header_value, ) .await?; Ok(()) @@ -434,6 +464,7 @@ impl Show { &ManifestLocation::PluginsRepository(PluginLookup::new(&self.name, None)), false, SPIN_VERSION, + &None, ) .await?; @@ -789,6 +820,7 @@ async fn try_install( override_compatibility_check: bool, downgrade: bool, source: &ManifestLocation, + auth_header_value: &Option, ) -> Result { let install_action = manager.check_manifest( manifest, @@ -804,7 +836,9 @@ async fn try_install( let package = manager::get_package(manifest)?; if continue_to_install(manifest, package, yes_to_all)? { - let installed = manager.install(manifest, package, source).await?; + let installed = manager + .install(manifest, package, source, auth_header_value) + .await?; println!("Plugin '{installed}' was installed successfully!"); if let Some(description) = manifest.description() {