|
@@ -73,10 +73,20 @@ mod tests {
|
|
|
use super::*;
|
|
|
|
|
|
use btfproto::{client::FsClient, msg::*};
|
|
|
- use btlib::{log::BuilderExt, BlockMetaSecrets, Epoch};
|
|
|
+ use btlib::{
|
|
|
+ crypto::{ConcreteCreds, CredsPriv, CredsPub},
|
|
|
+ log::BuilderExt,
|
|
|
+ AuthzAttrs, BlockMetaSecrets, Epoch, IssuedProcRec, Principaled, ProcRec,
|
|
|
+ };
|
|
|
use btlib_tests::TpmCredStoreHarness;
|
|
|
- use btmsg::Transmitter;
|
|
|
- use std::{future::ready, net::Ipv4Addr};
|
|
|
+ use btmsg::{BlockAddr, Transmitter};
|
|
|
+ use btserde::from_slice;
|
|
|
+ use std::{
|
|
|
+ future::ready,
|
|
|
+ net::{Ipv4Addr, SocketAddr},
|
|
|
+ time::Duration,
|
|
|
+ };
|
|
|
+ use swtpm_harness::SwtpmHarness;
|
|
|
use tempdir::TempDir;
|
|
|
|
|
|
const LOG_LEVEL: &str = "warn";
|
|
@@ -90,12 +100,13 @@ mod tests {
|
|
|
struct TestCase<R, T> {
|
|
|
client: FsClient<T>,
|
|
|
rx: R,
|
|
|
- _harness: TpmCredStoreHarness,
|
|
|
+ harness: TpmCredStoreHarness,
|
|
|
_dir: TempDir,
|
|
|
}
|
|
|
|
|
|
const ROOT_PASSWD: &str = "existential_threat";
|
|
|
const LOCALHOST: IpAddr = IpAddr::V6(Ipv6Addr::LOCALHOST);
|
|
|
+ const BT_DIR: &str = "bt";
|
|
|
|
|
|
async fn test_case(
|
|
|
dir: TempDir,
|
|
@@ -106,14 +117,14 @@ mod tests {
|
|
|
ip_addr,
|
|
|
tabrmd: harness.swtpm().tabrmd_config().to_owned(),
|
|
|
tpm_state_path: harness.swtpm().state_path().to_owned(),
|
|
|
- block_dir: dir.path().join("bt"),
|
|
|
+ block_dir: dir.path().join(BT_DIR),
|
|
|
};
|
|
|
let rx = receiver(config);
|
|
|
let tx = rx.transmitter(rx.addr().clone()).await.unwrap();
|
|
|
let client = FsClient::new(tx);
|
|
|
TestCase {
|
|
|
_dir: dir,
|
|
|
- _harness: harness,
|
|
|
+ harness,
|
|
|
rx,
|
|
|
client,
|
|
|
}
|
|
@@ -130,7 +141,11 @@ mod tests {
|
|
|
) -> TestCase<impl Receiver, impl Transmitter> {
|
|
|
case.rx.stop().await.unwrap();
|
|
|
case.rx.complete().unwrap().await.unwrap();
|
|
|
- let TestCase { _dir, _harness, .. } = case;
|
|
|
+ let TestCase {
|
|
|
+ _dir,
|
|
|
+ harness: _harness,
|
|
|
+ ..
|
|
|
+ } = case;
|
|
|
test_case(_dir, _harness, IpAddr::V4(Ipv4Addr::LOCALHOST)).await
|
|
|
}
|
|
|
|
|
@@ -456,13 +471,12 @@ mod tests {
|
|
|
.unwrap();
|
|
|
let before_read = Epoch::now();
|
|
|
let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
|
|
|
- let after = Epoch::now();
|
|
|
|
|
|
let actual = attrs.mode;
|
|
|
assert_eq!(FileType::Reg | EXPECTED, actual);
|
|
|
assert!(before_create <= attrs.ctime && attrs.ctime <= before_read);
|
|
|
- assert!(before_read <= attrs.mtime && attrs.mtime <= after);
|
|
|
- assert!(before_read <= attrs.atime && attrs.atime <= after);
|
|
|
+ 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());
|
|
@@ -470,7 +484,7 @@ mod tests {
|
|
|
|
|
|
#[tokio::test]
|
|
|
async fn write_meta() {
|
|
|
- fn check(expected: &Attrs, actual: &BlockMetaSecrets) {
|
|
|
+ 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);
|
|
@@ -511,9 +525,327 @@ mod tests {
|
|
|
.write_meta(inode, Some(handle), expected.clone(), AttrsSet::ALL)
|
|
|
.await
|
|
|
.unwrap();
|
|
|
- check(&expected, &attrs);
|
|
|
+ assert_eq(&expected, &attrs);
|
|
|
let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
|
|
|
|
|
|
- check(&expected, &attrs);
|
|
|
+ assert_eq(&expected, &attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[tokio::test]
|
|
|
+ async fn allocate_when_empty() {
|
|
|
+ const FILENAME: &str = "output.dat";
|
|
|
+ const EXPECTED: &[u8] = &[0u8; 8];
|
|
|
+ let case = new_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 msg = Read {
|
|
|
+ inode,
|
|
|
+ handle,
|
|
|
+ offset: 0,
|
|
|
+ size: EXPECTED.len() as u64,
|
|
|
+ };
|
|
|
+ let actual = client
|
|
|
+ .read(msg, |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_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 msg = Read {
|
|
|
+ inode,
|
|
|
+ handle,
|
|
|
+ offset: 0,
|
|
|
+ size: EXPECTED.len() as u64,
|
|
|
+ };
|
|
|
+ let actual = client
|
|
|
+ .read(msg, |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_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_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.principal(), creds.concrete_pub().enc)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ #[tokio::test]
|
|
|
+ async fn grant_access_to_root() {
|
|
|
+ let case = new_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(),
|
|
|
+ vec![],
|
|
|
+ Epoch::now() + Duration::from_secs(3600),
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ creds.set_writecap(writecap);
|
|
|
+ let expected = IssuedProcRec {
|
|
|
+ addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 52982),
|
|
|
+ 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 read = Read {
|
|
|
+ inode,
|
|
|
+ handle,
|
|
|
+ offset: 0,
|
|
|
+ size: entry.attr.size,
|
|
|
+ };
|
|
|
+ let record = client
|
|
|
+ .read(read, |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_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(),
|
|
|
+ vec![DIRNAME.to_owned()],
|
|
|
+ Epoch::now() + Duration::from_secs(3600),
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ creds.set_writecap(writecap);
|
|
|
+ let expected = IssuedProcRec {
|
|
|
+ addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 52982),
|
|
|
+ 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 read = Read {
|
|
|
+ inode: record_inode,
|
|
|
+ handle: record_handle,
|
|
|
+ offset: 0,
|
|
|
+ size: entry.attr.size,
|
|
|
+ };
|
|
|
+ let record = client
|
|
|
+ .read(read, |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_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(),
|
|
|
+ vec![],
|
|
|
+ Epoch::now() + Duration::from_secs(3600),
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ cred_store
|
|
|
+ .assign_node_writecap(&mut creds, writecap)
|
|
|
+ .unwrap();
|
|
|
+ creds
|
|
|
+ };
|
|
|
+ let expected = IssuedProcRec {
|
|
|
+ addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 52982),
|
|
|
+ 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();
|
|
|
+ // Note that non-root users do not have permission to write to ROOT_FILE, but
|
|
|
+ // UID 9001 has permission to write to USER_FILE.
|
|
|
+ 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 = btmsg::transmitter(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);
|
|
|
}
|
|
|
}
|