Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload rules while profiling #990

Merged
merged 8 commits into from
Jan 10, 2024
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/pyo3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fapolicy-app = { path = "../app" }
fapolicy-daemon = { path = "../daemon" }
fapolicy-rules = { path = "../rules" }
fapolicy-trust = { path = "../trust" }
fapolicy-util = { path = "../util" }

[features]
default = []
Expand Down
26 changes: 25 additions & 1 deletion crates/pyo3/src/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

use chrono::Utc;
use fapolicy_analyzer::users::read_users;
use fapolicy_app::sys::Error::WriteRulesFail;
use fapolicy_daemon::fapolicyd::wait_until_ready;
use fapolicy_daemon::pipe;
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::{PyResult, Python};
use pyo3::{exceptions, PyResult, Python};
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
Expand All @@ -23,6 +25,7 @@ use std::sync::Arc;
use std::time::{Duration, SystemTime};
use std::{io, thread};

use crate::system::PySystem;
use fapolicy_daemon::profiler::Profiler;
use fapolicy_rules::read::load_rules_db;

Expand Down Expand Up @@ -472,9 +475,30 @@ impl PyProfiler {
}
}

/// Update the compiled.rules in place and send a signal to the fapolicyd pipe to reload
/// Cleanup of the change here is handled in the normal shutdown flow by the profiler
#[pyfunction]
fn reload_profiler_rules(system: &PySystem) -> PyResult<()> {
println!("writing rules update");

let compiled_rules_path = PathBuf::from(&system.rs.config.system.rules_file_path)
.parent()
.expect("invalid toml: rules_file_path")
.join("compiled.rules");

fapolicy_rules::write::compiled_rules(&system.rs.rules_db, &compiled_rules_path)
.map_err(WriteRulesFail)
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("{:?}", e)))?;

pipe::reload_rules()
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("Reload failed: {:?}", e)))
}

pub fn init_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PyProfiler>()?;
m.add_class::<ProcHandle>()?;
m.add_class::<ExecHandle>()?;
m.add_function(wrap_pyfunction!(reload_profiler_rules, m)?)?;

Ok(())
}
2 changes: 1 addition & 1 deletion crates/pyo3/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ pub(crate) fn to_text(db: &DB) -> String {
.1
}

fn text_for_entry(e: &Entry) -> String {
pub(crate) fn text_for_entry(e: &Entry) -> String {
match e {
Invalid { text, .. } => text.clone(),
InvalidSet { text, .. } => text.clone(),
Expand Down
21 changes: 21 additions & 0 deletions crates/pyo3/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use fapolicy_analyzer::events::db::DB as EventDB;
use fapolicy_app::app::State;
use fapolicy_app::cfg;
use fapolicy_app::sys::deploy_app_state;
use fapolicy_rules::db::Entry::Comment;
use fapolicy_trust::stat::Status::*;
use fapolicy_util::sha::sha256_digest;
// use fapolicy_util::sha::sha256_digest;

use crate::acl::{PyGroup, PyUser};
use crate::analysis::PyEventLog;
Expand Down Expand Up @@ -254,10 +257,28 @@ fn checked_system(py: Python) -> PyResult<PySystem> {
})
}

/// Generate a sha256 hash of the db text
/// The text hashed here is the same as what would be written to
/// compiled.rules by either fapolicyd or the analyzer
#[pyfunction]
pub fn rule_identity(system: &PySystem) -> PyResult<String> {
let txt = system
.rs
.rules_db
.iter()
.fold(String::new(), |acc, (_, (_, x))| match x {
Comment(_) => acc,
e => format!("{}\n{}\n", acc, crate::rules::text_for_entry(e)),
});
sha256_digest(txt.as_bytes())
.map_err(|e| exceptions::PyRuntimeError::new_err(format!("{:?}", e)))
}

pub fn init_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PySystem>()?;
m.add_function(wrap_pyfunction!(config_difference, m)?)?;
m.add_function(wrap_pyfunction!(rules_difference, m)?)?;
m.add_function(wrap_pyfunction!(checked_system, m)?)?;
m.add_function(wrap_pyfunction!(rule_identity, m)?)?;
Ok(())
}
22 changes: 12 additions & 10 deletions examples/show_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@
import argparse
import sys

red = '\033[91m'
green = '\033[92m'
yellow = '\033[93m'
blue = '\033[96m'
gray = '\033[33m'
red = "\033[91m"
green = "\033[92m"
yellow = "\033[93m"
blue = "\033[96m"
gray = "\033[33m"


def main(*argv):
parser = argparse.ArgumentParser()
parser.add_argument("--plain", action='store_true', help="Plain text rules")
parser.add_argument("--plain", action="store_true", help="Plain text rules")
args = parser.parse_args(argv)

s1 = System()
print(f"Rule Identity: {rule_identity(s1)}")

if args.plain:
print(s1.rules_text())
else:
Expand All @@ -38,14 +40,14 @@ def main(*argv):
if r.origin != origin:
origin = r.origin
print()
print(gray, end='')
print(gray, end="")
print(f"🗎 [{origin}]\033[0m")

