From 7496618489dfbc9e51b4b671cefecbcd5778c4bf Mon Sep 17 00:00:00 2001 From: LeChatP Date: Wed, 4 Sep 2024 13:34:08 +0200 Subject: [PATCH] dependency management, now tesing it on containers --- .cargo/config.toml | 3 +- .github/workflows/build.yml | 14 +---- Cargo.toml | 1 + dependencies.rs | 8 --- dependencies.sh | 70 ------------------------ rar-common/Cargo.toml | 5 +- rar-common/src/database/finder.rs | 23 ++++++-- rar-common/src/database/options.rs | 68 +++++++++++++++-------- xtask/src/install/build.rs | 14 +++-- xtask/src/install/configure.rs | 3 +- xtask/src/install/dependencies.rs | 88 ++++++++++++++++++++++++++++++ xtask/src/install/mod.rs | 39 ++++++++++++- xtask/src/main.rs | 2 + 13 files changed, 210 insertions(+), 128 deletions(-) delete mode 100644 dependencies.rs delete mode 100755 dependencies.sh create mode 100644 xtask/src/install/dependencies.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 05c041aa..4f2153a2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,2 @@ [alias] -xtask = "run --package xtask --" -dependencies = "run --package dependencies --" \ No newline at end of file +xtask = "run --package xtask --release --" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce17b8d7..4be5f3fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,14 +12,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Update version - run: sudo apt-get update - - name: Install Dependencies - run: | - . ./dependencies.sh -yd - sudo ./configure.sh -yd - sudo sed -i 's/"immutable": true/"immutable": false/g' /etc/security/rootasrole.json - echo "/home/runner/.cargo/bin" >> $GITHUB_PATH - name: Configure PAM run: | sudo bash -c 'echo "#%PAM-1.0 @@ -32,16 +24,14 @@ jobs: session [success=1 default=ignore] pam_permit.so session requisite pam_permit.so session required pam_permit.so" | tee /etc/pam.d/sr' + - name: Install RootAsRole + run: sudo cargo xtask install --debug - name: Add read access on config on rootasrole... Because Github Actions... run: sudo chmod a+r /etc/security/rootasrole.json - name: print config run: cat /etc/security/rootasrole.json - name: getenv run: env - - name: Install Project - env: - PROFILE: debug - run: make -e install - name: Run Sr env: RUST_LOG: debug diff --git a/Cargo.toml b/Cargo.toml index dcb01308..27f33997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ opt-level = "s" [[bin]] name = "sr" path = "src/sr/main.rs" +required-features = ["rar-common/pcre2"] [[bin]] diff --git a/dependencies.rs b/dependencies.rs deleted file mode 100644 index bbfaad8b..00000000 --- a/dependencies.rs +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env rust-script -//! ``` -//! [dependencies] -//! clap = { version = "4.5.16", features = ["derive"] } -//! ``` -fn main() { - println!("Hello, world!"); -} \ No newline at end of file diff --git a/dependencies.sh b/dependencies.sh deleted file mode 100755 index 4e7ac2a2..00000000 --- a/dependencies.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -DOCKER=0 - -while getopts "yd" opt; do - case ${opt} in - y ) YES="-y" - ;; - d ) DOCKER=1 - ;; - esac -done - -echo "Capabilities & PAM packages installation" - -if [ ! `id -u` -eq 0 ]; then -PRIV_EXE="${PRIV_EXE:-sudo}" -else -PRIV_EXE="" -fi - -if command -v apt-get &>/dev/null; then - $PRIV_EXE apt-get update - $PRIV_EXE apt-get install "${YES}" "linux-headers-$(uname -r)" || $PRIV_EXE apt-get install "${YES}" linux-headers-generic - $PRIV_EXE apt-get install "${YES}" linux-tools-common linux-tools-generic "linux-tools-$(uname -r)" - $PRIV_EXE apt-get install "${YES}" bpftool man pkg-config openssl libssl-dev curl gcc llvm clang libcap2 libcap2-bin libcap-dev libcap-ng-dev libelf-dev libpam0g-dev libclang-dev make - if [ -n "${DEBUG}" ]; then - $PRIV_EXE apt-get install "${YES}" gdb - fi; - if [ -n "${COV}" ]; then - $PRIV_EXE apt-get install "${YES}" gcovr - fi; -elif command -v yum &>/dev/null; then - $PRIV_EXE yum install ${YES} man pkgconfig openssl-devel curl gcc llvm clang clang-devel libcap libcap-ng elfutils make kernel-headers pam-devel bpftool - if [ -n "${DEBUG}" ]; then - $PRIV_EXE yum install "${YES}" gdb - fi; - if [ -n "${COV}" ]; then - $PRIV_EXE yum install "${YES}" gcovr - fi; -elif command -v pacman &>/dev/null; then - if [ -n "${YES}" ]; then - NOCONFIRM="--noconfirm" - fi - $PRIV_EXE pacman -S "${NOCONFIRM}" man-db pkgconf openssl curl gcc llvm clang libcap libcap-ng libelf linux-headers linux-api-headers make bpf - if [ -n "${DEBUG}" ]; then - $PRIV_EXE pacman -S "${YES}" gdb - fi; - if [ -n "${COV}" ]; then - $PRIV_EXE pacman -S "${YES}" gcovr - fi; -else - echo "Unable to find a supported package manager, exiting..." - exit 2 -fi - -echo "Install Rust Cargo compiler" -if [ $(command -v cargo &>/dev/null; echo $?) -eq 0 ]; then - echo "Cargo is installed" -else - curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly ${YES} # nightly because aya use -Z feature -fi - -. "$HOME/.cargo/env" - -cargo install --force bpf-linker bindgen-cli -cargo install --git https://github.com/aya-rs/aya -- aya-tool - -echo "dependencies installed. Ready to compile & install." -echo "To install, run: cargo xtask install" diff --git a/rar-common/Cargo.toml b/rar-common/Cargo.toml index 62a653f0..73055067 100644 --- a/rar-common/Cargo.toml +++ b/rar-common/Cargo.toml @@ -43,4 +43,7 @@ tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } pest-test-gen = "0.1.7" pest-test = "0.1.6" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" + +[features] +pcre2 = [] \ No newline at end of file diff --git a/rar-common/src/database/finder.rs b/rar-common/src/database/finder.rs index db4eb3e3..3bbfeeec 100644 --- a/rar-common/src/database/finder.rs +++ b/rar-common/src/database/finder.rs @@ -13,6 +13,7 @@ use nix::{ libc::dev_t, unistd::{Group, Pid, User}, }; +#[cfg(feature = "pcre2")] use pcre2::bytes::RegexBuilder; use strum::EnumIs; use tracing::{debug, warn}; @@ -397,14 +398,26 @@ fn match_args(input_args: &[String], role_args: &[String]) -> Result Result> { + let regex = RegexBuilder::new().build(&role_args)?; + if regex.is_match(commandline.as_bytes())? { + Ok(CmdMin::RegexArgs) + } else { + Err(Box::new(MatchError::NoMatch)) + } +} + +#[cfg(not(feature = "pcre2"))] +fn evaluate_regex_cmd(_role_args: String, _commandline: String) -> Result> { Err(Box::new(MatchError::NoMatch)) } diff --git a/rar-common/src/database/options.rs b/rar-common/src/database/options.rs index 8c03730e..1efdcb00 100644 --- a/rar-common/src/database/options.rs +++ b/rar-common/src/database/options.rs @@ -6,6 +6,7 @@ use chrono::Duration; use libc::PATH_MAX; use linked_hash_set::LinkedHashSet; +#[cfg(feature = "pcre2")] use pcre2::bytes::Regex; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Map, Value}; @@ -325,32 +326,49 @@ impl Default for SPathOptions { } } +fn is_valid_env_name(s: &str) -> bool { + let mut chars = s.chars(); + + // Check if the first character is a letter or underscore + if let Some(first_char) = chars.next() { + if !(first_char.is_ascii_alphabetic() || first_char == '_') { + return false; + } + } else { + return false; // Empty string + } + + // Check if the remaining characters are alphanumeric or underscores + chars.all(|c| c.is_ascii_alphanumeric() || c == '_') +} + +#[cfg(feature = "pcre2")] +fn is_regex(s: &str) -> bool { + Regex::new(s).is_ok() +} + +#[cfg(not(feature = "pcre2"))] +fn is_regex(_s: &str) -> bool { + true // Always return true if regex feature is disabled +} + impl EnvKey { pub fn new(s: String) -> Result { //debug!("Creating env key: {}", s); - if Regex::new("^[a-zA-Z_]+[a-zA-Z0-9_]*$") // check if it is a valid env name - .unwrap() - .is_match(s.as_bytes()) - .is_ok_and(|m| m) + if is_valid_env_name(&s) { Ok(EnvKey { env_type: EnvKeyType::Normal, value: s, }) - } else if Regex::new("^[a-zA-Z_*?]+.*$") // check if it is a valid env name - .unwrap() - .is_match(s.as_bytes()) - .is_ok_and(|m| m) + } else if is_regex(&s) { Ok(EnvKey { env_type: EnvKeyType::Wildcarded, value: s, }) } else { - Err(format!( - "Invalid env key {}, must start with letter or underscore following by a regex", - s - )) + Err(format!("env key {}, must be a valid env, or a valid regex", s)) } } } @@ -428,13 +446,7 @@ impl EnvSet for HashMap { EnvKeyType::Normal => self.contains_key(&wildcarded.value), EnvKeyType::Wildcarded => { self.keys().any(|s| { - Regex::new(&format!( - "^{}$", - wildcarded.value.replace('*', ".*").replace('?', ".") - )) // convert to regex - .unwrap() - .is_match(s.as_bytes()) - .is_ok_and(|m| m) + check_wildcarded(wildcarded, s) }) } } @@ -447,16 +459,26 @@ impl EnvSet for LinkedHashSet { EnvKeyType::Normal => self.contains(wildcarded), EnvKeyType::Wildcarded => { self.iter().any(|s| { - Regex::new(&format!("^{}$", wildcarded.value)) // convert to regex - .unwrap() - .is_match(s.value.as_bytes()) - .is_ok_and(|m| m) + check_wildcarded(wildcarded, &s.value) }) } } } } +#[cfg(feature = "pcre2")] +fn check_wildcarded(wildcarded: &EnvKey, s: &String) -> bool { + Regex::new(&format!("^{}$", wildcarded.value)) // convert to regex + .unwrap() + .is_match(s.as_bytes()) + .is_ok_and(|m| m) +} + +#[cfg(not(feature = "pcre2"))] +fn check_wildcarded(_wildcarded: &EnvKey, _s: &String) -> bool { + true +} + fn tz_is_safe(tzval: &str) -> bool { // tzcode treats a value beginning with a ':' as a path. let tzval = if tzval.starts_with(':') { diff --git a/xtask/src/install/build.rs b/xtask/src/install/build.rs index 86132960..048938c3 100644 --- a/xtask/src/install/build.rs +++ b/xtask/src/install/build.rs @@ -3,20 +3,26 @@ use std::process::Command; use super::BuildOptions; -pub fn build(options: &BuildOptions) -> Result<(), anyhow::Error> { +fn build_binary(name: &str, options: &BuildOptions, additionnal_args: Vec<&str>) { let toolchain = format!("+{}", options.toolchain.to_string()); - let mut args = vec![toolchain.as_str(), "build", "--bin", "sr", "--bin", "chsr"]; + let mut args = vec![&toolchain, "build", "--bin", name]; if options.profile.is_release() { args.push("--release"); } if options.clean_before { args.push("--clean"); } - println!("Building sr and chsr with {:?}", &args); + args.extend(additionnal_args); Command::new("cargo") .args(args) .status() - .expect("failed to install rootasrole"); + .expect(format!("failed to build {} binary", name).as_str()); +} + +pub fn build(options: &BuildOptions) -> Result<(), anyhow::Error> { + + build_binary("sr", options, vec!["--features", "rar-common/pcre2"]); + build_binary("chsr", options, vec![]); Ok(()) } \ No newline at end of file diff --git a/xtask/src/install/configure.rs b/xtask/src/install/configure.rs index dbdd872f..e032a573 100644 --- a/xtask/src/install/configure.rs +++ b/xtask/src/install/configure.rs @@ -161,7 +161,8 @@ fn deploy_config_file() -> Result { match status { ConfigState::Unchanged => { - println!("Config file newly created or has not been modified checking if filesystem allows immutability"); + println!("Config file newly created or has not been modified."); + println!("Checking if filesystem allows immutability."); let res = check_filesystem().context("Failed to configure the filesystem parameter"); if res.is_err() { // If the filesystem check fails, ignore the error if running in a container as it may not have immutable access diff --git a/xtask/src/install/dependencies.rs b/xtask/src/install/dependencies.rs new file mode 100644 index 00000000..1b6ca1cc --- /dev/null +++ b/xtask/src/install/dependencies.rs @@ -0,0 +1,88 @@ +use anyhow::Context; + +use crate::install::OsTarget; + +use super::InstallDependenciesOptions; + +fn update_package_manager() -> Result<(), anyhow::Error> { + let os = OsTarget::detect()?; + + match os { + OsTarget::Debian | OsTarget::Ubuntu => { + let _ = std::process::Command::new("apt-get") + .arg("update") + .status()?; + }, + OsTarget::RedHat | OsTarget::Fedora | OsTarget::CentOS => { + let _ = std::process::Command::new("yum") + .arg("update") + .arg("-y") + .status()?; + }, + OsTarget::ArchLinux => {}, + } + + Ok(()) +} + +pub fn install(opts: InstallDependenciesOptions) -> Result<(), anyhow::Error> { + update_package_manager()?; + // dependencies are : libpam and libpcre2 + println!("Installing dependencies: libpam.so and libpcre2.so for running the application"); + + let os = if let Some(os) = opts.os { + os + } else { + OsTarget::detect() + .and_then(|t| { + println!("Detected OS is : {}", t); + Ok(t) + }) + .context("Failed to detect the OS")? + }; + + match os { + OsTarget::Debian | OsTarget::Ubuntu => { + let _ = std::process::Command::new("apt-get") + .arg("install") + .arg("-y") + .arg("libpam0g") + .arg("libpcre2-8-0") + .status()?; + }, + OsTarget::RedHat => { + let _ = std::process::Command::new("yum") + .arg("install") + .arg("-y") + .arg("pcre2") + .status()?; + }, + OsTarget::CentOS => { + let _ = std::process::Command::new("yum") + .arg("install") + .arg("-y") + .arg("pam") + .arg("pcre2") + .status()?; + }, + OsTarget::Fedora => { + let _ = std::process::Command::new("dnf") + .arg("install") + .arg("-y") + .arg("pam") + .arg("pcre2") + .status()?; + } + OsTarget::ArchLinux => { + let _ = std::process::Command::new("pacman") + .arg("-Sy") + .arg("--noconfirm") + .arg("pam") + .arg("pcre2") + .status()?; + }, + } + + println!("Dependencies installed successfully"); + Ok(()) +} \ No newline at end of file diff --git a/xtask/src/install/mod.rs b/xtask/src/install/mod.rs index 31894e18..e7176b5a 100644 --- a/xtask/src/install/mod.rs +++ b/xtask/src/install/mod.rs @@ -3,6 +3,7 @@ mod build; mod uninstall; mod configure; mod util; +mod dependencies; use std::collections::VecDeque; use std::str::FromStr; @@ -34,15 +35,37 @@ pub struct InstallOptions { #[clap(flatten)] pub build : BuildOptions, - /// The OS target for PAM configuration (default tries to autodetect it) + /// The OS target for PAM configuration and dependencies installation (if -i is set) + /// By default, it tries to autodetect it #[clap(long, short)] pub os: Option, + /// Do not build the binaries + #[clap(long, short = 'n')] + pub no_build: bool, + + /// Install dependencies before building + #[clap(long, short = 'i')] + pub install_dependencies: bool, + /// Clean the target directory after installing #[clap(long, short = 'a')] pub clean_after: bool, } +#[derive(Debug, Parser)] +pub struct InstallDependenciesOptions { + + /// The OS target for PAM configuration and dependencies installation (if -i is set) + /// By default, it tries to autodetect it + #[clap(long, short)] + pub os: Option, + + /// Install dependencies before building + #[clap(long, short = 'i')] + pub install_dependencies: bool, +} + #[derive(Debug, Parser)] pub struct UninstallOptions { /// Delete all configuration files @@ -247,8 +270,20 @@ pub(crate) fn configure(os: Option) -> Result<(), anyhow::Error> { configure::configure(os) } +pub(crate) fn dependencies(opts: InstallDependenciesOptions) -> Result<(), anyhow::Error> { + dependencies::install(opts) +} + pub(crate) fn install(opts: &InstallOptions) -> Result<(), anyhow::Error> { - build(&opts.build)?; + if opts.install_dependencies { + dependencies(InstallDependenciesOptions { + os: opts.os.clone(), + install_dependencies: true, + })?; + } + if ! opts.no_build { + build(&opts.build)?; + } if opts.build.ebpf.is_some() { let mut opts = opts.clone(); opts.build.toolchain.channel = Channel::Nightly; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e81697ca..1427ff56 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -14,6 +14,7 @@ pub struct Options { #[derive(Debug, Parser)] enum Command { + Dependencies(install::InstallDependenciesOptions), BuildEbpf(install::BuildOptions), RunEbpf(ebpf::run::RunOptions), Build(install::BuildOptions), @@ -31,6 +32,7 @@ fn main() { use Command::*; let ret = match opts.command { + Dependencies(opts) => install::dependencies(opts), BuildEbpf(opts) => ebpf::build_all(&opts), RunEbpf(opts) => ebpf::run(&opts), Build(opts) => install::build(&opts),