mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
feat: add TEE attestation shield to web gateway UI (#275)
Show a shield indicator in the tab bar when the instance is running inside a TEE deployment. On hover, fetches and displays the TDX attestation report (image digest, TLS cert fingerprint, report data, VM config) from the management API. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -38,6 +38,7 @@ function authenticate() {
|
||||
connectSSE();
|
||||
connectLogSSE();
|
||||
startGatewayStatusPolling();
|
||||
checkTeeStatus();
|
||||
loadThreads();
|
||||
loadMemoryTree();
|
||||
loadJobs();
|
||||
@@ -2098,6 +2099,100 @@ document.getElementById('gateway-status-trigger').addEventListener('mouseleave',
|
||||
document.getElementById('gateway-popover').classList.remove('visible');
|
||||
});
|
||||
|
||||
// --- TEE attestation ---
|
||||
|
||||
let teeInfo = null;
|
||||
let teeReportCache = null;
|
||||
let teeReportLoading = false;
|
||||
|
||||
function teeApiBase() {
|
||||
var parts = window.location.hostname.split('.');
|
||||
if (parts.length < 2) return null;
|
||||
var domain = parts.slice(1).join('.');
|
||||
return window.location.protocol + '//api.' + domain;
|
||||
}
|
||||
|
||||
function teeInstanceName() {
|
||||
return window.location.hostname.split('.')[0];
|
||||
}
|
||||
|
||||
function checkTeeStatus() {
|
||||
var base = teeApiBase();
|
||||
if (!base) return;
|
||||
var name = teeInstanceName();
|
||||
fetch(base + '/instances/' + encodeURIComponent(name) + '/attestation').then(function(res) {
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
return res.json();
|
||||
}).then(function(data) {
|
||||
teeInfo = data;
|
||||
document.getElementById('tee-shield').style.display = 'flex';
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
function fetchTeeReport() {
|
||||
if (teeReportCache) {
|
||||
renderTeePopover(teeReportCache);
|
||||
return;
|
||||
}
|
||||
if (teeReportLoading) return;
|
||||
teeReportLoading = true;
|
||||
var base = teeApiBase();
|
||||
if (!base) return;
|
||||
var popover = document.getElementById('tee-popover');
|
||||
popover.innerHTML = '<div class="tee-popover-loading">Loading attestation report...</div>';
|
||||
fetch(base + '/attestation/report').then(function(res) {
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
return res.json();
|
||||
}).then(function(data) {
|
||||
teeReportCache = data;
|
||||
renderTeePopover(data);
|
||||
}).catch(function() {
|
||||
popover.innerHTML = '<div class="tee-popover-loading">Could not load attestation report</div>';
|
||||
}).finally(function() {
|
||||
teeReportLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function renderTeePopover(report) {
|
||||
var popover = document.getElementById('tee-popover');
|
||||
var digest = (teeInfo && teeInfo.image_digest) || 'N/A';
|
||||
var fingerprint = report.tls_certificate_fingerprint || 'N/A';
|
||||
var reportData = report.report_data || '';
|
||||
var vmConfig = report.vm_config || 'N/A';
|
||||
var truncated = reportData.length > 32 ? reportData.slice(0, 32) + '...' : reportData;
|
||||
popover.innerHTML = '<div class="tee-popover-title">'
|
||||
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>'
|
||||
+ 'TEE Attestation</div>'
|
||||
+ '<div class="tee-field"><div class="tee-field-label">Image Digest</div>'
|
||||
+ '<div class="tee-field-value">' + escapeHtml(digest) + '</div></div>'
|
||||
+ '<div class="tee-field"><div class="tee-field-label">TLS Certificate Fingerprint</div>'
|
||||
+ '<div class="tee-field-value">' + escapeHtml(fingerprint) + '</div></div>'
|
||||
+ '<div class="tee-field"><div class="tee-field-label">Report Data</div>'
|
||||
+ '<div class="tee-field-value">' + escapeHtml(truncated) + '</div></div>'
|
||||
+ '<div class="tee-field"><div class="tee-field-label">VM Config</div>'
|
||||
+ '<div class="tee-field-value">' + escapeHtml(vmConfig) + '</div></div>'
|
||||
+ '<div class="tee-popover-actions">'
|
||||
+ '<button class="tee-btn-copy" onclick="copyTeeReport()">Copy Full Report</button></div>';
|
||||
}
|
||||
|
||||
function copyTeeReport() {
|
||||
if (!teeReportCache) return;
|
||||
var combined = Object.assign({}, teeReportCache, teeInfo || {});
|
||||
navigator.clipboard.writeText(JSON.stringify(combined, null, 2)).then(function() {
|
||||
showToast('Attestation report copied', 'success');
|
||||
}).catch(function() {
|
||||
showToast('Failed to copy report', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('tee-shield').addEventListener('mouseenter', function() {
|
||||
fetchTeeReport();
|
||||
document.getElementById('tee-popover').classList.add('visible');
|
||||
});
|
||||
document.getElementById('tee-shield').addEventListener('mouseleave', function() {
|
||||
document.getElementById('tee-popover').classList.remove('visible');
|
||||
});
|
||||
|
||||
// --- Extension install ---
|
||||
|
||||
function installExtension() {
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
<button data-tab="routines">Routines</button>
|
||||
<button data-tab="extensions">Extensions</button>
|
||||
<div class="spacer"></div>
|
||||
<div class="tee-shield" id="tee-shield" style="display:none" title="Running in a Trusted Execution Environment">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
<span id="tee-shield-label">TEE Verified</span>
|
||||
<div class="tee-popover" id="tee-popover"></div>
|
||||
</div>
|
||||
<div class="status" id="gateway-status-trigger">
|
||||
<div class="dot" id="sse-dot"></div>
|
||||
<span id="sse-status">Connected</span>
|
||||
|
||||
@@ -189,6 +189,126 @@ body {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
/* TEE Shield */
|
||||
.tee-shield {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--success);
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(63, 185, 80, 0.1);
|
||||
border: 1px solid rgba(63, 185, 80, 0.25);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.tee-shield:hover {
|
||||
background: rgba(63, 185, 80, 0.18);
|
||||
}
|
||||
|
||||
.tee-shield svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#tee-shield-label {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tee-popover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
min-width: 340px;
|
||||
max-width: 420px;
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.tee-popover.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tee-popover-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tee-popover-title svg {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.tee-field {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tee-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tee-field-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.tee-field-value {
|
||||
font-size: 12px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
background: var(--bg);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tee-popover-actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tee-btn-copy {
|
||||
padding: 4px 10px;
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.tee-btn-copy:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.tee-popover-loading {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* Tab Panels */
|
||||
.tab-panel {
|
||||
display: none;
|
||||
|
||||
Reference in New Issue
Block a user