print(green if r.is_valid else red, end='')
print(green if r.is_valid else red, end="")
print(f"{r.id} {r.text} \033[0m")
for info in r.info:
marker = f'[{info.category}]' if info.category != 'e' else ''
print(yellow if info.category == 'w' else blue, end='')
marker = f"[{info.category}]" if info.category != "e" else ""
print(yellow if info.category == "w" else blue, end="")
print(f"\t- {marker} {info.message} \033[0m")


Expand Down
3 changes: 2 additions & 1 deletion fapolicy_analyzer/ui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def __pack_main_content(self, page: UIPage):
page.dispose()
return

if self.__page:
if self.__page is not None:
Copy link
Member Author

Choose a reason for hiding this comment

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

Cause of split view when navigating away from rule view after Events was mixed in.

The type after Event mixin must not have had a bool ability. It also didnt have a string rep, perhaps that was one in the same with the bool rep.

Checking for not None here is appropriate regardless, since there is not a valid bool rep of a page.

self.__page.dispose()
self.__page = page
self.mainContent.pack_start(page.get_ref(), True, True, 0)
Expand Down Expand Up @@ -482,6 +482,7 @@ def on_rulesAdminMenu_activate(self, *args, **kwargs):
rulesPage = router(PAGE_SELECTION.RULES_ADMIN)
if kwargs.get("rule_id", None) is not None:
rulesPage.highlight_row_from_data(kwargs["rule_id"])
rulesPage.refresh_toolbar += self._refresh_toolbar
self.__pack_main_content(rulesPage)

def on_profileExecMenu_activate(self, *args):
Expand Down
34 changes: 32 additions & 2 deletions fapolicy_analyzer/ui/rules/rules_admin_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import logging
from typing import Any, Optional, Sequence, Tuple

from fapolicy_analyzer import Rule, System
from events import Events

from fapolicy_analyzer import Rule, System, reload_profiler_rules
from fapolicy_analyzer.ui.actions import (
Notification,
NotificationType,
Expand All @@ -29,13 +31,15 @@
request_rules_text,
)
from fapolicy_analyzer.ui.changeset_wrapper import Changeset, RuleChangeset
from fapolicy_analyzer.ui.reducers.profiler_reducer import ProfilerState
from fapolicy_analyzer.ui.rules.rules_list_view import RulesListView
from fapolicy_analyzer.ui.rules.rules_status_info import RulesStatusInfo
from fapolicy_analyzer.ui.rules.rules_text_view import RulesTextView
from fapolicy_analyzer.ui.store import (
dispatch,
get_notifications_feature,
get_system_feature,
get_profiling_feature,
)
from fapolicy_analyzer.ui.strings import (
APPLY_CHANGESETS_ERROR_MESSAGE,
Expand All @@ -56,14 +60,20 @@
VALIDATION_NOTE_CATEGORY = "invalid rules"


class RulesAdminPage(UIConnectedWidget, UIPage):
class RulesAdminPage(UIConnectedWidget, UIPage, Events):
def __init__(self):
features = [
{get_system_feature(): {"on_next": self.on_next_system}},
{get_notifications_feature(): {"on_next": self.on_next_notifications}},
{get_profiling_feature(): {"on_next": self.on_next_profiling_state}},
]
UIConnectedWidget.__init__(self, features=features)

self.__events__ = [
"refresh_toolbar",
]
Events.__init__(self)

actions = {
"rules": [
UIAction(
Expand All @@ -80,6 +90,13 @@ def __init__(self):
signals={"clicked": self.on_save_clicked},
sensitivity_func=self.__rules_dirty,
),
UIAction(
name="Profile",
tooltip="Apply to Profiler",
icon="media-seek-forward",
signals={"clicked": self.on_load_in_profiler_clicked},
sensitivity_func=self.__can_load_in_profiler,
),
],
}
UIPage.__init__(self, actions)
Expand All @@ -100,6 +117,7 @@ def __init__(self):
self._first_pass = True
self.__system: System = None
self.__validation_notifications: Sequence[Notification] = []
self.__profiling_active = False

self._list_view.treeView.connect(
"row-collapsed", self._list_view.on_row_collapsed
Expand Down Expand Up @@ -310,3 +328,15 @@ def on_next_notifications(self, notifications: Sequence[Notification]):
self.__validation_notifications = [
n for n in notifications if n.category == VALIDATION_NOTE_CATEGORY
]

def on_next_profiling_state(self, state: ProfilerState):
if self.__profiling_active != state.running:
print(state)
self.__profiling_active = state.running
self.refresh_toolbar()

def on_load_in_profiler_clicked(self, *args):
reload_profiler_rules(self.__system)

def __can_load_in_profiler(self):
return self.__profiling_active and not self.__rules_dirty()
1 change: 1 addition & 0 deletions news/990.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow rules to be loaded dynamically into a profiling session
Loading