diff --git a/nvflare/app_opt/confidential_computing/aci_authorizer.py b/nvflare/app_opt/confidential_computing/aci_authorizer.py new file mode 100644 index 0000000000..a696eec1d3 --- /dev/null +++ b/nvflare/app_opt/confidential_computing/aci_authorizer.py @@ -0,0 +1,66 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import json +import time + +import jwt +import requests +from jwt import PyJWKClient + +from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer + +ACI_NAMESPACE = "x-ms" +maa_endpoint = "sharedeus2.eus2.attest.azure.net" + + +class ACIAuthorizer(CCAuthorizer): + def __init__(self, retry_count=5, retry_sleep=2): + self.retry_count = retry_count + self.retry_sleep = retry_sleep + + def generate(self): + count = 0 + token = "" + while True: + count = count + 1 + try: + r = requests.post( + "http://localhost:8284/attest/maa", + data=json.dumps({"maa_endpoint": maa_endpoint, "runtime_data": "ewp9"}), + headers={"Content-Type": "application/json"}, + ) + if r.status_code == requests.codes.ok: + token = r.json().get("token") + break + except: + if count > self.retry_count: + break + time.sleep(self.retry_sleep) + return token + + def verify(self, token): + try: + header = jwt.get_unverified_header(token) + alg = header.get("alg") + jwks_client = PyJWKClient(f"https://{maa_endpoint}/certs") + signing_key = jwks_client.get_signing_key_from_jwt(token) + claims = jwt.decode(token, signing_key.key, algorithms=[alg]) + if claims: + return True + except: + return False + return False + + def get_namespace(self) -> str: + return ACI_NAMESPACE diff --git a/nvflare/app_opt/confidential_computing/cc_helper.py b/nvflare/app_opt/confidential_computing/cc_helper.py deleted file mode 100644 index c01d0146e0..0000000000 --- a/nvflare/app_opt/confidential_computing/cc_helper.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# import os.path - -import os -from typing import Dict - -from nv_attestation_sdk.attestation import Attestation, Devices, Environment - -from nvflare.fuel.utils.log_utils import get_obj_logger - - -class VerifierProp: - - DEVICES = "devices" # GPU, CPU, etc. - ENV = "env" - URL = "url" - APPRAISAL_POLICY_FILE = "appraisal_policy_file" - RESULT_POLICY_FILE = "result_policy_file" - - -class Device: - - GPU = "gpu" - CPU = "cpu" - NIC = "nic" - OS = "os" - DPU = "dpu" - - mapping = {GPU: Devices.GPU, CPU: Devices.CPU, NIC: Devices.NIC, OS: Devices.OS, DPU: Devices.DPU} - - -class Env: - - TEST = "test" - LOCAL = "local" - AZURE = "azure" - GCP = "gcp" - - mapping = {TEST: Environment.TEST, LOCAL: Environment.LOCAL, AZURE: Environment.AZURE, GCP: Environment.GCP} - - -class CCHelper(object): - def __init__(self, site_name: str, verifiers: list): - """Create an AttestationHelper instance - - Args: - site_name: name of the site - verifiers: dict that specifies verifiers to be used - """ - self.site_name = site_name - self.verifiers = verifiers - attestation = Attestation() - attestation.set_name(site_name) - self.attestation = attestation - self.token = None - self.logger = get_obj_logger(self) - for v in verifiers: - assert isinstance(v, dict) - url = None - env = None - devices = 0 - appraisal_policy_file = None - result_policy_file = None - for prop, value in v.items(): - if prop == VerifierProp.URL: - url = value - elif prop == VerifierProp.ENV: - env = Env.mapping.get(value) - elif prop == VerifierProp.DEVICES: - dv = Device.mapping.get(value) - if not dv: - raise ValueError(f"invalid device '{value}'") - devices = dv - elif prop == VerifierProp.APPRAISAL_POLICY_FILE: - appraisal_policy_file = value - elif prop == VerifierProp.RESULT_POLICY_FILE: - result_policy_file = value - if not env: - raise ValueError("Environment is not specified for verifier") - if not devices: - raise ValueError("Devices is not specified for verifier") - if url is None: - raise ValueError("Url is not specified for verifier") - if appraisal_policy_file is None: - raise ValueError("Appraisal policy file is not specified for verifier") - if not os.path.exists(appraisal_policy_file): - raise ValueError(f"Appraisal policy file '{appraisal_policy_file}' does not exist") - appraisal_policy = open(appraisal_policy_file, "rt").read().rstrip() - if result_policy_file is None: - raise ValueError("Result policy file is not specified for verifier") - if not os.path.exists(result_policy_file): - raise ValueError(f"Result policy file '{result_policy_file}' does not exist") - self.result_policy = open(result_policy_file, "rt").read().rstrip() - attestation.add_verifier(devices, env, url, appraisal_policy) - - def reset_participant(self, participant_name: str): - pass - - def prepare(self) -> bool: - """Prepare for attestation process - - Returns: error if any - """ - ok = self.attestation.attest() - self.logger.info(f"CC - attest result (is valid?): {ok}") - self.token = self.attestation.get_token(self.site_name) - self.logger.info(f"token {self.token=}") - return True - - def get_token(self): - return self.token - - def validate_participants(self, participants: Dict[str, str]) -> Dict[str, bool]: - """Validate CC policies of specified participants against the requirement policy of the site. - - Args: - participants: dict of participant name => token - - Returns: dict of participant name => bool - - """ - if not participants: - return {} - result = {k: self.attestation.validate_token(self.result_policy, v) for k, v in participants.items()} - self.logger.debug(f"CC - results from validating participants' tokens: {result}") - return result diff --git a/nvflare/app_opt/confidential_computing/gpu_authorizer.py b/nvflare/app_opt/confidential_computing/gpu_authorizer.py index bd55e2a463..1090059380 100644 --- a/nvflare/app_opt/confidential_computing/gpu_authorizer.py +++ b/nvflare/app_opt/confidential_computing/gpu_authorizer.py @@ -13,81 +13,85 @@ # limitations under the License. -from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer - -GPU_NAMESPACE = "x-nv-gpu-" - - -class GPUAuthorizer(CCAuthorizer): - """Note: This is just a fake implementation for GPU authorizer. It will be replaced later - with the real implementation. - - """ - - def __init__(self, verifiers: list) -> None: - """ +import json +import logging +import uuid - Args: - verifiers (list): - each element in this list is a dictionary and the keys of dictionary are - "devices", "env", "url", "appraisal_policy_file" and "result_policy_file." +import jwt +from nv_attestation_sdk import attestation - the values of devices are "gpu" and "cpu" - the values of env are "local" and "test" - currently, valid combination is gpu + local - - url must be an empty string - appraisal_policy_file must point to an existing file - currently supports an empty file only - - result_policy_file must point to an existing file - currently supports the following content only +from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer - .. code-block:: json +GPU_NAMESPACE = "x-nv-gpu" +default_policy = """{ + "version":"1.0", + "authorization-rules":{ + "sub":"NVIDIA-GPU-ATTESTATION", + "secboot":true, + "x-nvidia-gpu-manufacturer":"NVIDIA Corporation", + "x-nvidia-attestation-type":"GPU", + "x-nvidia-attestation-detailed-result":{ + "x-nvidia-gpu-driver-rim-schema-validated":true, + "x-nvidia-gpu-vbios-rim-cert-validated":true, + "x-nvidia-gpu-attestation-report-cert-chain-validated":true, + "x-nvidia-gpu-driver-rim-schema-fetched":true, + "x-nvidia-gpu-attestation-report-parsed":true, + "x-nvidia-gpu-nonce-match":true, + "x-nvidia-gpu-vbios-rim-signature-verified":true, + "x-nvidia-gpu-driver-rim-signature-verified":true, + "x-nvidia-gpu-arch-check":true, + "x-nvidia-gpu-measurements-match":true, + "x-nvidia-gpu-attestation-report-signature-verified":true, + "x-nvidia-gpu-vbios-rim-schema-validated":true, + "x-nvidia-gpu-driver-rim-cert-validated":true, + "x-nvidia-gpu-vbios-rim-schema-fetched":true, + "x-nvidia-gpu-vbios-rim-measurements-available":true + }, + "x-nvidia-gpu-driver-version":"535.104.05", + "hwmodel":"GH100 A01 GSP BROM", + "measres":"comparison-successful", + "x-nvidia-gpu-vbios-version":"96.00.5E.00.02" + } +} +""" - { - "version":"1.0", - "authorization-rules":{ - "x-nv-gpu-available":true, - "x-nv-gpu-attestation-report-available":true, - "x-nv-gpu-info-fetched":true, - "x-nv-gpu-arch-check":true, - "x-nv-gpu-root-cert-available":true, - "x-nv-gpu-cert-chain-verified":true, - "x-nv-gpu-ocsp-cert-chain-verified":true, - "x-nv-gpu-ocsp-signature-verified":true, - "x-nv-gpu-cert-ocsp-nonce-match":true, - "x-nv-gpu-cert-check-complete":true, - "x-nv-gpu-measurement-available":true, - "x-nv-gpu-attestation-report-parsed":true, - "x-nv-gpu-nonce-match":true, - "x-nv-gpu-attestation-report-driver-version-match":true, - "x-nv-gpu-attestation-report-vbios-version-match":true, - "x-nv-gpu-attestation-report-verified":true, - "x-nv-gpu-driver-rim-schema-fetched":true, - "x-nv-gpu-driver-rim-schema-validated":true, - "x-nv-gpu-driver-rim-cert-extracted":true, - "x-nv-gpu-driver-rim-signature-verified":true, - "x-nv-gpu-driver-rim-driver-measurements-available":true, - "x-nv-gpu-driver-vbios-rim-fetched":true, - "x-nv-gpu-vbios-rim-schema-validated":true, - "x-nv-gpu-vbios-rim-cert-extracted":true, - "x-nv-gpu-vbios-rim-signature-verified":true, - "x-nv-gpu-vbios-rim-driver-measurements-available":true, - "x-nv-gpu-vbios-index-conflict":true, - "x-nv-gpu-measurements-match":true - } - } - """ - super().__init__() - self.verifiers = verifiers +class GPUAuthorizer(CCAuthorizer): + def __init__(self, verifier_url="https://nras.attestation.nvidia.com/v1/attest/gpu", policy_file=None): + self._can_generate = True + self.client = attestation.Attestation() + self.client.set_name("nvflare_node") + nonce = uuid.uuid4().hex + uuid.uuid1().hex + self.client.set_nonce(nonce) + if policy_file is None: + self.remote_att_result_policy = default_policy + else: + self.remote_att_result_policy = open(policy_file).read() + self.client.add_verifier(attestation.Devices.GPU, attestation.Environment.REMOTE, verifier_url, "") + self.logger = logging.getLogger(self.__class__.__name__) + + def generate(self): + try: + self.client.attest() + token = self.client.get_token() + except BaseException: + self.can_generate = False + token = "[[],{}]" + return token + + def verify(self, eat_token): + try: + jwt_token = json.loads(eat_token)[1] + claims = jwt.decode(jwt_token.get("REMOTE_GPU_CLAIMS"), options={"verify_signature": False}) + # With claims, we will retrieve the nonce + nonce = claims.get("eat_nonce") + self.client.set_nonce(nonce) + self.client.set_token(name="nvflare_node", eat_token=eat_token) + result = self.client.validate_token(self.remote_att_result_policy) + except BaseException as e: + self.logger.info(f"Token verification failed {e=}") + result = False + return result def get_namespace(self) -> str: return GPU_NAMESPACE - - def generate(self) -> str: - raise NotImplementedError - - def verify(self, token: str) -> bool: - raise NotImplementedError diff --git a/nvflare/app_opt/confidential_computing/mock_authorizer.py b/nvflare/app_opt/confidential_computing/mock_authorizer.py new file mode 100644 index 0000000000..b9e3e99818 --- /dev/null +++ b/nvflare/app_opt/confidential_computing/mock_authorizer.py @@ -0,0 +1,28 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer + +MOCK_NAMESPACE = "x-mock" + + +class MockAuthorizer(CCAuthorizer): + def generate(self): + return "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vc2hhcmVkZXVzMi5ldXMyLmF0dGVzdC5henVyZS5uZXQvY2VydHMiLCJraWQiOiJKMHBBUGRmWFhIcVdXaW1nckg4NTN3TUlkaDUvZkxlMXo2dVNYWVBYQ2EwPSIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDk5NDM0OTYsImlhdCI6MTcwOTkxNDY5NiwiaXNzIjoiaHR0cHM6Ly9zaGFyZWRldXMyLmV1czIuYXR0ZXN0LmF6dXJlLm5ldCIsImp0aSI6IjQ1MGIxNWMxMmRmYzIxMWM5ZWRkOGU4MmFiY2NiZTEzYmMyOTgzZjlhNjAzOTZlMzljZTJmZGIwYjFmNTg1YzEiLCJuYmYiOjE3MDk5MTQ2OTYsInNlY3VyZWJvb3QiOnRydWUsIngtbXMtYXR0ZXN0YXRpb24tdHlwZSI6ImF6dXJldm0iLCJ4LW1zLWF6dXJldm0tYXR0ZXN0YXRpb24tcHJvdG9jb2wtdmVyIjoiMi4wIiwieC1tcy1henVyZXZtLWF0dGVzdGVkLXBjcnMiOlswLDEsMiwzLDQsNSw2LDddLCJ4LW1zLWF6dXJldm0tYm9vdGRlYnVnLWVuYWJsZWQiOmZhbHNlLCJ4LW1zLWF6dXJldm0tZGJ2YWxpZGF0ZWQiOnRydWUsIngtbXMtYXp1cmV2bS1kYnh2YWxpZGF0ZWQiOnRydWUsIngtbXMtYXp1cmV2bS1kZWJ1Z2dlcnNkaXNhYmxlZCI6dHJ1ZSwiwC1tcy1henVyZXZtLWRlZmF1bHQtc2VjdXJlYm9vdGtleXN2YWxpZGF0ZWQiOnRydWUsIngtbXMtYXp1cmV2bS1lbGFtLWVuYWJsZWQiOmZhbHNlLCJ4LW1zLWF6dXJldm0tZmxpZ2h0c2lnbmluZy1lbmFibGVkIjpmYWxzZSwieC1tcy1henVyZXZtLWh2Y2ktcG9saWN5IjowLCJ4LW1zLWF6dXJldm0taHlwZXJ2aXNvcmRlYnVnLWVuYWJsZWQiOmZhbHNlLCJ4LW1zLWF6dXJldm0taXMtd2luZG93cyI6ZmFsc2UsIngtbXMtYXp1cmV2bS1rZXJuZWxkZWJ1Zy1lbmFibGVkIjpmYWxzZSwieC1tcy1henVyZXZtLW9zYnVpbGQiOiJOb3RBcHBsaWNhdGlvbiIsIngtbXMtYXp1cmV2bS1vc2Rpc3RybyI6IlVidW50dSIsIngtbXMtYXp1cmV2bS1vc3R5cGUiOiJMaW51eCIsIngtbXMtYXp1cmV2bS1vc3ZlcnNpb24tbWFqb3IiOjIyLCJ4LW1zLWF6dXJldm0tb3N2ZXJzaW9uLW1pbm9yIjo0LCJ4LW1zLWF6dXJldm0tc2lnbmluZ2Rpc2FibGVkIjp0cnVlLCJ4LW1zLWF6dXJldm0tdGVzdHNpZ25pbmctZW5hYmxlZCI6ZmFsc2UsIngtbXMtYXp1cmV2bS12bWlkIjoiQzRGRkM0QjMtOUFERi00MEQxLThDQ0MtMTAxMUUxQkNDREIwIiwieC1tcy1pc29sYXRpb24tdGVlIjp7IngtbXMtYXR0ZXN0YXRpb24tdHlwZSI6InNldnNucHZtIiwieC1tcy1jb21wbGlhbmNlLXN0YXR1cyI6ImF6dXJlLWNvbXBsaWFudC1jdm0iLCJ4LW1zLXJ1bnRpbWUiOnsia2V5cyI6W3siZSI6IkFRQUIiLCJrZXlfb3BzIjpbInNpZ24iXSwia2lkIjoiSENMQWtQdWIiLCJrdHkiOiJSU0EiLCJuIjoiNEFQakF3QUFwQjE4cnc0bDh4Y0pmQXNpT1pJb1lSdGpYLVdOM0RhdVZ2cWlOSGlNU2RFaFNtWW9CQnVTcUVfa2pHblpZOFRWb2RSRkdJNWtFalR4NmhBZFM1OHIzY2R6OEtYMERmOHZERjF3Y2NjVW52SHJGY3FnRnNGVWs0UHJZZko2eU9nell2bmhvdWFSQlZ4dmZ5bEMwZWZhTUNUUkdES2pZSzhPVV9RcWxFeGIzY19neEJZWGlSZ3dBYWFaZUd4eFNId3U0a3lwZ3hwMlhjWXlHLVU3a3FHc01VWnlkZmM0eUxiS1BQcl9zMUJYUTNSbkFtdTQtblhkdVRmcWlWX2gxbGN4V29fYVhSWkFNdG9hTnVkclkyMVZVV3AzVW5xeFFtdGVGcXplWWpEQm8ta2Fjel9iNG9Gem5OM29aSlVUZmJnb09sTzJzbDc3U05Xa0x3In0seyJlIjoiQVFBQiIsImtleV9vcHMiOlsiZW5jcnlwdCJdLCJraWQiOiJIQ0xFa1B1YiIsImt0eSI6IlJTQSIsIm4iOiJ3c3lxREFBQloySHlTd08wTDFaWXZwVkhOVFVpdXpPaGcyb3Bfa1VpckNqM1M5bEtpT051YmU4RWFsSFVBcUh6bGdEcF84dHdBdGRONGNOUzZUSWdITG94TXpaZUFpaWU1OGd2VGtYdzVqMThmVUY4UEVvT2NXVERFSmRMVXIxWnBEdTRSdUZXbDdkZHNIdFBJRDVqcmt0R21FajBCZVp5NzZWVGFUYU1iamhGRmphUGNCT01fOWNaVHJYdFduSG80WTF1TG53VUJPRzA3T1hrUmlTSjBHZ3phVFhvaFVzVGE4X0w5NDJfeml5QU16STliUnJmUV9JMXY0SFV0M1YzS3laaUZJS3B4X2hWQnZZZUhwcTZBbXlYRTExS0VXek5HMTJaZVNkbzBzMUhudFNidTQ3dUktYklIdnBLaVVQSzBYSGZWbDQwVnNwenJ2MjlqekppR1EifV0sInVzZXItZGF0YSI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwidm0tY29uZmlndXJhdGlvbiI6eyJjb25zb2xlLWVuYWJsZWQiOnRydWUsInJvb3QtY2VydC10aHVtYnByaW50IjoiNm5aWm5ZYUpjNEtxVVpfeXZBLW11Y0ZkWU5vdXZsUG5JVG5OTVhzSGwtMCIsInNlY3VyZS1ib290Ijp0cnVlLCJ0cG0tZW5hYmxlZCI6dHJ1ZSwidHBtLXBlcnNpc3RlZCI6dHJ1ZSwidm1VbmlxdWVJZCI6IkM0RkZDNEIzLTlBREYtNDBEMS04Q0NDLTEwMTFFMUJDQ0RCMCJ9fSwieC1tcy1zZXZzbnB2bS1hdXRob3JrZXlkaWdlc3QiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ4LW1zLXNldnNucHZtLWJvb3Rsb2FkZXItc3ZuIjo3LCJ4LW1zLXNldnNucHZtLWZhbWlseUlkIjoiMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ4LW1zLXNldnNucHZtLWd1ZXN0c3ZuIjo2NTU0MSwieC1tcy1zZXZzbnB2bS1ob3N0ZGF0YSI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ4LW1zLXNldnNucHZtLWlka2V5ZGlnZXN0IjoiMDM1NjIxNTg4MmE4MjUyNzlhODViMzAwYjBiNzQyOTMxZDExM2JmN2UzMmRkZTJlNTBmZmRlN2VjNzQzY2E0OTFlY2RkN2YzMzZkYzI4YTZlMGIyYmI1N2FmN2E0NGEzIiwieC1tcy1zZXZzbnB2bS1pbWFnZUlkIjoiMDIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ4LW1zLXNldnNucHZtLWlzLWRlYnVnZ2FibGUiOmZhbHNlLCJ4LW1zLXNldnNucHZtLWxhdW5jaG1lYXN1cmVtZW50IjoiN2M0MjA4NjE0ZDMyNzYzMDI4M2VkOGFhNjUyOTcxZjNkYzI0YzU0NmY2ZWUxMzBkMzJlNGUzYjg0ZjFhYTFmNWVmMmQyMTAxMmQwZmRlMDU2ZDhmOTAwYzM5MmM3NzJjIiwieC1tcy1zZXZzbnB2bS1taWNyb2NvZGUtc3ZuIjo2MiwieC1tcy1zZXZzbnB2bS1taWdyYXRpb24tYWxsb3dlZCI6ZmFsc2UsIngtbXMtc2V2c25wdm0tcmVwb3J0ZGF0YSI6IjU0ODE0ZTlhNjQ0N2JjMWM5MGE5YTExNmYxYjRjYjdlMDU5ZTYzMzQzNDgwY2Q4N2FmMjcxZjc5MjdjOThlMTMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwieC1tcy1zZXZzbnB2bS1yZXBvcnRpZCI6ImRhNjU0YWY3NGZiODIzZjJiM2E1ODMzMzVmOGYzYmUzMmY2ODkwYTdkYjIzMzUyM2RkOWUwNTRmNDQzMDU2OWYiLCJ4LW1zLXNldnNucHZtLXNtdC1hbGxvd2VkIjp0cnVlLCJ4LW1zLXNldnNucHZtLXNucGZ3LXN2biI6MTUsIngtbXMtc2V2c25wdm0tdGVlLXN2biI6MCwieC1tcy1zZXZzbnB2bS12bXBsIjowfSwieC1tcy1wb2xpY3ktaGFzaCI6IndtOW1IbHZUVTgyZThVcW9PeTFZajFGQlJTTmtmZTk5LTY5SVlEcTllV3MiLCJ4LW1zLXJ1bnRpbWUiOnsiY2xpZW50LXBheWxvYWQiOnsibm9uY2UiOiIifSwia2V5cyI6W3siZSI6IkFRQUIiLCJrZXlfb3BzIjpbImVuY3J5cHQiXSwia2lkIjoiVHBtRXBoZW1lcmFsRW5jcnlwdGlvbktleSIsImt0eSI6IlJTQSIsIm4iOiJ6eHN2bWdBQV9rRlBKMjZzYnRfdFhVUDhqcHowMk50YnhRU2hPd0lxa1h6U1hxUGJmV1Vxb1hpUm9idzJqTC01ZTNiQU1LU3J3cmpMU09DcnNtbjdPMnZxVmNBMW9Ucl9ObFE3NEpMMnlBanZVWGFId0dBZVVzbkNfWm51UXltdzNFMXJ4NnNCUUxZWFcwMTRiQjNKX01feFBDaGZfQk9NTGdNTlRzbTNwbGh0eTVNRWJ5OWJBSXp6a2ZPNjZhblhWUWxUVE1xQk43SkJVeU5QSFBWQU82V3N6bzl6YnI1aWhlUUxOZDZLZXNEMHU1VXBYaVo4UU5zUng2cXJCUS12TkVsVXQ2cXRvbC1xVzh4TkdvckxBWlhhZ0FyRVpGTE9aOEZWa0hQWGlMM0wwZ253eDBvb0l5UG5pbk5WY2dLanFYR3pOdnB1ejJGTmNuQU9uS2pKZFEifV19LCJ4LW1zLXZlciI6IjEuMCJ9.COvydLJUjR5voFyG-AUJlEp9fyJNBtbptkgq9p-KNkMlFCorY87VZPDrmITH4gYM5YpYuDk370P81hvd2Pw9COZB1-t9VSaWMJzcyL-T43Sh8nSGNO13kOqDQiHss1907kBiFy2jWngaoxuJvO4BSNFkxL9bsCsEZVSpMDmO9zZkp1Ja7sp-Cptm9rwf5JTfKuWZ4cazn2hUkWbQHBg51b8AeryNzU-35oEhGCIPYqryXv5SY32PB9s-lwh6l7K3t768P817XKF3Szip0TZpgIMoM0GU4oNOnjnFZ3u8DnvuyEim-pCZgP7qpQmJI4lrgI6Sn-jxqTg8q0FUmAkcnQ" + + def verify(self, token): + return True + + def get_namespace(self) -> str: + return MOCK_NAMESPACE diff --git a/nvflare/app_opt/confidential_computing/snp_authorizer.py b/nvflare/app_opt/confidential_computing/snp_authorizer.py new file mode 100644 index 0000000000..ba0650b304 --- /dev/null +++ b/nvflare/app_opt/confidential_computing/snp_authorizer.py @@ -0,0 +1,59 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import logging +import os +import subprocess +import uuid + +from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer + +SNP_NAMESPACE = "x-snp" + + +class SNPAuthorizer(CCAuthorizer): + def __init__(self): + super().__init__() + self.logger = logging.getLogger(self.__class__.__name__) + + def generate(self): + cmd = ["sudo", "snpguest", "report", "report.bin", "request.bin"] + with open("request.bin", "wb") as request_file: + request_file.write(b"\x01" * 64) + _ = subprocess.run(cmd, capture_output=True) + with open("report.bin", "rb") as report_file: + token = base64.b64encode(report_file.read()) + return token + + def verify(self, token): + try: + report_bin = base64.b64decode(token) + tmp_bin_file = uuid.uuid4().hex + with open(tmp_bin_file, "wb") as report_file: + report_file.write(report_bin) + cmd = ["snpguest", "verify", "attestation", "./cert", tmp_bin_file] + cp = subprocess.run(cmd, capture_output=True) + if cp.returncode != 0: + return False + return True + except Exception as e: + self.logger.info(f"Token verification failed {e=}") + return False + finally: + if os.path.exists(tmp_bin_file): + os.remove(tmp_bin_file) + + def get_namespace(self) -> str: + return SNP_NAMESPACE diff --git a/nvflare/app_opt/confidential_computing/tdx_authorizer.py b/nvflare/app_opt/confidential_computing/tdx_authorizer.py index 21bff9035e..c8a3bb5756 100644 --- a/nvflare/app_opt/confidential_computing/tdx_authorizer.py +++ b/nvflare/app_opt/confidential_computing/tdx_authorizer.py @@ -17,7 +17,7 @@ from nvflare.app_opt.confidential_computing.cc_authorizer import CCAuthorizer -TDX_NAMESPACE = "tdx_" +TDX_NAMESPACE = "x-tdx" TDX_CLI_CONFIG = "config.json" TOKEN_FILE = "token.txt" VERIFY_FILE = "verify.txt"