Compare commits

...

5 Commits

Author SHA1 Message Date
brotoskyj 0dbc744471 Merge remote-tracking branch 'origin/RustImplementation' into RustImplementation 2025-12-15 14:14:16 -05:00
brotoskyj 7a912bddab Bug Fixes
Security Vulnerabilities Patched
RUSTSEC-2025-0009 - Some AES functions may panic when overflow checking is enabled
RUSTSEC-2024-0336 - rustls::Connection::Common::complete_io could fall into an infinite loop based on network input
closes #47
2025-12-15 14:13:57 -05:00
brotoskyj 24211c318b Merge remote-tracking branch 'origin/RustImplementation' into RustImplementation
Build Library / Build Library (push) Successful in 5m2s
2025-12-15 14:12:30 -05:00
brotoskyj 630e0a3cdf Bug Fixes
Security Vulnerabilities Patched
RUSTSEC-2025-0009 - Some AES functions may panic when overflow checking is enabled
RUSTSEC-2024-0336 - rustls::Connection::Common::complete_io could fall into an infinite loop based on network input
2025-12-15 14:12:03 -05:00
Zarithas 797d0f4462 fix(policy-prep): implement table editors and workflow improvements
- Add table editors for Policy Prep workflow
- 'Add to policy' remains a placeholder
- Apply planned tweaks:
  - Replace ballot checkbox with ✓ for selection
  - Relocate loading screen text to bottom:
    'Building Path exclusions and publisher lists...
     This may take a moment for large datasets.'
  - Ensure interaction with all tables before allowing review steps
  - Move excessive logging to debug level
  - Add Step 0 to explain process before user begins

