Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trustee-client - a simple client for Trustee #755

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"confidential-data-hub/storage",
"image-rs",
"ocicrypt-rs",
"trustee-client",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name might be too general. This tool only exercises a subset of the Trustee APIs. Btw see this proposal which might end up with a similar name.

Maybe this could be called cc-kbc-resource-getter or trustee-resource-getter or trustee-resource-client or something else

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll take a look at that proposal and think of names.

]

[workspace.dependencies]
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ Confidential Data Hub.
[coco-keyprovider](attestation-agent/coco_keyprovider/)
CoCo Keyprovider. Used to encrypt the container images.

[trustee-client](trustee-client)
A simple client to fetch secrets from Trustee

## Tools

[secret-cli](confidential-data-hub/secret)
Expand Down
17 changes: 17 additions & 0 deletions trustee-client/Cargo.toml
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
29 changes: 29 additions & 0 deletions trustee-client/README.md
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
83 changes: 83 additions & 0 deletions trustee-client/src/main.rs
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(())
}
148 changes: 148 additions & 0 deletions trustee-client/src/tcconfig.rs
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()),
}
}
}
Loading