123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 |
- // SPDX-License-Identifier: AGPL-3.0-or-later
- mod config;
- use btfproto::{
- local_fs::{LocalFs, ModeAuthorizer},
- server::{new_fs_server, FsProvider},
- };
- use btlib::{
- config_helpers::from_envvar,
- crypto::{tpm::TpmCredStore, CredStore, Creds},
- };
- use btmsg::Receiver;
- use config::{Config, ConfigRef, Envvars};
- use std::{
- net::{IpAddr, Ipv6Addr},
- path::PathBuf,
- str::FromStr,
- sync::Arc,
- };
- const ENVVARS: Envvars<'static> = Envvars {
- ip_addr: "BTFSD_IPADDR",
- tabrmd: "BTFSD_TABRMD",
- tpm_state_path: "BTFSD_TPMSTATE",
- block_dir: "BTFSD_BLOCKDIR",
- };
- const DEFAULT_CONFIG: ConfigRef<'static> = ConfigRef {
- ip_addr: IpAddr::V6(Ipv6Addr::LOCALHOST),
- tabrmd: "bus_type=session",
- tpm_state_path: "./tpm_state",
- block_dir: "./bt",
- };
- async fn provider<C: 'static + Send + Sync + Creds>(
- block_dir: PathBuf,
- creds: C,
- ) -> impl FsProvider {
- if block_dir.exists() {
- LocalFs::new_existing(block_dir, creds, ModeAuthorizer).unwrap()
- } else {
- std::fs::create_dir_all(&block_dir).unwrap();
- LocalFs::new_empty(block_dir, 0, creds, ModeAuthorizer)
- .await
- .unwrap()
- }
- }
- async fn receiver(config: Config) -> impl Receiver {
- let cred_store = TpmCredStore::from_tabrmd(&config.tabrmd, config.tpm_state_path).unwrap();
- let node_creds = cred_store.node_creds().unwrap();
- let provider = Arc::new(provider(config.block_dir, node_creds.clone()).await);
- new_fs_server(config.ip_addr, Arc::new(node_creds), provider).unwrap()
- }
- #[tokio::main]
- async fn main() {
- let ip_addr = from_envvar(ENVVARS.ip_addr)
- .unwrap()
- .map(|txt| IpAddr::from_str(&txt).unwrap());
- let tabrmd = from_envvar(ENVVARS.tabrmd).unwrap();
- let tpm_state_path = from_envvar(ENVVARS.tpm_state_path)
- .unwrap()
- .map(PathBuf::from);
- let block_dir = from_envvar(ENVVARS.block_dir).unwrap().map(PathBuf::from);
- let config = Config::builder()
- .with_ip_addr(ip_addr)
- .with_tabrmd(tabrmd)
- .with_tpm_state_path(tpm_state_path)
- .with_block_dir(block_dir)
- .build();
- let receiver = receiver(config).await;
- receiver.complete().unwrap().await.unwrap();
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- use btfproto::{client::FsClient, msg::*};
- use btlib::{
- crypto::{ConcreteCreds, CredsPriv, CredsPub},
- log::BuilderExt,
- AuthzAttrs, BlockMetaSecrets, Epoch, IssuedProcRec, Principaled, ProcRec,
- };
- use btlib_tests::TpmCredStoreHarness;
- use btmsg::{BlockAddr, Transmitter};
- use btserde::from_slice;
- use std::{future::ready, net::Ipv4Addr, 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 TestCase<R, T> {
- client: FsClient<T>,
- rx: R,
- 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,
- harness: TpmCredStoreHarness,
- ip_addr: IpAddr,
- ) -> TestCase<impl Receiver, impl Transmitter> {
- let config = Config {
- ip_addr,
- tabrmd: harness.swtpm().tabrmd_config().to_owned(),
- tpm_state_path: harness.swtpm().state_path().to_owned(),
- block_dir: dir.path().join(BT_DIR),
- };
- let rx = receiver(config).await;
- let tx = rx.transmitter(rx.addr().clone()).await.unwrap();
- let client = FsClient::new(tx);
- TestCase {
- _dir: dir,
- harness,
- rx,
- client,
- }
- }
- async fn new_case() -> TestCase<impl Receiver, impl Transmitter> {
- let dir = TempDir::new("btfsd").unwrap();
- let harness = TpmCredStoreHarness::new(ROOT_PASSWD.to_owned()).unwrap();
- test_case(dir, harness, LOCALHOST).await
- }
- async fn existing_case<R: Receiver, T: Transmitter>(
- case: TestCase<R, T>,
- ) -> TestCase<impl Receiver, impl Transmitter> {
- case.rx.stop().await.unwrap();
- case.rx.complete().unwrap().await.unwrap();
- let TestCase {
- _dir,
- harness: _harness,
- ..
- } = case;
- test_case(_dir, _harness, IpAddr::V4(Ipv4Addr::LOCALHOST)).await
- }
- #[allow(dead_code)]
- async fn manual_test() {
- let case = new_case().await;
- case.rx.complete().unwrap().await.unwrap();
- }
- #[tokio::test]
- async fn create_write_read() {
- const FILENAME: &str = "file.txt";
- const EXPECTED: &[u8] = b"potato";
- 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();
- 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 read_from_different_instance() {
- const FILENAME: &str = "file.txt";
- const EXPECTED: &[u8] = b"potato";
- 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();
- 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_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_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_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_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_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_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_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_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_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_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_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: IpAddr::V6(Ipv6Addr::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_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: IpAddr::V6(Ipv6Addr::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_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: IpAddr::V6(Ipv6Addr::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();
- // 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);
- }
- }
|