Notes:
Further discussion needed on enforcing table interaction before review.
2025-12-11 16:55:12 -05:00
9 changed files with 1718 additions and 1245 deletions
File diff suppressed because it is too large Load Diff
+24 -24
View File
@@ -88,9 +88,9 @@ def sortHashes(
):
working_dir = load_env("WORKING_DIR")
history_days = Selector.select_value(
prompt="Enter how many days of history to pull (1150): ",
prompt="Enter how many days of history to pull (1-365): ",
value_type=int,
valid_range=(1, 150),
valid_range=(1, 365),
)
logger.debug(f"{history_days} day selected for history")
@@ -655,7 +655,7 @@ def section_header(title):
def printEnforceChecklist(selected_policies, destination_policy, destination_allowlist):
working_dir = load_env("WORKING_DIR")
section_header("Prepare to Enforce Policy ")
section_header("Prepare to Enforce Policy")
print(
colorText(
"\nSequentially follow these steps to prepare a policy for enforcement:",
@@ -670,11 +670,11 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
)
)
if not selected_policies:
print(colorText(" [] No policies have been chosen", "red"))
print(colorText(" [❌] No policies have been chosen", "red"))
else:
print(colorText("The following policies have been chosen:", "green"))
for policy in selected_policies:
print(colorText(f" [] {policy.name}", "green"))
print(colorText(f" [✅] {policy.name}", "green"))
# Step 2: Destination Policy and Allowlist
print(
@@ -683,22 +683,22 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
if destination_policy:
print(
colorText(
f" [] {destination_policy[0].name} has been selected as the destination policy",
f" [✅] {destination_policy[0].name} has been selected as the destination policy",
"green",
)
)
else:
print(colorText(" [] No destination policy has been chosen", "red"))
print(colorText(" [❌] No destination policy has been chosen", "red"))
if destination_allowlist:
print(
colorText(
f" [] {destination_allowlist[0].name} has been selected as allowlist",
f" [✅] {destination_allowlist[0].name} has been selected as allowlist",
"green",
)
)
else:
print(colorText(" [] No allowlist has been chosen", "red"))
print(colorText(" [❌] No allowlist has been chosen", "red"))
# Step 3: Data Preparation
print(
@@ -713,9 +713,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(
colorText(
(
" [] Data has been fetched"
" [✅] Data has been fetched"
if os.path.exists(review_path)
else " [] Data has not been fetched"
else " [❌] Data has not been fetched"
),
"green" if os.path.exists(review_path) else "red",
)
@@ -723,7 +723,7 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
else:
print(
colorText(
" [] No policies selected, cannot check data fetch status", "red"
" [❌] No policies selected, cannot check data fetch status", "red"
)
)
@@ -756,9 +756,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(
colorText(
(
" [] Reviewed hashes have been loaded"
" [✅] Reviewed hashes have been loaded"
if os.path.exists(approved_path)
else " [] Reviewed hashes have not been loaded"
else " [❌] Reviewed hashes have not been loaded"
),
"green" if os.path.exists(approved_path) else "red",
)
@@ -766,9 +766,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(
colorText(
(
" [] Path review list created"
" [✅] Path review list created"
if os.path.exists(second_review_path)
else " [] Path review list has not been created"
else " [❌] Path review list has not been created"
),
"green" if os.path.exists(second_review_path) else "red",
)
@@ -776,7 +776,7 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
else:
print(
colorText(
" [] No policies selected, cannot check reviewed hashes or path list",
" [❌] No policies selected, cannot check reviewed hashes or path list",
"red",
)
)
@@ -812,9 +812,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(
colorText(
(
" [] Reviewed path list detected"
" [✅] Reviewed path list detected"
if os.path.exists(reviewed_path)
else " [] Path review list has not been detected"
else " [❌] Path review list has not been detected"
),
"green" if os.path.exists(reviewed_path) else "red",
)
@@ -825,9 +825,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(
colorText(
(
" [] Preflight Path Exclusion List has been generated"
" [✅] Preflight Path Exclusion List has been generated"
if preflight_ready
else " [] Preflight Path Exclusion List has not been generated"
else " [❌] Preflight Path Exclusion List has not been generated"
),
"green" if preflight_ready else "red",
)
@@ -835,7 +835,7 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
else:
print(
colorText(
" [] No policies selected, cannot check preflight status", "red"
" [❌] No policies selected, cannot check preflight status", "red"
)
)
@@ -866,5 +866,5 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(colorText(" Apply approved hashes to allowlist", "cyan"))
# Utility Options
print(colorText("F. Open Working Directory", "cyan"))
print(colorText("B. Back", "cyan"))
print(colorText("F. Open Working Directory", "cyan"))
print(colorText("B. Back", "cyan"))
+355 -884
View File
File diff suppressed because it is too large Load Diff
+11 -13
View File
@@ -1,33 +1,31 @@
[package]
name = "airlock_libs"
version = "5.2.1"
name = "signoz_test"
version = "6.0.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
chrono = "0.4.42"
indicatif = "0.18.2"
mongodb = "3.3.0"
opentelemetry = { version = "0.18.0", features = ["rt-tokio", "metrics", "trace"] }
opentelemetry-otlp = { version = "0.11.0", features = ["trace", "metrics"] }
opentelemetry-semantic-conventions = { version = "0.10.0" }
opentelemetry-proto = { version = "0.1.0"}
opentelemetry = { version = "0.27.0", features = ["logs", "metrics", "trace"] }
opentelemetry-otlp = { version = "0.27.0", features = ["trace", "metrics", "grpc-tonic", "http-proto", "tls", "reqwest-client", "reqwest-rustls"] }
opentelemetry-semantic-conventions = { version = "0.27.0" }
opentelemetry-proto = { version = "0.27.0"}
pyo3 = { version = "0.27.0", features = ["extension-module", "generate-import-lib"] }
reqwest = { version = "0.12.24", features = ["json", "native-tls"] }
reqwest = { version = "0.12.24", features = ["json", "native-tls", "rustls-tls"] }
serde = "1.0.228"
serde-pyobject = "0.8.0"
serde_json = "1.0.145"
tokio = { version = "1.48.0", features = ["full"] }
tonic = { version = "0.8.2", features = ["tls-roots"] }
tonic = { version = "0.12.3", features = ["tls-roots"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.20"
tracing-opentelemetry = "0.32.0"
pyo3-async-runtimes = { version = "0.27.0", features = ["async-std", "tokio"] }
crossbeam = "0.8.4"
log = "0.4.29"
flexi_logger = "0.31.7"
opentelemetry-appender-log = "0.27.0"
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] }
[package.metadata.maturin]
generate-abi-stubs = true
@@ -40,4 +38,4 @@ codegen-units = 1
panic = 'abort'
strip = true
debug-assertions = false
overflow-checks = false
overflow-checks = true
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "airlock_libs"
version = "5.2.1"
version = "6.0.0"
description = "Airlock Digital API Wrapper"
readme = "README.md"
license = { text = "AGPL-3.0-only" }
+6 -6
View File
@@ -1,15 +1,15 @@
pub use chrono::{Duration, Local, NaiveDate};
pub use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
pub use mongodb::bson::oid::ObjectId;
pub use opentelemetry::global::shutdown_tracer_provider;
pub use opentelemetry::sdk::Resource;
pub use opentelemetry::global::GlobalTracerProvider;
pub use opentelemetry::trace::noop::NoopTracerProvider;
pub use opentelemetry::trace::{Status, TraceContextExt, TraceError};
pub use opentelemetry::{Context, KeyValue, sdk::trace as sdktrace, trace::Tracer};
pub use opentelemetry::{Key, global};
pub use opentelemetry::trace::{Status, TraceContextExt, Tracer};
pub use opentelemetry::*;
pub use opentelemetry_otlp::ExportConfig;
pub use opentelemetry_otlp::WithExportConfig;
pub use opentelemetry_sdk::Resource;
pub use opentelemetry_sdk::trace::{Config, TracerProvider};
pub use pyo3::{prelude::*, types::PyString};
pub use pyo3_async_runtimes::async_std;
pub use reqwest::{
Client,
header::{HeaderMap, HeaderName, HeaderValue},
+39 -30
View File
@@ -1,8 +1,10 @@
use crate::modules::datatypes::*;
use crate::prelude::*;
use crossbeam::channel::unbounded;
use opentelemetry_otlp::WithTonicConfig;
use std::sync::{Arc, Mutex};
use std::thread;
use tonic::transport::{Channel, ClientTlsConfig};
#[pyfunction]
pub fn pull_policy_exec_histories(
py: Python<'_>,
@@ -11,9 +13,10 @@ pub fn pull_policy_exec_histories(
exec_types: String,
days: i64,
) -> Py<PyString> {
let data = PyData::extract_data(py, &py_self);
let headers = data.headers;
let base_url = data.base_url;
println!();
let data: PyData = PyData::extract_data(py, &py_self);
let headers: HeaderMap = data.headers;
let base_url: String = data.base_url;
let handle: thread::JoinHandle<String> = std::thread::spawn(move || {
let rt: tokio::runtime::Runtime = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
@@ -22,10 +25,9 @@ pub fn pull_policy_exec_histories(
std::process::abort();
}
};
rt.block_on(async {
let _ = init_tracer();
});
let tracer: global::BoxedTracer = global::tracer("global_tracer");
let tracer_provider = rt.block_on(async { init_tracer() });
global::set_tracer_provider(tracer_provider.clone());
let tracer: global::BoxedTracer = global::tracer("tracer");
let _cx: Context = Context::new();
let file_path: PathBuf = format!(
"{}\\cache\\chunkinator.json",
@@ -106,7 +108,8 @@ pub fn pull_policy_exec_histories(
}
}
});
let cutoff: chrono::NaiveDateTime = Local::now().naive_local() - Duration::days(days);
let cutoff: chrono::NaiveDateTime =
Local::now().naive_local() - chrono::Duration::days(days);
let (tx, rx) = unbounded::<Vec<Group>>();
let pb_clone = progress_bar.clone();
thread::spawn(move || {
@@ -181,7 +184,10 @@ pub fn pull_policy_exec_histories(
.unwrap()
.enable_steady_tick(std::time::Duration::from_millis(100));
let span: opentelemetry::trace::SpanRef<'_> = cx.span();
span.set_attribute(Key::new("Days").string(days.to_string()));
//span.set_attribute(Key::new("Days").string(days.to_string()));
//span.set_attribute(Key::new("Days"));
//span.set_attribute(KeyValue::new("Policy Name", policy_names.clone()));
span.set_attribute(KeyValue::new("Days", days));
span.set_attribute(KeyValue::new("Policy Name", policy_names.clone()));
loop {
let execution_histories = tracer.in_span(checkpoint_number.to_string(), |cx| {
@@ -237,8 +243,10 @@ pub fn pull_policy_exec_histories(
std::process::abort();
}
};
tracer_provider
.shutdown()
.expect("Failed to Shutdown Tracer Provdier");
drop(tx);
shutdown_tracer_provider();
return_data.to_string()
});
let gil_value: String = handle.join().unwrap();
@@ -313,25 +321,26 @@ pub fn get_base_directory() -> PathBuf {
}
}
fn init_tracer() -> Result<Option<sdktrace::Tracer>, TraceError> {
fn init_tracer() -> opentelemetry_sdk::trace::TracerProvider {
let cfg: TelemetryConfig = TelemetryConfig::load();
if !cfg.TELEMETRY {
global::set_tracer_provider(NoopTracerProvider::new());
return Ok(None);
}
let endpoint: String = cfg.TELEM_URL.unwrap_or_default();
let tracer: sdktrace::Tracer =
opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(endpoint),
)
.with_trace_config(sdktrace::config().with_resource(Resource::new(vec![
KeyValue::new("service.name", "LoxideLibs"),
])))
.install_simple()
.unwrap();
Ok(Some(tracer))
let endpoint = cfg.TELEM_URL.unwrap_or_default().clone();
let channel_endpoint = endpoint.clone();
let channel = Channel::from_shared(channel_endpoint.clone())
.unwrap()
.tls_config(ClientTlsConfig::new().with_native_roots())
.unwrap()
.connect_lazy();
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint.clone())
.with_channel(channel)
.build()
.expect("Failed to build exporter");
opentelemetry_sdk::trace::TracerProvider::builder()
.with_simple_exporter(exporter)
.with_resource(Resource::new(vec![KeyValue::new(
"service.name",
"LoxideLibs",
)]))
.build()
}
+1 -1
View File
@@ -11,4 +11,4 @@ urllib3==2.5.0
pyperclip==1.11.0
--extra-index-url https://git.racooncity.org/api/packages/brotoskyj/pypi/simple/
airlock_libs==5.2.1
airlock_libs==6.0.0
+15 -15
View File
@@ -38,9 +38,9 @@ logger = logging.getLogger(__name__)
def devicehistory(api: AirlockAPIWrapper, outputjson: bool):
agents = selectAgents(api)
history_days = Selector.select_value(
prompt="Enter how many days of history to pull (1150): ",
prompt="Enter how many days of history to pull (1–365): ",
value_type=int,
valid_range=(1, 150),
valid_range=(1, 365),
)
if not agents or not history_days:
@@ -60,7 +60,7 @@ def devicehistory(api: AirlockAPIWrapper, outputjson: bool):
except Exception as e:
print(
colorText(
f" Error retrieving history for {agent.hostname}: {e}", "red"
f"❌ Error retrieving history for {agent.hostname}: {e}", "red"
)
)
continue
@@ -139,7 +139,7 @@ def findAgents(api, return_dataframe):
print(
colorText(
f"\n Matched devices exported to: {working_dir}\\{filename}",
f"\n✓ Matched devices exported to: {working_dir}\\{filename}",
"green",
)
)
@@ -148,7 +148,7 @@ def findAgents(api, return_dataframe):
def collect_device_names() -> List[str]:
print(colorText("🖥Â Device Search", "cyan"))
print(colorText("🖥��Â Device Search", "cyan"))
print(
colorText(
"Enter the device hostnames you'd like to search for, one per line.", "cyan"
@@ -185,7 +185,7 @@ def collect_device_names() -> List[str]:
else:
print(
colorText(
f"⚠️ Invalid input: '{stripped_line}'  only letters, numbers, underscores, spaces, and hyphens are allowed.",
f"⚠️ Invalid input: '{stripped_line}' — only letters, numbers, underscores, spaces, and hyphens are allowed.",
"yellow",
)
)
@@ -235,8 +235,8 @@ def show_unmatched(
]
if unmatched:
logger.debug(f"⚠️ No matches for: {', '.join(unmatched)}")
print(colorText(f"⚠️ No matches for: {', '.join(unmatched)}", "yellow"))
logger.debug(f"⚠️ No matches for: {', '.join(unmatched)}")
print(colorText(f"⚠️ No matches for: {', '.join(unmatched)}", "yellow"))
def enrich_agents(agents: List["Agent"], policies: List["Policy"]):
@@ -248,7 +248,7 @@ def selectAgents(api: "AirlockAPIWrapper") -> List["Agent"]:
device_names = collect_device_names()
if not device_names:
logger.debug("No device names entered")
print(colorText("⚠️ No device names entered.", "red"))
print(colorText("⚠️ No device names entered.", "red"))
return []
use_exact = choose_match_type()
@@ -261,11 +261,11 @@ def selectAgents(api: "AirlockAPIWrapper") -> List["Agent"]:
show_unmatched(device_names, matched_agents, use_exact)
if not matched_agents:
logger.debug(" No matching devices found.")
print(colorText(" No matching devices found.", "red"))
logger.debug("❌ No matching devices found.")
print(colorText("❌ No matching devices found.", "red"))
return []
print(colorText(f" Found {len(matched_agents)} matching device(s).", "green"))
print(colorText(f"✓ Found {len(matched_agents)} matching device(s).", "green"))
logger.info("Matched agent hostnames:")
rows = (len(matched_agents) + 2) // 3 # 3 columns
for row in range(rows):
@@ -283,8 +283,8 @@ def selectAgents(api: "AirlockAPIWrapper") -> List["Agent"]:
)
if not matched_agents:
logger.debug(" No matching devices remain after refinement.")
print(colorText(" No matching devices remain after refinement.", "red"))
logger.debug("❌ No matching devices remain after refinement.")
print(colorText("❌ No matching devices remain after refinement.", "red"))
return []
enrich_agents(matched_agents, policies)
@@ -302,7 +302,7 @@ def moveAgentToRelatedPolicy(
Args:
api: AirlockAPIWrapper instance.
agent: Agent object.
policy_relationship_map: Dict mapping enforcement â  audit.
policy_relationship_map: Dict mapping enforcement âââ¬Â ââ¬â¢ audit.
mode: 'audit' to move to audit, 'enforcement' to move to enforcement.
"""
policy_relationship_map = get_system_json("POLICY_MAP_ENF_AUD", "{}")