From 75c19bd25dc7ca538e62cac01e29cdec34d00723 Mon Sep 17 00:00:00 2001 From: Kartik Joshi Date: Mon, 15 Jan 2024 20:43:15 +0530 Subject: [PATCH] attestation-service: Replace anyhow error crate with thiserror crate Fixes: #231 Signed-off-by: Kartik Joshi --- .../attestation-service/src/bin/grpc/mod.rs | 22 ++++--- .../attestation-service/src/bin/restful-as.rs | 40 +++++++----- .../attestation-service/src/config.rs | 21 ++++--- .../attestation-service/src/lib.rs | 63 ++++++++++++------- .../attestation-service/src/rvps/builtin.rs | 14 +++-- .../attestation-service/src/rvps/grpc.rs | 10 +-- .../attestation-service/src/rvps/mod.rs | 28 +++++++-- 7 files changed, 133 insertions(+), 65 deletions(-) diff --git a/attestation-service/attestation-service/src/bin/grpc/mod.rs b/attestation-service/attestation-service/src/bin/grpc/mod.rs index cd22fa7617..9ebd2f77d0 100644 --- a/attestation-service/attestation-service/src/bin/grpc/mod.rs +++ b/attestation-service/attestation-service/src/bin/grpc/mod.rs @@ -1,7 +1,7 @@ -use anyhow::{anyhow, Result}; +use thiserror::Error; use attestation_service::policy_engine::SetPolicyInput; use attestation_service::HashAlgorithm; -use attestation_service::{config::Config, AttestationService as Service, Tee}; +use attestation_service::{config::Config, config::ConfigError, AttestationService as Service, Tee, ServiceError}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use log::{debug, info}; @@ -40,15 +40,22 @@ fn to_kbs_tee(tee: GrpcTee) -> Tee { } } +#[derive(Error, Debug)] +pub enum GrpcError { + #[error("Read AS config file failed: {0}")] + Config(#[from] ConfigError), + #[error("Creating attestation service failed: {0}")] + Service(#[from] ServiceError), +} + pub struct AttestationServer { attestation_service: Service, } impl AttestationServer { - pub async fn new(config_path: Option) -> Result { + pub async fn new(config_path: Option) -> Result { let config = match config_path { - Some(path) => Config::try_from(Path::new(&path)) - .map_err(|e| anyhow!("Read AS config file failed: {:?}", e))?, + Some(path) => Config::try_from(Path::new(&path))?, None => Config::default(), }; @@ -222,7 +229,7 @@ impl ReferenceValueProviderService for Arc> { } } -pub async fn start(socket: SocketAddr, config_path: Option) -> Result<()> { +pub async fn start(socket: SocketAddr, config_path: Option) -> Result<(), GrpcError> { info!("Listen socket: {}", &socket); let attestation_server = Arc::new(RwLock::new(AttestationServer::new(config_path).await?)); @@ -231,6 +238,7 @@ pub async fn start(socket: SocketAddr, config_path: Option) -> Result<() .add_service(AttestationServiceServer::new(attestation_server.clone())) .add_service(ReferenceValueProviderServiceServer::new(attestation_server)) .serve(socket) - .await?; + .await + .map_err(|e| ServiceError::StartService(e.to_string()))?; Ok(()) } diff --git a/attestation-service/attestation-service/src/bin/restful-as.rs b/attestation-service/attestation-service/src/bin/restful-as.rs index de797f7e8e..79a988e26f 100644 --- a/attestation-service/attestation-service/src/bin/restful-as.rs +++ b/attestation-service/attestation-service/src/bin/restful-as.rs @@ -1,8 +1,9 @@ use std::{net::SocketAddr, path::Path, sync::Arc}; use actix_web::{web, App, HttpServer}; -use anyhow::{anyhow, Context, Result}; -use attestation_service::{config::Config, AttestationService}; +use thiserror::Error; +use anyhow::Result; +use attestation_service::{config::Config, AttestationService, ServiceError, config::ConfigError}; use clap::{arg, command, Parser}; use log::info; use openssl::{ @@ -49,8 +50,22 @@ enum WebApi { Policy, } +#[derive(Error, Debug)] +pub enum RestfulError { + #[error("Creating service failed: {0}: {0}")] + Service(#[from] ServiceError), + #[error("Failed to read AS config file: {0}")] + Config(#[from] ConfigError), + #[error("Openssl errorstack: {0}")] + Openssl(#[from] openssl::error::ErrorStack), + #[error("io error")] + IO(#[from] std::io::Error), + #[error("Failed to start server: {0}")] + StartServer(String), +} + #[actix_web::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), RestfulError> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let cli = Cli::parse(); @@ -58,8 +73,7 @@ async fn main() -> Result<()> { let config = match cli.config_file { Some(path) => { info!("Using config file {path}"); - Config::try_from(Path::new(&path)) - .map_err(|e| anyhow!("Read AS config file failed: {:?}", e))? + Config::try_from(Path::new(&path))? } None => { info!("No confile path provided, use default one."); @@ -82,19 +96,17 @@ async fn main() -> Result<()> { let mut builder = SslAcceptor::mozilla_modern(SslMethod::tls())?; let prikey = tokio::fs::read(prikey) - .await - .context("read HTTPS private key")?; + .await?; let prikey = - PKey::private_key_from_pem(&prikey).context("read HTTPS private key from pem")?; + PKey::private_key_from_pem(&prikey)?; builder - .set_private_key(&prikey) - .context("set private key failed")?; + .set_private_key(&prikey)?; builder - .set_certificate_chain_file(pubkey_cert) - .context("set HTTPS public key cert")?; + .set_certificate_chain_file(pubkey_cert)?; log::info!("starting HTTPS server at https://{}", cli.socket); - server.bind_openssl(cli.socket, builder)?.run() + server.bind_openssl(cli.socket, builder)? + .run() } _ => { log::info!("starting HTTP server at http://{}", cli.socket); @@ -104,7 +116,7 @@ async fn main() -> Result<()> { } }; - server.await?; + server.await.map_err(|e| RestfulError::StartServer(e.to_string()))?; Ok(()) } diff --git a/attestation-service/attestation-service/src/config.rs b/attestation-service/attestation-service/src/config.rs index 814136ad64..60a64aa5af 100644 --- a/attestation-service/attestation-service/src/config.rs +++ b/attestation-service/attestation-service/src/config.rs @@ -3,7 +3,7 @@ use crate::{ token::{AttestationTokenBrokerType, AttestationTokenConfig}, }; -use anyhow::{anyhow, Result}; +use thiserror::Error; use serde::Deserialize; use std::convert::TryFrom; use std::fs::File; @@ -34,6 +34,14 @@ pub struct Config { pub attestation_token_config: AttestationTokenConfig, } +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("io error")] + IO(#[from] std::io::Error), + #[error("Serde Json Error")] + SerdeJson(#[from] serde_json::Error), +} + impl Default for Config { // Construct a default instance of `Config` fn default() -> Config { @@ -65,12 +73,9 @@ impl TryFrom<&Path> for Config { /// "duration_min": 5 /// } /// } - type Error = anyhow::Error; - fn try_from(config_path: &Path) -> Result { - let file = File::open(config_path) - .map_err(|e| anyhow!("failed to open AS config file {}", e.to_string()))?; - - serde_json::from_reader::(file) - .map_err(|e| anyhow!("failed to parse AS config file {}", e.to_string())) + type Error = ConfigError; + fn try_from(config_path: &Path) -> Result { + let file = File::open(config_path)?; + Ok(serde_json::from_reader::(file)?) } } diff --git a/attestation-service/attestation-service/src/lib.rs b/attestation-service/attestation-service/src/lib.rs index 85370f72b7..344af2795a 100644 --- a/attestation-service/attestation-service/src/lib.rs +++ b/attestation-service/attestation-service/src/lib.rs @@ -12,12 +12,13 @@ mod utils; use crate::token::AttestationTokenBroker; -use anyhow::{anyhow, Context, Result}; +use anyhow::Result; +use thiserror::Error; use config::Config; pub use kbs_types::{Attestation, Tee}; use log::debug; use policy_engine::{PolicyEngine, PolicyEngineType, SetPolicyInput}; -use rvps::RvpsApi; +use rvps::{RvpsApi, RvpsError}; use serde_json::{json, Value}; use serde_variant::to_variant_name; use sha2::{Digest, Sha256, Sha384, Sha512}; @@ -77,6 +78,24 @@ pub enum Data { Structured(Value), } +#[derive(Error, Debug)] +pub enum ServiceError { + #[error("io error: {0}")] + IO(#[from] std::io::Error), + #[error("Parse error:{0}")] + ParseError(#[from] strum::ParseError), + #[error("Policy Engine {0} is not supported")] + UnsupportedPolicyEngine(String), + #[error("Create rvps failed.")] + Rvps(#[from] RvpsError), + #[error("Set Policy failed {0}")] + SetPolicyFailed(String), + #[error("Starting service failed: {0}")] + StartService(String), + #[error("Anyhow error: {0}")] + Anyhow(#[from] anyhow::Error) +} + pub struct AttestationService { _config: Config, policy_engine: Box, @@ -86,22 +105,20 @@ pub struct AttestationService { impl AttestationService { /// Create a new Attestation Service instance. - pub async fn new(config: Config) -> Result { + pub async fn new(config: Config) -> Result { if !config.work_dir.as_path().exists() { fs::create_dir_all(&config.work_dir) - .await - .context("Create AS work dir failed: {:?}")?; + .await?; } - let policy_engine = PolicyEngineType::from_str(&config.policy_engine) - .map_err(|_| anyhow!("Policy Engine {} is not supported", &config.policy_engine))? - .to_policy_engine(config.work_dir.as_path())?; - + let policy_engine = PolicyEngineType::from_str(&config.policy_engine)? + .to_policy_engine(config.work_dir.as_path()) + .map_err(|e| ServiceError::UnsupportedPolicyEngine(e.to_string()))?; + let rvps = config .rvps_config .to_rvps() - .await - .context("create rvps failed.")?; + .await?; let token_broker = config .attestation_token_broker @@ -116,11 +133,13 @@ impl AttestationService { } /// Set Attestation Verification Policy. - pub async fn set_policy(&mut self, input: SetPolicyInput) -> Result<()> { + pub async fn set_policy(&mut self, input: SetPolicyInput) -> Result<(), ServiceError> { self.policy_engine .set_policy(input) .await - .map_err(|e| anyhow!("Cannot Set Policy: {:?}", e)) + .map_err(|e| ServiceError::SetPolicyFailed(e.to_string()))?; + + Ok(()) } /// Evaluate Attestation Evidence. @@ -153,7 +172,7 @@ impl AttestationService { let verifier = verifier::to_verifier(&tee)?; let (report_data, runtime_data_claims) = - parse_data(runtime_data, &runtime_data_hash_algorithm).context("parse runtime data")?; + parse_data(runtime_data, &runtime_data_hash_algorithm)?; let report_data = match &report_data { Some(data) => ReportData::Value(data), @@ -161,7 +180,7 @@ impl AttestationService { }; let (init_data, init_data_claims) = - parse_data(init_data, &init_data_hash_algorithm).context("parse init data")?; + parse_data(init_data, &init_data_hash_algorithm)?; let init_data_hash = match &init_data { Some(data) => InitDataHash::Value(data), @@ -170,8 +189,7 @@ impl AttestationService { let claims_from_tee_evidence = verifier .evaluate(&evidence, &report_data, &init_data_hash) - .await - .map_err(|e| anyhow!("Verifier evaluate failed: {e:?}"))?; + .await?; let flattened_claims = flatten_claims(tee, &claims_from_tee_evidence)?; @@ -179,14 +197,12 @@ impl AttestationService { let reference_data_map = self .get_reference_data(flattened_claims.keys()) - .await - .map_err(|e| anyhow!("Generate reference data failed: {:?}", e))?; + .await?; let evaluation_report = self .policy_engine .evaluate(reference_data_map.clone(), tcb_json, policy_ids.clone()) - .await - .map_err(|e| anyhow!("Policy Engine evaluation failed: {e}"))?; + .await?; let policies: Vec<_> = evaluation_report .into_iter() @@ -215,7 +231,8 @@ impl AttestationService { }, }); - let attestation_results_token = self.token_broker.issue(token_claims)?; + let attestation_results_token = self.token_broker + .issue(token_claims)?; Ok(attestation_results_token) } @@ -253,7 +270,7 @@ fn parse_data( Data::Structured(structured) => { // by default serde_json will enforence the alphabet order for keys let hash_materials = - serde_json::to_vec(&structured).context("parse JSON structured data")?; + serde_json::to_vec(&structured)?; let digest = hash_algorithm.accumulate_hash(hash_materials); Ok((Some(digest), structured)) } diff --git a/attestation-service/attestation-service/src/rvps/builtin.rs b/attestation-service/attestation-service/src/rvps/builtin.rs index 49fca776e9..8bfadc8118 100644 --- a/attestation-service/attestation-service/src/rvps/builtin.rs +++ b/attestation-service/attestation-service/src/rvps/builtin.rs @@ -1,6 +1,7 @@ use anyhow::*; use async_trait::async_trait; use reference_value_provider_service::Core; +use crate::rvps::RvpsError; use super::RvpsApi; @@ -9,16 +10,21 @@ pub struct Rvps { } impl Rvps { - pub fn new(store_type: &str) -> Result { - let core = Core::new(store_type)?; - Ok(Self { core }) + pub fn new(store_type: &str) -> Result { + Core::new(store_type) + .map(|core| Self::from_core(core)) + .map_err(|error| RvpsError::CreateRvps(error.to_string())) + } + + fn from_core(core: Core) -> Self { + Self { core } } } #[async_trait] impl RvpsApi for Rvps { async fn verify_and_extract(&mut self, message: &str) -> Result<()> { - self.core.verify_and_extract(message).await + Ok(self.core.verify_and_extract(message).await?) } async fn get_digests(&self, name: &str) -> Result> { diff --git a/attestation-service/attestation-service/src/rvps/grpc.rs b/attestation-service/attestation-service/src/rvps/grpc.rs index e749884825..bb927878c4 100644 --- a/attestation-service/attestation-service/src/rvps/grpc.rs +++ b/attestation-service/attestation-service/src/rvps/grpc.rs @@ -1,5 +1,6 @@ -use anyhow::*; +use anyhow::Result; use tokio::sync::Mutex; +use crate::rvps::RvpsError; use self::rvps_api::{ reference_value_provider_service_client::ReferenceValueProviderServiceClient, @@ -16,8 +17,9 @@ pub struct Agent { client: Mutex>, } + impl Agent { - pub async fn new(addr: &str) -> Result { + pub async fn new(addr: &str) -> Result { Ok(Self { client: Mutex::new( ReferenceValueProviderServiceClient::connect(addr.to_string()).await?, @@ -25,7 +27,6 @@ impl Agent { }) } } - #[async_trait::async_trait] impl RvpsApi for Agent { async fn verify_and_extract(&mut self, message: &str) -> Result<()> { @@ -37,8 +38,7 @@ impl RvpsApi for Agent { .lock() .await .register_reference_value(req) - .await - .context("register failed")?; + .await?; Ok(()) } diff --git a/attestation-service/attestation-service/src/rvps/mod.rs b/attestation-service/attestation-service/src/rvps/mod.rs index f826ab12ef..2c0bd62949 100644 --- a/attestation-service/attestation-service/src/rvps/mod.rs +++ b/attestation-service/attestation-service/src/rvps/mod.rs @@ -3,9 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::*; +use anyhow::Result; use log::{info, warn}; use serde::Deserialize; +use thiserror::Error; + /// The interfaces of Reference Value Provider Service /// * `verify_and_extract` is responsible for verify a message and @@ -55,10 +57,28 @@ impl Default for RvpsConfig { } } +#[derive(Error, Debug)] +pub enum RvpsError { + #[error("Failed creating rvps service: {0}")] + CreateRvps(String), + #[error("feature `rvps-grpc` or `rvps-builtin` not enabled: {0}")] + FeatureNotEnabled(String), + #[error("Failed to register grpc agent: {0}")] + AgentRegistration(String), + #[error("Serde Json Error")] + SerdeJson(#[from] serde_json::Error), + #[error("Returned status: {0}")] + Status(#[from] tonic::Status), + #[error("tonic transport error: {0}")] + TonicTransport(#[from] tonic::transport::Error), + #[error("anyhow error: {0}")] + Anyhow(#[from] anyhow::Error) +} + impl RvpsConfig { /// If remote addr is specified and the feature `rvps-grpc` is enabled when /// built, will try to connect the remote rvps. Or, will use a built-in rvps. - pub async fn to_rvps(&self) -> Result> { + pub async fn to_rvps(&self) -> Result, RvpsError> { cfg_if::cfg_if! { if #[cfg(feature = "rvps-grpc")] { if !self.remote_addr.is_empty() { @@ -70,7 +90,7 @@ impl RvpsConfig { warn!("No RVPS address provided and will launch a built-in rvps"); Ok(Box::new(builtin::Rvps::new(&self.store_type)?) as Box) } else { - Err(anyhow!("either feature `rvps-grpc` or `rvps-builtin` should be enabled.")) + return RvpsError::FeatureNotEnabled; } } } @@ -78,7 +98,7 @@ impl RvpsConfig { info!("launch a built-in RVPS."); Ok(Box::new(builtin::Rvps::new(&self.store_type)) as Box) } else { - Err(anyhow!("either feature `rvps-grpc` or `rvps-builtin` should be enabled.")) + return RvpsError::FeatureNotEnabled; } } }