This is an automated email from the ASF dual-hosted git repository.
mssun pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git
The following commit(s) were added to refs/heads/develop by this push:
new 06a9410 [services] Add access control service (#216)
06a9410 is described below
commit 06a9410b382c3c5ed3985ab723c4513a168e4bd3
Author: TX <[email protected]>
AuthorDate: Tue Feb 4 15:12:48 2020 -0800
[services] Add access control service (#216)
---
CMakeLists.txt | 39 +-
cmake/scripts/sgx_link_sign.sh | 2 +-
cmake/scripts/test.sh | 1 +
cmake/tomls/Cargo.sgx_trusted_lib.toml | 1 +
cmake/tomls/Cargo.sgx_untrusted_app.toml | 1 +
config/runtime.config.toml | 1 +
config/src/runtime.rs | 1 +
services/access_control/app/Cargo.toml | 19 +
services/access_control/app/build.rs | 51 ++
services/access_control/app/src/main.rs | 48 ++
services/access_control/enclave/Cargo.toml | 48 ++
services/access_control/enclave/Enclave.config.xml | 12 +
services/access_control/enclave/src/acs.rs | 341 +++++++++++
services/access_control/enclave/src/lib.rs | 112 ++++
services/access_control/enclave/src/service.rs | 350 ++++++++++++
services/access_control/model.conf | 28 +
services/access_control/python/acs_engine.py | 631 +++++++++++++++++++++
services/access_control/python/acs_engine_test.py | 82 +++
services/access_control/python/ffi.py | 15 +
services/proto/build.rs | 1 +
services/proto/src/lib.rs | 5 +
.../proto/teaclave_access_control_service.proto | 48 ++
.../proto/src/teaclave_access_control_service.rs | 219 +++++++
tests/fixtures/runtime.config.toml | 1 +
tests/functional_tests/enclave/src/lib.rs | 2 +
.../enclave/src/teaclave_access_control_service.rs | 222 ++++++++
tests/unit_tests/enclave/Cargo.toml | 3 +
tests/unit_tests/enclave/src/lib.rs | 4 +-
28 files changed, 2266 insertions(+), 22 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81bc9f3..1f4e722 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -92,24 +92,24 @@ foreach(_i RANGE ${SGX_APP_LAST_INDEX})
endforeach()
# mesapy components
-# add_custom_command(
-# OUTPUT ${MESATEE_OUT_DIR}/acs_py_enclave.c
-# COMMAND env
-# ARGS PYTHONPATH=${PROJECT_SOURCE_DIR}/third_party/mesapy/sgx
-# PYPY_FFI_OUTDIR=${MESATEE_OUT_DIR}
-# pypy ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/ffi.py
-# DEPENDS prep
-# ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/ffi.py
-# ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/acs_engine.py
-# COMMENT "Generating mesapy ffi stubs"
-# )
-# add_library(pycomponent STATIC ${MESATEE_OUT_DIR}/acs_py_enclave.c)
-# set_target_properties(pycomponent PROPERTIES ARCHIVE_OUTPUT_DIRECTORY
${MESATEE_OUT_DIR})
-# target_compile_definitions(pycomponent PUBLIC SGX)
-# if(NOT EXISTS "/usr/lib/pypy/include")
-# message(FATAL_ERROR "pypy development package not found\nFor Ubuntu,
please run `apt-get install pypy-dev`")
-# endif()
-# target_compile_options(pycomponent PUBLIC -UWITH_THREAD -O2 -fPIC -Wimplicit
-I/usr/lib/pypy/include)
+add_custom_command(
+ OUTPUT ${MESATEE_OUT_DIR}/acs_py_enclave.c
+ COMMAND env
+ ARGS PYTHONPATH=${PROJECT_SOURCE_DIR}/third_party/mesapy/sgx
+ PYPY_FFI_OUTDIR=${MESATEE_OUT_DIR}
+ pypy ${PROJECT_SOURCE_DIR}/services/access_control/python/ffi.py
+ DEPENDS prep
+ ${PROJECT_SOURCE_DIR}/services/access_control/python/ffi.py
+ ${PROJECT_SOURCE_DIR}/services/access_control/python/acs_engine.py
+ COMMENT "Generating mesapy ffi stubs"
+)
+add_library(pycomponent STATIC ${MESATEE_OUT_DIR}/acs_py_enclave.c)
+set_target_properties(pycomponent PROPERTIES ARCHIVE_OUTPUT_DIRECTORY
${MESATEE_OUT_DIR})
+target_compile_definitions(pycomponent PUBLIC SGX)
+if(NOT EXISTS "/usr/lib/pypy/include")
+ message(FATAL_ERROR "pypy development package not found\nFor Ubuntu,
please run `apt-get install pypy-dev`")
+endif()
+target_compile_options(pycomponent PUBLIC -UWITH_THREAD -O2 -fPIC -Wimplicit
-I/usr/lib/pypy/include)
# sgx_trusted_lib
@@ -120,8 +120,7 @@ foreach(_i RANGE ${SGX_LIB_LAST_INDEX})
list(GET SGX_LIB_PATHS ${_i} _pkg_path)
list(GET SGX_LIB_CATEGORIES ${_i} _category)
add_sgx_build_target(${_pkg_path} ${_pkg_name}
- # TODO: DEPENDS prep pycomponent
- DEPENDS prep
+ DEPENDS prep pycomponent
INSTALL_DIR ${MESATEE_INSTALL_DIR}/${_category}
)
endforeach()
diff --git a/cmake/scripts/sgx_link_sign.sh b/cmake/scripts/sgx_link_sign.sh
index 84a6b45..4432239 100755
--- a/cmake/scripts/sgx_link_sign.sh
+++ b/cmake/scripts/sgx_link_sign.sh
@@ -28,7 +28,7 @@ ${CMAKE_C_COMPILER} libEnclave_t.o -o \
-Wl,--no-whole-archive -Wl,--start-group \
-l${Service_Library_Name} -lsgx_tprotected_fs -lsgx_tkey_exchange \
-lsgx_tstdc -lsgx_tcxx -lsgx_tservice -lsgx_tcrypto \
- -L${MESATEE_OUT_DIR} ffi.o -lpypy-c -lsgx_tlibc_ext -lffi \
+ -L${MESATEE_OUT_DIR} -lpycomponent ffi.o -lpypy-c -lsgx_tlibc_ext -lffi \
-L${TRUSTED_TARGET_DIR}/${TARGET} -l${CUR_PKG_NAME} -Wl,--end-group \
-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \
-Wl,-pie,-eenclave_entry -Wl,--export-dynamic \
diff --git a/cmake/scripts/test.sh b/cmake/scripts/test.sh
index 3303053..b3fad6e 100755
--- a/cmake/scripts/test.sh
+++ b/cmake/scripts/test.sh
@@ -37,6 +37,7 @@ cargo test --manifest-path
${MESATEE_PROJECT_ROOT}/common/protected_fs_rs/Cargo.
echo_title "functional tests"
trap 'kill $(jobs -p)' EXIT
pushd ${MESATEE_SERVICE_INSTALL_DIR}
+./teaclave_access_control_service &
./teaclave_authentication_service &
sleep 3 # wait for authentication service
./teaclave_storage_service &
diff --git a/cmake/tomls/Cargo.sgx_trusted_lib.toml
b/cmake/tomls/Cargo.sgx_trusted_lib.toml
index f8f805d..33be7e8 100644
--- a/cmake/tomls/Cargo.sgx_trusted_lib.toml
+++ b/cmake/tomls/Cargo.sgx_trusted_lib.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
+ "services/access_control/enclave",
"services/authentication/enclave",
"services/storage/enclave",
"services/execution/enclave",
diff --git a/cmake/tomls/Cargo.sgx_untrusted_app.toml
b/cmake/tomls/Cargo.sgx_untrusted_app.toml
index f7517a9..ab4268c 100644
--- a/cmake/tomls/Cargo.sgx_untrusted_app.toml
+++ b/cmake/tomls/Cargo.sgx_untrusted_app.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
+ "services/access_control/app",
"services/authentication/app",
"services/storage/app",
"services/execution/app",
diff --git a/config/runtime.config.toml b/config/runtime.config.toml
index 3de5dda..b34cce5 100644
--- a/config/runtime.config.toml
+++ b/config/runtime.config.toml
@@ -9,6 +9,7 @@ authentication = { listen_address = "0.0.0.0:7776" }
frontend = { listen_address = "0.0.0.0:7777" }
[internal_endpoints]
+access_control = { listen_address = "0.0.0.0:7779", advertised_address =
"localhost:7779" }
authentication = { listen_address = "0.0.0.0:17776", advertised_address =
"localhost:17776", inbound_services = ["frontend"] }
management = { listen_address = "0.0.0.0:17777", advertised_address =
"localhost:17777" }
storage = { listen_address = "0.0.0.0:7778", advertised_address =
"127.0.0.1:7778", inbound_services = ["frontend"] }
diff --git a/config/src/runtime.rs b/config/src/runtime.rs
index 04cf6b0..8c6ba6c 100644
--- a/config/src/runtime.rs
+++ b/config/src/runtime.rs
@@ -31,6 +31,7 @@ pub struct ApiEndpointsConfig {
#[derive(Debug, Serialize, Deserialize)]
pub struct InternalEndpointsConfig {
+ pub access_control: InternalEndpoint,
pub authentication: InternalEndpoint,
pub management: InternalEndpoint,
pub storage: InternalEndpoint,
diff --git a/services/access_control/app/Cargo.toml
b/services/access_control/app/Cargo.toml
new file mode 100644
index 0000000..c3df477
--- /dev/null
+++ b/services/access_control/app/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "teaclave_access_control_service"
+version = "0.1.0"
+authors = ["Teaclave Contributors <[email protected]>"]
+description = "Teaclave Access Control Service"
+license = "Apache-2.0"
+build = "build.rs"
+edition = "2018"
+
+[dependencies]
+log = { version = "0.4.6" }
+env_logger = { version = "0.7.1" }
+anyhow = { version = "1.0.26" }
+
+teaclave_ipc = { path = "../../../ipc" }
+teaclave_binder = { path = "../../../binder" }
+teaclave_config = { path = "../../../config" }
+
+sgx_types = { version = "1.1.0" }
diff --git a/services/access_control/app/build.rs
b/services/access_control/app/build.rs
new file mode 100644
index 0000000..b92afb5
--- /dev/null
+++ b/services/access_control/app/build.rs
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::env;
+use std::path::PathBuf;
+
+fn choose_sgx_dylib(is_sim: bool) {
+ if is_sim {
+ println!("cargo:rustc-link-lib=dylib=sgx_urts_sim");
+ println!("cargo:rustc-link-lib=dylib=sgx_uae_service_sim");
+ } else {
+ println!("cargo:rustc-link-lib=dylib=sgx_urts");
+ println!("cargo:rustc-link-lib=dylib=sgx_uae_service");
+ }
+}
+
+fn main() {
+ let sdk_dir = env::var("SGX_SDK").unwrap_or("/opt/intel/sgxsdk".into());
+ println!("cargo:rustc-link-search=native={}/lib64", sdk_dir);
+
+ let out_path = env::var_os("ENCLAVE_OUT_DIR").unwrap_or("out".into());
+ let out_dir = &PathBuf::from(out_path);
+
+ println!("cargo:rustc-link-search=native={}", out_dir.display());
+ println!("cargo:rustc-link-lib=static=Enclave_u");
+
+ let is_sim = match env::var("SGX_MODE") {
+ Ok(ref v) if v == "SW" => true,
+ Ok(ref v) if v == "HW" => false,
+ Err(env::VarError::NotPresent) => false,
+ _ => {
+ panic!("Stop build process, wrong SGX_MODE env provided.");
+ }
+ };
+
+ choose_sgx_dylib(is_sim);
+}
diff --git a/services/access_control/app/src/main.rs
b/services/access_control/app/src/main.rs
new file mode 100644
index 0000000..5e16411
--- /dev/null
+++ b/services/access_control/app/src/main.rs
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate log;
+
+use teaclave_ipc::proto::{ECallCommand, StartServiceInput, StartServiceOutput};
+
+use anyhow::Result;
+use teaclave_binder::TeeBinder;
+
+fn main() -> Result<()> {
+ env_logger::init();
+ let tee = TeeBinder::new(env!("CARGO_PKG_NAME"), 1)?;
+ run(&tee)?;
+
+ Ok(())
+}
+
+fn start_enclave_service(tee: &TeeBinder) -> Result<()> {
+ info!("Start enclave service");
+ let config =
teaclave_config::RuntimeConfig::from_toml("runtime.config.toml")?;
+ let input = StartServiceInput { config };
+ let cmd = ECallCommand::StartService;
+ let _ = tee.invoke::<StartServiceInput, StartServiceOutput>(cmd.into(),
input);
+
+ Ok(())
+}
+
+fn run(tee: &TeeBinder) -> Result<()> {
+ start_enclave_service(tee)?;
+
+ Ok(())
+}
diff --git a/services/access_control/enclave/Cargo.toml
b/services/access_control/enclave/Cargo.toml
new file mode 100644
index 0000000..359c862
--- /dev/null
+++ b/services/access_control/enclave/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "teaclave_access_control_service_enclave"
+version = "0.1.0"
+authors = ["Teaclave Contributors <[email protected]>"]
+description = "Teaclave Access Control Service enclave"
+license = "Apache-2.0"
+edition = "2018"
+
+[lib]
+name = "teaclave_access_control_service_enclave"
+crate-type = ["staticlib", "rlib"]
+
+[features]
+default = []
+mesalock_sgx = [
+ "sgx_tstd",
+ "teaclave_attestation/mesalock_sgx",
+ "teaclave_proto/mesalock_sgx",
+ "teaclave_ipc/mesalock_sgx",
+ "teaclave_rpc/mesalock_sgx",
+ "teaclave_service_enclave_utils/mesalock_sgx",
+ "teaclave_types/mesalock_sgx",
+ "teaclave_config/mesalock_sgx",
+]
+cov = ["teaclave_service_enclave_utils/cov"]
+enclave_unit_test = ["teaclave_ipc/enclave_unit_test",
"teaclave_test_utils/mesalock_sgx"]
+
+[dependencies]
+anyhow = { version = "1.0.26" }
+cfg-if = { version = "0.1.9" }
+log = { version = "0.4.6" }
+serde = { version = "1.0.92" }
+serde_json = { version = "1.0.39" }
+thiserror = { version = "1.0.9" }
+ring = { version = "0.16.5" }
+rand = { version = "0.7.0" }
+
+teaclave_attestation = { path = "../../../attestation" }
+teaclave_config = { path = "../../../config" }
+teaclave_proto = { path = "../../proto" }
+teaclave_ipc = { path = "../../../ipc" }
+teaclave_rpc = { path = "../../../rpc" }
+teaclave_service_enclave_utils = { path =
"../../../utils/service_enclave_utils" }
+teaclave_types = { path = "../../../types" }
+teaclave_test_utils = { path = "../../../tests/test_utils" }
+
+sgx_tstd = { version = "1.1.0", features = ["net", "thread",
"backtrace"], optional = true }
+sgx_types = { version = "1.1.0" }
diff --git a/services/access_control/enclave/Enclave.config.xml
b/services/access_control/enclave/Enclave.config.xml
new file mode 100644
index 0000000..705edcd
--- /dev/null
+++ b/services/access_control/enclave/Enclave.config.xml
@@ -0,0 +1,12 @@
+<!-- Please refer to User's Guide for the explanation of each field -->
+<EnclaveConfiguration>
+ <ProdID>0</ProdID>
+ <ISVSVN>0</ISVSVN>
+ <StackMaxSize>0x200000</StackMaxSize>
+ <HeapMaxSize>0x1000000</HeapMaxSize>
+ <TCSNum>22</TCSNum>
+ <TCSPolicy>0</TCSPolicy>
+ <DisableDebug>0</DisableDebug>
+ <MiscSelect>0</MiscSelect>
+ <MiscMask>0xFFFFFFFF</MiscMask>
+</EnclaveConfiguration>
diff --git a/services/access_control/enclave/src/acs.rs
b/services/access_control/enclave/src/acs.rs
new file mode 100644
index 0000000..7f8f753
--- /dev/null
+++ b/services/access_control/enclave/src/acs.rs
@@ -0,0 +1,341 @@
+use anyhow::{anyhow, Result};
+use cfg_if::cfg_if;
+use std::collections::HashSet;
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::prelude::v1::*;
+use std::sync::Arc;
+cfg_if! {
+ if #[cfg(feature = "mesalock_sgx")] {
+ use std::sync::SgxMutex as Mutex;
+ } else {
+ use std::sync::Mutex;
+ }
+}
+
+const MODEL_TEXT: &str = include_str!("../../model.conf");
+extern "C" {
+ fn acs_setup_model(model_text: *const c_char) -> i32;
+ fn acs_enforce_request(request_type: *const c_char, request_content:
*const c_char) -> i32;
+}
+#[cfg(test_mode)]
+extern "C" {
+ fn acs_announce_fact(fact_type: *const c_char, fact_vals: *const c_char)
-> i32;
+}
+
+pub(crate) enum EnforceRequest {
+ // user_access_data = usr, data
+ UserAccessData(String, String),
+ // user_access_function = usr, function
+ UserAccessFunction(String, String),
+ // user_access_task= usr, task
+ UserAccessTask(String, String),
+ // task_access_function = task, function
+ TaskAccessFunction(String, String),
+ // task_access_data = task, data
+ TaskAccessData(String, String),
+}
+
+#[cfg(test_mode)]
+pub(crate) enum AccessControlTerms {
+ // data_owner = data, usr
+ DataOwner(String, String),
+ // function_owner = functoin, usr
+ FunctionOwner(String, String),
+ // is_public_function = function
+ IsPublicFunction(String),
+ // task_participant = task, usr
+ TaskParticipant(String, String),
+}
+
+pub trait PyMarshallable {
+ fn marshal(&self, buffer: &mut String);
+}
+
+impl<T> PyMarshallable for (T,)
+where
+ T: PyMarshallable,
+{
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('[');
+ self.0.marshal(buffer);
+ buffer.push(']');
+ }
+}
+
+impl<U, V> PyMarshallable for (U, V)
+where
+ U: PyMarshallable,
+ V: PyMarshallable,
+{
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('[');
+ self.0.marshal(buffer);
+ buffer.push(',');
+ self.1.marshal(buffer);
+ buffer.push(']');
+ }
+}
+
+impl<X, Y, Z> PyMarshallable for (X, Y, Z)
+where
+ X: PyMarshallable,
+ Y: PyMarshallable,
+ Z: PyMarshallable,
+{
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('[');
+ self.0.marshal(buffer);
+ buffer.push(',');
+ self.1.marshal(buffer);
+ buffer.push(',');
+ self.2.marshal(buffer);
+ buffer.push(']');
+ }
+}
+
+impl<T> PyMarshallable for [T]
+where
+ T: PyMarshallable,
+{
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('[');
+ for t in self {
+ t.marshal(buffer);
+ buffer.push(',');
+ }
+ buffer.push(']');
+ }
+}
+
+impl<T: PyMarshallable> PyMarshallable for &HashSet<T> {
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push_str("set([");
+ for t in *self {
+ t.marshal(buffer);
+ buffer.push(',');
+ }
+ buffer.push_str("])");
+ }
+}
+
+impl PyMarshallable for String {
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('\'');
+ buffer.push_str(self);
+ buffer.push('\'');
+ }
+}
+
+impl PyMarshallable for &String {
+ fn marshal(&self, buffer: &mut String) {
+ buffer.push('\'');
+ buffer.push_str(self);
+ buffer.push('\'');
+ }
+}
+
+#[cfg(test_mode)]
+pub(crate) fn init_mock_data() -> Result<()> {
+ // mock data for AuthorizeData
+ let term = AccessControlTerms::DataOwner("mock_data".to_string(),
"mock_user_a".to_string());
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner("mock_data".to_string(),
"mock_user_b".to_string());
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner("mock_data".to_string(),
"mock_user_c".to_string());
+ announce_fact(term)?;
+
+ // mock data for AuthorizeFunction
+ let term = AccessControlTerms::FunctionOwner(
+ "mock_private_function".to_string(),
+ "mock_private_function_owner".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::FunctionOwner(
+ "mock_public_function".to_string(),
+ "mock_public_function_owner".to_string(),
+ );
+ announce_fact(term)?;
+ let term =
AccessControlTerms::IsPublicFunction("mock_public_function".to_string());
+ announce_fact(term)?;
+
+ // mock data for AuthorizeTask
+ let term = AccessControlTerms::TaskParticipant(
+ "mock_task".to_string(),
+ "mock_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::TaskParticipant(
+ "mock_task".to_string(),
+ "mock_participant_b".to_string(),
+ );
+ announce_fact(term)?;
+
+ // mock data for AuthorizeStagedTask
+ let term = AccessControlTerms::TaskParticipant(
+ "mock_staged_task".to_string(),
+ "mock_staged_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::TaskParticipant(
+ "mock_staged_task".to_string(),
+ "mock_staged_participant_b".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::FunctionOwner(
+ "mock_staged_allowed_private_function".to_string(),
+ "mock_staged_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::FunctionOwner(
+ "mock_staged_disallowed_private_function".to_string(),
+ "mock_staged_non_participant".to_string(),
+ );
+ announce_fact(term)?;
+ let term =
AccessControlTerms::IsPublicFunction("mock_staged_public_function".to_string());
+ announce_fact(term)?;
+
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_allowed_data1".to_string(),
+ "mock_staged_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_allowed_data2".to_string(),
+ "mock_staged_participant_b".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_allowed_data3".to_string(),
+ "mock_staged_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_allowed_data3".to_string(),
+ "mock_staged_participant_b".to_string(),
+ );
+ announce_fact(term)?;
+
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_disallowed_data1".to_string(),
+ "mock_staged_non_participant".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_disallowed_data2".to_string(),
+ "mock_staged_participant_a".to_string(),
+ );
+ announce_fact(term)?;
+ let term = AccessControlTerms::DataOwner(
+ "mock_staged_disallowed_data2".to_string(),
+ "mock_staged_non_participant".to_string(),
+ );
+ announce_fact(term)?;
+
+ Ok(())
+}
+
+#[derive(Clone)]
+pub(crate) struct AccessControlModule {
+ lock: Arc<Mutex<u32>>,
+}
+
+impl AccessControlModule {
+ pub(crate) fn new() -> Self {
+ AccessControlModule {
+ lock: Arc::new(Mutex::new(0)),
+ }
+ }
+
+ pub(crate) fn enforce_request(&self, request: EnforceRequest) ->
Result<bool> {
+ let (request_type, request_content) = match request {
+ EnforceRequest::UserAccessData(usr, data) => {
+ let mut buffer = String::new();
+ (usr, data).marshal(&mut buffer);
+ ("user_access_data", buffer)
+ }
+ EnforceRequest::UserAccessFunction(usr, function) => {
+ let mut buffer = String::new();
+ (usr, function).marshal(&mut buffer);
+ ("user_access_function", buffer)
+ }
+ EnforceRequest::UserAccessTask(usr, task) => {
+ let mut buffer = String::new();
+ (usr, task).marshal(&mut buffer);
+ ("user_access_task", buffer)
+ }
+ EnforceRequest::TaskAccessFunction(task, function) => {
+ let mut buffer = String::new();
+ (task, function).marshal(&mut buffer);
+ ("task_access_function", buffer)
+ }
+ EnforceRequest::TaskAccessData(task, data) => {
+ let mut buffer = String::new();
+ (task, data).marshal(&mut buffer);
+ ("task_access_data", buffer)
+ }
+ };
+
+ let c_request_type = CString::new(request_type.to_string())?;
+ let c_request_content = CString::new(request_content)?;
+ let _lock = self
+ .lock
+ .lock()
+ .map_err(|_| anyhow!("failed to accquire lock"))?;
+ let py_ret =
+ unsafe { acs_enforce_request(c_request_type.as_ptr(),
c_request_content.as_ptr()) };
+
+ match py_ret {
+ 0 => Ok(false),
+ 1 => Ok(true),
+ _ => Err(anyhow!("mesapy error")),
+ }
+ }
+}
+pub(crate) fn init_acs() -> Result<()> {
+ let ec = unsafe {
acs_setup_model(CString::new(MODEL_TEXT).unwrap().as_ptr()) };
+
+ if ec != 0 {
+ Err(anyhow!("failed to init mesapy"))
+ } else {
+ #[cfg(test_mode)]
+ init_mock_data()?;
+ Ok(())
+ }
+}
+
+#[cfg(test_mode)]
+fn announce_fact(term: AccessControlTerms) -> Result<()> {
+ let (term_type, term_fact) = match term {
+ AccessControlTerms::DataOwner(data, usr) => {
+ let mut buffer = String::new();
+ (data, usr).marshal(&mut buffer);
+ ("data_owner", buffer)
+ }
+ AccessControlTerms::FunctionOwner(function, usr) => {
+ let mut buffer = String::new();
+ (function, usr).marshal(&mut buffer);
+ ("function_owner", buffer)
+ }
+ AccessControlTerms::IsPublicFunction(function) => {
+ let mut buffer = String::new();
+ (function,).marshal(&mut buffer);
+ ("is_public_function", buffer)
+ }
+ AccessControlTerms::TaskParticipant(task, usr) => {
+ let mut buffer = String::new();
+ (task, usr).marshal(&mut buffer);
+ ("task_participant", buffer)
+ }
+ };
+ let c_term_type = CString::new(term_type.to_string())?;
+ let c_term_fact = CString::new(term_fact)?;
+
+ let py_ret = unsafe { acs_announce_fact(c_term_type.as_ptr(),
c_term_fact.as_ptr()) };
+
+ if py_ret != 0 {
+ Err(anyhow!("mesapy error"))
+ } else {
+ Ok(())
+ }
+}
diff --git a/services/access_control/enclave/src/lib.rs
b/services/access_control/enclave/src/lib.rs
new file mode 100644
index 0000000..883a24b
--- /dev/null
+++ b/services/access_control/enclave/src/lib.rs
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![cfg_attr(feature = "mesalock_sgx", no_std)]
+#[cfg(feature = "mesalock_sgx")]
+#[macro_use]
+extern crate sgx_tstd as std;
+
+#[macro_use]
+extern crate log;
+
+use anyhow::Result;
+use std::prelude::v1::*;
+use teaclave_attestation::{AttestationConfig, RemoteAttestation};
+use teaclave_ipc::proto::{
+ ECallCommand, FinalizeEnclaveInput, FinalizeEnclaveOutput,
InitEnclaveInput, InitEnclaveOutput,
+ StartServiceInput, StartServiceOutput,
+};
+use teaclave_ipc::{handle_ecall, register_ecall_handler};
+use teaclave_proto::teaclave_access_control_service::{
+ TeaclaveAccessControlRequest, TeaclaveAccessControlResponse,
+};
+use teaclave_rpc::config::SgxTrustedTlsServerConfig;
+use teaclave_rpc::server::SgxTrustedTlsServer;
+use teaclave_service_enclave_utils::ServiceEnclave;
+
+mod acs;
+mod service;
+
+#[handle_ecall]
+fn handle_start_service(args: &StartServiceInput) ->
Result<StartServiceOutput> {
+ debug!("handle_start_service");
+ let listen_address =
args.config.internal_endpoints.access_control.listen_address;
+ let ias_config = args.config.ias.as_ref().unwrap();
+ let attestation =
RemoteAttestation::generate_and_endorse(&AttestationConfig::ias(
+ &ias_config.ias_key,
+ &ias_config.ias_spid,
+ ))
+ .unwrap();
+ let config = SgxTrustedTlsServerConfig::new_without_verifier(
+ &attestation.cert,
+ &attestation.private_key,
+ )
+ .unwrap();
+
+ acs::init_acs().unwrap();
+ let mut server = SgxTrustedTlsServer::<
+ TeaclaveAccessControlResponse,
+ TeaclaveAccessControlRequest,
+ >::new(listen_address, &config);
+ let service = service::TeaclaveAccessControlService::new();
+ match server.start(service) {
+ Ok(_) => (),
+ Err(e) => {
+ error!("Service exit, error: {}.", e);
+ }
+ }
+
+ Ok(StartServiceOutput::default())
+}
+
+#[handle_ecall]
+fn handle_init_enclave(_args: &InitEnclaveInput) -> Result<InitEnclaveOutput> {
+ ServiceEnclave::init(env!("CARGO_PKG_NAME"))?;
+ Ok(InitEnclaveOutput::default())
+}
+
+#[handle_ecall]
+fn handle_finalize_enclave(_args: &FinalizeEnclaveInput) ->
Result<FinalizeEnclaveOutput> {
+ ServiceEnclave::finalize()?;
+ Ok(FinalizeEnclaveOutput::default())
+}
+
+register_ecall_handler!(
+ type ECallCommand,
+ (ECallCommand::StartService, StartServiceInput, StartServiceOutput),
+ (ECallCommand::InitEnclave, InitEnclaveInput, InitEnclaveOutput),
+ (ECallCommand::FinalizeEnclave, FinalizeEnclaveInput,
FinalizeEnclaveOutput),
+);
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+ use super::*;
+ use teaclave_test_utils::*;
+
+ pub fn run_tests() -> bool {
+ if crate::acs::init_acs().is_err() {
+ return false;
+ }
+ run_tests!(
+ service::tests::user_access_data,
+ service::tests::user_access_function,
+ service::tests::user_access_task,
+ service::tests::task_access_function,
+ service::tests::task_access_data,
+ )
+ }
+}
diff --git a/services/access_control/enclave/src/service.rs
b/services/access_control/enclave/src/service.rs
new file mode 100644
index 0000000..9325a7a
--- /dev/null
+++ b/services/access_control/enclave/src/service.rs
@@ -0,0 +1,350 @@
+use crate::acs::{AccessControlModule, EnforceRequest};
+use std::prelude::v1::*;
+use teaclave_proto::teaclave_access_control_service::{
+ AuthorizeDataRequest, AuthorizeDataResponse, AuthorizeFunctionRequest,
+ AuthorizeFunctionResponse, AuthorizeStagedTaskRequest,
AuthorizeStagedTaskResponse,
+ AuthorizeTaskRequest, AuthorizeTaskResponse, TeaclaveAccessControl,
+};
+use teaclave_rpc::Request;
+use teaclave_service_enclave_utils::teaclave_service;
+use teaclave_types::{TeaclaveServiceResponseError,
TeaclaveServiceResponseResult};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+enum TeaclavAccessControlError {
+ #[error("access control error")]
+ AccessControlError,
+}
+
+impl From<TeaclavAccessControlError> for TeaclaveServiceResponseError {
+ fn from(error: TeaclavAccessControlError) -> Self {
+ TeaclaveServiceResponseError::RequestError(error.to_string())
+ }
+}
+
+#[teaclave_service(teaclave_access_control_service, TeaclaveAccessControl)]
+#[derive(Clone)]
+pub(crate) struct TeaclaveAccessControlService {
+ access_control_module: AccessControlModule,
+}
+
+impl TeaclaveAccessControlService {
+ pub(crate) fn new() -> Self {
+ TeaclaveAccessControlService {
+ access_control_module: AccessControlModule::new(),
+ }
+ }
+}
+impl TeaclaveAccessControl for TeaclaveAccessControlService {
+ fn authorize_data(
+ &self,
+ request: Request<AuthorizeDataRequest>,
+ ) -> TeaclaveServiceResponseResult<AuthorizeDataResponse> {
+ let request = request.message;
+ let request =
+ EnforceRequest::UserAccessData(request.subject_user_id,
request.object_data_id);
+ match self.access_control_module.enforce_request(request) {
+ Ok(accept) => {
+ let response = AuthorizeDataResponse { accept };
+ Ok(response)
+ }
+ Err(_) =>
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ }
+
+ fn authorize_function(
+ &self,
+ request: Request<AuthorizeFunctionRequest>,
+ ) -> TeaclaveServiceResponseResult<AuthorizeFunctionResponse> {
+ let request = request.message;
+ let request =
+ EnforceRequest::UserAccessFunction(request.subject_user_id,
request.object_function_id);
+ match self.access_control_module.enforce_request(request) {
+ Ok(accept) => {
+ let response = AuthorizeFunctionResponse { accept };
+ Ok(response)
+ }
+ Err(_) =>
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ }
+
+ fn authorize_task(
+ &self,
+ request: Request<AuthorizeTaskRequest>,
+ ) -> TeaclaveServiceResponseResult<AuthorizeTaskResponse> {
+ let request = request.message;
+ let request =
+ EnforceRequest::UserAccessTask(request.subject_user_id,
request.object_task_id);
+ match self.access_control_module.enforce_request(request) {
+ Ok(accept) => {
+ let response = AuthorizeTaskResponse { accept };
+ Ok(response)
+ }
+ Err(_) =>
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ }
+
+ fn authorize_staged_task(
+ &self,
+ request: Request<AuthorizeStagedTaskRequest>,
+ ) -> TeaclaveServiceResponseResult<AuthorizeStagedTaskResponse> {
+ let request = request.message;
+ let enforce_access_function_request =
EnforceRequest::TaskAccessFunction(
+ request.subject_task_id.clone(),
+ request.object_function_id,
+ );
+ match self
+ .access_control_module
+ .enforce_request(enforce_access_function_request)
+ {
+ Ok(accept) => {
+ if !accept {
+ return Ok(AuthorizeStagedTaskResponse { accept: false });
+ }
+ }
+ Err(_) => return
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ for object_data_id in request.object_input_data_id_list.iter() {
+ let enforce_access_data_request = EnforceRequest::TaskAccessData(
+ request.subject_task_id.clone(),
+ object_data_id.to_string(),
+ );
+ match self
+ .access_control_module
+ .enforce_request(enforce_access_data_request)
+ {
+ Ok(accept) => {
+ if !accept {
+ return Ok(AuthorizeStagedTaskResponse { accept: false
});
+ }
+ }
+ Err(_) => return
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ }
+ for object_data_id in request.object_output_data_id_list.iter() {
+ let enforce_access_data_request = EnforceRequest::TaskAccessData(
+ request.subject_task_id.clone(),
+ object_data_id.to_string(),
+ );
+ match self
+ .access_control_module
+ .enforce_request(enforce_access_data_request)
+ {
+ Ok(accept) => {
+ if !accept {
+ return Ok(AuthorizeStagedTaskResponse { accept: false
});
+ }
+ }
+ Err(_) => return
Err(TeaclavAccessControlError::AccessControlError.into()),
+ }
+ }
+ Ok(AuthorizeStagedTaskResponse { accept: true })
+ }
+}
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+ use super::*;
+
+ pub fn user_access_data() {
+ let service = TeaclaveAccessControlService::new();
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_a".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_data(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_b".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_data(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_c".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_data(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_d".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_data(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_a".to_string(),
+ object_data_id: "mock_data_b".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_data(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+ }
+
+ pub fn user_access_function() {
+ let service = TeaclaveAccessControlService::new();
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_public_function_owner".to_string(),
+ object_function_id: "mock_public_function".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_function(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_private_function_owner".to_string(),
+ object_function_id: "mock_private_function".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_function(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_private_function_owner".to_string(),
+ object_function_id: "mock_public_function".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_function(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_public_function_owner".to_string(),
+ object_function_id: "mock_private_function".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_function(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+ }
+
+ pub fn user_access_task() {
+ let service = TeaclaveAccessControlService::new();
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_a".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_task(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_b".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_task(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_c".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let request = Request::new(request);
+ let response = service.authorize_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+ }
+
+ pub fn task_access_function() {
+ let service = TeaclaveAccessControlService::new();
+ let mut request = get_correct_authorized_stage_task_req();
+ request.object_function_id =
"mock_staged_allowed_private_function".to_string();
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request.object_function_id = "mock_staged_public_function".to_string();
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request.object_function_id =
"mock_staged_disallowed_private_function".to_string();
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+ }
+
+ fn get_correct_authorized_stage_task_req() -> AuthorizeStagedTaskRequest {
+ AuthorizeStagedTaskRequest {
+ subject_task_id: "mock_staged_task".to_string(),
+ object_function_id:
"mock_staged_allowed_private_function".to_string(),
+ object_input_data_id_list: vec![
+ "mock_staged_allowed_data1".to_string(),
+ "mock_staged_allowed_data2".to_string(),
+ "mock_staged_allowed_data3".to_string(),
+ ],
+ object_output_data_id_list: vec![
+ "mock_staged_allowed_data1".to_string(),
+ "mock_staged_allowed_data2".to_string(),
+ "mock_staged_allowed_data3".to_string(),
+ ],
+ }
+ }
+ pub fn task_access_data() {
+ let service = TeaclaveAccessControlService::new();
+ let request = get_correct_authorized_stage_task_req();
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request
+ .object_input_data_id_list
+ .push("mock_staged_disallowed_data1".to_string());
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request
+ .object_input_data_id_list
+ .push("mock_staged_disallowed_data2".to_string());
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request
+ .object_output_data_id_list
+ .push("mock_staged_disallowed_data1".to_string());
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+
+ let mut request = get_correct_authorized_stage_task_req();
+ request
+ .object_output_data_id_list
+ .push("mock_staged_disallowed_data2".to_string());
+ let request = Request::new(request);
+ let response = service.authorize_staged_task(request);
+ assert!(response.is_ok());
+ assert!(!response.unwrap().accept);
+ }
+}
diff --git a/services/access_control/model.conf
b/services/access_control/model.conf
new file mode 100644
index 0000000..d05e8d1
--- /dev/null
+++ b/services/access_control/model.conf
@@ -0,0 +1,28 @@
+[requests]
+user_access_data = usr, data
+user_access_function = usr, function
+user_access_task = usr, task
+
+task_access_function = task, function
+task_access_data = task, data
+
+[terms]
+data_owner = data, usr
+function_owner = function, usr
+is_public_function = function
+task_participant = task, usr
+
+[matchers]
+user_access_data = data_owner(user_access_data.data, user_access_data.usr)
+
+user_access_function = \
+ is_public_function(user_access_function.function) or \
+ function_owner(user_access_function.function, user_access_function.usr)
+
+user_access_task = task_participant(user_access_task.task,
user_access_task.usr)
+
+task_access_function = \
+ is_public_function(task_access_function.function) or \
+ function_owner(task_access_function.function, _) <=
task_participant(task_access_function.task, _)
+
+task_access_data = data_owner(task_access_data.data, _) <=
task_participant(task_access_data.task, _)
\ No newline at end of file
diff --git a/services/access_control/python/acs_engine.py
b/services/access_control/python/acs_engine.py
new file mode 100644
index 0000000..4a12bf4
--- /dev/null
+++ b/services/access_control/python/acs_engine.py
@@ -0,0 +1,631 @@
+###############################################################################
+# Parser Combinators
+###############################################################################
+class Pair(tuple):
+ def __new__(cls, a, b):
+ return super(Pair, cls).__new__(cls, [a, b])
+
+class Either(object):
+ def __init__(self, left, right):
+ self.__left = left
+ self.__right = right
+
+ def left(self):
+ if not self.is_left():
+ raise ValueError('wrong extractor for either')
+ return self.__left
+
+ def right(self):
+ if not self.is_right():
+ raise ValueError('wrong extractor for either')
+ return self.__right
+
+ def is_right(self):
+ return False
+
+ def is_left(self):
+ return False
+
+ def get(self):
+ if self.is_right():
+ return self.right()
+ if self.is_left():
+ return self.left()
+ raise ValueError('incomplete Either object')
+
+ def __str__(self):
+ if self.is_left():
+ return 'Left(' + str(self.left()) + ')'
+ else:
+ return 'Right(' + str(self.right()) + ')'
+
+ def __repr__(self):
+ if self.is_left():
+ return 'Left(' + repr(self.left()) + ')'
+ else:
+ return 'Right(' + repr(self.right()) + ')'
+
+class Left(Either):
+ def __init__(self, payload):
+ super(Left, self).__init__(payload, None)
+
+ def is_left(self):
+ return True
+
+class Right(Either):
+ def __init__(self, payload):
+ super(Right, self).__init__(None, payload)
+
+ def is_right(self):
+ return True
+
+class Stream(object):
+ WHITESPACES = [' ', '\t', '\r']
+ def __init__(self, items, pos = 0):
+ self.__items = items
+ self.__pos = pos
+
+ def accept_strlit(self, string):
+ # Typically parsers want to skip white spaces except line breaks
+ # In the future this should be configurable
+ pos = self.__pos
+ l = len(self.__items)
+ while pos < l and self.__items[pos] in self.WHITESPACES:
+ pos += 1
+
+ match_pos = 0
+ l = len(string)
+ while match_pos < l and string[match_pos] in self.WHITESPACES:
+ match_pos += 1
+ if pos < match_pos:
+ raise ParsingError(self, 'expecting "{}"'.format(string))
+ if match_pos:
+ string = string[match_pos:]
+ if self.__items.startswith(string, pos):
+ return Stream(self.__items, pos + len(string))
+ raise ParsingError(self, 'expecting "{}"'.format(string))
+
+ def accept_matcher(self, matcher):
+ pos = self.__pos
+ l = len(self.__items)
+ while pos < l and self.__items[pos] in self.WHITESPACES:
+ pos += 1
+
+ res = matcher(self.__items, pos)
+ if res is None:
+ raise ParsingError(self, 'matcher for {}
failed'.format(matcher.__doc__))
+ obj, npos = res
+ return obj, Stream(self.__items, npos)
+
+ def end(self):
+ return self.__pos == len(self.__items)
+
+ def pos(self):
+ return self.__pos
+
+ def __repr__(self):
+ line_start = self.__items.rfind('\n', 0, self.__pos) + 1
+ line_end = self.__items.find('\n', self.__pos)
+ if line_end == -1:
+ line_end = self.__pos
+
+ if line_end - line_start > 80:
+ line_start = max(line_start, self.__pos - 40)
+ line_end = min(line_start + 80, len(self.__items))
+
+ return ''.join([
+ self.__items[line_start:line_end],
+ '\n',
+ ' ' * (self.__pos - line_start),
+ '^',
+ ' ' * (line_end - self.__pos),
+ '\nerror at character ',
+ str(self.__pos),
+ ])
+
+class State(object):
+ def __init__(self, stream, payload = None, success = True):
+ self.stream = stream
+ self.payload = payload
+ self.success = success
+
+ def __bool__(self):
+ return self.success
+
+ def __nonzero__(self):
+ return self.__bool__()
+
+ def fmap(self, f):
+ if self:
+ return State(self.stream, f(self.payload))
+ return self
+
+class ParsingError(Exception):
+ def __init__(self, stream, msg = ''):
+ super(ParsingError, self).__init__(msg)
+ self.stream = stream
+
+ def __repr__(self):
+ return repr(self.stream)
+
+class Parser(object):
+ def __init__(self):
+ pass
+
+ def __call__(self, stream):
+ raise NotImplementedError("pure abstract parser cannot be called")
+
+ def parse_from(self, stream):
+ n_state = self(stream)
+ if not n_state:
+ raise ParsingError(n_state.stream, n_state.payload)
+ elif not n_state.stream.end():
+ raise ParsingError(n_state.stream, 'trailing unparsable input')
+ return n_state
+
+ def fail(self, exception):
+ return State(exception.stream, str(exception), False)
+
+ def ignore(self):
+ return Ignore(self)
+
+ def __or__(self, p):
+ return Or(self, p)
+
+ def __add__(self, p):
+ if isinstance(self, Ignore) and isinstance(p, Ignore):
+ return Ignore(Concat(self, p))
+ else:
+ return Concat(self, p)
+
+ def __invert__(self):
+ return Rep(self)
+
+ def __neg__(self):
+ return Optional(self)
+
+ def __pow__(self, f):
+ return Apply(self, f)
+
+class Optional(Parser):
+ def __init__(self, opt):
+ super(Optional, self).__init__()
+ self.__opt = opt
+
+ def __call__(self, stream):
+ n_state = self.__opt(stream)
+ if n_state:
+ return n_state.fmap(lambda x: Left(x))
+ return State(stream, Right(None))
+
+class StrLiteral(Parser):
+ def __init__(self, string):
+ super(StrLiteral, self).__init__()
+ self.__string = string
+
+ def __call__(self, stream):
+ if stream.end():
+ return self.fail(ParsingError(
+ stream, 'insufficient input, expecting
{}'.format(self.__string))
+ )
+ try:
+ n_stream = stream.accept_strlit(self.__string)
+ except ParsingError as e:
+ return self.fail(e)
+
+ return State(n_stream, self.__string)
+
+class CustomMatcher(Parser):
+ def __init__(self, matcher):
+ super(CustomMatcher, self).__init__()
+ self.__matcher = matcher
+
+ def __call__(self, stream):
+ try:
+ res = stream.accept_matcher(self.__matcher)
+ except ParsingError as e:
+ return self.fail(e)
+
+ obj, n_stream = res
+ return State(n_stream, obj)
+
+class Concat(Parser):
+ def __init__(self, c1, c2):
+ super(Concat, self).__init__()
+ assert not isinstance(self, Ignore) or not isinstance(p, Ignore)
+ self.__first = c1
+ self.__second = c2
+
+ def __call__(self, stream):
+ n_state = self.__first(stream)
+ if not n_state:
+ return n_state
+ p1 = n_state.payload
+ n_state = self.__second(n_state.stream)
+ if not n_state:
+ return n_state
+ p2 = n_state.payload
+
+ if isinstance(self.__first, Ignore):
+ return State(n_state.stream, p2)
+ if isinstance(self.__second, Ignore):
+ return State(n_state.stream, p1)
+ # The construction of Concat ensures that at least
+ # one of this children is not Ignore
+ return State(n_state.stream, Pair(p1, p2))
+
+class Or(Parser):
+ def __init__(self, c1, c2):
+ super(Or, self).__init__()
+ self.__if = c1
+ self.__else = c2
+
+ def __call__(self, stream):
+ n_state = self.__if(stream)
+ if n_state:
+ return n_state.fmap(lambda x: Left(x))
+ n_state = self.__else(stream)
+ if n_state:
+ return n_state.fmap(lambda x: Right(x))
+ return n_state
+
+class Rep(Parser):
+ def __init__(self, c):
+ super(Rep, self).__init__()
+ self.__loop = c
+
+ def __call__(self, stream):
+ payload = []
+
+ n_state = self.__loop(stream)
+ if n_state:
+ payload.append(n_state.payload)
+ stream = n_state.stream
+ n_state = self(stream)
+ if n_state:
+ payload = payload + n_state.payload
+ stream = n_state.stream
+ return State(stream, payload)
+
+class Apply(Parser):
+ def __init__(self, base, f):
+ super(Apply, self).__init__()
+ self.__base = base
+ self.__trans = f
+
+ def __call__(self, stream):
+ return self.__base(stream).fmap(self.__trans)
+
+class Ignore(Parser):
+ def __init__(self, base):
+ super(Ignore, self).__init__()
+ self.__base = base
+
+ def __call__(self, stream):
+ return self.__base(stream)
+
+###############################################################################
+# Grammars for PERM model configuration
+###############################################################################
+from operator import or_, add
+
+def extract(nested_or):
+ while isinstance(nested_or, Either):
+ nested_or = nested_or.left() if nested_or.is_left() else
nested_or.right()
+ return nested_or
+
+def flatten(nested_concat):
+ res = []
+
+ def pre_order(pair, res):
+ if isinstance(pair, Pair):
+ pre_order(pair[0], res)
+ pre_order(pair[1], res)
+ else:
+ res.append(pair)
+
+ pre_order(nested_concat, res)
+ return res
+
+def one_of(parsers):
+ nested = reduce(or_, parsers)
+ return nested ** extract
+
+def join(sl):
+ return ''.join(sl)
+
+def rep_with_sep(to_rep, sep):
+ if not isinstance(sep, Ignore):
+ sep = sep.ignore()
+ r = to_rep + ~(sep + to_rep)
+ r = r ** (lambda x: [x[0]] + x[1])
+ return r
+
+ALPHA = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+DIGIT = set('0123456789')
+ALPHA_DIGIT = ALPHA | DIGIT
+
+Alpha = one_of(map(StrLiteral, ALPHA))
+Digit = one_of(map(StrLiteral, DIGIT))
+
+Equal, Comma, Dot = [StrLiteral(c).ignore() for c in ['=', ',', '.']]
+Underscore = StrLiteral('_')
+NewLine = (~ StrLiteral('\n')).ignore()
+
+def identifier_matcher(text, pos):
+ """identifier"""
+ end = len(text)
+ start = pos
+ if pos >= end:
+ return None
+ first = text[pos]
+ if first != '_' and first not in ALPHA:
+ return None
+ pos += 1
+ while pos < end:
+ char = text[pos]
+ if char == '_' or char in ALPHA_DIGIT:
+ pos += 1
+ else:
+ break
+ return text[start:pos], pos
+
+Identifier = CustomMatcher(identifier_matcher)
+
+IdTuple = rep_with_sep(Identifier, Comma)
+
+Definition = Identifier + Equal + IdTuple + NewLine
+
+Relation = Identifier + Equal + IdTuple + NewLine
+Relation = Relation ** (lambda x: (x[0], 1 + len(x[1][1])))
+
+def pyparser_matcher(text, pos):
+ """syntactically correct python code"""
+ line_end = text.find('\n', pos)
+ if line_end == -1:
+ return None
+ try:
+ c = compile(text[pos:line_end], '__abac_model__.py', 'eval')
+ except SyntaxError:
+ return None
+ return c, line_end
+
+PyExpr = CustomMatcher(pyparser_matcher)
+Matcher = Identifier + Equal + PyExpr + NewLine
+
+RequestDefHeader = StrLiteral('[requests]') + NewLine
+TermDefHeader = StrLiteral('[terms]') + NewLine
+MatchersHeader = StrLiteral('[matchers]') + NewLine
+
+RequestDefSec = RequestDefHeader.ignore() + ~Definition
+TermDefSec = TermDefHeader.ignore() + ~Definition
+MatchersSec = MatchersHeader.ignore() + ~Matcher
+
+ModelDef = (RequestDefSec + TermDefSec + MatchersSec) ** flatten
+
+def preprocess(conf):
+ # process escaped line breaks
+ conf = conf.replace('\\\n', '')
+ # remove comments
+ conf = '\n'.join(line.partition('#')[0] for line in conf.splitlines())
+ # remove redundant new lines
+ conf = conf.strip()
+
+ return conf + '\n'
+
+def parse_model(text):
+ text = preprocess(text)
+ raw_model = ModelDef.parse_from(Stream(text)).payload
+ return raw_model
+
+class InvalidModelDefinition(Exception):
+ def __init__(self, msg = ''):
+ super(InvalidModelDefinition, self).__init__(msg)
+
+ @staticmethod
+ def redundant_def(redefined_vars, g1, g2):
+ msg_parts = [
+ 'multiple definition(s) of identifiers(s)',
+ ', '.join(redfined_vars),
+ 'found in sections',
+ g1, g2
+ ]
+ return InvalidModelDefinition(''.join(msg_parts))
+
+ @staticmethod
+ def missing_matchers(missing_matchers):
+ msg = 'missing matcher(s) for request type(s): {}'
+ return InvalidModelDefinition(msg.format(', '.join(missing_matchers)))
+
+ @staticmethod
+ def unknown_requests(unknown_requests):
+ msg = 'matcher(s) defined for unknown request type(s): {}'
+ return InvalidModelDefinition(msg.format(', '.join(unknown_requests)))
+
+class Request(object):
+ def __init__(self, attrs, vals):
+ assert len(attrs) == len(vals)
+ self.__named_attrs = attrs
+ for attr, val in zip(attrs, vals):
+ setattr(self, attr, val)
+
+ def __repr__(self):
+ parts = ['Request {\n']
+ for attr in self.__named_attrs:
+ parts.append(' ')
+ parts.append(attr)
+ parts.append(': ')
+ parts.append(repr(getattr(self, attr)))
+ parts.append('\n')
+ parts.append('}\n')
+ return ''.join(parts)
+
+class QueryResult(object):
+ def __init__(self, generator):
+ self.__gen = generator
+
+ def __iter__(self):
+ return self.__gen
+
+ def __le__(self, iterable):
+ return set(self) <= set(iterable)
+
+ def __lt__(self, iterable):
+ return set(self) < set(iterable)
+
+ def __ge__(self, iterable):
+ return set(self) >= set(iterable)
+
+ def __gt__(self, iterable):
+ return set(self) > set(iterable)
+
+class Term(object):
+ PLACEHOLDER = object()
+ WILDCARD = None
+ def __init__(self, arity):
+ self.__arity = arity
+ self.__facts = set()
+
+ def add_facts(self, facts):
+ for fact in facts:
+ self.add_fact(fact)
+
+ def add_fact(self, fact):
+ assert len(fact) == self.__arity
+ if not isinstance(fact, tuple):
+ fact = tuple(fact)
+ self.__facts.add(fact)
+
+ def __call__(self, *args):
+ assert len(args) == self.__arity
+ # When all arguments are concrete, calling a term just returns boolean
results
+ # indicating whether the called tuple is part of the known facts
+ n_placeholders = sum(arg is Term.PLACEHOLDER for arg in args)
+ if not n_placeholders:
+ return any(all(a == b for a, b in zip(fact, args)) for fact in
self.__facts)
+ # If arguments contain one or more placeholders, calling a term is
more like a
+ # query. The call returns a generator that iterates all facts that
match with
+ # the pattern described by the arguments
+ def gen():
+ for fact in self.__facts:
+ rns = []
+ matched = True
+ for a, b in zip(fact, args):
+ if b is Term.PLACEHOLDER:
+ rns.append(a)
+ else:
+ if a != b:
+ matched = False
+ break
+ if matched:
+ if n_placeholders == 1:
+ yield rns[0]
+ else:
+ yield tuple(rns)
+ return QueryResult(gen())
+
+class Model(object):
+ def __init__(self, raw_model):
+ request_def, term_def, matchers = raw_model
+ self.__request_template = { r[0]:r[1] for r in request_def }
+ self.__term_template = { t[0]:t[1] for t in term_def }
+ self.__matchers = { m[0]:m[1] for m in matchers }
+
+ def_sections = zip(
+ ['[requests]', '[terms]'],
+ [self.__request_template, self.__term_template],
+ )
+
+ n_sec = len(def_sections)
+ for i in range(n_sec):
+ for j in range(i + 1, n_sec):
+ overlap = set(def_sections[i][1].keys()) &
set(def_sections[j][1].keys())
+ if overlap:
+ raise InvalidModelDefinition.redundant_def(
+ overalp, def_sections[i][0], def_sections[j][0]
+ )
+
+ missing_matchers = set(self.__request_template.keys()) -
set(self.__matchers.keys())
+ if missing_matchers:
+ raise InvalidModelDefinition.missing_matchers(missing_matchers)
+
+ unknown_requests = set(self.__matchers.keys()) -
set(self.__request_template.keys())
+ if unknown_requests:
+ raise InvalidModelDefinition.unknown_requests(unknown_requests)
+
+ self.__term_knowledge_base = {
+ term_name:Term(len(term_tpl)) for term_name, term_tpl in
self.__term_template.items()
+ }
+
+ def add_term_items(self, term_items):
+ for ti in term_items:
+ self.add_term_item(ti[0], ti[1:])
+
+ def add_term_item(self, term_name, fact):
+ term = self.__term_knowledge_base[term_name]
+ term.add_fact(fact)
+
+ def get_matcher_proxy(self, request_type, env):
+ def matcher_proxy():
+ return eval(self.__matchers[request_type], env)
+ return matcher_proxy
+
+ def enforce(self, request_type, request_content):
+ tpl = self.__request_template[request_type]
+ request = Request(tpl, request_content)
+
+ enforcer_env = {
+ request_type: request,
+ 'true': True, 'false': False, 'null': None,
+ '_': Term.PLACEHOLDER,
+ 'X': Term.WILDCARD,
+ }
+ enforcer_env.update(self.__term_knowledge_base)
+
+ return eval(self.__matchers[request_type], enforcer_env)
+
+global_perm_model = None
+
+if __name__ == '__builtin__':
+ from acs_py_enclave import ffi
+else:
+ class ffi:
+ @staticmethod
+ def def_extern():
+ return lambda x: x
+
+ @staticmethod
+ def string(s):
+ return s
+
[email protected]_extern()
+def acs_setup_model(conf):
+ try:
+ global global_perm_model
+ conf = ffi.string(conf)
+ global_perm_model = Model(parse_model(conf))
+ except:
+ return -1
+ return 0
+
[email protected]_extern()
+def acs_enforce_request(request_type, request_content):
+ try:
+ request_type = ffi.string(request_type)
+ # request_content is a list of ffi c strings which are syntactically
valid
+ # python primitive-type objects, including strings, integers, foating
point
+ # numbers, and lists/dictionaries of primitive-type objects
+ request_content = eval(ffi.string(request_content))
+ return global_perm_model.enforce(request_type, request_content)
+ except:
+ return -1
+
[email protected]_extern()
+def acs_announce_fact(term_type, term_fact):
+ try:
+ term_type = ffi.string(term_type)
+ term_fact = eval(ffi.string(term_fact))
+ global_perm_model.add_term_item(term_type, term_fact)
+ except:
+ return -1
+ return 0
diff --git a/services/access_control/python/acs_engine_test.py
b/services/access_control/python/acs_engine_test.py
new file mode 100644
index 0000000..b951d41
--- /dev/null
+++ b/services/access_control/python/acs_engine_test.py
@@ -0,0 +1,82 @@
+if __name__ == '__main__':
+ import sys
+ import os
+ from acs_engine import *
+
+ model_path = os.path.join(os.path.dirname(__file__), '../model.conf')
+ test_model = open(model_path).read()
+ acs_setup_model(test_model)
+
+ FUSION_TASK = "data_fusion"
+ FUSION_TASK_PARTY_1 = "usr_party1"
+ FUSION_TASK_DATA_1 = "data1"
+ FUSION_TASK_PARTY_2 = "usr_party2"
+ FUSION_TASK_DATA_2 = "data2"
+ FUSION_TASK_SCRIPT = "fusion_script"
+ FUSION_TASK_SCRIPT_WRITER = "usr_party3"
+ PUBLIC_SCRIPT = "public_script"
+ PUBLIC_SCRIPT_WRITER = "usr_party4"
+
+ IRRELEVANT_TASK = "task_irrelevant"
+ IRRELEVANT_PARTY = "usr_irrelevant"
+ IRRELEVANT_DATA = "data_irrelevant"
+
+ acs_announce_fact('task_creator', repr([FUSION_TASK, FUSION_TASK_PARTY_1]))
+ acs_announce_fact('task_participant', repr([FUSION_TASK,
FUSION_TASK_PARTY_1]))
+ acs_announce_fact('task_participant', repr([FUSION_TASK,
FUSION_TASK_PARTY_2]))
+
+ acs_announce_fact('data_owner', repr([FUSION_TASK_DATA_1,
FUSION_TASK_PARTY_1]))
+ acs_announce_fact('data_owner', repr([FUSION_TASK_DATA_2,
FUSION_TASK_PARTY_2]))
+ acs_announce_fact('data_owner', repr([IRRELEVANT_DATA, IRRELEVANT_PARTY]))
+
+ acs_announce_fact('script_owner', repr([FUSION_TASK_SCRIPT,
FUSION_TASK_SCRIPT_WRITER]))
+
+ acs_announce_fact('script_owner', repr([PUBLIC_SCRIPT,
PUBLIC_SCRIPT_WRITER]))
+ acs_announce_fact('is_public_script', repr([PUBLIC_SCRIPT]))
+
+
+ assert acs_enforce_request('launch_task', repr([FUSION_TASK,
set([FUSION_TASK_PARTY_1, FUSION_TASK_PARTY_2])]))
+ assert not acs_enforce_request('launch_task', repr([FUSION_TASK, set()]))
+ assert not acs_enforce_request('launch_task', repr([FUSION_TASK,
set([FUSION_TASK_PARTY_1])]))
+ assert not acs_enforce_request('launch_task', repr([FUSION_TASK,
set([FUSION_TASK_PARTY_2])]))
+
+ assert acs_enforce_request('access_data', repr([FUSION_TASK,
FUSION_TASK_DATA_1]))
+ assert acs_enforce_request('access_data', repr([FUSION_TASK,
FUSION_TASK_DATA_2]))
+ assert not acs_enforce_request('access_data', repr([FUSION_TASK,
IRRELEVANT_DATA]))
+
+ assert acs_enforce_request('access_script', repr([FUSION_TASK,
PUBLIC_SCRIPT]))
+ assert not acs_enforce_request('access_script', repr([FUSION_TASK,
FUSION_TASK_SCRIPT]))
+
+ acs_announce_fact('task_participant', repr([FUSION_TASK,
FUSION_TASK_SCRIPT_WRITER]))
+ assert acs_enforce_request('access_script', repr([FUSION_TASK,
FUSION_TASK_SCRIPT]))
+
+ acs_announce_fact('task_participant', repr([FUSION_TASK,
IRRELEVANT_PARTY]))
+ assert acs_enforce_request('access_script', repr([FUSION_TASK,
FUSION_TASK_SCRIPT]))
+
+ acs_announce_fact('task_creator', repr([IRRELEVANT_TASK,
IRRELEVANT_PARTY]))
+ acs_announce_fact('task_participant', repr([IRRELEVANT_TASK,
IRRELEVANT_PARTY]))
+ acs_announce_fact('task_participant', repr([IRRELEVANT_TASK,
FUSION_TASK_PARTY_2]))
+
+ assert acs_enforce_request('launch_task', repr([IRRELEVANT_TASK,
set([IRRELEVANT_PARTY, FUSION_TASK_PARTY_2])]))
+ assert not acs_enforce_request('access_data', repr([IRRELEVANT_TASK,
FUSION_TASK_DATA_1]))
+ assert acs_enforce_request('access_data', repr([IRRELEVANT_TASK,
FUSION_TASK_DATA_2]))
+ assert not acs_enforce_request('access_script', repr([IRRELEVANT_TASK,
FUSION_TASK_SCRIPT]))
+ assert acs_enforce_request('access_script', repr([IRRELEVANT_TASK,
PUBLIC_SCRIPT]))
+
+ assert acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1,
FUSION_TASK_DATA_1]))
+ assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1,
FUSION_TASK_DATA_2]))
+ assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1,
IRRELEVANT_DATA]))
+ assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_1,
FUSION_TASK_SCRIPT]))
+ assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_1,
PUBLIC_SCRIPT]))
+
+ assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2,
FUSION_TASK_DATA_1]))
+ assert acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2,
FUSION_TASK_DATA_2]))
+ assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2,
IRRELEVANT_DATA]))
+ assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_2,
FUSION_TASK_SCRIPT]))
+ assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_2,
PUBLIC_SCRIPT]))
+
+ assert not acs_enforce_request('delete_data',
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_DATA_1]))
+ assert not acs_enforce_request('delete_data',
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_DATA_2]))
+ assert not acs_enforce_request('delete_data',
repr([FUSION_TASK_SCRIPT_WRITER, IRRELEVANT_DATA]))
+ assert acs_enforce_request('delete_script',
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_SCRIPT]))
+ assert not acs_enforce_request('delete_script',
repr([FUSION_TASK_SCRIPT_WRITER, PUBLIC_SCRIPT]))
diff --git a/services/access_control/python/ffi.py
b/services/access_control/python/ffi.py
new file mode 100644
index 0000000..3f56be7
--- /dev/null
+++ b/services/access_control/python/ffi.py
@@ -0,0 +1,15 @@
+import os
+import sgx_cffi
+import _cffi_backend as backend
+
+ffi = sgx_cffi.FFI(backend)
+
+ffi.embedding_api("int acs_setup_model(const char *configuration);")
+ffi.embedding_api("""int acs_enforce_request(const char *request_type,
+ const char *request_content);""")
+ffi.embedding_api("""int acs_announce_fact(const char *term_type,
+ const char *term_fact);""")
+with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"acs_engine.py")) as f:
+ ffi.embedding_init_code(f.read())
+ffi.set_source('acs_py_enclave', '')
+ffi.emit_c_code(os.environ.get('PYPY_FFI_OUTDIR', ".") + "/acs_py_enclave.c")
diff --git a/services/proto/build.rs b/services/proto/build.rs
index 8f4b885..205fc4a 100644
--- a/services/proto/build.rs
+++ b/services/proto/build.rs
@@ -21,6 +21,7 @@ use std::str;
fn main() {
let proto_files = [
+ "src/proto/teaclave_access_control_service.proto",
"src/proto/teaclave_authentication_service.proto",
"src/proto/teaclave_common.proto",
"src/proto/teaclave_storage_service.proto",
diff --git a/services/proto/src/lib.rs b/services/proto/src/lib.rs
index c069174..6783c79 100644
--- a/services/proto/src/lib.rs
+++ b/services/proto/src/lib.rs
@@ -19,6 +19,7 @@
#[cfg(feature = "mesalock_sgx")]
extern crate sgx_tstd as std;
+pub mod teaclave_access_control_service;
pub mod teaclave_authentication_service;
pub mod teaclave_common;
pub mod teaclave_execution_service;
@@ -55,3 +56,7 @@ pub mod teaclave_frontend_service_proto {
pub mod teaclave_management_service_proto {
include_proto!("teaclave_management_service_proto");
}
+
+pub mod teaclave_access_control_service_proto {
+ include_proto!("teaclave_access_control_service_proto");
+}
diff --git a/services/proto/src/proto/teaclave_access_control_service.proto
b/services/proto/src/proto/teaclave_access_control_service.proto
new file mode 100644
index 0000000..af94428
--- /dev/null
+++ b/services/proto/src/proto/teaclave_access_control_service.proto
@@ -0,0 +1,48 @@
+syntax = "proto3";
+
+package teaclave_access_control_service_proto;
+
+message AuthorizeDataRequest {
+ string subject_user_id = 1;
+ string object_data_id = 2;
+}
+
+message AuthorizeDataResponse {
+ bool accept = 1;
+}
+
+message AuthorizeFunctionRequest {
+ string subject_user_id = 1;
+ string object_function_id = 2;
+}
+
+message AuthorizeFunctionResponse {
+ bool accept = 1;
+}
+
+message AuthorizeTaskRequest {
+ string subject_user_id = 1;
+ string object_task_id = 2;
+}
+
+message AuthorizeTaskResponse {
+ bool accept = 1;
+}
+
+message AuthorizeStagedTaskRequest {
+ string subject_task_id = 1;
+ string object_function_id = 2;
+ repeated string object_input_data_id_list = 3;
+ repeated string object_output_data_id_list = 4;
+}
+
+message AuthorizeStagedTaskResponse {
+ bool accept = 1;
+}
+
+service TeaclaveAccessControl {
+ rpc AuthorizeData (AuthorizeDataRequest) returns (AuthorizeDataResponse);
+ rpc AuthorizeFunction (AuthorizeFunctionRequest) returns
(AuthorizeFunctionResponse);
+ rpc AuthorizeTask (AuthorizeTaskRequest) returns (AuthorizeTaskResponse);
+ rpc AuthorizeStagedTask (AuthorizeStagedTaskRequest) returns
(AuthorizeStagedTaskResponse);
+}
diff --git a/services/proto/src/teaclave_access_control_service.rs
b/services/proto/src/teaclave_access_control_service.rs
new file mode 100644
index 0000000..2a51071
--- /dev/null
+++ b/services/proto/src/teaclave_access_control_service.rs
@@ -0,0 +1,219 @@
+use crate::teaclave_access_control_service_proto as proto;
+use anyhow::{Error, Result};
+use serde::{Deserialize, Serialize};
+use std::prelude::v1::*;
+
+pub use proto::TeaclaveAccessControl;
+pub use proto::TeaclaveAccessControlClient;
+pub use proto::TeaclaveAccessControlRequest;
+pub use proto::TeaclaveAccessControlResponse;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeDataRequest {
+ pub subject_user_id: String,
+ pub object_data_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeDataResponse {
+ pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeFunctionRequest {
+ pub subject_user_id: String,
+ pub object_function_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeFunctionResponse {
+ pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeTaskRequest {
+ pub subject_user_id: String,
+ pub object_task_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeTaskResponse {
+ pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeStagedTaskRequest {
+ pub subject_task_id: String,
+ pub object_function_id: String,
+ pub object_input_data_id_list: Vec<String>,
+ pub object_output_data_id_list: Vec<String>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeStagedTaskResponse {
+ pub accept: bool,
+}
+
+impl std::convert::TryFrom<proto::AuthorizeDataRequest> for
AuthorizeDataRequest {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeDataRequest) -> Result<Self> {
+ let ret = Self {
+ subject_user_id: proto.subject_user_id,
+ object_data_id: proto.object_data_id,
+ };
+
+ Ok(ret)
+ }
+}
+
+impl From<AuthorizeDataRequest> for proto::AuthorizeDataRequest {
+ fn from(request: AuthorizeDataRequest) -> Self {
+ Self {
+ subject_user_id: request.subject_user_id,
+ object_data_id: request.object_data_id,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeDataResponse> for
AuthorizeDataResponse {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeDataResponse) -> Result<Self> {
+ Ok(Self {
+ accept: proto.accept,
+ })
+ }
+}
+
+impl From<AuthorizeDataResponse> for proto::AuthorizeDataResponse {
+ fn from(response: AuthorizeDataResponse) -> Self {
+ Self {
+ accept: response.accept,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeFunctionRequest> for
AuthorizeFunctionRequest {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeFunctionRequest) -> Result<Self> {
+ let ret = Self {
+ subject_user_id: proto.subject_user_id,
+ object_function_id: proto.object_function_id,
+ };
+
+ Ok(ret)
+ }
+}
+
+impl From<AuthorizeFunctionRequest> for proto::AuthorizeFunctionRequest {
+ fn from(request: AuthorizeFunctionRequest) -> Self {
+ Self {
+ subject_user_id: request.subject_user_id,
+ object_function_id: request.object_function_id,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeFunctionResponse> for
AuthorizeFunctionResponse {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeFunctionResponse) -> Result<Self> {
+ Ok(Self {
+ accept: proto.accept,
+ })
+ }
+}
+
+impl From<AuthorizeFunctionResponse> for proto::AuthorizeFunctionResponse {
+ fn from(response: AuthorizeFunctionResponse) -> Self {
+ Self {
+ accept: response.accept,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeTaskRequest> for
AuthorizeTaskRequest {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeTaskRequest) -> Result<Self> {
+ let ret = Self {
+ subject_user_id: proto.subject_user_id,
+ object_task_id: proto.object_task_id,
+ };
+
+ Ok(ret)
+ }
+}
+
+impl From<AuthorizeTaskRequest> for proto::AuthorizeTaskRequest {
+ fn from(request: AuthorizeTaskRequest) -> Self {
+ Self {
+ subject_user_id: request.subject_user_id,
+ object_task_id: request.object_task_id,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeTaskResponse> for
AuthorizeTaskResponse {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeTaskResponse) -> Result<Self> {
+ Ok(Self {
+ accept: proto.accept,
+ })
+ }
+}
+
+impl From<AuthorizeTaskResponse> for proto::AuthorizeTaskResponse {
+ fn from(response: AuthorizeTaskResponse) -> Self {
+ Self {
+ accept: response.accept,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeStagedTaskRequest> for
AuthorizeStagedTaskRequest {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeStagedTaskRequest) -> Result<Self> {
+ let ret = Self {
+ subject_task_id: proto.subject_task_id,
+ object_function_id: proto.object_function_id,
+ object_input_data_id_list: proto.object_input_data_id_list,
+ object_output_data_id_list: proto.object_output_data_id_list,
+ };
+
+ Ok(ret)
+ }
+}
+
+impl From<AuthorizeStagedTaskRequest> for proto::AuthorizeStagedTaskRequest {
+ fn from(request: AuthorizeStagedTaskRequest) -> Self {
+ Self {
+ subject_task_id: request.subject_task_id,
+ object_function_id: request.object_function_id,
+ object_input_data_id_list: request.object_input_data_id_list,
+ object_output_data_id_list: request.object_output_data_id_list,
+ }
+ }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeStagedTaskResponse> for
AuthorizeStagedTaskResponse {
+ type Error = Error;
+
+ fn try_from(proto: proto::AuthorizeStagedTaskResponse) -> Result<Self> {
+ Ok(Self {
+ accept: proto.accept,
+ })
+ }
+}
+
+impl From<AuthorizeStagedTaskResponse> for proto::AuthorizeStagedTaskResponse {
+ fn from(response: AuthorizeStagedTaskResponse) -> Self {
+ Self {
+ accept: response.accept,
+ }
+ }
+}
diff --git a/tests/fixtures/runtime.config.toml
b/tests/fixtures/runtime.config.toml
index cd544c8..716b478 100644
--- a/tests/fixtures/runtime.config.toml
+++ b/tests/fixtures/runtime.config.toml
@@ -3,6 +3,7 @@ authentication = { listen_address = "0.0.0.0:7776" }
frontend = { listen_address = "0.0.0.0:7777" }
[internal_endpoints]
+access_control = { listen_address = "0.0.0.0:7779", advertised_address =
"localhost:7779" }
authentication = { listen_address = "0.0.0.0:17776", advertised_address =
"localhost:17776" }
management = { listen_address = "0.0.0.0:17777", advertised_address =
"localhost:17777" }
storage = { listen_address = "0.0.0.0:7778", advertised_address =
"127.0.0.1:7778", inbound_services = ["frontend"] }
diff --git a/tests/functional_tests/enclave/src/lib.rs
b/tests/functional_tests/enclave/src/lib.rs
index 9f3ad37..b4b3106 100644
--- a/tests/functional_tests/enclave/src/lib.rs
+++ b/tests/functional_tests/enclave/src/lib.rs
@@ -36,6 +36,7 @@ use teaclave_ipc::{handle_ecall, register_ecall_handler};
use teaclave_service_enclave_utils::ServiceEnclave;
use teaclave_test_utils::check_all_passed;
+mod teaclave_access_control_service;
mod teaclave_authentication_service;
mod teaclave_execution_service;
mod teaclave_frontend_service;
@@ -44,6 +45,7 @@ mod teaclave_storage_service;
#[handle_ecall]
fn handle_run_test(_args: &RunTestInput) -> Result<RunTestOutput> {
let ret = check_all_passed!(
+ teaclave_access_control_service::run_tests(),
teaclave_authentication_service::run_tests(),
teaclave_storage_service::run_tests(),
teaclave_execution_service::run_tests(),
diff --git
a/tests/functional_tests/enclave/src/teaclave_access_control_service.rs
b/tests/functional_tests/enclave/src/teaclave_access_control_service.rs
new file mode 100644
index 0000000..32ebb43
--- /dev/null
+++ b/tests/functional_tests/enclave/src/teaclave_access_control_service.rs
@@ -0,0 +1,222 @@
+use std::prelude::v1::*;
+use teaclave_attestation::verifier;
+use teaclave_config::RuntimeConfig;
+use teaclave_config::BUILD_CONFIG;
+use teaclave_proto::teaclave_access_control_service::*;
+use teaclave_rpc::config::SgxTrustedTlsClientConfig;
+use teaclave_rpc::endpoint::Endpoint;
+use teaclave_types::EnclaveInfo;
+
+pub fn run_tests() -> bool {
+ use teaclave_test_utils::*;
+
+ run_tests!(
+ test_authorize_data_success,
+ test_authorize_data_fail,
+ test_authorize_function_success,
+ test_authorize_function_fail,
+ test_authorize_task_success,
+ test_authorize_task_fail,
+ test_authorize_staged_task_success,
+ test_authorize_staged_task_fail,
+ test_concurrency,
+ )
+}
+
+fn get_client() -> TeaclaveAccessControlClient {
+ let runtime_config =
RuntimeConfig::from_toml("runtime.config.toml").expect("runtime");
+ let enclave_info =
+
EnclaveInfo::from_bytes(&runtime_config.audit.enclave_info_bytes.as_ref().unwrap());
+ let enclave_attr = enclave_info
+ .get_enclave_attr("teaclave_access_control_service")
+ .expect("access_control");
+ let config = SgxTrustedTlsClientConfig::new().attestation_report_verifier(
+ vec![enclave_attr],
+ BUILD_CONFIG.ias_root_ca_cert,
+ verifier::universal_quote_verifier,
+ );
+
+ let channel = Endpoint::new(
+ &runtime_config
+ .internal_endpoints
+ .access_control
+ .advertised_address,
+ )
+ .config(config)
+ .connect()
+ .unwrap();
+ TeaclaveAccessControlClient::new(channel).unwrap()
+}
+
+fn test_authorize_data_success() {
+ let mut client = get_client();
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_a".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let response_result = client.authorize_data(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_data_fail() {
+ let mut client = get_client();
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_d".to_string(),
+ object_data_id: "mock_data".to_string(),
+ };
+ let response_result = client.authorize_data(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+
+ let request = AuthorizeDataRequest {
+ subject_user_id: "mock_user_a".to_string(),
+ object_data_id: "mock_data_b".to_string(),
+ };
+ let response_result = client.authorize_data(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_function_success() {
+ let mut client = get_client();
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_public_function_owner".to_string(),
+ object_function_id: "mock_public_function".to_string(),
+ };
+ let response_result = client.authorize_function(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_private_function_owner".to_string(),
+ object_function_id: "mock_private_function".to_string(),
+ };
+ let response_result = client.authorize_function(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_private_function_owner".to_string(),
+ object_function_id: "mock_public_function".to_string(),
+ };
+ let response_result = client.authorize_function(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_function_fail() {
+ let mut client = get_client();
+ let request = AuthorizeFunctionRequest {
+ subject_user_id: "mock_public_function_owner".to_string(),
+ object_function_id: "mock_private_function".to_string(),
+ };
+ let response_result = client.authorize_function(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_task_success() {
+ let mut client = get_client();
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_a".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let response_result = client.authorize_task(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_b".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let response_result = client.authorize_task(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_task_fail() {
+ let mut client = get_client();
+ let request = AuthorizeTaskRequest {
+ subject_user_id: "mock_participant_c".to_string(),
+ object_task_id: "mock_task".to_string(),
+ };
+ let response_result = client.authorize_task(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_staged_task_success() {
+ let mut client = get_client();
+ let request = AuthorizeStagedTaskRequest {
+ subject_task_id: "mock_staged_task".to_string(),
+ object_function_id: "mock_staged_allowed_private_function".to_string(),
+ object_input_data_id_list: vec![
+ "mock_staged_allowed_data1".to_string(),
+ "mock_staged_allowed_data2".to_string(),
+ "mock_staged_allowed_data3".to_string(),
+ ],
+ object_output_data_id_list: vec![
+ "mock_staged_allowed_data1".to_string(),
+ "mock_staged_allowed_data2".to_string(),
+ "mock_staged_allowed_data3".to_string(),
+ ],
+ };
+ let response_result = client.authorize_staged_task(request);
+ assert!(response_result.is_ok());
+ assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_staged_task_fail() {
+ let mut client = get_client();
+ let request = AuthorizeStagedTaskRequest {
+ subject_task_id: "mock_staged_task".to_string(),
+ object_function_id:
"mock_staged_disallowed_private_function".to_string(),
+ object_input_data_id_list: vec![],
+ object_output_data_id_list: vec![],
+ };
+ let response_result = client.authorize_staged_task(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+
+ let request = AuthorizeStagedTaskRequest {
+ subject_task_id: "mock_staged_task".to_string(),
+ object_function_id: "mock_staged_allowed_private_function".to_string(),
+ object_input_data_id_list:
vec!["mock_staged_disallowed_data1".to_string()],
+ object_output_data_id_list: vec![],
+ };
+ let response_result = client.authorize_staged_task(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+
+ let request = AuthorizeStagedTaskRequest {
+ subject_task_id: "mock_staged_task".to_string(),
+ object_function_id: "mock_staged_allowed_private_function".to_string(),
+ object_input_data_id_list: vec![],
+ object_output_data_id_list:
vec!["mock_staged_disallowed_data2".to_string()],
+ };
+ let response_result = client.authorize_staged_task(request);
+ assert!(response_result.is_ok());
+ assert!(!response_result.unwrap().accept);
+}
+
+fn test_concurrency() {
+ let mut thread_pool = Vec::new();
+ for _i in 0..10 {
+ let child = std::thread::spawn(move || {
+ for _j in 0..10 {
+ test_authorize_data_fail();
+ test_authorize_function_fail();
+ test_authorize_task_success();
+ test_authorize_staged_task_fail();
+ }
+ });
+ thread_pool.push(child);
+ }
+ for thr in thread_pool.into_iter() {
+ assert!(thr.join().is_ok());
+ }
+}
diff --git a/tests/unit_tests/enclave/Cargo.toml
b/tests/unit_tests/enclave/Cargo.toml
index 1e30aab..9256c77 100644
--- a/tests/unit_tests/enclave/Cargo.toml
+++ b/tests/unit_tests/enclave/Cargo.toml
@@ -21,6 +21,8 @@ mesalock_sgx = [
"teaclave_types/mesalock_sgx",
"teaclave_types/enclave_unit_test",
"teaclave_config/mesalock_sgx",
+ "teaclave_access_control_service_enclave/mesalock_sgx",
+ "teaclave_access_control_service_enclave/enclave_unit_test",
"teaclave_authentication_service_enclave/mesalock_sgx",
"teaclave_authentication_service_enclave/enclave_unit_test",
"teaclave_storage_service_enclave/mesalock_sgx",
@@ -38,6 +40,7 @@ anyhow = { version = "1.0.26" }
serde = { version = "1.0.92" }
thiserror = { version = "1.0.9" }
+teaclave_access_control_service_enclave = { path =
"../../../services/access_control/enclave" }
teaclave_authentication_service_enclave = { path =
"../../../services/authentication/enclave" }
teaclave_storage_service_enclave = { path =
"../../../services/storage/enclave" }
teaclave_execution_service_enclave = { path =
"../../../services/execution/enclave" }
diff --git a/tests/unit_tests/enclave/src/lib.rs
b/tests/unit_tests/enclave/src/lib.rs
index 722bbcc..74c5fae 100644
--- a/tests/unit_tests/enclave/src/lib.rs
+++ b/tests/unit_tests/enclave/src/lib.rs
@@ -34,6 +34,7 @@ use teaclave_ipc::proto::{
use teaclave_ipc::{handle_ecall, register_ecall_handler};
use teaclave_service_enclave_utils::ServiceEnclave;
+use teaclave_access_control_service_enclave;
use teaclave_authentication_service_enclave;
use teaclave_execution_service_enclave;
use teaclave_test_utils::check_all_passed;
@@ -43,10 +44,11 @@ use teaclave_worker;
fn handle_run_test(_args: &RunTestInput) -> Result<RunTestOutput> {
let ret = check_all_passed!(
teaclave_storage_service_enclave::tests::run_tests(),
+ teaclave_access_control_service_enclave::tests::run_tests(),
teaclave_execution_service_enclave::tests::run_tests(),
teaclave_authentication_service_enclave::tests::run_tests(),
teaclave_worker::tests::run_tests(),
- teaclave_types::tests::run_tests()
+ teaclave_types::tests::run_tests(),
);
assert_eq!(ret, true);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]