]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
suricatactl: rust version of suricatactl
authorJason Ish <jason.ish@oisf.net>
Tue, 27 Jun 2023 06:28:07 +0000 (00:28 -0600)
committerVictor Julien <victor@inliniac.net>
Thu, 27 Feb 2025 05:57:45 +0000 (06:57 +0100)
configure.ac
python/Makefile.am
rust/Cargo.lock.in
rust/Cargo.toml.in
rust/Makefile.am
rust/suricatactl/Cargo.toml.in [new file with mode: 0644]
rust/suricatactl/Makefile.am [new file with mode: 0644]
rust/suricatactl/src/filestore/mod.rs [new file with mode: 0644]
rust/suricatactl/src/filestore/prune.rs [new file with mode: 0644]
rust/suricatactl/src/main.rs [new file with mode: 0644]

index 3fe429afe12e63ccb13885b2a751280821e1e708..b5d0e00c56aa813bdb5d3e9ee6d288e20e4fbd2d 100644 (file)
@@ -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)
index 968f673376067ec976f97620865bd85bb1a4a915..f34f4e430341568f43f91b2b53918b4c5a196ea9 100644 (file)
@@ -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:
index a867f3966e0d223f5f74c7508a3af364f04f7c52..484a92f8ceb254782cbde73783a4db4345fbe942 100644 (file)
@@ -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"
index fd4613b9eac5a65a07a6ca092c61f6a66a0ee4ea..54f79244846edc578d59b6c9e2b8bf01008930e9 100644 (file)
@@ -10,12 +10,14 @@ rust-version = "1.67.1"
 members = [
     ".",
     "derive",
+    "suricatactl",
     "suricatasc",
     "sys",
 ]
 
 default-members = [
     ".",
+    "suricatactl",
     "suricatasc",
 ]
 
index 3630fdab8951e929aecd2559aee9f73f26cff6d9..f6a8a364e2e8f17e652ac548eeed3f91413f3f25 100644 (file)
@@ -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
diff --git a/rust/suricatactl/Cargo.toml.in b/rust/suricatactl/Cargo.toml.in
new file mode 100644 (file)
index 0000000..d60eab1
--- /dev/null
@@ -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"] }
diff --git a/rust/suricatactl/Makefile.am b/rust/suricatactl/Makefile.am
new file mode 100644 (file)
index 0000000..acf00ae
--- /dev/null
@@ -0,0 +1,3 @@
+EXTRA_DIST =   Cargo.toml
+
+all-local: Cargo.toml
diff --git a/rust/suricatactl/src/filestore/mod.rs b/rust/suricatactl/src/filestore/mod.rs
new file mode 100644 (file)
index 0000000..1169eed
--- /dev/null
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: Copyright 2023 Open Information Security Foundation
+// SPDX-License-Identifier: GPL-2.0-only
+
+pub(crate) mod prune;
diff --git a/rust/suricatactl/src/filestore/prune.rs b/rust/suricatactl/src/filestore/prune.rs
new file mode 100644 (file)
index 0000000..d4289d8
--- /dev/null
@@ -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);
+    }
+}
diff --git a/rust/suricatactl/src/main.rs b/rust/suricatactl/src/main.rs
new file mode 100644 (file)
index 0000000..54e6f64
--- /dev/null
@@ -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),
+        },
+    }
+}