Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
53 changes: 51 additions & 2 deletions mgmtd/src/bee_msg/common.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
use super::*;
use crate::db::node_nic::ReplaceNic;
use db::misc::MetaRoot;
use rusqlite::Transaction;
use protobuf::license::VerifyResult;
use rusqlite::{Transaction, params};
use shared::bee_msg::node::*;
use shared::bee_msg::target::*;
use shared::types::{NodeId, TargetId};
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;

// Maximum number of clients that can register if license verification fails or license is invalid
const MAX_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> = if msg.node_type == NodeType::Client {
match app.get_license_cert_data() {
Ok(r) => match r.result() {
// If license is valid, no limit to client count
VerifyResult::VerifyValid => None,
// no license file loaded, limit number of clients to NUM_CLIENTS
VerifyResult::VerifyError => Some(MAX_NUM_CLIENTS),
// license file was loaded and is outside validity period
VerifyResult::VerifyInvalid => None,
_ => {
log::debug!(

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
philippfalk marked this conversation as resolved.
Dismissed
"Unexpected error during license verification, limiting number of clients to {MAX_NUM_CLIENTS}: {0}",
r.message
);
Some(MAX_NUM_CLIENTS)
}
},
Err(e) => {
log::debug!(
"Error during license verification, limiting number of clients to {MAX_NUM_CLIENTS}: {e:#}",
);
Some(MAX_NUM_CLIENTS)
}
}
} else {
// not a client registration, so not going to be used anyway. But let's be defensive
Some(MAX_NUM_CLIENTS)
};

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

let num_reg_clients: 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),
)?;

if msg.node_type == NodeType::Client
&& let Some(cs) = licensed_clients
&& num_reg_clients >= cs {
if reject {
bail!("Number of licensed clients ({MAX_NUM_CLIENTS}) exhausted. Client registration denied.");
} else {
log::warn!("Number of licensed clients ({MAX_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: 6 additions & 2 deletions 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 COMPATFLAG_CLIENT_SUPPORTS_REGREJ: u8 = 1;

impl HandleWithResponse for RegisterNode {
type Response = RegisterNodeResp;

async fn handle(self, app: &impl App, _req: &mut impl Request) -> Result<Self::Response> {
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() & 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
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)
{
app.write_tx(|tx| db::config::set(tx, Config::TrialSerial, serial))
.await?;
}
}
let cert_data = app.get_license_cert_data()?;
Ok(GetLicenseResponse {
Expand Down
27 changes: 27 additions & 0 deletions 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 @@ -114,6 +116,31 @@ 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 and verify license certificate
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}"
Comment on lines +139 to +140
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Potentially incorrect message.

Should be rather like "Loading and verifying license failed"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought about that. But it also doesn't really matter to the user. I intended "Initializing library" to mean the entire process of initialization including loading and verifying a license file. But I have changed it. There is another log before on warning level when the license library can not be loaded.

),
};

// Fill node addrs store from db
db.read_tx(db::node_nic::get_all_addrs)
.await?
Expand Down
34 changes: 24 additions & 10 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,14 +224,27 @@ 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();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why is this called again here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clear the cert store. The license we loaded might be valid, so we have to actively clear it if we reject applying it.

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!(
"Internal error during certificate verification: {message}"
)),
VerifyResult::VerifyError => {
Err(anyhow!("Error during license verification: {message}"))
}
VerifyResult::VerifyUnspecified => Err(anyhow!("Unspecified result.")),
}
}
Expand Down Expand Up @@ -288,9 +302,9 @@ impl LicenseVerifier {
match result {
VerifyResult::VerifyValid => Ok(()),
VerifyResult::VerifyInvalid => Err(anyhow!(message)),
VerifyResult::VerifyError => Err(anyhow!(
"Internal error during feature verification: {message}"
)),
VerifyResult::VerifyError => {
Err(anyhow!("Error during feature verification: {message}"))
}
VerifyResult::VerifyUnspecified => Err(anyhow!("Unspecified result.")),
}
}
Expand Down
35 changes: 10 additions & 25 deletions mgmtd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,33 +113,18 @@ If you want to initialize a new system, refer to --help or doc.beegfs.io.",
.max_blocking_threads(user_config.max_blocking_threads)
.build()?;

// Load the licensing library
//
// 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) };

// 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()
};

// Start the actual daemon
let run = start(
StaticInfo {
Expand Down
Loading
Loading