mirror of
https://github.com/matter-labs/teepot.git
synced 2026-04-21 16:54:12 +02:00
chore: split-out vault code from teepot in teepot-vault
Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
63c16b1177
commit
f8bd9e6a08
61 changed files with 450 additions and 308 deletions
309
crates/teepot-vault/src/client/mod.rs
Normal file
309
crates/teepot-vault/src/client/mod.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod vault;
|
||||
|
||||
use crate::server::pki::{RaTlsCollateralExtension, RaTlsQuoteExtension};
|
||||
use actix_web::http::header;
|
||||
use anyhow::Result;
|
||||
use awc::{Client, Connector};
|
||||
use clap::Args;
|
||||
use const_oid::AssociatedOid;
|
||||
use intel_tee_quote_verification_rs::Collateral;
|
||||
use rustls::{
|
||||
client::{
|
||||
danger::{HandshakeSignatureValid, ServerCertVerifier},
|
||||
WebPkiServerVerifier,
|
||||
},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
ClientConfig, DigitallySignedStruct, Error, SignatureScheme,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{sync::Arc, time, time::Duration};
|
||||
use teepot::{quote::Report, sgx::Quote};
|
||||
pub use teepot::{
|
||||
quote::{verify_quote_with_collateral, QuoteVerificationResult},
|
||||
sgx::{parse_tcb_levels, sgx_ql_qv_result_t, EnumSet, TcbLevel},
|
||||
};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use x509_cert::{
|
||||
der::{Decode as _, Encode as _},
|
||||
Certificate,
|
||||
};
|
||||
|
||||
/// Options and arguments needed to attest a TEE
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct AttestationArgs {
|
||||
/// hex encoded SGX mrsigner of the enclave to attest
|
||||
#[arg(long)]
|
||||
pub sgx_mrsigner: Option<String>,
|
||||
/// hex encoded SGX mrenclave of the enclave to attest
|
||||
#[arg(long)]
|
||||
pub sgx_mrenclave: Option<String>,
|
||||
/// URL of the server
|
||||
#[arg(long, required = true)]
|
||||
pub server: String,
|
||||
/// allowed TCB levels, comma separated:
|
||||
/// Ok, ConfigNeeded, ConfigAndSwHardeningNeeded, SwHardeningNeeded, OutOfDate, OutOfDateConfigNeeded
|
||||
#[arg(long, value_parser = parse_tcb_levels)]
|
||||
pub sgx_allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||
}
|
||||
|
||||
/// A connection to a TEE, which implements the `teepot` attestation API
|
||||
pub struct TeeConnection {
|
||||
/// Options and arguments needed to attest a TEE
|
||||
server: String,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl TeeConnection {
|
||||
/// Create a new connection to a TEE
|
||||
///
|
||||
/// This will verify the attestation report and check that the enclave
|
||||
/// is running the expected code.
|
||||
pub fn new(args: &AttestationArgs) -> Self {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
let tls_config = Arc::new(
|
||||
ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(Self::make_verifier(args.clone())))
|
||||
.with_no_client_auth(),
|
||||
);
|
||||
|
||||
let agent = Client::builder()
|
||||
.add_default_header((header::USER_AGENT, "teepot/1.0"))
|
||||
// a "connector" wraps the stream into an encrypted connection
|
||||
.connector(Connector::new().rustls_0_23(tls_config))
|
||||
.timeout(Duration::from_secs(12000))
|
||||
.finish();
|
||||
|
||||
Self {
|
||||
server: args.server.clone(),
|
||||
client: agent,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new connection to a TEE
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe, because it does not verify the attestation report.
|
||||
pub unsafe fn new_from_client_without_attestation(server: String, client: Client) -> Self {
|
||||
Self { server, client }
|
||||
}
|
||||
|
||||
/// Get a reference to the agent, which can be used to make requests to the TEE
|
||||
///
|
||||
/// Note, that it will refuse to connect to any other TLS server than the one
|
||||
/// specified in the `AttestationArgs` of the `Self::new` function.
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Get a reference to the server URL
|
||||
pub fn server(&self) -> &str {
|
||||
&self.server
|
||||
}
|
||||
|
||||
/// Save the hash of the public server key to `REPORT_DATA` to check
|
||||
/// the attestations against it and it does not change on reconnect.
|
||||
pub fn make_verifier(args: AttestationArgs) -> impl ServerCertVerifier {
|
||||
#[derive(Debug)]
|
||||
struct V {
|
||||
args: AttestationArgs,
|
||||
server_verifier: Arc<WebPkiServerVerifier>,
|
||||
}
|
||||
impl ServerCertVerifier for V {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer,
|
||||
_intermediates: &[CertificateDer],
|
||||
_server_name: &ServerName,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||
let cert = Certificate::from_der(end_entity.as_ref())
|
||||
.map_err(|e| Error::General(format!("Failed get certificate {e:?}")))?;
|
||||
let pub_key = cert
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.to_der()
|
||||
.unwrap();
|
||||
|
||||
let hash = Sha256::digest(pub_key);
|
||||
|
||||
let exts = cert
|
||||
.tbs_certificate
|
||||
.extensions
|
||||
.ok_or_else(|| Error::General("Failed get quote in certificate".into()))?;
|
||||
|
||||
trace!("Get quote bytes!");
|
||||
|
||||
let quote_bytes = exts
|
||||
.iter()
|
||||
.find(|ext| ext.extn_id == RaTlsQuoteExtension::OID)
|
||||
.ok_or_else(|| Error::General("Failed get quote in certificate".into()))?
|
||||
.extn_value
|
||||
.as_bytes();
|
||||
|
||||
trace!("Get collateral bytes!");
|
||||
|
||||
let collateral = exts
|
||||
.iter()
|
||||
.find(|ext| ext.extn_id == RaTlsCollateralExtension::OID)
|
||||
.and_then(|ext| {
|
||||
serde_json::from_slice::<Collateral>(ext.extn_value.as_bytes())
|
||||
.map_err(|e| {
|
||||
debug!("Failed to get collateral in certificate {e:?}");
|
||||
trace!(
|
||||
"Failed to get collateral in certificate {:?}",
|
||||
String::from_utf8_lossy(ext.extn_value.as_bytes())
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
if collateral.is_none() {
|
||||
debug!("Failed to get collateral in certificate");
|
||||
}
|
||||
|
||||
let quote = Quote::try_from_bytes(quote_bytes).map_err(|e| {
|
||||
Error::General(format!("Failed get quote in certificate {e:?}"))
|
||||
})?;
|
||||
|
||||
if "e.report_body.reportdata[..32] != hash.as_slice() {
|
||||
error!("Report data mismatch");
|
||||
return Err(Error::General("Report data mismatch".to_string()));
|
||||
} else {
|
||||
info!(
|
||||
"Report data matches `{}`",
|
||||
hex::encode("e.report_body.reportdata[..32])
|
||||
);
|
||||
}
|
||||
|
||||
let current_time: i64 = time::SystemTime::now()
|
||||
.duration_since(time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as _;
|
||||
|
||||
let QuoteVerificationResult {
|
||||
collateral_expired,
|
||||
result,
|
||||
quote,
|
||||
advisories,
|
||||
earliest_expiration_date,
|
||||
..
|
||||
} = verify_quote_with_collateral(quote_bytes, collateral.as_ref(), current_time)
|
||||
.unwrap();
|
||||
|
||||
let Report::SgxEnclave(report_body) = quote.report else {
|
||||
return Err(Error::General("TDX quote and not SGX quote".into()));
|
||||
};
|
||||
|
||||
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK {
|
||||
if collateral_expired {
|
||||
error!(
|
||||
"Collateral is out of date! Expired {}",
|
||||
earliest_expiration_date
|
||||
);
|
||||
return Err(Error::General(format!(
|
||||
"Collateral is out of date! Expired {}",
|
||||
earliest_expiration_date
|
||||
)));
|
||||
}
|
||||
|
||||
let tcblevel = TcbLevel::from(result);
|
||||
if self
|
||||
.args
|
||||
.sgx_allowed_tcb_levels
|
||||
.map_or(true, |levels| !levels.contains(tcblevel))
|
||||
{
|
||||
error!("Quote verification result: {}", tcblevel);
|
||||
return Err(Error::General(format!(
|
||||
"Quote verification result: {}",
|
||||
tcblevel
|
||||
)));
|
||||
}
|
||||
|
||||
info!("TcbLevel is allowed: {}", tcblevel);
|
||||
}
|
||||
|
||||
for advisory in advisories {
|
||||
warn!("Info: Advisory ID: {advisory}");
|
||||
}
|
||||
|
||||
if let Some(mrsigner) = &self.args.sgx_mrsigner {
|
||||
let mrsigner_bytes = hex::decode(mrsigner)
|
||||
.map_err(|e| Error::General(format!("Failed to decode mrsigner: {}", e)))?;
|
||||
if report_body.mr_signer[..] != mrsigner_bytes {
|
||||
return Err(Error::General(format!(
|
||||
"mrsigner mismatch: got {}, expected {}",
|
||||
hex::encode(report_body.mr_signer),
|
||||
&mrsigner
|
||||
)));
|
||||
} else {
|
||||
info!("mrsigner `{mrsigner}` matches");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mrenclave) = &self.args.sgx_mrenclave {
|
||||
let mrenclave_bytes = hex::decode(mrenclave).map_err(|e| {
|
||||
Error::General(format!("Failed to decode mrenclave: {}", e))
|
||||
})?;
|
||||
if report_body.mr_enclave[..] != mrenclave_bytes {
|
||||
return Err(Error::General(format!(
|
||||
"mrenclave mismatch: got {}, expected {}",
|
||||
hex::encode(report_body.mr_enclave),
|
||||
&mrenclave
|
||||
)));
|
||||
} else {
|
||||
info!("mrenclave `{mrenclave}` matches");
|
||||
}
|
||||
}
|
||||
|
||||
info!("Quote verified! Connection secure!");
|
||||
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, Error> {
|
||||
self.server_verifier
|
||||
.verify_tls12_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, Error> {
|
||||
self.server_verifier
|
||||
.verify_tls13_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
self.server_verifier.supported_verify_schemes()
|
||||
}
|
||||
}
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let server_verifier = WebPkiServerVerifier::builder(Arc::new(root_store))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
V {
|
||||
args,
|
||||
server_verifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
376
crates/teepot-vault/src/client/vault.rs
Normal file
376
crates/teepot-vault/src/client/vault.rs
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use super::{AttestationArgs, TeeConnection};
|
||||
use crate::{
|
||||
json::http::{AuthRequest, AuthResponse},
|
||||
server::{pki::make_self_signed_cert, AnyHowResponseError, HttpResponseError, Status},
|
||||
};
|
||||
use actix_http::error::PayloadError;
|
||||
use actix_web::{http::header, ResponseError};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use awc::{
|
||||
error::{SendRequestError, StatusCode},
|
||||
Client, ClientResponse, Connector,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
|
||||
use rustls::ClientConfig;
|
||||
use serde_json::{json, Value};
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
sync::Arc,
|
||||
time,
|
||||
};
|
||||
use teepot::quote::error::QuoteContext;
|
||||
pub use teepot::{
|
||||
quote::{verify_quote_with_collateral, QuoteVerificationResult},
|
||||
sgx::{
|
||||
parse_tcb_levels, sgx_gramine_get_quote, sgx_ql_qv_result_t, Collateral, EnumSet, TcbLevel,
|
||||
},
|
||||
};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";
|
||||
|
||||
/// Error returned when sending a request to Vault
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VaultSendError {
|
||||
/// Error sending the request
|
||||
SendRequest(String),
|
||||
/// Error returned by the Vault API
|
||||
#[error(transparent)]
|
||||
Vault(#[from] HttpResponseError),
|
||||
}
|
||||
|
||||
impl Display for VaultSendError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VaultSendError::SendRequest(e) => write!(f, "VaultSendError: {}", e),
|
||||
VaultSendError::Vault(e) => write!(f, "VaultSendError: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
fn assert_send<T: Send>() {}
|
||||
let _ = assert_send::<VaultSendError>;
|
||||
};
|
||||
|
||||
impl From<VaultSendError> for HttpResponseError {
|
||||
fn from(value: VaultSendError) -> Self {
|
||||
match value {
|
||||
VaultSendError::SendRequest(e) => HttpResponseError::Anyhow(AnyHowResponseError {
|
||||
status_code: StatusCode::BAD_GATEWAY,
|
||||
error: anyhow!(e),
|
||||
}),
|
||||
VaultSendError::Vault(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A connection to a Vault TEE, which implements the `teepot` attestation API
|
||||
/// called by a TEE itself. This authenticates the TEE to Vault and gets a token,
|
||||
/// which can be used to access the Vault API.
|
||||
pub struct VaultConnection {
|
||||
/// Options and arguments needed to attest Vault
|
||||
pub conn: TeeConnection,
|
||||
key_hash: [u8; 64],
|
||||
client_token: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl VaultConnection {
|
||||
/// Create a new connection to Vault
|
||||
///
|
||||
/// This will verify the attestation report and check that the enclave
|
||||
/// is running the expected code.
|
||||
pub async fn new(args: &AttestationArgs, name: String) -> Result<Self> {
|
||||
let (key_hash, rustls_certificate, rustls_pk) =
|
||||
make_self_signed_cert("CN=localhost", None)?;
|
||||
|
||||
let tls_config = Arc::new(
|
||||
ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(TeeConnection::make_verifier(
|
||||
args.clone(),
|
||||
)))
|
||||
.with_client_auth_cert(vec![rustls_certificate], rustls_pk)?,
|
||||
);
|
||||
|
||||
let client = Client::builder()
|
||||
.add_default_header((header::USER_AGENT, "teepot/1.0"))
|
||||
// a "connector" wraps the stream into an encrypted connection
|
||||
.connector(Connector::new().rustls_0_23(tls_config))
|
||||
.timeout(time::Duration::from_secs(12000))
|
||||
.finish();
|
||||
|
||||
let mut this = Self {
|
||||
name,
|
||||
key_hash,
|
||||
conn: unsafe {
|
||||
TeeConnection::new_from_client_without_attestation(args.server.clone(), client)
|
||||
},
|
||||
client_token: Default::default(),
|
||||
};
|
||||
|
||||
this.client_token = this.auth().await?.auth.client_token;
|
||||
|
||||
trace!("Got Token: {:#?}", &this.client_token);
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// create a new [`VaultConnection`] to Vault from an existing connection
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe, because it does not verify the attestation report.
|
||||
pub unsafe fn new_from_client_without_attestation(
|
||||
server: String,
|
||||
client: Client,
|
||||
name: String,
|
||||
client_token: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
client_token,
|
||||
conn: unsafe { TeeConnection::new_from_client_without_attestation(server, client) },
|
||||
key_hash: [0u8; 64],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the agent, which can be used to make requests to the TEE
|
||||
///
|
||||
/// Note, that it will refuse to connect to any other TLS server than the one
|
||||
/// specified in the `AttestationArgs` of the `Self::new` function.
|
||||
pub fn agent(&self) -> &Client {
|
||||
self.conn.client()
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Result<AuthResponse> {
|
||||
info!("Getting attestation report");
|
||||
let attestation_url = AuthRequest::URL;
|
||||
let quote = sgx_gramine_get_quote(&self.key_hash).context("Failed to get own quote")?;
|
||||
let collateral = tee_qv_get_collateral("e).context("Failed to get own collateral")?;
|
||||
|
||||
let auth_req = AuthRequest {
|
||||
name: self.name.clone(),
|
||||
tee_type: "sgx".to_string(),
|
||||
quote,
|
||||
collateral: serde_json::to_string(&collateral)?,
|
||||
challenge: None,
|
||||
};
|
||||
|
||||
let mut response = self
|
||||
.agent()
|
||||
.post(&format!(
|
||||
"{server}{attestation_url}",
|
||||
server = self.conn.server,
|
||||
))
|
||||
.send_json(&auth_req)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Error sending attestation request: {}", e))?;
|
||||
|
||||
let status_code = response.status();
|
||||
if !status_code.is_success() {
|
||||
error!("Failed to login to vault: {}", status_code);
|
||||
if let Ok(r) = response.json::<Value>().await {
|
||||
eprintln!("Failed to login to vault: {}", r);
|
||||
}
|
||||
bail!("failed to login to vault: {}", status_code);
|
||||
}
|
||||
|
||||
let auth_response: Value = response.json().await.context("failed to login to vault")?;
|
||||
trace!(
|
||||
"Got AuthResponse: {:?}",
|
||||
serde_json::to_string(&auth_response)
|
||||
);
|
||||
|
||||
let auth_response: AuthResponse =
|
||||
serde_json::from_value(auth_response).context("Failed to parse AuthResponse")?;
|
||||
|
||||
trace!("Got AuthResponse: {:#?}", &auth_response);
|
||||
|
||||
Ok(auth_response)
|
||||
}
|
||||
|
||||
/// Send a put request to the vault
|
||||
pub async fn vault_put(
|
||||
&self,
|
||||
action: &str,
|
||||
url: &str,
|
||||
json: &Value,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError> {
|
||||
let full_url = format!("{}{url}", self.conn.server);
|
||||
info!("{action} via put {full_url}");
|
||||
debug!(
|
||||
"sending json: {:?}",
|
||||
serde_json::to_string(json).unwrap_or_default()
|
||||
);
|
||||
let res = self
|
||||
.agent()
|
||||
.put(full_url)
|
||||
.insert_header((VAULT_TOKEN_HEADER, self.client_token.clone()))
|
||||
.send_json(json)
|
||||
.await;
|
||||
Self::handle_client_response(action, res).await
|
||||
}
|
||||
|
||||
/// Send a get request to the vault
|
||||
pub async fn vault_get(
|
||||
&self,
|
||||
action: &str,
|
||||
url: &str,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError> {
|
||||
let full_url = format!("{}{url}", self.conn.server);
|
||||
info!("{action} via get {full_url}");
|
||||
let res = self
|
||||
.agent()
|
||||
.get(full_url)
|
||||
.insert_header((VAULT_TOKEN_HEADER, self.client_token.clone()))
|
||||
.send()
|
||||
.await;
|
||||
Self::handle_client_response(action, res).await
|
||||
}
|
||||
|
||||
async fn handle_client_response<S>(
|
||||
action: &str,
|
||||
res: std::result::Result<ClientResponse<S>, SendRequestError>,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||
{
|
||||
match res {
|
||||
Ok(mut r) => {
|
||||
let status_code = r.status();
|
||||
if status_code.is_success() {
|
||||
let msg = r.json().await.ok();
|
||||
debug!(
|
||||
"{action}: status code: {status_code} {:?}",
|
||||
serde_json::to_string(&msg)
|
||||
);
|
||||
Ok((status_code, msg))
|
||||
} else {
|
||||
let err = HttpResponseError::from_proxy(r).await;
|
||||
error!("{action}: {err:?}");
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}: {}", action, e);
|
||||
Err(VaultSendError::SendRequest(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Revoke the token
|
||||
pub async fn revoke_token(&self) -> std::result::Result<(), VaultSendError> {
|
||||
self.vault_put(
|
||||
"Revoke the token",
|
||||
"/v1/auth/token/revoke-self",
|
||||
&Value::default(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_rel_path(rel_path: &str) -> Result<(), HttpResponseError> {
|
||||
if !rel_path.is_ascii() {
|
||||
return Err(anyhow!("path is not ascii")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// check if rel_path is alphanumeric
|
||||
if !rel_path
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '_' || c == '/')
|
||||
{
|
||||
return Err(anyhow!("path is not alphanumeric")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// set a secret in the vault
|
||||
pub async fn store_secret<'de, T: serde::Serialize>(
|
||||
&self,
|
||||
val: T,
|
||||
rel_path: &str,
|
||||
) -> Result<(), HttpResponseError> {
|
||||
self.store_secret_for_tee(&self.name, val, rel_path).await
|
||||
}
|
||||
|
||||
/// set a secret in the vault for a different TEE
|
||||
pub async fn store_secret_for_tee<'de, T: serde::Serialize>(
|
||||
&self,
|
||||
tee_name: &str,
|
||||
val: T,
|
||||
rel_path: &str,
|
||||
) -> Result<(), HttpResponseError> {
|
||||
Self::check_rel_path(rel_path)?;
|
||||
|
||||
let value = serde_json::to_value(val)
|
||||
.context("converting value to json")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let value = json!({ "data" : { "_" : value } });
|
||||
|
||||
self.vault_put(
|
||||
"Setting secret",
|
||||
&format!("/v1/secret/data/tee/{}/{}", tee_name, rel_path),
|
||||
&value,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get a secret from the vault
|
||||
pub async fn load_secret<'de, T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
rel_path: &str,
|
||||
) -> Result<Option<T>, HttpResponseError> {
|
||||
self.load_secret_for_tee(&self.name, rel_path).await
|
||||
}
|
||||
|
||||
/// get a secret from the vault for a specific TEE
|
||||
pub async fn load_secret_for_tee<'de, T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
tee_name: &str,
|
||||
rel_path: &str,
|
||||
) -> Result<Option<T>, HttpResponseError> {
|
||||
Self::check_rel_path(rel_path)?;
|
||||
let v = self
|
||||
.vault_get(
|
||||
"Getting secret",
|
||||
&format!("/v1/secret/data/tee/{}/{}", tee_name, rel_path),
|
||||
)
|
||||
.await
|
||||
.or_else(|e| match e {
|
||||
VaultSendError::Vault(ref se) => {
|
||||
if se.status_code() == StatusCode::NOT_FOUND {
|
||||
debug!("Secret not found: {}", rel_path);
|
||||
Ok((StatusCode::OK, None))
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
VaultSendError::SendRequest(_) => Err(e),
|
||||
})?
|
||||
.1
|
||||
.as_ref()
|
||||
.and_then(|v| v.get("data"))
|
||||
.and_then(|v| v.get("data"))
|
||||
.and_then(|v| v.get("_"))
|
||||
.and_then(|v| serde_json::from_value(v.clone()).transpose())
|
||||
.transpose()
|
||||
.context("Error getting value from vault")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.flatten();
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue