blob: d2ac9b21d312ada3d7c62c9f85fd35e7a0029fe2 [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, Result};
use std::sync::Arc;
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::transport::{channel::Endpoint, Channel};
use teaclave_rpc::Request;
use teaclave_service_enclave_utils::bail;
use teaclave_types::{TeaclaveServiceResponseResult, UserAuthClaims, UserRole};
use tokio::sync::Mutex;
#[derive(Clone)]
pub(crate) struct TeaclaveFrontendService {
authentication_client: Arc<Mutex<TeaclaveAuthenticationInternalClient<Channel>>>,
management_client: Arc<Mutex<TeaclaveManagementClient<Channel>>>,
}
macro_rules! authentication_and_forward_to_management {
($service: ident, $request: ident, $func: ident, $endpoint: expr) => {{
let claims = match $service.authenticate(&$request).await {
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().await;
let meta = $request.metadata().clone();
let message = $request.get_ref().to_owned();
let mut request = Request::new(message);
let metadata = request.metadata_mut();
*metadata = meta;
metadata.insert("role", claims.role.parse().unwrap());
let response = client.$func(request).await?;
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) async fn new(
authentication_service_endpoint: Endpoint,
management_service_endpoint: Endpoint,
) -> Result<Self> {
let authentication_channel = authentication_service_endpoint
.connect()
.await
.map_err(|e| anyhow!("Failed to connect to authentication service, retry {:?}", e))?;
let authentication_client = Arc::new(Mutex::new(
TeaclaveAuthenticationInternalClient::new(authentication_channel),
));
let management_channel = management_service_endpoint
.connect()
.await
.map_err(|e| anyhow!("Failed to connect to management service, {:?}", e))?;
let management_client = Arc::new(Mutex::new(TeaclaveManagementClient::new(
management_channel,
)));
Ok(Self {
authentication_client,
management_client,
})
}
}
#[teaclave_rpc::async_trait]
impl TeaclaveFrontend for TeaclaveFrontendService {
async fn register_input_file(
&self,
request: Request<RegisterInputFileRequest>,
) -> TeaclaveServiceResponseResult<RegisterInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
register_input_file,
Endpoints::RegisterInputFile
)
}
async fn update_input_file(
&self,
request: Request<UpdateInputFileRequest>,
) -> TeaclaveServiceResponseResult<UpdateInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
update_input_file,
Endpoints::UpdateInputFile
)
}
async fn register_output_file(
&self,
request: Request<RegisterOutputFileRequest>,
) -> TeaclaveServiceResponseResult<RegisterOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
register_output_file,
Endpoints::RegisterOutputFile
)
}
async fn update_output_file(
&self,
request: Request<UpdateOutputFileRequest>,
) -> TeaclaveServiceResponseResult<UpdateOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
update_output_file,
Endpoints::UpdateOutputFile
)
}
async fn register_fusion_output(
&self,
request: Request<RegisterFusionOutputRequest>,
) -> TeaclaveServiceResponseResult<RegisterFusionOutputResponse> {
authentication_and_forward_to_management!(
self,
request,
register_fusion_output,
Endpoints::RegisterFusionOutput
)
}
async fn register_input_from_output(
&self,
request: Request<RegisterInputFromOutputRequest>,
) -> TeaclaveServiceResponseResult<RegisterInputFromOutputResponse> {
authentication_and_forward_to_management!(
self,
request,
register_input_from_output,
Endpoints::RegisterInputFromOutput
)
}
async fn get_output_file(
&self,
request: Request<GetOutputFileRequest>,
) -> TeaclaveServiceResponseResult<GetOutputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
get_output_file,
Endpoints::GetOutputFile
)
}
async fn get_input_file(
&self,
request: Request<GetInputFileRequest>,
) -> TeaclaveServiceResponseResult<GetInputFileResponse> {
authentication_and_forward_to_management!(
self,
request,
get_input_file,
Endpoints::GetInputFile
)
}
async fn register_function(
&self,
request: Request<RegisterFunctionRequest>,
) -> TeaclaveServiceResponseResult<RegisterFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
register_function,
Endpoints::RegisterFunction
)
}
async fn update_function(
&self,
request: Request<UpdateFunctionRequest>,
) -> TeaclaveServiceResponseResult<UpdateFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
update_function,
Endpoints::UpdateFunction
)
}
async fn get_function(
&self,
request: Request<GetFunctionRequest>,
) -> TeaclaveServiceResponseResult<GetFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
get_function,
Endpoints::GetFunction
)
}
async fn get_function_usage_stats(
&self,
request: Request<GetFunctionUsageStatsRequest>,
) -> TeaclaveServiceResponseResult<GetFunctionUsageStatsResponse> {
authentication_and_forward_to_management!(
self,
request,
get_function_usage_stats,
Endpoints::GetFunctionUsageStats
)
}
async fn delete_function(
&self,
request: Request<DeleteFunctionRequest>,
) -> TeaclaveServiceResponseResult<DeleteFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
delete_function,
Endpoints::DeleteFunction
)
}
async fn disable_function(
&self,
request: Request<DisableFunctionRequest>,
) -> TeaclaveServiceResponseResult<DisableFunctionResponse> {
authentication_and_forward_to_management!(
self,
request,
disable_function,
Endpoints::DisableFunction
)
}
async fn list_functions(
&self,
request: Request<ListFunctionsRequest>,
) -> TeaclaveServiceResponseResult<ListFunctionsResponse> {
authentication_and_forward_to_management!(
self,
request,
list_functions,
Endpoints::ListFunctions
)
}
async fn create_task(
&self,
request: Request<CreateTaskRequest>,
) -> TeaclaveServiceResponseResult<CreateTaskResponse> {
authentication_and_forward_to_management!(self, request, create_task, Endpoints::CreateTask)
}
async fn get_task(
&self,
request: Request<GetTaskRequest>,
) -> TeaclaveServiceResponseResult<GetTaskResponse> {
authentication_and_forward_to_management!(self, request, get_task, Endpoints::GetTask)
}
async fn assign_data(
&self,
request: Request<AssignDataRequest>,
) -> TeaclaveServiceResponseResult<AssignDataResponse> {
authentication_and_forward_to_management!(self, request, assign_data, Endpoints::AssignData)
}
async fn approve_task(
&self,
request: Request<ApproveTaskRequest>,
) -> TeaclaveServiceResponseResult<ApproveTaskResponse> {
authentication_and_forward_to_management!(
self,
request,
approve_task,
Endpoints::ApproveTask
)
}
async fn invoke_task(
&self,
request: Request<InvokeTaskRequest>,
) -> TeaclaveServiceResponseResult<InvokeTaskResponse> {
authentication_and_forward_to_management!(self, request, invoke_task, Endpoints::InvokeTask)
}
async fn cancel_task(
&self,
request: Request<CancelTaskRequest>,
) -> TeaclaveServiceResponseResult<CancelTaskResponse> {
authentication_and_forward_to_management!(self, request, cancel_task, Endpoints::CancelTask)
}
}
impl TeaclaveFrontendService {
async fn authenticate<T>(
&self,
request: &Request<T>,
) -> Result<UserAuthClaims, FrontendServiceError> {
let id = request
.metadata()
.get("id")
.and_then(|x| x.to_str().ok())
.ok_or(AuthenticationError::MissingUserId)?;
let token = request
.metadata()
.get("token")
.and_then(|x| x.to_str().ok())
.ok_or(AuthenticationError::MissingToken)?;
let credential = Some(UserCredential::new(id, token));
let auth_request = UserAuthenticateRequest { credential };
let claims = self
.authentication_client
.clone()
.lock()
.await
.user_authenticate(auth_request)
.await
.map_err(|_| AuthenticationError::IncorrectCredential)?
.into_inner()
.claims
.and_then(|x| x.try_into().ok())
.ok_or(AuthenticationError::IncorrectCredential)?;
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);
}
}