Skip to content

feat: on-chain TCB policy with dcap-qvl two-phase verification#538

Draft
kvinwang wants to merge 9 commits intomasterfrom
policy
Draft

feat: on-chain TCB policy with dcap-qvl two-phase verification#538
kvinwang wants to merge 9 commits intomasterfrom
policy

Conversation

@kvinwang
Copy link
Collaborator

Summary

  • Upgrade dcap-qvl to policy branch with two-phase verification API (QuoteVerificationResult → policy validation → VerifiedReport)
  • Propagate QuoteVerificationResult up through DstackVerifiedReport so KMS/verifier can apply app-specific TCB policies
  • Add IAppTcbPolicy interface: both DstackApp.sol and DstackKms.sol store a versioned JSON TCB policy string on-chain
  • auth-eth middleware reads policy from contract (with try-catch for backward compatibility with old contracts) and attaches it to BootResponse
  • KMS parses the policy JSON ({"version": 1, "intel_qal": [...]}) and validates QVR against RegoPolicySet
  • Fail-close: unknown policy versions are rejected
  • Baseline policy rejects high-risk advisories: INTEL-SA-01397, INTEL-SA-01367, INTEL-SA-01314, INTEL-SA-00837

Changes across layers

Layer Files What
Solidity IAppTcbPolicy.sol, DstackApp.sol, DstackKms.sol On-chain policy storage (ERC-165 detectable)
TypeScript ethereum.ts, server.ts, types.ts Read policy from contract, pass in response
Rust (attest) attestation.rs, host_api.rs, verification.rs QVR propagation, baseline policy, advisory blacklist
Rust (kms) main_service.rs, upgrade_authority.rs Parse on-chain policy, RegoPolicySet validation

Depends on

Test plan

  • cargo check passes (verified)
  • hardhat compile passes (verified)
  • tsc --noEmit passes (verified)
  • Integration test with contract deploying TCB policy and KMS validating against it
  • Backward compatibility: KMS works with old contracts that don't implement IAppTcbPolicy
  • Fail-close: KMS rejects boot when policy version is unknown

…upport

- Update dcap-qvl dependency to policy branch (two-phase verification API)
- Propagate QuoteVerificationResult through DstackVerifiedReport for
  flexible policy validation by business layer
- Add baseline TCB policy with high-risk advisory rejection (INTEL-SA-01397,
  INTEL-SA-01367, INTEL-SA-01314, INTEL-SA-00837)
- Add IAppTcbPolicy interface for on-chain TCB policy storage
- Update DstackApp.sol and DstackKms.sol to store versioned TCB policy JSON
- auth-eth reads policy from contracts and passes in BootResponse
- KMS validates QuoteVerificationResult against on-chain RegoPolicySet
- Fail-close on unknown policy version (version != 1 is rejected)
/// Get a [`TdxVerifiedReport`] without additional policy checks.
///
/// Safe to call because the baseline policy was already validated during verification.
pub fn tdx_report(&self) -> Option<TdxVerifiedReport> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better remove this function.

