You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesatee.apache.org by ur...@apache.org on 2019/11/20 02:39:20 UTC

[incubator-mesatee] 01/01: WIP: access control service

This is an automated email from the ASF dual-hosted git repository.

uraj pushed a commit to branch acs
in repository https://gitbox.apache.org/repos/asf/incubator-mesatee.git

commit fe6344674fe659b9795b4d20fa919fd8a0492fa0
Author: Pei Wang <wa...@baidu.com>
AuthorDate: Tue Oct 15 16:22:03 2019 -0700

    WIP: access control service
---
 .gitmodules                                        |   4 +
 CMakeLists.txt                                     |   2 +-
 Makefile.deprecated                                |  23 +-
 config.toml                                        |   1 +
 mesatee_config/src/runtime_config.rs               |  11 +
 mesatee_core/src/config/internal.rs                |  16 +
 mesatee_core/src/error.rs                          |   7 +-
 mesatee_services/acs/client/Cargo.toml             |  22 +
 mesatee_services/acs/client/src/acs_client.rs      | 150 +++++
 .../acs/client/src/lib.rs                          |  12 +-
 mesatee_services/acs/model.conf                    |  32 ++
 mesatee_services/acs/proto/Cargo.toml              |  23 +
 .../acs/proto/src/lib.rs                           |  11 +-
 mesatee_services/acs/proto/src/proto.rs            |  75 +++
 mesatee_services/acs/python/acs_engine.py          | 631 +++++++++++++++++++++
 mesatee_services/acs/python/acs_engine_test.py     |  82 +++
 mesatee_services/acs/python/ffi.py                 |  15 +
 mesatee_services/acs/sgx_app/Cargo.toml            |  23 +
 mesatee_services/acs/sgx_app/build.rs              |  61 ++
 mesatee_services/acs/sgx_app/src/main.rs           |  86 +++
 mesatee_services/acs/sgx_trusted_lib/Cargo.toml    |  30 +
 .../acs/sgx_trusted_lib/Enclave.config.xml         |  12 +
 mesatee_services/acs/sgx_trusted_lib/src/acs.rs    | 231 ++++++++
 .../acs/sgx_trusted_lib/src/lib.rs                 |  21 +-
 mesatee_services/acs/sgx_trusted_lib/src/sgx.rs    | 106 ++++
 tests/functional_test.sh                           |  10 +-
 tests/functional_test/sgx_trusted_lib/Cargo.toml   |   3 +-
 tests/functional_test/sgx_trusted_lib/src/sgx.rs   |   1 +
 .../sgx_trusted_lib/src/tests/acs_test.rs          | 267 +++++++++
 .../sgx_trusted_lib/src/tests/common_setup.rs      |   7 +
 .../sgx_trusted_lib/src/tests/mod.rs               |   1 +
 third_party/mesapy                                 |   1 +
 toolchain_deps/Cargo.sgx_trusted_lib.toml          |   1 +
 toolchain_deps/Cargo.sgx_untrusted_app.toml        |   1 +
 34 files changed, 1952 insertions(+), 27 deletions(-)

diff --git a/.gitmodules b/.gitmodules
index e7c30ff..d8d15b1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,7 @@
 [submodule "third_party/rust-sgx-sdk"]
 	path = third_party/rust-sgx-sdk
 	url = https://github.com/baidu/rust-sgx-sdk.git
+[submodule "third_party/mesapy"]
+	path = third_party/mesapy
+	url = https://github.com/mesalock-linux/mesapy.git
+	branch = sgx
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ed4978..eb0b460 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@ option(COV OFF "Turn on coverage or not")
 set(UNIX_LIBS mesatee_sdk protected_fs_rs)
 # need relative path for SGX_MODULE_PATHS to find Enclave.config.xml
 set(SGX_MODULE_PATHS mesatee_services/kms mesatee_services/tdfs mesatee_services/tms
-    mesatee_services/fns tests/functional_test)
+    mesatee_services/fns mesatee_services/acs tests/functional_test)
 # SGX_MODULES is generated from SGX_MODULE_PATHS
 # set(SGX_MODULES kms tdfs tms fns functional_test)
 # ================ VARIABLES FOR MANUAL CHANGE END ================
diff --git a/Makefile.deprecated b/Makefile.deprecated
index d6d0271..006f354 100644
--- a/Makefile.deprecated
+++ b/Makefile.deprecated
@@ -72,7 +72,7 @@ TEE_BINDER_DIR := $(MODULES_DIR)/mesatee_binder
 EDL_FILE := $(TEE_BINDER_DIR)/Enclave.edl
 
 SGX_MODULES := mesatee_services/kms mesatee_services/tdfs mesatee_services/tms \
-	mesatee_services/fns tests/functional_test
+	mesatee_services/fns mesatee_services/acs tests/functional_test
 SGX_LIBS :=
 UNIX_MODULES := integration_test private_join_and_compute quickstart \
 	image_resizing online_decrypt rsa_sign py_matrix_multiply py_file py_logistic_reg kmeans \
@@ -182,14 +182,14 @@ sgx_build_clean:
 #arg1: module name
 #arg2: enclave config
 define sgx_link
-	cd $(OUT_DIR) && $(CC) libEnclave_t.o ffi.o -o \
+	cd $(OUT_DIR) && $(CC) libEnclave_t.o -o \
 		$(OUT_DIR)/$(strip $(1)).enclave.so $(SGX_COMMON_CFLAGS) \
 		-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles \
 		-L$(SGX_LIBRARY_PATH) -Wl,--whole-archive -l$(Trts_Library_Name)  \
 		-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$(OUT_DIR) -lpypy-c -lsgx_tlibc_ext -lffi \
+		-L$(OUT_DIR) -lpycomponent ffi.o -lpypy-c -lsgx_tlibc_ext -lffi \
 		-L$(TRUSTED_TARGET_DIR)/$(TARGET) -l$(1)_enclave -Wl,--end-group \
 		-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \
 		-Wl,-pie,-eenclave_entry -Wl,--export-dynamic  \
@@ -225,7 +225,7 @@ sgx_pregen: prep $(EDL_FILE) $(SGX_EDGER8R)
 		--search-path $(RUST_SGX_SDK)/edl --trusted-dir $(OUT_DIR)
 	cd $(OUT_DIR) && $(CC) $(SGX_TRUSTED_CFLAGS) -c Enclave_t.c -o libEnclave_t.o
 
-$(SGX_MODULES): config_gen sgx_pregen
+$(SGX_MODULES): config_gen sgx_pregen pycomponent
 	echo -e "$(BOLD)[*] Building $@$(END_BOLD)"
 	$(call cargo_toml_prepare, sgx_untrusted_app)
 	$(call cargo_build, $(MODULES_DIR), $(UNTRUSTED_TARGET_DIR), -p $(notdir $@))
@@ -238,6 +238,17 @@ $(SGX_MODULES): config_gen sgx_pregen
 	$(call sgx_build_clean)
 	$(call sgx_link, $(notdir $@), $@/sgx_trusted_lib/Enclave.config.xml)
 
