Skip to content

Commit

Permalink
Save/Load firewall rules (#27)
Browse files Browse the repository at this point in the history
Co-authored-by: Badr <[email protected]>
  • Loading branch information
adgaultier and pythops authored Oct 10, 2024
1 parent 8f55c6a commit b7f42d9
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 34 deletions.
35 changes: 35 additions & 0 deletions 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 Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ show interface:
# Run oryx debug
run-debug:
echo "" > log-file
RUST_LOG=info cargo xtask run 2> log-file
RUST_LOG=info RUST_BACKTRACE=1 cargo xtask run 2> log-file

run:
cargo xtask run
Expand Down
2 changes: 2 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ sudo oryx

`e`: Edit a firewall rule.

`s`: Save firewall rules to `~/oryx/firewall.json`

`Enter`: Create or Save a firewall rule.

## ⚖️ License
Expand Down
1 change: 1 addition & 0 deletions Release.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Added

- Firewall
- Save and Load firewall rules.

## v0.3 - 2024-09-25

Expand Down
4 changes: 3 additions & 1 deletion oryx-tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ kanal = "0.1.0-pre8"
mimalloc = "0.1"
clap = { version = "4", features = ["derive", "cargo"] }
network-types = "0.0.7"
uuid = { version = "1", default-features = false, features = ["v4"] }
uuid = { version = "1", default-features = false, features = ["v4", "serde"] }
log = "0.4"
env_logger = "0.11"
serde_json = "1"
serde = { version = "1", features = ["derive"] }

[[bin]]
name = "oryx"
Expand Down
5 changes: 5 additions & 0 deletions oryx-tui/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use log::error;
use oryx_common::RawPacket;
use ratatui::{
layout::{Constraint, Direction, Layout},
Expand Down Expand Up @@ -134,6 +135,10 @@ impl App {
}

pub fn quit(&mut self) {
if let Err(e) = self.section.firewall.save_rules() {
error!("{}", e)
}

self.running = false;
}
}
3 changes: 2 additions & 1 deletion oryx-tui/src/filter/direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Row, Table, TableState},
Frame,
};
use serde::{Deserialize, Serialize};

#[derive(Debug)]
pub struct TrafficDirectionFilter {
Expand All @@ -22,7 +23,7 @@ pub struct TrafficDirectionFilter {
pub terminate_egress: Arc<AtomicBool>,
}

#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum TrafficDirection {
Ingress,
Egress,
Expand Down
25 changes: 0 additions & 25 deletions oryx-tui/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ use std::{thread, time::Duration};
use crate::{
app::{ActivePopup, App, AppResult},
event::Event,
export::export,
filter::FocusedBlock,
notification::{Notification, NotificationLevel},
section::FocusedSection,
};
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
Expand Down Expand Up @@ -190,29 +188,6 @@ pub fn handle_key_events(
}
}

