feat(management): add disable function API (#620)
diff --git a/cmake/scripts/test.sh b/cmake/scripts/test.sh
index 3b18f33..e4e5740 100755
--- a/cmake/scripts/test.sh
+++ b/cmake/scripts/test.sh
@@ -250,6 +250,7 @@
python3 wasm_c_simple_add.py
python3 wasm_rust_psi.py
python3 wasm_tvm_mnist.py
+ python3 test_disable_function.py
popd
pushd ${TEACLAVE_PROJECT_ROOT}/examples/c
diff --git a/examples/python/test_disable_function.py b/examples/python/test_disable_function.py
new file mode 100644
index 0000000..5c51e6d
--- /dev/null
+++ b/examples/python/test_disable_function.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import sys
+
+from teaclave import FunctionInput, FunctionOutput, OwnerList, DataMap
+from utils import USER_ID, USER_PASSWORD, connect_authentication_service, connect_frontend_service, PlatformAdmin
+
+
+class UserData:
+
+ def __init__(self, user_id, password):
+ self.user_id = user_id
+ self.password = password
+
+
+USER_DATA_0 = UserData("user0", "password")
+
+
+class ConfigClient:
+
+ def __init__(self, user_id, user_password):
+ self.user_id = user_id
+ self.user_password = user_password
+ with connect_authentication_service() as client:
+ print(f"[+] {self.user_id} login")
+ token = client.user_login(self.user_id, self.user_password)
+ self.client = connect_frontend_service()
+ metadata = {"id": self.user_id, "token": token}
+ self.client.metadata = metadata
+
+ def register_function(self, func_name):
+ client = self.client
+
+ print(f"[+] {self.user_id} registering function")
+
+ function_id = client.register_function(name=func_name,
+ description=func_name,
+ executor_type="builtin",
+ arguments=["num_user"],
+ inputs=[],
+ outputs=[])
+
+ return function_id
+
+ def list_function(self):
+ client = self.client
+
+ print(f"[+] {self.user_id} list function")
+ functions = client.list_functions(self.user_id)
+ return functions
+
+ def disable_function(self, function_id):
+ client = self.client
+
+ print(f"[+] {self.user_id} disable function")
+ client.disable_function(function_id)
+
+
+def main():
+ platform_admin = PlatformAdmin("admin", "teaclave")
+ try:
+ platform_admin.register_user(USER_DATA_0.user_id, USER_DATA_0.password)
+ except Exception:
+ pass
+
+ config_client = ConfigClient(USER_DATA_0.user_id, USER_DATA_0.password)
+ function_id1 = config_client.register_function("func_test1")
+ function_id2 = config_client.register_function("func_test1")
+ functions = config_client.list_function()
+ func_nums_before = len(functions['registered_functions'])
+ print(f"{func_nums_before} functions registered")
+ print(f"Disable {function_id2}")
+ config_client.disable_function(function_id2)
+ functions = config_client.list_function()
+ func_nums_after = len(functions['registered_functions'])
+ print(f"{func_nums_after} functions registered")
+ assert (func_nums_before == func_nums_after + 1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/sdk/python/teaclave.py b/sdk/python/teaclave.py
index 88080f3..b7a59b5 100644
--- a/sdk/python/teaclave.py
+++ b/sdk/python/teaclave.py
@@ -579,6 +579,14 @@
self.function_id = function_id
+class DisableFunctionRequest(Request):
+
+ def __init__(self, metadata: Metadata, function_id: str):
+ self.request = "disable_function"
+ self.metadata = metadata
+ self.function_id = function_id
+
+
class GetFunctionRequest(Request):
def __init__(self, metadata: Metadata, function_id: str):
@@ -796,6 +804,17 @@
else:
raise TeaclaveException("Failed to delete function")
+ def disable_function(self, function_id: str):
+ self.check_metadata()
+ self.check_channel()
+ request = DisableFunctionRequest(self.metadata, function_id)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
+ if response["result"] == "ok":
+ return response["content"]
+ else:
+ raise TeaclaveException("Failed to disable function")
+
def register_input_file(self, url: str, schema: str, key: List[int],
iv: List[int], cmac: List[int]):
self.check_metadata()
diff --git a/services/frontend/enclave/src/service.rs b/services/frontend/enclave/src/service.rs
index bd0d3bc..924f606 100644
--- a/services/frontend/enclave/src/service.rs
+++ b/services/frontend/enclave/src/service.rs
@@ -28,15 +28,16 @@
use teaclave_proto::teaclave_frontend_service::{
ApproveTaskRequest, ApproveTaskResponse, AssignDataRequest, AssignDataResponse,
CancelTaskRequest, CancelTaskResponse, CreateTaskRequest, CreateTaskResponse,
- DeleteFunctionRequest, DeleteFunctionResponse, GetFunctionRequest, GetFunctionResponse,
- GetInputFileRequest, GetInputFileResponse, GetOutputFileRequest, GetOutputFileResponse,
- GetTaskRequest, GetTaskResponse, InvokeTaskRequest, InvokeTaskResponse, ListFunctionsRequest,
- ListFunctionsResponse, RegisterFunctionRequest, RegisterFunctionResponse,
- RegisterFusionOutputRequest, RegisterFusionOutputResponse, RegisterInputFileRequest,
- RegisterInputFileResponse, RegisterInputFromOutputRequest, RegisterInputFromOutputResponse,
- RegisterOutputFileRequest, RegisterOutputFileResponse, TeaclaveFrontend, UpdateFunctionRequest,
- UpdateFunctionResponse, UpdateInputFileRequest, UpdateInputFileResponse,
- UpdateOutputFileRequest, UpdateOutputFileResponse,
+ DeleteFunctionRequest, DeleteFunctionResponse, DisableFunctionRequest, DisableFunctionResponse,
+ GetFunctionRequest, GetFunctionResponse, GetInputFileRequest, GetInputFileResponse,
+ GetOutputFileRequest, GetOutputFileResponse, GetTaskRequest, GetTaskResponse,
+ InvokeTaskRequest, InvokeTaskResponse, ListFunctionsRequest, ListFunctionsResponse,
+ RegisterFunctionRequest, RegisterFunctionResponse, RegisterFusionOutputRequest,
+ RegisterFusionOutputResponse, RegisterInputFileRequest, RegisterInputFileResponse,
+ RegisterInputFromOutputRequest, RegisterInputFromOutputResponse, RegisterOutputFileRequest,
+ RegisterOutputFileResponse, TeaclaveFrontend, UpdateFunctionRequest, UpdateFunctionResponse,
+ UpdateInputFileRequest, UpdateInputFileResponse, UpdateOutputFileRequest,
+ UpdateOutputFileResponse,
};
use teaclave_proto::teaclave_management_service::TeaclaveManagementClient;
use teaclave_rpc::endpoint::Endpoint;
@@ -108,6 +109,7 @@
UpdateFunction,
ListFunctions,
DeleteFunction,
+ DisableFunction,
CreateTask,
GetTask,
AssignData,
@@ -127,9 +129,10 @@
}
match request {
- Endpoints::RegisterFunction | Endpoints::UpdateFunction | Endpoints::DeleteFunction => {
- role.is_function_owner()
- }
+ Endpoints::RegisterFunction
+ | Endpoints::UpdateFunction
+ | Endpoints::DeleteFunction
+ | Endpoints::DisableFunction => role.is_function_owner(),
Endpoints::RegisterInputFile
| Endpoints::RegisterOutputFile
| Endpoints::UpdateInputFile
@@ -338,6 +341,18 @@
)
}
+ fn disable_function(
+ &self,
+ request: Request<DisableFunctionRequest>,
+ ) -> TeaclaveServiceResponseResult<DisableFunctionResponse> {
+ authentication_and_forward_to_management!(
+ self,
+ request,
+ disable_function,
+ Endpoints::DisableFunction
+ )
+ }
+
fn list_functions(
&self,
request: Request<ListFunctionsRequest>,
diff --git a/services/management/enclave/src/service.rs b/services/management/enclave/src/service.rs
index 55e730d..fd7adbf 100644
--- a/services/management/enclave/src/service.rs
+++ b/services/management/enclave/src/service.rs
@@ -24,15 +24,16 @@
use teaclave_proto::teaclave_frontend_service::{
ApproveTaskRequest, ApproveTaskResponse, AssignDataRequest, AssignDataResponse,
CancelTaskRequest, CancelTaskResponse, CreateTaskRequest, CreateTaskResponse,
- DeleteFunctionRequest, DeleteFunctionResponse, GetFunctionRequest, GetFunctionResponse,
- GetInputFileRequest, GetInputFileResponse, GetOutputFileRequest, GetOutputFileResponse,
- GetTaskRequest, GetTaskResponse, InvokeTaskRequest, InvokeTaskResponse, ListFunctionsRequest,
- ListFunctionsResponse, RegisterFunctionRequest, RegisterFunctionResponse,
- RegisterFusionOutputRequest, RegisterFusionOutputResponse, RegisterInputFileRequest,
- RegisterInputFileResponse, RegisterInputFromOutputRequest, RegisterInputFromOutputResponse,
- RegisterOutputFileRequest, RegisterOutputFileResponse, UpdateFunctionRequest,
- UpdateFunctionResponse, UpdateInputFileRequest, UpdateInputFileResponse,
- UpdateOutputFileRequest, UpdateOutputFileResponse,
+ DeleteFunctionRequest, DeleteFunctionResponse, DisableFunctionRequest, DisableFunctionResponse,
+ GetFunctionRequest, GetFunctionResponse, GetInputFileRequest, GetInputFileResponse,
+ GetOutputFileRequest, GetOutputFileResponse, GetTaskRequest, GetTaskResponse,
+ InvokeTaskRequest, InvokeTaskResponse, ListFunctionsRequest, ListFunctionsResponse,
+ RegisterFunctionRequest, RegisterFunctionResponse, RegisterFusionOutputRequest,
+ RegisterFusionOutputResponse, RegisterInputFileRequest, RegisterInputFileResponse,
+ RegisterInputFromOutputRequest, RegisterInputFromOutputResponse, RegisterOutputFileRequest,
+ RegisterOutputFileResponse, UpdateFunctionRequest, UpdateFunctionResponse,
+ UpdateInputFileRequest, UpdateInputFileResponse, UpdateOutputFileRequest,
+ UpdateOutputFileResponse,
};
use teaclave_proto::teaclave_management_service::TeaclaveManagement;
use teaclave_proto::teaclave_storage_service::{
@@ -394,6 +395,66 @@
Ok(response)
}
+ // access control: function.owner == user_id
+ // disable function
+ // 1. `List functions` does not show this function
+ // 2. `Create new task` with the function id fails
+ fn disable_function(
+ &self,
+ request: Request<DisableFunctionRequest>,
+ ) -> TeaclaveServiceResponseResult<DisableFunctionResponse> {
+ let user_id = self.get_request_user_id(request.metadata())?;
+ let role = self.get_request_role(request.metadata())?;
+
+ let mut function: Function = self
+ .read_from_db(&request.message.function_id)
+ .map_err(|_| TeaclaveManagementServiceError::PermissionDenied)?;
+
+ if role != UserRole::PlatformAdmin {
+ ensure!(
+ function.owner == user_id,
+ TeaclaveManagementServiceError::PermissionDenied
+ );
+ }
+ let func_id = function.external_id().to_string();
+
+ // Updated function owner
+ let mut u = User::default();
+ u.id = function.owner.clone();
+ let external_id = u.external_id();
+ let user: Result<User> = self.read_from_db(&external_id);
+ if let Ok(mut us) = user {
+ us.allowed_functions.retain(|f| !f.eq(&func_id));
+ us.registered_functions.retain(|f| !f.eq(&func_id));
+ self.write_to_db(&us)
+ .map_err(|_| TeaclaveManagementServiceError::StorageError)?;
+ } else {
+ log::warn!("Invalid user id from functions");
+ }
+
+ // Update allowed function list for users
+ for user_id in &function.user_allowlist {
+ let mut u = User::default();
+ u.id = user_id.into();
+ let external_id = u.external_id();
+ let user: Result<User> = self.read_from_db(&external_id);
+ if let Ok(mut us) = user {
+ us.allowed_functions.retain(|f| !f.eq(&func_id));
+ us.registered_functions.retain(|f| !f.eq(&func_id));
+ self.write_to_db(&us)
+ .map_err(|_| TeaclaveManagementServiceError::StorageError)?;
+ } else {
+ log::warn!("Invalid user id from functions");
+ }
+ }
+
+ function.user_allowlist.clear();
+ self.write_to_db(&function)
+ .map_err(|_| TeaclaveManagementServiceError::StorageError)?;
+ let response = DisableFunctionResponse {};
+ Ok(response)
+ }
+
// access contro: user_id = request.user_id
fn list_functions(
&self,
diff --git a/services/proto/src/proto/teaclave_frontend_service.proto b/services/proto/src/proto/teaclave_frontend_service.proto
index 71d3628..31d8fd0 100644
--- a/services/proto/src/proto/teaclave_frontend_service.proto
+++ b/services/proto/src/proto/teaclave_frontend_service.proto
@@ -168,6 +168,13 @@
message DeleteFunctionResponse { }
+message DisableFunctionRequest {
+ string function_id = 1;
+}
+
+message DisableFunctionResponse { }
+
+
message ListFunctionsRequest {
string user_id = 1;
}
@@ -254,6 +261,7 @@
rpc UpdateFunction (UpdateFunctionRequest) returns (UpdateFunctionResponse);
rpc ListFunctions (ListFunctionsRequest) returns (ListFunctionsResponse);
rpc DeleteFunction (DeleteFunctionRequest) returns (DeleteFunctionResponse);
+ rpc DisableFunction (DisableFunctionRequest) returns (DisableFunctionResponse);
rpc CreateTask (CreateTaskRequest) returns (CreateTaskResponse);
rpc GetTask (GetTaskRequest) returns (GetTaskResponse);
rpc AssignData (AssignDataRequest) returns (AssignDataResponse);
diff --git a/services/proto/src/proto/teaclave_management_service.proto b/services/proto/src/proto/teaclave_management_service.proto
index 82c50a4..d79bd42 100644
--- a/services/proto/src/proto/teaclave_management_service.proto
+++ b/services/proto/src/proto/teaclave_management_service.proto
@@ -37,6 +37,7 @@
rpc UpdateFunction (teaclave_frontend_service_proto.UpdateFunctionRequest) returns (teaclave_frontend_service_proto.UpdateFunctionResponse);
rpc GetFunction (teaclave_frontend_service_proto.GetFunctionRequest) returns (teaclave_frontend_service_proto.GetFunctionResponse);
rpc DeleteFunction (teaclave_frontend_service_proto.DeleteFunctionRequest) returns (teaclave_frontend_service_proto.DeleteFunctionResponse);
+ rpc DisableFunction (teaclave_frontend_service_proto.DisableFunctionRequest) returns (teaclave_frontend_service_proto.DisableFunctionResponse);
rpc ListFunctions (teaclave_frontend_service_proto.ListFunctionsRequest) returns (teaclave_frontend_service_proto.ListFunctionsResponse);
rpc CreateTask (teaclave_frontend_service_proto.CreateTaskRequest) returns (teaclave_frontend_service_proto.CreateTaskResponse);
rpc GetTask (teaclave_frontend_service_proto.GetTaskRequest) returns (teaclave_frontend_service_proto.GetTaskResponse);
diff --git a/services/proto/src/teaclave_frontend_service.rs b/services/proto/src/teaclave_frontend_service.rs
index 26d3198..b353ad0 100644
--- a/services/proto/src/teaclave_frontend_service.rs
+++ b/services/proto/src/teaclave_frontend_service.rs
@@ -552,6 +552,23 @@
#[derive(Debug)]
pub struct DeleteFunctionResponse {}
+#[into_request(TeaclaveManagementRequest::DisableFunction)]
+#[into_request(TeaclaveFrontendRequest::DisableFunction)]
+#[derive(Debug)]
+pub struct DisableFunctionRequest {
+ pub function_id: ExternalID,
+}
+
+impl DisableFunctionRequest {
+ pub fn new(function_id: ExternalID) -> Self {
+ Self { function_id }
+ }
+}
+
+#[into_request(TeaclaveManagementResponse::DisableFunction)]
+#[derive(Debug)]
+pub struct DisableFunctionResponse {}
+
#[into_request(TeaclaveManagementRequest::CreateTask)]
#[into_request(TeaclaveFrontendRequest::CreateTask)]
#[derive(Default)]
@@ -1282,6 +1299,20 @@
}
}
+impl std::convert::TryFrom<proto::DisableFunctionResponse> for DisableFunctionResponse {
+ type Error = Error;
+
+ fn try_from(_proto: proto::DisableFunctionResponse) -> Result<Self> {
+ Ok(DisableFunctionResponse {})
+ }
+}
+
+impl From<DisableFunctionResponse> for proto::DisableFunctionResponse {
+ fn from(_response: DisableFunctionResponse) -> Self {
+ Self {}
+ }
+}
+
impl std::convert::TryFrom<proto::ListFunctionsRequest> for ListFunctionsRequest {
type Error = Error;
@@ -1342,6 +1373,25 @@
}
}
+impl std::convert::TryFrom<proto::DisableFunctionRequest> for DisableFunctionRequest {
+ type Error = Error;
+
+ fn try_from(proto: proto::DisableFunctionRequest) -> Result<Self> {
+ let function_id = proto.function_id.try_into()?;
+ let ret = Self { function_id };
+
+ Ok(ret)
+ }
+}
+
+impl From<DisableFunctionRequest> for proto::DisableFunctionRequest {
+ fn from(request: DisableFunctionRequest) -> Self {
+ Self {
+ function_id: request.function_id.to_string(),
+ }
+ }
+}
+
impl std::convert::TryFrom<proto::GetFunctionResponse> for GetFunctionResponse {
type Error = Error;
diff --git a/services/proto/src/teaclave_management_service.rs b/services/proto/src/teaclave_management_service.rs
index c868d59..6f8cbcb 100644
--- a/services/proto/src/teaclave_management_service.rs
+++ b/services/proto/src/teaclave_management_service.rs
@@ -52,6 +52,8 @@
pub type UpdateFunctionResponse = crate::teaclave_frontend_service::UpdateFunctionResponse;
pub type DeleteFunctionRequest = crate::teaclave_frontend_service::DeleteFunctionRequest;
pub type DeleteFunctionResponse = crate::teaclave_frontend_service::DeleteFunctionResponse;
+pub type DisableFunctionRequest = crate::teaclave_frontend_service::DisableFunctionRequest;
+pub type DisableFunctionResponse = crate::teaclave_frontend_service::DisableFunctionResponse;
pub type GetFunctionRequest = crate::teaclave_frontend_service::GetFunctionRequest;
pub type GetFunctionResponse = crate::teaclave_frontend_service::GetFunctionResponse;
pub type ListFunctionsRequest = crate::teaclave_frontend_service::ListFunctionsRequest;
diff --git a/tests/functional/enclave/src/management_service.rs b/tests/functional/enclave/src/management_service.rs
index 765192b..ad016a0 100644
--- a/tests/functional/enclave/src/management_service.rs
+++ b/tests/functional/enclave/src/management_service.rs
@@ -189,6 +189,29 @@
}
#[test_case]
+fn test_disable_function() {
+ let function_input = FunctionInput::new("input", "input_desc", false);
+ let function_output = FunctionOutput::new("output", "output_desc", false);
+ let request = RegisterFunctionRequestBuilder::new()
+ .name("mock_function")
+ .executor_type(ExecutorType::Python)
+ .payload(b"def entrypoint:\n\treturn".to_vec())
+ .public(true)
+ .arguments(vec!["arg"])
+ .inputs(vec![function_input])
+ .outputs(vec![function_output])
+ .build();
+
+ let mut client = authorized_client("mock_user");
+ let response = client.register_function(request);
+ let function_id = response.unwrap().function_id;
+
+ let request = DisableFunctionRequest::new(function_id);
+ let response = client.disable_function(request);
+ assert!(response.is_ok());
+}
+
+#[test_case]
fn test_update_function() {
let function_input = FunctionInput::new("input", "input_desc", false);
let function_output = FunctionOutput::new("output", "output_desc", false);