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

Bring KBS Resource URI into Image-rs #119

Merged
merged 8 commits into from
Mar 10, 2023
Merged
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
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ edition = "2018"
anyhow = "1"
async-compression = { version = "0.3.15", features = ["futures-io", "tokio", "gzip", "zstd"] }
async-trait = "0.1.56"
attestation_agent = { git = "https://github.com/confidential-containers/attestation-agent.git", rev = "55db121", optional = true }
attestation_agent = { git = "https://github.com/confidential-containers/attestation-agent.git", rev = "280c805", optional = true }
base64 = "0.13.0"
cfg-if = { version = "1.0.0", optional = true }
dircpy = { version = "0.3.12", optional = true }
flate2 = "1.0"
flume = "0.10.14"
fs_extra = { version = "1.2.0", optional = true }
futures-util = "0.3"
hex = { version = "0.4.3", optional = true }
lazy_static = { version = "1.4.0", optional = true }
libc = "0.2"
log = "0.4.14"
nix = { version = "0.26", optional = true }
oci-distribution = "0.9.4"
oci-spec = "0.5.8"
ocicrypt-rs = { git = "https://github.com/confidential-containers/ocicrypt-rs.git", rev = "16e07c7", default-features = false, features = ["keywrap-jwe", "async-io"], optional = true }
ocicrypt-rs = { git = "https://github.com/confidential-containers/ocicrypt-rs.git", rev = "b720152", default-features = false, features = ["keywrap-jwe", "async-io"], optional = true }
prost = { version = "0.11", optional = true }
sequoia-openpgp = { version = "1.7.0", default-features = false, features = ["compression", "crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"], optional = true }
protobuf = { version = "3.2.0", optional = true }
Expand All @@ -41,7 +43,7 @@ strum_macros = "0.24"
tar = "0.4.37"
tokio = "1.0"
tonic = { version = "0.8", optional = true }
url = { version = "2.2.2", optional = true }
url = "2.2.2"
ttrpc = { version = "0.7.1", features = [ "async" ], optional = true }
walkdir = "2"
zstd = "0.11"
Expand Down Expand Up @@ -83,10 +85,10 @@ keywrap-ttrpc = ["ocicrypt-rs/keywrap-keyprovider-ttrpc", "dep:ttrpc", "dep:prot
eaa-kbc = ["attestation_agent/eaa_kbc", "ocicrypt-rs/eaa_kbc"]

signature = ["hex"]
signature-simple = ["signature", "sequoia-openpgp", "serde_yaml", "url"]
signature-simple = ["signature", "sequoia-openpgp", "serde_yaml"]
signature-cosign = ["signature", "sigstore"]

snapshot-overlayfs = ["nix"]
snapshot-unionfs = ["nix", "dircpy", "fs_extra"]

getresource = []
getresource = [ "lazy_static", "cfg-if" ]
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() -> Result<()> {

#[cfg(feature = "ttrpc-codegen")]
ttrpc_codegen::Codegen::new()
.out_dir("./src/secure_channel/ttrpc_proto")
.out_dir("./src/resource/kbs/ttrpc_proto")
.input("./protos/getresource.proto")
.include("./protos")
.rust_protobuf()
Expand Down
5 changes: 2 additions & 3 deletions protos/getresource.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ syntax = "proto3";
package getresource;

message GetResourceRequest {
string KbcName = 1;
string KbsUri = 2;
string ResourceDescription = 3;
string ResourceUri = 1;
string KbcName = 2;
}

message GetResourceResponse {
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_attestation_agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
AA_DIR=$SCRIPT_DIR/attestation_agent

pushd $SCRIPT_DIR
git clone --depth 1 "https://github.com/confidential-containers/attestation-agent.git" $AA_DIR
git clone "https://github.com/confidential-containers/attestation-agent.git" $AA_DIR
pushd $AA_DIR

make $parameters
Expand Down
48 changes: 5 additions & 43 deletions src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

pub mod auth_config;

use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
use std::collections::HashMap;

use anyhow::*;
use oci_distribution::{secrets::RegistryAuth, Reference};
Expand All @@ -26,53 +26,15 @@ pub struct DockerAuthConfig {
}

/// Get a credential (RegistryAuth) for the given Reference.
/// First, it will try to find auth info in the local
/// `auth.json`. If there is not one, it will
/// ask one from the [`crate::secure_channel::SecureChannel`], which connects
/// to the GetResource API of Attestation Agent.
/// Then, it will use the `auth.json` to find
/// a credential of the given image reference.
#[cfg(feature = "getresource")]
/// The path can be from different places. Like `path://` or
/// `kbs://`.
pub async fn credential_for_reference(
reference: &Reference,
secure_channel: std::sync::Arc<tokio::sync::Mutex<crate::secure_channel::SecureChannel>>,
auth_file_path: &str,
) -> Result<RegistryAuth> {
// if Policy config file does not exist, get if from KBS.
if !Path::new(auth_file_path).exists() {
secure_channel
.lock()
.await
.get_resource(RESOURCE_DESCRIPTION, HashMap::new(), auth_file_path)
.await?;
}
let auth = crate::resource::get_resource(auth_file_path).await?;
Xynnn007 marked this conversation as resolved.
Show resolved Hide resolved

let reader = File::open(auth_file_path)?;
let buf_reader = BufReader::new(reader);
let config: DockerConfigFile = serde_json::from_reader(buf_reader)?;

// TODO: support credential helpers
auth_config::credential_from_auth_config(reference, &config.auths)
}

/// Get a credential (RegistryAuth) for the given Reference.
/// First, it will try to find auth info in the local
/// `auth.json`. If there is not one, it will
/// directly return [`RegistryAuth::Anonymous`].
/// Or, it will use the `auth.json` to find
/// a credential of the given image reference.
pub async fn credential_for_reference_local(
reference: &Reference,
auth_file_path: &str,
) -> Result<RegistryAuth> {
// if Policy config file does not exist, get if from KBS.
if !Path::new(auth_file_path).exists() {
return Ok(RegistryAuth::Anonymous);
}

let reader = File::open(auth_file_path)?;
let buf_reader = BufReader::new(reader);
let config: DockerConfigFile = serde_json::from_reader(buf_reader)?;
let config: DockerConfigFile = serde_json::from_slice(&auth)?;

// TODO: support credential helpers
auth_config::credential_from_auth_config(reference, &config.auths)
Expand Down
32 changes: 8 additions & 24 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,22 @@ use crate::CC_IMAGE_WORK_DIR;
const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";

/// Default policy file path.
pub const POLICY_FILE_PATH: &str = "/run/image-security/security_policy.json";
pub const POLICY_FILE_PATH: &str = "kbs:///default/security-policy/test";

/// Dir of Sigstore Config file.
/// The reason for using the `/run` directory here is that in general HW-TEE,
/// the `/run` directory is mounted in `tmpfs`, which is located in the encrypted memory protected by HW-TEE.
pub const SIG_STORE_CONFIG_DIR: &str = "/run/image-security/simple_signing/sigstore_config";

pub const SIG_STORE_CONFIG_DEFAULT_FILE: &str =
"/run/image-security/simple_signing/sigstore_config/default.yaml";
pub const SIG_STORE_CONFIG_DEFAULT_FILE: &str = "kbs:///default/sigstore-config/test";

/// Path to the gpg pubkey ring of the signature
pub const GPG_KEY_RING: &str = "/run/image-security/simple_signing/pubkey.gpg";

/// Dir for storage of cosign verification keys.
pub const COSIGN_KEY_DIR: &str = "/run/image-security/cosign";

/// The reason for using the `/run` directory here is that in general HW-TEE,
/// the `/run` directory is mounted in `tmpfs`, which is located in the encrypted memory protected by HW-TEE.
/// [`AUTH_FILE_PATH`] shows the path to the `auth.json` file.
pub const AUTH_FILE_PATH: &str = "/run/image-security/auth.json";
pub const AUTH_FILE_PATH: &str = "kbs:///default/credential/test";

/// `image-rs` configuration information.
#[derive(Clone, Debug, Deserialize)]
Expand Down Expand Up @@ -108,33 +104,21 @@ impl TryFrom<&Path> for ImageConfig {

#[derive(Clone, Debug, Deserialize)]
pub struct Paths {
/// sigstore config file for simple signing
pub sigstore_config: String,

/// Path to `Policy.json`
pub policy_path: String,
Xynnn007 marked this conversation as resolved.
Show resolved Hide resolved

/// Dir of `Sigstore Config file`, used by simple signing
pub sig_store_config_dir: String,

/// Default sigstore config file, used by simple signing
pub default_sig_store_config_file: String,

/// Path to the gpg pubkey ring of the signature
pub gpg_key_ring: String,

/// Dir for storage of cosign verification keys
pub cosign_key_dir: String,

/// Path to the auth file
pub auth_file: String,
}

impl Default for Paths {
fn default() -> Self {
Self {
sigstore_config: SIG_STORE_CONFIG_DEFAULT_FILE.into(),
policy_path: POLICY_FILE_PATH.into(),
sig_store_config_dir: SIG_STORE_CONFIG_DIR.into(),
default_sig_store_config_file: SIG_STORE_CONFIG_DEFAULT_FILE.into(),
gpg_key_ring: GPG_KEY_RING.into(),
cosign_key_dir: COSIGN_KEY_DIR.into(),
auth_file: AUTH_FILE_PATH.into(),
}
}
Expand All @@ -150,10 +134,10 @@ mod tests {

#[test]
fn test_image_config() {
std::env::remove_var(CC_IMAGE_WORK_DIR);
let config = ImageConfig::default();
let work_dir = PathBuf::from(DEFAULT_WORK_DIR);

std::env::remove_var(CC_IMAGE_WORK_DIR);
assert_eq!(config.work_dir, work_dir);
assert_eq!(config.default_snapshot, SnapshotType::Overlay);

Expand Down
92 changes: 22 additions & 70 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,48 +180,38 @@ impl ImageClient {
// If one of self.config.auth and self.config.security_validate is enabled,
// there will establish a secure channel between image-rs and Attestation-Agent
#[cfg(feature = "getresource")]
let secure_channel = match self.config.auth || self.config.security_validate {
true => {
// Both we need a [`IMAGE_SECURITY_CONFIG_DIR`] dir
if !Path::new(IMAGE_SECURITY_CONFIG_DIR).exists() {
tokio::fs::create_dir_all(IMAGE_SECURITY_CONFIG_DIR)
.await
.map_err(|e| {
anyhow!("Create image security runtime config dir failed: {:?}", e)
})?;
}
if self.config.auth || self.config.security_validate {
// Both we need a [`IMAGE_SECURITY_CONFIG_DIR`] dir
if !Path::new(IMAGE_SECURITY_CONFIG_DIR).exists() {
tokio::fs::create_dir_all(IMAGE_SECURITY_CONFIG_DIR)
.await
.map_err(|e| {
anyhow!("Create image security runtime config dir failed: {:?}", e)
})?;
}

if let Some(wrapped_aa_kbc_params) = decrypt_config {
let wrapped_aa_kbc_params = wrapped_aa_kbc_params.to_string();
let aa_kbc_params =
wrapped_aa_kbc_params.trim_start_matches("provider:attestation-agent:");

// The secure channel to communicate with KBS.
let secure_channel = Arc::new(Mutex::new(
crate::secure_channel::SecureChannel::new(aa_kbc_params).await?,
));
Some(secure_channel)
} else {
bail!("Secure channel creation needs aa_kbc_params.");
}
if let Some(wrapped_aa_kbc_params) = decrypt_config {
let wrapped_aa_kbc_params = wrapped_aa_kbc_params.to_string();
let aa_kbc_params =
wrapped_aa_kbc_params.trim_start_matches("provider:attestation-agent:");

// The secure channel to communicate with KBS.
// This step will initialize the secure channel
let mut channel = crate::resource::SECURE_CHANNEL.lock().await;
*channel = Some(crate::resource::kbs::SecureChannel::new(aa_kbc_params).await?);
} else {
bail!("Secure channel creation needs aa_kbc_params.");
}
false => None,
};

// If no valid auth is given and config.auth is enabled, try to load
// auth from `auth.json`.
// auth from `auth.json` of given place.
// If a proper auth is given, use this auth.
// If no valid auth is given and config.auth is disabled, use Anonymous auth.
#[cfg(feature = "getresource")]
let auth = match (self.config.auth, auth.is_none()) {
(true, true) => {
let secure_channel = secure_channel
.as_ref()
.expect("unexpected uninitialized secure channel")
.clone();
match crate::auth::credential_for_reference(
&reference,
secure_channel,
&self.config.file_paths.auth_file,
)
.await
Expand All @@ -240,27 +230,6 @@ impl ImageClient {
_ => auth.expect("unexpected uninitialized auth"),
};

#[cfg(not(feature = "getresource"))]
let auth = match (self.config.auth, auth.is_none()) {
(true, true) => match crate::auth::credential_for_reference_local(
&reference,
&self.config.file_paths.auth_file,
)
.await
{
Ok(cred) => cred,
Err(e) => {
warn!(
"get credential failed, use Anonymous auth instead: {}",
e.to_string()
);
RegistryAuth::Anonymous
}
},
(false, true) => RegistryAuth::Anonymous,
_ => auth.expect("unexpected uninitialized auth"),
};

let mut client = PullClient::new(reference, &self.config.work_dir.join("layers"), &auth)?;
let (image_manifest, image_digest, image_config) = client.pull_manifest().await?;

Expand All @@ -284,24 +253,7 @@ impl ImageClient {
}
}

#[cfg(all(feature = "getresource", feature = "signature"))]
if self.config.security_validate {
let secure_channel = secure_channel
.as_ref()
.expect("unexpected uninitialized secure channel")
.clone();
crate::signature::allows_image(
image_url,
&image_digest,
secure_channel,
&auth,
&self.config.file_paths,
)
.await
.map_err(|e| anyhow!("Security validate failed: {:?}", e))?;
}

#[cfg(all(not(feature = "getresource"), feature = "signature"))]
#[cfg(feature = "signature")]
if self.config.security_validate {
crate::signature::allows_image(
image_url,
Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ pub mod decrypt;
pub mod image;
pub mod meta_store;
pub mod pull;
#[cfg(feature = "getresource")]
pub mod secure_channel;
pub mod resource;
#[cfg(feature = "signature")]
pub mod signature;
pub mod snapshots;
Expand Down
10 changes: 2 additions & 8 deletions src/secure_channel/grpc.rs → src/resource/kbs/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,10 @@ impl Grpc {

#[async_trait]
impl Client for Grpc {
async fn get_resource(
&mut self,
kbc_name: &str,
kbs_uri: &str,
resource_description: String,
) -> Result<Vec<u8>> {
async fn get_resource(&mut self, kbc_name: &str, resource_uri: &str) -> Result<Vec<u8>> {
let req = tonic::Request::new(GetResourceRequest {
kbc_name: kbc_name.to_string(),
kbs_uri: kbs_uri.to_string(),
resource_description,
resource_uri: resource_uri.to_string(),
});
Ok(self.inner.get_resource(req).await?.into_inner().resource)
}
Expand Down
Loading