From 877515c464b95db1dae48bda28bf3342b6dcc2ab Mon Sep 17 00:00:00 2001 From: Randall Naar Date: Thu, 11 Apr 2024 10:51:00 -0400 Subject: [PATCH] Added convenience methods to credentials. --- libs/gl-client-py/glclient/glclient.pyi | 2 + libs/gl-client-py/src/credentials.rs | 40 +++++++++++-- libs/gl-client/src/credentials.rs | 79 +++++++++++++++++++------ libs/gl-client/src/lib.rs | 8 +-- libs/gl-client/src/scheduler.rs | 40 +++---------- libs/gl-client/src/util.rs | 51 +++++++--------- libs/gl-client/src/utils.rs | 31 ++++++++++ 7 files changed, 159 insertions(+), 92 deletions(-) create mode 100644 libs/gl-client/src/utils.rs diff --git a/libs/gl-client-py/glclient/glclient.pyi b/libs/gl-client-py/glclient/glclient.pyi index 3bfd16c98..6e1704b1d 100644 --- a/libs/gl-client-py/glclient/glclient.pyi +++ b/libs/gl-client-py/glclient/glclient.pyi @@ -22,6 +22,8 @@ class Credentials: @staticmethod def nobody_with(cert: bytes, key: bytes, ca: bytes) -> Credentials: ... @staticmethod + def with_identity(cert: bytes, key: bytes) -> Credentials: ... + @staticmethod def from_bytes(data: bytes) -> Credentials: ... @staticmethod def from_path(path: str) -> Credentials: ... diff --git a/libs/gl-client-py/src/credentials.rs b/libs/gl-client-py/src/credentials.rs index b90b6319b..042c99c05 100644 --- a/libs/gl-client-py/src/credentials.rs +++ b/libs/gl-client-py/src/credentials.rs @@ -2,6 +2,8 @@ use crate::runtime::exec; use crate::scheduler::Scheduler; use crate::signer::Signer; use gl_client::credentials::{self, RuneProvider, TlsConfigProvider}; +use gl_client::tls::TlsConfig; +use gl_client::utils::get_node_id_from_tls_config; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::PyBytes; @@ -55,6 +57,25 @@ where UnifiedCredentials::Device(d) => d.tls_config(), } } + + fn with_identity(device_cert: V, device_key: V) -> Self + where + V: Into>, + { + let device_cert: Vec = device_cert.into(); + let device_key: Vec = device_key.into(); + let tls_config = TlsConfig::new().identity(device_cert.clone(), device_key.clone()); + match get_node_id_from_tls_config(&tls_config) { + Ok(n) => { + debug!("Initializing device credentials with node_id {}", hex::encode(n)); + UnifiedCredentials::Device(R::with_identity(device_cert, device_key)) + }, + Err(e) => { + debug!("Device credentials could not be initialized with this certificate: {}.\nFalling back to NOBODY credentials.", e.to_string()); + UnifiedCredentials::Nobody(T::with_identity(device_cert, device_key)) + } + } + } } impl RuneProvider for UnifiedCredentials @@ -65,7 +86,7 @@ where fn rune(&self) -> String { match self { UnifiedCredentials::Nobody(_) => panic!( - "can not provide rune from nobody credentials! something really bad happended." + "can not provide rune from nobody credentials! something really bad happened." ), UnifiedCredentials::Device(d) => d.rune(), } @@ -83,7 +104,10 @@ impl Credentials { #[new] pub fn new() -> Self { let inner = UnifiedCredentials::Nobody(gl_client::credentials::Nobody::default()); - log::debug!("Created NOBODY credentials {:?}", inner.tls_config().client_tls_config()); + log::debug!( + "Created NOBODY credentials {:?}", + inner.tls_config().client_tls_config() + ); Self { inner } } @@ -94,6 +118,13 @@ impl Credentials { Self { inner } } + #[staticmethod] + pub fn with_identity(cert: &[u8], key: &[u8]) -> Self { + let inner = + UnifiedCredentials::with_identity(cert, key); + Self { inner } + } + #[staticmethod] pub fn from_path(path: &str) -> Self { let inner = UnifiedCredentials::Device(gl_client::credentials::Device::from_path(path)); @@ -110,7 +141,8 @@ impl Credentials { #[staticmethod] pub fn from_parts(cert: &[u8], key: &[u8], ca: &[u8], rune: &str) -> Self { - let inner = UnifiedCredentials::Device(gl_client::credentials::Device::with(cert, key, ca, rune)); + let inner = + UnifiedCredentials::Device(gl_client::credentials::Device::with(cert, key, ca, rune)); Self { inner } } @@ -164,4 +196,4 @@ impl From for pyo3::PyErr { fn from(value: ErrorWrapper) -> Self { PyErr::new::(value.to_string()) } -} \ No newline at end of file +} diff --git a/libs/gl-client/src/credentials.rs b/libs/gl-client/src/credentials.rs index c7770bea4..e489b055e 100644 --- a/libs/gl-client/src/credentials.rs +++ b/libs/gl-client/src/credentials.rs @@ -2,6 +2,7 @@ use crate::{ scheduler::Scheduler, signer::Signer, tls::{self, TlsConfig}, + utils::get_node_id_from_tls_config, }; /// Credentials is a collection of all relevant keys and attestations /// required to authenticate a device and authorize a command on the node. @@ -44,6 +45,9 @@ type Result = std::result::Result; pub trait TlsConfigProvider: Send + Sync { fn tls_config(&self) -> TlsConfig; + fn with_identity(device_cert: V, device_key: V) -> Self + where + V: Into>; } pub trait RuneProvider { fn rune(&self) -> String; @@ -99,6 +103,22 @@ impl TlsConfigProvider for Nobody { fn tls_config(&self) -> TlsConfig { tls::TlsConfig::with(&self.cert, &self.key, &self.ca) } + + /// Returns a new Nobody instance with a custom set of parameters. + /// Tries to load the CA from file and defaults to CA_RAW. + fn with_identity(cert: V, key: V) -> Self + where + V: Into>, + { + let ca = + load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT"); + + Self { + cert: cert.into(), + key: key.into(), + ca, + } + } } impl Default for Nobody { @@ -177,31 +197,30 @@ impl Device { /// Asynchronously upgrades the credentials using the provided scheduler and /// signer, potentially involving network operations or other async tasks. - pub async fn upgrade(mut self, scheduler: &Scheduler, signer: &Signer) -> Result + pub async fn upgrade(mut self, _scheduler: &Scheduler, signer: &Signer) -> Result where T: TlsConfigProvider, { use Error::*; - // For now, upgrade is covered by recover - let res = scheduler - .recover(signer) - .await - .map_err(|e| UpgradeCredentialsError(e.to_string()))?; - let mut data = model::Data::try_from(&res.creds[..]) + self.version = CRED_VERSION; + + if self.rune != String::default() { + let node_id = self + .node_id() + .map_err(|e| UpgradeCredentialsError(e.to_string()))?; + + let alt = runeauth::Alternative::new( + "pubkey".to_string(), + runeauth::Condition::Equal, + hex::encode(node_id), + false, + ) .map_err(|e| UpgradeCredentialsError(e.to_string()))?; - data.version = CRED_VERSION; - if let Some(cert) = data.cert { - self.cert = cert - } - if let Some(key) = data.key { - self.key = key - } - if let Some(ca) = data.ca { - self.ca = ca - } - if let Some(rune) = data.rune { - self.rune = rune + + self.rune = signer + .create_rune(None, vec![vec![&alt.encode()]]) + .map_err(|e| UpgradeCredentialsError(e.to_string()))?; }; Ok(self) } @@ -211,12 +230,34 @@ impl Device { pub fn to_bytes(&self) -> Vec { self.to_owned().into() } + + pub fn node_id(&self) -> anyhow::Result> { + get_node_id_from_tls_config(&self.tls_config()) + } } impl TlsConfigProvider for Device { fn tls_config(&self) -> TlsConfig { tls::TlsConfig::with(&self.cert, &self.key, &self.ca) } + + /// Returns a new Device instance with a custom identity. + /// Tries to load the CA from file and defaults to CA_RAW. + /// Rune is initialized to the default value. + fn with_identity(cert: V, key: V) -> Self + where + V: Into>, + { + let ca = + load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT"); + + Self { + cert: cert.into(), + key: key.into(), + ca, + ..Default::default() + } + } } impl RuneProvider for Device { diff --git a/libs/gl-client/src/lib.rs b/libs/gl-client/src/lib.rs index e665eb511..612e65e13 100644 --- a/libs/gl-client/src/lib.rs +++ b/libs/gl-client/src/lib.rs @@ -46,13 +46,7 @@ pub mod tls; pub mod export; /// Tools to interact with a node running on greenlight. -pub mod utils { - - pub fn scheduler_uri() -> String { - std::env::var("GL_SCHEDULER_GRPC_URI") - .unwrap_or_else(|_| "https://scheduler.gl.blckstrm.com".to_string()) - } -} +pub mod utils; pub mod credentials; diff --git a/libs/gl-client/src/scheduler.rs b/libs/gl-client/src/scheduler.rs index 1d1593872..81f9ef3e0 100644 --- a/libs/gl-client/src/scheduler.rs +++ b/libs/gl-client/src/scheduler.rs @@ -2,6 +2,7 @@ use crate::credentials::{self, RuneProvider, TlsConfigProvider}; use crate::node::{self, GrpcClient}; use crate::pb::scheduler::scheduler_client::SchedulerClient; use crate::tls::{self}; +use crate::utils::get_node_id_from_tls_config; use crate::utils::scheduler_uri; use crate::{pb, signer::Signer}; use anyhow::{anyhow, Result}; @@ -433,10 +434,10 @@ where where T: GrpcClient, { - let cert_node_id = self.get_node_id_from_tls_config()?; + let node_id = get_node_id_from_tls_config(&self.creds.tls_config())?; - if cert_node_id != self.node_id { - return Err(anyhow!("The node_id defined on the Credential's certificate does not match the node_id the scheduler was initialized with\nExpected {}, got {}", hex::encode(&self.node_id), hex::encode(&cert_node_id))); + if node_id != self.node_id { + return Err(anyhow!("The node_id defined on the Credential's certificate does not match the node_id the scheduler was initialized with\nExpected {}, got {}", hex::encode(&self.node_id), hex::encode(&node_id))); } let res = self.schedule().await?; @@ -479,7 +480,7 @@ where &self, uri: String, ) -> Result { - let node_id = self.get_node_id_from_tls_config()?; + let node_id = get_node_id_from_tls_config(&self.creds.tls_config())?; let res = self .client .clone() @@ -491,7 +492,7 @@ where pub async fn list_outgoing_webhooks( &self, ) -> Result { - let node_id = self.get_node_id_from_tls_config()?; + let node_id = get_node_id_from_tls_config(&self.creds.tls_config())?; let res = self .client .clone() @@ -501,7 +502,7 @@ where } pub async fn delete_webhooks(&self, webhook_ids: Vec) -> Result { - let node_id = self.get_node_id_from_tls_config()?; + let node_id = get_node_id_from_tls_config(&self.creds.tls_config())?; let res = self .client .clone() @@ -517,7 +518,7 @@ where &self, webhook_id: i64, ) -> Result { - let node_id = self.get_node_id_from_tls_config()?; + let node_id = get_node_id_from_tls_config(&self.creds.tls_config())?; let res = self .client .clone() @@ -528,31 +529,6 @@ where .await?; Ok(res.into_inner()) } - - fn get_node_id_from_tls_config(&self) -> Result> { - let tls_config = self.creds.tls_config(); - let subject_common_name = match &tls_config.x509_cert { - Some(x) => match x.subject_common_name() { - Some(cn) => cn, - None => { - return Err(anyhow!( - "Failed to parse the subject common name in the provided x509 certificate" - )) - } - }, - None => { - return Err(anyhow!( - "The certificate could not be parsed in the x509 format" - )) - } - }; - - let split_subject_common_name = subject_common_name.split("/").collect::>(); - - assert!(split_subject_common_name[1] == "users"); - Ok(hex::decode(split_subject_common_name[2]) - .expect("Failed to parse the node_id from the TlsConfig to bytes")) - } } #[cfg(test)] diff --git a/libs/gl-client/src/util.rs b/libs/gl-client/src/util.rs index fead0f6dc..c9bd4f8c7 100644 --- a/libs/gl-client/src/util.rs +++ b/libs/gl-client/src/util.rs @@ -1,33 +1,24 @@ - - - - - -pub fn is_feature_bit_enabled(bitmap : &[u8], index : usize) -> bool { +pub fn is_feature_bit_enabled(bitmap: &[u8], index: usize) -> bool { let n_bytes = bitmap.len(); - let (byte_index, bit_index ) = (index / 8, index % 8); + let (byte_index, bit_index) = (index / 8, index % 8); // The index doesn't fit in the byte-array if byte_index >= n_bytes { - return false + return false; } let selected_byte = bitmap[n_bytes - 1 - byte_index]; let bit_mask = 1u8 << (bit_index); - - return (selected_byte & bit_mask) != 0 + return (selected_byte & bit_mask) != 0; } - - #[cfg(test)] mod test { use super::*; - fn to_bitmap(feature_hex_string : &str) -> Result, String> { - hex::decode(&feature_hex_string) - .map_err(|x| x.to_string()) + fn to_bitmap(feature_hex_string: &str) -> Result, String> { + hex::decode(&feature_hex_string).map_err(|x| x.to_string()) } #[test] @@ -53,21 +44,21 @@ mod test { assert!(is_feature_bit_enabled(&feature_bitmap_07, 7)); // Check that other bits are disabled - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 0)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 2)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 3)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 4)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 5)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 6)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 7)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 8)); - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 9)); - - assert!(! is_feature_bit_enabled(&feature_bitmap_01, 1000)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 0)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 2)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 3)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 4)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 5)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 6)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 7)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 8)); + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 9)); + + assert!(!is_feature_bit_enabled(&feature_bitmap_01, 1000)); } #[test] - fn test_lsps_option_enabled_bitmap() { + fn test_lsps_option_enabled_bitmap() { // Copied from LSPS0 // This set bit number 729 let data = "0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; @@ -77,7 +68,7 @@ mod test { assert!(is_feature_bit_enabled(&bitmap, 729)); // Check that the expected bit is disabled - assert!(! is_feature_bit_enabled(&bitmap, 728)); - assert!(! is_feature_bit_enabled(&bitmap, 730)); + assert!(!is_feature_bit_enabled(&bitmap, 728)); + assert!(!is_feature_bit_enabled(&bitmap, 730)); } -} \ No newline at end of file +} diff --git a/libs/gl-client/src/utils.rs b/libs/gl-client/src/utils.rs new file mode 100644 index 000000000..7795938c1 --- /dev/null +++ b/libs/gl-client/src/utils.rs @@ -0,0 +1,31 @@ +use anyhow::{anyhow, Result}; +use crate::tls::TlsConfig; + +pub fn scheduler_uri() -> String { + std::env::var("GL_SCHEDULER_GRPC_URI") + .unwrap_or_else(|_| "https://scheduler.gl.blckstrm.com".to_string()) +} + +pub fn get_node_id_from_tls_config(tls_config: &TlsConfig) -> Result> { + let subject_common_name = match &tls_config.x509_cert { + Some(x) => match x.subject_common_name() { + Some(cn) => cn, + None => { + return Err(anyhow!( + "Failed to parse the subject common name in the provided x509 certificate" + )) + } + }, + None => { + return Err(anyhow!( + "The certificate could not be parsed in the x509 format" + )) + } + }; + + let split_subject_common_name = subject_common_name.split("/").collect::>(); + + assert!(split_subject_common_name[1] == "users"); + Ok(hex::decode(split_subject_common_name[2]) + .expect("Failed to parse the node_id from the TlsConfig to bytes")) +} \ No newline at end of file