Skip to content

Commit ec91ec3

Browse files
feat: Add Prometheus metrics for connector lifecycle events and global refactor.
+ Remove lazy static for lazyLock + RSA Key extraction to specific mod + Improve code quality
1 parent 13117b6 commit ec91ec3

20 files changed

Lines changed: 375 additions & 265 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ kube = { version = "0.98.0", features = ["runtime", "derive"] }
2424
k8s-openapi = { version = "0.24.0", features = ["latest"] }
2525
base64 = "0.22.1"
2626
aes-gcm = "0.10.3"
27+
axum = "0.8.8"
28+
prometheus = "0.14.0"
2729

2830
[build-dependencies]
2931
cynic-codegen = { version = "3" }

config/default.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ manager:
1515
# credentials_key_filepath: /path/to/private_key.pem
1616

1717
# Note: If both are set, filepath takes priority with a warning
18-
18+
prometheus:
19+
enable: false
20+
port: 14270
1921
logger:
2022
level: info
2123
format: json

src/api/mod.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ impl ApiConnector {
8989
}
9090

9191
pub fn container_envs(&self) -> Vec<EnvVariable> {
92-
let settings = crate::settings();
92+
let settings = &crate::config::settings::SETTINGS;
9393
let mut envs = self
9494
.contract_configuration
9595
.iter()
@@ -114,24 +114,28 @@ impl ApiConnector {
114114

115115
/// Display environment variables with sensitive values masked (if configured)
116116
pub fn display_env_variables(&self) {
117-
let settings = crate::settings();
118-
117+
let settings = &crate::config::settings::SETTINGS;
118+
119119
// Check if display is enabled in configuration
120-
let should_display = settings.manager.debug
120+
let should_display = settings
121+
.manager
122+
.debug
121123
.as_ref()
122124
.map_or(false, |debug| debug.show_env_vars);
123-
125+
124126
if !should_display {
125127
return;
126128
}
127-
129+
128130
// Check if we should show sensitive values
129-
let show_sensitive = settings.manager.debug
131+
let show_sensitive = settings
132+
.manager
133+
.debug
130134
.as_ref()
131135
.map_or(false, |debug| debug.show_sensitive_env_vars);
132-
136+
133137
let envs = self.container_envs();
134-
138+
135139
// Build environment variables map with masked sensitive values
136140
let env_vars: HashMap<String, String> = envs
137141
.into_iter()
@@ -144,7 +148,7 @@ impl ApiConnector {
144148
(env.key, value)
145149
})
146150
.collect();
147-
151+
148152
// Log with structured fields
149153
info!(
150154
connector_name = %self.name,
@@ -173,5 +177,11 @@ pub trait ComposerApi {
173177

174178
async fn patch_logs(&self, id: String, logs: Vec<String>) -> Option<cynic::Id>;
175179

176-
async fn patch_health(&self, id: String, restart_count: u32, started_at: String, is_in_reboot_loop: bool) -> Option<cynic::Id>;
180+
async fn patch_health(
181+
&self,
182+
id: String,
183+
restart_count: u32,
184+
started_at: String,
185+
is_in_reboot_loop: bool,
186+
) -> Option<cynic::Id>;
177187
}

src/api/openbas/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub struct ApiOpenBAS {
2424

2525
impl ApiOpenBAS {
2626
pub fn new() -> Self {
27-
let settings = crate::settings();
27+
let settings = &crate::config::settings::SETTINGS;
2828
let bearer = format!("{} {}", BEARER, settings.openbas.token);
2929
let api_uri = format!("{}/api", &settings.openbas.url);
3030
let daemon = settings.openbas.daemon.clone();
@@ -81,7 +81,13 @@ impl ComposerApi for ApiOpenBAS {
8181
todo!()
8282
}
8383

84-
async fn patch_health(&self, id: String, restart_count: u32, started_at: String, is_in_reboot_loop: bool) -> Option<cynic::Id> {
84+
async fn patch_health(
85+
&self,
86+
id: String,
87+
restart_count: u32,
88+
started_at: String,
89+
is_in_reboot_loop: bool,
90+
) -> Option<cynic::Id> {
8591
todo!()
8692
}
8793
}

src/api/opencti/manager/post_ping.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::api::opencti::ApiOpenCTI;
2+
use crate::api::opencti::error_handler::{extract_optional_field, handle_graphql_response};
23
use crate::api::opencti::manager::ConnectorManager;
3-
use crate::api::opencti::error_handler::{handle_graphql_response, extract_optional_field};
4-
use crate::settings;
4+
55
use tracing::error;
66

77
use crate::api::opencti::opencti as schema;
@@ -32,7 +32,7 @@ pub struct UpdateConnectorManagerStatusInput<'a> {
3232
pub async fn ping(api: &ApiOpenCTI) -> Option<String> {
3333
use cynic::MutationBuilder;
3434

35-
let settings = settings();
35+
let settings = &crate::config::settings::SETTINGS;
3636
let vars = UpdateConnectorManagerStatusVariables {
3737
input: UpdateConnectorManagerStatusInput {
3838
id: &cynic::Id::new(&settings.manager.id),

src/api/opencti/manager/post_register.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::api::opencti::ApiOpenCTI;
2+
use crate::api::opencti::error_handler::{extract_optional_field, handle_graphql_response};
23
use crate::api::opencti::manager::ConnectorManager;
3-
use crate::api::opencti::error_handler::{handle_graphql_response, extract_optional_field};
44
use crate::api::opencti::opencti as schema;
55
use cynic;
6-
use tracing::{error, info};
76
use rsa::{RsaPublicKey, pkcs1::EncodeRsaPublicKey};
7+
use tracing::{error, info};
88

99
// region schema
1010
#[derive(cynic::QueryVariables, Debug)]
@@ -34,9 +34,8 @@ pub struct RegisterConnectorsManagerInput<'a> {
3434
pub async fn register(api: &ApiOpenCTI) {
3535
use cynic::MutationBuilder;
3636

37-
let settings = crate::settings();
38-
// Use the singleton private key
39-
let priv_key = crate::private_key();
37+
let settings = &crate::config::settings::SETTINGS;
38+
let priv_key = &*crate::config::rsa::PRIVATE_KEY;
4039
let pub_key = RsaPublicKey::from(priv_key);
4140
let public_key = RsaPublicKey::to_pkcs1_pem(&pub_key, Default::default()).unwrap();
4241

@@ -54,12 +53,12 @@ pub async fn register(api: &ApiOpenCTI) {
5453
if let Some(data) = handle_graphql_response(
5554
response,
5655
"register_connectors_manager",
57-
"OpenCTI backend does not support XTM composer manager registration. The composer will continue to run but won't be registered in OpenCTI."
56+
"OpenCTI backend does not support XTM composer manager registration. The composer will continue to run but won't be registered in OpenCTI.",
5857
) {
5958
if let Some(manager) = extract_optional_field(
6059
data.register_connectors_manager,
6160
"register_connectors_manager",
62-
"register_connectors_manager"
61+
"register_connectors_manager",
6362
) {
6463
info!(manager_id = manager.id.into_inner(), "Manager registered");
6564
}

src/api/opencti/mod.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use crate::config::settings::Daemon;
33
use async_trait::async_trait;
44
use cynic::Operation;
55
use cynic::http::CynicReqwestError;
6+
use rsa::RsaPrivateKey;
67
use serde::Serialize;
78
use serde::de::DeserializeOwned;
89
use std::time::Duration;
9-
use rsa::RsaPrivateKey;
1010

1111
pub mod connector;
12-
pub mod manager;
1312
pub mod error_handler;
13+
pub mod manager;
1414

1515
const BEARER: &str = "Bearer";
1616
const AUTHORIZATION_HEADER: &str = "Authorization";
@@ -30,23 +30,23 @@ pub struct ApiOpenCTI {
3030

3131
impl ApiOpenCTI {
3232
pub fn new() -> Self {
33-
let settings = crate::settings();
33+
let settings = &crate::config::settings::SETTINGS;
3434
let bearer = format!("{} {}", BEARER, settings.opencti.token);
3535
let api_uri = format!("{}/graphql", &settings.opencti.url);
3636
let daemon = settings.opencti.daemon.clone();
3737
let logs_schedule = settings.opencti.logs_schedule;
3838
let request_timeout = settings.opencti.request_timeout;
3939
let connect_timeout = settings.opencti.connect_timeout;
4040
// Use the singleton private key
41-
let private_key = crate::private_key().clone();
41+
let private_key = crate::config::rsa::PRIVATE_KEY.clone();
4242
Self {
4343
api_uri,
4444
bearer,
4545
daemon,
4646
logs_schedule,
4747
request_timeout,
4848
connect_timeout,
49-
private_key
49+
private_key,
5050
}
5151
}
5252

@@ -105,7 +105,13 @@ impl ComposerApi for ApiOpenCTI {
105105
connector::post_logs::logs(id, logs, self).await
106106
}
107107

108-
async fn patch_health(&self, id: String, restart_count: u32, started_at: String, is_in_reboot_loop: bool) -> Option<cynic::Id> {
108+
async fn patch_health(
109+
&self,
110+
id: String,
111+
restart_count: u32,
112+
started_at: String,
113+
is_in_reboot_loop: bool,
114+
) -> Option<cynic::Id> {
109115
connector::post_health::health(id, restart_count, started_at, is_in_reboot_loop, self).await
110116
}
111117
}

src/config/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod rsa;
12
pub mod settings;

src/config/rsa.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use rsa::{RsaPrivateKey, pkcs8::DecodePrivateKey};
2+
use std::fs;
3+
use std::sync::LazyLock;
4+
use tracing::{info, warn};
5+
6+
// Singleton RSA private key for all application
7+
pub static PRIVATE_KEY: LazyLock<RsaPrivateKey> =
8+
LazyLock::new(|| load_and_verify_credentials_key());
9+
10+
// Load and verify RSA private key from configuration
11+
fn load_and_verify_credentials_key() -> RsaPrivateKey {
12+
let setting = &crate::config::settings::SETTINGS;
13+
14+
// Priority: file > environment variable
15+
let key_content = if let Some(filepath) = &setting.manager.credentials_key_filepath {
16+
// Warning if both are set
17+
if setting.manager.credentials_key.is_some() {
18+
warn!(
19+
"Both credentials_key and credentials_key_filepath are set. Using filepath (priority)."
20+
);
21+
}
22+
23+
// Read key from file
24+
match fs::read_to_string(filepath) {
25+
Ok(content) => content,
26+
Err(e) => panic!("Failed to read credentials key file '{}': {}", filepath, e),
27+
}
28+
} else if let Some(key) = &setting.manager.credentials_key {
29+
// Use environment variable or config value
30+
key.clone()
31+
} else {
32+
panic!(
33+
"No credentials key provided! Set either 'manager.credentials_key' or 'manager.credentials_key_filepath' in configuration."
34+
);
35+
};
36+
37+
// Validate key format (trim to handle trailing whitespace)
38+
// Check for presence of RSA PRIVATE KEY markers for PKCS#8 format
39+
let trimmed_content = key_content.trim();
40+
if !trimmed_content.contains("BEGIN PRIVATE KEY")
41+
|| !trimmed_content.contains("END PRIVATE KEY")
42+
{
43+
panic!(
44+
"Invalid private key format. Expected PKCS#8 PEM format with 'BEGIN PRIVATE KEY' and 'END PRIVATE KEY' markers."
45+
);
46+
}
47+
48+
// Parse and validate RSA private key using PKCS#8 format
49+
match RsaPrivateKey::from_pkcs8_pem(&key_content) {
50+
Ok(key) => {
51+
info!("Successfully loaded RSA private key (PKCS#8 format)");
52+
key
53+
}
54+
Err(e) => {
55+
panic!("Failed to decode RSA private key: {}", e);
56+
}
57+
}
58+
}

src/config/settings.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ use config::{Config, ConfigError, Environment, File};
22
use k8s_openapi::api::apps::v1::Deployment;
33
use serde::Deserialize;
44
use std::env;
5+
use std::sync::LazyLock;
56

67
const ENV_PRODUCTION: &str = "production";
78

9+
// Singleton settings for all application
10+
pub static SETTINGS: LazyLock<Settings> = LazyLock::new(|| Settings::new().unwrap());
11+
812
#[derive(Debug, Deserialize, Clone)]
913
#[allow(unused)]
1014
pub struct Logger {
@@ -39,6 +43,7 @@ pub struct Manager {
3943
pub credentials_key: Option<String>,
4044
pub credentials_key_filepath: Option<String>,
4145
pub debug: Option<Debug>,
46+
pub prometheus: Option<Prometheus>,
4247
}
4348

4449
#[derive(Debug, Deserialize, Clone)]
@@ -129,6 +134,13 @@ pub struct Docker {
129134
pub ulimits: Option<Vec<std::collections::HashMap<String, serde_json::Value>>>,
130135
}
131136

137+
#[derive(Debug, Deserialize, Clone)]
138+
#[allow(unused)]
139+
pub struct Prometheus {
140+
pub enable: bool,
141+
pub port: u16,
142+
}
143+
132144
#[derive(Debug, Deserialize, Clone)]
133145
#[allow(unused)]
134146
pub struct Settings {

0 commit comments

Comments
 (0)