Skip to content
Merged
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
3 changes: 2 additions & 1 deletion scripts/aws/conf/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@
"identity_token_expires_after_seconds": 86400,
"refresh_token_expires_after_seconds": 2592000,
"refresh_identity_token_after_seconds": 3600,
"operator_type": "private"
"operator_type": "private",
"uid_instance_id_prefix": "unknown"
}
21 changes: 19 additions & 2 deletions scripts/aws/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_meta_url(cls) -> str:
return f"http://{cls.AWS_METADATA}/latest/dynamic/instance-identity/document"


class EC2EntryPoint(ConfidentialCompute):
class EC2(ConfidentialCompute):

def __init__(self):
super().__init__()
Expand All @@ -77,6 +77,21 @@ def __get_current_region(self) -> str:
except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch region: {e}")

def __get_ec2_instance_info(self) -> tuple[str, str]:
"""Fetches the instance ID, and AMI ID from EC2 metadata."""
token = self.__get_aws_token()
headers = {"X-aws-ec2-metadata-token": token}
try:
response = requests.get(AuxiliaryConfig.get_meta_url(), headers=headers, timeout=2)
response.raise_for_status()
data = response.json()
instance_id = data["instanceId"]
ami_id = data["imageId"]
return instance_id, ami_id

except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch instance info: {e}")

def __validate_aws_specific_config(self):
if "enclave_memory_mb" in self.configs or "enclave_cpu_count" in self.configs:
max_capacity = self.__get_max_capacity()
Expand Down Expand Up @@ -104,6 +119,8 @@ def add_defaults(configs: Dict[str, any]) -> AWSConfidentialComputeConfig:
client = boto3.client("secretsmanager", region_name=region)
try:
self.configs = add_defaults(json.loads(client.get_secret_value(SecretId=secret_identifier)["SecretString"]))
instance_id, ami_id = self.__get_ec2_instance_info()
self.configs.setdefault("uid_instance_id_prefix", self.get_uid_instance_id(identifier=instance_id,version=ami_id))
self.__validate_aws_specific_config()
except json.JSONDecodeError as e:
raise OperatorKeyNotFoundError(self.__class__.__name__, f"Can not parse secret {secret_identifier} in {region}")
Expand Down Expand Up @@ -293,7 +310,7 @@ def __kill_auxiliaries(self) -> None:
args = parser.parse_args()

