blob: bc072769c99af5af56f64470c6e6ec082fb9e004 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::error::AuthenticationError;
use crate::error::FrontendServiceError;
use anyhow::anyhow;
use anyhow::ensure;
use anyhow::Result;
use std::sync::{Arc, Mutex};
use teaclave_proto::teaclave_authentication_service::{
TeaclaveAuthenticationInternalClient, UserAuthenticateRequest,
};
use teaclave_proto::teaclave_common::UserCredential;
use teaclave_proto::teaclave_frontend_service::{
ApproveTaskRequest, ApproveTaskResponse, AssignDataRequest, AssignDataResponse,
CancelTaskRequest, CancelTaskResponse, CreateTaskRequest, CreateTaskResponse,
DeleteFunctionRequest, DeleteFunctionResponse, DisableFunctionRequest, DisableFunctionResponse,
GetFunctionRequest, GetFunctionResponse, GetFunctionUsageStatsRequest,
GetFunctionUsageStatsResponse, 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;
use teaclave_rpc::Request;
use teaclave_service_enclave_utils::{bail, teaclave_service};
use teaclave_types::{TeaclaveServiceResponseResult, UserAuthClaims, UserRole};
#[teaclave_service(teaclave_frontend_service, TeaclaveFrontend, FrontendServiceError)]
#[derive(Clone)]
pub(crate) struct TeaclaveFrontendService {
authentication_client: Arc<Mutex<TeaclaveAuthenticationInternalClient>>,
management_client: Arc<Mutex<TeaclaveManagementClient>>,
}
macro_rules! authentication_and_forward_to_management {
($service: ident, $request: ident, $func: ident, $endpoint: expr) => {{
let claims = match $service.authenticate(&$request) {
Ok(claims) => {
if authorize(&claims, $endpoint) {
claims
} else {
log::debug!(
"User is not authorized to access endpoint: {}, func: {}",
stringify!($endpoint),
stringify!($func)
);
bail!(FrontendServiceError::PermissionDenied);
}
}
Err(e) => {
log::debug!(
"User is not authenticated to access endpoint: {}, func: {}",
stringify!($endpoint),
stringify!($func)
);
bail!(e);
}
};
let client = $service.management_client.clone();
let mut client = client.lock().map_err(|_| {
FrontendServiceError::Service(anyhow!("failed to lock management client"))
})?;
client.metadata_mut().clear();
client.metadata_mut().extend($request.metadata);
client
.metadata_mut()
.insert("role".to_string(), claims.role);
let response = client.$func($request.message);
client.metadata_mut().clear();
let response = response?;
Ok(response)
}};
}
enum Endpoints {
RegisterInputFile,
RegisterOutputFile,
UpdateInputFile,
UpdateOutputFile,
RegisterFusionOutput,
RegisterInputFromOutput,
GetOutputFile,
GetInputFile,
RegisterFunction,
GetFunction,
GetFunctionUsageStats,
UpdateFunction,
ListFunctions,
DeleteFunction,
DisableFunction,
CreateTask,
GetTask,
AssignData,
ApproveTask,
InvokeTask,
CancelTask,
}
fn authorize(claims: &UserAuthClaims, request: Endpoints) -> bool {
let role = claims.get_role();
if role == UserRole::Invalid {
return false;
}
if role == UserRole::PlatformAdmin {
return true;
}
match request {
Endpoints::RegisterFunction
| Endpoints::UpdateFunction
| Endpoints::DeleteFunction
| Endpoints::DisableFunction => role.is_function_owner(),
Endpoints::RegisterInputFile
| Endpoints::RegisterOutputFile
| Endpoints::UpdateInputFile
| Endpoints::UpdateOutputFile
| Endpoints::RegisterFusionOutput
| Endpoints::RegisterInputFromOutput
| Endpoints::GetOutputFile
| Endpoints::GetInputFile
| Endpoints::CreateTask
| Endpoints::GetTask
| Endpoints::AssignData
| Endpoints::ApproveTask
| Endpoints::InvokeTask
| Endpoints::CancelTask => role.is_data_owner(),
Endpoints::GetFunction | Endpoints::ListFunctions | Endpoints::GetFunctionUsageStats => {
role.is_function_owner() || role.is_data_owner()
}
}
}
impl TeaclaveFrontendService {
pub(crate) fn new(
authentication_service_endpoint: Endpoint,
management_service_endpoint: Endpoint,
) -> Result<Self> {
let mut i = 0;
let authentication_channel = loop {
match authentication_service_endpoint.connect() {
Ok(channel) => break channel,
Err(_) => {
ensure!(i < 10, "failed to connect to authentication service");
log::warn!("Failed to connect to authentication service, retry {}", i);
i += 1;
}
}
std::thread::sleep(std::time::Duration::from_secs(3));
};
let authentication_client = Arc::new(Mutex::new(
TeaclaveAuthenticationInternalClient::new(authentication_channel)?,
));
let mut i = 0;
let management_channel = loop {
match management_service_endpoint.connect() {
Ok(channel) => break channel,
Err(_) => {
ensure!(i < 10, "failed to connect to management service");
log::warn!("Failed to connect to management service, retry {}", i);
i += 1;
}
}
std::thread::sleep(std::time::Duration::from_secs(3));
};
let management_client = Arc::new(Mutex::new(TeaclaveManagementClient::new(
management_channel,
)?));
Ok(Self {
authentication_client,
management_client,
})
}
}
impl TeaclaveFrontend for TeaclaveFrontendService {
fn register_input_file(
&self,
request: Request<RegisterInputFileRequest>,
) -> TeaclaveServiceResponseResult<RegisterInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
register_input_file,
Endpoints::RegisterInputFile
)
}
fn update_input_file(
&self,
request: Request<UpdateInputFileRequest>,
) -> TeaclaveServiceResponseResult<UpdateInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
update_input_file,
Endpoints::UpdateInputFile
)
}
fn register_output_file(
&self,
request: Request<RegisterOutputFileRequest>,
) -> TeaclaveServiceResponseResult<RegisterOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
register_output_file,
Endpoints::RegisterOutputFile
)
}
fn update_output_file(
&self,
request: Request<UpdateOutputFileRequest>,
) -> TeaclaveServiceResponseResult<UpdateOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
update_output_file,
Endpoints::UpdateOutputFile
)
}
fn register_fusion_output(
&self,
request: Request<RegisterFusionOutputRequest>,
) -> TeaclaveServiceResponseResult<RegisterFusionOutputResponse> {
authentication_and_forward_to_management!(
self,
request,
register_fusion_output,
Endpoints::RegisterFusionOutput
)
}
fn register_input_from_output(
&self,
request: Request<RegisterInputFromOutputRequest>,
) -> TeaclaveServiceResponseResult<RegisterInputFromOutputResponse> {
authentication_and_forward_to_management!(
self,
request,
register_input_from_output,
Endpoints::RegisterInputFromOutput
)
}
fn get_output_file(
&self,
request: Request<GetOutputFileRequest>,
) -> TeaclaveServiceResponseResult<GetOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
get_output_file,
Endpoints::GetOutputFile
)
}
fn get_input_file(
&self,
request: Request<GetInputFileRequest>,
) -> TeaclaveServiceResponseResult<GetInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
get_input_file,
Endpoints::GetInputFile
)
}
fn register_function(
&self,
request: Request<RegisterFunctionRequest>,
) -> TeaclaveServiceResponseResult<RegisterFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
register_function,
Endpoints::RegisterFunction
)
}
fn update_function(
&self,
request: Request<UpdateFunctionRequest>,
) -> TeaclaveServiceResponseResult<UpdateFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
update_function,
Endpoints::UpdateFunction
)
}
fn get_function(
&self,
request: Request<GetFunctionRequest>,
) -> TeaclaveServiceResponseResult<GetFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
get_function,
Endpoints::GetFunction
)
}
fn get_function_usage_stats(
&self,
request: Request<GetFunctionUsageStatsRequest>,
) -> TeaclaveServiceResponseResult<GetFunctionUsageStatsResponse> {
authentication_and_forward_to_management!(
self,
request,
get_function_usage_stats,
Endpoints::GetFunctionUsageStats
)
}
fn delete_function(
&self,
request: Request<DeleteFunctionRequest>,
) -> TeaclaveServiceResponseResult<DeleteFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
delete_function,
Endpoints::DeleteFunction
)
}
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>,
) -> TeaclaveServiceResponseResult<ListFunctionsResponse> {
authentication_and_forward_to_management!(
self,
request,
list_functions,
Endpoints::ListFunctions
)
}
fn create_task(
&self,
request: Request<CreateTaskRequest>,
) -> TeaclaveServiceResponseResult<CreateTaskResponse> {
authentication_and_forward_to_management!(self, request, create_task, Endpoints::CreateTask)
}
fn get_task(
&self,
request: Request<GetTaskRequest>,
) -> TeaclaveServiceResponseResult<GetTaskResponse> {
authentication_and_forward_to_management!(self, request, get_task, Endpoints::GetTask)
}
fn assign_data(
&self,
request: Request<AssignDataRequest>,
) -> TeaclaveServiceResponseResult<AssignDataResponse> {
authentication_and_forward_to_management!(self, request, assign_data, Endpoints::AssignData)
}
fn approve_task(
&self,
request: Request<ApproveTaskRequest>,
) -> TeaclaveServiceResponseResult<ApproveTaskResponse> {
authentication_and_forward_to_management!(
self,
request,
approve_task,
Endpoints::ApproveTask
)
}
fn invoke_task(
&self,
request: Request<InvokeTaskRequest>,
) -> TeaclaveServiceResponseResult<InvokeTaskResponse> {
authentication_and_forward_to_management!(self, request, invoke_task, Endpoints::InvokeTask)
}
fn cancel_task(
&self,
request: Request<CancelTaskRequest>,
) -> TeaclaveServiceResponseResult<CancelTaskResponse> {
authentication_and_forward_to_management!(self, request, cancel_task, Endpoints::CancelTask)
}
}
impl TeaclaveFrontendService {
fn authenticate<T>(
&self,
request: &Request<T>,
) -> Result<UserAuthClaims, FrontendServiceError> {
let id = request
.metadata
.get("id")
.ok_or(AuthenticationError::MissingUserId)?;
let token = request
.metadata
.get("token")
.ok_or(AuthenticationError::MissingToken)?;
let credential = UserCredential::new(id, token);
let auth_request = UserAuthenticateRequest { credential };
let claims = self
.authentication_client
.clone()
.lock()
.map_err(|_| {
FrontendServiceError::Service(anyhow!("failed to lock authentication client"))
})?
.user_authenticate(auth_request)
.map_err(|_| AuthenticationError::IncorrectCredential)?
.claims;
Ok(claims)
}
}
#[cfg(feature = "enclave_unit_test")]
pub mod tests {
use super::*;
pub fn test_authorize_platform_admin() {
let claims = UserAuthClaims {
role: "PlatformAdmin".to_string(),
..Default::default()
};
let result = authorize(&claims, Endpoints::GetFunction);
assert!(result);
}
pub fn test_authorize_function_owner() {
let claims = UserAuthClaims {
role: "FunctionOwner".to_string(),
..Default::default()
};
let result = authorize(&claims, Endpoints::GetFunction);
assert!(result);
let result = authorize(&claims, Endpoints::RegisterFunction);
assert!(result);
let result = authorize(&claims, Endpoints::UpdateFunction);
assert!(result);
let result = authorize(&claims, Endpoints::InvokeTask);
assert!(!result);
}
pub fn test_authorize_data_owner() {
let claims = UserAuthClaims {
role: "DataOwnerManager-Attribute".to_string(),
..Default::default()
};
let result = authorize(&claims, Endpoints::GetFunction);
assert!(result);
let result = authorize(&claims, Endpoints::InvokeTask);
assert!(result);
let claims = UserAuthClaims {
role: "DataOwner-Attribute".to_string(),
..Default::default()
};
let result = authorize(&claims, Endpoints::GetFunction);
assert!(result);
let result = authorize(&claims, Endpoints::InvokeTask);
assert!(result);
}
}