Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kms/auth-eth/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function build(): Promise<FastifyInstance> {
return {
status: 'ok',
kmsContractAddr: kmsContractAddr,
ethRpcUrl: rpcUrl,
gatewayAppId: batch[0],
chainId: batch[1],
appAuthImplementation: batch[2], // NOTE: for backward compatibility
Expand Down
69 changes: 62 additions & 7 deletions kms/auth-mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ const BootResponseSchema = z.object({
type BootInfo = z.infer<typeof BootInfoSchema>;
type BootResponse = z.infer<typeof BootResponseSchema>;

// authorization policy - configurable via environment variables
// MOCK_POLICY: "allow-all" (default), "deny-kms", "deny-app", "deny-all",
// "allowlist-device", "allowlist-mr"
// MOCK_ALLOWED_DEVICE_IDS: comma-separated device IDs (for allowlist-device policy)
// MOCK_ALLOWED_MR_AGGREGATED: comma-separated MR aggregated values (for allowlist-mr policy)

type MockPolicy = 'allow-all' | 'deny-kms' | 'deny-app' | 'deny-all' | 'allowlist-device' | 'allowlist-mr';

function getPolicy(): MockPolicy {
const policy = process.env.MOCK_POLICY || 'allow-all';
const valid: MockPolicy[] = ['allow-all', 'deny-kms', 'deny-app', 'deny-all', 'allowlist-device', 'allowlist-mr'];
if (!valid.includes(policy as MockPolicy)) {
console.warn(`unknown MOCK_POLICY "${policy}", falling back to allow-all`);
return 'allow-all';
}
return policy as MockPolicy;
}

function parseList(envVar: string): Set<string> {
const raw = process.env[envVar] || '';
return new Set(raw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean));
}

// mock backend class - no blockchain interaction
class MockBackend {
private mockGatewayAppId: string;
Expand All @@ -44,14 +67,45 @@ class MockBackend {
}

async checkBoot(bootInfo: BootInfo, isKms: boolean): Promise<BootResponse> {
// always return success for mock backend
const reason = isKms ? 'mock KMS always allowed' : 'mock app always allowed';

return {
const policy = getPolicy();
const deny = (reason: string): BootResponse => ({
isAllowed: false,
reason,
gatewayAppId: '',
});
const allow = (reason: string): BootResponse => ({
isAllowed: true,
reason,
gatewayAppId: this.mockGatewayAppId,
};
});

switch (policy) {
case 'deny-all':
return deny(`mock policy: deny-all`);
case 'deny-kms':
if (isKms) return deny(`mock policy: deny-kms`);
return allow('mock app allowed (deny-kms policy)');
case 'deny-app':
if (!isKms) return deny(`mock policy: deny-app`);
return allow('mock KMS allowed (deny-app policy)');
case 'allowlist-device': {
const allowed = parseList('MOCK_ALLOWED_DEVICE_IDS');
const deviceId = bootInfo.deviceId.toLowerCase().replace(/^0x/, '');
if (allowed.size === 0) return deny('mock policy: allowlist-device with empty list');
if (!allowed.has(deviceId)) return deny(`mock policy: device ${bootInfo.deviceId} not in allowlist`);
return allow(`mock policy: device ${bootInfo.deviceId} allowed`);
}
case 'allowlist-mr': {
const allowed = parseList('MOCK_ALLOWED_MR_AGGREGATED');
const mr = bootInfo.mrAggregated.toLowerCase().replace(/^0x/, '');
if (allowed.size === 0) return deny('mock policy: allowlist-mr with empty list');
if (!allowed.has(mr)) return deny(`mock policy: mrAggregated ${bootInfo.mrAggregated} not in allowlist`);
return allow(`mock policy: mrAggregated ${bootInfo.mrAggregated} allowed`);
}
case 'allow-all':
default:
return allow(isKms ? 'mock KMS always allowed' : 'mock app always allowed');
}
}

async getGatewayAppId(): Promise<string> {
Expand Down Expand Up @@ -85,6 +139,7 @@ app.get('/', async (c) => {
return c.json({
status: 'ok',
kmsContractAddr: process.env.KMS_CONTRACT_ADDR || '0xmockcontract1234567890123456789012345678',
ethRpcUrl: process.env.ETH_RPC_URL || '',
gatewayAppId: batch[0],
chainId: batch[1],
appAuthImplementation: batch[2], // NOTE: for backward compatibility
Expand Down Expand Up @@ -155,8 +210,8 @@ app.post('/bootAuth/kms',

// start server
const port = parseInt(process.env.PORT || '3000');
console.log(`starting mock auth server on port ${port}`);
console.log('note: this is a mock backend - all authentications will succeed');
const policy = getPolicy();
console.log(`starting mock auth server on port ${port} (policy: ${policy})`);

export default {
port,
Expand Down
1 change: 1 addition & 0 deletions kms/kms.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mandatory = false
cert_dir = "/etc/kms/certs"
subject_postfix = ".dstack"
admin_token_hash = ""
site_name = ""

[core.image]
verify = true
Expand Down
8 changes: 8 additions & 0 deletions kms/rpc/proto/kms_rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ message OnboardRequest {
}

message OnboardResponse {
// k256 public key (secp256k1) inherited from source KMS
bytes k256_pubkey = 1;
}

// Attestation info needed for on-chain KMS authorization.
Expand All @@ -143,6 +145,12 @@ message AttestationInfoResponse {
bytes os_image_hash = 3;
// Attestation mode (e.g. "dstack-tdx", "dstack-gcp-tdx")
string attestation_mode = 4;
// Custom site name for display
string site_name = 5;
// Ethereum RPC URL from auth API
string eth_rpc_url = 6;
// KMS contract address from auth API
string kms_contract_address = 7;
}

// The Onboard RPC service.
Expand Down
2 changes: 2 additions & 0 deletions kms/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub(crate) struct KmsConfig {
pub image: ImageConfig,
#[serde(with = "serde_human_bytes")]
pub admin_token_hash: Vec<u8>,
#[serde(default)]
pub site_name: String,
}

impl KmsConfig {
Expand Down
10 changes: 10 additions & 0 deletions kms/src/main_service/upgrade_authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ pub(crate) struct BootResponse {
pub(crate) struct AuthApiInfoResponse {
pub status: String,
pub kms_contract_addr: String,
#[serde(default)]
pub eth_rpc_url: String,
pub gateway_app_id: String,
pub chain_id: u64,
pub app_implementation: String,
Expand All @@ -110,6 +112,7 @@ pub(crate) struct GetInfoResponse {
pub is_dev: bool,
pub gateway_app_id: Option<String>,
pub kms_contract_address: Option<String>,
pub eth_rpc_url: Option<String>,
pub chain_id: Option<u64>,
pub app_implementation: Option<String>,
}
Expand Down Expand Up @@ -161,15 +164,22 @@ impl AuthApi {
AuthApi::Dev { dev } => Ok(GetInfoResponse {
is_dev: true,
kms_contract_address: None,
eth_rpc_url: None,
gateway_app_id: Some(dev.gateway_app_id.clone()),
chain_id: None,
app_implementation: None,
}),
AuthApi::Webhook { webhook } => {
let info: AuthApiInfoResponse = http_get(&webhook.url).await?;
let eth_rpc_url = if info.eth_rpc_url.is_empty() {
None
} else {
Some(info.eth_rpc_url.clone())
};
Ok(GetInfoResponse {
is_dev: false,
kms_contract_address: Some(info.kms_contract_addr.clone()),
eth_rpc_url,
chain_id: Some(info.chain_id),
gateway_app_id: Some(info.gateway_app_id.clone()),
app_implementation: Some(info.app_implementation.clone()),
Expand Down
18 changes: 17 additions & 1 deletion kms/src/onboard_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ impl OnboardRpc for OnboardHandler {
)
.await
.context("Failed to onboard")?;
let k256_pubkey = keys.k256_key.verifying_key().to_sec1_bytes().to_vec();
keys.store(&self.state.config)
.context("Failed to store keys")?;
Ok(OnboardResponse {})
Ok(OnboardResponse { k256_pubkey })
}

async fn get_attestation_info(self) -> Result<AttestationInfoResponse> {
Expand Down Expand Up @@ -136,11 +137,26 @@ impl OnboardRpc for OnboardHandler {
.decode_app_info_ex(false, &info.vm_config)
.context("Failed to decode app info")?;

let (eth_rpc_url, kms_contract_address) = match self.state.config.auth_api.get_info().await
{
Ok(info) => (
info.eth_rpc_url.unwrap_or_default(),
info.kms_contract_address.unwrap_or_default(),
),
Err(err) => {
tracing::warn!("failed to get auth api info: {err}");
(String::new(), String::new())
}
};

Ok(AttestationInfoResponse {
device_id: app_info.device_id,
mr_aggregated: app_info.mr_aggregated.to_vec(),
os_image_hash: app_info.os_image_hash,
attestation_mode,
site_name: self.state.config.site_name.clone(),
eth_rpc_url,
kms_contract_address,
})
}

Expand Down
37 changes: 35 additions & 2 deletions kms/src/www/onboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@
color: #333;
}

.chain-info {
background-color: #f8f4e8;
border: 1px solid #ddc;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}

.chain-info h3 {
margin-top: 0;
color: #444;
}

.loading {
color: #888;
font-style: italic;
Expand All @@ -154,7 +167,7 @@

<body>
<div id="app" class="container">
<h1>dstack KMS Setup</h1>
<h1>{{ siteName || 'dstack KMS Setup' }}</h1>

<div v-if="attestationLoading" class="loading">Loading attestation info...</div>
<div v-else-if="attestationError" class="error">Attestation info: {{ attestationError }}</div>
Expand All @@ -178,6 +191,18 @@ <h3>Attestation Info (for on-chain registration)</h3>
</div>
</div>

<div v-if="attestationInfo && (attestationInfo.eth_rpc_url || attestationInfo.kms_contract_address)" class="chain-info">
<h3>Chain Info</h3>
<div v-if="attestationInfo.eth_rpc_url" class="info-row">
<span class="info-label">ETH RPC URL:</span>
<span class="info-value">{{ attestationInfo.eth_rpc_url }}</span>
</div>
<div v-if="attestationInfo.kms_contract_address" class="info-row">
<span class="info-label">KMS Contract:</span>
<span class="info-value">{{ attestationInfo.kms_contract_address }}</span>
</div>
</div>

<div v-if="!setupFinished">
<div v-if="!selectedOption" class="initial-buttons">
<button @click="selectedOption = 'bootstrap'">Bootstrap</button>
Expand Down Expand Up @@ -261,7 +286,8 @@ <h2>Onboard from an Existing KMS Instance</h2>
setupFinished: false,
attestationInfo: null,
attestationLoading: true,
attestationError: ''
attestationError: '',
siteName: ''
}
},
async mounted() {
Expand All @@ -271,6 +297,10 @@ <h2>Onboard from an Existing KMS Instance</h2>
this.attestationError = data.error;
} else {
this.attestationInfo = data;
if (data.site_name) {
this.siteName = data.site_name;
document.title = data.site_name;
}
}
} catch (err) {
this.attestationError = err.message;
Expand Down Expand Up @@ -310,6 +340,9 @@ <h2>Onboard from an Existing KMS Instance</h2>
if (data.error) throw new Error(data.error);

this.success = 'Onboarding successful!';
this.result = JSON.stringify({
k256Pubkey: '0x' + data.k256_pubkey,
}, null, 2);
this.error = '';
} catch (err) {
this.error = err.message;
Expand Down
Loading