diff --git a/.dockerignore b/.dockerignore index 1de565933..c7bc6eec1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,3 @@ -target \ No newline at end of file +target +Dockerfile.aa +Dockerfile.cdh \ No newline at end of file diff --git a/APPLICATION/attestation-agent/buildspec.yml b/APPLICATION/attestation-agent/buildspec.yml new file mode 100644 index 000000000..f57624e91 --- /dev/null +++ b/APPLICATION/attestation-agent/buildspec.yml @@ -0,0 +1,36 @@ +name: &NAME +version: &VERSION +image_type: &IMAGE_TYPE +baseos_version: &BASEOS_VERSION + +# 定义镜像仓库信息 +repository_info: + acr: &ACR_PROD confidential-ai-registry.cn-shanghai.cr.aliyuncs.com/product/attestation-agent + +# 定义镜像测试信息 +t-one: + # 配置测试信息 workspace 和模版 + workspace: &WORKSPACE anolis + project: &PROJECT default_anolis + test_suite: &TEST_SUITE image-ci-test + # 执行测试 case, 多个用数组表示 + test_conf: &TEST_CONF baseos_container + test_case: &TEST_CASE baseos_container_default + cloud_server_tag: &CLOUD_SERVER_TAG [alinux-image-ci-x86-ecs] + +# 构建镜像配置 +images: + # 分组名称,支持自定义 + Attestation-Agent: + build: true + test: false + region: cn-hongkong + platform: [linux/amd64] + docker_file: + path: Dockerfile.aa + scene: + args: [] + tags: [[0.0.2, latest]] + registry: [*ACR_PROD] + # 测试配置 + test_config: [*WORKSPACE, *PROJECT, *TEST_SUITE, *TEST_CONF, *TEST_CASE, *CLOUD_SERVER_TAG[0], ''] diff --git a/APPLICATION/attestation-agent/version.yml b/APPLICATION/attestation-agent/version.yml new file mode 100644 index 000000000..b237863c1 --- /dev/null +++ b/APPLICATION/attestation-agent/version.yml @@ -0,0 +1,13 @@ +# 版本关系依赖表,默认继承 version-base.yml 配置,可重写覆盖 +BaseDependency: ../version-base.yml +Dependency: + name: trustiflux + image_type: application + versions: + 1.0.1: + # 对 AI 框架版本对 python 版本的要求 + python_version: [] + # gpu 对 cuda 版本的要求 + cuda_version: [] + # 对 baseos 的要求,*AnolisOS8.6 表示 Anolis8.6 + baseos_version: [*Alinux3.2304] diff --git a/APPLICATION/ci-image/buildspec.yml b/APPLICATION/ci-image/buildspec.yml new file mode 100644 index 000000000..ab1272d5c --- /dev/null +++ b/APPLICATION/ci-image/buildspec.yml @@ -0,0 +1,36 @@ +name: &NAME +version: &VERSION +image_type: &IMAGE_TYPE +baseos_version: &BASEOS_VERSION + +# 定义镜像仓库信息 +repository_info: + acr: &ACR_PROD confidential-ai-registry.cn-shanghai.cr.aliyuncs.com/dev/guest-components-ci + +# 定义镜像测试信息 +t-one: + # 配置测试信息 workspace 和模版 + workspace: &WORKSPACE anolis + project: &PROJECT default_anolis + test_suite: &TEST_SUITE image-ci-test + # 执行测试 case, 多个用数组表示 + test_conf: &TEST_CONF baseos_container + test_case: &TEST_CASE baseos_container_default + cloud_server_tag: &CLOUD_SERVER_TAG [alinux-image-ci-x86-ecs] + +# 构建镜像配置 +images: + # 分组名称,支持自定义 + CI-image: + build: true + test: false + region: cn-hongkong + platform: [linux/amd64] + docker_file: + path: Dockerfile.ci + scene: + args: [] + tags: [[0.0.2, latest]] + registry: [*ACR_PROD] + # 测试配置 + test_config: [*WORKSPACE, *PROJECT, *TEST_SUITE, *TEST_CONF, *TEST_CASE, *CLOUD_SERVER_TAG[0], ''] diff --git a/APPLICATION/ci-image/version.yml b/APPLICATION/ci-image/version.yml new file mode 100644 index 000000000..b237863c1 --- /dev/null +++ b/APPLICATION/ci-image/version.yml @@ -0,0 +1,13 @@ +# 版本关系依赖表,默认继承 version-base.yml 配置,可重写覆盖 +BaseDependency: ../version-base.yml +Dependency: + name: trustiflux + image_type: application + versions: + 1.0.1: + # 对 AI 框架版本对 python 版本的要求 + python_version: [] + # gpu 对 cuda 版本的要求 + cuda_version: [] + # 对 baseos 的要求,*AnolisOS8.6 表示 Anolis8.6 + baseos_version: [*Alinux3.2304] diff --git a/APPLICATION/confidential-data-hub/buildspec.yml b/APPLICATION/confidential-data-hub/buildspec.yml new file mode 100644 index 000000000..2f4f956e1 --- /dev/null +++ b/APPLICATION/confidential-data-hub/buildspec.yml @@ -0,0 +1,36 @@ +name: &NAME +version: &VERSION +image_type: &IMAGE_TYPE +baseos_version: &BASEOS_VERSION + +# 定义镜像仓库信息 +repository_info: + acr: &ACR_PROD confidential-ai-registry.cn-shanghai.cr.aliyuncs.com/product/confidential-data-hub + +# 定义镜像测试信息 +t-one: + # 配置测试信息 workspace 和模版 + workspace: &WORKSPACE anolis + project: &PROJECT default_anolis + test_suite: &TEST_SUITE image-ci-test + # 执行测试 case, 多个用数组表示 + test_conf: &TEST_CONF baseos_container + test_case: &TEST_CASE baseos_container_default + cloud_server_tag: &CLOUD_SERVER_TAG [alinux-image-ci-x86-ecs] + +# 构建镜像配置 +images: + # 分组名称,支持自定义 + Confidential-Data-Hub: + build: true + test: false + region: cn-hongkong + platform: [linux/amd64] + docker_file: + path: Dockerfile.cdh + scene: + args: [] + tags: [[0.0.1, latest]] + registry: [*ACR_PROD] + # 测试配置 + test_config: [*WORKSPACE, *PROJECT, *TEST_SUITE, *TEST_CONF, *TEST_CASE, *CLOUD_SERVER_TAG[0], ''] diff --git a/APPLICATION/confidential-data-hub/version.yml b/APPLICATION/confidential-data-hub/version.yml new file mode 100644 index 000000000..b237863c1 --- /dev/null +++ b/APPLICATION/confidential-data-hub/version.yml @@ -0,0 +1,13 @@ +# 版本关系依赖表,默认继承 version-base.yml 配置,可重写覆盖 +BaseDependency: ../version-base.yml +Dependency: + name: trustiflux + image_type: application + versions: + 1.0.1: + # 对 AI 框架版本对 python 版本的要求 + python_version: [] + # gpu 对 cuda 版本的要求 + cuda_version: [] + # 对 baseos 的要求,*AnolisOS8.6 表示 Anolis8.6 + baseos_version: [*Alinux3.2304] diff --git a/Cargo.lock b/Cargo.lock index 34f8a0295..a8eb375bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,7 +384,9 @@ dependencies = [ "hyper-tls 0.5.0", "kbs-types", "log", + "nix 0.29.0", "occlum_dcap", + "pnet", "rstest", "s390_pv", "scroll", @@ -398,6 +400,7 @@ dependencies = [ "tempfile", "thiserror 2.0.9", "tokio", + "udev", ] [[package]] @@ -3070,6 +3073,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "io-uring" version = "0.5.13" @@ -3092,6 +3106,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -3348,9 +3371,8 @@ dependencies = [ [[package]] name = "kbs-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6441ed73b0faa50707d4de41c6b45c76654b661b96aaf7b26a41331eedc0a5" +version = "0.6.0" +source = "git+https://github.com/inclavare-containers/kbs-types.git?rev=d881395#d8813950a035b037322cdcde53b2a9c9f513489b" dependencies = [ "serde", "serde_json", @@ -3547,7 +3569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3578,6 +3600,16 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "libz-sys" version = "1.1.20" @@ -3859,6 +3891,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nom" version = "7.1.3" @@ -4642,6 +4680,97 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "pnet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.87", +] + +[[package]] +name = "pnet_macros_support" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -6889,6 +7018,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "udev" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5c197b95f1769931c89f85c33c407801d1fb7a311113bc0b39ad036f1bd81" +dependencies = [ + "io-lifetimes", + "libc", + "libudev-sys", + "pkg-config", +] + [[package]] name = "unicase" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index 4eed169f2..5c2c2dc39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,10 @@ ctr = "0.9.2" env_logger = "0.11.6" hex = "0.4.3" hmac = "0.12.1" -jwt-simple = { version = "0.12", default-features = false, features = ["pure-rust"] } -kbs-types = "0.7.0" +jwt-simple = { version = "0.12", default-features = false, features = [ + "pure-rust", +] } +kbs-types = { git = "https://github.com/inclavare-containers/kbs-types.git", rev = "d881395" } lazy_static = "1.5.0" log = "0.4.22" nix = "0.29" diff --git a/Dockerfile.aa b/Dockerfile.aa new file mode 100644 index 000000000..d77e3e897 --- /dev/null +++ b/Dockerfile.aa @@ -0,0 +1,45 @@ +# Copyright (c) 2024 by Alibaba. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +FROM registry.cn-hangzhou.aliyuncs.com/lxx/trustiflux:anolisos-latest as builder + +WORKDIR /tmp +RUN curl https://download.01.org/intel-sgx/sgx-dcap/1.21/linux/distro/Anolis86/sgx_rpm_local_repo.tgz --output sgx_rpm_local_repo.tgz && \ + tar zxvf sgx_rpm_local_repo.tgz && \ + yum -y install yum-utils && yum-config-manager --add-repo file:///tmp/sgx_rpm_local_repo && \ + yum -y install epel-release && \ + yum install -y --setopt=install_weak_deps=False --nogpgcheck libtdx-attest-devel perl wget curl clang openssl-devel protobuf-devel git libudev-devel && \ + yum clean all && \ + rm -rf /tmp/* + +WORKDIR /usr/src/guest-components +COPY . . + +# Install Rust +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN rustup toolchain install 1.79.0-x86_64-unknown-linux-gnu + +# Build attestation-agent. Notice that libc version is not enough thus --release cannot pass +RUN cargo +1.79.0 build -p attestation-agent --bin ttrpc-aa --no-default-features --features bin,ttrpc,rust-crypto,coco_as,kbs,tdx-attester,system-attester --target x86_64-unknown-linux-gnu + +RUN strip target/x86_64-unknown-linux-gnu/debug/ttrpc-aa + +FROM registry.cn-hangzhou.aliyuncs.com/lxx/trustiflux:anolisos-latest + +WORKDIR /tmp +RUN curl https://download.01.org/intel-sgx/sgx-dcap/1.21/linux/distro/Anolis86/sgx_rpm_local_repo.tgz --output sgx_rpm_local_repo.tgz && \ + tar zxvf sgx_rpm_local_repo.tgz && \ + yum -y install yum-utils && yum-config-manager --add-repo file:///tmp/sgx_rpm_local_repo && \ + yum -y install epel-release && \ + yum install -y --setopt=install_weak_deps=False --nogpgcheck libtdx-attest-devel && \ + yum clean all && \ + rm -rf /tmp/* + +# Copy binaries +COPY --from=builder /usr/src/guest-components/target/x86_64-unknown-linux-gnu/debug/ttrpc-aa /usr/local/bin/attestation-agent +COPY aa-start.sh /usr/bin/start.sh +COPY tdx-attest.conf /etc/tdx-attest.conf \ No newline at end of file diff --git a/Dockerfile.cdh b/Dockerfile.cdh new file mode 100644 index 000000000..507d79da8 --- /dev/null +++ b/Dockerfile.cdh @@ -0,0 +1,33 @@ +# Copyright (c) 2024 by Alibaba. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +ARG BASE_IMAGE=eci-nydus-registry.cn-hangzhou.cr.aliyuncs.com/docker/debian:stable-slim + +FROM ${BASE_IMAGE} AS builder + +WORKDIR /usr/src/guest-components +COPY . . + +RUN apt update -y && apt install -y clang protobuf-compiler git curl musl-tools libssl-dev make && \ + apt clean all && \ + rm -rf /tmp/* + +# Install Rust +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN rustup target add x86_64-unknown-linux-musl + +# Build confidential-data-hub +RUN cd confidential-data-hub/hub && \ + cargo build --release --bin cdh-oneshot --no-default-features --features "bin,aliyun,kbs" --target x86_64-unknown-linux-musl + +RUN strip target/x86_64-unknown-linux-musl/release/cdh-oneshot + +FROM ${BASE_IMAGE} + +# Copy binaries +COPY --from=builder /usr/src/guest-components/target/x86_64-unknown-linux-musl/release/cdh-oneshot /usr/local/bin/confidential-data-hub +COPY cdh-start.sh /usr/bin/start.sh \ No newline at end of file diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 000000000..05865190b --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,15 @@ +# Copyright (c) 2024 by Alibaba. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +FROM rust:1.78.0-slim + +WORKDIR /tmp +RUN rustup component add rustfmt clippy + +RUN sed -i 's/deb\.debian\.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources + +RUN apt update && apt install -y gcc perl make gnupg curl protobuf-compiler git clang && \ + curl -L https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | tee intel-sgx-deb.key | apt-key add - && \ + echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu jammy main' | tee /etc/apt/sources.list.d/intel-sgx.list && \ + apt-get update && apt-get install -y libtdx-attest-dev libudev-dev pkg-config diff --git a/aa-start.sh b/aa-start.sh new file mode 100755 index 000000000..c9a64e75c --- /dev/null +++ b/aa-start.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -euo pipefail +set -o noglob + +# Initialize parameters +trustee_address='' +as_addr='' + +usage() { + echo "This script is used to start Attestation Agent" 1>&2 + echo "" 1>&2 + echo "Usage: $0 --trustee-addr Address of remote trustee" 1>&2 + + exit 1 +} + +# Parse cmd +while [[ "$#" -gt 0 ]]; do + case "$1" in + --as-addr) + as_addr="$2" + shift 2 + ;; + --trustee-addr) + trustee_address="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +cat << EOF > /etc/attestation-agent.toml +[token_configs] +[token_configs.coco_as] +url = "${as_addr}" + +[token_configs.kbs] +url = "${trustee_address}" +EOF + +attestation-agent -c /etc/attestation-agent.toml \ No newline at end of file diff --git a/attestation-agent/attestation-agent/Cargo.toml b/attestation-agent/attestation-agent/Cargo.toml index 076953ef4..db0030de0 100644 --- a/attestation-agent/attestation-agent/Cargo.toml +++ b/attestation-agent/attestation-agent/Cargo.toml @@ -73,6 +73,7 @@ all-attesters = [ "snp-attester", "se-attester", "cca-attester", + "system-attester", ] tdx-attester = ["kbs_protocol?/tdx-attester", "attester/tdx-attester"] sgx-attester = ["kbs_protocol?/sgx-attester", "attester/sgx-attester"] @@ -87,6 +88,7 @@ az-tdx-vtpm-attester = [ snp-attester = ["kbs_protocol?/snp-attester", "attester/snp-attester"] se-attester = ["kbs_protocol?/se-attester", "attester/se-attester"] cca-attester = ["kbs_protocol?/cca-attester", "attester/cca-attester"] +system-attester = ["kbs_protocol?/system-attester", "attester/system-attester"] # Either `rust-crypto` or `openssl` should be enabled to work as underlying crypto module rust-crypto = ["kbs_protocol?/rust-crypto"] diff --git a/attestation-agent/attester/Cargo.toml b/attestation-agent/attester/Cargo.toml index f371e3a06..864567333 100644 --- a/attestation-agent/attester/Cargo.toml +++ b/attestation-agent/attester/Cargo.toml @@ -8,20 +8,31 @@ edition = "2021" [dependencies] anyhow.workspace = true async-trait.workspace = true -az-snp-vtpm = { version = "0.7.1", default-features = false, features = ["attester"], optional = true } -az-tdx-vtpm = { version = "0.7.0", default-features = false, features = ["attester"], optional = true } +az-snp-vtpm = { version = "0.7.1", default-features = false, features = [ + "attester", +], optional = true } +az-tdx-vtpm = { version = "0.7.0", default-features = false, features = [ + "attester", +], optional = true } base64.workspace = true clap = { workspace = true, features = ["derive"], optional = true } hex.workspace = true kbs-types.workspace = true log.workspace = true +nix = { workspace = true, optional = true, default-features = false } occlum_dcap = { git = "https://github.com/occlum/occlum", tag = "v0.29.7", optional = true } +pnet = { version = "0.35.0", optional = true } pv = { version = "0.10.0", package = "s390_pv", optional = true } -scroll = { version = "0.12.0", default-features = false, features = ["derive", "std"], optional = true } +scroll = { version = "0.12.0", default-features = false, features = [ + "derive", + "std", +], optional = true } serde.workspace = true serde_json.workspace = true serde_with.workspace = true -sev = { version = "4.0.0", default-features = false, features = ["snp"], optional = true } +sev = { version = "4.0.0", default-features = false, features = [ + "snp", +], optional = true } sha2.workspace = true strum.workspace = true tdx-attest-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.22", optional = true } @@ -33,6 +44,7 @@ hyper = { version = "0.14", features = ["full"], optional = true } hyper-tls = { version = "0.5", optional = true } tokio = { version = "1", features = ["full"], optional = true } tempfile = { workspace = true, optional = true } +udev = { version = "0.9.1", optional = true } [dev-dependencies] tokio.workspace = true @@ -53,6 +65,7 @@ all-attesters = [ "csv-attester", "cca-attester", "se-attester", + "system-attester", ] # tsm-report enables a module that helps attesters to use Linux TSM_REPORTS for generating @@ -66,6 +79,7 @@ az-tdx-vtpm-attester = ["az-snp-vtpm-attester", "az-tdx-vtpm"] snp-attester = ["sev"] csv-attester = ["csv-rs", "codicon", "hyper", "hyper-tls", "tokio"] cca-attester = ["tsm-report"] -se-attester = ["pv"] +se-attester = ["pv"] +system-attester = ["nix/feature", "pnet", "udev"] bin = ["tokio/rt", "tokio/macros", "clap"] diff --git a/attestation-agent/attester/src/lib.rs b/attestation-agent/attester/src/lib.rs index 195e79eb3..9c95daee4 100644 --- a/attestation-agent/attester/src/lib.rs +++ b/attestation-agent/attester/src/lib.rs @@ -36,6 +36,9 @@ pub mod tsm_report; #[cfg(feature = "se-attester")] pub mod se; +#[cfg(feature = "system-attester")] +pub mod system; + pub type BoxedAttester = Box; impl TryFrom for BoxedAttester { @@ -60,6 +63,8 @@ impl TryFrom for BoxedAttester { Tee::Csv => Box::::default(), #[cfg(feature = "se-attester")] Tee::Se => Box::::default(), + #[cfg(feature = "system-attester")] + Tee::System => Box::new(system::SystemAttester::new()?), _ => bail!("TEE is not supported!"), }; @@ -143,6 +148,11 @@ pub fn detect_tee_type() -> Tee { return Tee::Se; } + #[cfg(feature = "system-attester")] + if system::detect_platform() { + return Tee::System; + } + log::warn!("No TEE platform detected. Sample Attester will be used."); Tee::Sample } diff --git a/attestation-agent/attester/src/system/inner.rs b/attestation-agent/attester/src/system/inner.rs new file mode 100644 index 000000000..00969e1f3 --- /dev/null +++ b/attestation-agent/attester/src/system/inner.rs @@ -0,0 +1,143 @@ +use super::sysinfo::get_machine_info; +use anyhow::*; +use log::{info, warn}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha384}; + +#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)] +pub struct MeasurementEntry { + pub name: String, + pub algorithm: String, + pub digest: String, +} + +#[derive(Default)] +pub struct SystemAttesterdInner { + mr_register: String, + measurements: Vec, +} + +impl SystemAttesterdInner { + pub fn measure(&mut self, name: String, data: Vec) -> Result<()> { + // Measure data + let mut hasher = Sha384::new(); + hasher.update(data); + let digest = hasher.finalize().to_vec(); + let digest_hex = hex::encode(&digest); + + // Add New Measurements Entry + let entry = MeasurementEntry { + name, + algorithm: "sha384".to_string(), + digest: digest_hex, + }; + info!("{}", format!("Measurement Entry: {:?}", &entry)); + self.measurements.push(entry); + + // Extend MR Register Hash + let mr_register_value_bytes = hex::decode(self.mr_register.clone())?; + let mut hasher = Sha384::new(); + if !mr_register_value_bytes.is_empty() { + hasher.update(&mr_register_value_bytes); + } + hasher.update(&digest); + self.mr_register = hex::encode(hasher.finalize()); + info!("{}", format!("Updated MR Register: {}", self.mr_register)); + + Ok(()) + } + + pub fn get_measurements(&self) -> Vec { + self.measurements.clone() + } + + pub fn read_mr_register(&self) -> String { + self.mr_register.clone() + } + + pub fn read_sys_report(&self) -> Result { + let machine_info = get_machine_info()?; + let sys_report = serde_json::to_string(&machine_info)?; + info!( + "System Report: {}", + serde_json::to_string_pretty(&machine_info)? + ); + Ok(sys_report) + } +} + +impl SystemAttesterdInner { + pub fn init(&mut self) -> Result<()> { + info!("Initialize: measure Kernel and Initrams of this system..."); + let uname_output = std::process::Command::new("uname").arg("-r").output()?; + let kernel_version = match uname_output.status.success() { + true => String::from_utf8_lossy(&uname_output.stdout) + .trim() + .to_string(), + false => bail!("Failed to get kernel version"), + }; + let kernel_blob_path = format!("/boot/vmlinuz-{kernel_version}"); + let initramfs_img_path = format!("/boot/initramfs-{kernel_version}.img"); + match std::fs::read(kernel_blob_path) { + std::result::Result::Ok(kernel_blob) => { + self.measure("kernel".to_string(), kernel_blob)? + } + Err(e) => warn!("Failed to read kernel blob: {e}"), + } + + match std::fs::read(initramfs_img_path) { + std::result::Result::Ok(initramfs_blob) => { + self.measure("initramfs".to_string(), initramfs_blob)? + } + Err(e) => warn!("Failed to read initramfs blob: {e}"), + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use sha2::{Digest, Sha384}; + + #[test] + fn test_measure() { + let mut attesterd = SystemAttesterdInner::default(); + let data = b"1234567890".to_vec(); + let result = attesterd.measure("test".to_string(), data.clone()); + assert!(result.is_ok()); + let mut hasher = Sha384::new(); + hasher.update(data); + let digest = hasher.finalize().to_vec(); + let digest_hex = hex::encode(&digest); + let mut hasher = Sha384::new(); + hasher.update(&digest); + let new_mr_register = hex::encode(hasher.finalize().to_vec()); + let mr_register_read = attesterd.read_mr_register(); + assert_eq!(new_mr_register, mr_register_read); + let entry = MeasurementEntry { + name: "test".to_string(), + algorithm: "sha384".to_string(), + digest: digest_hex, + }; + let measurement_entry = attesterd.get_measurements()[0].clone(); + assert_eq!(measurement_entry, entry); + } + + #[test] + fn test_init() { + let mut attesterd = SystemAttesterdInner::default(); + let result = attesterd.init(); + let measurements = attesterd.get_measurements(); + let _measurements_str = serde_json::to_string(&measurements).unwrap(); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_read_sysreport() { + let attesterd = SystemAttesterdInner::default(); + let result = attesterd.read_sys_report(); + assert!(result.is_ok()); + } +} diff --git a/attestation-agent/attester/src/system/mod.rs b/attestation-agent/attester/src/system/mod.rs new file mode 100644 index 000000000..da21acc3e --- /dev/null +++ b/attestation-agent/attester/src/system/mod.rs @@ -0,0 +1,78 @@ +// Copyright (c) 2024 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// +mod inner; +pub mod sysinfo; +use super::Attester; +use anyhow::*; +use base64::Engine; +use inner::SystemAttesterdInner; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::env; + +// System attester is always supported +pub fn detect_platform() -> bool { + if let Result::Ok(system_attestation) = std::env::var("SYSTEM_ATTESTATION") { + system_attestation.to_lowercase() == "true" + } else { + false + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct SystemQuote { + system_report: String, + measurements: String, + mr_register: String, + environment: HashMap, + report_data: String, +} + +pub struct SystemAttester { + inner: SystemAttesterdInner, +} + +impl SystemAttester { + pub fn new() -> Result { + let mut inner = SystemAttesterdInner::default(); + inner.init()?; + Ok(Self { inner }) + } +} + +#[async_trait::async_trait] +impl Attester for SystemAttester { + async fn get_evidence(&self, report_data: Vec) -> Result { + let system_report = self.inner.read_sys_report()?; + let measurements = serde_json::to_string(&self.inner.get_measurements())?; + let mr_register = self.inner.read_mr_register(); + let mut environment: HashMap = HashMap::new(); + for (env_name, env_value) in env::vars() { + environment.insert(env_name, env_value); + } + + let evidence = SystemQuote { + system_report, + measurements, + mr_register, + environment, + report_data: base64::engine::general_purpose::STANDARD.encode(report_data), + }; + serde_json::to_string(&evidence).context("Serialize system evidence failed") + } +} + +#[cfg(test)] +mod tests { + use crate::{system::SystemAttester, Attester}; + + #[tokio::test] + async fn test_system_get_evidence() { + let attester = SystemAttester::new().unwrap(); // Update for sync + let report_data: Vec = vec![0; 48]; + let evidence = attester.get_evidence(report_data).await; + assert!(evidence.is_ok()); + } +} diff --git a/attestation-agent/attester/src/system/sysinfo/mod.rs b/attestation-agent/attester/src/system/sysinfo/mod.rs new file mode 100644 index 000000000..ea46bb70f --- /dev/null +++ b/attestation-agent/attester/src/system/sysinfo/mod.rs @@ -0,0 +1,33 @@ +//! A library for retrieving system information. +//! +//! This module provides functionality to gather hardware and software information +//! about the machine it's running on. It includes details such as CPU, disk, +//! network interfaces, operating system, and more. +//! +//! # Platform Support +//! - **Linux**: Full support for retrieving system information, including detailed hardware and software details (e.g., using `libudev` for disk serial number retrieval). +//! +//! # Dependencies +//! - **Linux**: Requires `libudev` for certain hardware information retrieval (e.g., disk serial numbers). Ensure `libudev` is installed on the system. +//! - On Debian/Ubuntu: `sudo apt-get install libudev-dev` +//! - On Fedora/Red Hat: `sudo dnf install systemd-devel` +//! +//! # Environment Setup +//! - Ensure the appropriate development packages are installed for the target platform. +//! - On **non-Linux** platforms, functionality relying on `libudev` will be disabled to ensure compatibility. +//! - For custom configurations, such as setting `PKG_CONFIG_PATH`, ensure the environment is correctly configured when building on Linux systems. +//! +//! # Example +//! ``` +//! use attester::system::sysinfo::get_machine_info; +//! +//! fn main() -> Result<(), Box> { +//! let machine_info = get_machine_info()?; +//! println!("Machine Info: {:?}", machine_info); +//! Ok(()) +//! } +//! ``` + +pub mod system_info; + +pub use system_info::get_machine_info; diff --git a/attestation-agent/attester/src/system/sysinfo/system_info/hardware.rs b/attestation-agent/attester/src/system/sysinfo/system_info/hardware.rs new file mode 100644 index 000000000..47b95e203 --- /dev/null +++ b/attestation-agent/attester/src/system/sysinfo/system_info/hardware.rs @@ -0,0 +1,467 @@ +use anyhow::{bail, Context, Result}; +use pnet::datalink; +use serde::{Deserialize, Serialize}; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, Read}; +use std::path::Path; +use std::process::Command; +use udev; + +const BIOS_INFO_PATH: &str = "/sys/firmware/dmi/entries/0-0/raw"; +const SYSTEM_INFO_PATH: &str = "/sys/firmware/dmi/entries/1-0/raw"; +const ENCLOSURE_INFO_PATH: &str = "/sys/firmware/dmi/entries/3-0/raw"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HardwareInfo { + pub cpu_is_virtual: bool, + pub disk_serial_number: String, + pub mac_addresses: String, + pub bios_info: BiosInfo, + pub system_info: SystemInfo, + pub enclosure_info: EnclosureInfo, + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, +} + +impl HardwareInfo { + pub fn new() -> Result { + Ok(HardwareInfo { + cpu_is_virtual: determine_virtual_machine_status(), + disk_serial_number: get_root_device() + .and_then(|disk_part_name| get_serial_number(&disk_part_name)) + .unwrap_or_default(), + mac_addresses: get_mac_addresses()?, + bios_info: read_bios_info(BIOS_INFO_PATH).unwrap_or_default(), + system_info: read_system_info(SYSTEM_INFO_PATH).unwrap_or_default(), + enclosure_info: read_enclosure_info(ENCLOSURE_INFO_PATH).unwrap_or_default(), + extra: None, + }) + } + + pub fn with_extra(mut self, extra: serde_json::Value) -> Self { + self.extra = Some(extra); + self + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct BiosInfo { + pub vendor: String, + pub bios_version: String, + pub bios_release_date: String, + pub is_virtual_machine: bool, + pub system_bios_major_release: String, + pub system_bios_minor_release: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct SystemInfo { + pub manufacturer: String, + pub product_name: String, + pub serial_number: String, + pub uuid: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct EnclosureInfo { + pub manufacturer: String, + pub enclosure_type: String, + pub version: String, + pub serial_number: String, + pub asset_tag_number: String, +} + +#[cfg(target_arch = "x86_64")] +fn is_hypervisor_present() -> bool { + use std::arch::x86_64::__cpuid; + + // Check CPUID hypervisor bit + let basic_cpuid = unsafe { __cpuid(1) }; + let is_vm = (basic_cpuid.ecx & (1 << 31)) != 0; + + // Early return if hypervisor bit is set + if is_vm { + return true; + } + + // Check hypervisor name + if get_hypervisor_name().is_some() { + return true; + } + + // Check system indicators + check_sys_hypervisor() || check_dmesg_hypervisor() +} + +#[cfg(target_arch = "x86_64")] +fn get_hypervisor_name() -> Option<&'static str> { + use std::arch::x86_64::__cpuid; + + // CPUID leaf 0x40000000 returns hypervisor signature + let hypervisor_cpuid = unsafe { __cpuid(0x40000000) }; + let signature = [ + hypervisor_cpuid.ebx, + hypervisor_cpuid.ecx, + hypervisor_cpuid.edx, + ]; + + const VMWARE: [u32; 3] = [0x56_4D_77_61, 0x72_65_56_4D, 0x77_61_72_65]; + const HYPERV: [u32; 3] = [0x4D_69_63_72, 0x6F_73_6F_66, 0x74_20_48_76]; + const KVM: [u32; 3] = [0x4B_56_4D_4B, 0x56_4D_4B_56, 0x4D_4B_56_4D]; + const XEN: [u32; 3] = [0x58_65_6E_56, 0x4D_4D_58_65, 0x6E_56_4D_4D]; + + match signature { + VMWARE => Some("VMware"), + HYPERV => Some("Microsoft Hyper-V"), + KVM => Some("KVM"), + XEN => Some("Xen"), + _ => None, + } +} + +fn check_sys_hypervisor() -> bool { + match fs::read_to_string("/sys/hypervisor/type") { + Ok(content) => content.contains("xen") || content.contains("kvm"), + Err(_) => false, + } +} + +fn check_dmesg_hypervisor() -> bool { + Command::new("dmesg") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).contains("hypervisor")) + .unwrap_or(false) +} + +#[cfg(target_arch = "aarch64")] +fn is_hypervisor_present() -> bool { + // Use lazy evaluation with || to short-circuit checks + check_cpuinfo_hypervisor() + || check_sys_hypervisor() + || check_rdmsr_hypervisor() + || check_dmesg_hypervisor() + || check_device_tree_hypervisor() +} + +#[cfg(target_arch = "aarch64")] +fn check_cpuinfo_hypervisor() -> bool { + fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("hypervisor")) + .unwrap_or(false) +} + +#[cfg(target_arch = "aarch64")] +fn check_rdmsr_hypervisor() -> bool { + // TODO: rdmsr command needs to be installed in Dockerfile for ARM architecture + // Note: Currently ARM platform is not supported in production + // An issue should be created to track adding rdmsr dependency to Dockerfile + Command::new("rdmsr") + .arg("0xC0C") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).contains("hypervisor")) + .unwrap_or(false) +} + +#[cfg(target_arch = "aarch64")] +fn check_device_tree_hypervisor() -> bool { + Command::new("cat") + .arg("/proc/device-tree/hypervisor") + .output() + .map(|output| !String::from_utf8_lossy(&output.stdout).is_empty()) + .unwrap_or(false) +} + +// TODO: add other arch support, such as riscv +#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] +fn is_hypervisor_present() -> bool { + // Use const array for paths to check + const VIRT_PATHS: [&str; 4] = ["/.dockerenv", "/proc/xen", "/proc/vz", "/proc/bc"]; + + // Check cpuinfo first as it's most reliable + if let Ok(content) = fs::read_to_string("/proc/cpuinfo") { + if content.contains("hypervisor") + || content.contains("virtualization") + || content.contains("paravirtualized") + { + return true; + } + } + + if check_sys_hypervisor() { + return true; + } + + if check_dmesg_hypervisor() { + return true; + } + + if fs::metadata("/proc/device-tree/hypervisor").is_ok() { + return true; + } + + VIRT_PATHS.iter().any(|path| fs::metadata(path).is_ok()) +} + +/// Detects if the system is running in a virtual machine environment. +/// Uses various detection techniques inspired by Al-khaser and Pafish projects. +/// - Al-khaser: https://github.com/LordNoteworthy/al-khaser +/// - Pafish: https://github.com/a0rtega/pafish +/// TODO: more virtual machine detection methods are adding. +fn determine_virtual_machine_status() -> bool { + const CONTAINER_PATHS: [&str; 2] = ["/.dockerenv", "/.dockerinit"]; + + if is_hypervisor_present() { + return true; + } + + if CONTAINER_PATHS + .iter() + .any(|path| fs::metadata(path).is_ok()) + { + return true; + } + + Command::new("systemctl") + .arg("is-system-running") + .output() + .map(|output| String::from_utf8_lossy(&output.stdout).contains("running in container")) + .unwrap_or(false) +} + +fn get_root_device() -> Result { + let file = File::open("/proc/mounts").context("Failed to open /proc/mounts")?; + let reader = BufReader::with_capacity(4096, file); + + for line in reader.lines() { + let line = line?; + let mut parts = line.split_whitespace().take(2); + if let (Some(device), Some("/")) = (parts.next(), parts.next()) { + if let Some(stripped) = device.strip_prefix("/dev/") { + return Ok(stripped.to_string()); + } + return Ok(device.to_string()); + } + } + bail!("Root device not found in /proc/mounts") +} + +fn get_serial_number(disk_part_name: &str) -> Result { + let udev = udev::Udev::new()?; + let mut enumerator = udev::Enumerator::with_udev(udev)?; + + enumerator.match_subsystem("block")?; + enumerator.match_sysname(disk_part_name)?; + + let device = enumerator + .scan_devices()? + .next() + .ok_or_else(|| anyhow::anyhow!("Device not found"))?; + + let parent = device + .parent() + .ok_or_else(|| anyhow::anyhow!("Failed to get parent device"))?; + + let serial = parent + .property_value("ID_SERIAL") + .ok_or_else(|| anyhow::anyhow!("Serial number not found"))? + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid serial number encoding"))? + .to_string(); + + Ok(serial) +} + +fn get_mac_addresses() -> Result { + let interfaces = datalink::interfaces(); + let mut mac_addresses = Vec::new(); + + for iface in interfaces { + if let Some(mac) = iface.mac { + mac_addresses.push(format!("{}", mac)); + } + } + + Ok(mac_addresses.join(", ")) +} + +fn read_bios_info>(path: P) -> Result { + let mut buffer = Vec::new(); + File::open(&path)?.read_to_end(&mut buffer)?; + + if buffer.len() < 2 { + bail!("Buffer too small"); + } + + let length = buffer[1] as usize; + if buffer.len() <= length { + bail!("Invalid buffer length"); + } + + let unformatted_section = &buffer[length..]; + + if buffer.len() <= 0x15 { + bail!("Buffer too small for BIOS info"); + } + + Ok(BiosInfo { + vendor: extract_string(unformatted_section, buffer[0x04])?, + bios_version: extract_string(unformatted_section, buffer[0x05])?, + bios_release_date: extract_string(unformatted_section, buffer[0x08])?, + is_virtual_machine: (buffer[0x13] & 0x08) >> 3 == 1 || determine_virtual_machine_status(), + system_bios_major_release: buffer[0x14].to_string(), + system_bios_minor_release: buffer[0x15].to_string(), + }) +} + +fn read_system_info>(path: P) -> Result { + let mut buffer = Vec::new(); + File::open(&path)?.read_to_end(&mut buffer)?; + + if buffer.len() < 2 { + bail!("Buffer too small"); + } + + let length = buffer[1] as usize; + if buffer.len() <= length { + bail!("Invalid buffer length"); + } + + let unformed_section = &buffer[length..]; + + if buffer.len() <= 0x17 { + bail!("Buffer too small for system info"); + } + + Ok(SystemInfo { + manufacturer: extract_string(unformed_section, buffer[0x04])?, + product_name: extract_string(unformed_section, buffer[0x05])?, + serial_number: extract_string(unformed_section, buffer[0x07])?, + uuid: format!( + "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + buffer[0x08], buffer[0x09], buffer[0x0a], buffer[0x0b], + buffer[0x0c], buffer[0x0d], buffer[0x0e], buffer[0x0f], + buffer[0x10], buffer[0x11], buffer[0x12], buffer[0x13], + buffer[0x14], buffer[0x15], buffer[0x16], buffer[0x17] + ), + }) +} + +fn read_enclosure_info>(path: P) -> Result { + let mut buffer = Vec::new(); + File::open(&path)?.read_to_end(&mut buffer)?; + + if buffer.len() < 2 { + bail!("Buffer too small"); + } + + let length = buffer[1] as usize; + if buffer.len() <= length { + bail!("Invalid buffer length"); + } + + let unformed_section = &buffer[length..]; + + if buffer.len() <= 0x08 { + bail!("Buffer too small for enclosure info"); + } + + Ok(EnclosureInfo { + manufacturer: extract_string(unformed_section, buffer[0x04])?, + enclosure_type: extract_string(unformed_section, buffer[0x05])?, + version: extract_string(unformed_section, buffer[0x06])?, + serial_number: extract_string(unformed_section, buffer[0x07])?, + asset_tag_number: extract_string(unformed_section, buffer[0x08])?, + }) +} + +fn extract_string(unformed_section: &[u8], index: u8) -> Result { + if index == 0 { + return Ok(String::new()); + } + + let s = unformed_section + .split(|&b| b == 0) + .nth(index as usize - 1) + .ok_or_else(|| anyhow::anyhow!("String not found"))?; + + Ok(String::from_utf8_lossy(s).into_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_root_device() { + let root_device = get_root_device().unwrap(); + assert!(!root_device.is_empty()); + } + + #[test] + fn test_get_serial_number() { + let Ok(disk_part_name) = get_root_device() else { + return; + }; + + let Ok(serial_number) = get_serial_number(&disk_part_name) else { + return; + }; + + assert!(!serial_number.is_empty()); + } + + #[test] + fn test_get_mac_addresses() { + let mac_addresses = get_mac_addresses().unwrap(); + assert!(!mac_addresses.is_empty()); + } + + #[test] + fn test_get_bios_info() { + let Ok(bios_info) = read_bios_info(BIOS_INFO_PATH) else { + return; + }; + + assert!(!bios_info.vendor.is_empty()); + } + + #[test] + fn test_get_system_info() { + let Ok(system_info) = read_system_info(SYSTEM_INFO_PATH) else { + return; + }; + + assert!(!system_info.manufacturer.is_empty()); + } + + #[test] + fn test_get_enclosure_info() { + let Ok(enclosure_info) = read_enclosure_info(ENCLOSURE_INFO_PATH) else { + return; + }; + + assert!(!enclosure_info.manufacturer.is_empty()); + } + + #[test] + fn test_hardware_info_with_extra() { + let hardware_info = HardwareInfo::new() + .unwrap() + .with_extra(serde_json::json!({"custom_field": "value"})); + + assert!(hardware_info.extra.is_some()); + assert_eq!(hardware_info.extra.unwrap()["custom_field"], "value"); + } + + #[test] + fn test_hardware_info_serialization() { + let hardware_info = HardwareInfo::new().unwrap(); + let serialized = serde_json::to_string(&hardware_info).unwrap(); + let deserialized: HardwareInfo = serde_json::from_str(&serialized).unwrap(); + assert_eq!(hardware_info.cpu_is_virtual, deserialized.cpu_is_virtual); + assert_eq!( + hardware_info.disk_serial_number, + deserialized.disk_serial_number + ); + } +} diff --git a/attestation-agent/attester/src/system/sysinfo/system_info/mod.rs b/attestation-agent/attester/src/system/sysinfo/system_info/mod.rs new file mode 100644 index 000000000..9f2cec510 --- /dev/null +++ b/attestation-agent/attester/src/system/sysinfo/system_info/mod.rs @@ -0,0 +1,303 @@ +pub mod hardware; +pub mod software; + +use anyhow::Result; +use hardware::HardwareInfo; +use serde::{Deserialize, Serialize}; +use software::SoftwareInfo; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MachineInfo { + pub hardware: HardwareInfo, + pub software: SoftwareInfo, + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, +} + +#[derive(Default)] +pub struct MachineInfoBuilder { + hardware: Option, + software: Option, + extra: Option, +} + +impl MachineInfoBuilder { + pub fn hardware(mut self, hardware: HardwareInfo) -> Self { + self.hardware = Some(hardware); + self + } + + pub fn software(mut self, software: SoftwareInfo) -> Self { + self.software = Some(software); + self + } + + pub fn build(self) -> Result { + Ok(MachineInfo { + hardware: self + .hardware + .ok_or_else(|| anyhow::anyhow!("Hardware info is required"))?, + software: self + .software + .ok_or_else(|| anyhow::anyhow!("Software info is required"))?, + extra: self.extra, + }) + } + + pub fn with_extra(mut self, extra: serde_json::Value) -> Self { + self.extra = Some(extra); + self + } +} + +pub fn get_machine_info() -> Result { + MachineInfoBuilder::default() + .hardware(HardwareInfo::new()?) + .software(SoftwareInfo::new()?) + .build() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::system::sysinfo::system_info::{ + hardware::{BiosInfo, EnclosureInfo, HardwareInfo, SystemInfo}, + software::SoftwareInfo, + }; + + #[test] + fn test_machine_info_builder() { + let hardware = HardwareInfo::new().unwrap(); + let software = SoftwareInfo::new().unwrap(); + + let machine_info = MachineInfoBuilder::default() + .hardware(hardware.clone()) + .software(software.clone()) + .with_extra(serde_json::json!({"custom_field": "value"})) + .build() + .unwrap(); + + assert_eq!( + machine_info.hardware.cpu_is_virtual, + hardware.cpu_is_virtual + ); + assert_eq!( + machine_info.extra, + Some(serde_json::json!({"custom_field": "value"})) + ); + } + + #[test] + fn test_get_machine_info() { + let machine_info = get_machine_info().unwrap(); + assert!(!machine_info.hardware.mac_addresses.is_empty()); + } + + #[test] + fn test_machine_info_serialization() { + let machine_info = get_machine_info().unwrap(); + let serialized = serde_json::to_string(&machine_info).unwrap(); + let deserialized: MachineInfo = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + machine_info.hardware.cpu_is_virtual, + deserialized.hardware.cpu_is_virtual + ); + } + + #[test] + fn test_forward_compatibility() { + let json_data = r#" + { + "hardware": { + "cpu_is_virtual": true, + "disk_serial_number": "********", + "mac_addresses": "**:**:**:**:**:**", + "bios_info": { + "vendor": "EFI Development Kit II / OVMF", + "bios_version": "0.0.0", + "bios_release_date": "02/06/2015", + "is_virtual_machine": true, + "system_bios_major_release": "0", + "system_bios_minor_release": "0" + }, + "system_info": { + "manufacturer": "Cloud Provider", + "product_name": "Cloud ECS", + "serial_number": "********", + "uuid": "********-****-****-****-************" + }, + "enclosure_info": { + "manufacturer": "Cloud Provider", + "enclosure_type": "Cloud", + "version": "pc-i440fx-2.1", + "serial_number": "", + "asset_tag_number": "" + } + }, + "software": { + "uname": "{\"machine\":\"x86_64\",\"nodename\":\"********\",\"release\":\"6.6.31-cloudlinux\",\"sysname\":\"Linux\",\"version\":\"1 SMP Thu May 23 08:36:57 UTC 2024\"}" + } + }"#; + + let deserialized: MachineInfo = serde_json::from_str(json_data).unwrap(); + + // Test hardware fields + assert!(deserialized.hardware.cpu_is_virtual); + assert_eq!(deserialized.hardware.disk_serial_number, "********"); + assert_eq!(deserialized.hardware.mac_addresses, "**:**:**:**:**:**"); + + // Test bios_info fields + assert_eq!( + deserialized.hardware.bios_info.vendor, + "EFI Development Kit II / OVMF" + ); + assert_eq!(deserialized.hardware.bios_info.bios_version, "0.0.0"); + assert_eq!( + deserialized.hardware.bios_info.bios_release_date, + "02/06/2015" + ); + assert!(deserialized.hardware.bios_info.is_virtual_machine); + assert_eq!( + deserialized.hardware.bios_info.system_bios_major_release, + "0" + ); + assert_eq!( + deserialized.hardware.bios_info.system_bios_minor_release, + "0" + ); + + // Test system_info fields + assert_eq!( + deserialized.hardware.system_info.manufacturer, + "Cloud Provider" + ); + assert_eq!(deserialized.hardware.system_info.product_name, "Cloud ECS"); + assert_eq!(deserialized.hardware.system_info.serial_number, "********"); + assert_eq!( + deserialized.hardware.system_info.uuid, + "********-****-****-****-************" + ); + + // Test enclosure_info fields + assert_eq!( + deserialized.hardware.enclosure_info.manufacturer, + "Cloud Provider" + ); + assert_eq!(deserialized.hardware.enclosure_info.enclosure_type, "Cloud"); + assert_eq!( + deserialized.hardware.enclosure_info.version, + "pc-i440fx-2.1" + ); + assert_eq!(deserialized.hardware.enclosure_info.serial_number, ""); + assert_eq!(deserialized.hardware.enclosure_info.asset_tag_number, ""); + + // Test software fields + assert!(deserialized.software.uname.contains("x86_64")); + } + + #[test] + fn test_backward_compatibility() { + let machine_info = MachineInfo { + hardware: HardwareInfo { + cpu_is_virtual: true, + disk_serial_number: "********".to_string(), + mac_addresses: "**:**:**:**:**:**".to_string(), + bios_info: BiosInfo { + vendor: "Test Vendor".to_string(), + bios_version: "1.0".to_string(), + bios_release_date: "2023-01-01".to_string(), + is_virtual_machine: true, + system_bios_major_release: "1".to_string(), + system_bios_minor_release: "0".to_string(), + }, + system_info: SystemInfo { + manufacturer: "Test Manufacturer".to_string(), + product_name: "Test Product".to_string(), + serial_number: "********".to_string(), + uuid: "********-****-****-****-************".to_string(), + }, + enclosure_info: EnclosureInfo { + manufacturer: "Test Enclosure".to_string(), + enclosure_type: "Test Type".to_string(), + version: "1.0".to_string(), + serial_number: "********".to_string(), + asset_tag_number: "********".to_string(), + }, + extra: None, + }, + software: SoftwareInfo { + uname: "Test Uname".to_string(), + extra: None, + }, + extra: None, + }; + + let serialized = serde_json::to_string(&machine_info).unwrap(); + let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + + // Check that all fields are present in the serialized JSON + assert!(deserialized["hardware"]["cpu_is_virtual"].is_boolean()); + assert!(deserialized["hardware"]["disk_serial_number"].is_string()); + assert!(deserialized["hardware"]["mac_addresses"].is_string()); + assert!(deserialized["hardware"]["bios_info"]["vendor"].is_string()); + assert!(deserialized["hardware"]["system_info"]["manufacturer"].is_string()); + assert!(deserialized["hardware"]["enclosure_info"]["manufacturer"].is_string()); + assert!(deserialized["software"]["uname"].is_string()); + + // Ensure that extra fields are not present + assert!(deserialized["hardware"]["extra"].is_null()); + assert!(deserialized["software"]["extra"].is_null()); + assert!(deserialized["extra"].is_null()); + } + + #[test] + fn test_extra_fields() { + let json_data = r#" + { + "hardware": { + "cpu_is_virtual": true, + "disk_serial_number": "********", + "mac_addresses": "**:**:**:**:**:**", + "bios_info": { + "vendor": "Test Vendor", + "bios_version": "1.0", + "bios_release_date": "2023-01-01", + "is_virtual_machine": true, + "system_bios_major_release": "1", + "system_bios_minor_release": "0", + "extra_bios_field": "extra_value" + }, + "system_info": { + "manufacturer": "Test Manufacturer", + "product_name": "Test Product", + "serial_number": "********", + "uuid": "********-****-****-****-************" + }, + "enclosure_info": { + "manufacturer": "Test Enclosure", + "enclosure_type": "Test Type", + "version": "1.0", + "serial_number": "********", + "asset_tag_number": "********" + }, + "extra_hardware_field": "extra_hardware_value" + }, + "software": { + "uname": "Test Uname", + "extra_software_field": "extra_software_value" + }, + "extra_top_level_field": "extra_top_level_value" + }"#; + + let deserialized: MachineInfo = serde_json::from_str(json_data).unwrap(); + + // Check that known fields are correctly deserialized + assert!(deserialized.hardware.cpu_is_virtual); + assert_eq!(deserialized.hardware.disk_serial_number, "********"); + + // Check that extra fields are ignored without causing errors + assert!(deserialized.hardware.extra.is_none()); + assert!(deserialized.software.extra.is_none()); + assert!(deserialized.extra.is_none()); + } +} diff --git a/attestation-agent/attester/src/system/sysinfo/system_info/software.rs b/attestation-agent/attester/src/system/sysinfo/system_info/software.rs new file mode 100644 index 000000000..4b9be4ae7 --- /dev/null +++ b/attestation-agent/attester/src/system/sysinfo/system_info/software.rs @@ -0,0 +1,83 @@ +use anyhow::{Context, Result}; +use nix::sys::utsname::uname; +use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SoftwareInfo { + pub uname: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, +} + +// Cache uname info since it rarely changes +static UNAME_INFO: OnceLock = OnceLock::new(); + +impl SoftwareInfo { + pub fn new() -> Result { + Ok(Self { + uname: get_cached_uname()?, + extra: None, + }) + } +} + +fn get_cached_uname() -> Result { + Ok(UNAME_INFO + .get_or_init(|| get_uname().expect("Failed to get uname info")) + .clone()) +} + +fn get_uname() -> Result { + let uname = uname().context("Failed to get uname info")?; + + let fields = vec![ + ("sysname", uname.sysname().to_string_lossy().into_owned()), + ("nodename", uname.nodename().to_string_lossy().into_owned()), + ("release", uname.release().to_string_lossy().into_owned()), + ("version", uname.version().to_string_lossy().into_owned()), + ("machine", uname.machine().to_string_lossy().into_owned()), + ( + "domainname", + uname.domainname().to_string_lossy().into_owned(), + ), + ]; + + let uname_info = serde_json::Map::from_iter( + fields + .into_iter() + .map(|(k, v)| (k.to_owned(), serde_json::Value::String(v))), + ); + + Ok(serde_json::Value::Object(uname_info).to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_uname() { + let uname = get_uname().unwrap(); + assert!(!uname.is_empty()); + } + + #[test] + fn test_software_info_with_extra() { + let mut software_info = SoftwareInfo::new().expect("Failed to create SoftwareInfo"); + software_info.extra = Some(serde_json::json!({"custom_field": "value"})); + + assert!(software_info.extra.is_some()); + if let Some(extra) = &software_info.extra { + assert_eq!(extra["custom_field"], "value"); + } + } + + #[test] + fn test_software_info_serialization() { + let software_info = SoftwareInfo::new().unwrap(); + let serialized = serde_json::to_string(&software_info).unwrap(); + let deserialized: SoftwareInfo = serde_json::from_str(&serialized).unwrap(); + assert_eq!(software_info.uname, deserialized.uname); + } +} diff --git a/attestation-agent/kbc/Cargo.toml b/attestation-agent/kbc/Cargo.toml index df7eebe28..6d3dc8ad8 100644 --- a/attestation-agent/kbc/Cargo.toml +++ b/attestation-agent/kbc/Cargo.toml @@ -19,7 +19,10 @@ serde.workspace = true serde_json.workspace = true sev = { path = "../deps/sev", optional = true } strum.workspace = true -tokio = { workspace = true, features = ["macros", "rt-multi-thread"], optional = true } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", +], optional = true } tonic = { workspace = true, optional = true } url.workspace = true uuid = { workspace = true, features = ["serde", "v4"], optional = true } @@ -39,11 +42,12 @@ cc_kbc = ["kbs_protocol/background_check"] all-attesters = ["kbs_protocol?/all-attesters"] tdx-attester = ["kbs_protocol/tdx-attester"] sgx-attester = ["kbs_protocol/sgx-attester"] -az-snp-vtpm-attester= ["kbs_protocol/az-snp-vtpm-attester"] -az-tdx-vtpm-attester= ["kbs_protocol/az-tdx-vtpm-attester"] +az-snp-vtpm-attester = ["kbs_protocol/az-snp-vtpm-attester"] +az-tdx-vtpm-attester = ["kbs_protocol/az-tdx-vtpm-attester"] snp-attester = ["kbs_protocol/snp-attester"] cca-attester = ["kbs_protocol/cca-attester"] -se-attester = ["kbs_protocol/se-attester"] +se-attester = ["kbs_protocol/se-attester"] +system-attester = ["kbs_protocol/system-attester"] sample_kbc = [] offline_fs_kbc = [] diff --git a/attestation-agent/kbs_protocol/Cargo.toml b/attestation-agent/kbs_protocol/Cargo.toml index 667969a72..ac785a729 100644 --- a/attestation-agent/kbs_protocol/Cargo.toml +++ b/attestation-agent/kbs_protocol/Cargo.toml @@ -16,7 +16,7 @@ env_logger = { workspace = true, optional = true } jwt-simple.workspace = true kbs-types.workspace = true log.workspace = true -protobuf = { workspace = true, optional = true} +protobuf = { workspace = true, optional = true } reqwest = { workspace = true, features = ["cookies", "json"], optional = true } resource_uri.path = "../deps/resource_uri" serde.workspace = true @@ -24,7 +24,7 @@ serde_json.workspace = true sha2.workspace = true thiserror.workspace = true tokio.workspace = true -ttrpc = { workspace = true, optional = true} +ttrpc = { workspace = true, optional = true } url.workspace = true zeroize.workspace = true @@ -33,7 +33,7 @@ rstest.workspace = true serial_test.workspace = true tempfile.workspace = true testcontainers.workspace = true -tokio = { workspace = true, features = [ "rt", "macros", "fs", "process" ]} +tokio = { workspace = true, features = ["rt", "macros", "fs", "process"] } [build-dependencies] ttrpc-codegen = { workspace = true, optional = true } @@ -58,7 +58,8 @@ az-tdx-vtpm-attester = ["attester/az-tdx-vtpm-attester"] snp-attester = ["attester/snp-attester"] csv-attester = ["attester/csv-attester"] cca-attester = ["attester/cca-attester"] -se-attester = ["attester/se-attester"] +se-attester = ["attester/se-attester"] +system-attester = ["attester/system-attester"] rust-crypto = ["reqwest/rustls-tls", "crypto/rust-crypto"] openssl = ["reqwest/native-tls-vendored", "crypto/openssl"] diff --git a/build-image-push.sh b/build-image-push.sh new file mode 100755 index 000000000..4437de951 --- /dev/null +++ b/build-image-push.sh @@ -0,0 +1,5 @@ +#!/bin/bash +REPO_PREFIX=registry.cn-hangzhou.aliyuncs.com/lxx/trustiflux + +docker build -t $REPO_PREFIX:confidential-data-hub-20240821 -f Dockerfile.cdh . --push +# docker build -t $REPO_PREFIX:attestation-agent -f Dockerfile.aa . --push \ No newline at end of file diff --git a/cdh-start.sh b/cdh-start.sh new file mode 100755 index 000000000..ac65d2351 --- /dev/null +++ b/cdh-start.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +set -euo pipefail +set -o noglob + +# Initialize parameters +trustee_address='' +key_id='' +resource_path='' + +usage() { + echo "This script is used to start Attestation Agent" 1>&2 + echo "" 1>&2 + echo "Usage: $0 --trustee-addr Address of remote trustee" 1>&2 + echo "--key-id the id of the confidential resource from trustee" 1>&2 + echo "--resource-path the file path that will store the confidential resource" 1>&2 + + exit 1 +} + +# Parse cmd +while [[ "$#" -gt 0 ]]; do + case "$1" in + --trustee-addr) + trustee_address="$2" + shift 2 + ;; + --key-id) + key_id="$2" + shift 2 + ;; + --resource-path) + resource_path="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + echo "Unknown option: $1" + usage + ;; + esac +done + +cat << EOF > /etc/confidential-data-hub.toml +socket = "unix:///run/confidential-containers/cdh.sock" +[kbc] +name = "cc_kbc" +url = "${trustee_address}" +EOF + +blob=$(confidential-data-hub -c /etc/confidential-data-hub.toml get-resource --resource-uri "${key_id}") +echo "$blob" | base64 -d > "$resource_path" + +sleep 100000000 \ No newline at end of file diff --git a/confidential-data-hub/hub/src/bin/cdh-oneshot.rs b/confidential-data-hub/hub/src/bin/cdh-oneshot.rs index c1e1f9888..5357fb5e4 100644 --- a/confidential-data-hub/hub/src/bin/cdh-oneshot.rs +++ b/confidential-data-hub/hub/src/bin/cdh-oneshot.rs @@ -94,6 +94,7 @@ struct PullImageArgs { #[tokio::main] async fn main() { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let args = Cli::parse(); let config = CdhConfig::new(args.config).expect("failed to initialize cdh config"); config.set_configuration_envs(); diff --git a/tdx-attest.conf b/tdx-attest.conf new file mode 100644 index 000000000..d7c6361ae --- /dev/null +++ b/tdx-attest.conf @@ -0,0 +1 @@ +port=4050 \ No newline at end of file diff --git a/version-base.yml b/version-base.yml new file mode 100644 index 000000000..506d4bf05 --- /dev/null +++ b/version-base.yml @@ -0,0 +1,16 @@ +# BaseOS 版本定义 [BaseOS 应用名, 版本号, 镜像地址] +Dependency: + AnolisOS: + Anolis8: + 8.6: &AnolisOS8.6 [anolisos, 8.6, openanolis/anolisos:8.6] + 8.8: &AnolisOS8.8 [anolisos, 8.8, openanolis/anolisos:8.8] + Anolis23: + 23: &AnolisOS23 [anolisos, 23, openanolis/anolisos:23] + Alinux: + 3: &Alinux3 [ alinux, 3, alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3 ] + 2: &Alinux2 [ alinux, 2, alibaba-cloud-linux-2-registry.cn-hangzhou.cr.aliyuncs.com/alinux2 ] + 3-inc: &Alinux3-inc [ alinux, 3, reg.docker.alibaba-inc.com/alinux/base ] + 2-inc: &Alinux2-inc [ alinux, 2, reg.docker.alibaba-inc.com/aliyun-linux/aliyunlinux ] + 3.2304: &Alinux3.2304 [ alinux, 3.2304, reg.docker.alibaba-inc.com/alinux/base ] + Ubuntu: + 22.04: &Ubuntu22.04 [ ubuntu, 22.04, docker.io/library/ubuntu ]