mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-22 15:34:48 +02:00
feat: initial commit
Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
aff4dd30bd
commit
89ffbd35a8
123 changed files with 16508 additions and 0 deletions
26
bin/tee-vault-admin/Cargo.toml
Normal file
26
bin/tee-vault-admin/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "tee-vault-admin"
|
||||
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
|
||||
base64.workspace = true
|
||||
bytemuck.workspace = true
|
||||
clap.workspace = true
|
||||
hex.workspace = true
|
||||
mio.workspace = true
|
||||
rustls.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
teepot.workspace = true
|
||||
tracing-actix-web.workspace = true
|
||||
tracing-log.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
85
bin/tee-vault-admin/Dockerfile-azure
Normal file
85
bin/tee-vault-admin/Dockerfile-azure
Normal file
|
@ -0,0 +1,85 @@
|
|||
FROM docker.io/ubuntu:20.04 AS azuredcap
|
||||
WORKDIR /build
|
||||
ADD https://github.com/microsoft/Azure-DCAP-Client/archive/refs/tags/1.12.0.tar.gz ./Azure-DCAP-Client.tar.gz
|
||||
RUN tar -xvf Azure-DCAP-Client.tar.gz
|
||||
COPY assets/Azure-DCAP-Client.patch ./Azure-DCAP-Client.patch
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y software-properties-common; \
|
||||
add-apt-repository ppa:team-xbmc/ppa -y; \
|
||||
apt-get update; \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
libssl-dev \
|
||||
libcurl4-openssl-dev \
|
||||
pkg-config \
|
||||
nlohmann-json3-dev \
|
||||
wget \
|
||||
dos2unix \
|
||||
;
|
||||
|
||||
WORKDIR /build/Azure-DCAP-Client-1.12.0
|
||||
RUN dos2unix src/dcap_provider.cpp && patch -p1 < ../Azure-DCAP-Client.patch
|
||||
WORKDIR /build/Azure-DCAP-Client-1.12.0/src/Linux
|
||||
RUN ./configure && make && make install
|
||||
|
||||
FROM docker.io/rust:1-bullseye AS buildtee
|
||||
RUN curl -fsSLo /usr/share/keyrings/intel.asc https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel.asc] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
rsync \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libprotobuf-dev \
|
||||
protobuf-compiler \
|
||||
clang \
|
||||
libsgx-headers \
|
||||
libsgx-dcap-quote-verify-dev
|
||||
|
||||
WORKDIR /opt/vault/plugins
|
||||
|
||||
WORKDIR /build
|
||||
RUN --mount=type=bind,target=/data rsync --exclude='/.git' --filter="dir-merge,- .gitignore" --exclude "Dockerfile-*" --exclude 'tee-vault-admin.manifest.template' -av /data/ ./
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry --mount=type=cache,target=target \
|
||||
RUSTFLAGS="-C target-cpu=icelake-server --cfg mio_unsupported_force_waker_pipe" \
|
||||
cargo build --locked --target x86_64-unknown-linux-gnu --release -p tee-vault-admin --bin tee-vault-admin \
|
||||
&& mv ./target/x86_64-unknown-linux-gnu/release/tee-vault-admin ./
|
||||
|
||||
FROM docker.io/gramineproject/gramine:v1.5
|
||||
|
||||
RUN curl -fsSLo /usr/share/keyrings/microsoft.asc https://packages.microsoft.com/keys/microsoft.asc \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.asc] https://packages.microsoft.com/ubuntu/20.04/prod focal main" > /etc/apt/sources.list.d/msprod.list \
|
||||
&& apt-get update \
|
||||
&& apt purge -y libsgx-dcap-default-qpl \
|
||||
&& apt-get install -y az-dcap-client
|
||||
|
||||
RUN apt purge -y libsgx-ae-qve
|
||||
# libsgx-urts
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# So we only have to use one gramine template
|
||||
RUN touch /etc/sgx_default_qcnl.conf
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=buildtee /build/tee-vault-admin .
|
||||
COPY ./bin/tee-vault-admin/tee-vault-admin.manifest.template .
|
||||
COPY vault/enclave-key.pem .
|
||||
|
||||
# The original Azure library is still delivering expired collateral, so we have to use a patched version
|
||||
COPY --from=azuredcap /usr/local/lib/libdcap_quoteprov.so /usr/lib/
|
||||
|
||||
RUN gramine-manifest -Darch_libdir=/lib/x86_64-linux-gnu -Dexecdir=/usr/bin -Dlog_level=warning tee-vault-admin.manifest.template tee-vault-admin.manifest \
|
||||
&& gramine-sgx-sign --manifest tee-vault-admin.manifest --output tee-vault-admin.manifest.sgx --key enclave-key.pem \
|
||||
&& rm enclave-key.pem
|
||||
|
||||
EXPOSE 8443
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "-c"]
|
||||
CMD [ "/restart_aesm.sh ; exec gramine-sgx tee-vault-admin" ]
|
58
bin/tee-vault-admin/Dockerfile-intel
Normal file
58
bin/tee-vault-admin/Dockerfile-intel
Normal file
|
@ -0,0 +1,58 @@
|
|||
FROM docker.io/rust:1-bullseye AS buildtee
|
||||
RUN curl -fsSLo /usr/share/keyrings/intel.asc https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel.asc] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
rsync \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libprotobuf-dev \
|
||||
protobuf-compiler \
|
||||
clang \
|
||||
libsgx-headers \
|
||||
libsgx-dcap-quote-verify-dev
|
||||
|
||||
WORKDIR /opt/vault/plugins
|
||||
|
||||
WORKDIR /build
|
||||
RUN --mount=type=bind,target=/data rsync --exclude='/.git' --filter="dir-merge,- .gitignore" --exclude "Dockerfile-*" --exclude 'tee-vault-admin.manifest.template' -av /data/ ./
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry --mount=type=cache,target=target \
|
||||
RUSTFLAGS="-C target-cpu=icelake-server --cfg mio_unsupported_force_waker_pipe" \
|
||||
cargo build --locked --target x86_64-unknown-linux-gnu --release -p tee-vault-admin --bin tee-vault-admin \
|
||||
&& mv ./target/x86_64-unknown-linux-gnu/release/tee-vault-admin ./
|
||||
|
||||
FROM docker.io/gramineproject/gramine:v1.5
|
||||
|
||||
RUN curl -fsSLo /usr/share/keyrings/intel.asc https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel.asc] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
libsgx-dcap-default-qpl \
|
||||
libsgx-urts \
|
||||
libsgx-enclave-common \
|
||||
libsgx-dcap-quote-verify
|
||||
RUN apt purge -y libsgx-ae-qve
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# So we only have to use one gramine template
|
||||
RUN touch /lib/libdcap_quoteprov.so
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=buildtee /build/tee-vault-admin .
|
||||
COPY ./bin/tee-vault-admin/tee-vault-admin.manifest.template .
|
||||
COPY vault/enclave-key.pem .
|
||||
|
||||
COPY assets/sgx_default_qcnl.conf.json /etc/sgx_default_qcnl.conf
|
||||
|
||||
RUN gramine-manifest -Darch_libdir=/lib/x86_64-linux-gnu -Dexecdir=/usr/bin -Dlog_level=warning tee-vault-admin.manifest.template tee-vault-admin.manifest \
|
||||
&& gramine-sgx-sign --manifest tee-vault-admin.manifest --output tee-vault-admin.manifest.sgx --key enclave-key.pem \
|
||||
&& rm enclave-key.pem
|
||||
|
||||
EXPOSE 8443
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "-c"]
|
||||
CMD [ "/restart_aesm.sh ; exec gramine-sgx tee-vault-admin" ]
|
25
bin/tee-vault-admin/src/attestation.rs
Normal file
25
bin/tee-vault-admin/src/attestation.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! attestation
|
||||
|
||||
use crate::ServerState;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Data, Json};
|
||||
use anyhow::{Context, Result};
|
||||
use std::sync::Arc;
|
||||
use teepot::json::http::AttestationResponse;
|
||||
use teepot::server::attestation::get_quote_and_collateral;
|
||||
use teepot::server::{HttpResponseError, Status};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Get attestation
|
||||
#[instrument(level = "info", name = "/v1/sys/attestation", skip_all)]
|
||||
pub async fn get_attestation(
|
||||
worker: Data<Arc<ServerState>>,
|
||||
) -> Result<Json<AttestationResponse>, HttpResponseError> {
|
||||
get_quote_and_collateral(None, &worker.report_data)
|
||||
.context("Error getting attestation")
|
||||
.map(Json)
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
89
bin/tee-vault-admin/src/command.rs
Normal file
89
bin/tee-vault-admin/src/command.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! post commands
|
||||
|
||||
use crate::ServerState;
|
||||
use actix_web::web;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use awc::http::StatusCode;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
use teepot::client::vault::VaultConnection;
|
||||
use teepot::json::http::{
|
||||
VaultCommandRequest, VaultCommandResponse, VaultCommands, VaultCommandsResponse,
|
||||
};
|
||||
use teepot::json::secrets::{AdminConfig, AdminState};
|
||||
use teepot::server::{signatures::VerifySig, HttpResponseError, Status};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Post command
|
||||
#[instrument(level = "info", name = "/v1/command", skip_all)]
|
||||
pub async fn post_command(
|
||||
state: web::Data<Arc<ServerState>>,
|
||||
item: web::Json<VaultCommandRequest>,
|
||||
) -> Result<web::Json<VaultCommandsResponse>, HttpResponseError> {
|
||||
let conn = VaultConnection::new(&state.vault_attestation.clone().into(), "admin".to_string())
|
||||
.await
|
||||
.context("connecting to vault")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let mut admin_state: AdminState = conn
|
||||
.load_secret("state")
|
||||
.await?
|
||||
.context("empty admin state")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let commands: VaultCommands = serde_json::from_str(&item.commands)
|
||||
.context("parsing commands")
|
||||
.status(StatusCode::BAD_REQUEST)?;
|
||||
|
||||
if admin_state.last_digest.to_ascii_lowercase() != commands.last_digest {
|
||||
return Err(anyhow!(
|
||||
"last digest does not match {} != {}",
|
||||
admin_state.last_digest.to_ascii_lowercase(),
|
||||
commands.last_digest
|
||||
))
|
||||
.status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let admin_config: AdminConfig = conn
|
||||
.load_secret("config")
|
||||
.await?
|
||||
.context("empty admin config")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
admin_config.check_sigs(&item.signatures, item.commands.as_bytes())?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(item.commands.as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
let digest = hex::encode(hash);
|
||||
admin_state.last_digest = digest.clone();
|
||||
conn.store_secret(admin_state, "state").await?;
|
||||
|
||||
let mut responds = VaultCommandsResponse {
|
||||
digest,
|
||||
results: vec![],
|
||||
};
|
||||
|
||||
for (pos, command) in commands.commands.iter().enumerate() {
|
||||
let resp = conn
|
||||
.vault_put(
|
||||
&format!("Executing command {pos}"),
|
||||
&command.url,
|
||||
&command.data,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let vcr = VaultCommandResponse {
|
||||
status_code: resp.0.as_u16(),
|
||||
value: resp.1,
|
||||
};
|
||||
|
||||
responds.results.push(vcr);
|
||||
}
|
||||
|
||||
let _ = conn.revoke_token().await;
|
||||
|
||||
Ok(web::Json(responds))
|
||||
}
|
34
bin/tee-vault-admin/src/digest.rs
Normal file
34
bin/tee-vault-admin/src/digest.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! digest
|
||||
|
||||
use crate::ServerState;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use anyhow::{Context, Result};
|
||||
use awc::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use teepot::client::vault::VaultConnection;
|
||||
use teepot::json::secrets::AdminState;
|
||||
use teepot::server::{HttpResponseError, Status};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Get last digest
|
||||
#[instrument(level = "info", name = "/v1/digest", skip_all)]
|
||||
pub async fn get_digest(
|
||||
state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, HttpResponseError> {
|
||||
let conn = VaultConnection::new(&state.vault_attestation.clone().into(), "admin".to_string())
|
||||
.await
|
||||
.context("connecting to vault")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let admin_state: AdminState = conn
|
||||
.load_secret("state")
|
||||
.await?
|
||||
.context("empty admin state")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!({"last_digest": admin_state.last_digest })))
|
||||
}
|
148
bin/tee-vault-admin/src/main.rs
Normal file
148
bin/tee-vault-admin/src/main.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Server to handle requests to the Vault TEE
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
mod attestation;
|
||||
mod command;
|
||||
mod digest;
|
||||
mod sign;
|
||||
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use anyhow::{Context, Result};
|
||||
use attestation::get_attestation;
|
||||
use clap::Parser;
|
||||
use command::post_command;
|
||||
use digest::get_digest;
|
||||
use rustls::ServerConfig;
|
||||
use sign::post_sign;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::sync::Arc;
|
||||
use teepot::json::http::{SignRequest, VaultCommandRequest, ATTESTATION_URL, DIGEST_URL};
|
||||
use teepot::server::attestation::{get_quote_and_collateral, VaultAttestationArgs};
|
||||
use teepot::server::new_json_cfg;
|
||||
use teepot::server::pki::make_self_signed_cert;
|
||||
use teepot::sgx::{parse_tcb_levels, EnumSet, TcbLevel};
|
||||
use tracing::{error, info};
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
/// Server state
|
||||
pub struct ServerState {
|
||||
/// Server TLS public key hash
|
||||
pub report_data: [u8; 64],
|
||||
/// Vault attestation args
|
||||
pub vault_attestation: VaultAttestationArgs,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Arguments {
|
||||
/// allowed TCB levels, comma separated
|
||||
#[arg(long, value_parser = parse_tcb_levels, env = "ALLOWED_TCB_LEVELS", default_value = "Ok")]
|
||||
server_sgx_allowed_tcb_levels: EnumSet<TcbLevel>,
|
||||
/// port to listen on
|
||||
#[arg(long, env = "PORT", default_value = "8444")]
|
||||
port: u16,
|
||||
#[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_writer(std::io::stderr));
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
|
||||
let args = Arguments::parse();
|
||||
|
||||
let (report_data, cert_chain, priv_key) = make_self_signed_cert()?;
|
||||
|
||||
if let Err(e) = get_quote_and_collateral(Some(args.server_sgx_allowed_tcb_levels), &report_data)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
// 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")?;
|
||||
|
||||
info!("Starting HTTPS server at port {}", args.port);
|
||||
|
||||
info!("Quote verified! Connection secure!");
|
||||
|
||||
let server_state = Arc::new(ServerState {
|
||||
report_data,
|
||||
vault_attestation: args.attestation,
|
||||
});
|
||||
|
||||
let server = match HttpServer::new(move || {
|
||||
App::new()
|
||||
// enable logger
|
||||
.wrap(TracingLogger::default())
|
||||
.app_data(new_json_cfg())
|
||||
.app_data(Data::new(server_state.clone()))
|
||||
.service(web::resource(ATTESTATION_URL).route(web::get().to(get_attestation)))
|
||||
.service(web::resource(VaultCommandRequest::URL).route(web::post().to(post_command)))
|
||||
.service(web::resource(SignRequest::URL).route(web::post().to(post_sign)))
|
||||
.service(web::resource(DIGEST_URL).route(web::get().to(get_digest)))
|
||||
})
|
||||
.bind_rustls_0_22((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(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use teepot::json::http::{VaultCommand, VaultCommands};
|
||||
|
||||
const TEST_DATA: &str = include_str!("../../../tests/data/test.json");
|
||||
|
||||
#[test]
|
||||
fn test_vault_commands() {
|
||||
let cmd = VaultCommand {
|
||||
url: "/v1/auth/tee/tees/test".to_string(),
|
||||
data: json!({
|
||||
"lease": "1000",
|
||||
"name": "test",
|
||||
"types": "sgx",
|
||||
"sgx_allowed_tcb_levels": "Ok,SwHardeningNeeded",
|
||||
"sgx_mrsigner": "c5591a72b8b86e0d8814d6e8750e3efe66aea2d102b8ba2405365559b858697d",
|
||||
"token_policies": "test"
|
||||
}),
|
||||
};
|
||||
let cmds = VaultCommands {
|
||||
commands: vec![cmd],
|
||||
last_digest: "".into(),
|
||||
};
|
||||
|
||||
let test_data_cmds: VaultCommands = serde_json::from_str(TEST_DATA).unwrap();
|
||||
|
||||
assert_eq!(cmds, test_data_cmds);
|
||||
}
|
||||
}
|
149
bin/tee-vault-admin/src/sign.rs
Normal file
149
bin/tee-vault-admin/src/sign.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! post signing request
|
||||
|
||||
use crate::ServerState;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
use teepot::client::vault::VaultConnection;
|
||||
use teepot::json::http::{SignRequest, SignRequestData, SignResponse};
|
||||
use teepot::json::secrets::{AdminConfig, AdminState, SGXSigningKey};
|
||||
use teepot::server::signatures::VerifySig as _;
|
||||
use teepot::server::{HttpResponseError, Status};
|
||||
use teepot::sgx::sign::PrivateKey as _;
|
||||
use teepot::sgx::sign::{Author, Signature};
|
||||
use teepot::sgx::sign::{Body, RS256PrivateKey};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Sign command
|
||||
#[instrument(level = "info", name = "/v1/sign", skip_all)]
|
||||
pub async fn post_sign(
|
||||
state: web::Data<Arc<ServerState>>,
|
||||
item: web::Json<SignRequest>,
|
||||
) -> Result<web::Json<SignResponse>, HttpResponseError> {
|
||||
let conn = VaultConnection::new(&state.vault_attestation.clone().into(), "admin".to_string())
|
||||
.await
|
||||
.context("connecting to vault")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let mut admin_state: AdminState = conn
|
||||
.load_secret("state")
|
||||
.await?
|
||||
.context("empty admin state")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
|
||||
let sign_request: SignRequestData = serde_json::from_str(&item.sign_request_data)
|
||||
.context("parsing sign request data")
|
||||
.status(StatusCode::BAD_REQUEST)?;
|
||||
|
||||
// Sanity checks
|
||||
if sign_request.tee_type != "sgx" {
|
||||
return Err(anyhow!("tee_type not supported")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let tee_name = sign_request.tee_name;
|
||||
|
||||
if !tee_name.is_ascii() {
|
||||
return Err(anyhow!("tee_name is not ascii")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// check if tee_name is alphanumeric
|
||||
if !tee_name.chars().all(|c| c.is_alphanumeric()) {
|
||||
return Err(anyhow!("tee_name is not alphanumeric")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// check if tee_name starts with an alphabetic char
|
||||
if !tee_name.chars().next().unwrap().is_alphabetic() {
|
||||
return Err(anyhow!("tee_name does not start with an alphabetic char"))
|
||||
.status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
if admin_state.last_digest != sign_request.last_digest {
|
||||
return Err(anyhow!(
|
||||
"last digest does not match {} != {}",
|
||||
admin_state.last_digest.to_ascii_lowercase(),
|
||||
sign_request.last_digest
|
||||
))
|
||||
.status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let admin_config: AdminConfig = conn
|
||||
.load_secret("config")
|
||||
.await?
|
||||
.context("empty admin config")
|
||||
.status(StatusCode::BAD_GATEWAY)?;
|
||||
admin_config.check_sigs(&item.signatures, item.sign_request_data.as_bytes())?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(item.sign_request_data.as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
let digest = hex::encode(hash);
|
||||
admin_state.last_digest = digest.clone();
|
||||
conn.store_secret(admin_state, "state").await?;
|
||||
|
||||
// Sign SGX enclave
|
||||
let key_path = format!("signing_keys/{}", tee_name);
|
||||
|
||||
let sgx_key = match conn
|
||||
.load_secret::<SGXSigningKey>(&key_path)
|
||||
.await
|
||||
.context("Error loading signing key")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
{
|
||||
Some(key) => RS256PrivateKey::from_pem(&key.pem_pk)
|
||||
.context("Failed to parse private key")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?,
|
||||
None => {
|
||||
let private_key = RS256PrivateKey::generate(3)
|
||||
.context("Failed to generate private key")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let pem_pk = private_key
|
||||
.to_pem()
|
||||
.context("Failed to convert private key to pem")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let key = SGXSigningKey { pem_pk };
|
||||
|
||||
conn.store_secret(key.clone(), &key_path)
|
||||
.await
|
||||
.context("Error storing generated private key")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
private_key
|
||||
}
|
||||
};
|
||||
|
||||
let signed_data = sign_sgx(&sign_request.data, &sgx_key)?;
|
||||
let respond = SignResponse {
|
||||
digest,
|
||||
signed_data,
|
||||
};
|
||||
|
||||
let _ = conn.revoke_token().await;
|
||||
|
||||
Ok(web::Json(respond))
|
||||
}
|
||||
|
||||
fn sign_sgx(body_bytes: &[u8], sgx_key: &RS256PrivateKey) -> Result<Vec<u8>, HttpResponseError> {
|
||||
let body: Body = bytemuck::try_pod_read_unaligned(body_bytes)
|
||||
.context("Invalid SGX input data")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
if body.can_set_debug() {
|
||||
return Err(anyhow!("Not signing SGX enclave with debug flag"))
|
||||
.status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// FIXME: do we need the date and sw defined value?
|
||||
let author = Author::new(0, 0);
|
||||
let sig = Signature::new(sgx_key, author, body)
|
||||
.context("Failed to create RSA signature")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(bytemuck::bytes_of(&sig).to_vec())
|
||||
}
|
66
bin/tee-vault-admin/tee-vault-admin.manifest.template
Normal file
66
bin/tee-vault-admin/tee-vault-admin.manifest.template
Normal file
|
@ -0,0 +1,66 @@
|
|||
libos.entrypoint = "/app/tee-vault-admin"
|
||||
|
||||
[loader]
|
||||
argv = [ "/app/tee-vault-admin" ]
|
||||
entrypoint = "file:{{ gramine.libos }}"
|
||||
env.LD_LIBRARY_PATH = "/lib:{{ arch_libdir }}:/usr{{ arch_libdir }}:/lib"
|
||||
env.HOME = "/app"
|
||||
env.MALLOC_ARENA_MAX = "1"
|
||||
env.AZDCAP_DEBUG_LOG_LEVEL = "ignore"
|
||||
env.AZDCAP_COLLATERAL_VERSION = "v4"
|
||||
|
||||
### Admin Config ###
|
||||
env.PORT = { passthrough = true }
|
||||
|
||||
### VAULT attestation ###
|
||||
env.VAULT_ADDR = { passthrough = true }
|
||||
env.VAULT_SGX_MRENCLAVE = { passthrough = true }
|
||||
env.VAULT_SGX_MRSIGNER = { passthrough = true }
|
||||
env.VAULT_SGX_ALLOWED_TCB_LEVELS = { passthrough = true }
|
||||
|
||||
### DEBUG ###
|
||||
env.RUST_BACKTRACE = "1"
|
||||
env.RUST_LOG="info,tee_vault_admin=trace,teepot=trace,vault_tee_client=trace,tee_client=trace,awc=debug"
|
||||
|
||||
[fs]
|
||||
root.uri = "file:/"
|
||||
start_dir = "/app"
|
||||
mounts = [
|
||||
{ path = "{{ execdir }}", uri = "file:{{ execdir }}" },
|
||||
{ path = "/lib", uri = "file:{{ gramine.runtimedir() }}" },
|
||||
{ path = "{{ arch_libdir }}", uri = "file:{{ arch_libdir }}" },
|
||||
{ path = "/etc", uri = "file:/etc" },
|
||||
{ type = "tmpfs", path = "/var/tmp" },
|
||||
{ type = "tmpfs", path = "/tmp" },
|
||||
{ type = "tmpfs", path = "/app/.dcap-qcnl" },
|
||||
{ type = "tmpfs", path = "/app/.az-dcap-client" },
|
||||
{ path = "/lib/libdcap_quoteprov.so", uri = "file:/lib/libdcap_quoteprov.so" },
|
||||
]
|
||||
|
||||
[sgx]
|
||||
trusted_files = [
|
||||
"file:/etc/ld.so.cache",
|
||||
"file:/app/",
|
||||
"file:{{ execdir }}/",
|
||||
"file:{{ arch_libdir }}/",
|
||||
"file:/usr/{{ arch_libdir }}/",
|
||||
"file:{{ gramine.libos }}",
|
||||
"file:{{ gramine.runtimedir() }}/",
|
||||
"file:/usr/lib/ssl/openssl.cnf",
|
||||
"file:/etc/ssl/",
|
||||
"file:/etc/sgx_default_qcnl.conf",
|
||||
"file:/lib/libdcap_quoteprov.so",
|
||||
]
|
||||
remote_attestation = "dcap"
|
||||
max_threads = 64
|
||||
edmm_enable = false
|
||||
## max enclave size
|
||||
enclave_size = "8G"
|
||||
|
||||
[sys]
|
||||
enable_extra_runtime_domain_names_conf = true
|
||||
enable_sigterm_injection = true
|
||||
|
||||
# possible tweak option, if problems with mio
|
||||
# currently mio is compiled with `mio_unsupported_force_waker_pipe`
|
||||
# insecure__allow_eventfd = true
|
Loading…
Add table
Add a link
Reference in a new issue