Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ itertools = "0"
libc = "0"
log = { version = "0", features = ["std"] }
prost = "0.14"
protobuf = { git = "https://github.com/thinkparq/protobuf", rev = "e2e774e7db7e3d4474d6e7232bb06bbdffc5610c" }
protobuf = { git = "https://github.com/thinkparq/protobuf", rev = "4d5e5db085065acbbaa5bb76ce4b81d6d733e446" }
regex = "1"
ring = "0"
rusqlite = { version = "0", features = ["bundled", "vtab", "array", "fallible_uint"] }
Expand Down
1 change: 1 addition & 0 deletions mgmtd/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub(crate) trait App: Debug + Clone + Send + 'static {
fn load_and_verify_license_cert(
&self,
cert_path: &Path,
prev_trial_serial: Option<String>,
) -> impl Future<Output = Result<String>> + Send;

/// Get license certificate data
Expand Down
9 changes: 7 additions & 2 deletions mgmtd/src/app/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,13 @@ impl App for RuntimeApp {
}
}

async fn load_and_verify_license_cert(&self, cert_path: &Path) -> Result<String> {
LicenseVerifier::load_and_verify_license_cert(&self.license, cert_path).await
async fn load_and_verify_license_cert(
&self,
cert_path: &Path,
prev_trial_serial: Option<String>,
) -> Result<String> {
LicenseVerifier::load_and_verify_license_cert(&self.license, cert_path, prev_trial_serial)
.await
}

fn get_license_cert_data(&self) -> Result<GetCertDataResult> {
Expand Down
6 changes: 5 additions & 1 deletion mgmtd/src/app/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ impl App for TestApp {

fn notify_client_pulled_state(&self, _node_type: NodeType, _node_id: NodeId) {}

async fn load_and_verify_license_cert(&self, _cert_path: &std::path::Path) -> Result<String> {
async fn load_and_verify_license_cert(
&self,
_cert_path: &std::path::Path,
_prev_trial_serial: Option<String>,
) -> Result<String> {
Ok("dummy cert".to_string())
}

Expand Down
39 changes: 38 additions & 1 deletion mgmtd/src/bee_msg/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::*;
use crate::db::node_nic::ReplaceNic;
use db::misc::MetaRoot;
use protobuf::license::VerifyResult;
use rusqlite::Transaction;
use shared::bee_msg::node::*;
use shared::bee_msg::target::*;
Expand All @@ -9,12 +10,38 @@
use std::sync::Arc;
use std::time::Duration;

const NUM_CLIENTS: u32 = 5;

/// Processes incoming node information. Registers new nodes if config allows it
pub(super) async fn update_node(msg: RegisterNode, app: &impl App) -> Result<NodeId> {
pub(super) async fn update_node(msg: RegisterNode, app: &impl App, reject: bool) -> Result<NodeId> {
let nics = msg.nics.clone();
let requested_node_id = msg.node_id;
let registration_disable = app.static_info().user_config.registration_disable;

let licensed_clients: Option<u32> = match app.get_license_cert_data() {
Ok(r) => match r.result {
Comment thread
rustybee42 marked this conversation as resolved.
Outdated
// If license is valid, no limit to client count
_ if r.result == VerifyResult::VerifyValid as i32 => None,
// no license file loaded, limit number of clients to NUM_CLIENTS
_ if r.result == VerifyResult::VerifyError as i32 => Some(NUM_CLIENTS),
// license file was loaded and is outside validity period
_ if r.result == VerifyResult::VerifyInvalid as i32 => None,
_ => {
log::error!(

Check failure

Code scanning / CodeQL

Cleartext logging of sensitive information High

This operation writes
app.get_license_cert_data()
to a log file.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
"Unexpected error during license verification, limiting number of clients to {NUM_CLIENTS}: {0}",
r.message
);
Some(NUM_CLIENTS)
}
},
Err(e) => {
log::error!(
"Unexpected error during license verification, limiting number of clients to {NUM_CLIENTS}: {e:#}",
Comment thread
rustybee42 marked this conversation as resolved.
Outdated
);
Some(NUM_CLIENTS)
}
};

let licensed_machines = match app.get_licensed_machines() {
Ok(n) => n,
Err(err) => {
Expand Down Expand Up @@ -97,6 +124,16 @@
bail!("Registration of new nodes is not allowed");
}

if msg.node_type == NodeType::Client
&& let Some(cs) = licensed_clients
&& db::node::count_clients(tx)? >= cs {
if reject {
bail!("Number of licensed clients ({NUM_CLIENTS}) exhausted. Client registration denied.");
} else {
log::warn!("Number of licensed clients ({NUM_CLIENTS}) exhausted but client doesn't support rejection.");
}
}

let new_alias = if msg.node_type == NodeType::Client {
// In versions prior to 8.0 the string node ID generated by the client
// started with a number which is not allowed by the new alias schema.
Expand Down
1 change: 1 addition & 0 deletions mgmtd/src/bee_msg/heartbeat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl HandleWithResponse for Heartbeat {
machine_uuid: self.machine_uuid,
},
app,
false,
)
.await?;

Expand Down
8 changes: 7 additions & 1 deletion mgmtd/src/bee_msg/register_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ use super::*;
use common::update_node;
use shared::bee_msg::node::*;

const REGISTERNODEMSG_COMPATFLAG_CLIENT_SUPPORTS_REGREJ: u8 = 1;
Comment thread
rustybee42 marked this conversation as resolved.
Outdated

impl HandleWithResponse for RegisterNode {
type Response = RegisterNodeResp;

async fn handle(self, app: &impl App, _req: &mut impl Request) -> Result<Self::Response> {
fail_on_pre_shutdown(app)?;

let node_id = update_node(self, app).await?;
let reject = (_req.msg_compat_feature_flags()
Comment thread
rustybee42 marked this conversation as resolved.
Outdated
& REGISTERNODEMSG_COMPATFLAG_CLIENT_SUPPORTS_REGREJ)
!= 0;

let node_id = update_node(self, app, reject).await?;

let fs_uuid: String = app
.read_tx(|tx| db::config::get(tx, db::config::Config::FsUuid))
Expand Down
3 changes: 3 additions & 0 deletions mgmtd/src/bee_msg/request_exceeded_quota.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::license::LicensedFeature;
use rusqlite::params;
use shared::bee_msg::quota::*;

Expand All @@ -13,6 +14,8 @@ impl HandleWithResponse for RequestExceededQuota {
}

async fn handle(self, app: &impl App, _req: &mut impl Request) -> Result<Self::Response> {
app.verify_licensed_feature(LicensedFeature::Quota)?;

let inner = app
.read_tx(move |tx| {
// Quota is calculated per pool, so if a target ID is given, use its assigned pools
Expand Down
2 changes: 2 additions & 0 deletions mgmtd/src/db/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) enum Config {
#[allow(unused)]
FsName,
CounterLastClientID,
TrialSerial,
}

// Config entries that should not be changed after initially set. Note that this only controls the
Expand All @@ -31,6 +32,7 @@ impl Config {
Config::FsInitDateSecs => "fs_init_date_secs",
Config::FsName => "fs_name",
Config::CounterLastClientID => "counter_last_client_id",
Config::TrialSerial => "trial_serial",
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions mgmtd/src/db/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,19 @@ pub(crate) fn count_machines(
.map_err(|e| anyhow!(e))
}

/// Counts the number of currently registered distinct clients.
///
/// # Return value
/// Returns the number of currently registered distinct clients if successful.
pub(crate) fn count_clients(tx: &Transaction) -> Result<u32> {
tx.query_row(
sql!("SELECT COUNT(DISTINCT node_uid) FROM nodes WHERE node_type = ?1"),
params![NodeType::Client.sql_variant()],
|row| row.get(0),
)
.map_err(|e| anyhow!(e))
}

Comment thread
rustybee42 marked this conversation as resolved.
Outdated
/// Delete a node from the database.
pub(crate) fn delete(tx: &Transaction, node_uid: Uid) -> Result<()> {
let affected = tx.execute_cached(sql!("DELETE FROM nodes WHERE node_uid = ?1"), [node_uid])?;
Expand Down
21 changes: 20 additions & 1 deletion mgmtd/src/grpc/get_license.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::*;
use crate::db::config::Config;
use protobuf::license::CertType;
use protobuf::management::{self as pm, GetLicenseResponse};

pub(crate) async fn get_license(
Expand All @@ -7,8 +9,25 @@ pub(crate) async fn get_license(
) -> Result<pm::GetLicenseResponse> {
let reload: bool = required_field(req.reload)?;
if reload {
app.load_and_verify_license_cert(&app.static_info().user_config.license_cert_file)
let prev_trial_serial: Option<String> = app
.read_tx(|tx| db::config::get(tx, Config::TrialSerial))
.await?;

let serial = app
.load_and_verify_license_cert(
&app.static_info().user_config.license_cert_file,
prev_trial_serial,
)
.await?;

if app
.get_license_cert_data()?
.data
.is_some_and(|d| d.r#type == CertType::Trial.into())
Comment thread
rustybee42 marked this conversation as resolved.
Outdated
{
app.write_tx(|tx| db::config::set(tx, Config::TrialSerial, serial))
.await?;
}
}
let cert_data = app.get_license_cert_data()?;
Ok(GetLicenseResponse {
Expand Down
43 changes: 42 additions & 1 deletion mgmtd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ use crate::app::RuntimeApp;
use crate::config::Config;
use anyhow::{Context, Result};
use app::App;
use db::config::Config as dbConfig;
use db::node_nic::ReplaceNic;
use license::LicenseVerifier;
use protobuf::license::CertType;
use shared::bee_msg::target::RefreshTargetStates;
use shared::conn::incoming;
use shared::conn::outgoing::Pool;
Expand Down Expand Up @@ -54,7 +56,7 @@ pub struct StaticInfo {
/// Returns after all setup work is done and all tasks are started. The caller is responsible for
/// keeping the shutdown control handle and send a shutdown request when the program shall
/// be terminated.
pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunControl> {
pub async fn start(info: StaticInfo) -> Result<RunControl> {
// Initialization

let (run_state, run_state_control) = run_state::new();
Expand Down Expand Up @@ -114,6 +116,45 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
})
.await?;

let prev_trial_serial: Option<String> = db
.read_tx(|tx| db::config::get(tx, db::config::Config::TrialSerial))
.await?;

// Load the licensing library
let license = if !info.user_config.license_disable {
// SAFETY:
// There is no way to verify that the user loaded dynamic library matches the
// requirements of LicenseVerifier. After all, users can load anything they
// want. Therefore, this is just not safe to do from the Rust compilers
// perspective and loading anything with non-matching fp signatures or not
// behaving as expected will lead to undefined behavior.
let license = unsafe { LicenseVerifier::with_lib(&info.user_config.license_lib_file) };
Comment thread
rustybee42 marked this conversation as resolved.
Outdated

match license
.load_and_verify_license_cert(&info.user_config.license_cert_file, prev_trial_serial)
.await
{
Ok(serial) => {
if license
.get_license_cert_data()?
.data
.is_some_and(|d| d.r#type == CertType::Trial.into())
{
db.write_tx(|tx| db::config::set(tx, dbConfig::TrialSerial, serial))
.await?;
}
}
Err(err) => log::warn!(
"Initializing licensing library failed. \
Licensed features will be unavailable: {err}"
),
}

license
} else {
LicenseVerifier::with_no_lib()
};

// Fill node addrs store from db
db.read_tx(db::node_nic::get_all_addrs)
.await?
Expand Down
22 changes: 18 additions & 4 deletions mgmtd/src/license.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ impl LicenseVerifier {
pub async fn load_and_verify_license_cert(
&self,
cert_path: impl AsRef<Path>,
prev_trial_serial: Option<String>,
) -> Result<String> {
let Some(ref library) = self.0 else {
bail!("License verification library not loaded.");
Expand All @@ -223,10 +224,23 @@ impl LicenseVerifier {
let message = res.message;

match result {
VerifyResult::VerifyValid => {
log::info!("Successfully loaded license certificate: {serial}");
Ok(serial)
}
VerifyResult::VerifyValid => match self.get_license_cert_data() {
Ok(c) => {
if c.data.is_some_and(|d| {
d.r#type() == CertType::Trial
&& prev_trial_serial.is_some_and(|s| serial != s)
}) {
library.init_cert_store();
Comment thread
philippfalk marked this conversation as resolved.
Err(anyhow!(
Comment thread
iamjoemccormick marked this conversation as resolved.
"System has previously used different trial license."
Comment thread
iamjoemccormick marked this conversation as resolved.
Outdated
))
} else {
log::info!("Successfully loaded license certificate: {serial}");
Ok(serial)
}
}
Err(err) => Err(anyhow!("Error getting license data: {err}")),
Comment thread
iamjoemccormick marked this conversation as resolved.
},
VerifyResult::VerifyInvalid => Err(anyhow!(message)),
VerifyResult::VerifyError => Err(anyhow!(
"Internal error during certificate verification: {message}"
Expand Down
41 changes: 6 additions & 35 deletions mgmtd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use anyhow::{Context, Result, anyhow};
use log::LevelFilter;
use mgmtd::config::LogTarget;
use mgmtd::db::{self};
use mgmtd::license::LicenseVerifier;
use mgmtd::{StaticInfo, start};
use shared::journald_logger;
use shared::nic::check_ipv6;
Expand Down Expand Up @@ -115,41 +114,13 @@ If you want to initialize a new system, refer to --help or doc.beegfs.io.",

// Run the tokio executor
rt.block_on(async move {
// Load the licensing library
let license = if !user_config.license_disable {
// SAFETY:
// There is no way to verify that the user loaded dynamic library matches the
// requirements of LicenseVerifier. After all, users can load anything they
// want. Therefore, this is just not safe to do from the Rust compilers
// perspective and loading anything with non-matching fp signatures or not
// behaving as expected will lead to undefined behavior.
let license = unsafe { LicenseVerifier::with_lib(&user_config.license_lib_file) };

if let Err(err) = license
.load_and_verify_license_cert(&user_config.license_cert_file)
.await
{
log::warn!(
"Initializing licensing library failed. \
Licensed features will be unavailable: {err}"
);
}

license
} else {
LicenseVerifier::with_no_lib()
};
Comment thread
iamjoemccormick marked this conversation as resolved.

// Start the actual daemon
let run = start(
StaticInfo {
use_ipv6,
user_config,
auth_secret,
network_addrs,
},
license,
)
let run = start(StaticInfo {
use_ipv6,
user_config,
auth_secret,
network_addrs,
})
.await?;

// Mgmtds systemd unit is set to service type "notify". Here we send out the
Expand Down
Loading
Loading