Skip to content

Commit

Permalink
Add trustee-client - a simple client for Trustee
Browse files Browse the repository at this point in the history
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
uril committed Oct 15, 2024
1 parent 25dbce5 commit e8ed16c
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 0 deletions.
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",
]

[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()),
}
}
}

0 comments on commit e8ed16c

Please sign in to comment.