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

Persist firewall rules #27

Merged
merged 12 commits into from
Oct 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
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