diff --git a/vmm/rpc/proto/vmm_rpc.proto b/vmm/rpc/proto/vmm_rpc.proto index 1d3c89ee5..d0adb9364 100644 --- a/vmm/rpc/proto/vmm_rpc.proto +++ b/vmm/rpc/proto/vmm_rpc.proto @@ -69,6 +69,10 @@ message VmConfiguration { bool pin_numa = 12; // Gpu config GpuConfig gpus = 13; + // KMS URLs + repeated string kms_urls = 14; + // Gateway URLs + repeated string gateway_urls = 15; } message GpuConfig { diff --git a/vmm/src/app.rs b/vmm/src/app.rs index accaaf171..2cbf83366 100644 --- a/vmm/src/app.rs +++ b/vmm/src/app.rs @@ -52,6 +52,10 @@ pub struct Manifest { pub pin_numa: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub gpus: Option, + #[serde(default)] + pub kms_urls: Vec, + #[serde(default)] + pub gateway_urls: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -460,6 +464,16 @@ impl App { let image_path = cfg.image_path.join(&manifest.image); let image = Image::load(image_path).context("Failed to load image info")?; let img_ver = image.info.version_tuple().unwrap_or((0, 0, 0)); + let kms_urls = if manifest.kms_urls.is_empty() { + cfg.cvm.kms_urls.clone() + } else { + manifest.kms_urls.clone() + }; + let gateway_urls = if manifest.gateway_urls.is_empty() { + cfg.cvm.gateway_urls.clone() + } else { + manifest.gateway_urls.clone() + }; let sys_config = if img_ver >= (0, 5, 0) { let vm_config = serde_json::to_string(&json!({ "os_image_hash": image.digest.unwrap_or_default(), @@ -467,8 +481,8 @@ impl App { "memory_size": manifest.memory as u64 * 1024 * 1024, }))?; json!({ - "kms_urls": cfg.cvm.kms_urls, - "gateway_urls": cfg.cvm.gateway_urls, + "kms_urls": kms_urls, + "gateway_urls": gateway_urls, "pccs_url": cfg.cvm.pccs_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), @@ -476,8 +490,8 @@ impl App { }) } else if img_ver >= (0, 4, 2) { json!({ - "kms_urls": cfg.cvm.kms_urls, - "gateway_urls": cfg.cvm.gateway_urls, + "kms_urls": kms_urls, + "gateway_urls": gateway_urls, "pccs_url": cfg.cvm.pccs_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), @@ -490,8 +504,8 @@ impl App { .context("Rootfs hash not found in image info")?; json!({ "rootfs_hash": rootfs_hash, - "kms_urls": cfg.cvm.kms_urls, - "tproxy_urls": cfg.cvm.gateway_urls, + "kms_urls": kms_urls, + "tproxy_urls": gateway_urls, "pccs_url": cfg.cvm.pccs_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), @@ -504,8 +518,8 @@ impl App { .context("Rootfs hash not found in image info")?; json!({ "rootfs_hash": rootfs_hash, - "kms_url": cfg.cvm.kms_urls.first(), - "tproxy_url": cfg.cvm.gateway_urls.first(), + "kms_url": kms_urls.first(), + "tproxy_url": gateway_urls.first(), "pccs_url": cfg.cvm.pccs_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), diff --git a/vmm/src/app/qemu.rs b/vmm/src/app/qemu.rs index 8eb150353..6a1b173bc 100644 --- a/vmm/src/app/qemu.rs +++ b/vmm/src/app/qemu.rs @@ -98,6 +98,16 @@ impl VmInfo { configuration: if brief { None } else { + let vm_config = workdir.manifest(); + let kms_urls = vm_config + .as_ref() + .map(|c| c.kms_urls.clone()) + .unwrap_or_default(); + let gateway_urls = vm_config + .as_ref() + .map(|c| c.gateway_urls.clone()) + .unwrap_or_default(); + Some(pb::VmConfiguration { name: self.manifest.name.clone(), image: self.manifest.image.clone(), @@ -135,6 +145,8 @@ impl VmInfo { }) .collect(), }), + kms_urls, + gateway_urls, }) }, app_url: self diff --git a/vmm/src/main_service.rs b/vmm/src/main_service.rs index 8cb53db00..e369e9004 100644 --- a/vmm/src/main_service.rs +++ b/vmm/src/main_service.rs @@ -164,6 +164,8 @@ impl VmmRpc for RpcHandler { .hugepages(request.hugepages) .pin_numa(request.pin_numa) .gpus(gpus) + .kms_urls(request.kms_urls.clone()) + .gateway_urls(request.gateway_urls.clone()) .build(); let vm_work_dir = self.app.work_dir(&id); vm_work_dir diff --git a/vmm/src/vmm-cli.py b/vmm/src/vmm-cli.py index 5a1096499..37fa0c7a3 100755 --- a/vmm/src/vmm-cli.py +++ b/vmm/src/vmm-cli.py @@ -12,6 +12,7 @@ import socket import http.client import urllib.parse +import ssl from eth_keys import keys from eth_utils import keccak @@ -162,7 +163,11 @@ def request(self, method: str, path: str, headers: Dict[str, str] = None, conn = UnixSocketHTTPConnection(self.uds_path) else: if self.is_https: - conn = http.client.HTTPSConnection(self.host) + # TODO: we should verify TLS cert. + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + conn = http.client.HTTPSConnection(self.host, context=context) else: conn = http.client.HTTPConnection(self.host) @@ -313,9 +318,18 @@ def list_images(self) -> List[Dict]: response = self.rpc_call('ListImages') return response['images'] - def get_app_env_encrypt_pub_key(self, app_id: str) -> Dict: + def get_app_env_encrypt_pub_key(self, app_id: str, kms_url: Optional[str] = None) -> Dict: """Get the encryption public key for the specified application ID""" - response = self.rpc_call('GetAppEnvEncryptPubKey', {'app_id': app_id}) + if kms_url: + client = VmmClient(kms_url) + path = f"/prpc/GetAppEnvEncryptPubKey?json" + status, response = client.request( + 'POST', path, headers={ + 'Content-Type': 'application/json' + }, body={'app_id': app_id}) + print(f"Getting encryption public key for {app_id} from {kms_url}") + else: + response = self.rpc_call('GetAppEnvEncryptPubKey', {'app_id': app_id}) # Verify the signature if available if 'signature' not in response: @@ -443,6 +457,8 @@ def create_vm(self, name: str, image: str, compose_file: str, gpus: Optional[List[str]] = None, pin_numa: bool = False, hugepages: bool = False, + kms_urls: Optional[List[str]] = None, + gateway_urls: Optional[List[str]] = None, ) -> None: """Create a new VM""" # Read and validate compose file @@ -471,11 +487,15 @@ def create_vm(self, name: str, image: str, compose_file: str, "attach_mode": "listed", "gpus": [{"slot": gpu} for gpu in gpus or []] } + if kms_urls: + params["kms_urls"] = kms_urls + if gateway_urls: + params["gateway_urls"] = gateway_urls app_id = app_id or self.calc_app_id(compose_content) print(f"App ID: {app_id}") if envs: - encrypt_pubkey = self.get_app_env_encrypt_pub_key(app_id) + encrypt_pubkey = self.get_app_env_encrypt_pub_key(app_id, kms_urls[0] if kms_urls else None) print( f"Encrypting environment variables with key: {encrypt_pubkey}") envs_list = [{"key": k, "value": v} for k, v in envs.items()] @@ -484,7 +504,7 @@ def create_vm(self, name: str, image: str, compose_file: str, print(f"Created VM with ID: {response.get('id')}") return response.get('id') - def update_vm_env(self, vm_id: str, envs: Dict[str, str]) -> None: + def update_vm_env(self, vm_id: str, envs: Dict[str, str], kms_urls: Optional[List[str]] = None) -> None: """Update environment variables for a VM""" # First get the VM info to retrieve the app_id vm_info_response = self.rpc_call('GetInfo', {'id': vm_id}) @@ -496,11 +516,7 @@ def update_vm_env(self, vm_id: str, envs: Dict[str, str]) -> None: print(f"Retrieved app ID: {app_id}") # Now get the encryption key for the app - response = self.rpc_call('GetAppEnvEncryptPubKey', {'app_id': app_id}) - if 'public_key' not in response: - raise Exception("Failed to get encryption public key for the VM") - - encrypt_pubkey = response['public_key'] + encrypt_pubkey = self.get_app_env_encrypt_pub_key(app_id, kms_urls[0] if kms_urls else None) print(f"Encrypting environment variables with key: {encrypt_pubkey}") envs_list = [{"key": k, "value": v} for k, v in envs.items()] encrypted_env = encrypt_env(envs_list, encrypt_pubkey) @@ -815,6 +831,10 @@ def main(): help='Pin VM to specific NUMA node') deploy_parser.add_argument('--hugepages', action='store_true', help='Enable hugepages for the VM') + deploy_parser.add_argument('--kms-url', action='append', type=str, + help='KMS URL') + deploy_parser.add_argument('--gateway-url', action='append', type=str, + help='Gateway URL') # Images command _images_parser = subparsers.add_parser( @@ -829,6 +849,9 @@ def main(): update_env_parser.add_argument('vm_id', help='VM ID to update') update_env_parser.add_argument( '--env-file', required=True, help='File with environment variables to encrypt') + update_env_parser.add_argument( + '--kms-url', action='append', type=str, + help='KMS URL') # Whitelist command kms_parser = subparsers.add_parser( @@ -891,7 +914,9 @@ def main(): app_id=args.app_id, gpus=args.gpu, hugepages=args.hugepages, - pin_numa=args.pin_numa + pin_numa=args.pin_numa, + kms_urls=args.kms_url, + gateway_urls=args.gateway_url ) elif args.command == 'lsimage': images = cli.list_images() @@ -901,7 +926,7 @@ def main(): elif args.command == 'lsgpu': cli.list_gpus() elif args.command == 'update-env': - cli.update_vm_env(args.vm_id, parse_env_file(args.env_file)) + cli.update_vm_env(args.vm_id, parse_env_file(args.env_file), kms_urls=args.kms_url) elif args.command == 'kms': if not args.kms_action: kms_parser.print_help()