+pycomponent: $(OUT_DIR)/libpycomponent.a
+
+$(OUT_DIR)/libpycomponent.a: $(OUT_DIR)/acs_py_enclave.o
+	cd $(OUT_DIR) && ar rcs $@ $?
+
+$(OUT_DIR)/acs_py_enclave.c: mesatee_services/acs/python/ffi.py mesatee_services/acs/python/acs_engine.py prep
+	env PYTHONPATH=$(THIRD_PARTY_DIR)/mesapy/sgx PYPY_FFI_OUTDIR=$(OUT_DIR) pypy $<
+
+$(OUT_DIR)/acs_py_enclave.o: $(OUT_DIR)/acs_py_enclave.c
+	$(CC) -O2 -UWITH_THREAD -DSGX -fPIC -Wimplicit -I/usr/lib/pypy/include $< -c -o $@
+
 sgx_untrusted: config_gen sgx_pregen
 	echo -e "$(BOLD)[*] Building $@$(END_BOLD)"
 	$(call cargo_toml_prepare, sgx_untrusted_app)
@@ -245,7 +256,7 @@ sgx_untrusted: config_gen sgx_pregen
 	$(call cargo_toml_clean)
 	for m in $(SGX_MODULES); do cp $(UNTRUSTED_TARGET_DIR)/$(TARGET)/$${m##*/} $(MESATEE_BIN_DIR); done
 
-sgx_trusted: config_gen sgx_pregen sgx_untrusted
+sgx_trusted: config_gen sgx_pregen sgx_untrusted pycomponent
 	$(call sgx_build_prepare)
 	for m in $(SGX_MODULES); do \
 		echo -e "$(BOLD)[*] Building $${m}_enclave$(END_BOLD)" && \
@@ -293,6 +304,7 @@ fns: mesatee_services/fns
 kms: mesatee_services/kms
 tms: mesatee_services/tms
 tdfs: mesatee_services/tdfs
+acs: mesatee_services/acs
 
 sgx: sgx_trusted sgx_untrusted
 
@@ -304,6 +316,7 @@ cov:
 	find . \( -name "*.gcda" -and \( ! -name "sgx_cov*" \
 		-and ! -name "kms*" -and ! -name "fns*" \
 		-and ! -name "tdfs*" -and ! -name "tms*" \
+		-and ! -name "acs*" \
 		-and ! -name "private_join_and_compute*"\
 		-and ! -name "online_decrypt*"\
 		-and ! -name "image_resizing*"\
diff --git a/config.toml b/config.toml
index 7c10c74..552dfb4 100644
--- a/config.toml
+++ b/config.toml
@@ -48,6 +48,7 @@ fns  = { listen_ip = "0.0.0.0", connect_ip = "127.0.0.1", port = 3444 }
 tms  = { listen_ip = "0.0.0.0", connect_ip = "127.0.0.1", port = 5555 }
 tdfs = { listen_ip = "0.0.0.0", connect_ip = "127.0.0.1", port = 5066 }
 kms  = { listen_ip = "0.0.0.0", connect_ip = "127.0.0.1", port = 6016 }
+acs  = { listen_ip = "0.0.0.0", connect_ip = "127.0.0.1", port = 5077 }
 
 
 # This section configures the location of certificate/private key used to
diff --git a/mesatee_config/src/runtime_config.rs b/mesatee_config/src/runtime_config.rs
index d8a6274..6ef8c80 100644
--- a/mesatee_config/src/runtime_config.rs
+++ b/mesatee_config/src/runtime_config.rs
@@ -91,6 +91,9 @@ impl MesateeConfigToml {
         if !internal_endpoints.contains_key("kms") {
             return Err(err("[api_endpoint]: missing `kms`"));
         }
+        if !internal_endpoints.contains_key("acs") {
+            return Err(err("[api_endpoint]: missing `acs`"));
+        }
 
         let audited_enclave_config = &self.audited_enclave_config;
         if !audited_enclave_config.contains_key("enclave_info") {
@@ -165,6 +168,10 @@ pub struct MesateeConfig {
     pub kms_internal_connect_addr: IpAddr,
     pub kms_internal_port: u16,
 
+    pub acs_internal_listen_addr: IpAddr,
+    pub acs_internal_connect_addr: IpAddr,
+    pub acs_internal_port: u16,
+
     pub fns_external_listen_addr: IpAddr,
     pub fns_external_connect_addr: IpAddr, // for TMS to return to users
     pub fns_external_port: u16,
@@ -220,6 +227,10 @@ lazy_static! {
             kms_internal_connect_addr: MESATEE_CONFIG_TOML.internal_endpoints["kms"].connect_ip,
             kms_internal_port: MESATEE_CONFIG_TOML.internal_endpoints["kms"].port,
 
+            acs_internal_listen_addr: MESATEE_CONFIG_TOML.internal_endpoints["acs"].listen_ip,
+            acs_internal_connect_addr: MESATEE_CONFIG_TOML.internal_endpoints["acs"].connect_ip,
+            acs_internal_port: MESATEE_CONFIG_TOML.internal_endpoints["acs"].port,
+
             fns_external_listen_addr: MESATEE_CONFIG_TOML.api_endpoints["fns"].listen_ip,
             // `connect_ip` is a required field for FNS in [api_endpoints];
             // we can unwrap() safely because we have checked it in MesateeConfigToml::is_valid().
diff --git a/mesatee_core/src/config/internal.rs b/mesatee_core/src/config/internal.rs
index 82b2b4a..e350511 100644
--- a/mesatee_core/src/config/internal.rs
+++ b/mesatee_core/src/config/internal.rs
@@ -45,6 +45,14 @@ impl Internal {
         )
     }
 
+    pub fn acs() -> ServiceConfig {
+        ServiceConfig::new(
+            MESATEE_CONFIG.acs_internal_listen_addr,
+            MESATEE_CONFIG.acs_internal_port,
+            InboundDesc::Sgx(get_trusted_enclave_attr(vec!["kms", "tms", "tdfs"])),
+        )
+    }
+
     pub fn target_tms() -> TargetDesc {
         TargetDesc::new(
             MESATEE_CONFIG.tms_internal_connect_addr,
@@ -68,4 +76,12 @@ impl Internal {
             OutboundDesc::Sgx(get_trusted_enclave_attr(vec!["tdfs"])),
         )
     }
+
+    pub fn target_acs() -> TargetDesc {
+        TargetDesc::new(
+            MESATEE_CONFIG.acs_internal_connect_addr,
+            MESATEE_CONFIG.acs_internal_port,
+            OutboundDesc::Sgx(get_trusted_enclave_attr(vec!["acs"])),
+        )
+    }
 }
diff --git a/mesatee_core/src/error.rs b/mesatee_core/src/error.rs
index 84655b6..f54a6a5 100644
--- a/mesatee_core/src/error.rs
+++ b/mesatee_core/src/error.rs
@@ -113,10 +113,12 @@ pub enum ErrorKind {
     IPCError,
     /// IAS client key or cert not available
     IASClientKeyCertError,
-    /// No valid worker for the task,
+    /// No valid worker for the task
     NoValidWorkerError,
     /// RPC Message size excceds the limit
     MsgSizeLimitExceedError,
+    /// Unhandled MesaPy exception encountered
+    MesaPyError,
     /// Others.
     Unknown,
 }
@@ -189,6 +191,7 @@ impl ErrorKind {
             }
             ErrorKind::NoValidWorkerError => "no valid worker error",
             ErrorKind::MsgSizeLimitExceedError => "message size exceeds limit",
+            ErrorKind::MesaPyError => "unhandled mesapy exception",
             ErrorKind::Unknown => "unknown error",
         }
     }
@@ -240,6 +243,7 @@ impl From<u32> for Error {
             0x0000_1011 => ErrorKind::IASClientKeyCertError,
             0x0000_1012 => ErrorKind::NoValidWorkerError,
             0x0000_1013 => ErrorKind::MsgSizeLimitExceedError,
+            0x0000_1014 => ErrorKind::MesaPyError,
             _ => ErrorKind::Unknown,
         };
 
@@ -287,6 +291,7 @@ impl Into<u32> for Error {
             ErrorKind::IASClientKeyCertError => 0x0000_1011,
             ErrorKind::NoValidWorkerError => 0x0000_1012,
             ErrorKind::MsgSizeLimitExceedError => 0x0000_1013,
+            ErrorKind::MesaPyError => 0x0000_1014,
             ErrorKind::Unknown => 0xffff_ffff,
         }
     }
diff --git a/mesatee_services/acs/client/Cargo.toml b/mesatee_services/acs/client/Cargo.toml
new file mode 100644
index 0000000..e7fe225
--- /dev/null
+++ b/mesatee_services/acs/client/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "acs_client"
+version = "0.1.0"
+authors = ["MesaTEE Authors <de...@mesatee.org>"]
+description = "RPC client of ACS."
+license = "Apache-2.0"
+edition = "2018"
+
+[features]
+default = []
+mesalock_sgx = ["sgx_tstd", "mesatee_core/mesalock_sgx", "acs_proto/mesalock_sgx"]
+cov = ["sgx_cov"]
+
+[dependencies]
+cfg-if          = { version = "0.1.9" }
+acs_proto       = { path = "../proto" }
+
+mesatee_core    = { version = "0.1.0" }
+
+sgx_types       = { version = "1.0.9" }
+sgx_cov         = { version = "0.1.0", optional = true }
+sgx_tstd        = { version = "1.0.9", optional = true }
diff --git a/mesatee_services/acs/client/src/acs_client.rs b/mesatee_services/acs/client/src/acs_client.rs
new file mode 100644
index 0000000..9a1ceff
--- /dev/null
+++ b/mesatee_services/acs/client/src/acs_client.rs
@@ -0,0 +1,150 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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.
+
+// Insert std prelude in the top for the sgx feature
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+
+use acs_proto::*;
+use mesatee_core::config::{OutboundDesc, TargetDesc};
+use mesatee_core::rpc::channel::SgxTrustedChannel;
+use mesatee_core::{Error, ErrorKind, Result};
+
+use std::collections::HashSet;
+
+pub struct ACSClient {
+    channel: SgxTrustedChannel<ACSRequest, ACSResponse>,
+}
+
+impl ACSClient {
+    pub fn new(target: TargetDesc) -> Result<Self> {
+        let addr = target.addr;
+        let channel = match target.desc {
+            OutboundDesc::Sgx(enclave_addr) => {
+                SgxTrustedChannel::<ACSRequest, ACSResponse>::new(addr, enclave_addr)?
+            }
+        };
+        Ok(ACSClient { channel })
+    }
+
+    pub fn enforce_task_launch(
+        &mut self,
+        task: String,
+        participants: HashSet<String>,
+    ) -> Result<bool> {
+        let req = ACSRequest::Enforce(EnforceRequest::LaunchTask(task, participants));
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Enforce(allow) => Ok(allow),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+
+    pub fn enforce_data_access(
+        &mut self,
+        task: String,
+        data: String
+    ) -> Result<bool> {
+        let req = ACSRequest::Enforce(EnforceRequest::AccessData(task, data));
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Enforce(allow) => Ok(allow),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+
+    pub fn enforce_data_deletion(
+        &mut self,
+        usr: String,
+        data: String,
+    ) -> Result<bool> {
+        let req = ACSRequest::Enforce(EnforceRequest::DeleteData(usr, data));
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Enforce(allow) => Ok(allow),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+    
+    pub fn enforce_script_access(
+        &mut self,
+        task: String,
+        script: String,
+    ) -> Result<bool> {
+        let req = ACSRequest::Enforce(EnforceRequest::AccessScript(task, script));
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Enforce(allow) => Ok(allow),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+    
+    pub fn enforce_script_deletion(
+        &mut self,
+        usr: String,
+        script: String
+    ) -> Result<bool> {
+        let req = ACSRequest::Enforce(EnforceRequest::DeleteScript(usr, script));
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Enforce(allow) => Ok(allow),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+
+    fn _announce_terms(&mut self, facts: Vec<AccessControlTerms>) -> Result<()> {
+        let req = ACSRequest::Announce(AnnounceRequest { facts });
+        let resp = self.channel.invoke(req)?;
+        match resp {
+            ACSResponse::Announce => Ok(()),
+            _ => Err(Error::from(ErrorKind::RPCResponseError)),
+        }
+    }
+
+    pub fn announce_task_creation(
+        &mut self,
+        task: String,
+        creator: String,
+        participants: &HashSet<String>,
+    ) -> Result<()> {
+        let mut facts = Vec::with_capacity(1 + participants.len());
+        for par in participants {
+            facts.push(AccessControlTerms::TaskParticipant(task.clone(), par.clone()));
+        }
+        facts.push(AccessControlTerms::TaskCreator(task, creator));
+        self._announce_terms(facts)
+    }
+
+    pub fn announce_data_creation(
+        &mut self,
+        data: String,
+        creator: String,
+    ) -> Result<()> {
+        self._announce_terms(std::vec!(AccessControlTerms::DataOwner(data, creator)))
+    }
+
+    pub fn announce_script_creation(
+        &mut self,
+        script: String,
+        creator: String,
+        is_public: bool,
+    ) -> Result<()> {
+        let mut terms = Vec::new();
+        if is_public {
+            terms.push(AccessControlTerms::IsPublicScript(script.clone()))
+        }
+        terms.push(AccessControlTerms::ScriptOwner(script, creator));
+        self._announce_terms(terms)
+    }
+}
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs b/mesatee_services/acs/client/src/lib.rs
similarity index 74%
copy from tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
copy to mesatee_services/acs/client/src/lib.rs
index dde9bb0..f774564 100644
--- a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
+++ b/mesatee_services/acs/client/src/lib.rs
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-pub mod common_setup;
-pub mod kms_test;
-pub mod protected_fs_test;
-pub mod tdfs_test;
-pub mod tms_test;
+// Use sgx_tstd to replace Rust's default std
+#![cfg_attr(feature = "mesalock_sgx", no_std)]
+#[cfg(feature = "mesalock_sgx")]
+extern crate sgx_tstd as std;
+
+mod acs_client;
+pub use acs_client::ACSClient;
diff --git a/mesatee_services/acs/model.conf b/mesatee_services/acs/model.conf
new file mode 100644
index 0000000..7e30003
--- /dev/null
+++ b/mesatee_services/acs/model.conf
@@ -0,0 +1,32 @@
+[requests]
+launch_task = task, participants
+access_data = task, data
+delete_data = usr, data
+access_script = task, script
+delete_script = usr, script
+
+[terms]
+task_creator = task, usr
+task_participant = task, usr
+
+script_owner = script, usr
+is_public_script = script
+
+data_owner = data, usr
+
+[matchers]
+# All participants must approve task launch
+launch_task = task_participant(launch_task.task, _) <= launch_task.participants
+
+# All participants must approve data access
+access_data = data_owner(access_data.data, _) <= task_participant(access_data.task, _)
+
+# Any creator of the data can request for deletion
+delete_data = data_owner(delete_data.data, delete_data.usr)
+
+access_script = \
+    is_public_script(access_script.script) or \
+    script_owner(access_script.script, _) <= task_participant(access_script.task, _)
+
+# Only creator of the script can request for deletion
+delete_script = script_owner(delete_script.script, delete_script.usr)
\ No newline at end of file
diff --git a/mesatee_services/acs/proto/Cargo.toml b/mesatee_services/acs/proto/Cargo.toml
new file mode 100644
index 0000000..55be836
--- /dev/null
+++ b/mesatee_services/acs/proto/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "acs_proto"
+version = "0.1.0"
+authors = ["MesaTEE Authors <de...@mesatee.org>"]
+description = "RPC protocol of ACS."
+license = "Apache-2.0"
+edition = "2018"
+
+[features]
+default = []
+mesalock_sgx = ["sgx_tstd", "mesatee_core/mesalock_sgx"]
+cov = ["sgx_cov"]
+
+[dependencies]
+mesatee_core = { version = "0.1.0" }
+serde        = { version = "1.0.39" }
+serde_json   = { version = "1.0.39" }
+serde_derive = { version = "1.0.92" }
+uuid         = { version = "0.7.4", features = ["v4"] }
+
+sgx_cov      = { version = "0.1.0", optional = true }
+sgx_tstd     = { version = "1.0.9", features = ["net", "backtrace"], optional = true }
+sgx_types    = { version = "1.0.9" }
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs b/mesatee_services/acs/proto/src/lib.rs
similarity index 80%
copy from tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
copy to mesatee_services/acs/proto/src/lib.rs
index dde9bb0..ff8f727 100644
--- a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
+++ b/mesatee_services/acs/proto/src/lib.rs
@@ -11,9 +11,10 @@
 // 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")]
 
-pub mod common_setup;
-pub mod kms_test;
-pub mod protected_fs_test;
-pub mod tdfs_test;
-pub mod tms_test;
+extern crate sgx_tstd as std;
+
+mod proto;
+pub use proto::*;
diff --git a/mesatee_services/acs/proto/src/proto.rs b/mesatee_services/acs/proto/src/proto.rs
new file mode 100644
index 0000000..78c63f1
--- /dev/null
+++ b/mesatee_services/acs/proto/src/proto.rs
@@ -0,0 +1,75 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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.
+
+// Insert std prelude in the top for the sgx feature
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+
+use std::collections::HashSet;
+
+use serde_derive::*;
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[serde(tag = "type")]
+pub enum ACSRequest {
+    Enforce(EnforceRequest),
+    Announce(AnnounceRequest),
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub enum ACSResponse {
+    Enforce(bool),
+    Announce,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub enum EnforceRequest {
+    // launch_task = task, participants
+    LaunchTask(String, HashSet<String>),
+
+    // access_data = task, data
+    AccessData(String, String),
+    
+    // delete_data = usr, data
+    DeleteData(String, String),
+
+    // access_script = task, script
+    AccessScript(String, String),
+
+    // delete_script = usr, script
+    DeleteScript(String, String),
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct AnnounceRequest {
+    pub facts: Vec<AccessControlTerms>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub enum AccessControlTerms {
+    // task_creator = task, usr
+    TaskCreator(String, String),
+    
+    // task_participant = task, usr
+    TaskParticipant(String, String),
+
+    // data_owner = data, usr
+    DataOwner(String, String),
+
+    // script_owner = script, usr
+    ScriptOwner(String, String),
+
+    // is_public_script = script
+    IsPublicScript(String),
+}
diff --git a/mesatee_services/acs/python/acs_engine.py b/mesatee_services/acs/python/acs_engine.py
new file mode 100644
index 0000000..4a12bf4
--- /dev/null
+++ b/mesatee_services/acs/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
+
+@ffi.def_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
+
+@ffi.def_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
+
+@ffi.def_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/mesatee_services/acs/python/acs_engine_test.py b/mesatee_services/acs/python/acs_engine_test.py
new file mode 100644
index 0000000..b951d41
--- /dev/null
+++ b/mesatee_services/acs/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/mesatee_services/acs/python/ffi.py b/mesatee_services/acs/python/ffi.py
new file mode 100644
index 0000000..3f56be7
--- /dev/null
+++ b/mesatee_services/acs/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/mesatee_services/acs/sgx_app/Cargo.toml b/mesatee_services/acs/sgx_app/Cargo.toml
new file mode 100644
index 0000000..6119b11
--- /dev/null
+++ b/mesatee_services/acs/sgx_app/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "acs"
+version = "0.1.0"
+authors = ["MesaTEE Authors <de...@mesatee.org>"]
+description = "Access control service."
+license = "Apache-2.0"
+build = "build.rs"
+edition = "2018"
+
+[features]
+default = []
+
+[dependencies]
+sgx_types       = { version = "1.0.9" }
+sgx_urts        = { version = "1.0.9" }
+
+mesatee_core    = { version = "0.1.0" }
+mesatee_binder  = { version = "0.1.0" }
+
+ctrlc           = { version = "3.1.2" }
+log             = { version = "0.4.6" }
+env_logger      = { version = "0.6.1" }
+threadpool      = { version = "1.0" }
diff --git a/mesatee_services/acs/sgx_app/build.rs b/mesatee_services/acs/sgx_app/build.rs
new file mode 100644
index 0000000..df76028
--- /dev/null
+++ b/mesatee_services/acs/sgx_app/build.rs
@@ -0,0 +1,61 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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::io::Write;
+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);
+
+    // This would triggers `unwrap()` which results in panic, if no such env
+    // var found. Cargo documents say that this env variable is provided by
+    // cargo. See
+    // https://doc.rust-lang.org/cargo/reference/environment-variables.html
+    let enclave_name = env!("CARGO_PKG_NAME");
+
+    // Once we enclave_name ready, write it to `../pkg_name`
+    std::fs::File::create("../pkg_name")
+        .unwrap()
+        .write_all(enclave_name.as_bytes())
+        .unwrap();
+
+    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/mesatee_services/acs/sgx_app/src/main.rs b/mesatee_services/acs/sgx_app/src/main.rs
new file mode 100644
index 0000000..63ed44f
--- /dev/null
+++ b/mesatee_services/acs/sgx_app/src/main.rs
@@ -0,0 +1,86 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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 mesatee_core::prelude::*;
+use mesatee_core::{config, Result};
+
+use std::net::TcpListener;
+use std::os::unix::io::IntoRawFd;
+use threadpool::ThreadPool;
+
+use mesatee_binder::TeeBinder;
+use std::sync::Arc;
+
+fn main() -> Result<()> {
+    env_logger::init();
+
+    let tee = match TeeBinder::new("acs", 1) {
+        Ok(r) => {
+            info!("Init TEE Successfully!");
+            r
+        }
+        Err(x) => {
+            error!("Init TEE Failed {}!", x);
+            std::process::exit(-1)
+        }
+    };
+
+    let tee = Arc::new(tee);
+
+    {
+        let ref_tee = tee.clone();
+        ctrlc::set_handler(move || {
+            info!("\nCTRL+C pressed. Destroying server enclave");
+            let _ = ref_tee.finalize();
+            std::process::exit(0);
+        })
+        .expect("Error setting Ctrl-C handler");
+    }
+
+    run_access_control_service(tee)?;
+
+    Ok(())
+}
+
+fn run_access_control_service(tee: Arc<TeeBinder>) -> Result<()> {
+    info!("Running as ACS Server ...");
+
+    let config = config::Internal::acs();
+    let listener = TcpListener::bind(config.addr)?;
+    let port = config.addr.port();
+
+    let n_workers = 10;
+    let pool = ThreadPool::new(n_workers);
+    for stream in listener.incoming() {
+        match stream {
+            Ok(stream) => {
+                let tee = tee.clone();
+                pool.execute(move || {
+                    debug!("new worker from {:?}", stream.peer_addr());
+                    let fd = stream.into_raw_fd();
+                    let input = ServeConnectionInput::new(fd, port);
+                    let cmd = ECallCommand::ServeConnection;
+                    let _ = tee
+                        .invoke::<ServeConnectionInput, ServeConnectionOutput>(cmd.into(), input);
+                });
+            }
+            Err(e) => warn!("couldn't get client: {:?}", e),
+        }
+    }
+
+    Ok(())
+}
diff --git a/mesatee_services/acs/sgx_trusted_lib/Cargo.toml b/mesatee_services/acs/sgx_trusted_lib/Cargo.toml
new file mode 100644
index 0000000..94973f3
--- /dev/null
+++ b/mesatee_services/acs/sgx_trusted_lib/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "acs_enclave"
+version = "0.1.0"
+authors = ["MesaTEE Authors <de...@mesatee.org>"]
+description = "Access control service."
+license = "Apache-2.0"
+edition = "2018"
+
+[lib]
+name = "acs_enclave"
+crate-type = ["staticlib"]
+
+[features]
+default = []
+mesalock_sgx = ["sgx_tstd", "mesatee_core/mesalock_sgx", "acs_proto/mesalock_sgx"]
+cov = ["sgx_cov"]
+
+[dependencies]
+cfg-if          = { version = "0.1.9" }
+lazy_static     = { version = "1.0.2", features = ["spin_no_std"] }
+log             = { version = "0.4.6" }
+env_logger      = { version = "0.6.1" }
+uuid            = { version = "0.7.4", features = ["v4"] }
+
+acs_proto       = { path = "../proto", optional = true}
+mesatee_core    = { version = "0.1.0" }
+
+sgx_cov         = { version = "0.1.0", optional = true }
+sgx_tstd        = { version = "1.0.9", features = ["net", "backtrace"], optional = true }
+sgx_types       = { version = "1.0.9" }
diff --git a/mesatee_services/acs/sgx_trusted_lib/Enclave.config.xml b/mesatee_services/acs/sgx_trusted_lib/Enclave.config.xml
new file mode 100644
index 0000000..705edcd
--- /dev/null
+++ b/mesatee_services/acs/sgx_trusted_lib/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/mesatee_services/acs/sgx_trusted_lib/src/acs.rs b/mesatee_services/acs/sgx_trusted_lib/src/acs.rs
new file mode 100644
index 0000000..4e2c735
--- /dev/null
+++ b/mesatee_services/acs/sgx_trusted_lib/src/acs.rs
@@ -0,0 +1,231 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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.
+
+// Insert std prelude in the top for the sgx feature
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+
+use mesatee_core::rpc::EnclaveService;
+use mesatee_core::{Result, ErrorKind};
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::collections::HashSet;
+
+use acs_proto::*;
+
+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.as_ref() {
+            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('\'');
+    }
+}
+
+pub trait HandleRequest {
+    fn handle_request(&self) -> Result<ACSResponse>;
+}
+
+extern "C" {
+    fn acs_enforce_request(request_type: *const c_char, request_content: *const c_char) -> i32;
+    fn acs_announce_fact(fact_type: *const c_char, fact_vals: *const c_char) -> i32;
+}
+
+impl HandleRequest for EnforceRequest {
+    fn handle_request(&self) -> Result<ACSResponse> {
+        let (request_type, request_content) = match self {
+            EnforceRequest::LaunchTask(usr, task) => {
+                let mut buffer = String::new();
+                (usr, task).marshal(&mut buffer);
+                ("launch_task", buffer)
+            }
+            EnforceRequest::AccessData(task, data) => {
+                let mut buffer = String::new();
+                (task, data).marshal(&mut buffer);
+                ("access_data", buffer)
+            }
+            EnforceRequest::DeleteData(usr, data) => {
+                let mut buffer = String::new();
+                (usr, data).marshal(&mut buffer);
+                ("delete_data", buffer)
+            }
+            EnforceRequest::AccessScript(task, script) => {
+                let mut buffer = String::new();
+                (task, script).marshal(&mut buffer);
+                ("access_script", buffer)
+            }
+            EnforceRequest::DeleteScript(usr, script) => {
+                let mut buffer = String::new();
+                (usr, script).marshal(&mut buffer);
+                ("delete_script", buffer)
+            }
+        };
+        
+        let c_request_type = CString::new(request_type.to_string()).unwrap();
+        let c_request_content = CString::new(request_content).unwrap();
+        let py_ret = unsafe {
+            acs_enforce_request(c_request_type.as_ptr(), c_request_content.as_ptr())
+        };
+
+        match py_ret {
+            0 => Ok(ACSResponse::Enforce(false)),
+            1 => Ok(ACSResponse::Enforce(true)),
+            _ => Err(ErrorKind::MesaPyError.into()),
+        }
+    }
+}
+
+impl HandleRequest for AnnounceRequest {
+    fn handle_request(&self) -> Result<ACSResponse> {
+        for fact in &self.facts {
+            use AccessControlTerms::*;
+            let (term_type, term_fact) = match fact {
+                TaskCreator(task, usr) => {
+                    let mut buffer = String::new();
+                    (task, usr).marshal(&mut buffer);
+                    ("task_creator", buffer)
+                }
+                TaskParticipant(task, usr) => {
+                    let mut buffer = String::new();
+                    (task, usr).marshal(&mut buffer);
+                    ("task_participant", buffer)
+                }
+                DataOwner(data, usr) => {
+                    let mut buffer = String::new();
+                    (data, usr).marshal(&mut buffer);
+                    ("data_owner", buffer)
+                }
+                ScriptOwner(script, usr) => {
+                    let mut buffer = String::new();
+                    (script, usr).marshal(&mut buffer);
+                    ("script_owner", buffer)
+                }
+                IsPublicScript(script) => {
+                    let mut buffer = String::new();
+                    (script,).marshal(&mut buffer);
+                    ("is_public_script", buffer)
+                }
+            };
+
+            let c_term_type = CString::new(term_type.to_string()).unwrap();
+            let c_term_fact = CString::new(term_fact).unwrap();
+
+            let py_ret = unsafe {
+                acs_announce_fact(c_term_type.as_ptr(), c_term_fact.as_ptr())
+            };
+            
+            if py_ret != 0 {
+                return Err(ErrorKind::MesaPyError.into());
+            }
+        }
+        Ok(ACSResponse::Announce)
+    }
+}
+
+pub struct ACSEnclave;
+
+impl Default for ACSEnclave {
+    fn default() -> Self {
+        ACSEnclave {}
+    }
+}
+
+impl EnclaveService<ACSRequest, ACSResponse> for ACSEnclave {
+    fn handle_invoke(&mut self, input: ACSRequest) -> Result<ACSResponse> {
+        debug!("handle_invoke invoked!");
+        debug!("incoming payload = {:?}", input);
+
+        let response = match input {
+            ACSRequest::Enforce(req) => req.handle_request()?,
+            ACSRequest::Announce(req) => req.handle_request()?,
+        };
+ 
+        Ok(response)
+    }
+}
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs b/mesatee_services/acs/sgx_trusted_lib/src/lib.rs
similarity index 68%
copy from tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
copy to mesatee_services/acs/sgx_trusted_lib/src/lib.rs
index dde9bb0..d83f8f7 100644
--- a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
+++ b/mesatee_services/acs/sgx_trusted_lib/src/lib.rs
@@ -11,9 +11,20 @@
 // 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;
 
-pub mod common_setup;
-pub mod kms_test;
-pub mod protected_fs_test;
-pub mod tdfs_test;
-pub mod tms_test;
+#[macro_use]
+extern crate log;
+
+mod acs;
+
+use cfg_if::cfg_if;
+cfg_if! {
+    if #[cfg(feature = "mesalock_sgx")]  {
+        mod sgx;
+    } else {
+    }
+}
diff --git a/mesatee_services/acs/sgx_trusted_lib/src/sgx.rs b/mesatee_services/acs/sgx_trusted_lib/src/sgx.rs
new file mode 100644
index 0000000..85266e6
--- /dev/null
+++ b/mesatee_services/acs/sgx_trusted_lib/src/sgx.rs
@@ -0,0 +1,106 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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.
+
+// Insert std prelude in the top for the sgx feature
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+
+use std::ffi::CString;
+use std::os::raw::c_char;
+
+use mesatee_core::config;
+use mesatee_core::prelude::*;
+use mesatee_core::{Result, Error, ErrorKind};
+
+use env_logger;
+use std::backtrace::{self, PrintFormat};
+
+use crate::acs::ACSEnclave;
+
+register_ecall_handler!(
+    type ECallCommand,
+    (ECallCommand::ServeConnection, ServeConnectionInput, ServeConnectionOutput),
+    (ECallCommand::InitEnclave, InitEnclaveInput, InitEnclaveOutput),
+    (ECallCommand::FinalizeEnclave, FinalizeEnclaveInput, FinalizeEnclaveOutput),
+);
+
+extern "C" {
+    fn acs_setup_model(model_text: *const c_char) -> i32;
+}
+
+#[handle_ecall]
+fn handle_serve_connection(args: &ServeConnectionInput) -> Result<ServeConnectionOutput> {
+    debug!("Enclave [ACS]: Serve Connection.");
+
+    let server_instance = ACSEnclave::default();
+    let acs_config = config::Internal::acs();
+    assert_eq!(args.port, acs_config.addr.port());
+
+    let enclave_attr = match acs_config.inbound_desc {
+        config::InboundDesc::Sgx(enclave_attr) => enclave_attr,
+        _ => unreachable!(),
+    };
+
+    let config = PipeConfig {
+        fd: args.socket_fd,
+        retry: 0,
+        client_attr: Some(enclave_attr),
+    };
+
+    let mut server = match Pipe::start(config) {
+        Ok(s) => s,
+        Err(e) => {
+            error!("Start Pipe failed: {}", e);
+            return Ok(ServeConnectionOutput::default());
+        }
+    };
+
+    let _ = server.serve(server_instance);
+
+    // We discard all enclave internal errors here.
+    Ok(ServeConnectionOutput::default())
+}
+
+const MODEL_TEXT: &'static str = include_str!("../../model.conf");
+
+#[handle_ecall]
+fn handle_init_enclave(_args: &InitEnclaveInput) -> Result<InitEnclaveOutput> {
+    debug!("Enclave [ACS]: Initializing...");
+
+    env_logger::init();
+    let _ = backtrace::enable_backtrace(
+        concat!(include_str!("../../pkg_name"), ".enclave.signed.so"),
+        PrintFormat::Full,
+    );
+    mesatee_core::rpc::sgx::prelude();
+
+    eprintln!("setting up acs model");
+
+    let ec = unsafe { acs_setup_model(CString::new(MODEL_TEXT).unwrap().as_ptr()) };
+
+    if ec != 0 {
+        Err(Error::from(ErrorKind::MesaPyError))
+    } else {
+        Ok(InitEnclaveOutput::default())
+    }
+}
+
+#[handle_ecall]
+fn handle_finalize_enclave(_args: &FinalizeEnclaveInput) -> Result<FinalizeEnclaveOutput> {
+    #[cfg(feature = "cov")]
+    sgx_cov::cov_writeout();
+
+    debug!("Enclave [ACS]: Finalized.");
+    Ok(FinalizeEnclaveOutput::default())
+}
diff --git a/tests/functional_test.sh b/tests/functional_test.sh
index a825c23..963c60f 100755
--- a/tests/functional_test.sh
+++ b/tests/functional_test.sh
@@ -15,6 +15,10 @@ if lsof -i :5066; then
     echo "[-] port 5066 is in use"
     exit 1
 fi
+if lsof -i :5077; then
+    echo "[-] port 5066 is in use"
+    exit 1
+fi
 if lsof -i :5065; then
     echo "[-] port 5065 is in use"
     exit 1
@@ -27,10 +31,6 @@ if lsof -i :5555; then
     echo "[-] port 5555 is in use"
     exit 1
 fi
-if lsof -i :5555; then
-    echo "[-] port 5555 is in use"
-    exit 1
-fi
 if lsof -i :3444; then
     echo "[-] port 3444 is in use"
     exit 1
@@ -41,6 +41,7 @@ fi
 ./tdfs 2>&1 | tee tdfs.log &
 ./tms 2>&1 | tee tms.log &
 ./fns 2>&1 | tee fns.log &
+./acs 2>&1 | tee acs.log &
 
 wait_service() {
     name=$1
@@ -61,6 +62,7 @@ wait_service tdfs 5065 30
 wait_service tms 5554 30
 wait_service tms 5555 30
 wait_service fns 3444 30
+wait_service acs 5077 30
 
 ./functional_test 2>&1 | tee functional_test.log
 exit ${PIPESTATUS[0]}
diff --git a/tests/functional_test/sgx_trusted_lib/Cargo.toml b/tests/functional_test/sgx_trusted_lib/Cargo.toml
index 27f78b5..65b052d 100644
--- a/tests/functional_test/sgx_trusted_lib/Cargo.toml
+++ b/tests/functional_test/sgx_trusted_lib/Cargo.toml
@@ -12,7 +12,7 @@ crate-type = ["staticlib"]
 
 [features]
 default = []
-mesalock_sgx = ["sgx_tstd", "sgx_tunittest", "mesatee_core/mesalock_sgx", "kms_client/mesalock_sgx", "tdfs_internal_client/mesalock_sgx", "tms_internal_client/mesalock_sgx", "tms_internal_proto/mesalock_sgx", "protected_fs_rs/mesalock_sgx"]
+mesalock_sgx = ["sgx_tstd", "sgx_tunittest", "mesatee_core/mesalock_sgx", "kms_client/mesalock_sgx", "acs_client/mesalock_sgx", "tdfs_internal_client/mesalock_sgx", "tms_internal_client/mesalock_sgx", "tms_internal_proto/mesalock_sgx", "protected_fs_rs/mesalock_sgx"]
 cov = ["sgx_cov"]
 
 [dependencies]
@@ -22,6 +22,7 @@ env_logger      = { version = "0.7.1" }
 rand            = { version = "0.7" }
 
 kms_client      = { path = "../../../mesatee_services/kms/client", optional = true }
+acs_client      = { path = "../../../mesatee_services/acs/client", optional = true }
 tdfs_internal_client = { path = "../../../mesatee_services/tdfs/internal/client", optional = true }
 tms_internal_client = { path = "../../../mesatee_services/tms/internal/client", optional = true }
 tms_internal_proto = { path = "../../../mesatee_services/tms/internal/proto", optional = true }
diff --git a/tests/functional_test/sgx_trusted_lib/src/sgx.rs b/tests/functional_test/sgx_trusted_lib/src/sgx.rs
index 1fc2faa..3069e7e 100644
--- a/tests/functional_test/sgx_trusted_lib/src/sgx.rs
+++ b/tests/functional_test/sgx_trusted_lib/src/sgx.rs
@@ -47,6 +47,7 @@ fn handle_run_functional_test(_args: &RunFunctionalTestInput) -> Result<RunFunct
         tests::tms_test::update_task_result,
         tests::tms_test::update_private_result,
         tests::tms_test::update_status,
+        tests::acs_test::access_control_model,
     );
 
     Ok(RunFunctionalTestOutput::new(nfailed))
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/acs_test.rs b/tests/functional_test/sgx_trusted_lib/src/tests/acs_test.rs
new file mode 100644
index 0000000..51d2d5c
--- /dev/null
+++ b/tests/functional_test/sgx_trusted_lib/src/tests/acs_test.rs
@@ -0,0 +1,267 @@
+// Copyright 2019 MesaTEE Authors
+//
+// Licensed 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 super::common_setup::setup_acs_internal_client;
+
+use std::collections::HashSet;
+use std::string::ToString;
+    
+const FUSION_TASK: &'static str = "data_fusion";
+
+const FUSION_TASK_PARTY_1: &'static str = "usr1";
+const FUSION_TASK_PARTY_2: &'static str = "usr2";
+
+const FUSION_TASK_DATA_1: &'static str = "data1";
+const FUSION_TASK_DATA_2: &'static str = "data2";
+
+const FUSION_TASK_SCRIPT: &'static str = "fusion_script";
+const FUSION_TASK_SCRIPT_WRITER: &'static str = "usr3";
+const PUBLIC_SCRIPT: &'static str = "public_script";
+const PUBLIC_SCRIPT_WRITER: &'static str = "usr4";
+
+const IRRELEVANT_TASK: &'static str = "task_irrelevant";
+const IRRELEVANT_PARTY: &'static str = "usr_irrelevant";
+const IRRELEVANT_DATA: &'static str = "data_irrelevant";
+
+pub fn access_control_model() {
+    trace!("Test ACS: access control model.");
+    let mut client = setup_acs_internal_client();
+
+    let mut participants = HashSet::new();
+    participants.insert(FUSION_TASK_PARTY_1.to_string());
+    participants.insert(FUSION_TASK_PARTY_2.to_string());
+    participants.insert(FUSION_TASK_SCRIPT_WRITER.to_string());
+
+    client.announce_task_creation(
+        FUSION_TASK.to_string(),
+        FUSION_TASK_PARTY_1.to_string(),
+        &participants,
+    ).expect("fusion task creation announcement failed");
+
+    client.announce_data_creation(
+        FUSION_TASK_DATA_1.to_string(),
+        FUSION_TASK_PARTY_1.to_string(),
+    ).expect("fusion data n1 creation announcement failed");
+
+    client.announce_data_creation(
+        FUSION_TASK_DATA_2.to_string(),
+        FUSION_TASK_PARTY_2.to_string(),
+    ).expect("fusion data 2 creation announcement failed");
+
+    client.announce_data_creation(
+        IRRELEVANT_DATA.to_string(),
+        IRRELEVANT_PARTY.to_string(),
+    ).expect("irrelevant data creation announcement failed");
+
+    client.announce_script_creation(
+        FUSION_TASK_SCRIPT.to_string(),
+        FUSION_TASK_SCRIPT_WRITER.to_string(),
+        false,
+    ).expect("fusion script creation announcement failed");
+
+    client.announce_script_creation(
+        PUBLIC_SCRIPT.to_string(),
+        PUBLIC_SCRIPT_WRITER.to_string(),
+        true,
+    ).expect("public script creation announcement failed");
+
+    let mut participants = HashSet::new();
+    
+    assert_eq!(
+        client.enforce_task_launch(
+            FUSION_TASK.to_string(),
+            participants.clone(),
+        ).unwrap(),
+        false,
+    );
+
+    participants.insert(FUSION_TASK_PARTY_1.to_string());
+
+    assert_eq!(
+        client.enforce_task_launch(
+            FUSION_TASK.to_string(),
+            participants.clone(),
+        ).unwrap(),
+        false,
+    );
+
+    participants.insert(FUSION_TASK_PARTY_2.to_string());
+
+        assert_eq!(
+        client.enforce_task_launch(
+            FUSION_TASK.to_string(),
+            participants.clone(),
+        ).unwrap(),
+        false,
+    );
+
+    participants.insert(FUSION_TASK_SCRIPT_WRITER.to_string());
+
+    assert_eq!(
+        client.enforce_task_launch(
+            FUSION_TASK.to_string(),
+            participants.clone(),
+        ).unwrap(),
+        true,
+    );
+    
+    // Load fusion script
+    assert_eq!(
+        client.enforce_script_access(
+            FUSION_TASK.to_string(),
+            FUSION_TASK_SCRIPT.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    // Load public script
+    assert_eq!(
+        client.enforce_script_access(
+            FUSION_TASK.to_string(),
+            PUBLIC_SCRIPT.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    // Read data1
+    assert_eq!(
+        client.enforce_data_access(
+            FUSION_TASK.to_string(),
+            FUSION_TASK_DATA_1.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    // Read data2
+    assert_eq!(
+        client.enforce_data_access(
+            FUSION_TASK.to_string(),
+            FUSION_TASK_DATA_2.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    let mut participants = HashSet::new();
+
+    participants.insert(IRRELEVANT_PARTY.to_string());
+    participants.insert(FUSION_TASK_PARTY_2.to_string());
+    
+    client.announce_task_creation(
+        IRRELEVANT_TASK.to_string(),
+        IRRELEVANT_PARTY.to_string(),
+        &participants,
+    ).expect("irrelevant task creation announcement failed");
+
+    // Launch irrelevant task
+    assert_eq!(
+        client.enforce_task_launch(
+            IRRELEVANT_TASK.to_string(),
+            participants,
+        ).unwrap(),
+        true,
+    );
+
+    // Load fusion script; deny
+    assert_eq!(
+        client.enforce_script_access(
+            IRRELEVANT_TASK.to_string(),
+            FUSION_TASK_SCRIPT.to_string(),
+        ).unwrap(),
+        false,
+    );
+
+    // Load public script; allow
+    assert_eq!(
+        client.enforce_script_access(
+            IRRELEVANT_TASK.to_string(),
+            PUBLIC_SCRIPT.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    // Read data1; deny
+    assert_eq!(
+        client.enforce_data_access(
+            IRRELEVANT_TASK.to_string(),
+            FUSION_TASK_DATA_1.to_string(),
+        ).unwrap(),
+        false,
+    );
+
+    // Read data2; allow
+    assert_eq!(
+        client.enforce_data_access(
+            IRRELEVANT_TASK.to_string(),
+            FUSION_TASK_DATA_2.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    assert_eq!(
+        client.enforce_data_deletion(
+            FUSION_TASK_PARTY_1.to_string(),
+            FUSION_TASK_DATA_1.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    assert_eq!(
+        client.enforce_data_deletion(
+            FUSION_TASK_PARTY_2.to_string(),
+            FUSION_TASK_DATA_2.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    assert_eq!(
+        client.enforce_data_deletion(
+            FUSION_TASK_PARTY_1.to_string(),
+            FUSION_TASK_DATA_2.to_string(),
+        ).unwrap(),
+        false,
+    );
+
+    assert_eq!(
+        client.enforce_script_deletion(
+            FUSION_TASK_PARTY_1.to_string(),
+            FUSION_TASK_SCRIPT.to_string(),
+        ).unwrap(),
+        false,
+    );
+
+    assert_eq!(
+        client.enforce_script_deletion(
+            FUSION_TASK_SCRIPT_WRITER.to_string(),
+            FUSION_TASK_SCRIPT.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+    assert_eq!(
+        client.enforce_script_deletion(
+            IRRELEVANT_PARTY.to_string(),
+            PUBLIC_SCRIPT.to_string(),
+        ).unwrap(),
+        false,
+    );
+
+    assert_eq!(
+        client.enforce_script_deletion(
+            PUBLIC_SCRIPT_WRITER.to_string(),
+            PUBLIC_SCRIPT.to_string(),
+        ).unwrap(),
+        true,
+    );
+
+}
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/common_setup.rs b/tests/functional_test/sgx_trusted_lib/src/tests/common_setup.rs
index b6fa952..92244f3 100644
--- a/tests/functional_test/sgx_trusted_lib/src/tests/common_setup.rs
+++ b/tests/functional_test/sgx_trusted_lib/src/tests/common_setup.rs
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 use kms_client::KMSClient;
+use acs_client::ACSClient;
 use mesatee_core::config;
 use tdfs_internal_client::TDFSClient;
 use tms_internal_client::TMSClient;
@@ -21,6 +22,11 @@ pub(crate) fn setup_kms_internal_client() -> KMSClient {
     KMSClient::new(target).unwrap()
 }
 
+pub(crate) fn setup_acs_internal_client() -> ACSClient {
+    let target = config::Internal::target_acs();
+    ACSClient::new(target).unwrap()
+}
+
 pub(crate) fn setup_tdfs_internal_client() -> TDFSClient {
     let target = config::Internal::target_tdfs();
     TDFSClient::new(target).unwrap()
@@ -30,3 +36,4 @@ pub(crate) fn setup_tms_internal_client() -> TMSClient {
     let target = config::Internal::target_tms();
     TMSClient::new(target).unwrap()
 }
+
diff --git a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs b/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
index dde9bb0..d8b9735 100644
--- a/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
+++ b/tests/functional_test/sgx_trusted_lib/src/tests/mod.rs
@@ -17,3 +17,4 @@ pub mod kms_test;
 pub mod protected_fs_test;
 pub mod tdfs_test;
 pub mod tms_test;
+pub mod acs_test;
diff --git a/third_party/mesapy b/third_party/mesapy
new file mode 160000
index 0000000..544d70a
--- /dev/null
+++ b/third_party/mesapy
@@ -0,0 +1 @@
+Subproject commit 544d70a041643eed8a0f362e9746facb254381af
diff --git a/toolchain_deps/Cargo.sgx_trusted_lib.toml b/toolchain_deps/Cargo.sgx_trusted_lib.toml
index 6124d15..f53d6db 100644
--- a/toolchain_deps/Cargo.sgx_trusted_lib.toml
+++ b/toolchain_deps/Cargo.sgx_trusted_lib.toml
@@ -5,6 +5,7 @@ members = [
   "mesatee_services/fns/sgx_trusted_lib",
   "mesatee_services/tdfs/sgx_trusted_lib",
   "mesatee_services/tms/sgx_trusted_lib",
+  "mesatee_services/acs/sgx_trusted_lib",
   "tests/functional_test/sgx_trusted_lib",
 ]
 
diff --git a/toolchain_deps/Cargo.sgx_untrusted_app.toml b/toolchain_deps/Cargo.sgx_untrusted_app.toml
index c734558..8c8f2f2 100644
--- a/toolchain_deps/Cargo.sgx_untrusted_app.toml
+++ b/toolchain_deps/Cargo.sgx_untrusted_app.toml
@@ -5,6 +5,7 @@ members = [
   "mesatee_services/fns/sgx_app",
   "mesatee_services/tdfs/sgx_app",
   "mesatee_services/tms/sgx_app",
+  "mesatee_services/acs/sgx_app",
   "tests/functional_test/sgx_app",
 ]
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@mesatee.apache.org
For additional commands, e-mail: commits-help@mesatee.apache.org