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

agent: Add basic integration test #42

Merged
merged 2 commits into from
Nov 13, 2023
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
9 changes: 8 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
runs-on: ubuntu-latest
container:
image: registry.fedoraproject.org/fedora:38
options: --privileged
steps:
- uses: actions/checkout@v3
- name: Install dependencies
Expand All @@ -32,6 +33,12 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: |
cargo test --verbose --workspace --exclude crypto-auditing-agent
- name: Run integration tests
run: |
cd agent
cargo test --verbose
cd -
- name: Run clippy
run: cargo clippy
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions agent/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
runner = "sudo -E"
6 changes: 6 additions & 0 deletions agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ tokio-uring = { version = "0.4", optional = true }
toml = "0.6"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
probe = "0.3"

[build-dependencies]
libbpf-cargo = { version = "0.20", features = ["novendor"] }

[dev-dependencies]
tempfile = "3"
plain = "0.2"
agenttest = { path = "tests/agenttest" }
7 changes: 7 additions & 0 deletions agent/fixtures/agent.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# library = ["/usr/lib64/libgnutls.so.30", "/usr/lib64/libssl.so.3"]
# log_file = "/var/log/crypto-auditing/audit.cborseq"
# user = "crypto-auditing:crypto-auditing"
# coalesce_window = 100
# max_events = 1000000

coalesce_window = 0
6 changes: 6 additions & 0 deletions agent/src/log_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::config;
use anyhow::{bail, Context as _, Result};
use crypto_auditing::types::EventGroup;
use probe::probe;
use serde_cbor::{ser::IoWrite, Serializer};
use std::path::PathBuf;
use time::{macros::format_description, OffsetDateTime};
Expand Down Expand Up @@ -179,6 +180,11 @@ impl LogWriter {
config::Format::Minimal => to_vec_minimal(&group)?,
};
self.write_all(v).await?;
probe!(
crypto_auditing_internal_agent,
event_group,
group.events().len()
);
self.written_events += group.events().len();
}
self.groups.clear();
Expand Down
17 changes: 17 additions & 0 deletions agent/tests/agenttest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "agenttest"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["The crypto-auditing developers"]

[dependencies]
anyhow = "1.0"
libbpf-rs = { version = "0.20", features = ["novendor"] }
libc = "0.2"
nix = "0.26"
tempfile = "3"
plain = "0.2"

[build-dependencies]
libbpf-cargo = { version = "0.20", features = ["novendor"] }
17 changes: 17 additions & 0 deletions agent/tests/agenttest/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0

use libbpf_cargo::SkeletonBuilder;
use std::{env, path::PathBuf};

const SRC: &str = "src/bpf/agent.bpf.c";

fn main() {
let mut out =
PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be set in build script"));
out.push("agent.skel.rs");
SkeletonBuilder::new()
.source(SRC)
.build_and_generate(&out)
.unwrap();
println!("cargo:rerun-if-changed={}", SRC);
}
30 changes: 30 additions & 0 deletions agent/tests/agenttest/src/bpf/agent.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */

#include "vmlinux.h"
#include <bpf/usdt.bpf.h>

#define MAX_DATA_SIZE 512

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 /* one page */);
} ringbuf SEC(".maps");

SEC("usdt")
int
BPF_USDT(event_group, long count)
{
long *value;
long err;

value = bpf_ringbuf_reserve (&ringbuf, sizeof(*value), 0);
if (value)
{
*value = count;
bpf_ringbuf_submit (value, 0);
}

return 0;
}

char LICENSE[] SEC("license") = "GPL";
1 change: 1 addition & 0 deletions agent/tests/agenttest/src/bpf/vmlinux.h
88 changes: 88 additions & 0 deletions agent/tests/agenttest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2023 The crypto-auditing developers.

use anyhow::{bail, Result};
use libbpf_rs::{Link, Map, Object, RingBufferBuilder};
use std::env;
use std::path::{Path, PathBuf};
use std::process::Child;
use std::time::Duration;

mod skel {
include!(concat!(env!("OUT_DIR"), "/agent.skel.rs"));
}
use skel::*;

pub fn target_dir() -> PathBuf {
env::current_exe()
.ok()
.map(|mut path| {
path.pop();
if path.ends_with("deps") {
path.pop();
}
path
})
.unwrap()
}

pub fn agent_path() -> PathBuf {
target_dir().join("crypto-auditing-agent")
}

pub fn bump_memlock_rlimit() -> Result<()> {
let rlimit = libc::rlimit {
rlim_cur: 128 << 20,
rlim_max: 128 << 20,
};

if unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlimit) } != 0 {
bail!("Failed to increase rlimit");
}

Ok(())
}

pub fn attach_bpf(process: &Child, path: impl AsRef<Path>) -> Result<(Link, Object)> {
let skel_builder = AgentSkelBuilder::default();
let open_skel = skel_builder.open()?;
let mut skel = open_skel.load()?;

let mut progs = skel.progs_mut();
let prog = progs.event_group();

let link = prog
.attach_usdt(
process.id() as i32,
path.as_ref(),
"crypto_auditing_internal_agent",
"event_group",
)
.expect("unable to attach prog");

Ok((link, skel.obj))
}

// Copied from libbpf-rs/libbpf-rs/tests/test.rs
pub fn with_ringbuffer<F>(map: &Map, action: F, timeout: Duration) -> Result<i64>
where
F: FnOnce(),
{
let mut value = 0i64;
{
let callback = |data: &[u8]| {
plain::copy_from_bytes(&mut value, data).expect("Wrong size");
0
};

let mut builder = RingBufferBuilder::new();
builder.add(map, callback)?;
let mgr = builder.build()?;

action();
mgr.poll(timeout)?;
mgr.consume()?;
}

Ok(value)
}
101 changes: 101 additions & 0 deletions agent/tests/coalesce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2023 The crypto-auditing developers.

extern crate agenttest;
use agenttest::*;

use crypto_auditing::types::EventGroup;
use probe::probe;
use serde_cbor::de::Deserializer;
use std::env;
use std::panic;
use std::path::PathBuf;
use std::process::Command;
use std::thread;
use std::time::Duration;
use tempfile::tempdir;

fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures")
}

#[test]
fn test_probe_coalesce() {
bump_memlock_rlimit().expect("unable to bump memlock rlimit");

let agent_path = agent_path();
let log_dir = tempdir().expect("unable to create temporary directory");
let log_path = log_dir.path().join("agent.log");
let mut process = Command::new(&agent_path)
.arg("-c")
.arg(fixture_dir().join("agent.conf"))
.arg("--log-file")
.arg(&log_path)
.arg("--library")
.arg(&env::current_exe().unwrap())
.arg("--coalesce-window")
.arg("1000")
.spawn()
.expect("unable to spawn agent");

// Wait until the agent process starts up
while !log_path.exists() {
thread::sleep(Duration::from_millis(100));
}

let result = panic::catch_unwind(|| {
let foo = String::from("foo\0");
let bar = String::from("bar\0");
let baz = String::from("baz\0");

let (_link, object) =
attach_bpf(&process, &agent_path).expect("unable to attach agent.bpf.o");
let map = object.map("ringbuf").expect("unable to get ringbuf map");

let timeout = Duration::from_secs(10);

let result = with_ringbuffer(
map,
|| {
probe!(crypto_auditing, new_context, 1, 2);
probe!(crypto_auditing, word_data, 1, foo.as_ptr(), 3);
probe!(crypto_auditing, string_data, 1, bar.as_ptr(), bar.as_ptr());
probe!(
crypto_auditing,
blob_data,
1,
baz.as_ptr(),
baz.as_ptr(),
baz.len()
);
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 4);
let result = with_ringbuffer(
map,
|| {
probe!(crypto_auditing, new_context, 4, 5);
},
timeout,
)
.expect("unable to exercise probe points");
assert_eq!(result, 1);

let log_file = std::fs::File::open(&log_path)
.expect(&format!("unable to read file `{}`", log_path.display()));

let groups: Result<Vec<_>, _> = Deserializer::from_reader(&log_file)
.into_iter::<EventGroup>()
.collect();
let groups = groups.expect("error deserializing");
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].events().len(), 4);
assert_eq!(groups[1].events().len(), 1);
});

process.kill().expect("unable to kill agent");

assert!(result.is_ok());
}
Loading