Skip to content

Commit

Permalink
client: Promote client example to a standalone program
Browse files Browse the repository at this point in the history
This moves the client example in the crypto-auditing crate into a
separate application crate, with config file and tracing support.

Also adds "install-programs" make target so that only the programs can
be installed.

Signed-off-by: Daiki Ueno <[email protected]>
  • Loading branch information
ueno committed Jun 13, 2023
1 parent a8fe220 commit a87c032
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 7 deletions.
16 changes: 16 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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[workspace]

members = ["agent", "crypto-auditing", "event-broker", "log-parser"]
members = ["agent", "client", "crypto-auditing", "event-broker", "log-parser"]
resolver = "2"
15 changes: 9 additions & 6 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ systemdsystemunitdir := $(shell pkg-config systemd --variable=systemdsystemunitd

programs = \
${TARGETDIR}/${PROFILE}/crypto-auditing-agent \
${TARGETDIR}/${PROFILE}/crypto-auditing-client \
${TARGETDIR}/${PROFILE}/crypto-auditing-event-broker \
${TARGETDIR}/${PROFILE}/crypto-auditing-log-parser

conffiles = \
dist/conf/agent.conf \
dist/conf/client.conf \
dist/conf/event-broker.conf

.PHONY: all
Expand All @@ -32,21 +34,22 @@ agent/src/bpf/vmlinux.h:
$(programs): agent/src/bpf/vmlinux.h
cargo build --target-dir="${TARGETDIR}" ${CARGO_ARGS}

.PHONY: install-programs
install-programs: all
for f in $(programs); do \
install -D -t ${DESTDIR}/usr/bin "$$f"; \
done

.PHONY: install
install: all
install: install-programs
for f in $(conffiles); do \
install -D -m 644 -S .orig -t /etc/crypto-auditing "$$f"; \
done
for f in $(programs); do \
install -D -t ${DESTDIR}/usr/bin "$$f"; \
done
install -D -m 644 -t ${DESTDIR}$(systemdsystemunitdir) dist/systemd/system/crypto-auditing-agent.service
install -D -m 644 -t ${DESTDIR}$(systemdsystemunitdir) dist/systemd/system/crypto-auditing-event-broker.service
install -d ${DESTDIR}/var/lib/crypto-auditing
install -d ${DESTDIR}/var/log/crypto-auditing

# This only runs tests without TPM access. See tests/run.sh for
# running full testsuite with swtpm.
.PHONY: check
check: all
cargo test --target-dir="${TARGETDIR}"
Expand Down
21 changes: 21 additions & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "crypto-auditing-client"
description = "Event broker client for crypto-auditing project"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["The crypto-auditing developers"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0"
clap = { version = "4", features=["derive"] }
crypto-auditing = { path = "../crypto-auditing" }
futures = "0.3"
serde_cbor = "0.10"
serde_json = "1.0"
tokio = "1.23"
toml = "0.6"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features=["env-filter"] }
182 changes: 182 additions & 0 deletions client/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2022-2023 The crypto-auditing developers.

use anyhow::{anyhow, Context as _, Result};
use clap::{arg, command, parser::ValueSource, value_parser, ArgAction, ArgMatches, ValueEnum};
use crypto_auditing::event_broker::SOCKET_PATH;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml::{Table, Value};

const CONFIG: &'static str = "/etc/crypto-auditing/event-broker.conf";

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum Format {
Json,
Cbor,
}

#[derive(Debug)]
pub struct Config {
/// Path to Unix socket
pub socket_path: PathBuf,

/// Scope to match
pub scope: Vec<String>,

/// Output format
pub format: Format,

/// Path to output file
pub output: Option<PathBuf>,
}

impl Default for Config {
fn default() -> Self {
Self {
socket_path: PathBuf::from(SOCKET_PATH),
scope: Vec::default(),
format: Format::Json,
output: None,
}
}
}

impl Config {
pub fn new() -> Result<Self> {
let mut config = Config::default();

let matches = command!()
.arg(
arg!(
-c --config <FILE> "Path to configuration file"
)
.required(false)
.value_parser(value_parser!(PathBuf)),
)
.arg(
arg!(
--"socket-path" <PATH> "Path to Unix socket"
)
.required(false)
.value_parser(value_parser!(PathBuf))
.default_value(SOCKET_PATH),
)
.arg(
arg!(
--scope <SCOPE> "Scope to restrict matches"
)
.required(false)
.value_parser(value_parser!(PathBuf))
.action(ArgAction::Append),
)
.arg(
arg!(
--format <FORMAT> "Output format"
)
.required(false)
.value_parser(value_parser!(Format)),
)
.arg(
arg!(
--output <PATH> "Path to output file"
)
.required(false)
.value_parser(value_parser!(PathBuf)),
)
.get_matches();

if let Some(config_file) = matches.get_one::<PathBuf>("config") {
config.merge_config_file(&config_file)?;
} else if Path::new(CONFIG).exists() {
config.merge_config_file(CONFIG)?;
}

config.merge_arg_matches(&matches)?;

Ok(config)
}

fn merge_config_file(&mut self, file: impl AsRef<Path>) -> Result<()> {
let s = fs::read_to_string(file.as_ref())
.with_context(|| format!("unable to read config file `{}`", file.as_ref().display()))?;
let config = Table::from_str(&s).with_context(|| {
format!("unable to parse config file `{}`", file.as_ref().display())
})?;

if let Some(value) = config.get("socket_path") {
self.socket_path = pathbuf_from_value(value)?;
}

if let Some(value) = config.get("scope") {
self.scope = string_array_from_value(value)?;
}

if let Some(value) = config.get("format") {
self.format = format_from_value(value)?;
}

if let Some(value) = config.get("output") {
self.output = Some(pathbuf_from_value(value)?);
}

Ok(())
}

fn merge_arg_matches(&mut self, matches: &ArgMatches) -> Result<()> {
if let Some(ValueSource::CommandLine) = matches.value_source("socket-path") {
self.socket_path = matches
.try_get_one::<PathBuf>("socket-path")?
.unwrap()
.clone();
}

if let Some(ValueSource::CommandLine) = matches.value_source("scope") {
self.scope = matches.try_get_many("scope")?.unwrap().cloned().collect();
}

if let Some(ValueSource::CommandLine) = matches.value_source("format") {
self.format = *matches.try_get_one::<Format>("format")?.unwrap();
}

if let Some(ValueSource::CommandLine) = matches.value_source("output") {
self.output = Some(matches.try_get_one::<PathBuf>("output")?.unwrap().clone());
}

Ok(())
}
}

fn string_array_from_value(value: &Value) -> Result<Vec<String>> {
value
.as_array()
.ok_or_else(|| anyhow!("value must be array"))
.and_then(|array| {
array
.iter()
.map(|v| string_from_value(v))
.collect::<Result<Vec<String>>>()
})
}

fn string_from_value(value: &Value) -> Result<String> {
value
.as_str()
.ok_or_else(|| anyhow!("value must be string"))
.and_then(|v| Ok(v.to_string()))
}

fn pathbuf_from_value(value: &Value) -> Result<PathBuf> {
value
.as_str()
.ok_or_else(|| anyhow!("value must be string"))
.and_then(|v| Ok(PathBuf::from(v)))
}

fn format_from_value(value: &Value) -> Result<Format> {
value
.as_str()
.ok_or_else(|| anyhow!("value must be format"))
.and_then(|v| Format::from_str(v, false).map_err(|e| anyhow!("{}", e)))
}
64 changes: 64 additions & 0 deletions client/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2022-2023 The crypto-auditing developers.

use anyhow::{Context as _, Result};
use crypto_auditing::event_broker::Client;
use futures::StreamExt;
use std::fs::File;
use std::io::{stdout, Write};
use tokio::signal;
use tracing::info;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

mod config;

fn get_writer(c: &config::Config) -> Result<Box<dyn Write + Send>> {
if let Some(path) = &c.output {
Ok(File::create(path)
.map(|f| Box::new(f))
.with_context(|| format!("unable to create file {}", path.display()))?)
} else {
Ok(Box::new(stdout()))
}
}

#[tokio::main]
async fn main() -> Result<()> {
let config = config::Config::new()?;

tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_default_env())
.try_init()?;

let client = Client::new()
.address(&config.socket_path)
.scopes(&config.scope);

let mut writer = get_writer(&config)?;

let (_handle, mut reader) = client.start().await?;

tokio::spawn(async move {
while let Some(group) = reader.next().await {
match config.format {
config::Format::Json => {
if let Err(e) = serde_json::to_writer_pretty(&mut writer, &group) {
info!(error = %e,
"unable to write group");
}
}
config::Format::Cbor => {
if let Err(e) = serde_cbor::ser::to_writer(&mut writer, &group) {
info!(error = %e,
"unable to write group");
}
}
}
}
});

signal::ctrl_c().await?;

Ok(())
}
2 changes: 2 additions & 0 deletions dist/conf/client.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# socket_path = "/var/lib/crypto-auditing/audit.sock"
# scope = ["tls"]
1 change: 1 addition & 0 deletions event-broker/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "crypto-auditing-event-broker"
description = "Event broker for crypto-auditing project"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
Expand Down

0 comments on commit a87c032

Please sign in to comment.