KeyCode::Char('s') => {
let app_packets = app.packets.lock().unwrap();
if app_packets.is_empty() {
Notification::send(
"There is no packets".to_string(),
NotificationLevel::Info,
event_sender,
)?;
} else {
match export(&app_packets) {
Ok(_) => {
Notification::send(
"Packets exported to ~/oryx/capture file".to_string(),
NotificationLevel::Info,
event_sender,
)?;
}
Err(e) => {
Notification::send(e.to_string(), NotificationLevel::Error, event_sender)?;
}
}
}
}
_ => {
app.section.handle_keys(key_event, event_sender.clone())?;
}
Expand Down
4 changes: 4 additions & 0 deletions oryx-tui/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ impl Help {
(Cell::from("## Firewall").bold().yellow(), ""),
(Cell::from("n").bold(), "Add new firewall rule"),
(Cell::from("e").bold(), "Edit a firewall rule"),
(
Cell::from("s").bold(),
"Save firewall rules to ~/oryx/firewall.json ",
),
(Cell::from("Space").bold(), "Toggle firewall rule status"),
(Cell::from("Enter").bold(), "Create or Save a firewall rule"),
],
Expand Down
10 changes: 9 additions & 1 deletion oryx-tui/src/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ impl Section {
Span::from("i").bold(),
Span::from(" Infos").bold(),
Span::from(" | ").bold(),
Span::from("s").bold(),
Span::from(" Save").bold(),
Span::from(" | ").bold(),
Span::from("f").bold(),
Span::from(" Filters").bold(),
Span::from(" | ").bold(),
Expand All @@ -176,6 +179,9 @@ impl Section {
Span::from("e").bold(),
Span::from(" Edit").bold(),
Span::from(" | ").bold(),
Span::from("s").bold(),
Span::from(" Save").bold(),
Span::from(" | ").bold(),
Span::from("󱁐 ").bold(),
Span::from(" Toggle").bold(),
Span::from(" | ").bold(),
Expand Down Expand Up @@ -275,7 +281,9 @@ impl Section {
},

_ => match self.focused_section {
FocusedSection::Inspection => self.inspection.handle_keys(key_event),
FocusedSection::Inspection => self
.inspection
.handle_keys(key_event, notification_sender.clone())?,
FocusedSection::Firewall => self
.firewall
.handle_keys(key_event, notification_sender.clone())?,
Expand Down
80 changes: 76 additions & 4 deletions oryx-tui/src/section/firewall.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::fmt::Display;
use crossterm::event::{Event, KeyCode, KeyEvent};
use log::{error, info};
use oryx_common::MAX_FIREWALL_RULES;
use ratatui::{
layout::{Constraint, Direction, Flex, Layout, Margin, Rect},
Expand All @@ -8,7 +9,9 @@ use ratatui::{
widgets::{Block, Borders, Cell, Clear, HighlightSpacing, Padding, Row, Table, TableState},
Frame,
};
use std::{net::IpAddr, num::ParseIntError, str::FromStr};
use serde::{Deserialize, Serialize};
use serde_json;
use std::{fs, net::IpAddr, num::ParseIntError, os::unix::fs::chown, str::FromStr};
use tui_input::{backend::crossterm::EventHandler, Input};
use uuid;

Expand All @@ -20,7 +23,7 @@ pub enum FirewallSignal {
Kill,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirewallRule {
id: uuid::Uuid,
name: String,
Expand All @@ -30,7 +33,7 @@ pub struct FirewallRule {
direction: TrafficDirection,
}

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BlockedPort {
Single(u16),
All,
Expand Down Expand Up @@ -302,8 +305,16 @@ impl Firewall {
ingress_sender: kanal::Sender<FirewallSignal>,
egress_sender: kanal::Sender<FirewallSignal>,
) -> Self {
let rules_list: Vec<FirewallRule> = match Self::load_saved_rules() {
Ok(saved_rules) => saved_rules,

Err(err) => {
error!("{}", err.to_string());
Vec::new()
}
};
Self {
rules: Vec::new(),
rules: rules_list,
state: TableState::default(),
user_input: None,
ingress_sender,
Expand All @@ -315,6 +326,49 @@ impl Firewall {
self.user_input = Some(UserInput::new());
}

pub fn save_rules(&self) -> AppResult<()> {
info!("Saving Firewall Rules");

let json = serde_json::to_string(&self.rules)?;

let user_uid = unsafe { libc::geteuid() };

let oryx_export_dir = dirs::home_dir().unwrap().join("oryx");

if !oryx_export_dir.exists() {
fs::create_dir(&oryx_export_dir)?;
chown(&oryx_export_dir, Some(user_uid), Some(user_uid))?;
}

let oryx_export_file = oryx_export_dir.join("firewall.json");
fs::write(oryx_export_file, json)?;
info!("Firewall Rules saved");

Ok(())
}

fn load_saved_rules() -> AppResult<Vec<FirewallRule>> {
let oryx_export_file = dirs::home_dir().unwrap().join("oryx").join("firewall.json");
if oryx_export_file.exists() {
info!("Loading Firewall Rules");

let json_string = fs::read_to_string(oryx_export_file)?;

let mut parsed_rules: Vec<FirewallRule> = serde_json::from_str(&json_string)?;

// as we don't know if ingress/egress programs are loaded we have to disable all rules
parsed_rules
.iter_mut()
.for_each(|rule| rule.enabled = false);

info!("Firewall Rules loaded");
Ok(parsed_rules)
} else {
info!("Firewall Rules file not found");
Ok(Vec::new())
}
}

fn validate_duplicate_rules(rules: &[FirewallRule], user_input: &UserInput) -> AppResult<()> {
if let Some(exiting_rule_with_same_ip) = rules.iter().find(|rule| {
rule.ip == IpAddr::from_str(user_input.ip.field.value()).unwrap()
Expand Down Expand Up @@ -500,6 +554,24 @@ impl Firewall {
self.add_rule();
}

KeyCode::Char('s') => match self.save_rules() {
Ok(_) => {
Notification::send(
"Firewall rules saved to ~/oryx/firewall.json file",
crate::notification::NotificationLevel::Info,
sender.clone(),
)?;
}
Err(e) => {
Notification::send(
"Error while saving firewall rules.",
crate::notification::NotificationLevel::Error,
sender.clone(),
)?;
error!("Error while saving firewall rules. {}", e);
}
},

KeyCode::Char('e') => {
if let Some(index) = self.state.selected() {
let rule = self.rules[index].clone();
Expand Down
Loading

0 comments on commit b7f42d9

Please sign in to comment.