match self {
DstackVerifiedReport::DstackTdx(tdx_report) => tdx_report.ppid.to_vec(),
DstackVerifiedReport::DstackTdx(qvr) => {
qvr.clone().into_report_unchecked().ppid
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More lightweight way?

}
// Apply on-chain TCB policy (if set) to the TDX QuoteVerificationResult
if let Some(qvr) = att.report.tdx_qvr() {
validate_onchain_tcb_policy(qvr, &response.tcb_policy)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better move this before the traditional is_app_allowed verification.
I'am not a fan of the is_app_allowed returning the policy. we should add a new api in auth_api.get_app_policy. As well for kms, auth_api.get_kms_policy.

- Remove tdx_report() method, callers use tdx_qvr()+supplemental() instead
- Fix ppid access: use supplemental().platform.pck.ppid (no QVR clone)
- Split TCB policy into separate API endpoints:
  - GET /policy/app/:appId and GET /policy/kms
  - auth_api.get_app_policy() and auth_api.get_kms_policy() in Rust
- Remove tcbPolicy from BootResponse (separate concern)
- Move policy validation before is_app_allowed check
- Fix cargo fmt issues
dcap-qvl now provides QuoteVerificationResult::ppid() and report()
accessors, avoiding the heavyweight supplemental() computation or
clone+into_report_unchecked() just to read the PPID.
- Unit tests for validate_onchain_tcb_policy: empty policy, version
  checks, malformed JSON, invalid Rego, TcbPolicyDoc deserialization
- Serde contract tests with shared JSON fixtures (kms/tests/fixtures/)
  validated by both Rust and TypeScript to catch field naming drift
- TDX quote sample fixtures for building QuoteVerificationResult in tests
- url_join utility test
Move Rust tests from rust.yml (now lint-only) into a new tests.yml
workflow. Add auth-eth Jest tests as a parallel CI job.
Add end-to-end test suite that exercises the full KMS stack:
Hardhat node → contract deployment → auth-eth server → KMS server.

Tests cover auth-eth boot authorization (KMS/app allowed/denied),
KMS unauthenticated APIs (GetMeta, GetAppEnvEncryptPubKey, GetTempCaCert),
KMS authenticated APIs via RA-TLS (GetAppKey, SignCert), and TCB policy
scenarios including Intel QAL rules for dynamic_platform, cached_keys,
and TCB status validation.
Comment on lines +18 to +31
name: Rust tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.92.0

- name: Run tests
run: ./run-tests.sh

auth-eth-tests:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 1 day ago

Generally, to fix this issue you add an explicit permissions block either at the top level of the workflow (applies to all jobs) or per job, granting only the scopes needed. In this workflow, the jobs only need to read repository contents for actions/checkout; they don’t appear to require any write permissions or access to other privileged scopes.

The best fix without changing functionality is to add a root-level permissions block specifying contents: read. This ensures GITHUB_TOKEN is restricted to read-only repository contents for all jobs in this workflow. No job-specific overrides or additional scopes are required based on the shown steps. Concretely, in .github/workflows/tests.yml, insert:

permissions:
  contents: read

between the name: Tests line and the on: section. No imports or additional definitions are needed, as this is purely a YAML configuration change.

Suggested changeset 1
.github/workflows/tests.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -4,6 +4,9 @@
 
 name: Tests
 
+permissions:
+  contents: read
+
 on:
   push:
     branches: [ master, next, dev-* ]
EOF
@@ -4,6 +4,9 @@

name: Tests

permissions:
contents: read

on:
push:
branches: [ master, next, dev-* ]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +32 to +54
name: auth-eth tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: kms/auth-eth/package-lock.json

- name: Install dependencies
run: |
cd kms/auth-eth
npm ci

- name: Run tests
run: |
cd kms/auth-eth
npm test

kms-e2e-tests:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 1 day ago

In general, the fix is to add an explicit permissions block that restricts the GITHUB_TOKEN to the least privileges needed. For these test jobs (rust-tests, auth-eth-tests, kms-e2e-tests), they only need to read the repository contents to run tests, so contents: read at the workflow (root) level is sufficient and will apply to all jobs.

The best fix without changing functionality is to add a single permissions block at the root of .github/workflows/tests.yml, alongside name: and on:, configuring contents: read. This documents the required access and ensures the workflow will not gain broader permissions even if repository defaults change. No other changes to steps, jobs, or actions are required.

Concretely:

  • Edit .github/workflows/tests.yml.
  • After the name: Tests line (line 5), insert:
    permissions:
      contents: read
  • Leave all jobs (rust-tests, auth-eth-tests, kms-e2e-tests) unchanged so functionality stays identical, but with a read-only token.
Suggested changeset 1
.github/workflows/tests.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -3,6 +3,8 @@
 # SPDX-License-Identifier: Apache-2.0
 
 name: Tests
+permissions:
+  contents: read
 
 on:
   push:
EOF
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: Apache-2.0

name: Tests
permissions:
contents: read

on:
push:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +55 to +73
name: KMS E2E tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.92.0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: kms/auth-eth/package-lock.json

- name: Run KMS E2E tests
run: bash kms/e2e/run-e2e.sh

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 1 day ago

To fix the problem, add an explicit permissions block that limits the GITHUB_TOKEN to the least privilege needed. Since the jobs only check out code and run tests, contents: read is sufficient. The cleanest way is to define permissions at the workflow root (top level, alongside name, on, and env), so it applies to all jobs that don’t override it. This preserves existing functionality while constraining the token.

Concretely, in .github/workflows/tests.yml, insert a permissions: section after the on: block (or before env:) with contents: read. No other changes, imports, or additional definitions are required. Individual jobs do not need their own permissions blocks unless they require different scopes.

Suggested changeset 1
.github/workflows/tests.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,6 +10,9 @@
   pull_request:
     branches: [ master, next, dev-* ]
 
+permissions:
+  contents: read
+
 env:
   CARGO_TERM_COLOR: always
 
EOF
@@ -10,6 +10,9 @@
pull_request:
branches: [ master, next, dev-* ]

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant