-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add trustee-client - a simple client for Trustee
Gather evidence, attest and get secrets from Trustee Currently it simply uses AA's kbs_protocol client and attesters. If needed, it can be enhanced later. Signed-off-by: Uri Lublin <[email protected]>
- Loading branch information
Showing
7 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "trustee-client" | ||
version = "0.10.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
anyhow.workspace = true | ||
base64.workspace = true | ||
clap = { workspace = true, features = ["derive"] } | ||
config.workspace = true | ||
tokio = { workspace = true, features = ["macros", "rt"] } | ||
env_logger.workspace = true | ||
log.workspace = true | ||
kbs_protocol = { path = "../attestation-agent/kbs_protocol", features=["openssl", "all-attesters", "background_check", "passport" ] } | ||
rstest.workspace = true | ||
serde = { workspace = true, features = ["derive"] } | ||
serde_json.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Trustee client -- a simple tool to attest and fetch secrets from Trustee | ||
|
||
Trustee client is using attestation-agent's kbs_protocol client and | ||
attesters to gather hardware-based confidential-computing evidence | ||
and send it over to Trustee. | ||
|
||
Trustee client is a part of [confidential-containers](https://github.com/confidential-containers) | ||
[guest-components](https://github.com/confidential-containers/guest-components) | ||
project but can be used for confidential VMs as well. | ||
|
||
|
||
|
||
Build with: | ||
cargo build [--no-default-features] | ||
|
||
|
||
Configuration file: | ||
trustee-client configuration must contain the trustee (server) URL. | ||
Possibly it can also contain the trustee https certificate, either | ||
as a string in the configuration file or in another file (but not both). | ||
|
||
A configuration file path is an optional argument to trustee-client | ||
If no configuration file path is provided /etc/trustee-client.conf is used. | ||
|
||
Run: | ||
$ trustee-client [--config-file <path>] get-resource --path <resource-path> | ||
|
||
Example: | ||
$ trustee-client get-resource --path default/keys/dummy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright (c) 2023 by Alibaba. | ||
// Copyright (c) 2024 Red Hat, Inc | ||
// Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! A simple client for fetching resources from Trustee. | ||
use anyhow::Result; | ||
use base64::engine::general_purpose::STANDARD; | ||
use base64::Engine; | ||
use clap::{Parser, Subcommand}; | ||
use log::debug; | ||
|
||
use kbs_protocol::evidence_provider::NativeEvidenceProvider; | ||
use kbs_protocol::KbsClientBuilder; | ||
use kbs_protocol::KbsClientCapabilities; | ||
|
||
pub mod tcconfig; | ||
use tcconfig::TrusteeClientConfig; | ||
|
||
#[derive(Parser)] | ||
struct Cli { | ||
/// A configuration file for trustee-client (default is /etc/trustee-client.conf) | ||
#[clap(long, value_parser)] | ||
config_file: Option<String>, | ||
|
||
#[clap(subcommand)] | ||
command: Commands, | ||
} | ||
|
||
#[derive(Subcommand)] | ||
enum Commands { | ||
/// Get confidential resource | ||
#[clap(arg_required_else_help = true)] | ||
GetResource { | ||
/// KBS Resource path, e.g my_repo/resource_type/123abc | ||
/// Document: https://github.com/confidential-containers/attestation-agent/blob/main/docs/KBS_URI.md | ||
#[clap(long, value_parser)] | ||
path: String, | ||
}, | ||
} | ||
|
||
#[tokio::main(flavor = "current_thread")] | ||
async fn main() -> Result<()> { | ||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); | ||
|
||
let cli = Cli::parse(); | ||
|
||
let tcc = TrusteeClientConfig::new(cli.config_file)?; | ||
|
||
let url = tcc.url.clone(); | ||
let cert = tcc.get_cert(); | ||
|
||
debug!("url {}", url); | ||
debug!("cert {:?}", cert); | ||
|
||
let evidence_provider = Box::new(NativeEvidenceProvider::new()?); | ||
|
||
// build a kbs_protocol client with evidence_provider | ||
let mut client_builder = KbsClientBuilder::with_evidence_provider(evidence_provider, &url); | ||
|
||
// if a certificate is given, use it | ||
if let Some(c) = cert { | ||
client_builder = client_builder.add_kbs_cert(&c) | ||
} | ||
|
||
// Build the client. This client is used throughout the program | ||
let mut client = client_builder.build()?; | ||
|
||
match cli.command { | ||
Commands::GetResource { path } => { | ||
// get resource | ||
let resource_uri = format!("kbs:///{}", path); | ||
let resource_bytes = client | ||
.get_resource(serde_json::from_str(&format!("\"{resource_uri}\""))?) | ||
.await?; | ||
|
||
println!("{}", STANDARD.encode(resource_bytes)); | ||
} | ||
}; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright (c) 2024 Alibaba Cloud | ||
// Copyright (c) 2024 Red Hat, Inc | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
use anyhow::{bail, Context, Result}; | ||
use config::{Config, File, FileFormat}; | ||
use log::{debug, info}; | ||
use serde::Deserialize; | ||
use std::fs; | ||
use std::path::Path; | ||
|
||
const DEFAULT_TCCONFIG_FILE_PATH: &str = "/etc/trustee-client.conf"; | ||
#[derive(Deserialize, Debug, PartialEq)] | ||
pub struct TrusteeClientConfig { | ||
/// URL Address of Trustee. | ||
pub url: String, | ||
|
||
/// https:// certificate for Trustee as a string | ||
pub cert: Option<String>, | ||
|
||
/// https:// certificate for Trustee in a cert_file | ||
pub cert_file: Option<String>, | ||
} | ||
|
||
impl TrusteeClientConfig { | ||
pub fn new(path_arg: Option<String>) -> Result<Self> { | ||
let path = match path_arg { | ||
Some(f) => f, | ||
None => DEFAULT_TCCONFIG_FILE_PATH.to_string(), | ||
}; | ||
|
||
debug!("Using configuration file {path}"); | ||
if !Path::new(&path).exists() { | ||
bail!("Config file {path} not found.") | ||
} | ||
|
||
let c = Config::builder() | ||
.add_source(File::new(&path as &str, FileFormat::Toml)) | ||
.build()?; | ||
|
||
let tcc: TrusteeClientConfig = | ||
c.try_deserialize().context("failed to parse config_file")?; | ||
|
||
Ok(tcc) | ||
} | ||
|
||
// get the certificate from the configuration file, if exists | ||
// If cert does not exists but cert_file does, read cert from cert_file | ||
pub fn get_cert(&self) -> Option<String> { | ||
debug!( | ||
"cert={:?} cert_file={:?}", | ||
self.cert.clone(), | ||
self.cert_file.clone() | ||
); | ||
if let Some(c) = &self.cert { | ||
debug!("Some cert {}", c.clone()); | ||
return Some(c.clone()); | ||
} | ||
|
||
if let Some(cf) = &self.cert_file { | ||
debug!("Some cert_file {}", cf.clone()); | ||
let newcert = fs::read_to_string(cf.clone()).ok()?; | ||
return Some(newcert); | ||
} | ||
None | ||
} | ||
|
||
pub fn is_valid(&self) -> Result<()> { | ||
if self.cert.is_some() && self.cert_file.is_some() { | ||
bail!("Please provide only one of 'cert' and 'cert_file'"); | ||
} | ||
if self.url.starts_with("https://") && self.cert.is_none() { | ||
info!("An https:// URL is used but no certificate is provided"); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::TrusteeClientConfig; | ||
use rstest::rstest; | ||
use std::fs::File as fsfile; | ||
use std::io::Write; | ||
|
||
#[rstest] | ||
#[case::good_http( | ||
1, | ||
r#" | ||
url = "http://localhost:50000" | ||
"#, | ||
Some(TrusteeClientConfig { | ||
url : "http://localhost:50000".to_string(), | ||
cert : None, | ||
cert_file: None, | ||
}))] | ||
#[case::good_https_with_cert( | ||
2, | ||
r#" | ||
url = "https://localhost:50000" | ||
cert = "Trustee Certificate" | ||
"#, | ||
Some(TrusteeClientConfig { | ||
url : "https://localhost:50000".to_string(), | ||
cert : Some("Trustee Certificate".to_string()), | ||
cert_file: None, | ||
}) | ||
)] | ||
#[case::good_https_with_cert_file( | ||
3, | ||
r#" | ||
url = "https://localhost:50000" | ||
cert_file = "/tmp/test_cert_file.conf" | ||
"#, | ||
Some(TrusteeClientConfig { | ||
url : "https://localhost:50000".to_string(), | ||
cert : None, | ||
cert_file: Some("/tmp/test_cert_file.conf".to_string()), | ||
}) | ||
)] | ||
#[case::bad_empty(4, r#""#, None)] | ||
#[case::bad_nourl_only_cert_file( | ||
5, | ||
r#" | ||
cert_file = "/tmp/test_cert_file" | ||
"#, | ||
None | ||
)] | ||
fn check_trustee_config_file( | ||
#[case] n: i32, | ||
#[case] config: &str, | ||
#[case] expected: Option<TrusteeClientConfig>, | ||
) { | ||
let testfilename = format!("/tmp/tccconfigtest{n}.conf"); | ||
{ | ||
let mut f = fsfile::create(testfilename.clone()).unwrap(); | ||
f.write_all(config.as_bytes()).unwrap(); | ||
f.sync_all().unwrap(); | ||
} // close f | ||
let tcc = TrusteeClientConfig::new(Some(testfilename)); | ||
match expected { | ||
Some(cfg) => assert_eq!(cfg, tcc.unwrap()), | ||
None => assert!(tcc.is_err()), | ||
} | ||
} | ||
} |