Skip to content

Commit 82492ad

Browse files
authored
Merge pull request #68 from faasm/enhancement-trustee
[experiments] Fix Escrow Baselines
2 parents b5cb187 + 301c20b commit 82492ad

File tree

25 files changed

+627
-689
lines changed

25 files changed

+627
-689
lines changed

.github/workflows/azure.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: "SNP End-to-End Tests"
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
defaults:
9+
run:
10+
shell: bash
11+
12+
# Cancel previous running actions for the same PR
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
16+
17+
jobs:
18+
snp-vtpm-test:
19+
runs-on: [self-hosted]
20+
steps:
21+
- name: "Check out the code"
22+
uses: actions/checkout@v4
23+

.github/workflows/snp.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
4545
# Build SNP applications and embed the attestation service's certificate.
4646
- name: "Build SNP applications"
47-
run: ./scripts/accli_wrapper.sh applications build --clean --as-cert-path ./certs/cert.pem --in-cvm
47+
run: ./scripts/accli_wrapper.sh applications build --clean --as-cert-dir ./certs/cert.pem --in-cvm
4848

4949
- name: "Run supported SNP applications"
5050
run: |

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

accless/libs/attestation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_library(${CMAKE_PROJECT_TARGET}
2525
mock_snp.cpp
2626
utils.cpp
2727
snp.cpp
28+
vcek_cache.cpp
2829
)
2930
target_link_libraries(${CMAKE_PROJECT_TARGET} PUBLIC
3031
azguestattestation

accless/libs/attestation/http_client.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "attestation.h"
2+
#include "vcek_cache.h"
23

34
#include <cstring>
45
#include <curl/curl.h>
@@ -19,6 +20,11 @@ static size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb,
1920
}
2021

2122
HttpClient::HttpClient(const std::string &certPath) : certPath_(certPath) {
23+
// Initialize process-wide VCEK cache once. This is only populated if
24+
// we are deployed inside an Azure cVM, otherwise will return empty
25+
// strings.
26+
accless::attestation::snp::getVcekPemBundle();
27+
2228
curl_ = curl_easy_init();
2329
if (!curl_) {
2430
throw std::runtime_error("accless(att): failed to init curl");

accless/libs/attestation/utils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "attestation.h"
2+
#include "vcek_cache.h"
23

34
#include <nlohmann/json.hpp>
45

@@ -49,6 +50,15 @@ std::string buildRequestBody(const std::string &quoteB64,
4950
body["quote"] = quoteB64;
5051
body["runtimeData"]["data"] = runtimeB64;
5152
body["runtimeData"]["dataType"] = "Binary";
53+
54+
const auto &vcekPem = accless::attestation::snp::getVcekCertPem();
55+
const auto &chainPem = accless::attestation::snp::getVcekChainPem();
56+
57+
if (!vcekPem.empty()) {
58+
body["collateral"]["vcekCertPem"] = vcekPem;
59+
body["collateral"]["certificateChainPem"] = chainPem;
60+
}
61+
5262
return body.dump();
5363
}
5464
} // namespace accless::attestation::utils
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include "vcek_cache.h"
2+
3+
#include <curl/curl.h>
4+
#include <nlohmann/json.hpp>
5+
6+
#include <stdexcept>
7+
#include <string>
8+
9+
namespace accless::attestation::snp {
10+
namespace {
11+
12+
constexpr const char *THIM_URL =
13+
"http://169.254.169.254/metadata/THIM/amd/certification";
14+
constexpr const char *THIM_METADATA_HEADER = "Metadata:true";
15+
16+
struct VcekCache {
17+
std::once_flag once;
18+
std::string vcekCert;
19+
std::string certChain;
20+
std::string bundle;
21+
std::string error;
22+
};
23+
24+
VcekCache g_cache;
25+
26+
size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) {
27+
auto *out = static_cast<std::string *>(userdata);
28+
if (!out)
29+
return 0;
30+
const size_t total = size * nmemb;
31+
out->append(ptr, total);
32+
return total;
33+
}
34+
35+
// Perform one-time VCEK fetch, but *never throw*. If unavailable, returns empty
36+
// PEM strings.
37+
void initVcekCache() {
38+
CURL *curl = curl_easy_init();
39+
if (!curl) {
40+
g_cache.error = "failed to init curl for VCEK fetch";
41+
return;
42+
}
43+
44+
std::string response;
45+
char errbuf[CURL_ERROR_SIZE] = {0};
46+
47+
curl_easy_setopt(curl, CURLOPT_URL, THIM_URL);
48+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
49+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
50+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
51+
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
52+
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 500L); // do not block
53+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 300L);
54+
55+
// Add Metadata:true header
56+
struct curl_slist *headers = nullptr;
57+
headers = curl_slist_append(headers, THIM_METADATA_HEADER);
58+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
59+
60+
CURLcode res = curl_easy_perform(curl);
61+
long status = 0;
62+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
63+
64+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr);
65+
curl_slist_free_all(headers);
66+
curl_easy_cleanup(curl);
67+
68+
// IMDS unreachable → not a CVM → silently return empty values
69+
if (res != CURLE_OK) {
70+
g_cache.error = std::string("VCEK fetch failed: curl error: ") +
71+
(errbuf[0] ? errbuf : curl_easy_strerror(res));
72+
return;
73+
}
74+
75+
if (status != 200) {
76+
g_cache.error =
77+
"VCEK fetch failed: HTTP status " + std::to_string(status);
78+
return;
79+
}
80+
81+
// Parse JSON.
82+
try {
83+
auto json = nlohmann::json::parse(response);
84+
85+
g_cache.vcekCert = json.value("vcekCert", "");
86+
g_cache.certChain = json.value("certificateChain", "");
87+
88+
// Normalize newlines
89+
if (!g_cache.vcekCert.empty() && g_cache.vcekCert.back() != '\n')
90+
g_cache.vcekCert.push_back('\n');
91+
92+
if (!g_cache.certChain.empty() && g_cache.certChain.back() != '\n')
93+
g_cache.certChain.push_back('\n');
94+
95+
g_cache.bundle = g_cache.vcekCert + g_cache.certChain;
96+
} catch (const std::exception &e) {
97+
g_cache.error = std::string("VCEK fetch JSON parse error: ") + e.what();
98+
// Leave empty certs
99+
return;
100+
}
101+
}
102+
103+
void ensureInitialized() { std::call_once(g_cache.once, initVcekCache); }
104+
105+
} // namespace
106+
107+
// --- Public API ---
108+
109+
const std::string &getVcekPemBundle() {
110+
ensureInitialized();
111+
return g_cache.bundle;
112+
}
113+
114+
const std::string &getVcekCertPem() {
115+
ensureInitialized();
116+
return g_cache.vcekCert;
117+
}
118+
119+
const std::string &getVcekChainPem() {
120+
ensureInitialized();
121+
return g_cache.certChain;
122+
}
123+
} // namespace accless::attestation::snp
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <mutex>
4+
#include <string>
5+
6+
namespace accless::attestation::snp {
7+
8+
// Returns concatenated PEM (VCEK + chain) or an empty string on failure.
9+
const std::string &getVcekPemBundle();
10+
11+
// If you prefer separate pieces:
12+
const std::string &getVcekCertPem();
13+
const std::string &getVcekChainPem();
14+
} // namespace accless::attestation::snp

accli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ path = "src/lib.rs"
1717

1818
[dependencies]
1919
anyhow.workspace = true
20+
az-snp-vtpm.workspace = true
2021
base64.workspace = true
2122
bytes.workspace = true
2223
chrono.workspace = true

0 commit comments

Comments
 (0)