try:
ec2 = EC2EntryPoint()
ec2 = EC2()
if args.operation == "stop":
ec2.cleanup()
else:
Expand Down
2 changes: 2 additions & 0 deletions scripts/azure-aks/deployment/operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ spec:
value: OPERATOR_KEY_SECRET_NAME_PLACEHOLDER
- name: DEPLOYMENT_ENVIRONMENT
value: DEPLOYMENT_ENVIRONMENT_PLACEHOLDER
- name: IMAGE_NAME
value: IMAGE_PLACEHOLDER
ports:
- containerPort: 8080
protocol: TCP
Expand Down
4 changes: 2 additions & 2 deletions scripts/azure-cc/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ COPY ./conf/*.xml /app/conf/
RUN tar xzvf /app/static.tar.gz --no-same-owner --no-same-permissions && \
rm -f /app/static.tar.gz

COPY ./azureEntryPoint.py /app
COPY ./azr.py /app
COPY ./confidential_compute.py /app
RUN chmod a+x /app/*.py

Expand All @@ -51,4 +51,4 @@ RUN adduser -D uid2-operator && \
USER uid2-operator

# Run the Python entry point
CMD python3 /app/azureEntryPoint.py
CMD python3 /app/azr.py
62 changes: 35 additions & 27 deletions scripts/azure-cc/azureEntryPoint.py → scripts/azure-cc/azr.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import shutil
import requests
import logging
from datetime import datetime
from confidential_compute import ConfidentialCompute, ConfigurationMissingError, OperatorKeyPermissionError, OperatorKeyNotFoundError, ConfidentialComputeStartupError
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential, CredentialUnavailableError
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

class AzureEntryPoint(ConfidentialCompute):

class AZR(ConfidentialCompute):
kv_name = os.getenv("VAULT_NAME")
secret_name = os.getenv("OPERATOR_KEY_SECRET_NAME")
env_name = os.getenv("DEPLOYMENT_ENVIRONMENT")
Expand All @@ -31,79 +31,87 @@ def __init__(self):

def __check_env_variables(self):
# Check essential env variables
if AzureEntryPoint.kv_name is None:
if AZR.kv_name is None:
raise ConfigurationMissingError(self.__class__.__name__, ["VAULT_NAME"])
if AzureEntryPoint.secret_name is None:
if AZR.secret_name is None:
raise ConfigurationMissingError(self.__class__.__name__, ["OPERATOR_KEY_SECRET_NAME"])
if AzureEntryPoint.env_name is None:
if AZR.env_name is None:
raise ConfigurationMissingError(self.__class__.__name__, ["DEPLOYMENT_ENVIRONMENT"])
logging.info("Environment variables validation success")

def __create_final_config(self):
TARGET_CONFIG = f"/app/conf/{AzureEntryPoint.env_name}-uid2-config.json"
TARGET_CONFIG = f"/app/conf/{AZR.env_name}-uid2-config.json"
if not os.path.isfile(TARGET_CONFIG):
logging.error(f"Unrecognized config {TARGET_CONFIG}")
sys.exit(1)

logging.info(f"-- copying {TARGET_CONFIG} to {AzureEntryPoint.FINAL_CONFIG}")
logging.info(f"-- copying {TARGET_CONFIG} to {AZR.FINAL_CONFIG}")
try:
shutil.copy(TARGET_CONFIG, AzureEntryPoint.FINAL_CONFIG)
shutil.copy(TARGET_CONFIG, AZR.FINAL_CONFIG)
except IOError as e:
logging.error(f"Failed to create {AzureEntryPoint.FINAL_CONFIG} with error: {e}")
logging.error(f"Failed to create {AZR.FINAL_CONFIG} with error: {e}")
sys.exit(1)

logging.info(f"-- replacing URLs by {self.configs["core_base_url"]} and {self.configs["optout_base_url"]}")
with open(AzureEntryPoint.FINAL_CONFIG, "r") as file:
with open(AZR.FINAL_CONFIG, "r") as file:
config = file.read()

config = config.replace("https://core.uidapi.com", self.configs["core_base_url"])
config = config.replace("https://optout.uidapi.com", self.configs["optout_base_url"])
with open(AzureEntryPoint.FINAL_CONFIG, "w") as file:
with open(AZR.FINAL_CONFIG, "w") as file:
file.write(config)

with open(AzureEntryPoint.FINAL_CONFIG, "r") as file:
with open(AZR.FINAL_CONFIG, "r") as file:
logging.info(file.read())

def __set_operator_key(self):
try:
credential = DefaultAzureCredential()
kv_URL = f"https://{AzureEntryPoint.kv_name}.vault.azure.net"
kv_URL = f"https://{AZR.kv_name}.vault.azure.net"
secret_client = SecretClient(vault_url=kv_URL, credential=credential)
secret = secret_client.get_secret(AzureEntryPoint.secret_name)
secret = secret_client.get_secret(AZR.secret_name)
self.configs["operator_key"] = secret.value

except (CredentialUnavailableError, ClientAuthenticationError) as auth_error:
logging.error(f"Read operator key, authentication error: {auth_error}")
raise OperatorKeyPermissionError(self.__class__.__name__, str(auth_error))
except ResourceNotFoundError as not_found_error:
logging.error(f"Read operator key, secret not found: {AzureEntryPoint.secret_name}. Error: {not_found_error}")
logging.error(f"Read operator key, secret not found: {AZR.secret_name}. Error: {not_found_error}")
raise OperatorKeyNotFoundError(self.__class__.__name__, str(not_found_error))

def __get_azure_image_info(self) -> str:
"""
Fetches Image version from non-modifiable environment variable.
"""
try:
return os.getenv("IMAGE_NAME")
except Exception as e:
raise RuntimeError(f"Failed to fetch Azure image info: {e}")


def _set_confidential_config(self, secret_identifier: str = None):
"""Builds and sets ConfidentialComputeConfig"""
self.configs["skip_validations"] = os.getenv("SKIP_VALIDATIONS", "false").lower() == "true"
self.configs["debug_mode"] = os.getenv("DEBUG_MODE", "false").lower() == "true"
self.configs["environment"] = AzureEntryPoint.env_name
self.configs["core_base_url"] = os.getenv("CORE_BASE_URL") if os.getenv("CORE_BASE_URL") and AzureEntryPoint.env_name == "integ" else AzureEntryPoint.default_core_endpoint
self.configs["optout_base_url"] = os.getenv("OPTOUT_BASE_URL") if os.getenv("OPTOUT_BASE_URL") and AzureEntryPoint.env_name == "integ" else AzureEntryPoint.default_optout_endpoint
self.configs["environment"] = AZR.env_name
self.configs["core_base_url"] = os.getenv("CORE_BASE_URL") if os.getenv("CORE_BASE_URL") and AZR.env_name == "integ" else AZR.default_core_endpoint
self.configs["optout_base_url"] = os.getenv("OPTOUT_BASE_URL") if os.getenv("OPTOUT_BASE_URL") and AZR.env_name == "integ" else AZR.default_optout_endpoint
image_version = self.__get_azure_image_info()
self.configs["uid_instance_id_prefix"] = self.get_uid_instance_id(identifier=datetime.now().strftime("%H:%M:%S"), version=image_version)
self.__set_operator_key()

def __run_operator(self):

# Start the operator
os.environ["azure_vault_name"] = AzureEntryPoint.kv_name
os.environ["azure_secret_name"] = AzureEntryPoint.secret_name

os.environ["azure_vault_name"] = AZR.kv_name
os.environ["azure_secret_name"] = AZR.secret_name
java_command = [
"java",
"-XX:MaxRAMPercentage=95", "-XX:-UseCompressedOops", "-XX:+PrintFlagsFinal",
"-Djava.security.egd=file:/dev/./urandom",
"-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory",
"-Dlogback.configurationFile=/app/conf/logback.xml",
f"-Dvertx-config-path={AzureEntryPoint.FINAL_CONFIG}",
f"-Dvertx-config-path={AZR.FINAL_CONFIG}",
"-jar",
f"{AzureEntryPoint.jar_name}-{AzureEntryPoint.jar_version}.jar"
f"{AZR.jar_name}-{AZR.jar_version}.jar"
]
logging.info("-- starting java operator application")
self.run_command(java_command, separate_process=False)
Expand Down Expand Up @@ -152,9 +160,9 @@ def _setup_auxiliaries(self) -> None:
if __name__ == "__main__":

logging.basicConfig(level=logging.INFO)
logging.info("Start AzureEntryPoint")
logging.info("Start Azure")
try:
operator = AzureEntryPoint()
operator = AZR()
operator.run_compute()
except ConfidentialComputeStartupError as e:
logging.error(f"Failed starting up Azure Confidential Compute. Please checks the logs for errors and retry {e}", exc_info=True)
Expand Down
3 changes: 2 additions & 1 deletion scripts/azure-cc/conf/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
"sharing_token_expiry_seconds": 2592000,
"validate_service_links": false,
"operator_type": "private",
"enable_remote_config": false
"enable_remote_config": false,
"uid_instance_id_prefix": "unknown"
}
4 changes: 4 additions & 0 deletions scripts/azure-cc/deployment/operator.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
}
},
"environmentVariables": [
{
"name": "IMAGE_NAME",
"value": "IMAGE_PLACEHOLDER"
},
{
"name": "VAULT_NAME",
"value": "[parameters('vaultName')]"
Expand Down
18 changes: 14 additions & 4 deletions scripts/confidential_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@
from typing import TypedDict, NotRequired, get_type_hints
import subprocess
import logging
import hashlib

class ConfidentialComputeConfig(TypedDict):
operator_key: str
core_base_url: str
optout_base_url: str
environment: str
uid_instance_id_prefix: str
skip_validations: NotRequired[bool]
debug_mode: NotRequired[bool]

class ConfidentialComputeStartupError(Exception):
def __init__(self, error_name, provider, extra_message=None):
urls = {
"EC2EntryPoint": "https://unifiedid.com/docs/guides/operator-guide-aws-marketplace#uid2-operator-error-codes",
"AzureEntryPoint": "https://unifiedid.com/docs/guides/operator-guide-azure-enclave#uid2-operator-error-codes",
"GCPEntryPoint": "https://unifiedid.com/docs/guides/operator-private-gcp-confidential-space#uid2-operator-error-codes",
"EC2": "https://unifiedid.com/docs/guides/operator-guide-aws-marketplace#uid2-operator-error-codes",
"AZR": "https://unifiedid.com/docs/guides/operator-guide-azure-enclave#uid2-operator-error-codes",
"GCP": "https://unifiedid.com/docs/guides/operator-private-gcp-confidential-space#uid2-operator-error-codes",
}
url = urls.get(provider)
super().__init__(f"{error_name}\n" + (extra_message if extra_message else "") + f"\nVisit {url} for more details")
Expand Down Expand Up @@ -137,6 +139,14 @@ def run_compute(self) -> None:
""" Runs confidential computing."""
pass

def get_uid_instance_id(self, identifier, version):
logging.info(f"Generating UID Indentifier for {identifier} running version: {version}")
identifier_hash = hashlib.sha256(identifier.encode()).hexdigest()[:6]
cloud_provider = self.__class__.__name__.lower()
uid_instance_id = f"{cloud_provider}-{identifier_hash}-{version}"
logging.info(f"Generated and using uid_instance_id: {uid_instance_id}")
return uid_instance_id

@staticmethod
def run_command(command, separate_process=False, stdout=None, stderr=None):
logging.info(f"Running command: {' '.join(command)}")
Expand All @@ -148,4 +158,4 @@ def run_command(command, separate_process=False, stdout=None, stderr=None):

except Exception as e:
logging.error(f"Failed to run command: {e}", exc_info=True)
raise RuntimeError (f"Failed to start {' '.join(command)} ")
raise RuntimeError (f"Failed to start {' '.join(command)} ")
3 changes: 2 additions & 1 deletion scripts/gcp-oidc/conf/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
"failure_shutdown_wait_hours": 120,
"sharing_token_expiry_seconds": 2592000,
"validate_service_links": false,
"operator_type": "private"
"operator_type": "private",
"uid_instance_id_prefix": "unknown"
}
38 changes: 35 additions & 3 deletions scripts/gcp-oidc/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,28 @@
from typing import Dict
import sys
import logging
import requests
import re
from google.cloud import secretmanager
from google.auth.exceptions import DefaultCredentialsError
from google.api_core.exceptions import PermissionDenied, NotFound
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from confidential_compute import ConfidentialCompute, ConfidentialComputeConfig, ConfigurationMissingError, OperatorKeyNotFoundError, OperatorKeyPermissionError, ConfidentialComputeStartupError
from confidential_compute import ConfidentialCompute, ConfigurationMissingError, OperatorKeyNotFoundError, OperatorKeyPermissionError, ConfidentialComputeStartupError

class GCPEntryPoint(ConfidentialCompute):

class AuxiliaryConfig:
GCP_METADATA: str = "169.254.169.254"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this hardcoded IP address refer to ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the instance metadata endpoint. It is static 169.254.169.254 and local to instance (link local IP range). AWS, GCP both provides this and it returns instance metadata (region, instance-id, image used etc)

GCP_HEADER: dict = {"Metadata-Flavor": "Google"}

@classmethod
def get_gcp_instance_id_url(cls) -> str:
return f"http://{cls.GCP_METADATA}/computeMetadata/v1/instance/id"

@classmethod
def get_gcp_image_url(cls) -> str:
return f"http://{cls.GCP_METADATA}/computeMetadata/v1/instance/image"

class GCP(ConfidentialCompute):

def __init__(self):
super().__init__()
Expand Down Expand Up @@ -42,6 +57,8 @@ def _set_confidential_config(self, secret_identifier=None) -> None:
except NotFound:
raise OperatorKeyNotFoundError(self.__class__.__name__, f"Secret Manager {os.getenv("API_TOKEN_SECRET_NAME")}")
self.configs["operator_key"] = secret_value
instance_id, version = self.__get_gcp_instance_info()
self.configs["uid_instance_id_prefix"] = self.get_uid_instance_id(identifier=instance_id, version=version)

def __populate_operator_config(self, destination):
target_config = f"/app/conf/{self.configs["environment"].lower()}-config.json"
Expand All @@ -61,6 +78,21 @@ def _validate_auxiliaries(self) -> None:
""" No Auxiliariy service required for GCP Confidential compute. """
pass

def __get_gcp_instance_info(self) -> tuple[str, str]:
"""Fetches the GCP instance ID, and image version."""
try:
response = requests.get(AuxiliaryConfig.get_gcp_instance_id_url(), headers=AuxiliaryConfig.GCP_HEADER, timeout=2)
response.raise_for_status()
instance_id = response.text.strip()
image_response = requests.get(AuxiliaryConfig.get_gcp_image_url(), headers=AuxiliaryConfig.GCP_HEADER, timeout=2)
image_response.raise_for_status()
image = image_response.text.strip()
match = re.search(r":(\d+\.\d+\.\d+)", image)
version = match.group(1) if match else "unknown"
return instance_id, version
except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch GCP instance info: {e}")

def run_compute(self) -> None:
self._set_confidential_config()
logging.info("Fetched configs")
Expand All @@ -85,7 +117,7 @@ def run_compute(self) -> None:

if __name__ == "__main__":
try:
gcp = GCPEntryPoint()
gcp = GCP()
gcp.run_compute()
except ConfidentialComputeStartupError as e:
logging.error(f"Failed starting up Confidential Compute. Please checks the logs for errors and retry {e}")
Expand Down
Loading