Skip to content

Commit

Permalink
Added convenience methods to credentials.
Browse files Browse the repository at this point in the history
  • Loading branch information
Randy808 committed Apr 18, 2024
1 parent 9204fd2 commit 0291adf
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 79 deletions.
4 changes: 4 additions & 0 deletions libs/gl-client-py/glclient/glclient.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Credentials:
@staticmethod
def nobody_with(cert: bytes, key: bytes, ca: bytes) -> Credentials: ...
@staticmethod
def nobody_with_identity(cert: bytes, key: bytes) -> Credentials: ...
@staticmethod
def device_with_identity(cert: bytes, key: bytes) -> Credentials: ...
@staticmethod
def from_bytes(data: bytes) -> Credentials: ...
@staticmethod
def from_path(path: str) -> Credentials: ...
Expand Down
12 changes: 12 additions & 0 deletions libs/gl-client-py/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ impl Credentials {
Self { inner }
}

#[staticmethod]
pub fn nobody_with_identity(cert: &[u8], key: &[u8]) -> Self {
let inner = UnifiedCredentials::Nobody(gl_client::credentials::Nobody::with_identity(cert, key));
Self { inner }
}

#[staticmethod]
pub fn device_with_identity(cert: &[u8], key: &[u8]) -> Self {
let inner = UnifiedCredentials::Device(gl_client::credentials::Device::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));
Expand Down
79 changes: 60 additions & 19 deletions libs/gl-client/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
scheduler::Scheduler,
signer::Signer,
tls::{self, TlsConfig},
util::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.
Expand Down Expand Up @@ -44,6 +45,9 @@ type Result<T, E = Error> = std::result::Result<T, E>;

pub trait TlsConfigProvider: Send + Sync {
fn tls_config(&self) -> TlsConfig;
fn with_identity<V>(device_cert: V, device_key: V) -> Self
where
V: Into<Vec<u8>>;
}
pub trait RuneProvider {
fn rune(&self) -> String;
Expand Down Expand Up @@ -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.
/// Loads the CA using the GL_CA_CRT env var and defaults to CA_RAW.
fn with_identity<V>(cert: V, key: V) -> Self
where
V: Into<Vec<u8>>,
{
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 {
Expand Down Expand Up @@ -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<T>(mut self, scheduler: &Scheduler<T>, signer: &Signer) -> Result<Self>
pub async fn upgrade<T>(mut self, _scheduler: &Scheduler<T>, signer: &Signer) -> Result<Self>
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)
}
Expand All @@ -211,12 +230,34 @@ impl Device {
pub fn to_bytes(&self) -> Vec<u8> {
self.to_owned().into()
}

pub fn node_id(&self) -> anyhow::Result<Vec<u8>> {
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.
/// Loads the CA from file and defaults to CA_RAW while the
/// Rune is initialized to the default value.
fn with_identity<V>(cert: V, key: V) -> Self
where
V: Into<Vec<u8>>,
{
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 {
Expand Down
40 changes: 8 additions & 32 deletions libs/gl-client/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::util::get_node_id_from_tls_config;
use crate::utils::scheduler_uri;
use crate::{pb, signer::Signer};
use anyhow::{anyhow, Result};
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -479,7 +480,7 @@ where
&self,
uri: String,
) -> Result<pb::scheduler::AddOutgoingWebhookResponse> {
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()
Expand All @@ -491,7 +492,7 @@ where
pub async fn list_outgoing_webhooks(
&self,
) -> Result<pb::scheduler::ListOutgoingWebhooksResponse> {
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()
Expand All @@ -501,7 +502,7 @@ where
}

pub async fn delete_webhooks(&self, webhook_ids: Vec<i64>) -> Result<pb::greenlight::Empty> {
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()
Expand All @@ -517,7 +518,7 @@ where
&self,
webhook_id: i64,
) -> Result<pb::scheduler::WebhookSecretResponse> {
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()
Expand All @@ -528,31 +529,6 @@ where
.await?;
Ok(res.into_inner())
}

fn get_node_id_from_tls_config(&self) -> Result<Vec<u8>> {
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::<Vec<&str>>();

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)]
Expand Down
74 changes: 46 additions & 28 deletions libs/gl-client/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
use anyhow::{anyhow, Result};
use crate::tls::TlsConfig;





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;
}


pub fn get_node_id_from_tls_config(tls_config: &TlsConfig) -> Result<Vec<u8>> {
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::<Vec<&str>>();

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)]
mod test {
use super::*;

fn to_bitmap(feature_hex_string : &str) -> Result<Vec<u8>, String> {
hex::decode(&feature_hex_string)
.map_err(|x| x.to_string())
fn to_bitmap(feature_hex_string: &str) -> Result<Vec<u8>, String> {
hex::decode(&feature_hex_string).map_err(|x| x.to_string())
}

#[test]
Expand All @@ -53,21 +71,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";
Expand All @@ -77,7 +95,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));
}
}
}

0 comments on commit 0291adf

Please sign in to comment.