mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-22 15:34:48 +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
21
crates/teepot-vault/bin/tee-vault-unseal/Cargo.toml
Normal file
21
crates/teepot-vault/bin/tee-vault-unseal/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "tee-vault-unseal"
|
||||
publish = false
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-web.workspace = true
|
||||
anyhow.workspace = true
|
||||
awc.workspace = true
|
||||
clap.workspace = true
|
||||
rustls.workspace = true
|
||||
serde_json.workspace = true
|
||||
teepot.workspace = true
|
||||
teepot-vault.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-log.workspace = true
|
||||
tracing-subscriber.workspace = true
|
|
@ -0,0 +1,80 @@
|
|||
# Read system health check
|
||||
path "sys/health"
|
||||
{
|
||||
capabilities = ["read", "sudo"]
|
||||
}
|
||||
|
||||
# Create and manage ACL policies broadly across Vault
|
||||
|
||||
# List existing policies
|
||||
path "sys/policies/acl"
|
||||
{
|
||||
capabilities = ["list"]
|
||||
}
|
||||
|
||||
# Create and manage ACL policies
|
||||
path "sys/policies/acl/*"
|
||||
{
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# Enable and manage authentication methods broadly across Vault
|
||||
|
||||
# Manage auth methods broadly across Vault
|
||||
path "auth/*"
|
||||
{
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# Create, update, and delete auth methods
|
||||
path "sys/auth/*"
|
||||
{
|
||||
capabilities = ["create", "update", "delete", "sudo"]
|
||||
}
|
||||
|
||||
# List auth methods
|
||||
path "sys/auth"
|
||||
{
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Enable and manage the key/value secrets engine at `secret/` path
|
||||
|
||||
# List, create, update, and delete key/value secrets
|
||||
path "secret/*"
|
||||
{
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# Manage secrets engines
|
||||
path "sys/mounts/*"
|
||||
{
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# List existing secrets engines.
|
||||
path "sys/mounts"
|
||||
{
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Manage plugins
|
||||
# https://developer.hashicorp.com/vault/api-docs/system/plugins-catalog
|
||||
path "sys/plugins/catalog/*"
|
||||
{
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
|
||||
# List existing plugins
|
||||
# https://developer.hashicorp.com/vault/api-docs/system/plugins-catalog
|
||||
path "sys/plugins/catalog"
|
||||
{
|
||||
capabilities = ["list"]
|
||||
}
|
||||
|
||||
# Reload plugins
|
||||
# https://developer.hashicorp.com/vault/api-docs/system/plugins-reload-backend
|
||||
path "sys/plugins/reload/backend"
|
||||
{
|
||||
capabilities = ["create", "update", "sudo"]
|
||||
}
|
137
crates/teepot-vault/bin/tee-vault-unseal/src/init.rs
Normal file
137
crates/teepot-vault/bin/tee-vault-unseal/src/init.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use crate::{get_vault_status, UnsealServerState, Worker};
|
||||
use actix_web::error::ErrorBadRequest;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use awc::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use teepot_vault::client::TeeConnection;
|
||||
use teepot_vault::json::http::{Init, InitResponse, VaultInitRequest};
|
||||
use teepot_vault::json::secrets::AdminConfig;
|
||||
use teepot_vault::server::{HttpResponseError, Status};
|
||||
use tracing::{debug, error, info, instrument, trace};
|
||||
|
||||
#[instrument(level = "info", name = "/v1/sys/init", skip_all)]
|
||||
pub async fn post_init(
|
||||
worker: web::Data<Worker>,
|
||||
init: web::Json<Init>,
|
||||
) -> Result<HttpResponse, HttpResponseError> {
|
||||
let Init {
|
||||
pgp_keys,
|
||||
secret_shares,
|
||||
secret_threshold,
|
||||
admin_pgp_keys,
|
||||
admin_threshold,
|
||||
admin_tee_mrenclave,
|
||||
} = init.into_inner();
|
||||
let conn = TeeConnection::new(&worker.vault_attestation);
|
||||
let client = conn.client();
|
||||
let vault_url = &worker.config.vault_url;
|
||||
|
||||
let vault_init = VaultInitRequest {
|
||||
pgp_keys,
|
||||
secret_shares,
|
||||
secret_threshold,
|
||||
};
|
||||
|
||||
if admin_threshold < 1 {
|
||||
return Ok(HttpResponse::from_error(ErrorBadRequest(
|
||||
json!({"error": "admin_threshold must be at least 1"}),
|
||||
)));
|
||||
}
|
||||
|
||||
if admin_threshold > admin_pgp_keys.len() {
|
||||
return Ok(HttpResponse::from_error(ErrorBadRequest(
|
||||
json!({"error": "admin_threshold must be less than or equal to the number of admin_pgp_keys"}),
|
||||
)));
|
||||
}
|
||||
|
||||
loop {
|
||||
let current_state = worker.state.read().unwrap().clone();
|
||||
match current_state {
|
||||
UnsealServerState::VaultUninitialized => {
|
||||
break;
|
||||
}
|
||||
UnsealServerState::VaultUnsealed => {
|
||||
return Err(anyhow!("Vault already unsealed")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultInitialized { .. } => {
|
||||
return Err(anyhow!("Vault already initialized")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultInitializedAndConfigured => {
|
||||
return Err(anyhow!("Vault already initialized")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::Undefined => {
|
||||
let state = get_vault_status(vault_url, client).await;
|
||||
*worker.state.write().unwrap() = state;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Sending init request to Vault {}",
|
||||
serde_json::to_string(&vault_init).unwrap()
|
||||
);
|
||||
let mut response = client
|
||||
.post(format!("{}/v1/sys/init", vault_url))
|
||||
.send_json(&vault_init)
|
||||
.await?;
|
||||
|
||||
let status_code = response.status();
|
||||
if !status_code.is_success() {
|
||||
error!("Vault returned server error: {}", status_code);
|
||||
return Err(HttpResponseError::from_proxy(response).await);
|
||||
}
|
||||
|
||||
let response = response
|
||||
.json::<serde_json::Value>()
|
||||
.await
|
||||
.context("Failed to convert to json")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
info!("Vault initialized");
|
||||
trace!("response {}", response);
|
||||
|
||||
let root_token = response["root_token"]
|
||||
.as_str()
|
||||
.ok_or(anyhow!("No `root_token` field"))
|
||||
.status(StatusCode::BAD_GATEWAY)?
|
||||
.to_string();
|
||||
|
||||
debug!("Root token: {root_token}");
|
||||
|
||||
let unseal_keys = response["keys_base64"]
|
||||
.as_array()
|
||||
.ok_or(anyhow!("No `keys_base64` field"))
|
||||
.status(StatusCode::BAD_GATEWAY)?
|
||||
.iter()
|
||||
.map(|v| v.as_str().unwrap().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
debug!("Unseal keys: {}", unseal_keys.join(", "));
|
||||
|
||||
/*
|
||||
FIXME: use unseal keys to create new token
|
||||
let mut output = File::create("/opt/vault/data/root_token")
|
||||
.context("Failed to create `/opt/vault/data/root_token`")?;
|
||||
output
|
||||
.write_all(root_token.as_bytes())
|
||||
.context("Failed to write root_token")?;
|
||||
*/
|
||||
|
||||
*worker.state.write().unwrap() = UnsealServerState::VaultInitialized {
|
||||
admin_config: AdminConfig {
|
||||
admin_pgp_keys,
|
||||
admin_threshold,
|
||||
},
|
||||
admin_tee_mrenclave,
|
||||
root_token,
|
||||
};
|
||||
|
||||
let response = InitResponse { unseal_keys };
|
||||
|
||||
Ok(HttpResponse::Ok().json(response)) // <- send response
|
||||
}
|
236
crates/teepot-vault/bin/tee-vault-unseal/src/main.rs
Normal file
236
crates/teepot-vault/bin/tee-vault-unseal/src/main.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Server to initialize and unseal the Vault TEE.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
mod init;
|
||||
mod unseal;
|
||||
|
||||
use actix_web::{rt::time::sleep, web, web::Data, App, HttpServer};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use awc::Client;
|
||||
use clap::Parser;
|
||||
use init::post_init;
|
||||
use rustls::ServerConfig;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Read;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use teepot::pki::make_self_signed_cert;
|
||||
use teepot::sgx::{parse_tcb_levels, EnumSet, TcbLevel};
|
||||
use teepot_vault::client::{AttestationArgs, TeeConnection};
|
||||
use teepot_vault::json::http::{Init, Unseal};
|
||||
use teepot_vault::json::secrets::AdminConfig;
|
||||
use teepot_vault::server::attestation::{get_quote_and_collateral, VaultAttestationArgs};
|
||||
use teepot_vault::server::new_json_cfg;
|
||||
use tracing::{error, info};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
|
||||
use unseal::post_unseal;
|
||||
|
||||
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";
|
||||
|
||||
/// Worker thread state and data
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Worker {
|
||||
/// TLS config for the HTTPS client
|
||||
pub vault_attestation: Arc<AttestationArgs>,
|
||||
/// Server config
|
||||
pub config: Arc<UnsealServerConfig>,
|
||||
/// Server state
|
||||
pub state: Arc<RwLock<UnsealServerState>>,
|
||||
}
|
||||
|
||||
/// Global Server config
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UnsealServerConfig {
|
||||
/// Vault URL
|
||||
pub vault_url: String,
|
||||
/// The expected report_data for the Vault TEE
|
||||
pub report_data: Box<[u8]>,
|
||||
/// allowed TCB levels
|
||||
pub allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||
/// SHA256 of the vault_auth_tee plugin binary
|
||||
pub vault_auth_tee_sha: String,
|
||||
/// version string of the vault_auth_tee plugin
|
||||
pub vault_auth_tee_version: String,
|
||||
/// the common cacert file for the vault cluster
|
||||
pub ca_cert_file: PathBuf,
|
||||
}
|
||||
|
||||
/// Server state
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UnsealServerState {
|
||||
/// Undefined
|
||||
Undefined,
|
||||
/// Vault is not yet initialized
|
||||
VaultUninitialized,
|
||||
/// Vault is initialized but not unsealed
|
||||
VaultInitialized {
|
||||
/// config for the admin TEE
|
||||
admin_config: AdminConfig,
|
||||
/// initial admin TEE mrenclave
|
||||
admin_tee_mrenclave: String,
|
||||
/// Vault root token
|
||||
root_token: String,
|
||||
},
|
||||
/// Vault is already initialized but not unsealed
|
||||
/// and should already be configured
|
||||
VaultInitializedAndConfigured,
|
||||
/// Vault is unsealed
|
||||
VaultUnsealed,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// allowed TCB levels, comma separated
|
||||
#[arg(long, value_parser = parse_tcb_levels, env = "ALLOWED_TCB_LEVELS", default_value = "Ok")]
|
||||
allowed_tcb_levels: EnumSet<TcbLevel>,
|
||||
/// port to listen on
|
||||
#[arg(long, env = "PORT", default_value = "8443")]
|
||||
port: u16,
|
||||
/// the sha256 of the `vault_auth_tee` plugin, with precedence over the file
|
||||
#[arg(long, env = "VAULT_AUTH_TEE_SHA256")]
|
||||
vault_auth_tee_sha: Option<String>,
|
||||
/// the file containing the sha256 of the `vault_auth_tee` plugin
|
||||
#[arg(long, env = "VAULT_AUTH_TEE_SHA256_FILE")]
|
||||
vault_auth_tee_sha_file: Option<PathBuf>,
|
||||
#[arg(long, env = "VAULT_AUTH_TEE_VERSION")]
|
||||
vault_auth_tee_version: String,
|
||||
/// ca cert file
|
||||
#[arg(long, env = "CA_CERT_FILE", default_value = "/opt/vault/cacert.pem")]
|
||||
ca_cert_file: PathBuf,
|
||||
#[clap(flatten)]
|
||||
pub attestation: VaultAttestationArgs,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> Result<()> {
|
||||
LogTracer::init().context("Failed to set logger")?;
|
||||
|
||||
let subscriber = Registry::default()
|
||||
.with(EnvFilter::from_default_env())
|
||||
.with(
|
||||
fmt::layer()
|
||||
.with_span_events(fmt::format::FmtSpan::NEW)
|
||||
.with_writer(std::io::stderr),
|
||||
);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
info!("Starting up");
|
||||
|
||||
if let Err(e) = get_quote_and_collateral(Some(args.allowed_tcb_levels), &[0u8; 64]) {
|
||||
error!("failed to get quote and collateral: {e:?}");
|
||||
// don't return for now, we can still serve requests but we won't be able to attest
|
||||
}
|
||||
|
||||
let (report_data, cert_chain, priv_key) = make_self_signed_cert("CN=localhost", None)?;
|
||||
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
// init server config builder with safe defaults
|
||||
let config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert([cert_chain].into(), priv_key)
|
||||
.context("Failed to load TLS key/cert files")?;
|
||||
|
||||
let attestation_args: AttestationArgs = args.attestation.clone().into();
|
||||
|
||||
let conn = TeeConnection::new(&attestation_args);
|
||||
|
||||
let server_state = get_vault_status(&args.attestation.vault_addr, conn.client()).await;
|
||||
|
||||
let vault_auth_tee_sha = if let Some(vault_auth_tee_sha) = args.vault_auth_tee_sha {
|
||||
vault_auth_tee_sha
|
||||
} else if let Some(sha_file) = args.vault_auth_tee_sha_file {
|
||||
let mut file = std::fs::File::open(sha_file)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
contents.trim_end().into()
|
||||
} else {
|
||||
bail!("Neither `VAULT_AUTH_TEE_SHA256_FILE` nor `VAULT_AUTH_TEE_SHA256` set!");
|
||||
};
|
||||
|
||||
info!("Starting HTTPS server at port {}", args.port);
|
||||
let server_config = Arc::new(UnsealServerConfig {
|
||||
vault_url: args.attestation.vault_addr,
|
||||
report_data: Box::from(report_data),
|
||||
allowed_tcb_levels: Some(args.allowed_tcb_levels),
|
||||
vault_auth_tee_sha,
|
||||
vault_auth_tee_version: args.vault_auth_tee_version,
|
||||
ca_cert_file: args.ca_cert_file,
|
||||
});
|
||||
|
||||
let server_state = Arc::new(RwLock::new(server_state));
|
||||
|
||||
let worker = Worker {
|
||||
vault_attestation: Arc::new(attestation_args),
|
||||
config: server_config,
|
||||
state: server_state,
|
||||
};
|
||||
|
||||
let server = match HttpServer::new(move || {
|
||||
App::new()
|
||||
// enable logger
|
||||
//.wrap(TracingLogger::default())
|
||||
.app_data(new_json_cfg())
|
||||
.app_data(Data::new(worker.clone()))
|
||||
.service(web::resource(Init::URL).route(web::post().to(post_init)))
|
||||
.service(web::resource(Unseal::URL).route(web::post().to(post_unseal)))
|
||||
})
|
||||
.bind_rustls_0_23((Ipv6Addr::UNSPECIFIED, args.port), config)
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("Failed to bind to port {}: {e:?}", args.port);
|
||||
return Err(e).context(format!("Failed to bind to port {}", args.port));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = server.worker_max_blocking_threads(2).workers(8).run().await {
|
||||
error!("failed to start HTTPS server: {e:?}");
|
||||
return Err(e).context("Failed to start HTTPS server");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_vault_status(vault_url: &str, client: &Client) -> UnsealServerState {
|
||||
loop {
|
||||
let r = client
|
||||
.get(format!("{}/v1/sys/health", vault_url))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
if let Ok(r) = r {
|
||||
// https://developer.hashicorp.com/vault/api-docs/system/health
|
||||
match r.status().as_u16() {
|
||||
200 | 429 | 472 | 473 => {
|
||||
info!("Vault is initialized and unsealed");
|
||||
break UnsealServerState::VaultUnsealed;
|
||||
}
|
||||
501 => {
|
||||
info!("Vault is not initialized");
|
||||
break UnsealServerState::VaultUninitialized;
|
||||
}
|
||||
503 => {
|
||||
info!("Vault is initialized but not unsealed");
|
||||
break UnsealServerState::VaultInitializedAndConfigured;
|
||||
}
|
||||
s => {
|
||||
error!("Vault is not ready: status code {s}");
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Waiting for vault to be ready");
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
409
crates/teepot-vault/bin/tee-vault-unseal/src/unseal.rs
Normal file
409
crates/teepot-vault/bin/tee-vault-unseal/src/unseal.rs
Normal file
|
@ -0,0 +1,409 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use crate::{get_vault_status, UnsealServerConfig, UnsealServerState, Worker, VAULT_TOKEN_HEADER};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::rt::time::sleep;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use awc::{Client, ClientRequest, SendClientRequest};
|
||||
use serde_json::{json, Value};
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
use teepot_vault::client::vault::VaultConnection;
|
||||
use teepot_vault::client::TeeConnection;
|
||||
use teepot_vault::json::http::Unseal;
|
||||
use teepot_vault::json::secrets::{AdminConfig, AdminState};
|
||||
use teepot_vault::server::{HttpResponseError, Status};
|
||||
use tracing::{debug, error, info, instrument, trace};
|
||||
|
||||
#[instrument(level = "info", name = "/v1/sys/unseal", skip_all)]
|
||||
pub async fn post_unseal(
|
||||
worker: web::Data<Worker>,
|
||||
item: web::Json<Unseal>,
|
||||
) -> Result<HttpResponse, HttpResponseError> {
|
||||
let conn = TeeConnection::new(&worker.vault_attestation);
|
||||
let client = conn.client();
|
||||
let app = &worker.config;
|
||||
let vault_url = &app.vault_url;
|
||||
|
||||
loop {
|
||||
let current_state = worker.state.read().unwrap().clone();
|
||||
match current_state {
|
||||
UnsealServerState::VaultUninitialized => {
|
||||
return Err(anyhow!("Vault not yet initialized")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultUnsealed => {
|
||||
return Err(anyhow!("Vault already unsealed")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultInitialized { .. } => {
|
||||
break;
|
||||
}
|
||||
UnsealServerState::VaultInitializedAndConfigured => {
|
||||
break;
|
||||
}
|
||||
UnsealServerState::Undefined => {
|
||||
let state = get_vault_status(vault_url, client).await;
|
||||
*worker.state.write().unwrap() = state;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.post(format!("{}/v1/sys/unseal", vault_url))
|
||||
.send_json(&item.0)
|
||||
.await?;
|
||||
|
||||
let status_code = response.status();
|
||||
if !status_code.is_success() {
|
||||
error!("Vault returned server error: {}", status_code);
|
||||
let mut client_resp = HttpResponse::build(status_code);
|
||||
for (header_name, header_value) in response.headers().iter() {
|
||||
client_resp.insert_header((header_name.clone(), header_value.clone()));
|
||||
}
|
||||
return Ok(client_resp.streaming(response));
|
||||
}
|
||||
|
||||
let response: Value = response
|
||||
.json()
|
||||
.await
|
||||
.context("parsing unseal response")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
debug!("unseal: {:?}", response);
|
||||
|
||||
if response.get("errors").is_some() {
|
||||
return Ok(HttpResponse::Ok().json(response));
|
||||
}
|
||||
|
||||
let sealed = response
|
||||
.get("sealed")
|
||||
.map(|v| v.as_bool().unwrap_or(true))
|
||||
.unwrap_or(true);
|
||||
|
||||
debug!(sealed);
|
||||
|
||||
// if unsealed
|
||||
if !sealed {
|
||||
let mut state = UnsealServerState::VaultUnsealed;
|
||||
std::mem::swap(&mut *worker.state.write().unwrap(), &mut state);
|
||||
|
||||
match state {
|
||||
UnsealServerState::VaultUninitialized => {
|
||||
return Err(anyhow!("Invalid internal state")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultUnsealed => {
|
||||
return Err(anyhow!("Invalid internal state")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
UnsealServerState::VaultInitialized {
|
||||
admin_config,
|
||||
admin_tee_mrenclave,
|
||||
root_token,
|
||||
} => {
|
||||
debug!(root_token);
|
||||
info!("Vault is unsealed");
|
||||
|
||||
vault_configure_unsealed(
|
||||
app,
|
||||
&admin_config,
|
||||
&root_token,
|
||||
&admin_tee_mrenclave,
|
||||
client,
|
||||
)
|
||||
.await
|
||||
.context("Failed to configure unsealed vault")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
// destroy root token
|
||||
let _response = client
|
||||
.post(format!("{}/v1/auth/token/revoke-self", app.vault_url))
|
||||
.insert_header((VAULT_TOKEN_HEADER, root_token.to_string()))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
info!("Vault unsealed and configured!");
|
||||
}
|
||||
UnsealServerState::VaultInitializedAndConfigured => {
|
||||
info!("Vault is unsealed and hopefully configured!");
|
||||
info!("Initiating raft join");
|
||||
// load TLS cert chain
|
||||
let mut cert_file = File::open(&app.ca_cert_file)
|
||||
.context("Failed to open TLS cert chain")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let mut cert_buf = Vec::new();
|
||||
cert_file
|
||||
.read_to_end(&mut cert_buf)
|
||||
.context("Failed to read TLS cert chain")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let cert_chain = std::str::from_utf8(&cert_buf)
|
||||
.context("Failed to parse TLS cert chain as UTF-8")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.to_string();
|
||||
|
||||
let payload = json!({"leader_ca_cert": cert_chain, "retry": true });
|
||||
|
||||
let mut response = client
|
||||
.post(format!("{}/v1/sys/storage/raft/join", vault_url))
|
||||
.send_json(&payload)
|
||||
.await?;
|
||||
|
||||
let status_code = response.status();
|
||||
if !status_code.is_success() {
|
||||
error!("Vault returned server error: {}", status_code);
|
||||
let mut client_resp = HttpResponse::build(status_code);
|
||||
for (header_name, header_value) in response.headers().iter() {
|
||||
client_resp.insert_header((header_name.clone(), header_value.clone()));
|
||||
}
|
||||
return Ok(client_resp.streaming(response));
|
||||
}
|
||||
|
||||
let response: Value = response
|
||||
.json()
|
||||
.await
|
||||
.context("parsing raft join response")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
debug!("raft join: {:?}", response);
|
||||
|
||||
if response.get("errors").is_some() {
|
||||
return Ok(HttpResponse::Ok().json(response));
|
||||
}
|
||||
}
|
||||
UnsealServerState::Undefined => {
|
||||
unreachable!("Invalid internal state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Accepted().json(response)) // <- send response
|
||||
}
|
||||
|
||||
pub async fn vault_configure_unsealed(
|
||||
app: &UnsealServerConfig,
|
||||
admin_config: &AdminConfig,
|
||||
root_token: &str,
|
||||
admin_tee_mrenclave: &str,
|
||||
c: &Client,
|
||||
) -> Result<(), HttpResponseError> {
|
||||
wait_for_plugins_catalog(app, root_token, c).await;
|
||||
|
||||
if !plugin_is_already_running(app, root_token, c).await? {
|
||||
let r = vault(
|
||||
"Installing vault-auth-tee plugin",
|
||||
c.put(format!(
|
||||
"{}/v1/sys/plugins/catalog/auth/vault-auth-tee",
|
||||
app.vault_url
|
||||
)),
|
||||
root_token,
|
||||
json!({
|
||||
"sha256": app.vault_auth_tee_sha,
|
||||
"command": "vault-auth-tee",
|
||||
"version": app.vault_auth_tee_version
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
if !r.status().is_success() {
|
||||
let err = HttpResponseError::from_proxy(r).await;
|
||||
return Err(err);
|
||||
}
|
||||
} else {
|
||||
info!("vault-auth-tee plugin already installed");
|
||||
}
|
||||
|
||||
if !plugin_is_already_running(app, root_token, c).await? {
|
||||
let r = vault(
|
||||
"Activating vault-auth-tee plugin",
|
||||
c.post(format!("{}/v1/sys/auth/tee", app.vault_url)),
|
||||
root_token,
|
||||
json!({"type": "vault-auth-tee"}),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
if !r.status().is_success() {
|
||||
let err = HttpResponseError::from_proxy(r).await;
|
||||
return Err(err);
|
||||
}
|
||||
} else {
|
||||
info!("vault-auth-tee plugin already activated");
|
||||
}
|
||||
|
||||
if let Ok(mut r) = c
|
||||
.get(format!("{}/v1/auth/tee/tees?list=true", app.vault_url))
|
||||
.insert_header((VAULT_TOKEN_HEADER, root_token))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
let r: Value = r
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
trace!("{:?}", r);
|
||||
if let Some(tees) = r.get("data").and_then(|v| v.get("keys")) {
|
||||
if let Some(tees) = tees.as_array() {
|
||||
if tees.contains(&json!("root")) {
|
||||
info!("root TEE already installed");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vault(
|
||||
"Installing root TEE",
|
||||
c.put(format!("{}/v1/auth/tee/tees/admin", app.vault_url)),
|
||||
root_token,
|
||||
json!({
|
||||
"lease": "1000",
|
||||
"name": "admin",
|
||||
"types": "sgx",
|
||||
"sgx_allowed_tcb_levels": "Ok,SwHardeningNeeded",
|
||||
"sgx_mrenclave": &admin_tee_mrenclave,
|
||||
"token_policies": "admin"
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
// Install admin policies
|
||||
let admin_policy = include_str!("admin-policy.hcl");
|
||||
vault(
|
||||
"Installing admin policy",
|
||||
c.put(format!("{}/v1/sys/policies/acl/admin", app.vault_url)),
|
||||
root_token,
|
||||
json!({ "policy": admin_policy }),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
vault(
|
||||
"Enable the key/value secrets engine v1 at secret/.",
|
||||
c.put(format!("{}/v1/sys/mounts/secret", app.vault_url)),
|
||||
root_token,
|
||||
json!({ "type": "kv", "description": "K/V v1" } ),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
// Create a `VaultConnection` for the `admin` tee to initialize the secrets for it.
|
||||
// Safety: the connection was already attested
|
||||
let admin_vcon = unsafe {
|
||||
VaultConnection::new_from_client_without_attestation(
|
||||
app.vault_url.clone(),
|
||||
c.clone(),
|
||||
"admin".into(),
|
||||
root_token.to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
// initialize the admin config
|
||||
admin_vcon.store_secret(admin_config, "config").await?;
|
||||
admin_vcon
|
||||
.store_secret(AdminState::default(), "state")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_plugins_catalog(app: &UnsealServerConfig, root_token: &str, c: &Client) {
|
||||
info!("Waiting for plugins to be loaded");
|
||||
loop {
|
||||
let r = c
|
||||
.get(format!("{}/v1/sys/plugins/catalog", app.vault_url))
|
||||
.insert_header((VAULT_TOKEN_HEADER, root_token))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match r {
|
||||
Ok(r) => {
|
||||
if r.status().is_success() {
|
||||
break;
|
||||
} else {
|
||||
debug!("/v1/sys/plugins/catalog status: {:#?}", r)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("/v1/sys/plugins/catalog error: {}", e)
|
||||
}
|
||||
}
|
||||
|
||||
info!("Waiting for plugins to be loaded");
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn plugin_is_already_running(
|
||||
app: &UnsealServerConfig,
|
||||
root_token: &str,
|
||||
c: &Client,
|
||||
) -> std::result::Result<bool, HttpResponseError> {
|
||||
if let Ok(mut r) = c
|
||||
.get(format!("{}/v1/sys/auth", app.vault_url))
|
||||
.insert_header((VAULT_TOKEN_HEADER, root_token))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
if !r.status().is_success() {
|
||||
return Ok(false);
|
||||
}
|
||||
let r: Value = r
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| anyhow!("{:?}", e))
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
trace!("{}", r.to_string());
|
||||
|
||||
let is_running = r
|
||||
.get("data")
|
||||
.and_then(|v| v.get("tee/"))
|
||||
.and_then(|v| v.get("running_sha256"))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.and_then(|v| {
|
||||
if v == app.vault_auth_tee_sha {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
Ok(is_running)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn vault(
|
||||
action: &str,
|
||||
req: ClientRequest,
|
||||
token: &str,
|
||||
json: Value,
|
||||
) -> <SendClientRequest as Future>::Output {
|
||||
info!("{}", action);
|
||||
debug!("json: {:?}", json);
|
||||
match req
|
||||
.insert_header((VAULT_TOKEN_HEADER, token))
|
||||
.send_json(&json)
|
||||
.await
|
||||
{
|
||||
Ok(r) => {
|
||||
debug!("response {:?}", r);
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}: {}", action, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue