Skip to content

Commit

Permalink
KBS: Use one API to serve both admin and user requests
Browse files Browse the repository at this point in the history
Signed-off-by: Xynnn007 <[email protected]>
  • Loading branch information
Xynnn007 committed Oct 23, 2024
1 parent b442ef4 commit 3881a37
Show file tree
Hide file tree
Showing 23 changed files with 327 additions and 283 deletions.
15 changes: 6 additions & 9 deletions kbs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ documentation.workspace = true
edition.workspace = true

[features]
default = ["coco-as-builtin", "coco-as-grpc", "intel-trust-authority-as", "resource"]

# Feature that allows to access resources from KBS
resource = ["rsa", "reqwest", "aes-gcm", "jsonwebtoken"]
default = ["coco-as-builtin", "coco-as-grpc", "intel-trust-authority-as"]

# Support a backend attestation service for KBS
as = []
Expand All @@ -28,15 +25,15 @@ coco-as-builtin-no-verifier = ["coco-as", "attestation-service/rvps-builtin"]
coco-as-grpc = ["coco-as", "mobc", "tonic", "tonic-build", "prost"]

# Use Intel TA as backend attestation service
intel-trust-authority-as = ["as", "reqwest", "resource", "az-cvm-vtpm"]
intel-trust-authority-as = ["as", "az-cvm-vtpm"]

# Use aliyun KMS as KBS backend
aliyun = ["kms/aliyun"]

[dependencies]
actix-web = { workspace = true, features = ["openssl"] }
actix-web-httpauth.workspace = true
aes-gcm = { version = "0.10.1", optional = true }
aes-gcm = "0.10.1"
anyhow.workspace = true
async-trait.workspace = true
attestation-service = { path = "../attestation-service", default-features = false, optional = true }
Expand All @@ -45,7 +42,7 @@ cfg-if.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
config.workspace = true
env_logger.workspace = true
jsonwebtoken = { workspace = true, default-features = false, optional = true }
jsonwebtoken = { workspace = true, default-features = false }
jwt-simple.workspace = true
kbs-types.workspace = true
kms = { workspace = true, default-features = false }
Expand All @@ -55,8 +52,8 @@ mobc = { version = "0.8.3", optional = true }
prost = { workspace = true, optional = true }
rand = "0.8.5"
regorus.workspace = true
reqwest = { workspace = true, features = ["json"], optional = true }
rsa = { version = "0.9.2", optional = true, features = ["sha2"] }
reqwest = { workspace = true, features = ["json"] }
rsa = { version = "0.9.2", features = ["sha2"] }
scc = "2"
semver = "1.0.16"
serde = { workspace = true, features = ["derive"] }
Expand Down
10 changes: 5 additions & 5 deletions kbs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ COCO_AS_INTEGRATION_TYPE ?= builtin
INSTALL_DESTDIR ?= /usr/local/bin

ifeq ($(AS_TYPE), coco-as)
AS_FEATURE = $(AS_TYPE)-$(COCO_AS_INTEGRATION_TYPE)
else
AS_FEATURE = $(AS_TYPE)
AS_FEATURE += $(AS_TYPE)-$(COCO_AS_INTEGRATION_TYPE),
else ifneq ($(AS_TYPE), )
AS_FEATURE += $(AS_TYPE),
endif

ifeq ($(ALIYUN), true)
Expand All @@ -37,7 +37,7 @@ build: background-check-kbs

.PHONY: background-check-kbs
background-check-kbs:
cargo build -p kbs --locked --release --no-default-features --features $(AS_FEATURE),resource,$(FEATURES)
cargo build -p kbs --locked --release --no-default-features --features $(FEATURES),$(AS_FEATURE)

.PHONY: passport-issuer-kbs
passport-issuer-kbs:
Expand All @@ -46,7 +46,7 @@ passport-issuer-kbs:

.PHONY: passport-resource-kbs
passport-resource-kbs:
cargo build -p kbs --locked --release --no-default-features --features resource,$(FEATURES)
cargo build -p kbs --locked --release --no-default-features --features $(FEATURES),
mv ../target/release/kbs ../target/release/resource-kbs

.PHONY: cli
Expand Down
181 changes: 53 additions & 128 deletions kbs/src/api_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use log::info;

use crate::{
admin::Admin, config::KbsConfig, jwe::jwe, plugins::PluginManager, policy_engine::PolicyEngine,
resource::ResourceDesc, token::TokenVerifier, Error, Result,
token::TokenVerifier, Error, Result,
};

const KBS_PREFIX: &str = "/kbs/v0";
Expand All @@ -28,9 +28,6 @@ macro_rules! kbs_path {
pub struct ApiServer {
plugin_manager: PluginManager,

#[cfg(feature = "resource")]
resource_storage: crate::resource::ResourceStorage,

#[cfg(feature = "as")]
attestation_service: crate::attestation::AttestationService,

Expand Down Expand Up @@ -61,15 +58,11 @@ impl ApiServer {
}

pub async fn new(config: KbsConfig) -> Result<Self> {
let plugin_manager = PluginManager::try_from(config.client_plugins.clone())?;
let plugin_manager = PluginManager::try_from(config.plugins.clone())?;
let token_verifier = TokenVerifier::from_config(config.attestation_token.clone()).await?;
let policy_engine = PolicyEngine::new(&config.policy_engine).await?;
let admin_auth = Admin::try_from(config.admin.clone())?;

#[cfg(feature = "resource")]
let resource_storage =
crate::resource::ResourceStorage::try_from(config.repository.clone())?;

#[cfg(feature = "as")]
let attestation_service =
crate::attestation::AttestationService::new(config.attestation_service.clone()).await?;
Expand All @@ -81,9 +74,6 @@ impl ApiServer {
admin_auth,
token_verifier,

#[cfg(feature = "resource")]
resource_storage,

#[cfg(feature = "as")]
attestation_service,
})
Expand All @@ -110,13 +100,8 @@ impl ApiServer {
.app_data(web::Data::new(api_server))
.service(
web::resource([kbs_path!("{plugin}{sub_path:.*}")])
.route(web::get().to(client))
.route(web::post().to(client)),
)
.service(
web::resource([kbs_path!("admin/{plugin}/{sub_path:.*}")])
.route(web::get().to(admin))
.route(web::post().to(admin)),
.route(web::get().to(api))
.route(web::post().to(api)),
)
}
});
Expand Down Expand Up @@ -145,8 +130,8 @@ impl ApiServer {
}
}

/// Client APIs. /kbs/v0/XXX
pub(crate) async fn client(
/// APIs
pub(crate) async fn api(
request: HttpRequest,
body: web::Bytes,
core: web::Data<ApiServer>,
Expand Down Expand Up @@ -182,30 +167,44 @@ pub(crate) async fn client(
.map_err(From::from),
#[cfg(feature = "as")]
"attestation-policy" if request.method() == Method::POST => {
core.admin_auth.validate_auth(&request)?;

core.attestation_service.set_policy(&body).await?;

Ok(HttpResponse::Ok().finish())
}
"resource-policy" if request.method() == Method::POST => {
core.admin_auth.validate_auth(&request)?;

core.policy_engine.set_policy(&body).await?;

Ok(HttpResponse::Ok().finish())
}
#[cfg(feature = "resource")]
"resource" => {
if request.method() == Method::GET {
// Resource APIs needs to be authorized by the Token and policy
let resource_desc =
sub_path
.strip_prefix('/')
.ok_or(Error::IllegalAccessedPath {
path: end_point.clone(),
})?;
"resource-policy" if request.method() == Method::GET => {
core.admin_auth.validate_auth(&request)?;
let policy = core.policy_engine.get_policy().await?;

Ok(HttpResponse::Ok().content_type("text/xml").body(policy))
}
plugin_name => {
let plugin = core
.plugin_manager
.get(plugin_name)
.ok_or(Error::PluginNotFound {
plugin_name: plugin_name.to_string(),
})?;

let body = body.to_vec();
if plugin
.validate_auth(&body, query, sub_path, request.method())
.await?
{
// Plugin calls needs to be authorized by the admin auth
core.admin_auth.validate_auth(&request)?;
let response = plugin
.handle(&body, query, sub_path, request.method())
.await?;

Ok(HttpResponse::Ok().content_type("text/xml").body(response))
} else {
// Plugin calls needs to be authorized by the Token and policy
let token = core
.get_attestation_token(&request)
.await
Expand All @@ -214,104 +213,30 @@ pub(crate) async fn client(
let claims = core.token_verifier.verify(token).await?;

let claim_str = serde_json::to_string(&claims)?;
if !core
.policy_engine
.evaluate(resource_desc, &claim_str)
.await?
{
return Err(Error::PolicyDeny);
};

let resource_description = ResourceDesc::try_from(resource_desc)?;
let resource = core
.resource_storage
.get_secret_resource(resource_description)
.await?;
// TODO: add policy filter support for other plugins
if !core.policy_engine.evaluate(&end_point, &claim_str).await? {
return Err(Error::PolicyDeny);
}

let public_key = core.token_verifier.extract_tee_public_key(claims)?;
let jwe = jwe(public_key, resource).map_err(|e| Error::JweError { source: e })?;

let res = serde_json::to_string(&jwe)?;

Ok(HttpResponse::Ok()
.content_type("application/json")
.body(res))
} else if request.method() == Method::POST {
let resource_desc =
sub_path
.strip_prefix('/')
.ok_or(Error::IllegalAccessedPath {
path: end_point.clone(),
})?;
let resource_description = ResourceDesc::try_from(resource_desc)?;
core.admin_auth.validate_auth(&request)?;
core.resource_storage
.set_secret_resource(resource_description, &body)
let response = plugin
.handle(&body, query, sub_path, request.method())
.await?;

Ok(HttpResponse::Ok().content_type("application/json").body(""))
} else {
Ok(HttpResponse::NotImplemented()
.content_type("application/json")
.body(""))
}
}
plugin_name => {
// Plugin calls needs to be authorized by the Token and policy
let token = core
.get_attestation_token(&request)
.await
.map_err(|_| Error::TokenNotFound)?;

let claims = core.token_verifier.verify(token).await?;

let claim_str = serde_json::to_string(&claims)?;

// TODO: add policy filter support for other plugins
if !core.policy_engine.evaluate(&end_point, &claim_str).await? {
return Err(Error::PolicyDeny);
if plugin
.encrypted(&body, query, sub_path, request.method())
.await?
{
let public_key = core.token_verifier.extract_tee_public_key(claims)?;
let jwe =
jwe(public_key, response).map_err(|e| Error::JweError { source: e })?;
let res = serde_json::to_string(&jwe)?;
return Ok(HttpResponse::Ok()
.content_type("application/json")
.body(res));
}

Ok(HttpResponse::Ok().content_type("text/xml").body(response))
}

let plugin = core
.plugin_manager
.get(plugin_name)
.ok_or(Error::PluginNotFound {
plugin_name: plugin_name.to_string(),
})?;
let body = body.to_vec();
let response = plugin
.handle(body, query.into(), sub_path.into(), request.method())
.await?;
Ok(response)
}
}
}

/// Admin APIs.
pub(crate) async fn admin(
request: HttpRequest,
_body: web::Bytes,
core: web::Data<ApiServer>,
) -> Result<HttpResponse> {
// Admin APIs needs to be authorized by the admin asymmetric key
core.admin_auth.validate_auth(&request)?;

let plugin_name = request
.match_info()
.get("plugin")
.ok_or(Error::IllegalAccessedPath {
path: request.path().to_string(),
})?;
let sub_path = request
.match_info()
.get("sub_path")
.ok_or(Error::IllegalAccessedPath {
path: request.path().to_string(),
})?;

info!("Admin plugin {plugin_name} with path {sub_path} called");

// TODO: add admin path handlers
let response = HttpResponse::NotFound().body("no admin plugin found");
Ok(response)
}
31 changes: 31 additions & 0 deletions kbs/src/attestation/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,31 @@

use serde::Deserialize;

pub const DEFAULT_TIMEOUT: i64 = 5;

#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct AttestationConfig {
#[serde(flatten)]
#[serde(default)]
pub attestation_service: AttestationServiceConfig,

#[serde(default = "default_timeout")]
pub timeout: i64,
}

impl Default for AttestationConfig {
fn default() -> Self {
Self {
attestation_service: AttestationServiceConfig::default(),
timeout: DEFAULT_TIMEOUT,
}
}
}

fn default_timeout() -> i64 {
DEFAULT_TIMEOUT
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum AttestationServiceConfig {
Expand All @@ -27,3 +44,17 @@ pub enum AttestationServiceConfig {
#[serde(alias = "intel_ta")]
IntelTA(super::intel_trust_authority::IntelTrustAuthorityConfig),
}

impl Default for AttestationServiceConfig {
fn default() -> Self {
cfg_if::cfg_if! {
if #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] {
AttestationServiceConfig::CoCoASBuiltIn(attestation_service::config::Config::default())
} else if #[cfg(feature = "coco-as-grpc")] {
AttestationServiceConfig::CoCoASGrpc(super::coco::grpc::GrpcConfig::default())
} else {
AttestationServiceConfig::IntelTA(super::intel_trust_authority::IntelTrustAuthorityConfig::default())
}
}
}
}
2 changes: 1 addition & 1 deletion kbs/src/attestation/intel_trust_authority/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ struct ErrorResponse {
error: String,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[derive(Clone, Debug, Deserialize, PartialEq, Default)]
pub struct IntelTrustAuthorityConfig {
pub base_url: String,
pub api_key: String,
Expand Down
Loading

0 comments on commit 3881a37

Please sign in to comment.