Add maa init demo

This commit is contained in:
Zheng, Qi 2022-07-19 15:19:29 +08:00 committed by volcano
parent 54de00a3bc
commit 5c10af738e
11 changed files with 1450 additions and 0 deletions

@ -10,6 +10,10 @@ This demo is programming in C, covering the SGX quote generation and format the
This demo is programming in RUST, based on the Azure provided [`REST APIs`](https://docs.microsoft.com/en-us/rest/api/attestation/). It provides steps to do SGX quote generation and attestation. This demo is programming in RUST, based on the Azure provided [`REST APIs`](https://docs.microsoft.com/en-us/rest/api/attestation/). It provides steps to do SGX quote generation and attestation.
### MAA attestation in Occlum init stage [`maa_init`](./maa_init)
This demo bases on [`maa_attestation`](./maa_attestation), provides steps to do SGX quote generation and attestation in Occlum init process and save the attestation token to rootfs. With this flow, the real application loaded after Occlum init process may get the attestation token and do whatever it wants, without getting involved in the messy attestation part.
## Prerequisites ## Prerequisites
### Platform ### Platform

@ -0,0 +1,53 @@
## Sample code for doing Microsoft Azure Attestation in Occlum init
This demo is programming in RUST, based on the Azure provided [`REST APIs`](https://docs.microsoft.com/en-us/rest/api/attestation/). It provides steps to do SGX quote generation and attestation in Occlum init process and save the attestation token to rootfs.
![Flow Overview](./maa_init.png)
### Flow
1. **`Occlum run`** to start the Occlum instance.
2. For every Occlum instance, it starts `init` process first, then starts the real application in RootFS. The default [`init`](../../../tools/init/) process just run RootFS integrity check and then load the RootFS where the real application is located. For this demo, a modified [`init`](./init/) is used. Besides the general `init` operation, it does Azure Attestation and saves the token to `/root/token` in RootFS where the real application can access.
3. The real application starts with easy access to the Azure Attestation token. The application can set its own strategy for the token. In this demo, a simple `busybox` as real application is used to print the content of attestation token obtained in init process.
### Environments
There are three environments below which are provided to users to modify according to the actual scenarios.
* **MAA_PROVIDER_URL**
The MAA provider URL, this demo uses "https://shareduks.uks.attest.azure.net"
In default.
* **MAA_REPORT_DATA**
The report data (base64 encoded string) to be used for MAA quote generation.
* **MAA_TOKEN_PATH**
The MAA token and raw quote saved path in rootfs which is `/root` in default. Thus applications could find the attestation response token and raw quote (base64 encoded) in `/root/token` and `/root/quote_base64`.
Please refer to the [`scrit`](./build.sh) for how to modify the above environments.
* Build
1. Pull rust-sgx-sdk submodule which is the dependence of occlum dcap library.
```
# cd occlum
# git submodule update --init
```
2. Do the build with the [`scrit`](./build.sh).
```
# ./build.sh
```
* Run
```
# cd occlum_instance
# occlum run /bin/busybox cat /root/token
```
If successful, it prints the Azure attestation token.

@ -0,0 +1,9 @@
includes:
- base.yaml
targets:
# copy busybox
- target: /bin
copy:
- files:
- /opt/occlum/toolchains/busybox/glibc/busybox

@ -0,0 +1,46 @@
#!/bin/bash
set -e
BLUE='\033[1;34m'
NC='\033[0m'
INSTANCE_DIR="occlum_instance"
IMG_BOM="../bom.yaml"
INIT_BOM="../init_maa.yaml"
function build() {
pushd init
cargo clean
cargo build --release
popd
echo "Generate example base64 encoded string as report data"
openssl genrsa -out key.pem 2048
report_data=$(base64 -w 0 key.pem)
rm -rf ${INSTANCE_DIR} && occlum new ${INSTANCE_DIR}
pushd ${INSTANCE_DIR}
rm -rf image
copy_bom -f ${IMG_BOM} --root image --include-dir /opt/occlum/etc/template
# Update env
new_json="$(jq '.env.default += ["MAA_PROVIDER_URL=https://shareduks.uks.attest.azure.net"] |
.env.default += ["MAA_TOKEN_PATH=/root"] |
.env.default += ["MAA_REPORT_DATA=BASE64_STRING"]' Occlum.json)" && \
echo "${new_json}" > Occlum.json
# Update report data string
sed -i "s/BASE64_STRING/$report_data/g" Occlum.json
# prepare init maa content
rm -rf initfs
copy_bom -f ${INIT_BOM} --root initfs --include-dir /opt/occlum/etc/template
occlum build
popd
}
build

File diff suppressed because it is too large Load Diff

@ -0,0 +1,15 @@
[package]
name = "init"
version = "0.0.1"
authors = ["LI Qing geding.lq@antgroup.com"]
edition = "2018"
[dependencies]
libc = "0.2.84"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.9"
sha2 = "0.9.5"
reqwest = { version = "0.11", features = ["blocking", "json"] }
occlum_dcap = { path = "../../../../../tools/toolchains/dcap_lib" }

@ -0,0 +1,16 @@
include ../../src/sgxenv.mk
SRC_FILES := $(shell find . -type f -name '*.rs') Cargo.toml
RUST_TARGET_DIR := $(BUILD_DIR)/internal/tools/init/cargo-target
RUST_OUT_DIR := $(BUILD_DIR)/bin
TARGET_BINARY := $(RUST_OUT_DIR)/init
.PHONY: all clean
all: $(SRC_FILES)
@RUSTC_BOOTSTRAP=1 occlum-cargo build --release --target-dir=$(RUST_TARGET_DIR) -Z unstable-options --out-dir=$(RUST_OUT_DIR)
@echo "CARGO (release) => init"
clean:
@occlum-cargo clean --target-dir=$(RUST_TARGET_DIR)
@-$(RM) -f $(TARGET_BINARY)

@ -0,0 +1,82 @@
use serde_json::json;
use sha2::{Digest, Sha256};
use reqwest::blocking::Client;
use occlum_dcap::*;
pub const MAX_REPORT_DATA_SIZE: usize = 64;
fn maa_get_quote_base64(user_data: &[u8]) -> Result<String, &'static str> {
let mut dcap = DcapQuote::new();
let quote_size = dcap.get_quote_size();
let mut quote_buf: Vec<u8> = vec![0; quote_size as usize];
let mut report_data = sgx_report_data_t::default();
//fill in the report data array
let len = {
if user_data.len() > MAX_REPORT_DATA_SIZE {
MAX_REPORT_DATA_SIZE
} else {
user_data.len()
}
};
for i in 0..len {
report_data.d[i] = user_data[i];
}
dcap.generate_quote(quote_buf.as_mut_ptr(), &mut report_data).unwrap();
dcap.close();
let quote = base64::encode(&quote_buf);
Ok(quote)
}
pub fn maa_generate_json(user_data: &[u8]) -> Result<serde_json::Value, &'static str> {
let mut hasher = Sha256::new();
hasher.update(user_data);
let hash = hasher.finalize();
let quote_base64 = maa_get_quote_base64(&hash).unwrap();
// Format to MAA rest attestation API request body
// https://docs.microsoft.com/en-us/rest/api/attestation/attestation/attest-sgx-enclave#request-body
let mut maa_json: serde_json::Value = json!({
"quote": "0",
"runtimeData": {
"data": "0",
"dataType":"Binary"
}
});
*maa_json
.pointer_mut("/quote")
.unwrap() = serde_json::Value::String(quote_base64);
*maa_json
.pointer_mut("/runtimeData/data")
.unwrap() = serde_json::Value::String(base64::encode(&user_data));
Ok(maa_json.to_owned())
}
pub fn maa_attestation(url: String, request_body: serde_json::Value) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
let client = Client::new();
let att_url = format!("{}/attest/SgxEnclave?api-version=2020-10-01", url);
let resp = client.post(att_url)
.json(&request_body)
.send()?;
match resp.status() {
reqwest::StatusCode::OK => {
// println!("success!");
Ok(resp.json().unwrap())
},
s => {
println!("Received response status: {:?}", s);
Err("maa attestation failed".into())
}
}
}

