RustImplementation #23

Merged
mysticmomba merged 118 commits from RustImplementation into master 2025-11-04 18:13:24 -05:00
6 changed files with 117 additions and 82 deletions
Showing only changes of commit f8302e15c1 - Show all commits
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+28 -15
View File
@@ -132,6 +132,7 @@ def buildPathsandPublishers(selected_policies: List[Policy], split):
all_approved_hashes = pd.DataFrame() all_approved_hashes = pd.DataFrame()
path1 = f"{working_dir}\\Approved\\{selected_policies[0].name}_approved_executions.csv" path1 = f"{working_dir}\\Approved\\{selected_policies[0].name}_approved_executions.csv"
path2 = f"{working_dir}\\Approved\\{selected_policies[0].name}_needs_review_executions.csv" path2 = f"{working_dir}\\Approved\\{selected_policies[0].name}_needs_review_executions.csv"
path_exclusion_constant = get_protected_value("PATH_EXCLUSION_CONST", cast_type= int)
if os.path.exists(path1): if os.path.exists(path1):
df1 = pd.read_csv(path1) df1 = pd.read_csv(path1)
@@ -154,29 +155,31 @@ def buildPathsandPublishers(selected_policies: List[Policy], split):
else: else:
logger.warning("Warning: 'filename' column not found in concatenated DataFrame.") logger.warning("Warning: 'filename' column not found in concatenated DataFrame.")
if not all_approved_hashes.empty: if not all_approved_hashes.empty and path_exclusion_constant:
primary_path_exclusions = calculatePath( primary_path_exclusions = calculatePath(
all_approved_hashes, all_approved_hashes, path_exclusion_constant,
split, split,
) )
remaining_hashes = all_approved_hashes[ remaining_hashes = all_approved_hashes[
~all_approved_hashes["sha256"].isin(primary_path_exclusions["sha256"]) ~all_approved_hashes["sha256"].isin(primary_path_exclusions["sha256"])
] ]
secondary_path_exclusions = calculatePath( secondary_path_exclusions = calculatePath(
remaining_hashes, split remaining_hashes,(path_exclusion_constant - 1), split
) )
remaining_hashes = remaining_hashes[ remaining_hashes = remaining_hashes[
~remaining_hashes["sha256"].isin(secondary_path_exclusions["sha256"]) ~remaining_hashes["sha256"].isin(secondary_path_exclusions["sha256"])
] ]
dataframes = { dataframes = {
"all_approved_hashes" : all_approved_hashes,
"primary_Paths": primary_path_exclusions, "primary_Paths": primary_path_exclusions,
"secondary_Paths": secondary_path_exclusions, "secondary_Paths": secondary_path_exclusions,
"hashes_to_add": remaining_hashes, "hashes_not_approvable_by_path": remaining_hashes
} }
logger.debug("Preparing to sort dataframes") logger.debug("Preparing to sort dataframes")
for name, df in dataframes.items(): for name, df in dataframes.items():
logger.debug(f" DataFrame headers: {list(df.columns)}") logger.debug(f" DataFrame headers: {list(df.columns)}")
if name == "hashes_to_add": df.sort_values(by="filename", inplace=True) if "hashes" in name : df.sort_values(by="filename", inplace=True)
else: df.sort_values(by="longestcfp", inplace=True) else: df.sort_values(by="longestcfp", inplace=True)
df.to_csv(f"{working_dir}\\Needs_Review\\Review_Second\\{selected_policies[0].name}_{name}.csv", index=False) df.to_csv(f"{working_dir}\\Needs_Review\\Review_Second\\{selected_policies[0].name}_{name}.csv", index=False)
@@ -193,6 +196,8 @@ def buildPathsandPublishers(selected_policies: List[Policy], split):
publist = publist[["publisher"]] publist = publist[["publisher"]]
publist.sort_values(by="publisher", inplace=True) publist.sort_values(by="publisher", inplace=True)
publist.to_csv(f"{working_dir}\\Needs_Review\\Review_Second\\{selected_policies[0].name}_publishers.csv", index=False) publist.to_csv(f"{working_dir}\\Needs_Review\\Review_Second\\{selected_policies[0].name}_publishers.csv", index=False)
else:
logger.debug("Approved Hashes list appears empty")
def buildPreflights(selected_policies: List[Policy]): def buildPreflights(selected_policies: List[Policy]):
working_dir = load_env("WORKING_DIR") working_dir = load_env("WORKING_DIR")
@@ -202,17 +207,13 @@ def buildPreflights(selected_policies: List[Policy]):
approved_hashes = pd.DataFrame() approved_hashes = pd.DataFrame()
approved_publishers = pd.DataFrame() approved_publishers = pd.DataFrame()
hash = f"{working_dir}\\Approved\\{selected_policies[0].name}_hashes_to_add.csv" hash = f"{working_dir}\\Needs_Review\\Review_Second\\{selected_policies[0].name}_all_approved_hashes.csv"
path1 = f"{working_dir}\\Approved\\{selected_policies[0].name}_primary_Paths.csv" path1 = f"{working_dir}\\Approved\\{selected_policies[0].name}_primary_Paths.csv"
path2 = f"{working_dir}\\Approved\\{selected_policies[0].name}_secondary_Paths.csv" path2 = f"{working_dir}\\Approved\\{selected_policies[0].name}_secondary_Paths.csv"
publishers = f"{working_dir}\\Approved\\{selected_policies[0].name}_publishers.csv" publishers = f"{working_dir}\\Approved\\{selected_policies[0].name}_publishers.csv"
if os.path.exists(hash):
approved_hashes = pd.read_csv(hash)
else:
logger.warning(f"File not found: {hash}")
#Read in and combine the two path generations
if os.path.exists(path1): if os.path.exists(path1):
df1 = pd.read_csv(path1) df1 = pd.read_csv(path1)
else: else:
@@ -229,6 +230,19 @@ def buildPreflights(selected_policies: List[Policy]):
else: else:
approved_paths = pd.concat([df1, df2], ignore_index=True) approved_paths = pd.concat([df1, df2], ignore_index=True)
approved_paths = approved_paths.drop_duplicates(subset="longestcfp", keep ="first")
#We create a list of hashes that are left over if we exclude the ones that are covered by the path exclusions.
if os.path.exists(hash):
hashes = pd.read_csv(hash)
approved_hashes = hashes[~hashes['filename'].isin(approved_paths['longestcfp'])]
approved_hashes = approved_hashes.drop_duplicates(subset="sha256", keep ="first")
else:
logger.warning(f"File not found: {hash}")
if os.path.exists(publishers): if os.path.exists(publishers):
approved_publishers = pd.read_csv(publishers) approved_publishers = pd.read_csv(publishers)
@@ -246,8 +260,7 @@ def buildPreflights(selected_policies: List[Policy]):
df.to_csv(f"{working_dir}\\Preflight\\{selected_policies[0].name}_{name}.csv", index=False) df.to_csv(f"{working_dir}\\Preflight\\{selected_policies[0].name}_{name}.csv", index=False)
formatHTML(df, f"{working_dir}\\Preflight\\HTML\\{selected_policies[0].name}_{name}.html") formatHTML(df, f"{working_dir}\\Preflight\\HTML\\{selected_policies[0].name}_{name}.html")
def splitFilepathsGrouped(df, col="filename"): def splitFilepathsGrouped(df, path_exclusion_constant, col="filename"):
path_exclusion_constant = get_protected_value("PATH_EXCLUSION_CONST", cast_type= int)
min_files_for_path = get_protected_value("MIN_FILES_FOR_PATH", cast_type= int) min_files_for_path = get_protected_value("MIN_FILES_FOR_PATH", cast_type= int)
def clean_split(path): def clean_split(path):
@@ -307,7 +320,7 @@ def splitFilepathsGrouped(df, col="filename"):
return pd.DataFrame(new_rows).drop(columns=["group_key"]) return pd.DataFrame(new_rows).drop(columns=["group_key"])
def calculatePath(approved_hashes, split): def calculatePath(approved_hashes, path_exclusion_constant, split):
if split: if split:
dfs_by_policy = [group for _, group in approved_hashes.groupby("policy")] dfs_by_policy = [group for _, group in approved_hashes.groupby("policy")]
else: else:
@@ -319,7 +332,7 @@ def calculatePath(approved_hashes, split):
processed_dfs = [] processed_dfs = []
for df in dfs_by_policy: for df in dfs_by_policy:
haslcp = splitFilepathsGrouped(df, "filename") haslcp = splitFilepathsGrouped(df, path_exclusion_constant, "filename")
haslcp = haslcp.drop_duplicates() haslcp = haslcp.drop_duplicates()
forbidden = regulator(badpathparts, True) forbidden = regulator(badpathparts, True)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

