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 19, 2024
1 parent 9204fd2 commit 877515c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 92 deletions.
2 changes: 2 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,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: ...
Expand Down
40 changes: 36 additions & 4 deletions libs/gl-client-py/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,6 +57,25 @@ where
UnifiedCredentials::Device(d) => d.tls_config(),
}
}

fn with_identity<V>(device_cert: V, device_key: V) -> Self
where
V: Into<Vec<u8>>,
{
let device_cert: Vec<u8> = device_cert.into();
let device_key: Vec<u8> = 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<T, R> RuneProvider for UnifiedCredentials<T, R>
Expand All @@ -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(),
}
Expand All @@ -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 }
}

Expand All @@ -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));
Expand All @@ -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 }
}

Expand Down Expand Up @@ -164,4 +196,4 @@ impl From<ErrorWrapper> for pyo3::PyErr {
fn from(value: ErrorWrapper) -> Self {
PyErr::new::<PyValueError, _>(value.to_string())
}
}
}
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},
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.
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.
/// Tries to load the CA from file 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.
/// Tries to load the CA from file and defaults to CA_RAW.
/// 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
8 changes: 1 addition & 7 deletions libs/gl-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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::utils::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
Loading

0 comments on commit 877515c

Please sign in to comment.