use btconfig::{CredStoreConfig, FigmentExt, NodeCredConsumer};
use btconsole::{BtConsole, BtConsoleConfig};
use btfproto::{
local_fs::{LocalFs, ModeAuthorizer},
server::{new_fs_server, FsProvider},
};
use btlib::{bterr, crypto::Creds, error::DisplayErr, log::BuilderExt, Result};
use bttp::Receiver;
use figment::{providers::Serialized, Figment};
use serde::{Deserialize, Serialize};
use std::{net::IpAddr, path::PathBuf, sync::Arc};
#[derive(Debug, Serialize, Deserialize, Clone)]
struct BtfsdConfig {
#[serde(rename = "credstore")]
cred_store: CredStoreConfig,
#[serde(rename = "ipaddr")]
ip_addr: IpAddr,
#[serde(rename = "blockdir")]
block_dir: PathBuf,
console: Option<BtConsoleConfig>,
}
impl BtfsdConfig {
fn new() -> Result<BtfsdConfig> {
Figment::new()
.merge(Serialized::defaults(Self::default()))
.btconfig()?
.extract()
.map_err(|err| err.into())
}
}
impl Default for BtfsdConfig {
fn default() -> Self {
Self {
cred_store: CredStoreConfig::default(),
ip_addr: IpAddr::from([127, 0, 0, 1]),
block_dir: btconfig::default_block_dir(),
console: Some(BtConsoleConfig::default()),
}
}
}
async fn provider(block_dir: PathBuf, creds: Arc<dyn Creds>) -> Result<impl FsProvider> {
if block_dir.exists() {
LocalFs::new_existing(block_dir, creds, ModeAuthorizer)
} else {
std::fs::create_dir_all(&block_dir)?;
LocalFs::new_empty(block_dir, 0, creds, ModeAuthorizer).await
}
}
async fn receiver(config: BtfsdConfig) -> Result<Receiver> {
let node_creds = config.cred_store.consume(NodeCredConsumer)??;
let provider = Arc::new(provider(config.block_dir, node_creds.clone()).await?);
new_fs_server(config.ip_addr, Arc::new(node_creds), provider)
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_default_env().btformat().init();
let mut config = BtfsdConfig::new()?;
let console_config = config
.console
.take()
.ok_or_else(|| bterr!("No BtConsole configuration was provided"))?;
let (console, receiver) = tokio::join!(BtConsole::start(console_config), receiver(config));
let receiver = receiver?;
let console = console?;
log::debug!("ready to accept connections");
let (console, receiver) = tokio::join!(console.has_shutdown(), receiver.complete()?);
console?;
receiver.display_err()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use btconfig::CredStoreConfig;
use btconsole::BtConsoleConfig;
use btfproto::{client::FsClient, msg::*};
use btlib::{
crypto::{
file_cred_store::FileCredStore, tpm::TpmCredStore, ConcreteCreds, CredStore,
CredStoreMut, CredsPriv, CredsPub,
},
log::BuilderExt,
AuthzAttrs, BlockMetaSecrets, Epoch, IssuedProcRec, Principaled, ProcRec,
};
use btlib_tests::{CredStoreTestingExt, TpmCredStoreHarness};
use bttp::BlockAddr;
use btserde::from_slice;
use std::net::{IpAddr, Ipv4Addr};
use std::{future::ready, net::Ipv6Addr, time::Duration};
use swtpm_harness::SwtpmHarness;
use tempdir::TempDir;
const LOG_LEVEL: &str = "warn";
#[ctor::ctor]
fn ctor() {
std::env::set_var("RUST_LOG", LOG_LEVEL);
env_logger::Builder::from_default_env().btformat().init();
}
struct FileTestCase {
client: FsClient,
_rx: Receiver,
_dir: TempDir,
}
async fn file_test_case(dir: TempDir, ipaddr: IpAddr) -> FileTestCase {
let file_store_path = dir.path().join("cred_store");
let credstore = CredStoreConfig::File {
path: file_store_path.clone(),
};
{
let cred_store = FileCredStore::new(file_store_path).unwrap();
cred_store.provision(ROOT_PASSWD).unwrap();
}
let config = BtfsdConfig {
cred_store: credstore,
ip_addr: ipaddr,
block_dir: dir.path().join(BT_DIR),
console: Some(BtConsoleConfig::default()),
};
let rx = receiver(config).await.unwrap();
let tx = rx.transmitter(rx.addr().clone()).await.unwrap();
let client = FsClient::new(tx);
FileTestCase {
_dir: dir,
_rx: rx,
client,
}
}
async fn new_file_test_case() -> FileTestCase {
let dir = TempDir::new("btfsd").unwrap();
file_test_case(dir, LOCALHOST).await
}
struct TestCase {
client: FsClient,
rx: Receiver,
harness: TpmCredStoreHarness,
_dir: TempDir,
}
const ROOT_PASSWD: &str = "existential_threat";
const LOCALHOST: IpAddr = IpAddr::V6(Ipv6Addr::LOCALHOST);
const BT_DIR: &str = "bt";
async fn tpm_test_case(dir: TempDir, harness: TpmCredStoreHarness, ipaddr: IpAddr) -> TestCase {
let swtpm = harness.swtpm();
let credstore = CredStoreConfig::Tpm {
path: swtpm.state_path().to_owned(),
tabrmd: swtpm.tabrmd_config().to_owned(),
};
let config = BtfsdConfig {
cred_store: credstore,
ip_addr: ipaddr,
block_dir: dir.path().join(BT_DIR),
console: Some(BtConsoleConfig::default()),
};
let rx = receiver(config).await.unwrap();
let tx = rx.transmitter(rx.addr().clone()).await.unwrap();
let client = FsClient::new(tx);
TestCase {
_dir: dir,
harness,
rx,
client,
}
}
async fn new_tpm_test_case() -> TestCase {
let dir = TempDir::new("btfsd").unwrap();
let harness = TpmCredStoreHarness::new(ROOT_PASSWD.to_owned()).unwrap();
tpm_test_case(dir, harness, LOCALHOST).await
}
async fn existing_case(case: TestCase) -> TestCase {
case.rx.stop().unwrap();
case.rx.complete().unwrap().await.unwrap();
let TestCase {
_dir,
harness: _harness,
..
} = case;
tpm_test_case(_dir, _harness, IpAddr::V4(Ipv4Addr::LOCALHOST)).await
}
#[allow(dead_code)]
async fn manual_test() {
let case = new_tpm_test_case().await;
case.rx.complete().unwrap().await.unwrap();
}
async fn create_write_read(client: FsClient) {
const FILENAME: &str = "file.txt";
const EXPECTED: &[u8] = b"potato";
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
let WriteReply { written, .. } = client.write(inode, handle, 0, EXPECTED).await.unwrap();
assert_eq!(EXPECTED.len() as u64, written);
let actual = client
.read(inode, handle, 0, EXPECTED.len() as u64, |reply| {
let mut buf = Vec::with_capacity(EXPECTED.len());
buf.extend_from_slice(reply.data);
ready(buf)
})
.await
.unwrap();
assert_eq!(EXPECTED, &actual);
}
#[tokio::test]
async fn create_write_read_with_tpm() {
let case = new_tpm_test_case().await;
create_write_read(case.client).await;
}
#[tokio::test]
async fn create_write_read_with_file() {
let case = new_file_test_case().await;
create_write_read(case.client).await;
}
#[tokio::test]
async fn read_full_sector() {
const FILENAME: &str = "file.txt";
const EXPECTED: &[u8] = b"prawn crisps";
let case = new_tpm_test_case().await;
let client = case.client;
let CreateReply {
inode,
handle,
entry,
..
} = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
let WriteReply { written, .. } = client.write(inode, handle, 0, EXPECTED).await.unwrap();
assert_eq!(EXPECTED.len() as u64, written);
assert!(entry.attr.sect_sz > EXPECTED.len() as u64);
let actual = client
.read(inode, handle, 0, entry.attr.sect_sz, |reply| {
let mut buf = Vec::with_capacity(EXPECTED.len());
buf.extend_from_slice(reply.data);
ready(buf)
})
.await
.unwrap();
assert!(EXPECTED.eq(&actual));
}
#[tokio::test]
async fn read_from_different_instance() {
const FILENAME: &str = "file.txt";
const EXPECTED: &[u8] = b"potato";
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
let WriteReply { written, .. } = client.write(inode, handle, 0, EXPECTED).await.unwrap();
assert_eq!(EXPECTED.len() as u64, written);
client.flush(inode, handle).await.unwrap();
let case = existing_case(case).await;
let client = &case.client;
let LookupReply { inode, .. } = client
.lookup(SpecInodes::RootDir.into(), FILENAME)
.await
.unwrap();
let OpenReply { handle, .. } = client
.open(inode, FlagValue::ReadOnly.into())
.await
.unwrap();
let actual = client
.read(inode, handle, 0, EXPECTED.len() as u64, |reply| {
let mut buf = Vec::with_capacity(EXPECTED.len());
buf.extend_from_slice(reply.data);
ready(buf)
})
.await
.unwrap();
assert_eq!(EXPECTED, &actual);
}
#[tokio::test]
async fn create_lookup() {
const FILENAME: &str = "file.txt";
let case = new_tpm_test_case().await;
let client = case.client;
let CreateReply {
inode: expected, ..
} = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadOnly.into(),
0o644,
0,
)
.await
.unwrap();
let LookupReply { inode: actual, .. } = client
.lookup(SpecInodes::RootDir.into(), FILENAME)
.await
.unwrap();
assert_eq!(expected, actual);
}
#[tokio::test]
async fn open_existing() {
const FILENAME: &str = "file.txt";
let case = new_tpm_test_case().await;
let client = case.client;
let CreateReply { inode, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadOnly.into(),
0o644,
0,
)
.await
.unwrap();
let result = client.open(inode, FlagValue::ReadWrite.into()).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn write_flush_close_read() {
const FILENAME: &str = "lyrics.txt";
const EXPECTED: &[u8] = b"Fate, or something better";
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
let WriteReply { written, .. } = client.write(inode, handle, 0, EXPECTED).await.unwrap();
assert_eq!(EXPECTED.len() as u64, written);
client.flush(inode, handle).await.unwrap();
client.close(inode, handle).await.unwrap();
let OpenReply { handle, .. } = client
.open(inode, FlagValue::ReadOnly.into())
.await
.unwrap();
let actual = client
.read(inode, handle, 0, EXPECTED.len() as u64, |reply| {
ready(reply.data.to_owned())
})
.await
.unwrap();
assert_eq!(EXPECTED, &actual);
}
#[tokio::test]
async fn link() {
const FIRSTNAME: &str = "Jean-Luc";
const LASTNAME: &str = "Picard";
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, .. } = client
.create(
SpecInodes::RootDir.into(),
FIRSTNAME,
Flags::default(),
0o644,
0,
)
.await
.unwrap();
client
.link(inode, SpecInodes::RootDir.into(), LASTNAME)
.await
.unwrap();
let OpenReply {
handle: root_handle,
..
} = client
.open(
SpecInodes::RootDir.into(),
FlagValue::ReadOnly | FlagValue::Directory,
)
.await
.unwrap();
let ReadDirReply { entries, .. } = client
.read_dir(SpecInodes::RootDir.into(), root_handle, 0, 0)
.await
.unwrap();
let filenames: Vec<_> = entries.iter().map(|e| e.0.as_str()).collect();
assert!(filenames.contains(&FIRSTNAME));
assert!(filenames.contains(&LASTNAME));
}
#[tokio::test]
async fn unlink() {
const FIRSTNAME: &str = "Jean-Luc";
const LASTNAME: &str = "Picard";
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, .. } = client
.create(
SpecInodes::RootDir.into(),
FIRSTNAME,
Flags::default(),
0o644,
0,
)
.await
.unwrap();
client
.link(inode, SpecInodes::RootDir.into(), LASTNAME)
.await
.unwrap();
client
.unlink(SpecInodes::RootDir.into(), FIRSTNAME)
.await
.unwrap();
let OpenReply {
handle: root_handle,
..
} = client
.open(
SpecInodes::RootDir.into(),
FlagValue::ReadOnly | FlagValue::Directory,
)
.await
.unwrap();
let ReadDirReply { entries, .. } = client
.read_dir(SpecInodes::RootDir.into(), root_handle, 0, 0)
.await
.unwrap();
let filenames: Vec<_> = entries.iter().map(|e| e.0.as_str()).collect();
assert!(filenames.contains(&LASTNAME));
assert!(!filenames.contains(&FIRSTNAME));
}
#[tokio::test]
async fn delete() {
const FILENAME: &str = "MANIFESTO.tex";
let case = new_tpm_test_case().await;
let client = &case.client;
client
.create(
SpecInodes::RootDir.into(),
FILENAME,
Flags::default(),
0o644,
0,
)
.await
.unwrap();
client
.unlink(SpecInodes::RootDir.into(), FILENAME)
.await
.unwrap();
let OpenReply {
handle: root_handle,
..
} = client
.open(
SpecInodes::RootDir.into(),
FlagValue::ReadOnly | FlagValue::Directory,
)
.await
.unwrap();
let ReadDirReply { entries, .. } = client
.read_dir(SpecInodes::RootDir.into(), root_handle, 0, 0)
.await
.unwrap();
let filenames: Vec<_> = entries.iter().map(|e| e.0.as_str()).collect();
assert!(!filenames.contains(&FILENAME));
}
#[tokio::test]
async fn read_meta() {
const FILENAME: &str = "kibosh.txt";
const EXPECTED: u32 = 0o600;
let case = new_tpm_test_case().await;
let client = &case.client;
let before_create = Epoch::now();
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
EXPECTED,
0,
)
.await
.unwrap();
let before_read = Epoch::now();
let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
let actual = attrs.mode;
assert_eq!(FileType::Reg | EXPECTED, actual);
assert!(before_create <= attrs.ctime && attrs.ctime <= before_read);
assert!(before_create <= attrs.mtime && attrs.mtime <= before_read);
assert!(before_create <= attrs.atime && attrs.atime <= before_read);
assert_eq!(attrs.block_id.inode, inode);
assert_eq!(attrs.size, 0);
assert!(attrs.tags.is_empty());
}
#[tokio::test]
async fn write_meta() {
fn assert_eq(expected: &Attrs, actual: &BlockMetaSecrets) {
assert_eq!(expected.mode, actual.mode);
assert_eq!(expected.uid, actual.uid);
assert_eq!(expected.gid, actual.gid);
assert_eq!(expected.atime, actual.atime);
assert_eq!(expected.mtime, actual.mtime);
assert_eq!(expected.ctime, actual.ctime);
assert_eq!(expected.tags.len(), actual.tags.len());
for (key, expected_value) in expected.tags.iter() {
let actual_value = actual.tags.get(key).unwrap();
assert_eq!(expected_value, actual_value);
}
}
const FILENAME: &str = "word_salad.docx";
let expected = Attrs {
mode: 0o600,
uid: 9290,
gid: 2190,
atime: 23193.into(),
mtime: 53432.into(),
ctime: 87239.into(),
tags: Vec::new(),
};
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
expected.mode | 0o011,
0,
)
.await
.unwrap();
let WriteMetaReply { attrs, .. } = client
.write_meta(inode, Some(handle), expected.clone(), AttrsSet::ALL)
.await
.unwrap();
assert_eq(&expected, &attrs);
let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
assert_eq(&expected, &attrs);
}
#[tokio::test]
async fn allocate_when_empty() {
const FILENAME: &str = "output.dat";
const EXPECTED: &[u8] = &[0u8; 8];
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
client
.allocate(inode, handle, EXPECTED.len() as u64)
.await
.unwrap();
let actual = client
.read(inode, handle, 0, EXPECTED.len() as u64, |reply| {
ready(Vec::from(reply.data))
})
.await
.unwrap();
assert_eq!(EXPECTED, actual);
}
#[tokio::test]
async fn allocate_with_data_present() {
const FILENAME: &str = "output.dat";
const EXPECTED: &[u8] = &[1, 1, 1, 1, 0, 0, 0, 0];
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
client.write(inode, handle, 0, &[1, 1, 1, 1]).await.unwrap();
client
.allocate(inode, handle, EXPECTED.len() as u64)
.await
.unwrap();
let actual = client
.read(inode, handle, 0, EXPECTED.len() as u64, |reply| {
ready(Vec::from(reply.data))
})
.await
.unwrap();
assert_eq!(EXPECTED, actual);
}
#[tokio::test]
async fn forget() {
const FILENAME: &str = "seed.dat";
let case = new_tpm_test_case().await;
let client = &case.client;
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite.into(),
0o644,
0,
)
.await
.unwrap();
client.close(inode, handle).await.unwrap();
let result = client.forget(inode, 1).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn add_readcap() {
const FILENAME: &str = "net";
let case = new_tpm_test_case().await;
let client = &case.client;
let creds = ConcreteCreds::generate().unwrap();
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
FILENAME,
FlagValue::ReadWrite | FlagValue::Directory,
0o755,
0,
)
.await
.unwrap();
client
.add_readcap(inode, handle, creds.concrete_pub())
.await
.unwrap();
}
#[tokio::test]
async fn grant_access_to_root() {
let case = new_tpm_test_case().await;
let client = &case.client;
let mut creds = ConcreteCreds::generate().unwrap();
let root_creds = case.harness.root_creds().unwrap();
let writecap = root_creds
.issue_writecap(
creds.principal(),
&mut std::iter::empty(),
Epoch::now() + Duration::from_secs(3600),
)
.unwrap();
creds.set_writecap(writecap).unwrap();
let expected = IssuedProcRec {
addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
pub_creds: creds.concrete_pub(),
writecap: creds.writecap().unwrap().to_owned(),
authz_attrs: AuthzAttrs {
uid: 9001,
gid: 9001,
supp_gids: vec![12, 41, 19],
},
};
client
.grant_access(SpecInodes::RootDir.into(), expected.clone())
.await
.unwrap();
let LookupReply { inode, entry, .. } = client
.lookup(SpecInodes::RootDir.into(), &creds.principal().to_string())
.await
.unwrap();
let OpenReply { handle, .. } = client
.open(inode, FlagValue::ReadOnly.into())
.await
.unwrap();
let record = client
.read(inode, handle, 0, entry.attr.size, |reply| {
ready(from_slice::<ProcRec>(reply.data))
})
.await
.unwrap()
.unwrap();
let actual = record.validate().unwrap();
assert_eq!(expected, actual);
}
#[tokio::test]
async fn grant_access_to_non_root_dir() {
const DIRNAME: &str = "var";
let case = new_tpm_test_case().await;
let client = &case.client;
let mut creds = ConcreteCreds::generate().unwrap();
let root_creds = case.harness.root_creds().unwrap();
let writecap = root_creds
.issue_writecap(
creds.principal(),
&mut [DIRNAME].into_iter(),
Epoch::now() + Duration::from_secs(3600),
)
.unwrap();
creds.set_writecap(writecap).unwrap();
let expected = IssuedProcRec {
addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
pub_creds: creds.concrete_pub(),
writecap: creds.writecap().unwrap().to_owned(),
authz_attrs: AuthzAttrs {
uid: 9001,
gid: 9001,
supp_gids: vec![12, 41, 19],
},
};
let CreateReply { inode, handle, .. } = client
.create(
SpecInodes::RootDir.into(),
DIRNAME,
FlagValue::Directory | FlagValue::ReadWrite,
0o755,
0,
)
.await
.unwrap();
client.close(inode, handle).await.unwrap();
client.grant_access(inode, expected.clone()).await.unwrap();
let LookupReply {
inode: record_inode,
entry,
..
} = client
.lookup(inode, &creds.principal().to_string())
.await
.unwrap();
let OpenReply {
handle: record_handle,
..
} = client
.open(record_inode, FlagValue::ReadOnly.into())
.await
.unwrap();
let record = client
.read(record_inode, record_handle, 0, entry.attr.size, |reply| {
ready(from_slice::<ProcRec>(reply.data))
})
.await
.unwrap()
.unwrap();
let actual = record.validate().unwrap();
assert_eq!(expected, actual);
}
#[tokio::test]
async fn grant_access_non_root_user() {
const ROOT_FILE: &str = "root.txt";
const USER_FILE: &str = "user.txt";
let user_tpm = SwtpmHarness::new().unwrap();
let case = new_tpm_test_case().await;
let client = &case.client;
let root_creds = case.harness.root_creds().unwrap();
let user_creds = {
let cred_store = TpmCredStore::from_context(
user_tpm.context().unwrap(),
user_tpm.state_path().to_owned(),
)
.unwrap();
let mut creds = cred_store.node_creds().unwrap();
let writecap = root_creds
.issue_writecap(
creds.principal(),
&mut std::iter::empty(),
Epoch::now() + Duration::from_secs(3600),
)
.unwrap();
cred_store
.assign_node_writecap(&mut creds, writecap)
.unwrap();
creds
};
let expected = IssuedProcRec {
addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
pub_creds: user_creds.concrete_pub(),
writecap: user_creds.writecap().unwrap().to_owned(),
authz_attrs: AuthzAttrs {
uid: 9001,
gid: 9001,
supp_gids: vec![12, 41, 19],
},
};
let root_inode = SpecInodes::RootDir.into();
client
.grant_access(root_inode, expected.clone())
.await
.unwrap();
client
.create(root_inode, ROOT_FILE, FlagValue::ReadWrite.into(), 0o644, 0)
.await
.unwrap();
let CreateReply { inode, handle, .. } = client
.create(root_inode, USER_FILE, FlagValue::ReadWrite.into(), 0o644, 0)
.await
.unwrap();
let mut attrs = Attrs::default();
attrs.uid = expected.authz_attrs.uid;
attrs.gid = expected.authz_attrs.gid;
client
.write_meta(inode, Some(handle), attrs, AttrsSet::UID | AttrsSet::GID)
.await
.unwrap();
let node_creds = case.harness.cred_store().node_creds().unwrap();
let bind_path = node_creds.writecap().unwrap().bind_path();
let block_addr = Arc::new(BlockAddr::new(LOCALHOST, Arc::new(bind_path)));
let tx = bttp::Transmitter::new(block_addr, Arc::new(user_creds))
.await
.unwrap();
let client = FsClient::new(tx);
let root_dir = SpecInodes::RootDir.value();
let LookupReply { inode, .. } = client.lookup(root_dir, USER_FILE).await.unwrap();
let result = client.open(inode, FlagValue::ReadWrite.into()).await;
assert!(result.is_ok());
let LookupReply { inode, .. } = client.lookup(root_dir, ROOT_FILE).await.unwrap();
let result = client.open(inode, FlagValue::ReadWrite.into()).await;
let err = result.err().unwrap().to_string();
assert_eq!("write access denied", err);
}
}
#[cfg(test)]
mod config_tests {
use super::BtfsdConfig;
use std::{
net::{IpAddr, SocketAddr},
path::PathBuf,
};
use btconfig::CredStoreConfig;
use btconsole::BtConsoleConfig;
use figment::Jail;
#[test]
fn cred_store_file_path() {
Jail::expect_with(|jail| {
let expected = PathBuf::from("./file_credstore");
jail.set_env("BT_CREDSTORE_TYPE", "File");
jail.set_env("BT_CREDSTORE_PATH", expected.display());
let config = BtfsdConfig::new().unwrap();
let success = if let CredStoreConfig::File { path: actual } = config.cred_store {
expected == actual
} else {
false
};
assert!(success);
Ok(())
})
}
#[test]
fn cred_store_tpm_path_tabrmd() {
Jail::expect_with(|jail| {
let expected_path = PathBuf::from("./tpm_credstore");
let expected_tabrmd = String::from("bus_type=session");
jail.set_env("BT_CREDSTORE_TYPE", "Tpm");
jail.set_env("BT_CREDSTORE_PATH", expected_path.display());
jail.set_env("BT_CREDSTORE_TABRMD", expected_tabrmd.as_str());
let config = BtfsdConfig::new().unwrap();
let success = if let CredStoreConfig::Tpm {
path: actual_path,
tabrmd: actual_tabrmd,
} = config.cred_store
{
expected_path == actual_path && expected_tabrmd == actual_tabrmd
} else {
false
};
assert!(success);
Ok(())
})
}
#[test]
fn ip_addr() {
Jail::expect_with(|jail| {
let expected = IpAddr::from([172, 0, 0, 1]);
jail.set_env("BT_IPADDR", expected);
let config = BtfsdConfig::new().unwrap();
assert_eq!(expected, config.ip_addr);
Ok(())
})
}
#[test]
fn block_dir() {
Jail::expect_with(|jail| {
let expected = PathBuf::from("/tmp/blocks");
jail.set_env("BT_BLOCKDIR", expected.display());
let config = BtfsdConfig::new().unwrap();
assert_eq!(expected, config.block_dir);
Ok(())
})
}
#[test]
fn console() {
Jail::expect_with(|jail| {
let expected_addr = SocketAddr::from(([127, 0, 0, 42], 4129));
let expected_dist_path = PathBuf::from("/var/www");
jail.set_env("BT_CONSOLE_ADDR", expected_addr);
jail.set_env("BT_CONSOLE_DISTPATH", expected_dist_path.display());
let expected = BtConsoleConfig::new(expected_addr, expected_dist_path);
let config = BtfsdConfig::new().unwrap();
assert_eq!(expected, config.console.unwrap());
Ok(())
})
}
}