-
Notifications
You must be signed in to change notification settings - Fork 6
Initial commit #1
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
Changes from 3 commits
0dd1152
df53e98
49591e2
3763094
02633e7
996d224
45f83e1
4cee730
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Rust build artifacts | ||
| /target/ | ||
| Cargo.lock | ||
|
|
||
| # IDE files | ||
| .vscode/ | ||
| .idea/ | ||
| *.swp | ||
| *.swo | ||
|
|
||
| # Temporary files | ||
| *.tmp | ||
| *.temp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| [package] | ||
| name = "clevis-pin-trustee" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
| description = "Clevis PIN for URL-based encryption/decryption" | ||
| repository = "https://github.com/confidential-clusters/clevis-pin-trustee" | ||
|
|
||
| [dependencies] | ||
| serde = { version = "1.0", features = ["derive"] } | ||
| serde_json = "1.0" | ||
| reqwest = { version = "0.11", features = ["json", "blocking"] } | ||
| tokio = { version = "1.0", features = ["full"] } | ||
| clap = { version = "4.0", features = ["derive"] } | ||
| anyhow = "1.0" | ||
| josekit = "0.10.3" | ||
| base64 = "0.22.1" | ||
| rand = "0.9.2" | ||
| hex = "0.4.3" | ||
|
|
||
| [dev-dependencies] | ||
| tempfile = "3.0" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| FROM docker.io/library/rust:trixie as build | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use a Fedora image here to do the build. We can do what we did in compure-pcrs. |
||
|
|
||
| COPY . /src | ||
| WORKDIR /src | ||
| RUN cargo build --release | ||
|
|
||
| FROM quay.io/fedora/fedora:42 | ||
| COPY --from=build /src/target/release/clevis-pin-trustee /usr/bin/clevis-pin-trustee | ||
| COPY --from=build /src/clevis-encrypt-trustee /usr/bin/clevis-encrypt-trustee | ||
| COPY --from=build /src/clevis-decrypt-trustee /usr/bin/clevis-decrypt-trustee | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #!/bin/bash | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum, why do we need that? Let's place the binaries in the right place directly? |
||
|
|
||
| clevis-pin-trustee decrypt "$@" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #!/bin/bash | ||
|
|
||
| clevis-pin-trustee encrypt "$@" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
travier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "servers": [ | ||
| { | ||
| "url": "http://localhost:8080", | ||
| "cert": "" | ||
travier marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ], | ||
| "path": "conf-cluster/12345/root" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| use anyhow::{anyhow, Context, Result}; | ||
| use base64::{engine::general_purpose, Engine as _}; | ||
| use clap::{Parser, Subcommand}; | ||
| use josekit::jwe::alg::direct::DirectJweAlgorithm::Dir; | ||
| use josekit::jwk::Jwk; | ||
| use serde::{Deserialize, Serialize}; | ||
| use std::io::{self, Read, Write}; | ||
| use std::process::Command as StdCommand; | ||
| use std::thread; | ||
| use std::time::Duration; | ||
|
|
||
| #[derive(Debug, Serialize, Deserialize, Clone)] | ||
| struct Server { | ||
| url: String, | ||
| cert: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Serialize, Deserialize)] | ||
| struct Config { | ||
| servers: Vec<Server>, | ||
| path: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Serialize, Deserialize)] | ||
| struct ClevisHeader { | ||
| pin: String, | ||
| servers: Vec<Server>, | ||
| path: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Serialize, Deserialize)] | ||
| pub struct Key { | ||
| pub key_type: String, | ||
| pub key: String, | ||
| } | ||
|
|
||
| fn fetch_and_prepare_jwk( | ||
| servers: &[Server], | ||
| path: &str, | ||
| ) -> Result<Jwk> { | ||
alicefr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let key = fetch_luks_key(servers, path)?; | ||
| let key = String::from_utf8( | ||
| general_purpose::STANDARD | ||
| .decode(&key) | ||
| .context("Error decoding key in base64")?, | ||
| ) | ||
| .context("Error decoding the key in JSON")?; | ||
| eprintln!("Key: {:?}", key); | ||
| let key: Key = serde_json::from_str(&key).context("Error in parsing the fetched key")?; | ||
|
|
||
| let mut jwk = Jwk::new(&key.key_type); | ||
| jwk.set_key_value(&key.key); | ||
| jwk.set_key_operations(vec!["encrypt", "decrypt"]); | ||
|
|
||
| Ok(jwk) | ||
| } | ||
|
|
||
| fn encrypt(config: &str) -> Result<()> { | ||
| let config: Config = | ||
| serde_json::from_str(config).map_err(|e| anyhow!("Failed to parse config JSON: {}", e))?; | ||
|
|
||
| let mut input = Vec::new(); | ||
| io::stdin().read_to_end(&mut input)?; | ||
|
|
||
| let jwk = fetch_and_prepare_jwk( | ||
| &config.servers, | ||
| &config.path, | ||
| )?; | ||
|
|
||
| eprintln!("{}", jwk.to_string()); | ||
| let encrypter = Dir | ||
| .encrypter_from_jwk(&jwk) | ||
| .context("Error creating direct encrypter")?; | ||
|
|
||
| let private_hdr = ClevisHeader { | ||
| pin: "trustee".to_string(), | ||
| servers: config.servers.clone(), | ||
| path: config.path, | ||
| }; | ||
|
|
||
| let mut hdr = josekit::jwe::JweHeader::new(); | ||
| hdr.set_algorithm("ECDH-ES"); | ||
| hdr.set_content_encryption("A256GCM"); | ||
| hdr.set_claim( | ||
| "clevis", | ||
| Some(serde_json::value::to_value(private_hdr).context("Error serializing private header")?), | ||
| ) | ||
| .context("Error adding clevis claim")?; | ||
|
|
||
| let jwe_token = josekit::jwe::serialize_compact(&input, &hdr, &encrypter) | ||
| .context("Error serializing JWE token")?; | ||
|
|
||
| io::stdout() | ||
| .write_all(jwe_token.as_bytes()) | ||
| .context("Error writing the token on stdout")?; | ||
| eprintln!("Encryption successful."); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn decrypt() -> Result<()> { | ||
| let mut input = Vec::new(); | ||
| io::stdin().read_to_end(&mut input)?; | ||
| let input = std::str::from_utf8(&input).context("Input is not valid UTF-8")?; | ||
|
|
||
| let hdr = josekit::jwt::decode_header(&input).context("Error decoding header")?; | ||
| let hdr_clevis = hdr.claim("clevis").context("Error getting clevis claim")?; | ||
| let hdr_clevis: ClevisHeader = | ||
| serde_json::from_value(hdr_clevis.clone()).context("Error deserializing clevis header")?; | ||
|
|
||
| eprintln!("Decrypt with header: {:?}", hdr_clevis); | ||
|
|
||
| let decrypter_jwk = fetch_and_prepare_jwk( | ||
| &hdr_clevis.servers, | ||
| &hdr_clevis.path, | ||
| )?; | ||
|
|
||
| let decrypter = Dir | ||
| .decrypter_from_jwk(&decrypter_jwk) | ||
| .context("Error creating decrypter")?; | ||
|
|
||
| let (payload, _) = | ||
| josekit::jwe::deserialize_compact(&input, &decrypter).context("Error decrypting JWE")?; | ||
|
|
||
| io::stdout().write_all(&payload)?; | ||
|
|
||
| eprintln!("Decryption successful."); | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn fetch_luks_key( | ||
| servers: &[Server], | ||
| path: &str, | ||
| ) -> Result<String> { | ||
| const MAX_ATTEMPTS: u32 = 3; | ||
| const DELAY: Duration = Duration::from_secs(5); | ||
|
Comment on lines
+127
to
+128
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine for now but we'll need to make this either bigger or configurable. |
||
|
|
||
| if servers.is_empty() { | ||
| return Err(anyhow!("No URLs provided")); | ||
| } | ||
|
|
||
| (1..=MAX_ATTEMPTS) | ||
| .find_map(|attempt| { | ||
alicefr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| eprintln!( | ||
| "Attempting to fetch LUKS key (attempt {}/{})", | ||
| attempt, MAX_ATTEMPTS | ||
| ); | ||
|
|
||
| for (index, server) in servers.iter().enumerate() { | ||
| eprintln!("Trying URL {}/{}: {}", index + 1, servers.len(), server.url); | ||
| match try_fetch_luks_key(&server.url, path) { | ||
| Ok(key) => { | ||
| eprintln!("Successfully fetched LUKS key from URL: {}", server.url); | ||
| return Some(Ok(key)); | ||
| } | ||
| Err(e) => { | ||
| eprintln!("Error with URL {}: {}", server.url, e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if attempt < MAX_ATTEMPTS { | ||
| eprintln!("All URLs failed for attempt {}. Retrying in {:?} seconds...", attempt, DELAY); | ||
| thread::sleep(DELAY); | ||
| } | ||
| None | ||
| }) | ||
| .unwrap_or_else(|| { | ||
| Err(anyhow!( | ||
| "Failed to fetch the LUKS key from all URLs after {} attempts", | ||
| MAX_ATTEMPTS | ||
| )) | ||
| }) | ||
| } | ||
|
|
||
| fn try_fetch_luks_key( | ||
| url: &str, | ||
| path: &str, | ||
| ) -> Result<String> { | ||
| let output = StdCommand::new("trustee-attester") | ||
| .arg("--url") | ||
| .arg(url) | ||
| .arg("get-resource") | ||
| .arg("--path") | ||
| .arg(path) | ||
| .output() | ||
| .map_err(|e| anyhow!("Failed to execute trustee-attester: {}", e))?; | ||
|
|
||
| io::stderr().write_all(&output.stderr)?; | ||
| io::stderr().write_all(&output.stdout)?; | ||
|
|
||
| if !output.status.success() { | ||
| let stderr = String::from_utf8_lossy(&output.stderr); | ||
| return Err(anyhow!("trustee-attester failed: {}", stderr)); | ||
| } | ||
|
|
||
| let key = String::from_utf8(output.stdout) | ||
| .map_err(|e| anyhow!("Invalid UTF-8 for the LUKS key: {}", e))? | ||
| .trim() | ||
| .to_string(); | ||
|
|
||
| if key.is_empty() { | ||
| return Err(anyhow!("Received empty LUKS key")); | ||
| } | ||
|
|
||
| Ok(key) | ||
| } | ||
|
|
||
| /// Clevis PIN for confidential cluster | ||
| #[derive(Parser)] | ||
| #[command(name = "clevis-pin-trustee")] | ||
| #[command(version = "0.1.0")] | ||
| #[command(about = "Clevis PIN for confidential clusters")] | ||
| struct Cli { | ||
| #[command(subcommand)] | ||
| command: Commands, | ||
| } | ||
|
|
||
| #[derive(Subcommand)] | ||
| enum Commands { | ||
| /// Encrypt data using the configuration | ||
| Encrypt { | ||
| /// Input data or arguments | ||
| config: String, | ||
| }, | ||
| /// Decrypt the input data | ||
| Decrypt, | ||
| } | ||
|
|
||
| fn main() -> Result<()> { | ||
| let cli = Cli::parse(); | ||
|
|
||
| match cli.command { | ||
| Commands::Encrypt { config } => encrypt(&config), | ||
| Commands::Decrypt => decrypt(), | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| { "key_type": "oct", "key": "2b442dd5db4478367729ef8bbf2e7480" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| #!/bin/bash | ||
|
|
||
alicefr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| set -euo pipefail | ||
|
|
||
| device=$(sudo losetup |grep test.img | awk '{print $1}'|head -n1 || true) | ||
| if [ -z "$device" ]; then | ||
| truncate test.img --size 1GB | ||
| device=$(sudo losetup -f --show test.img) | ||
| fi | ||
| echo "Device $device" | ||
| echo "cLevisTest1234" > key | ||
| sudo cryptsetup isLuks $device | ||
| if [ $? -ne 0 ]; then | ||
| yes "YES"| sudo cryptsetup luksFormat -d key --force-password $device | ||
| fi | ||
| sudo clevis luks bind -f -k key -d $device trustee "$(cat data.json)" | ||
| sudo clevis luks list -d $device | ||
| sudo clevis luks unlock -d $device -n myroot | ||
Uh oh!
There was an error while loading. Please reload this page.