Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
6 changes: 4 additions & 2 deletions mgmtd/src/app/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,10 @@ 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
4 changes: 3 additions & 1 deletion mgmtd/src/app/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,9 @@ 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
38 changes: 37 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,37 @@
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:#}",
);
Some(NUM_CLIENTS)
},
};

let licensed_machines = match app.get_licensed_machines() {
Ok(n) => n,
Err(err) => {
Expand Down Expand Up @@ -97,6 +123,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
6 changes: 5 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,17 @@ 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() & 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
4 changes: 4 additions & 0 deletions mgmtd/src/bee_msg/request_exceeded_quota.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::license::LicensedFeature;

use super::*;
use rusqlite::params;
use shared::bee_msg::quota::*;
Expand All @@ -13,6 +15,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
17 changes: 15 additions & 2 deletions mgmtd/src/grpc/get_license.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
use super::*;
use crate::db::config::Config;
use protobuf::management::{self as pm, GetLicenseResponse};
use protobuf::license::CertType;

pub(crate) async fn get_license(
app: &impl App,
req: pm::GetLicenseRequest,
) -> 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)
.await?;
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())
{
app.write_tx(|tx| db::config::set(tx, Config::TrialSerial, serial)).await?;
}

}
let cert_data = app.get_license_cert_data()?;
Ok(GetLicenseResponse {
Expand Down
41 changes: 40 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,43 @@ 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
17 changes: 15 additions & 2 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 @@ -224,8 +225,20 @@ impl LicenseVerifier {

match result {
VerifyResult::VerifyValid => {
log::info!("Successfully loaded license certificate: {serial}");
Ok(serial)
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();
Err(anyhow!("System has previously used different trial license."))
} else {
log::info!("Successfully loaded license certificate: {serial}");
Ok(serial)
}
},
Err(err) => Err(anyhow!("Error getting license data: {err}")),
}
}
VerifyResult::VerifyInvalid => Err(anyhow!(message)),
VerifyResult::VerifyError => Err(anyhow!(
Expand Down
27 changes: 0 additions & 27 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,31 +114,6 @@ 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 {
Expand All @@ -148,7 +122,6 @@ If you want to initialize a new system, refer to --help or doc.beegfs.io.",
auth_secret,
network_addrs,
},
license,
)
.await?;

Expand Down
Loading
Loading