-
Notifications
You must be signed in to change notification settings - Fork 91
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
Add Azure TDX (preview) support #170
Changes from all commits
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,105 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
use anyhow::*; | ||
use ioctl_sys::ioctl; | ||
use raw_cpuid::{CpuId, Hypervisor}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::fs::OpenOptions; | ||
use std::io::Error; | ||
use std::os::unix::io::AsRawFd; | ||
|
||
use crate::Attester; | ||
|
||
// Length of the REPORTDATA used in TDG.MR.REPORT TDCALL | ||
const TDX_REPORTDATA_LEN: usize = 64; | ||
|
||
// Length of TDREPORT used in TDG.MR.REPORT TDCALL | ||
const TDX_REPORT_LEN: usize = 1024; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
pub struct TdxReportReq { | ||
reportdata: [u8; TDX_REPORTDATA_LEN], | ||
tdreport: [u8; TDX_REPORT_LEN], | ||
} | ||
|
||
impl TdxReportReq { | ||
pub fn new(reportdata: [u8; TDX_REPORTDATA_LEN]) -> Self { | ||
Self { | ||
reportdata, | ||
tdreport: [0; TDX_REPORT_LEN], | ||
} | ||
} | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
struct AzTdQuoteRequest { | ||
report: String, | ||
} | ||
#[derive(Serialize, Deserialize, Debug)] | ||
struct AzTdQuoteResponse { | ||
quote: String, | ||
} | ||
|
||
impl Default for TdxReportReq { | ||
fn default() -> Self { | ||
Self { | ||
reportdata: [0; TDX_REPORTDATA_LEN], | ||
tdreport: [0; TDX_REPORT_LEN], | ||
} | ||
} | ||
} | ||
|
||
ioctl!(readwrite tdx_cmd_get_report0 with b'T', 0x01; TdxReportReq); | ||
|
||
pub(super) async fn get_hyperv_tdx_evidence(report_data: &[u8]) -> Result<String> { | ||
let file = OpenOptions::new().write(true).open("/dev/tdx_guest")?; | ||
let fd = file.as_raw_fd(); | ||
let mut tdx_req = TdxReportReq::new(report_data.try_into()?); | ||
unsafe { | ||
let err = tdx_cmd_get_report0(fd, &mut tdx_req); | ||
if err != 0 { | ||
bail!("TDX Attester: ioctl failed: {}", Error::last_os_error()); | ||
} | ||
} | ||
let report = base64::encode_config(tdx_req.tdreport, base64::URL_SAFE_NO_PAD); | ||
let tdquotereq = AzTdQuoteRequest { report }; | ||
let req = reqwest::Client::new() | ||
.post("http://169.254.169.254/acc/tdquote") | ||
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. Seems that the conversion from report to quote doesn't follow the standard way, s.t. call to host. Why? 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. Unfortunately it doesn't look like there is a single standard way for TDX, see this https://lore.kernel.org/lkml/cover.1684048511.git.sathyanarayanan.kuppuswamy@linux.intel.com/:
It looks like TCP/IP using the IMDS service as a proxy to QE was the best way to implement the communication for preview. It might look different for GA. 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. Is the QE on the same platform/host? I mean the conversion from report to quote rely on the verification of the MAC of the report, and the key can only be generated on the same platform via EGETKEY. 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. I think yes, the QE VM is on the same node as the TD VM. |
||
.header(reqwest::header::CONTENT_TYPE, "application/json") | ||
.header(reqwest::header::ACCEPT, "application/json") | ||
.body(serde_json::to_string(&tdquotereq)?) | ||
.send() | ||
.await?; | ||
let tdquoteresp = req.json::<AzTdQuoteResponse>().await?; | ||
let quote = base64::decode_config(tdquoteresp.quote, base64::URL_SAFE_NO_PAD)?; | ||
let evidence = super::TdxEvidence { | ||
cc_eventlog: None, | ||
quote: base64::encode(quote), | ||
}; | ||
serde_json::to_string(&evidence).context("TDX Attester: Failed to serialize evidence") | ||
} | ||
|
||
pub(super) fn detect_platform() -> bool { | ||
// check cpuid if we are in a Hyper-V guest | ||
let cpuid = CpuId::new(); | ||
if let Some(hypervisor) = cpuid.get_hypervisor_info() { | ||
hypervisor.identify() == Hypervisor::HyperV | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
pub struct TdxAttester {} | ||
|
||
#[async_trait::async_trait] | ||
impl Attester for TdxAttester { | ||
async fn get_evidence(&self, report_data: String) -> Result<String> { | ||
let report_data_bin = super::convert_report_data(report_data)?; | ||
get_hyperv_tdx_evidence(&report_data_bin).await | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about this: if more CSPs are going to add their attester code under
tdx
, liketdx/az
,tdx/ali
,tdx/another-company
. They all shares a same feature here, but must introduce some dependencies they will not use, e.g. CoCo native does not needreqwest
,ioctl-sys
andraw-cpuid
.The advantage is the community version binary of attestation-agent can be easy to build (only one!)
The disadvantage is big footprint, which will influence the performance and cost.
I do not have a clear solution here, and maybe we should bring a more robust verified build pipeline (CD) of the components of CoCo Community to solve the trustiness here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for community builds we should support as many different attesters as possible, so that it is easy to try out coco.
But we will need to have ways to disable/enable things that pull in a lot of dependencies so that companies don't need to build in code that will never be used in their environment. We'll add them over time.
Of these dependencies
reqwest
is the largest, but it is also already used bykbs_protocol
.👍