1+ #!/usr/bin/env python3
2+
3+ import json
4+ import os
5+ import time
6+ from typing import Dict
7+ import sys
8+ import shutil
9+ import requests
10+ import logging
11+
12+ sys .path .append (os .path .dirname (os .path .dirname (os .path .abspath (__file__ ))))
13+ from confidential_compute import ConfidentialCompute , MissingConfig , MissingInstanceProfile , AuxiliariesException , SecretAccessDenied , ApiTokenNotFound , ConfidentialComputeStartupException
14+ from azure .keyvault .secrets import SecretClient
15+ from azure .identity import DefaultAzureCredential , CredentialUnavailableError
16+ from azure .core .exceptions import ResourceNotFoundError , ClientAuthenticationError
17+
18+ class AzureEntryPoint (ConfidentialCompute ):
19+
20+ kv_name = os .getenv ("VAULT_NAME" )
21+ secret_name = os .getenv ("OPERATOR_KEY_SECRET_NAME" )
22+ env_name = os .getenv ("DEPLOYMENT_ENVIRONMENT" )
23+ jar_name = os .getenv ("JAR_NAME" , "default-jar-name" )
24+ jar_version = os .getenv ("JAR_VERSION" , "default-jar-version" )
25+
26+ FINAL_CONFIG = "/tmp/final-config.json"
27+
28+ def __init__ (self ):
29+ super ().__init__ ()
30+
31+ def __check_env_variables (self ):
32+ # Check essential env variables
33+ if AzureEntryPoint .kv_name is None :
34+ raise MissingConfig (self .__class__ .__name__ , ["VAULT_NAME" ])
35+ if AzureEntryPoint .secret_name is None :
36+ raise MissingConfig (self .__class__ .__name__ , ["OPERATOR_KEY_SECRET_NAME" ])
37+ if AzureEntryPoint .env_name is None :
38+ raise MissingConfig (self .__class__ .__name__ , ["DEPLOYMENT_ENVIRONMENT" ])
39+ logging .info ("Environment variables validation success" )
40+
41+ def __create_final_config (self ):
42+ TARGET_CONFIG = f"/app/conf/{ AzureEntryPoint .env_name } -uid2-config.json"
43+ if not os .path .isfile (TARGET_CONFIG ):
44+ logging .error (f"Unrecognized config { TARGET_CONFIG } " )
45+ sys .exit (1 )
46+
47+ logging .info (f"-- copying { TARGET_CONFIG } to { AzureEntryPoint .FINAL_CONFIG } " )
48+ try :
49+ shutil .copy (TARGET_CONFIG , AzureEntryPoint .FINAL_CONFIG )
50+ except IOError as e :
51+ logging .error (f"Failed to create { AzureEntryPoint .FINAL_CONFIG } with error: { e } " )
52+ sys .exit (1 )
53+
54+ CORE_BASE_URL = os .getenv ("CORE_BASE_URL" )
55+ OPTOUT_BASE_URL = os .getenv ("OPTOUT_BASE_URL" )
56+ if CORE_BASE_URL and OPTOUT_BASE_URL and AzureEntryPoint .env_name != 'prod' :
57+ logging .info (f"-- replacing URLs by { CORE_BASE_URL } and { OPTOUT_BASE_URL } " )
58+ with open (AzureEntryPoint .FINAL_CONFIG , "r" ) as file :
59+ config = file .read ()
60+
61+ config = config .replace ("https://core-integ.uidapi.com" , CORE_BASE_URL )
62+ config = config .replace ("https://optout-integ.uidapi.com" , OPTOUT_BASE_URL )
63+
64+ with open (AzureEntryPoint .FINAL_CONFIG , "w" ) as file :
65+ file .write (config )
66+
67+ with open (AzureEntryPoint .FINAL_CONFIG , "r" ) as file :
68+ logging .info (file .read ())
69+
70+ def __set_base_urls (self ):
71+ with open (AzureEntryPoint .FINAL_CONFIG , "r" ) as file :
72+ jdata = json .load (file )
73+ self .configs ["core_base_url" ] = jdata ["core_attest_url" ]
74+ self .configs ["optout_base_url" ] = jdata ["optout_api_uri" ]
75+
76+ def __set_api_token (self ):
77+ try :
78+ credential = DefaultAzureCredential ()
79+ kv_URL = f"https://{ AzureEntryPoint .kv_name } .vault.azure.net"
80+ secret_client = SecretClient (vault_url = kv_URL , credential = credential )
81+ secret = secret_client .get_secret (AzureEntryPoint .secret_name )
82+ # print(f"Secret Value: {secret.value}")
83+ self .configs ["api_token" ] = secret .value
84+
85+ except (CredentialUnavailableError , ClientAuthenticationError ) as auth_error :
86+ logging .error (f"Read operator key, authentication error: { auth_error } " )
87+ raise SecretAccessDenied (self .__class__ .__name__ , str (auth_error ))
88+ except ResourceNotFoundError as not_found_error :
89+ logging .error (f"Read operator key, secret not found: { AzureEntryPoint .secret_name } . Error: { not_found_error } " )
90+ raise ApiTokenNotFound (self .__class__ .__name__ , str (not_found_error ))
91+
92+
93+ def _set_confidential_config (self , secret_identifier : str = None ):
94+ self .configs ["skip_validations" ] = os .getenv ("SKIP_VALIDATIONS" , "false" ).lower () == "true"
95+ self .configs ["debug_mode" ] = os .getenv ("DEBUG_MODE" , "false" ).lower () == "true"
96+ self .configs ["environment" ] = AzureEntryPoint .env_name
97+
98+ # set self.configs["api_token"]
99+ self .__set_api_token ()
100+ # set base urls from final config file
101+ self .__set_base_urls ()
102+
103+ def __run_operator (self ):
104+
105+ # Start the operator
106+ os .environ ["azure_vault_name" ] = AzureEntryPoint .kv_name
107+ os .environ ["azure_secret_name" ] = AzureEntryPoint .secret_name
108+
109+ java_command = [
110+ "java" ,
111+ "-XX:MaxRAMPercentage=95" , "-XX:-UseCompressedOops" , "-XX:+PrintFlagsFinal" ,
112+ "-Djava.security.egd=file:/dev/./urandom" ,
113+ "-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory" ,
114+ "-Dlogback.configurationFile=/app/conf/logback.xml" ,
115+ f"-Dvertx-config-path={ AzureEntryPoint .FINAL_CONFIG } " ,
116+ "-jar" ,
117+ f"{ AzureEntryPoint .jar_name } -{ AzureEntryPoint .jar_version } .jar"
118+ ]
119+ logging .info ("-- starting java operator application" )
120+ self .run_command (java_command , separate_process = False )
121+
122+ def _validate_auxiliaries (self ):
123+ logging .info ("Waiting for sidecar ..." )
124+
125+ MAX_RETRIES = 15
126+ PING_URL = "http://169.254.169.254/ping"
127+ delay = 1
128+
129+ for attempt in range (1 , MAX_RETRIES + 1 ):
130+ try :
131+ response = requests .get (PING_URL , timeout = 5 )
132+ if response .status_code in [200 , 204 ]:
133+ logging .info ("Sidecar started successfully." )
134+ return
135+ else :
136+ logging .warning (
137+ f"Attempt { attempt } : Unexpected status code { response .status_code } . Response: { response .text } "
138+ )
139+ except Exception as e :
140+ logging .info (f"Attempt { attempt } : Error during request - { e } " )
141+
142+ if attempt == MAX_RETRIES :
143+ logging .error (
144+ f"Sidecar failed to start after { MAX_RETRIES } attempts. Exiting."
145+ )
146+ raise AuxiliariesException (self .__class__ .__name__ )
147+
148+ logging .info (f"Retrying in { delay } seconds... (Attempt { attempt } /{ MAX_RETRIES } )" )
149+ time .sleep (delay )
150+ delay += 1
151+
152+ def run_compute (self ) -> None :
153+ """Main execution flow for confidential compute."""
154+ self .__check_env_variables ()
155+ self .__create_final_config ()
156+ self ._set_confidential_config ()
157+ if not self .configs .get ("skip_validations" ):
158+ self .validate_configuration ()
159+ self ._setup_auxiliaries ()
160+ self .__run_operator ()
161+
162+ def _setup_auxiliaries (self ) -> None :
163+ """ setup auxiliary services are running."""
164+ pass
165+
166+ if __name__ == "__main__" :
167+
168+ logging .basicConfig (level = logging .INFO )
169+ logging .info ("Start AzureEntryPoint" )
170+ try :
171+ operator = AzureEntryPoint ()
172+ operator .run_compute ()
173+ except ConfidentialComputeStartupException as e :
174+ logging .error (f"Failed starting up Azure Confidential Compute. Please checks the logs for errors and retry { e } " , exc_info = True )
175+ except Exception as e :
176+ logging .error (f"Unexpected failure while starting up Azure Confidential Compute. Please contact UID support team with this log { e } " , exc_info = True )
0 commit comments