+19 -8
View File
@@ -41,6 +41,7 @@ from utils.utils import (
get_sanitized_input, get_sanitized_input,
open_directory, open_directory,
printEnforceChecklist, printEnforceChecklist,
locked
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -99,7 +100,7 @@ def menu_main(api: AirlockAPIWrapper):
else: else:
print(colorText("Invalid choice. Please try again.", "red")) print(colorText("Invalid choice. Please try again.", "red"))
def menu_policy_enforce(api: AirlockAPIWrapper): def menu_policy_enforce(api: AirlockAPIWrapper): #TODO Need to clean up 6 and 7 into functions
selected_policies = [] selected_policies = []
destination_policy = [] destination_policy = []
destination_allowlist = [] destination_allowlist = []
@@ -139,7 +140,7 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
print("File not found. Please make sure it's saved correctly and try again.") print("File not found. Please make sure it's saved correctly and try again.")
elif choice == "5": elif choice == "5":
if os.path.exists(f"{working_dir}\\Approved\\{selected_policies[0].name}_hashes_to_add.csv") and os.path.exists( if os.path.exists(f"{working_dir}\\Approved\\{selected_policies[0].name}_approved_executions.csv") and os.path.exists(
f"{working_dir}\\Approved\\{selected_policies[0].name}_primary_Paths.csv" f"{working_dir}\\Approved\\{selected_policies[0].name}_primary_Paths.csv"
): ):
buildPreflights(selected_policies) buildPreflights(selected_policies)
@@ -167,11 +168,13 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
for path, ext in unique_combinations.itertuples(index=False, name=None) for path, ext in unique_combinations.itertuples(index=False, name=None)
] ]
print(processed_paths) for path in processed_paths:
print(path)
print(colorText("These publishers would added", "yellow")) print(colorText("These publishers would added", "yellow"))
if os.path.exists(f"{working_dir}\\Preflight\\{selected_policies[0].name}_approved_publishers.csv"): if os.path.exists(f"{working_dir}\\Preflight\\{selected_policies[0].name}_approved_publishers.csv"):
publishers = pd.read_csv(f"{working_dir}\\Preflight\\{selected_policies[0].name}_approved_hashes.csv") publishers = pd.read_csv(f"{working_dir}\\Preflight\\{selected_policies[0].name}_approved_publishers.csv")
if publishers.empty: if publishers.empty:
print(colorText("The publishers list is empty.", "red")) print(colorText("The publishers list is empty.", "red"))
else: else:
@@ -181,13 +184,14 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
.drop_duplicates() .drop_duplicates()
.tolist() .tolist()
) )
print(processed_publishers) for publisher in processed_publishers:
print(publisher)
print(colorText("These hashes would be added to:", "yellow")) print(colorText("These hashes would be added to:", "yellow"))
print(destination_allowlist) print(destination_allowlist)
processed_hashes = hashes["sha256"].unique().tolist() processed_hashes = hashes["sha256"].unique().tolist()
print(processed_hashes) print_three_wide(processed_hashes)
if processed_paths and processed_hashes: if processed_paths and processed_hashes:
tested = True tested = True
@@ -207,7 +211,6 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
for item in missing_items: for item in missing_items:
logger.error(f" - {item}") logger.error(f" - {item}")
elif choice == "7": elif choice == "7":
areYouSure() areYouSure()
confirmation = get_sanitized_input("Type 'I AGREE' to continue: ") confirmation = get_sanitized_input("Type 'I AGREE' to continue: ")
@@ -222,6 +225,9 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
api.policy_add_path_exclusions(destination_policy[0].groupid, processed_paths) api.policy_add_path_exclusions(destination_policy[0].groupid, processed_paths)
if processed_publishers: if processed_publishers:
api.policy_add_publishers(destination_policy[0].groupid, processed_publishers) api.policy_add_publishers(destination_policy[0].groupid, processed_publishers)
locked()
else: else:
logger.error("Confirmation block failed. Reasons:") logger.error("Confirmation block failed. Reasons:")
if not tested: if not tested:
@@ -237,7 +243,7 @@ def menu_policy_enforce(api: AirlockAPIWrapper):
open_directory(working_dir) open_directory(working_dir)
elif choice.upper() == "S": elif choice.upper() == "S":
menu_settings() menu_settings()
elif choice.upper == "B": elif choice.upper() == "B":
break break
@@ -314,3 +320,8 @@ def menu_settings():
break break
else: else:
print("Invalid choice. Please try again.") print("Invalid choice. Please try again.")
def print_three_wide(items):
for i in range(0, len(items), 3):
row = items[i:i+3]
print(" | ".join(row))
+51 -31
View File
@@ -15,6 +15,7 @@
import json import json
import logging import logging
import logging.config
import logging.handlers import logging.handlers
import os import os
import platform import platform
@@ -36,49 +37,69 @@ def get_base_directory() -> Path:
else: else:
return home / '.local' / 'share' / "AirlockTools" return home / '.local' / 'share' / "AirlockTools"
def configure_logging(log_dir: Path, log_level: str = "DEBUG"): def configure_logging(log_dir: Path, log_level: str = "DEBUG"):
log_file = log_dir / "airlocktools.log" log_file = log_dir / "airlocktools.log"
logger = logging.getLogger()
# Always allow all messages to propagate to handlers config = {
logger.setLevel(logging.DEBUG) "version": 1, # Required key for dictConfig format version
"disable_existing_loggers": False, # Keeps existing loggers active
"formatters": {
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# Includes timestamp, logger name, level, and message
},
"simple": {
"format": "%(levelname)s - %(message)s"
# Minimal format for console output
},
},
"handlers": {
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": str(log_file),
"when": "midnight", # Rotate logs at midnight
"interval": 1, # Every 1 day
"backupCount": 7, # Keep 7 days of logs
"encoding": "utf-8", # Ensure UTF-8 encoding
"level": "DEBUG", # Always log DEBUG and above
"formatter": "detailed", # Use detailed format
},
"console": {
"class": "logging.StreamHandler",
"level": log_level.upper(), # Configurable log level
"formatter": "simple", # Use simple format
},
},
"root": {
"level": "DEBUG", # Root logger level
"handlers": ["file", "console"], # Attach both handlers
},
}
# Remove existing handlers # Add Windows Event Log handler if on Windows
for handler in logger.handlers[:]:
logger.removeHandler(handler)
# File handler always logs DEBUG and above
file_handler = logging.handlers.RotatingFileHandler(
log_file, maxBytes=5_000_000, backupCount=5, encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
logger.addHandler(file_handler)
# Console handler respects the configured log level
console_handler = logging.StreamHandler()
console_handler.setLevel(getattr(logging, log_level.upper(), logging.INFO))
console_handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
logger.addHandler(console_handler)
# Optional Windows Event Log handler
if platform.system() == "Windows": if platform.system() == "Windows":
try: try:
event_handler = logging.handlers.NTEventLogHandler("AirlockTools") config["handlers"]["eventlog"] = {
event_handler.setLevel(logging.CRITICAL) "class": "logging.handlers.NTEventLogHandler",
event_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) "appname": "AirlockTools", # Event log source name
logger.addHandler(event_handler) "level": "CRITICAL", # Only log critical errors
"formatter": "simple", # Use simple format
}
config["root"]["handlers"].append("eventlog")
except Exception as e: except Exception as e:
logger.warning(f"Could not attach Windows Event Log handler: {e}") logging.warning(f"Could not attach Windows Event Log handler: {e}")
# Apply the logging configuration
logging.config.dictConfig(config)
logging.getLogger().debug("✅ Logging configured.")
logger.debug("✅ Logging configured.")
def get_system_config_path() -> Path: def get_system_config_path() -> Path:
base_path = Path(getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))) base_path = Path(getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))))
return base_path.parent / "system_config.json" return base_path.parent / "system_config.json"
def load_system_config() -> dict: def load_system_config() -> dict:
try: try:
config_path = get_system_config_path() config_path = get_system_config_path()
@@ -180,4 +201,3 @@ def setup():
logging.debug(f"Service URL set to: {url}") logging.debug(f"Service URL set to: {url}")
write_config_to_env(merged_config, env_path) write_config_to_env(merged_config, env_path)
+17 -26
View File
@@ -184,20 +184,18 @@ def displayIntro():
) )
) )
def section_header(title):
def printEnforceChecklist(selected_policies, destination_policy, destination_allowlist):
working_dir = load_env("WORKING_DIR")
def section_header(title):
print(colorText("\n --------------------------------------------------------------------", "cyan")) print(colorText("\n --------------------------------------------------------------------", "cyan"))
print(colorText(f" ------------- {title} -------------", "cyan")) print(colorText(f" ------------- {title} -------------", "cyan"))
print(colorText(" --------------------------------------------------------------------", "cyan")) print(colorText(" --------------------------------------------------------------------", "cyan"))
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:", "white")) print(colorText("\nSequentially follow these steps to prepare a policy for enforcement:", "white"))
# Step 1: Originating Policies # Step 1: Originating Policies
print(colorText("\n1. Choose which originating policy or policies to move to enforcement", "cyan")) print(colorText("\n1. Choose which policy or policies to gather execution info from", "cyan"))
if not selected_policies: if not selected_policies:
print(colorText(" [✗] No policies have been chosen", "red")) print(colorText(" [✗] No policies have been chosen", "red"))
else: else:
@@ -206,7 +204,7 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(colorText(f" [✓] {policy.name}", "green")) print(colorText(f" [✓] {policy.name}", "green"))
# Step 2: Destination Policy and Allowlist # Step 2: Destination Policy and Allowlist
print(colorText("2. Choose the destination policy and allowlist", "cyan")) print(colorText("2. Choose the destination policy and associated allowlist", "cyan"))
if destination_policy: if destination_policy:
print(colorText(f" [✓] {destination_policy[0].name} has been selected as the destination policy", "green")) print(colorText(f" [✓] {destination_policy[0].name} has been selected as the destination policy", "green"))
else: else:
@@ -218,7 +216,7 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(colorText(" [✗] No allowlist has been chosen", "red")) print(colorText(" [✗] No allowlist has been chosen", "red"))
# Step 3: Data Preparation # Step 3: Data Preparation
print(colorText("3. Pull and stage event history, combine the histories, add hash info, then categorize the hashes", "cyan")) print(colorText(f"3. Select to begin pulling execution history. The executions will be sorted and placed in {working_dir}\\data\\Needs_Review", "cyan"))
if selected_policies: if selected_policies:
policy_id = selected_policies[0].name policy_id = selected_policies[0].name
review_path = f"{working_dir}\\Needs_Review\\Review_First\\{policy_id}_approved_executions.csv" review_path = f"{working_dir}\\Needs_Review\\Review_First\\{policy_id}_approved_executions.csv"
@@ -228,15 +226,9 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
# Step 4: Manual Review # Step 4: Manual Review
print(colorText("4. Manually review the files:", "cyan")) print(colorText("4. Manually review the files:", "cyan"))
if selected_policies: print(colorText(" Remove the rows containing hashes you do not approve of", "cyan"))
policy_id = selected_policies[0].name print(colorText(f" When complete, save both csv files to {working_dir}\\data\\Approved and choose this option.", "cyan"))
print(colorText(f" 'prepare_policy\\needs_approved\\good_hashes.csv' and 'prepare_policy\\needs_approved\\{policy_id}_unknown_hashes.csv'\n", "cyan")) print(colorText(" This will start the process to generate possible filepath approvals", "cyan"))
else:
print(colorText(" 'prepare_policy\\needs_approved\\good_hashes.csv' and 'prepare_policy\\needs_approved\\<policy>_unknown_hashes.csv'\n", "cyan"))
print(colorText(" Remove the rows containing hashes you do not approve of...", "cyan"))
print(colorText(" When complete, save both csv files to the directory 'approved' and choose this option.", "cyan"))
print(colorText(" This will combine these approved hashes with the automatically approved hashes and generate a list of paths to be reviewed", "cyan"))
if selected_policies: if selected_policies:
policy_id = selected_policies[0].name policy_id = selected_policies[0].name
@@ -248,11 +240,10 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
print(colorText(" [✗] No policies selected, cannot check reviewed hashes or path list", "red")) print(colorText(" [✗] No policies selected, cannot check reviewed hashes or path list", "red"))
# Step 5: Path Review # Step 5: Path Review
print(colorText("5. Manually review the files \n 'prepare_policy\\needs_approved\\primary_Paths.csv'\n 'prepare_policy\\needs_approved\\secondary_Paths.csv'", "cyan")) print(colorText(f"5. Manually review the files in {working_dir}\\Needs_Review\\Review_Second\\", "cyan"))
print(colorText(" Remove the rows containing path exclusions you do not approve of...", "cyan")) print(colorText(" Remove the rows containing path exclusions or publishers you do not approve of.", "cyan"))
print(colorText(" When complete, save the csv file to the directory 'approved'", "cyan")) print(colorText(f" When complete, save the files to {working_dir}\\data\\Approved", "cyan"))
print(colorText(" Do the same process with the list of publishers for the same directories", "cyan")) print(colorText(" Choose this option when done to build your preflights", "cyan"))
print(colorText(" Preflight Lists will be generated", "cyan"))
if selected_policies: if selected_policies:
policy_id = selected_policies[0].name policy_id = selected_policies[0].name
@@ -267,12 +258,12 @@ def printEnforceChecklist(selected_policies, destination_policy, destination_all
# Final Steps # Final Steps
print(colorText("6. Test ------------------------------------------------------", "cyan")) print(colorText("6. Test ------------------------------------------------------", "cyan"))
print(colorText(" Print rather than apply selected data.", "cyan")) print(colorText(" Prints to console the changes that would be made, must be done to proceed. ", "cyan"))
print(colorText("7. Liftoff ------------------------------------------------------", "cyan")) print(colorText("7. Liftoff ------------------------------------------------------", "cyan"))
print(colorText(" Apply path exclusions according to allowed and approved paths", "cyan")) print(colorText(" Apply path exclusions and approved publishers to selected policy", "cyan"))
print(colorText(" Apply signed or attested hashes to Parent Allow List", "cyan")) print(colorText(" Apply approved hashes to allowlist", "cyan"))
print(colorText(" Apply approved, but unsigned hashes to the Child Allow List", "cyan"))
# Utility Options # Utility Options
print(colorText("R. Remove/Reset Generated data - will prompt to allow keeping execution history", "cyan")) print(colorText("R. Remove/Reset Generated data - will prompt to allow keeping execution history", "cyan"))