suricatactl: rust version of suricatactl

pull/12685/head
Jason Ish 2 years ago committed by Victor Julien
parent 7e4de3d1b9
commit 8fa347410e

@ -2537,6 +2537,7 @@ AM_CONDITIONAL([BUILD_SHARED_LIBRARY], [test "x$enable_shared" = "xyes"] && [tes
AC_CONFIG_FILES(Makefile src/Makefile rust/Makefile rust/Cargo.lock rust/Cargo.toml rust/derive/Cargo.toml rust/.cargo/config.toml)
AC_CONFIG_FILES(rust/sys/Makefile rust/sys/Cargo.toml)
AC_CONFIG_FILES(rust/suricatactl/Makefile rust/suricatactl/Cargo.toml)
AC_CONFIG_FILES(rust/suricatasc/Makefile rust/suricatasc/Cargo.toml)
AC_CONFIG_FILES(qa/Makefile qa/coccinelle/Makefile)
AC_CONFIG_FILES(rules/Makefile doc/Makefile doc/userguide/Makefile)

@ -11,8 +11,6 @@ LIBS = \
suricata/sc/suricatasc.py \
suricatasc/__init__.py
BINS = suricatactl
EXTRA_DIST = $(LIBS) bin suricata/config/defaults.py
if HAVE_PYTHON
@ -22,20 +20,13 @@ install-exec-local:
install -d -m 0755 "$(DESTDIR)$(prefix)/lib/suricata/python/suricata/ctl"
install -d -m 0755 "$(DESTDIR)$(prefix)/lib/suricata/python/suricata/sc"
install -d -m 0755 "$(DESTDIR)$(prefix)/lib/suricata/python/suricatasc"
install -d -m 0755 "$(DESTDIR)$(prefix)/bin"
for src in $(LIBS); do \
install -m 0644 $(srcdir)/$$src "$(DESTDIR)$(prefix)/lib/suricata/python/$$src"; \
done
install suricata/config/defaults.py \
"$(DESTDIR)$(prefix)/lib/suricata/python/suricata/config/defaults.py"
for bin in $(BINS); do \
cat "$(srcdir)/bin/$$bin" | \
sed -e "1 s,.*,#"'!'" ${HAVE_PYTHON}," > "${DESTDIR}$(bindir)/$$bin"; \
chmod 0755 "$(DESTDIR)$(bindir)/$$bin"; \
done
uninstall-local:
rm -f $(DESTDIR)$(bindir)/suricatactl
rm -rf $(DESTDIR)$(prefix)/lib/suricata/python
clean-local:

@ -731,6 +731,16 @@ dependencies = [
"nom-derive",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num"
version = "0.2.1"
@ -885,6 +895,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "phf"
version = "0.10.1"
@ -923,6 +939,12 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "polyval"
version = "0.5.3"
@ -1217,6 +1239,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "siphasher"
version = "0.3.11"
@ -1325,6 +1356,16 @@ dependencies = [
name = "suricata-sys"
version = "8.0.0-dev"
[[package]]
name = "suricatactl"
version = "8.0.0-dev"
dependencies = [
"clap",
"regex",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "suricatasc"
version = "8.0.0-dev"
@ -1455,6 +1496,16 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "time"
version = "0.3.37"
@ -1509,6 +1560,63 @@ dependencies = [
"serde",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "typenum"
version = "1.18.0"
@ -1561,6 +1669,12 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version_check"
version = "0.9.5"

@ -10,12 +10,14 @@ rust-version = "1.67.1"
members = [
".",
"derive",
"suricatactl",
"suricatasc",
"sys",
]
default-members = [
".",
"suricatactl",
"suricatasc",
]

@ -1,5 +1,6 @@
SUBDIRS = sys \
suricatasc
suricatasc \
suricatactl
EXTRA_DIST = src derive \
.cargo/config.toml.in \
@ -10,7 +11,8 @@ EXTRA_DIST = src derive \
derive/Cargo.toml \
sys \
sys/Cargo.toml \
suricatasc
suricatasc \
suricatactl
if !DEBUG
RELEASE = --release
@ -73,6 +75,7 @@ all-local: Cargo.toml
install-exec-local:
install -d -m 0755 "$(DESTDIR)$(bindir)"
install -m 0755 $(RUST_SURICATA_LIBDIR)/suricatasc "$(DESTDIR)$(bindir)/suricatasc"
install -m 0755 $(RUST_SURICATA_LIBDIR)/suricatactl "$(DESTDIR)$(bindir)/suricatactl"
install-library:
$(MKDIR_P) "$(DESTDIR)$(libdir)"
@ -81,6 +84,7 @@ install-library:
uninstall-local:
rm -f "$(DESTDIR)$(libdir)/$(RUST_SURICATA_LIBNAME)"
rm -f "$(DESTDIR)$(bindir)/suricatasc"
rm -f "$(DESTDIR)$(bindir)/suricatactl"
clean-local:
rm -rf target gen

@ -0,0 +1,16 @@
[package]
name = "suricatactl"
version = "@PACKAGE_VERSION@"
edition = "2021"
license = "GPL-2.0-only"
[[bin]]
name = "suricatactl"
[dependencies]
regex = "~1.5.5"
tracing = "0.1"
tracing-subscriber = "0.3"
# 4.0 is the newest version that builds with Rust 1.67.1.
clap = { version = "=4.2.0", default-features = false, features = ["std", "derive", "help", "usage"] }

@ -0,0 +1,3 @@
EXTRA_DIST = Cargo.toml
all-local: Cargo.toml

@ -0,0 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation
// SPDX-License-Identifier: GPL-2.0-only
pub(crate) mod prune;

@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation
// SPDX-License-Identifier: GPL-2.0-only
use std::path::{Path, PathBuf};
use tracing::{debug, error, info};
use crate::FilestorePruneArgs;
pub(crate) fn prune(args: FilestorePruneArgs) -> Result<(), Box<dyn std::error::Error>> {
let age = parse_age(&args.age)?;
info!("Pruning files older than {} seconds", age);
let mut total_bytes = 0;
let mut file_count = 0;
let mut stack = vec![PathBuf::from(&args.directory)];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
stack.push(path);
} else {
match FileInfo::from_path(&path) {
Ok(info) => {
if info.age > age {
debug!("Deleting {:?}", path);
file_count += 1;
total_bytes += info.size;
if !args.dry_run {
if let Err(err) = std::fs::remove_file(&path) {
error!("Failed to delete {}: {}", path.display(), err);
}
}
}
}
Err(err) => {
error!(
"Failed to get last modified time of file {}: {}",
path.display(),
err
);
}
}
}
}
}
info!("Removed {} files; {} bytes", file_count, total_bytes);
Ok(())
}
struct FileInfo {
age: u64,
size: u64,
}
impl FileInfo {
fn from_path(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let metadata = path.metadata()?;
let age = metadata.modified()?.elapsed()?.as_secs();
Ok(Self {
age,
size: metadata.len(),
})
}
}
/// Given input like "1s", "1m", "1h" or "1d" return the number of
/// seconds
fn parse_age(age: &str) -> Result<u64, String> {
// Use a regex to separate the value from the unit.
let re = regex::Regex::new(r"^(\d+)([smhd])$").unwrap();
let caps = re.captures(age).ok_or_else(|| {
format!(
"Invalid age: {}. Must be a number followed by one of s, m, h, d",
age
)
})?;
let value = caps
.get(1)
.unwrap()
.as_str()
.parse::<u64>()
.map_err(|e| format!("Invalid age: {}: {}", age, e))?;
let unit = caps.get(2).unwrap().as_str();
match unit {
"s" => Ok(value),
"m" => Ok(value * 60),
"h" => Ok(value * 60 * 60),
"d" => Ok(value * 60 * 60 * 24),
_ => unreachable!(),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_age() {
assert!(parse_age("1").is_err());
assert!(parse_age("s").is_err());
assert!(parse_age("1a").is_err());
// Valid tests
assert_eq!(parse_age("1s").unwrap(), 1);
assert_eq!(parse_age("3s").unwrap(), 3);
assert_eq!(parse_age("1m").unwrap(), 60);
assert_eq!(parse_age("3m").unwrap(), 180);
assert_eq!(parse_age("3h").unwrap(), 10800);
assert_eq!(parse_age("1d").unwrap(), 86400);
assert_eq!(parse_age("3d").unwrap(), 86400 * 3);
}
}

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation
// SPDX-License-Identifier: GPL-2.0-only
use clap::Parser;
use clap::Subcommand;
use tracing::Level;
mod filestore;
#[derive(Parser, Debug)]
struct Cli {
#[arg(long, short, global = true, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(
long,
short,
global = true,
help = "Quiet mode, only warnings and errors will be logged"
)]
quiet: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Filestore management commands
Filestore(FilestoreCommand),
}
#[derive(Parser, Debug)]
struct FilestoreCommand {
#[command(subcommand)]
command: FilestoreCommands,
}
#[derive(Subcommand, Debug)]
enum FilestoreCommands {
/// Remove files by age
Prune(FilestorePruneArgs),
}
#[derive(Parser, Debug)]
struct FilestorePruneArgs {
#[arg(long, short = 'n', help = "only print what would happen")]
dry_run: bool,
#[arg(long, short, help = "file-store directory")]
directory: String,
#[arg(long, help = "prune files older than age, units: s, m, h, d")]
age: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let log_level = if cli.quiet {
Level::WARN
} else if cli.verbose > 0 {
Level::DEBUG
} else {
Level::INFO
};
tracing_subscriber::fmt().with_max_level(log_level).init();
match cli.command {
Commands::Filestore(filestore) => match filestore.command {
FilestoreCommands::Prune(args) => crate::filestore::prune::prune(args),
},
}
}
Loading…
Cancel
Save