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 19:14:23 UTC

[incubator-mesatee] branch acs updated (fe63446 -> 0e5ec66)

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

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


 discard fe63446  WIP: access control service
     new 0e5ec66  WIP: access control service

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (fe63446)
            \
             N -- N -- N   refs/heads/acs (0e5ec66)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CMakeLists.txt | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)


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


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

Posted by ur...@apache.org.
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

View the commit online:
https://github.com/apache/incubator-mesatee/commit/0e5ec66d6d46251e3d19117a7c04015ddce1eac8

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

    WIP: access control service
---
 .gitmodules                                        |   4 +
 CMakeLists.txt                                     |  23 +-
 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, 1971 insertions(+), 29 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..1854bf9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,9 +20,9 @@ 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)
+# set(SGX_MODULES kms tdfs tms fns acs functional_test)
 # ================ VARIABLES FOR MANUAL CHANGE END ================
 
 # AUTO-GENERATE VARS
@@ -61,12 +61,29 @@ foreach(package_name ${SGX_MODULES})
     )
 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)
+target_compile_definitions(pycomponent PUBLIC SGX)
+target_compile_options(pycomponent PUBLIC -UWITH_THREAD -O2 -fPIC -Wimplicit -I/usr/lib/pypy/include)
+
+
 # sgx_trusted_lib
 foreach(package_path ${SGX_MODULE_PATHS})
     set(_package_name)
     get_filename_component(_package_name ${package_path} NAME)
     add_sgx_build_target(${package_path}
-        DEPENDS prep ${TARGET_CONFIG_GEN} "${SGXAPP_PREFIX}-${_package_name}"
+        DEPENDS prep ${TARGET_CONFIG_GEN} "${SGXAPP_PREFIX}-${_package_name}" pycomponent
     )
 endforeach()
 
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