@ -0,0 +1,126 @@
extern crate libc;
extern crate serde;
extern crate serde_json;
use libc::syscall;
use serde::Deserialize;
use std::error::Error;
use std::fs::{write, File};
use std::io::{ErrorKind, Read};
use std::env;
use crate::maa::{maa_generate_json, maa_attestation};
pub mod maa;
fn main() -> Result<(), Box<dyn Error>> {
// Load the configuration from initfs
const IMAGE_CONFIG_FILE: &str = "/etc/image_config.json";
let image_config = load_config(IMAGE_CONFIG_FILE)?;
// Get the MAC of Occlum.json.protected file
let occlum_json_mac = {
let mut mac: sgx_aes_gcm_128bit_tag_t = Default::default();
parse_str_to_bytes(&image_config.occlum_json_mac, &mut mac)?;
mac
};
let occlum_json_mac_ptr = &occlum_json_mac as *const sgx_aes_gcm_128bit_tag_t;
// Get the key of FS image if needed
let key = match &image_config.image_type[..] {
"encrypted" => {
// TODO: Get the key through RA or LA
const IMAGE_KEY_FILE: &str = "/etc/image_key";
let key_str = load_key(IMAGE_KEY_FILE)?;
let mut key: sgx_key_128bit_t = Default::default();
parse_str_to_bytes(&key_str, &mut key)?;
Some(key)
}
"integrity-only" => None,
_ => unreachable!(),
};
let key_ptr = key
.as_ref()
.map(|key| key as *const sgx_key_128bit_t)
.unwrap_or(std::ptr::null());
// Do Azure attestation and save attestation json to rootfs
// Get Attestation provider URL, rootfs token path and report data string from env
let maa_provider_url = env::var("MAA_PROVIDER_URL")
.unwrap_or("https://shareduks.uks.attest.azure.net".to_string());
let maa_token_path = env::var("MAA_TOKEN_PATH")
.unwrap_or("/root".to_string());
let report_data_base64 = env::var("MAA_REPORT_DATA")
.unwrap_or("example".to_string());
let report_data = base64::decode(&report_data_base64).unwrap();
// Get maa quote json
let maa_json = maa_generate_json(report_data.as_slice()).unwrap();
let quote_base64 = serde_json::to_string(&maa_json["quote"]).unwrap();
// Do maa attestation and get json token response
let response = maa_attestation(maa_provider_url, maa_json).unwrap();
let token = serde_json::to_string(&response).unwrap();
// Mount the image
const SYS_MOUNT_FS: i64 = 363;
let ret = unsafe { syscall(SYS_MOUNT_FS, key_ptr, occlum_json_mac_ptr) };
if ret < 0 {
return Err(Box::new(std::io::Error::last_os_error()));
}
// Write the raw quote and json token to rootfs
let quote_file = maa_token_path.clone() + "/quote_base64";
write(quote_file, quote_base64)?;
let token_file = maa_token_path.clone() + "/token";
write(token_file, token)?;
Ok(())
}
#[allow(non_camel_case_types)]
type sgx_key_128bit_t = [u8; 16];
#[allow(non_camel_case_types)]
type sgx_aes_gcm_128bit_tag_t = [u8; 16];
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct ImageConfig {
occlum_json_mac: String,
image_type: String,
}
fn load_config(config_path: &str) -> Result<ImageConfig, Box<dyn Error>> {
let mut config_file = File::open(config_path)?;
let config_json = {
let mut config_json = String::new();
config_file.read_to_string(&mut config_json)?;
config_json
};
let config: ImageConfig = serde_json::from_str(&config_json)?;
Ok(config)
}
fn load_key(key_path: &str) -> Result<String, Box<dyn Error>> {
let mut key_file = File::open(key_path)?;
let mut key = String::new();
key_file.read_to_string(&mut key)?;
Ok(key.trim_end_matches(|c| c == '\r' || c == '\n').to_string())
}
fn parse_str_to_bytes(arg_str: &str, bytes: &mut [u8]) -> Result<(), Box<dyn Error>> {
let bytes_str_vec = {
let bytes_str_vec: Vec<&str> = arg_str.split('-').collect();
if bytes_str_vec.len() != bytes.len() {
return Err(Box::new(std::io::Error::new(
ErrorKind::InvalidData,
"The length or format of Key/MAC string is invalid",
)));
}
bytes_str_vec
};
for (byte_i, byte_str) in bytes_str_vec.iter().enumerate() {
bytes[byte_i] = u8::from_str_radix(byte_str, 16)?;
}
Ok(())
}

@ -0,0 +1,19 @@
includes:
- base.yaml
targets:
- target: /bin/
copy:
- files:
- ../init/target/release/init
# copy libnss_files
- target: /opt/occlum/glibc/lib
copy:
- files:
- /opt/occlum/glibc/lib/libnss_files.so.2
- /opt/occlum/glibc/lib/libnss_dns.so.2
- /opt/occlum/glibc/lib/libresolv.so.2
# copy root CA
- target: /etc/ssl
copy:
- dirs:
- /etc/ssl/

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB